diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 514435a23a..41ee9800bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ permissions: contents: read concurrency: - group: build-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr || github.ref }} + group: build-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr || (github.event_name == 'push' && github.sha) || github.ref }} cancel-in-progress: ${{ github.event_name != 'push' }} env: @@ -56,6 +56,7 @@ jobs: relinker: ${{ steps.gate.outputs.relinker }} register-context: ${{ steps.gate.outputs.register-context }} write-watch: ${{ steps.gate.outputs.write-watch }} + edge-watch: ${{ steps.gate.outputs.edge-watch }} mingw-host-tests: ${{ steps.gate.outputs.mingw-host-tests }} full: ${{ steps.gate.outputs.full }} relinker-hosts: ${{ steps.gate.outputs.relinker-hosts }} @@ -139,6 +140,8 @@ jobs: registerContext: edits('register-context') || touches('core/libs/tests/register_context/', 'core/libs/tests/RegisterContext.cpp', libc), writeWatch: edits('write-watch') || touches('core/libs/tests/write_watch/', 'core/libs/tests/WriteWatch.cpp', libc), + edgeWatch: edits('edge-watch') || touches('core/libs/tests/edge_watch/', 'core/libs/tests/EdgeWatch.cpp', + 'core/libs/prx/libkernel/Equeue/src/EdgeWatch.cpp', 'core/libs/prx/libkernel/Equeue/include/EdgeWatch.hpp', libc), mingwHostTests: edits('eligibility') || edits('windows') || touches( 'core/CMakeLists.txt', 'core/libs/', 'cmake/', '3rdparty/', '.gitmodules', 'tools/build_mingw_host_only_tests.py', 'tools/build_changed_guest_tests.py', 'tools/configure_seed.py', 'tools/select_tests.py', @@ -178,6 +181,7 @@ jobs: core.setOutput('relinker', run.relinker); core.setOutput('register-context', run.registerContext); core.setOutput('write-watch', run.writeWatch); + core.setOutput('edge-watch', run.edgeWatch); core.setOutput('mingw-host-tests', false); core.setOutput('full', true); core.setOutput('relinker-hosts', relinkerHosts(true, true)); @@ -268,6 +272,7 @@ jobs: core.setOutput('relinker', relinker); core.setOutput('register-context', registerContext); core.setOutput('write-watch', writeWatch); + core.setOutput('edge-watch', run.edgeWatch); core.setOutput('mingw-host-tests', !manual && run.mingwHostTests); core.setOutput('full', full); core.setOutput('relinker-hosts', relinkerHosts(full, linux)); @@ -328,10 +333,10 @@ jobs: - if: runner.os == 'Windows' run: echo 'C:\winlibs\mingw64\bin' >> "$GITHUB_PATH" - if: ${{ matrix.architectures == 'x86_64' }} - run: sudo softwareupdate --install-rosetta --agree-to-license + run: arch -x86_64 /usr/bin/true || sudo softwareupdate --install-rosetta --agree-to-license - run: cmake -S . -B build-relinker -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=${{ matrix.compiler }} -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON -DCMAKE_OSX_ARCHITECTURES=${{ matrix.architectures }} - run: cmake --build build-relinker --parallel 3 - - run: ctest --test-dir build-relinker --output-on-failure --timeout 120 --parallel 4 + - run: ctest --test-dir build-relinker --output-on-failure --timeout 120 --parallel 4 --no-tests=error relinker-asan: needs: eligibility @@ -648,7 +653,7 @@ jobs: uses: actions/cache/restore@v6 with: path: build/externals - key: ffmpeg-${{ runner.os }}-${{ steps.submodules.outputs.ffmpeg }} + key: ffmpeg-${{ runner.os }}-winlibs-${{ env.WINLIBS_ZIP }}-${{ steps.submodules.outputs.ffmpeg }} - id: seed-cache uses: actions/cache/restore@v6 with: @@ -707,7 +712,7 @@ jobs: - name: Build and run changed guest core/libs tests if: github.event_name == 'pull_request' shell: cmd - run: python tools/build_changed_guest_tests.py build changed.txt + run: python tools/build_changed_guest_tests.py build changed.txt --output-log ctest.log - id: libs if: github.event_name != 'pull_request' shell: cmd @@ -736,3 +741,36 @@ jobs: 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"] }' ctest.log + edge-watch: + needs: eligibility + if: ${{ needs.eligibility.outputs.edge-watch == 'true' }} + strategy: + fail-fast: false + matrix: + os: [macos-15-intel, macos-15] + runs-on: ${{ matrix.os }} + timeout-minutes: 5 + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ needs.eligibility.outputs.sha }} + persist-credentials: false + - name: Merge the pull request into main + if: ${{ needs.eligibility.outputs.head != '' }} + shell: bash + env: + PR_NUMBER: ${{ needs.eligibility.outputs.pr }} + PR_HEAD: ${{ needs.eligibility.outputs.head }} + run: | + read -ra numbers <<< "$PR_NUMBER" + read -ra heads <<< "$PR_HEAD" + refspecs=() + for number in "${numbers[@]}"; do refspecs+=("+refs/pull/$number/head:refs/remotes/pull/$number"); done + git fetch --no-tags --unshallow origin "${refspecs[@]}" + for i in "${!numbers[@]}"; do + if [ "$(git rev-parse "refs/remotes/pull/${numbers[i]}")" != "${heads[i]}" ]; then echo "::error::Pull request #${numbers[i]} has newer commits; start a new build"; exit 1; fi + if ! git -c user.name=ci -c user.email=ci@localhost merge --no-ff --no-edit "refs/remotes/pull/${numbers[i]}"; then echo "::error::Pull request #${numbers[i]} does not merge cleanly into main and the pull requests before it; rebase it first"; exit 1; fi + done + - run: cmake -S core/libs/tests/edge_watch -B build/edge-watch -DCMAKE_BUILD_TYPE=Release + - run: cmake --build build/edge-watch --parallel + - run: ctest --test-dir build/edge-watch --output-on-failure --repeat until-fail:3 --no-tests=error diff --git a/.github/workflows/emscripten-libraries.yml b/.github/workflows/emscripten-libraries.yml index 790bf599a3..2c21a1db79 100644 --- a/.github/workflows/emscripten-libraries.yml +++ b/.github/workflows/emscripten-libraries.yml @@ -26,7 +26,7 @@ jobs: run: | cmake -S core/libs/tests/emscripten -B build-native -DCMAKE_BUILD_TYPE=Release cmake --build build-native --parallel 2 - ctest --test-dir build-native --output-on-failure + ctest --test-dir build-native --output-on-failure --no-tests=error - uses: actions/setup-node@v6 with: node-version: '26' @@ -40,4 +40,4 @@ jobs: source "$RUNNER_TEMP/emsdk/emsdk_env.sh" emcmake cmake -S core/libs/tests/emscripten -B build-portable -DCMAKE_BUILD_TYPE=Release -DANYPS5_NODE="$ANYPS5_TEST_NODE" cmake --build build-portable --parallel 2 - ctest --test-dir build-portable --output-on-failure + ctest --test-dir build-portable --output-on-failure --no-tests=error diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index 299adbcdb8..88af11b491 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -31,6 +31,7 @@ jobs: force_label 'unblocks title' 0e8a16 'Names a title ID on the Unblocks line' force_label perf fef2c0 'Performance change' force_label 'perf numbers missing' e99695 'perf pull request without a before and after summary line' + force_label 'review queue' 1d76db 'Next in the maintainer review queue: checks pass, no open change request, no conflict' label relinker 5319e7 'core/relinker' label libc fbca04 'libc' diff --git a/.github/workflows/pages-progress.yml b/.github/workflows/pages-progress.yml index 5aa5f304a6..85325ba27d 100644 --- a/.github/workflows/pages-progress.yml +++ b/.github/workflows/pages-progress.yml @@ -5,7 +5,11 @@ on: branches: [main] paths: - core/libs/prx/** - - core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp + - core/shader/recompiler/** + - tools/progress.py + - tools/shader_scan.py + - tools/rdna2_isa_forms.txt + - tools/rdna_isa.txt permissions: contents: read @@ -30,7 +34,7 @@ jobs: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} run: | - python3 tools/progress.py "$RUNNER_TEMP/progress" + python3 tools/progress.py --require-shader-source "$RUNNER_TEMP/progress" site=$(gh api "repos/$GH_REPO/pages" --jq .html_url) if curl -fsSL "${site%/}/progress.json?$GITHUB_SHA" -o "$RUNNER_TEMP/deployed.json" && cmp -s "$RUNNER_TEMP/deployed.json" "$RUNNER_TEMP/progress/progress.json"; then diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index bf8d6a0313..b49d7f68df 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -12,6 +12,8 @@ on: - tools/package_release.py - tools/nid_names.py - tools/progress.py + - tools/shader_scan.py + - tools/rdna2_isa_forms.txt - tools/progress_history.py - tools/rdna_isa.txt - tools/test_web.py @@ -47,7 +49,7 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} run: python3 tools/build_web.py --build build-wasm --release - - run: python3 tools/progress.py build-wasm/web + - run: python3 tools/progress.py --require-shader-source build-wasm/web - run: python3 tools/progress_history.py build-wasm/web - uses: actions/upload-pages-artifact@v5 with: diff --git a/.github/workflows/pr-ready.yml b/.github/workflows/pr-ready.yml new file mode 100644 index 0000000000..3344931f57 --- /dev/null +++ b/.github/workflows/pr-ready.yml @@ -0,0 +1,28 @@ +name: PR review queue +on: + schedule: + - cron: '50 * * * *' + workflow_dispatch: +permissions: {} +concurrency: + group: pr-review-queue +jobs: + queue: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + pull-requests: write + issues: write + steps: + - uses: actions/checkout@v7 + - env: + GH_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + QUEUE_ISSUE: ${{ vars.REVIEW_QUEUE_ISSUE }} + run: | + if [ -n "$QUEUE_ISSUE" ]; then + python3 -I tools/pr_ready.py --issue "$QUEUE_ISSUE" + else + python3 -I tools/pr_ready.py + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a10dd29ebc..80ae611a4b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -175,7 +175,42 @@ jobs: run: | gh api --method POST "repos/$GH_REPO/releases/generate-notes" -f tag_name="$RELEASE_TAG" > "$RUNNER_TEMP/release-notes.json" python tools/prepare_release_notes.py --input "$RUNNER_TEMP/release-notes.json" --body "$RUNNER_TEMP/release-body.md" --full dist/release-notes.md - - name: Create draft and upload files - run: gh release create "$RELEASE_TAG" dist/* --verify-tag --draft --title "$RELEASE_TAG" --notes-file "$RUNNER_TEMP/release-body.md" + - name: Create or refresh draft and upload files + shell: bash + run: | + release_marker="" + printf '\n%s\n' "$release_marker" >> "$RUNNER_TEMP/release-body.md" + release_state="$( + gh release list --limit 10000 --json tagName,isDraft | + python -c 'import json, os, sys; matches = [release for release in json.load(sys.stdin) if release["tagName"] == os.environ["RELEASE_TAG"]]; print("missing" if not matches else ("draft" if matches[0]["isDraft"] else "published"))' + )" + case "$release_state" in + missing) + gh release create "$RELEASE_TAG" dist/* --verify-tag --draft --title "$RELEASE_TAG" --notes-file "$RUNNER_TEMP/release-body.md" + ;; + draft) + release_body="$(gh release view "$RELEASE_TAG" --json body --jq .body)" + if [[ "$release_body" != *"$release_marker"* ]]; then + echo "::error::Draft release $RELEASE_TAG was not created by this workflow for $GITHUB_SHA" + exit 1 + fi + gh release edit "$RELEASE_TAG" --draft=true --title "$RELEASE_TAG" --notes-file "$RUNNER_TEMP/release-body.md" + gh release upload "$RELEASE_TAG" dist/* --clobber + ;; + published) + echo "::error::Release $RELEASE_TAG is already published" + exit 1 + ;; + *) + echo "::error::Unexpected release state for $RELEASE_TAG: $release_state" + exit 1 + ;; + esac + for file in dist/*; do basename "$file"; done | LC_ALL=C sort > "$RUNNER_TEMP/expected-release-assets" + gh release view "$RELEASE_TAG" --json assets --jq '.assets[].name' | LC_ALL=C sort > "$RUNNER_TEMP/actual-release-assets" + if ! diff -u "$RUNNER_TEMP/expected-release-assets" "$RUNNER_TEMP/actual-release-assets"; then + echo "::error::Draft release assets do not exactly match the packaged files" + exit 1 + fi - name: Publish complete release run: gh release edit "$RELEASE_TAG" --draft=false diff --git a/.github/workflows/stale-conflicts.yml b/.github/workflows/stale-conflicts.yml index 00150e1b70..c7fbbb1b04 100644 --- a/.github/workflows/stale-conflicts.yml +++ b/.github/workflows/stale-conflicts.yml @@ -16,6 +16,7 @@ jobs: steps: - env: GH_TOKEN: ${{ github.token }} + shell: bash run: | gh pr list -R "$GITHUB_REPOSITORY" --label stale -L 1000 --json number,labels \ --jq '.[] | select(all(.labels[]; .name != "needs rebase")) | .number' | diff --git a/.github/workflows/web-release-tools.yml b/.github/workflows/web-release-tools.yml new file mode 100644 index 0000000000..147868a660 --- /dev/null +++ b/.github/workflows/web-release-tools.yml @@ -0,0 +1,38 @@ +name: Web release tools + +on: + pull_request: + paths: + - .github/workflows/web-release-tools.yml + - demo/web/** + - tools/build_web.py + - tools/nid_names.py + - tools/package_headers.py + - tools/package_release.py + - tools/test_web.py + - tools/tests/test_build_web.py + - tools/tests/test_web_release.py + - core/relinker/relinker/tests/guest_import_fixtures.py + - core/relinker/relinker/tests/test_guest_intel_trampolines.py + - core/relinker/relinker/tests/test_guest_module_directories.py + - core/relinker/relinker/tests/test_linux_load_alignment.py + +permissions: + contents: read + +concurrency: + group: web-release-tools-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + release-tools: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Test web library assets + run: python3 tools/tests/test_build_web.py + - name: Test web release packaging + run: python3 tools/tests/test_web_release.py diff --git a/.github/workflows/web-tests.yml b/.github/workflows/web-tests.yml index bf158fd625..7a73e61110 100644 --- a/.github/workflows/web-tests.yml +++ b/.github/workflows/web-tests.yml @@ -35,6 +35,8 @@ jobs: run: node demo/web/tests/archive-windows.test.mjs demo/web - name: Test archive reserved names run: node demo/web/tests/archive-reserved.test.mjs demo/web + - name: Test library manifest validation + run: node --test demo/web/tests/worker-manifest.test.mjs - name: Check browser module syntax run: | node --check demo/web/app.mjs diff --git a/.github/workflows/windows-msys2.yml b/.github/workflows/windows-msys2.yml index def52370d2..554adb7ee9 100644 --- a/.github/workflows/windows-msys2.yml +++ b/.github/workflows/windows-msys2.yml @@ -81,7 +81,7 @@ jobs: uses: actions/cache/restore@v6 with: path: build/externals - key: ffmpeg-${{ runner.os }}-${{ steps.submodules.outputs.ffmpeg }} + key: ffmpeg-${{ runner.os }}-msys2-${{ 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' @@ -99,7 +99,7 @@ jobs: 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: ctest --output-on-failure --timeout 120 --parallel 4 --output-log ctest.log --output-junit ctest.xml --no-tests=error - run: python tools/check_skips.py build/ctest.xml --job windows - name: Report tests slower than 30 s if: ${{ !cancelled() }} diff --git a/.gitmodules b/.gitmodules index dd956d0799..e811a81b6c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,9 @@ [submodule "3rdparty/SDL2"] path = 3rdparty/SDL2 url = https://github.com/libsdl-org/SDL.git +[submodule "3rdparty/libressl-openbsd"] + path = 3rdparty/libressl-openbsd + url = https://github.com/libressl/openbsd.git [submodule "3rdparty/Vulkan-Headers"] path = 3rdparty/Vulkan-Headers url = https://github.com/KhronosGroup/Vulkan-Headers.git diff --git a/3rdparty/libressl-openbsd b/3rdparty/libressl-openbsd new file mode 160000 index 0000000000..078b3d1df8 --- /dev/null +++ b/3rdparty/libressl-openbsd @@ -0,0 +1 @@ +Subproject commit 078b3d1df8ca2011c1605cd40418c58fa0f5b143 diff --git a/AGENTS.md b/AGENTS.md index 7ded821d28..53162a4f7c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ AnyPS5 relinks PS5 executables into native Linux or Windows programs that run ag - New third-party code is a submodule at `3rdparty/`: add its `.gitmodules` entry (`git submodule add 3rdparty/`) and build it from source with CMake. Never `find_package` or copy sources in. - Known gaps go in `docs/dev/debt/.md`; notes, reports and logs go in the pull request. Add no other files. - Commit subjects and PR titles are Conventional Commits. -- PR body: copy [the template](.github/pull_request_template.md), keep every line, tick every box, and fill `What`, `Tested`, `Depends on` (#N or none), `Unblocks` (title ID and discussion, or none), `GPUs tested` (AGC, shader recompiler, VideoOut; otherwise n/a) and `AI-assisted` (yes or no). +- PR body: copy [the template](.github/pull_request_template.md), keep every line, tick every box, and fill `What`, `Tested`, `Depends on` (#N or none), `Fixes` (Fixes #N or none), `Unblocks` (title ID and discussion, or none), `GPUs tested` (AGC, shader recompiler, VideoOut; otherwise n/a) and `AI-assisted` (yes or no). - Before pushing run `python3 tools/check_conventions.py --base origin/main`; before opening run `python3 tools/pr_overlap.py --branch `. - Build and test: [BUILD](docs/dev/BUILD.md); a relinker-only change builds with `-DANYPS5_RELINKER_ONLY=ON`. - A speed claim shows `tools/trace_digest.py` summaries of `main` and of the change from the same machine and scene. diff --git a/CMakeLists.txt b/CMakeLists.txt index 204b976daa..f135ac95ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,6 +220,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap.py) add_test(NAME pr_labels COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_labels.py) + add_test(NAME pr_ready COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_ready.py) add_test(NAME progress_history COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress_history.py ProgressHistoryTests) add_test(NAME progress_history_output COMMAND ${Python3_EXECUTABLE} @@ -227,6 +229,8 @@ if(BUILD_TESTING) set_tests_properties(progress_history_output PROPERTIES TIMEOUT 20) add_test(NAME progress_report COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress.py) + add_test(NAME shader_scan COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_shader_scan.py) add_test(NAME progress_report_inputs COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_progress_report_inputs.py) add_test(NAME tools_compdb_inventory COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_compile_database_inventory.py) @@ -254,6 +258,8 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_unwrap_self.py) add_test(NAME audit_dump COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_audit_dump.py) + add_test(NAME ffmpeg_cache_keys COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_ffmpeg_cache_keys.py) foreach(shard RANGE 0 11) if(shard EQUAL 0) set(conventions_test_name conventions_function_bodies) @@ -268,7 +274,18 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_performance_csv.py) set_tests_properties(performance_csv PROPERTIES TIMEOUT 20) add_test(NAME conventions_wrapped_declarations COMMAND ${Python3_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_wrapped_declarations.py) + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_wrapped_declarations.py WrappedDeclarationTests) + set_tests_properties(conventions_wrapped_declarations PROPERTIES TIMEOUT 30) + set(conventions_wrapped_declarations_source "${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_wrapped_declarations.py") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${conventions_wrapped_declarations_source}") + file(STRINGS "${conventions_wrapped_declarations_source}" conventions_wrapped_declarations_classes REGEX "^class [A-Za-z0-9_]*Tests\\(") + foreach(test_class IN LISTS conventions_wrapped_declarations_classes) + string(REGEX REPLACE "^class ([A-Za-z0-9_]+).*" "\\1" test_class "${test_class}") + if(NOT test_class STREQUAL "WrappedDeclarationTests") + add_test(NAME conventions_wrapped_declarations_${test_class} COMMAND ${Python3_EXECUTABLE} "${conventions_wrapped_declarations_source}" ${test_class}) + set_tests_properties(conventions_wrapped_declarations_${test_class} PROPERTIES TIMEOUT 30) + endif() + endforeach() add_test(NAME conventions_ci_gate COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_ci_gate.py) add_test(NAME preview_pad_timeline COMMAND ${Python3_EXECUTABLE} @@ -276,8 +293,12 @@ if(BUILD_TESTING) set_tests_properties(preview_pad_timeline PROPERTIES TIMEOUT 20) add_test(NAME conventions_git_errors COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_git.py) + add_test(NAME tools_base_ref COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_base_ref.py) add_test(NAME tools_configure_diff COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_compare_configure_checks.py) set_tests_properties(tools_configure_diff PROPERTIES TIMEOUT 20) + add_test(NAME ctest_no_tests_flag COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_ctest_no_tests_flag.py) + set_tests_properties(ctest_no_tests_flag PROPERTIES TIMEOUT 20) add_test(NAME conventions_doc_links COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_doc_links.py) add_test(NAME conventions_unknown_nids COMMAND ${Python3_EXECUTABLE} @@ -286,10 +307,18 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_conventions_debt_length.py) add_test(NAME test_registration COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_test_registration.py) + add_test(NAME unbuilt_exports COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_unbuilt_exports.py) add_test(NAME test_script_registration COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_test_registration.py --scripts) + add_test(NAME tools_sanitizer_json COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_sanitizer_json.py) + set_tests_properties(tools_sanitizer_json PROPERTIES TIMEOUT 20) add_test(NAME build_eligibility COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_build_eligibility.py) + add_test(NAME tools_release_notes_check COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_release_notes_check.py) + set_tests_properties(tools_release_notes_check PROPERTIES TIMEOUT 20) add_test(NAME check_skips_ctest COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips_ctest.py) set_tests_properties(check_skips_ctest PROPERTIES TIMEOUT 30) @@ -299,8 +328,12 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_skips.py) add_test(NAME sanitizer_report COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_sanitizer_report.py) + add_test(NAME relinker_fuzz_fixture_warnings COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_relinker_fuzz_fixture_warnings.py) add_test(NAME select_tests COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_select_tests.py) + add_test(NAME relinker_test_step COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_relinker_test_step.py) add_test(NAME tools_self_inspect COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_inspect_self.py) set_tests_properties(tools_self_inspect PROPERTIES TIMEOUT 20) add_test(NAME check_main_sync COMMAND ${Python3_EXECUTABLE} @@ -364,6 +397,14 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_wrapped_exports.py) add_test(NAME pr_overlap_own_branch COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_own_branch.py) + add_test(NAME main_sync_renames COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_main_sync_renames.py RenamedPathsTests.test_feature_rename_keeps_the_original_path) + set_tests_properties(main_sync_renames PROPERTIES TIMEOUT 20) + foreach(test_case IN ITEMS test_feature_rename_with_detection_disabled test_main_rename_keeps_the_original_path test_main_rename_with_detection_disabled) + add_test(NAME main_sync_renames_${test_case} COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_main_sync_renames.py RenamedPathsTests.${test_case}) + set_tests_properties(main_sync_renames_${test_case} PROPERTIES TIMEOUT 20) + endforeach() add_test(NAME pr_overlap_errors COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pr_overlap_errors.py) add_test(NAME pr_overlap_branch_scan COMMAND ${Python3_EXECUTABLE} @@ -389,6 +430,8 @@ if(BUILD_TESTING) add_test(NAME pad_input COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_pad_input.py) set_tests_properties(pad_input PROPERTIES TIMEOUT 20) + add_test(NAME stale_conflicts_workflow COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_stale_conflicts_workflow.py) 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) @@ -442,10 +485,16 @@ if(BUILD_TESTING) "${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 tools_annotation_symbols_json COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_annotation_symbols_json.py) + set_tests_properties(tools_annotation_symbols_json PROPERTIES TIMEOUT 20) 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 ab_split_csv COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_ab_split_csv.py) + set_tests_properties(ab_split_csv PROPERTIES TIMEOUT 20) 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} @@ -473,6 +522,26 @@ if(BUILD_TESTING) endif() endif() endforeach() + set(unwrap_self_test_source "${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_unwrap_self.py") + set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${unwrap_self_test_source}") + file(STRINGS "${unwrap_self_test_source}" unwrap_self_cases REGEX "^class |^ def test_") + set(unwrap_self_first TRUE) + foreach(test_case IN LISTS unwrap_self_cases) + if(test_case MATCHES "^class ([A-Za-z0-9_]+)") + set(unwrap_self_class "${CMAKE_MATCH_1}") + else() + string(REGEX REPLACE "^ def (test_[A-Za-z0-9_]+).*" "\\1" test_case "${test_case}") + if(unwrap_self_first) + set_tests_properties(unwrap_self PROPERTIES + ENVIRONMENT "UNWRAP_SELF_TEST=${unwrap_self_class}.${test_case}" TIMEOUT 30) + set(unwrap_self_first FALSE) + else() + add_test(NAME unwrap_self_${unwrap_self_class}_${test_case} COMMAND ${Python3_EXECUTABLE} + "${unwrap_self_test_source}" "${unwrap_self_class}.${test_case}") + set_tests_properties(unwrap_self_${unwrap_self_class}_${test_case} PROPERTIES TIMEOUT 30) + endif() + endif() + endforeach() add_test(NAME tools_headers_manifest COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/tests/test_public_headers_manifest.py) set_tests_properties(tools_headers_manifest PROPERTIES TIMEOUT 20) add_test(NAME conventions_pr_template COMMAND ${Python3_EXECUTABLE} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b2b8e1a79..43024c1708 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,16 +40,16 @@ For pull requests, full Linux and Windows builds and tests run only when a colla gh workflow run build.yml --repo boykopovar/AnyPS5 --ref main --field pr=1234 ``` -This manual run also builds and tests the relinker on Linux, Windows and macOS when the pull request changes a file the relinker-only build uses, regardless of the author's previous contributions. The relinker matrix also runs automatically when a pull request targeting `main` is opened, updated or reopened, if its author already has a commit in `main` and it changes a file the relinker-only build uses; full builds never run automatically for pull requests. Every job that runs automatically for pull requests runs only when the pull request changes its files: a `paths` filter on the `pull_request` trigger, or `needs: eligibility` with an `if` on the files output it sets in `build.yml`. The Conventions check fails a workflow change that adds a job without one. All build jobs build the selected pull request merged into the current `main` (GitHub's merge commit), so a pull request that no longer merges cleanly must be rebased first. New commits require another manual run for full builds. +This manual run also builds and tests the relinker on Linux, Windows and macOS when the pull request changes a file the relinker-only build uses, regardless of the author's previous contributions. The relinker matrix also runs automatically when a pull request targeting `main` is opened, updated or reopened, if its author already has a commit or a merged pull request in `main` and it changes a file the relinker-only build uses; full builds never run automatically for pull requests. Every job that runs automatically for pull requests runs only when the pull request changes its files: a `paths` filter on the `pull_request` trigger, or `needs: eligibility` with an `if` on the files output it sets in `build.yml`. The Conventions check fails a workflow change that adds a job without one. All build jobs check out the current `main` and merge the head of each selected pull request into it with `git merge --no-ff`, so a pull request that no longer merges cleanly must be rebased first. New commits require another manual run for full builds. 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/`. With `--field linux=false`, the manual run skips the full Linux build and adds Linux to the relinker matrix, if the pull requests change a file the relinker-only build uses. With `--field select=true`, the full builds build and run only the tests whose build inputs the pull requests change; push builds of `main` always run every test. -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: +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; without `--base` it compares with `upstream/main`, then `origin/main`, then `main`, so a fork whose `origin/main` is behind the project is not checked against that old state: ``` -python3 tools/check_conventions.py --base origin/main +python3 tools/check_conventions.py ``` ## Branches and pull requests @@ -62,6 +62,7 @@ The [pull request template](.github/pull_request_template.md) is the checklist f - Keep the branch up to date with `main` and resolve conflicts yourself; rebasing and force-pushing is fine. - An AGC test executable and its tests are registered in `core/libs/prx/libSceAgcDriver/tests/cmake/.cmake`; the tests of an execution test in `tests/execution/` go in the fragment of its `agc_driver__tests` executable. `CMakeLists.txt` includes the fragments in sorted order, so a fragment uses nothing another fragment defines. A pull request whose change to `libSceAgcDriver/CMakeLists.txt` conflicts after the split rebases and runs `python3 tools/agc_test_split.py resolve` during the stopped rebase, which moves its registrations to their fragments and leaves conflict markers where `main` changed the same lines. - A pull request that conflicts with `main` and has no activity for 7 days is labeled `stale` with a reminder. It is not closed. +- An hourly job gives the `review queue` label to the oldest 30 pull requests that need no more work from their author, `unblocks title` ones first, and lists them in the review queue issue. A pull request needs more work while it is a draft, conflicts with `main`, has a `perf numbers missing` or `open PR limit` label, has a failing or unfinished check other than Intake, Labels, Eligibility and the progress report, or has a change request whose reviewer was not asked again; pushing alone does not answer a change request. `python3 tools/pr_ready.py --explain ` prints what blocks a pull request. - If a pull request needs another one first, say so on the checklist line (`Depends on: #N`; `none` otherwise). - If a pull request fixes a reported issue, write `Fixes #N` on its checklist line (`none` otherwise). GitHub closes the issue on merge, and the `fix error` label is added within the hour. - If a pull request fixes a stop or a wrong result seen on a title, name it on the checklist line with the title ID and its [Game bring-up](https://github.com/boykopovar/AnyPS5/discussions/categories/game-bring-up) discussion (`Unblocks: PPSA01325 #3232`; `none` otherwise). Reviewers review these first. Such a pull request gets the `unblocks title` label. @@ -69,7 +70,7 @@ The [pull request template](.github/pull_request_template.md) is the checklist f - Fixes a title needs go to `main` as pull requests. A Game bring-up discussion lists the open pull requests and local changes the title still needs; update the list when one is merged, closed or replaced. - The PR overlap comment lists open pull requests that change the same lines or exports; agree with their authors which one goes first and declare it with `Depends on`. - Run the tests before opening the pull request and describe what was tested (OS, title or homebrew). -- A pull request that claims a change in speed shows the summary lines of `main` and of the change, from the same machine and scene, and attaches the traces, captured as the [Performance report](.github/ISSUE_TEMPLATE/performance.yml) issue form describes. A slow scene is reported with the same form. A pull request titled `perf(...)` or `perf:` is labeled `perf`, and `perf numbers missing` until its description holds two valid summary lines with different commits. +- A pull request that claims a change in speed shows the summary lines of `main` and of the change, from the same machine and scene, and attaches the traces, captured as the [Performance report](.github/ISSUE_TEMPLATE/performance.yml) issue form describes. A slow scene is reported with the same form. A pull request titled `perf(...)` or `perf:` is labeled `perf`, and `perf numbers missing` until its description holds two valid summary lines with different commits. A `perf(tools)`, `perf(ci)` or `perf(build)` pull request has no title to capture, so it is labeled `perf` only and reports its own before and after timings. - Changes to the AGC driver, the shader recompiler or VideoOut run the same focused tests on `main` and the change with `VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation`, `ANYPS5_NO_SHADER_CACHE=1` and `ANYPS5_REQUIRE_VULKAN=1` using `ctest -V` from the same machine and GPU, confirm neither log reports `was not found but was requested by env var VK_INSTANCE_LAYERS`, confirm the change introduces no new `VUID-` validation errors compared with `main`, and name each GPU the GPU tests ran on (vendor, model, driver, OS) on the `GPUs tested` checklist line. CI runs these tests only on lavapipe, and hardware drivers fail where lavapipe and other vendors pass, so a reviewer can ask for a run on another vendor before merging. - Investigation notes, reports, screenshots and logs go in the pull request, not in the repository. Images for documentation go in the [gist](https://gist.github.com/boykopovar/0e53f2e1426f29ecd41e3b51540b8a90) comments. - Say whether the change was written with AI assistance. The author of the pull request is responsible for every line of it. diff --git a/README.md b/README.md index 7c0bad8246..5c7d958438 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -**English** | [Português (Brasil)](docs/user/README.pt-BR.md) | [Español](docs/user/README.es.md) | [简体中文](docs/user/README.zh-CN.md) | [Русский](docs/user/README.ru.md) +**English** | [Português (Brasil)](docs/user/README.pt-BR.md) | [Español](docs/user/README.es.md) | [Italiano](docs/user/README.it.md) | [简体中文](docs/user/README.zh-CN.md) | [Русский](docs/user/README.ru.md) # About @@ -16,7 +16,7 @@ Includes a [relinker](core/relinker) that converts executable to the target syst ## Status -[![libraries](https://boykopovar.github.io/AnyPS5/badge-libraries.svg)](https://boykopovar.github.io/AnyPS5/progress.html) [![shaders](https://boykopovar.github.io/AnyPS5/badge-shaders.svg)](https://boykopovar.github.io/AnyPS5/progress.html) +[![libraries](https://boykopovar.github.io/AnyPS5/badge-libraries.svg)](https://boykopovar.github.io/AnyPS5/progress.html) [![shader forms decoded, counted from the code](https://boykopovar.github.io/AnyPS5/badge-shaders.svg)](https://boykopovar.github.io/AnyPS5/progress.html) [![implemented in 7 days](https://boykopovar.github.io/AnyPS5/badge-week.svg)](https://boykopovar.github.io/AnyPS5/history.html) [![progress map](https://boykopovar.github.io/AnyPS5/progress.svg)](https://boykopovar.github.io/AnyPS5/progress.html) diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index c9b739fb83..7230150c09 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -160,6 +160,9 @@ set(PATCHED_OUTPUTS ${LIBC_PATCHED}) foreach(LIB_DIR ${LIBS_DIRS}) if(IS_DIRECTORY ${LIB_DIR}) get_filename_component(LIB_NAME ${LIB_DIR} NAME) + if((ANYPS5_ENABLE_ASAN OR ANYPS5_ENABLE_TSAN) AND LIB_NAME STREQUAL "libSceAddressSanitizer") + continue() + endif() if(EXISTS ${LIB_DIR}/CMakeLists.txt) set(LIBS_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) @@ -182,7 +185,7 @@ foreach(LIB_DIR ${LIBS_DIRS}) target_link_options(${LIB_NAME} PRIVATE LINKER:-z,now) endif() if(WIN32 AND LIB_NAME STREQUAL "libSceNet") - target_link_libraries(${LIB_NAME} PRIVATE ws2_32) + target_link_libraries(${LIB_NAME} PRIVATE ws2_32 iphlpapi) target_compile_definitions(${LIB_NAME} PRIVATE _WIN32_WINNT=0x0602) elseif(WIN32 AND LIB_NAME STREQUAL "libSceNetCtl") target_link_libraries(${LIB_NAME} PRIVATE ws2_32 iphlpapi) @@ -295,7 +298,12 @@ elseif(TARGET mimalloc) list(APPEND PATCHED_OUTPUTS ${PATCHED_DIR}/libmimalloc.dylib) endif() +include(prx/libSceFreeType/BuildInfo.cmake) + add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS}) +include(prx/libSceLibreSsl/ErrorQueue.cmake) +include(prx/libSceLibreSsl/MemoryBio.cmake) +include(prx/libSceLibreSsl/Cmac.cmake) include(prx/libSceLibreSsl/ProtocolSelection.cmake) @@ -355,6 +363,16 @@ if(BUILD_TESTING) set_tests_properties(guest_kernel_sys_neo_mode PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_kernel_sys_constant_unknowns_tests tests/GuestKernelSysConstantUnknowns.cpp) + target_include_directories(guest_kernel_sys_constant_unknowns_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_kernel_sys_constant_unknowns_tests PRIVATE libkernel_sys libkernel libc) + configure_windows_unwind(guest_kernel_sys_constant_unknowns_tests) + add_test(NAME guest_kernel_sys_constant_unknowns COMMAND guest_kernel_sys_constant_unknowns_tests) + set_tests_properties(guest_kernel_sys_constant_unknowns PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_kernel_sys_constant_unknowns 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) @@ -472,7 +490,7 @@ if(BUILD_TESTING) endif() add_test_executable(guest_kernel_sys_tests tests/GuestKernelSys.cpp) target_include_directories(guest_kernel_sys_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libSceAmpr libkernel libc) + target_link_libraries(guest_kernel_sys_tests PRIVATE libkernel_sys libSceNet libSceAmpr libkernel libc) configure_windows_unwind(guest_kernel_sys_tests) add_test(NAME guest_kernel_sys COMMAND guest_kernel_sys_tests) set_tests_properties(guest_kernel_sys PROPERTIES TIMEOUT 20) @@ -533,6 +551,16 @@ if(BUILD_TESTING) set_tests_properties(guest_playready PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_sdec_version_tests tests/GuestSdecVersion.cpp) + target_include_directories(guest_sdec_version_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_sdec_version_tests PRIVATE libSceVdecSavc libSceVdecSavc2 libSceVdecShevc libkernel libc) + configure_windows_unwind(guest_sdec_version_tests) + add_test(NAME guest_sdec_version COMMAND guest_sdec_version_tests) + set_tests_properties(guest_sdec_version PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_sdec_version PROPERTIES ENVIRONMENT_MODIFICATION + "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 $) @@ -671,6 +699,13 @@ if(BUILD_TESTING) endif() if(WIN32) + add_test_executable(windows_crash_thread_name_tests tests/WindowsCrashThreadName.cpp) + add_dependencies(windows_crash_thread_name_tests libc) + configure_host_test_executable(windows_crash_thread_name_tests) + add_test(NAME windows_crash_thread_name COMMAND windows_crash_thread_name_tests $) + set_tests_properties(windows_crash_thread_name PROPERTIES TIMEOUT 20 + PASS_REGULAR_EXPRESSION "on thread [0-9]+ 'a+Z'[^~]*thread [0-9]+ 'c+Y':" + FAIL_REGULAR_EXPRESSION "Zb|Yd|did not fault") add_test_executable(windows_crash_text_tests tests/WindowsCrashText.cpp) add_dependencies(windows_crash_text_tests libc) if(MINGW) @@ -680,6 +715,13 @@ if(BUILD_TESTING) set_tests_properties(windows_crash_text PROPERTIES TIMEOUT 20 PASS_REGULAR_EXPRESSION "faulting[^~]*FATAL: unhandled exception 0xc0000005[^~]* \\[r12\\] \"allocation failed in the heap\"[\r\n]+ \\[r13\\] u\"LowLevelFatalError \\[File:Unknown\\] \\[Line: 42\\] Out of memory\"[\r\n]+ \\[r14\\] \"PAGE END\"[.][.][.]" FAIL_REGULAR_EXPRESSION "\\[rbx\\] u?\"|did not fault") + add_test_executable(windows_integer_overflow_report_tests tests/WindowsIntegerOverflowReport.cpp) + add_dependencies(windows_integer_overflow_report_tests libc) + configure_host_test_executable(windows_integer_overflow_report_tests) + add_test(NAME windows_integer_overflow_report COMMAND windows_integer_overflow_report_tests $) + set_tests_properties(windows_integer_overflow_report PROPERTIES TIMEOUT 20 + PASS_REGULAR_EXPRESSION "dividing[^~]*FATAL: unhandled exception 0xc0000095" + FAIL_REGULAR_EXPRESSION "did not fault") endif() add_test_executable(guest_voice_port_attr_tests tests/GuestVoicePortAttr.cpp) @@ -777,7 +819,7 @@ if(BUILD_TESTING) 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) + foreach(testCase IN ITEMS ids sync clock wake) 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) @@ -843,6 +885,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_audiodec_cpu_tests tests/GuestAudiodecCpu.cpp) + target_include_directories(guest_audiodec_cpu_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_audiodec_cpu_tests PRIVATE libSceAudiodecCpu libc) + configure_windows_unwind(guest_audiodec_cpu_tests) + add_test(NAME guest_audiodec_cpu COMMAND guest_audiodec_cpu_tests) + if(WIN32) + set_tests_properties(guest_audiodec_cpu PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + 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 libSceUserService libc) @@ -1090,6 +1142,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_np_cpp_web_api_matches_tests tests/GuestNpCppWebApiMatches.cpp) + target_include_directories(guest_np_cpp_web_api_matches_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_np_cpp_web_api_matches_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_matches_tests) + add_test(NAME guest_np_cpp_web_api_matches COMMAND guest_np_cpp_web_api_matches_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_matches PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(savedata_native_write_replacement_tests tests/SaveDataWriteReplacement.cpp) target_include_directories(savedata_native_write_replacement_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(savedata_native_write_replacement_tests PRIVATE libSceSaveData.native) @@ -1237,6 +1299,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_sdma_tests tests/GuestSdma.cpp) + target_include_directories(guest_sdma_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_sdma_tests PRIVATE libSceSdma libc) + configure_windows_unwind(guest_sdma_tests) + add_test(NAME guest_sdma COMMAND guest_sdma_tests) + if(WIN32) + set_tests_properties(guest_sdma PROPERTIES ENVIRONMENT_MODIFICATION + "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) @@ -1268,6 +1340,15 @@ if(BUILD_TESTING) set_tests_properties(guest_gnm_neo_default_state PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_division_prx_abi_tests tests/GuestDivisionPrxAbi.cpp nid/src/NidCompute.cpp nid/src/Sha1.cpp) + target_include_directories(guest_division_prx_abi_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/nid/include) + configure_host_test_executable(guest_division_prx_abi_tests) + target_link_libraries(guest_division_prx_abi_tests PRIVATE ${CMAKE_DL_LIBS}) + add_custom_target(guest_division_prx_library DEPENDS ${LIBC_PATCHED}) + add_dependencies(guest_division_prx_abi_tests guest_division_prx_library) + add_test(NAME guest_division_prx_abi COMMAND guest_division_prx_abi_tests $ ${PATCHED_DIR}/libc.prx) + set_tests_properties(guest_division_prx_abi PROPERTIES TIMEOUT 20) + endif() include(prx/libSceFreeType/CurveBounds.cmake) diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 2290a2a3b3..5c175ffbf3 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -1711,6 +1711,16 @@ struct SaveDataMount3 { std::uint8_t reserved[32]; }; +struct SaveDataMount2 { + int user_id; + int pad; + const SceSaveDataDirName* dir_name; + std::uint64_t blocks; + std::uint32_t mount_mode; + std::uint8_t reserved[32]; + int pad2; +}; + struct SaveDataDirNameSearchCond { std::int32_t user_id; std::int32_t pad; @@ -1810,6 +1820,8 @@ struct SaveDataEvent { std::uint8_t reserved[40]; }; +using SaveDataEventCallback = void (APS5_VABI *)(const SaveDataEvent* event, void* userdata); + struct SaveDataBackup { std::int32_t user_id; std::int32_t pad; @@ -1819,6 +1831,40 @@ struct SaveDataBackup { std::uint8_t reserved[32]; }; +struct SaveDataCheckBackupData { + std::int32_t user_id; + std::int32_t pad; + const SceSaveDataTitleId* title_id; + const SceSaveDataDirName* dir_name; + SaveDataParam* param; + SaveDataIcon* icon; + std::uint8_t reserved[32]; +}; + +struct SaveDataRestoreBackupData { + std::int32_t user_id; + std::int32_t pad; + const SceSaveDataTitleId* title_id; + const SceSaveDataDirName* dir_name; + const void* fingerprint; + std::uint32_t unused; + std::uint8_t reserved[32]; + std::int32_t pad2; +}; +static_assert(offsetof(SaveDataCheckBackupData, title_id) == 0x08); +static_assert(offsetof(SaveDataCheckBackupData, dir_name) == 0x10); +static_assert(offsetof(SaveDataCheckBackupData, param) == 0x18); +static_assert(offsetof(SaveDataCheckBackupData, icon) == 0x20); +static_assert(offsetof(SaveDataCheckBackupData, reserved) == 0x28); +static_assert(sizeof(SaveDataCheckBackupData) == 0x48); +static_assert(offsetof(SaveDataRestoreBackupData, title_id) == 0x08); +static_assert(offsetof(SaveDataRestoreBackupData, dir_name) == 0x10); +static_assert(offsetof(SaveDataRestoreBackupData, fingerprint) == 0x18); +static_assert(offsetof(SaveDataRestoreBackupData, unused) == 0x20); +static_assert(offsetof(SaveDataRestoreBackupData, reserved) == 0x24); +static_assert(offsetof(SaveDataRestoreBackupData, pad2) == 0x44); +static_assert(sizeof(SaveDataRestoreBackupData) == 0x48); + struct SaveDataConvert { std::int32_t user_id; std::int32_t pad; @@ -2047,6 +2093,29 @@ struct JpegDecImageInfo { std::uint32_t output_image_height; }; +struct JpegDecInputChunk { + const void* data; + std::uint32_t size; + std::uint32_t reserved; +}; + +using JpegDecInputCallback = std::int32_t (APS5_VABI *)(JpegDecInputChunk*, void*); + +struct JpegDecInputControlParam { + JpegDecInputCallback input; + void* input_argument; + void* image_mem_addr; + void* coefficient_mem_addr; + std::uint32_t image_mem_size; + std::uint32_t coefficient_mem_size; + std::uint16_t operation; + std::uint16_t down_scale; + std::uint16_t pixel_format; + std::uint16_t alpha_value; + std::uint32_t image_pitch; + std::uint32_t reserved; +}; + struct PlayGoInitParams { const void* buf_addr; std::uint32_t buf_size; diff --git a/core/libs/nid/src/MachONidPatcher.cpp b/core/libs/nid/src/MachONidPatcher.cpp index 385d6298b3..a2263a97ea 100644 --- a/core/libs/nid/src/MachONidPatcher.cpp +++ b/core/libs/nid/src/MachONidPatcher.cpp @@ -188,7 +188,10 @@ std::optional FindTrie(const std::vector& binary, co return found; } -void ParseTrieNode(const std::vector& data, std::size_t start, std::size_t end, std::size_t node, std::string& prefix, std::set& visited, std::vector& out) { +constexpr std::size_t kMaxExportTrieDepth = 1024; + +void ParseTrieNode(const std::vector& data, std::size_t start, std::size_t end, std::size_t node, std::string& prefix, std::set& visited, std::vector& out, std::size_t depth) { + if (depth > kMaxExportTrieDepth) throw std::runtime_error("export trie nesting too deep"); if (node >= end - start) throw std::runtime_error("export trie node out of bounds"); if (!visited.insert(node).second) throw std::runtime_error("export trie has a cycle"); std::size_t cursor = start + node; @@ -216,7 +219,7 @@ void ParseTrieNode(const std::vector& data, std::size_t start, std const auto child = ReadUleb(data, cursor, end); const auto length = prefix.size(); prefix += label; - ParseTrieNode(data, start, end, static_cast(child), prefix, visited, out); + ParseTrieNode(data, start, end, static_cast(child), prefix, visited, out, depth + 1); prefix.resize(length); } } @@ -227,7 +230,7 @@ std::vector ParseTrie(const std::vector& binary, std: if (size == 0) return exports; std::string prefix; std::set visited; - ParseTrieNode(binary, offset, std::size_t{offset} + size, 0, prefix, visited, exports); + ParseTrieNode(binary, offset, std::size_t{offset} + size, 0, prefix, visited, exports, 0); return exports; } diff --git a/core/libs/nid/src/PeNidPatcher.cpp b/core/libs/nid/src/PeNidPatcher.cpp index e88c64d8a3..1e04d02937 100644 --- a/core/libs/nid/src/PeNidPatcher.cpp +++ b/core/libs/nid/src/PeNidPatcher.cpp @@ -99,6 +99,9 @@ void PeNidPatcher::PatchNids(std::vector& pe, const std::string& l const std::size_t namesArrayOffset = RvaToOffset(pe, exportTable.AddressOfNames, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); const std::size_t ordinalsArrayOffset = RvaToOffset(pe, exportTable.AddressOfNameOrdinals, peHeaderOffset, numberOfSections, sizeOfOptionalHeader); + if (std::uint64_t{namesArrayOffset} + std::uint64_t{exportTable.NumberOfNames} * 4u > pe.size() || + std::uint64_t{ordinalsArrayOffset} + std::uint64_t{exportTable.NumberOfNames} * 2u > pe.size()) + throw std::runtime_error("export name tables out of file bounds"); std::vector nameRvas(exportTable.NumberOfNames); std::vector names(exportTable.NumberOfNames); diff --git a/core/libs/nid/tests/test_macho_nids.py b/core/libs/nid/tests/test_macho_nids.py index 71853aefac..e3c05d0fb6 100644 --- a/core/libs/nid/tests/test_macho_nids.py +++ b/core/libs/nid/tests/test_macho_nids.py @@ -117,8 +117,8 @@ def dylib_command(path): return struct.pack("> (7 * index)) & 0x7F) | 0x80 for index in range(4)] + [(value >> 28) & 0x7F]) + + node_size = 9 + nodes = [b"\x00\x01a\x00" + fixed_uleb(node_size * (index + 1)) for index in range(depth - 1)] + nodes.append(b"\x02\x00\x00\x00") + return b"".join(nodes) + bytes(-sum(map(len, nodes)) % 8) + + +def check_deep_trie(patcher, directory): + image = build_dylib([("_puts_nid_postfix", 0, 0x100)], [], trie=chain_trie(200000)) + result, _ = patch(patcher, directory, "deep-trie.dylib", image) + assert result.returncode != 0 and "export trie nesting too deep" in result.stdout + result.stderr, (result.returncode, result.stdout, result.stderr) + + def check_short_imports(patcher, directory): imports = [("_sceXy", 1), ("_malloc", 2), ("_sceKernelFoo", 1), ("_sceNetRecv", 1)] result, path = patch(patcher, directory, "short.dylib", build_dylib([("_puts_nid_postfix", 0, 0x100)], imports)) @@ -297,6 +313,7 @@ def main(): check_preserved(patcher, directory) check_export_list(patcher, directory) check_refusals(patcher, directory) + check_deep_trie(patcher, directory) print("NID patcher Mach-O tests passed") diff --git a/core/libs/nid/tests/test_pe_references.py b/core/libs/nid/tests/test_pe_references.py index e7ec3ba572..2c04c4f489 100644 --- a/core/libs/nid/tests/test_pe_references.py +++ b/core/libs/nid/tests/test_pe_references.py @@ -2,6 +2,7 @@ import struct import subprocess import sys import tempfile +import time import unittest from pathlib import Path from types import SimpleNamespace @@ -564,6 +565,18 @@ class LibraryImportTests(unittest.TestCase): self.assertIn("delay-loaded", result.stdout + result.stderr) self.assertEqual(patched, image) + def test_huge_name_count_is_rejected_before_allocation(self): + for count in (0xFFFFFFFF, 0x40000000): + with self.subTest(count=count): + image = self.library(["puts"]) + struct.pack_into(" +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_DUMMY_FUN + +} diff --git a/core/libs/prx/libMozaSDK/libMozaSDK.hpp b/core/libs/prx/libMozaSDK/libMozaSDK.hpp new file mode 100644 index 0000000000..6f70f09bee --- /dev/null +++ b/core/libs/prx/libMozaSDK/libMozaSDK.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/Export.cpp b/core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/Export.cpp new file mode 100644 index 0000000000..2186d0d301 --- /dev/null +++ b/core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/Export.cpp @@ -0,0 +1,251 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libReactNative.Modules.Vsh.WebAppLauncher/include/InventoryExports.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI CheckSystemEvents() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CommerceDialogBrowseCategory() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CommerceDialogBrowseProduct() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CommerceDialogInit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CommerceDialogRedeemPromotionCode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CommerceHidePsStoreIcon() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CommerceSetPsStoreIconLayout() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CommerceShowPsStoreIcon() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI DisableSystemMedia() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ErrorDialogClose() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ErrorDialogInit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ErrorDialogOpen() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetAccountAgeA() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetAccountCountryA() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetAccountIdA() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetAccountLanguage2() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetAuthorizationCode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetClosedCaptionSettings() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetOnlineId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetProcessTime() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetVideoOutputStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetVideoOutputStatusInt() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI Hide() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI IsDebuggingSupported() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpWebAbortRequest() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpWebDeleteRequest() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpWebInitialize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpWebReadData() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpWebTerminate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI OpenClosedCaptionAdvancedSettings() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI OpenClosedCaptionSettings() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SoundInitialize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SoundLoad() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SoundPlay() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SoundTerminate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SpeechCancel() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SpeechClose() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SpeechInitialize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SpeechSpeak() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SpeechStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SpeechTerminate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SystemDialogInit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SystemDialogOpen() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SystemServiceParamGetInt() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI SystemServiceParamGetString() { + NotImplemented_nid_no_patch(__func__); +} + +} + +extern "C" { + +int APS5_VABI libReactNativeWebAppLauncherInventory__plus_ucrAt9fI7c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+ucrAt9fI7c", libReactNativeWebAppLauncherInventory__plus_ucrAt9fI7c_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_06N_minus_dAyE_plus_HY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("06N-dAyE+HY", libReactNativeWebAppLauncherInventory_06N_minus_dAyE_plus_HY_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_41blU2JCqLQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("41blU2JCqLQ", libReactNativeWebAppLauncherInventory_41blU2JCqLQ_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_BGgSEXB1Now_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BGgSEXB1Now", libReactNativeWebAppLauncherInventory_BGgSEXB1Now_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_CAK_minus_S2JVdsk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("CAK-S2JVdsk", libReactNativeWebAppLauncherInventory_CAK_minus_S2JVdsk_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_QxCdpkEsOgo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QxCdpkEsOgo", libReactNativeWebAppLauncherInventory_QxCdpkEsOgo_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_Tjj_plus__plus_S2qvOU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Tjj++S2qvOU", libReactNativeWebAppLauncherInventory_Tjj_plus__plus_S2qvOU_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_f5NHqUziG_plus_4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("f5NHqUziG+4", libReactNativeWebAppLauncherInventory_f5NHqUziG_plus_4_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_mI_minus_ltMuVN_plus_Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mI-ltMuVN+Q", libReactNativeWebAppLauncherInventory_mI_minus_ltMuVN_plus_Q_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_vDup_plus_0mWZf8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("vDup+0mWZf8", libReactNativeWebAppLauncherInventory_vDup_plus_0mWZf8_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_wR1ucKGDM9Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wR1ucKGDM9Q", libReactNativeWebAppLauncherInventory_wR1ucKGDM9Q_nid_no_patch); + +int APS5_VABI libReactNativeWebAppLauncherInventory_xPA_minus_tXzPGpc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xPA-tXzPGpc", libReactNativeWebAppLauncherInventory_xPA_minus_tXzPGpc_nid_no_patch); + +} diff --git a/core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/include/InventoryExports.hpp b/core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/include/InventoryExports.hpp new file mode 100644 index 0000000000..d84245abdb --- /dev/null +++ b/core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/include/InventoryExports.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI CheckSystemEvents(); +int APS5_VABI CommerceDialogBrowseCategory(); +int APS5_VABI CommerceDialogBrowseProduct(); +int APS5_VABI CommerceDialogInit(); +int APS5_VABI CommerceDialogRedeemPromotionCode(); +int APS5_VABI CommerceHidePsStoreIcon(); +int APS5_VABI CommerceSetPsStoreIconLayout(); +int APS5_VABI CommerceShowPsStoreIcon(); +int APS5_VABI DisableSystemMedia(); +int APS5_VABI ErrorDialogClose(); +int APS5_VABI ErrorDialogInit(); +int APS5_VABI ErrorDialogOpen(); +int APS5_VABI GetAccountAgeA(); +int APS5_VABI GetAccountCountryA(); +int APS5_VABI GetAccountIdA(); +int APS5_VABI GetAccountLanguage2(); +int APS5_VABI GetAuthorizationCode(); +int APS5_VABI GetClosedCaptionSettings(); +int APS5_VABI GetOnlineId(); +int APS5_VABI GetProcessTime(); +int APS5_VABI GetVideoOutputStatus(); +int APS5_VABI GetVideoOutputStatusInt(); +int APS5_VABI Hide(); +int APS5_VABI IsDebuggingSupported(); +int APS5_VABI NpWebAbortRequest(); +int APS5_VABI NpWebDeleteRequest(); +int APS5_VABI NpWebInitialize(); +int APS5_VABI NpWebReadData(); +int APS5_VABI NpWebTerminate(); +int APS5_VABI OpenClosedCaptionAdvancedSettings(); +int APS5_VABI OpenClosedCaptionSettings(); +int APS5_VABI SoundInitialize(); +int APS5_VABI SoundLoad(); +int APS5_VABI SoundPlay(); +int APS5_VABI SoundTerminate(); +int APS5_VABI SpeechCancel(); +int APS5_VABI SpeechClose(); +int APS5_VABI SpeechInitialize(); +int APS5_VABI SpeechSpeak(); +int APS5_VABI SpeechStatus(); +int APS5_VABI SpeechTerminate(); +int APS5_VABI SystemDialogInit(); +int APS5_VABI SystemDialogOpen(); +int APS5_VABI SystemServiceParamGetInt(); +int APS5_VABI SystemServiceParamGetString(); + + +int APS5_VABI libReactNativeWebAppLauncherInventory__plus_ucrAt9fI7c_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_06N_minus_dAyE_plus_HY_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_41blU2JCqLQ_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_BGgSEXB1Now_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_CAK_minus_S2JVdsk_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_QxCdpkEsOgo_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_Tjj_plus__plus_S2qvOU_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_f5NHqUziG_plus_4_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_mI_minus_ltMuVN_plus_Q_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_vDup_plus_0mWZf8_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_wR1ucKGDM9Q_nid_no_patch(); +int APS5_VABI libReactNativeWebAppLauncherInventory_xPA_minus_tXzPGpc_nid_no_patch(); + +} diff --git a/core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/libReactNative.Modules.Vsh.WebAppLauncher.hpp b/core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/libReactNative.Modules.Vsh.WebAppLauncher.hpp new file mode 100644 index 0000000000..4f7d8e0467 --- /dev/null +++ b/core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/libReactNative.Modules.Vsh.WebAppLauncher.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libReactNative.Modules.Vsh.WebAppLauncher/include/InventoryExports.hpp" diff --git a/core/libs/prx/libReactNative.Modules.Vsh/Export.cpp b/core/libs/prx/libReactNative.Modules.Vsh/Export.cpp new file mode 100644 index 0000000000..058f793c0f --- /dev/null +++ b/core/libs/prx/libReactNative.Modules.Vsh/Export.cpp @@ -0,0 +1,361 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libReactNative.Modules.Vsh/include/InventoryExports.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI CreateWebApiLibCtx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CryptographyDecryptAES128CBC() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CryptographyDecryptAES128CBC_HS256() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CryptographyDecryptAES128GCM() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CryptographyEncryptAES128CBC() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CryptographyEncryptAES128GCM() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI CryptographyGenerateHmacSha256() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI DestroyWebApiLibCtx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetLibHttpCtxId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI GetPs4AppCategoryForTitleId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI InetStatGetResultNative() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI InetStatGetStateNative() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI InetStatStartTestNative() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI InitializeNetLib() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI LoginMgrCreateUser() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI LoginMgrDestroyUser() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI LoginMgrInitialize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI LoginMgrSetLoginFlag() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI LoginMgrSetUserStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI LoginMgrTerminate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpStateManagerRegisterCallback() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpStateManagerRtcSetTick() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpStateManagerStart() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpStateManagerStop() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI NpStateManagerUnregisterCallback() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI PSNowGetStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI PSNowGetStreamingStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI PSNowInitialize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI PSNowReset() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI PSNowSendCommand() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI PSNowSetAuthCodes() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI PSNowSetSettings() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI PSNowTerminate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayClearConnectHistory() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayConfirmDeviceRegist() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayDisconnect() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayGeneratePinCode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayGetConnectHistory() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayGetConnectUserId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayGetConnectionStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayGetRemoteplayStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayGetRpMode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayIsRemotePlaying() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayNotifyCanWakeup() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayNotifyPinCodeError() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayNotifyUserDelete() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlayProhibitStreaming() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlaySetProhibition() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlaySetProhibitionForVsh() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RemotePlaySetRpMode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RtcGetCurrentAdNetworkTickNative() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RtcGetCurrentDebugNetworkTickNative() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI RtcGetCurrentNetworkTickNative() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI TerminateNetLib() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI WebBrowserGetEnvelopeData() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientAddEventListener() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientAttachVirtualMicrophone() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientDetachVirtualMicrophone() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientGetClientStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientInitialize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientRemoveController() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientShowHomeScreen() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientStartMicCapture() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientStopMicCapture() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRemotePlayClientTerminateStreaming() { + NotImplemented_nid_no_patch(__func__); +} + +} + +extern "C" { + +int APS5_VABI libReactNativeModulesVshInventory__plus_I5sPs9FLXw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+I5sPs9FLXw", libReactNativeModulesVshInventory__plus_I5sPs9FLXw_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory__minus_9hYGDAsb4E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-9hYGDAsb4E", libReactNativeModulesVshInventory__minus_9hYGDAsb4E_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_BzJsgzD97LU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BzJsgzD97LU", libReactNativeModulesVshInventory_BzJsgzD97LU_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_IQNf2nnftNI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("IQNf2nnftNI", libReactNativeModulesVshInventory_IQNf2nnftNI_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_N3mcm_minus_qDFR0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("N3mcm-qDFR0", libReactNativeModulesVshInventory_N3mcm_minus_qDFR0_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_OME2Be_plus_FlwA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OME2Be+FlwA", libReactNativeModulesVshInventory_OME2Be_plus_FlwA_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_PLEe_minus_toB_minus_KE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("PLEe-toB-KE", libReactNativeModulesVshInventory_PLEe_minus_toB_minus_KE_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_QGGsQrtLW9Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QGGsQrtLW9Y", libReactNativeModulesVshInventory_QGGsQrtLW9Y_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_Rm_plus_rgnBoPIE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Rm+rgnBoPIE", libReactNativeModulesVshInventory_Rm_plus_rgnBoPIE_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_SJU_plus_qK_plus_8yrM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("SJU+qK+8yrM", libReactNativeModulesVshInventory_SJU_plus_qK_plus_8yrM_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_T7dL_minus_hEySvU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("T7dL-hEySvU", libReactNativeModulesVshInventory_T7dL_minus_hEySvU_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_b_plus_MJDz3HB6s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("b+MJDz3HB6s", libReactNativeModulesVshInventory_b_plus_MJDz3HB6s_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_bbsP8_minus_e8GZA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bbsP8-e8GZA", libReactNativeModulesVshInventory_bbsP8_minus_e8GZA_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_fxEQAPDRhLw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("fxEQAPDRhLw", libReactNativeModulesVshInventory_fxEQAPDRhLw_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_j917Pp4k_minus_CI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("j917Pp4k-CI", libReactNativeModulesVshInventory_j917Pp4k_minus_CI_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_lZ_minus_JDEqB34k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lZ-JDEqB34k", libReactNativeModulesVshInventory_lZ_minus_JDEqB34k_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_tKy_minus_Qdg_plus_FRc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("tKy-Qdg+FRc", libReactNativeModulesVshInventory_tKy_minus_Qdg_plus_FRc_nid_no_patch); + +int APS5_VABI libReactNativeModulesVshInventory_uQfQgwm36L4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("uQfQgwm36L4", libReactNativeModulesVshInventory_uQfQgwm36L4_nid_no_patch); + +} diff --git a/core/libs/prx/libReactNative.Modules.Vsh/include/InventoryExports.hpp b/core/libs/prx/libReactNative.Modules.Vsh/include/InventoryExports.hpp new file mode 100644 index 0000000000..5062f04693 --- /dev/null +++ b/core/libs/prx/libReactNative.Modules.Vsh/include/InventoryExports.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI CreateWebApiLibCtx(); +int APS5_VABI CryptographyDecryptAES128CBC(); +int APS5_VABI CryptographyDecryptAES128CBC_HS256(); +int APS5_VABI CryptographyDecryptAES128GCM(); +int APS5_VABI CryptographyEncryptAES128CBC(); +int APS5_VABI CryptographyEncryptAES128GCM(); +int APS5_VABI CryptographyGenerateHmacSha256(); +int APS5_VABI DestroyWebApiLibCtx(); +int APS5_VABI GetLibHttpCtxId(); +int APS5_VABI GetPs4AppCategoryForTitleId(); +int APS5_VABI InetStatGetResultNative(); +int APS5_VABI InetStatGetStateNative(); +int APS5_VABI InetStatStartTestNative(); +int APS5_VABI InitializeNetLib(); +int APS5_VABI LoginMgrCreateUser(); +int APS5_VABI LoginMgrDestroyUser(); +int APS5_VABI LoginMgrInitialize(); +int APS5_VABI LoginMgrSetLoginFlag(); +int APS5_VABI LoginMgrSetUserStatus(); +int APS5_VABI LoginMgrTerminate(); +int APS5_VABI NpStateManagerRegisterCallback(); +int APS5_VABI NpStateManagerRtcSetTick(); +int APS5_VABI NpStateManagerStart(); +int APS5_VABI NpStateManagerStop(); +int APS5_VABI NpStateManagerUnregisterCallback(); +int APS5_VABI PSNowGetStatus(); +int APS5_VABI PSNowGetStreamingStatus(); +int APS5_VABI PSNowInitialize(); +int APS5_VABI PSNowReset(); +int APS5_VABI PSNowSendCommand(); +int APS5_VABI PSNowSetAuthCodes(); +int APS5_VABI PSNowSetSettings(); +int APS5_VABI PSNowTerminate(); +int APS5_VABI RemotePlayClearConnectHistory(); +int APS5_VABI RemotePlayConfirmDeviceRegist(); +int APS5_VABI RemotePlayDisconnect(); +int APS5_VABI RemotePlayGeneratePinCode(); +int APS5_VABI RemotePlayGetConnectHistory(); +int APS5_VABI RemotePlayGetConnectUserId(); +int APS5_VABI RemotePlayGetConnectionStatus(); +int APS5_VABI RemotePlayGetRemoteplayStatus(); +int APS5_VABI RemotePlayGetRpMode(); +int APS5_VABI RemotePlayIsRemotePlaying(); +int APS5_VABI RemotePlayNotifyCanWakeup(); +int APS5_VABI RemotePlayNotifyPinCodeError(); +int APS5_VABI RemotePlayNotifyUserDelete(); +int APS5_VABI RemotePlayProhibitStreaming(); +int APS5_VABI RemotePlaySetProhibition(); +int APS5_VABI RemotePlaySetProhibitionForVsh(); +int APS5_VABI RemotePlaySetRpMode(); +int APS5_VABI RtcGetCurrentAdNetworkTickNative(); +int APS5_VABI RtcGetCurrentDebugNetworkTickNative(); +int APS5_VABI RtcGetCurrentNetworkTickNative(); +int APS5_VABI TerminateNetLib(); +int APS5_VABI WebBrowserGetEnvelopeData(); +int APS5_VABI sceRemotePlayClientAddEventListener(); +int APS5_VABI sceRemotePlayClientAttachVirtualMicrophone(); +int APS5_VABI sceRemotePlayClientDetachVirtualMicrophone(); +int APS5_VABI sceRemotePlayClientGetClientStatus(); +int APS5_VABI sceRemotePlayClientInitialize(); +int APS5_VABI sceRemotePlayClientRemoveController(); +int APS5_VABI sceRemotePlayClientShowHomeScreen(); +int APS5_VABI sceRemotePlayClientStartMicCapture(); +int APS5_VABI sceRemotePlayClientStopMicCapture(); +int APS5_VABI sceRemotePlayClientTerminateStreaming(); + + +int APS5_VABI libReactNativeModulesVshInventory__plus_I5sPs9FLXw_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory__minus_9hYGDAsb4E_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_BzJsgzD97LU_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_IQNf2nnftNI_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_N3mcm_minus_qDFR0_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_OME2Be_plus_FlwA_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_PLEe_minus_toB_minus_KE_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_QGGsQrtLW9Y_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_Rm_plus_rgnBoPIE_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_SJU_plus_qK_plus_8yrM_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_T7dL_minus_hEySvU_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_b_plus_MJDz3HB6s_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_bbsP8_minus_e8GZA_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_fxEQAPDRhLw_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_j917Pp4k_minus_CI_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_lZ_minus_JDEqB34k_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_tKy_minus_Qdg_plus_FRc_nid_no_patch(); +int APS5_VABI libReactNativeModulesVshInventory_uQfQgwm36L4_nid_no_patch(); + +} diff --git a/core/libs/prx/libReactNative.Modules.Vsh/libReactNative.Modules.Vsh.hpp b/core/libs/prx/libReactNative.Modules.Vsh/libReactNative.Modules.Vsh.hpp new file mode 100644 index 0000000000..3fb0db9ae4 --- /dev/null +++ b/core/libs/prx/libReactNative.Modules.Vsh/libReactNative.Modules.Vsh.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libReactNative.Modules.Vsh/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceAbstractStorage/Export.cpp b/core/libs/prx/libSceAbstractStorage/Export.cpp index 061e032714..d7f544313d 100644 --- a/core/libs/prx/libSceAbstractStorage/Export.cpp +++ b/core/libs/prx/libSceAbstractStorage/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAbstractStorage/include/Api.hpp" #include "prx/libSceAbstractStorage/include/LegacyExports.hpp" #include "prx/libc/include/general/ExportMacros.hpp" #include "prx/libSceAbstractStorage/include/InventoryExports.hpp" @@ -9,555 +10,445 @@ extern "C" { -int APS5_VABI sceAbstractStorageUnknown_7RnkdybfYiQ_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage12Serializable12readExternalESt10shared_ptrINS_7ContentEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("7RnkdybfYiQ", sceAbstractStorageUnknown_7RnkdybfYiQ_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_gINcKTbrqk4_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage12Serializable13writeExternalESt10shared_ptrINS_7ContentEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("gINcKTbrqk4", sceAbstractStorageUnknown_gINcKTbrqk4_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_F6tunp5_4ZM_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage12SerializableC1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("F6tunp5+4ZM", sceAbstractStorageUnknown_F6tunp5_4ZM_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_QRnz_VjPrkY_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage12SerializableC2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("QRnz-VjPrkY", sceAbstractStorageUnknown_QRnz_VjPrkY_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_DkAGX6EPi_g_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage12SerializableD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("DkAGX6EPi+g", sceAbstractStorageUnknown_DkAGX6EPi_g_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_tDhVqVa_tvU_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage12SerializableD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("tDhVqVa-tvU", sceAbstractStorageUnknown_tDhVqVa_tvU_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_o9RD998mN8M_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent12readExternalESt10shared_ptrINS_7ContentEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("o9RD998mN8M", sceAbstractStorageUnknown_o9RD998mN8M_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_KAJD5gNtjzg_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent13writeExternalESt10shared_ptrINS_7ContentEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("KAJD5gNtjzg", sceAbstractStorageUnknown_KAJD5gNtjzg_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_jekg6a2C_KE_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent14CreateInstanceESbIcSt11char_traitsIcENS_8StlAllocIcEEEN3paf6common9SharedPtrINS6_6BufferEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("jekg6a2C-KE", sceAbstractStorageUnknown_jekg6a2C_KE_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_SPPYTszg6LQ_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent14CreateInstanceESbIcSt11char_traitsIcENS_8StlAllocIcEEEPvm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("SPPYTszg6LQ", sceAbstractStorageUnknown_SPPYTszg6LQ_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_ppyod3XEauc_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent4OpenEit() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("ppyod3XEauc", sceAbstractStorageUnknown_ppyod3XEauc_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_FC7E1riolAM_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent4ReadEPvlPl() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("FC7E1riolAM", sceAbstractStorageUnknown_FC7E1riolAM_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_h64Ojqk4fbE_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent5CloseEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("h64Ojqk4fbE", sceAbstractStorageUnknown_h64Ojqk4fbE_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_L9n8X5qE3gI_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent5FlushEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("L9n8X5qE3gI", sceAbstractStorageUnknown_L9n8X5qE3gI_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_UqSO97fYNAs_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent5WriteEPKvlPl() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("UqSO97fYNAs", sceAbstractStorageUnknown_UqSO97fYNAs_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_nxefs8JHV5U_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent6RemoveEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("nxefs8JHV5U", sceAbstractStorageUnknown_nxefs8JHV5U_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_TI_djeSDh5s_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent8SeekByteEliPl() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("TI+djeSDh5s", sceAbstractStorageUnknown_TI_djeSDh5s_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_23QZTBt3E60_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContent8SeekTimeElPl() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("23QZTBt3E60", sceAbstractStorageUnknown_23QZTBt3E60_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_CgSbdJjHTyc_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContentC1ESbIcSt11char_traitsIcENS_8StlAllocIcEEEN3paf6common9SharedPtrINS6_6BufferEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("CgSbdJjHTyc", sceAbstractStorageUnknown_CgSbdJjHTyc_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_uhxDqVGiDTY_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContentC2ESbIcSt11char_traitsIcENS_8StlAllocIcEEEN3paf6common9SharedPtrINS6_6BufferEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("uhxDqVGiDTY", sceAbstractStorageUnknown_uhxDqVGiDTY_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_93IuLXtj1sc_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContentD0Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("93IuLXtj1sc", sceAbstractStorageUnknown_93IuLXtj1sc_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_6g_lsIudFQY_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContentD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("6g-lsIudFQY", sceAbstractStorageUnknown_6g_lsIudFQY_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_r_kkC2OXw10_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14MemfileContentD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("r+kkC2OXw10", sceAbstractStorageUnknown_r_kkC2OXw10_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_xeunZETVUSk_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager10GetStorageERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEPSt10shared_ptrINS_7StorageEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("xeunZETVUSk", sceAbstractStorageUnknown_xeunZETVUSk_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_k0BKafeYaUo_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager10InitializeEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("k0BKafeYaUo", sceAbstractStorageUnknown_k0BKafeYaUo_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_iAN_9pPHZSE_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager11GetInstanceERNS_12MemAllocatorEPv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("iAN-9pPHZSE", sceAbstractStorageUnknown_iAN_9pPHZSE_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_QbKmf_2_cR0_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager11GetInstanceEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("QbKmf+2+cR0", sceAbstractStorageUnknown_QbKmf_2_cR0_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_JWT7NbPA9Ds_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager12NotifyCreateERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEb() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("JWT7NbPA9Ds", sceAbstractStorageUnknown_JWT7NbPA9Ds_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown__fMkDXxU9E0_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager12NotifyRemoveERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEb() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("+fMkDXxU9E0", sceAbstractStorageUnknown__fMkDXxU9E0_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_sFN0uAZmk04_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager15ReleaseInstanceEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("sFN0uAZmk04", sceAbstractStorageUnknown_sFN0uAZmk04_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_wvDKH1RiZhU_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager16NotifyWriteCloseERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("wvDKH1RiZhU", sceAbstractStorageUnknown_wvDKH1RiZhU_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_FxOHF0FBNfU_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager16RegisterListenerEPNS_17ItemEventListenerE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("FxOHF0FBNfU", sceAbstractStorageUnknown_FxOHF0FBNfU_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_JvyLbYhVigM_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager16RegisterListenerEPNS_20StorageEventListenerE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("JvyLbYhVigM", sceAbstractStorageUnknown_JvyLbYhVigM_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_bb_T5NMDPck_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager16get_storage_nameERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("bb-T5NMDPck", sceAbstractStorageUnknown_bb_T5NMDPck_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_LPRr40DzhbA_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager17NotifySetMetadataERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("LPRr40DzhbA", sceAbstractStorageUnknown_LPRr40DzhbA_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_oIR7L2c6df8_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager18GetStorageNameListEPSt6vectorISbIcSt11char_traitsIcENS_8StlAllocIcEEESaIS6_EE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("oIR7L2c6df8", sceAbstractStorageUnknown_oIR7L2c6df8_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_mhLGIWhMLlk_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager18UnregisterListenerEPKNS_17ItemEventListenerE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("mhLGIWhMLlk", sceAbstractStorageUnknown_mhLGIWhMLlk_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_0Ydc18_XsSw_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager18UnregisterListenerEPKNS_20StorageEventListenerE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("0Ydc18+XsSw", sceAbstractStorageUnknown_0Ydc18_XsSw_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_piG6ktf2BTU_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager20get_service_instanceENS_11ServiceTypeE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("piG6ktf2BTU", sceAbstractStorageUnknown_piG6ktf2BTU_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_iLakkVdPya4_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager21NotifyStorageAttachedERKSbIcSt11char_traitsIcENS_8StlAllocIcEEENS_11ServiceTypeE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("iLakkVdPya4", sceAbstractStorageUnknown_iLakkVdPya4_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_1Ko_sKZgqqs_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager21NotifyStorageDetachedERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("1Ko+sKZgqqs", sceAbstractStorageUnknown_1Ko_sKZgqqs_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown__PYC3_W6yUE_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager4FreeEPv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("-PYC3+W6yUE", sceAbstractStorageUnknown__PYC3_W6yUE_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_3iP9Xa6Z3z0_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager4LoadENS_11ServiceTypeE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("3iP9Xa6Z3z0", sceAbstractStorageUnknown_3iP9Xa6Z3z0_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_JREmVpFIgEY_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager6MallocEm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("JREmVpFIgEY", sceAbstractStorageUnknown_JREmVpFIgEY_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_kCX0k5t1v_w_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager6UnloadENS_11ServiceTypeE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("kCX0k5t1v-w", sceAbstractStorageUnknown_kCX0k5t1v_w_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_3Cp_nOwl47U_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager6UpdateENS_11ServiceTypeE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("3Cp-nOwl47U", sceAbstractStorageUnknown_3Cp_nOwl47U_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_v9CSqw6Ve0w_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManager9TerminateEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("v9CSqw6Ve0w", sceAbstractStorageUnknown_v9CSqw6Ve0w_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_KxTVuhlCL7o_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManagerC1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("KxTVuhlCL7o", sceAbstractStorageUnknown_KxTVuhlCL7o_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_YXXMdeJmxJw_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManagerC2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("YXXMdeJmxJw", sceAbstractStorageUnknown_YXXMdeJmxJw_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_2_S0J1RW2vQ_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManagerD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("2-S0J1RW2vQ", sceAbstractStorageUnknown_2_S0J1RW2vQ_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_K3qnBgQ0nC4_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage14StorageManagerD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("K3qnBgQ0nC4", sceAbstractStorageUnknown_K3qnBgQ0nC4_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_rHzXUC9OHh8_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage15HttpTransaction15SendHttpRequestERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEiRKSt3mapIS5_S5_St4lessIS5_ESaISt4pairIS6_S5_EEEmb() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("rHzXUC9OHh8", sceAbstractStorageUnknown_rHzXUC9OHh8_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_Dlri33kcH6o_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage15HttpTransaction4InitEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Dlri33kcH6o", sceAbstractStorageUnknown_Dlri33kcH6o_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_lH57wXDczHk_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage15HttpTransaction4TermEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("lH57wXDczHk", sceAbstractStorageUnknown_lH57wXDczHk_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_xJbY3QNYIzM_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage15HttpTransaction8SendDataEPKvm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("xJbY3QNYIzM", sceAbstractStorageUnknown_xJbY3QNYIzM_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_tbeA_hlRlHc_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage15HttpTransactionC1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("tbeA-hlRlHc", sceAbstractStorageUnknown_tbeA_hlRlHc_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_01BHTyfcbBs_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage15HttpTransactionC2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("01BHTyfcbBs", sceAbstractStorageUnknown_01BHTyfcbBs_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_1PrEoSKt3_w_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage15HttpTransactionD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("1PrEoSKt3-w", sceAbstractStorageUnknown_1PrEoSKt3_w_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_MoUJZiDoHMk_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage15HttpTransactionD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("MoUJZiDoHMk", sceAbstractStorageUnknown_MoUJZiDoHMk_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_I69kRgYrWmY_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4Item11GetPathNameEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("I69kRgYrWmY", sceAbstractStorageUnknown_I69kRgYrWmY_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_GCMRCWH3PU0_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4Item11SetMetadataEPN23sceMetadataReaderWriter8MetadataE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("GCMRCWH3PU0", sceAbstractStorageUnknown_GCMRCWH3PU0_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_SYb4Kmj0u88_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4Item13ParseMetadataEPN23sceMetadataReaderWriter8MetadataES3_() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("SYb4Kmj0u88", sceAbstractStorageUnknown_SYb4Kmj0u88_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_k2VonUL2gF8_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4Item14GetStorageNameEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("k2VonUL2gF8", sceAbstractStorageUnknown_k2VonUL2gF8_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_zkaNI1552m4_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4Item6RenameERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("zkaNI1552m4", sceAbstractStorageUnknown_zkaNI1552m4_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_KAtGZCifCMo_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4Item7GetNameERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("KAtGZCifCMo", sceAbstractStorageUnknown_KAtGZCifCMo_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown__sxz_ThMtGM_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4Item7GetNameEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("-sxz-ThMtGM", sceAbstractStorageUnknown__sxz_ThMtGM_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_wyEuZhFhbOE_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4Item8set_metaERN23sceMetadataReaderWriter8MetadataERKSbIcSt11char_traitsIcENS_8StlAllocIcEEESA_() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("wyEuZhFhbOE", sceAbstractStorageUnknown_wyEuZhFhbOE_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_F_Tz7UzYtJ4_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4Item8set_metaERN23sceMetadataReaderWriter8MetadataERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("F-Tz7UzYtJ4", sceAbstractStorageUnknown_F_Tz7UzYtJ4_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_zC_hnmcVEOw_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4ItemC2ERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("zC+hnmcVEOw", sceAbstractStorageUnknown_zC_hnmcVEOw_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_scYjSEuq2ik_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4ItemD0Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("scYjSEuq2ik", sceAbstractStorageUnknown_scYjSEuq2ik_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_QPNELEk9zkI_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4ItemD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("QPNELEk9zkI", sceAbstractStorageUnknown_QPNELEk9zkI_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_WnE1cxQELbY_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4ItemD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("WnE1cxQELbY", sceAbstractStorageUnknown_WnE1cxQELbY_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_Hg0GBcA80dk_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4ItemdlEPv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Hg0GBcA80dk", sceAbstractStorageUnknown_Hg0GBcA80dk_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_gid_GlhKgJM_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage4ItemnwEm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("gid+GlhKgJM", sceAbstractStorageUnknown_gid_GlhKgJM_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_oJSXws_cIv8_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage6FolderC2ERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("oJSXws+cIv8", sceAbstractStorageUnknown_oJSXws_cIv8_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_GgNWVxe_YSU_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage6FolderD0Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("GgNWVxe-YSU", sceAbstractStorageUnknown_GgNWVxe_YSU_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown__1PWHzoXWyU_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage6FolderD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("-1PWHzoXWyU", sceAbstractStorageUnknown__1PWHzoXWyU_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_sLg6rUpiYSM_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage6FolderD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("sLg6rUpiYSM", sceAbstractStorageUnknown_sLg6rUpiYSM_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_lYUGcgHYT7Y_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7AbsFreeEPv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("lYUGcgHYT7Y", sceAbstractStorageUnknown_lYUGcgHYT7Y_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_hQiPEotip7M_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Content12GetExtensionERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("hQiPEotip7M", sceAbstractStorageUnknown_hQiPEotip7M_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_pekT_IsAkQU_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Content12GetMimeTypesEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("pekT-IsAkQU", sceAbstractStorageUnknown_pekT_IsAkQU_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_xilbMn7q9qE_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Content6RenameERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("xilbMn7q9qE", sceAbstractStorageUnknown_xilbMn7q9qE_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_UZbuoykGKzE_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7ContentC2ERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("UZbuoykGKzE", sceAbstractStorageUnknown_UZbuoykGKzE_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_RenWwlxG9Tw_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7ContentD0Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("RenWwlxG9Tw", sceAbstractStorageUnknown_RenWwlxG9Tw_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_JZ3wKDjl0yQ_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7ContentD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("JZ3wKDjl0yQ", sceAbstractStorageUnknown_JZ3wKDjl0yQ_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_m7OSrfA7K_s_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7ContentD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("m7OSrfA7K+s", sceAbstractStorageUnknown_m7OSrfA7K_s_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_spsjzV2pTSc_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Service11NextStorageERSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("spsjzV2pTSc", sceAbstractStorageUnknown_spsjzV2pTSc_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_6Fp8jJ7A7CY_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Service13CreateStorageERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEPSt10shared_ptrINS_7StorageEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("6Fp8jJ7A7CY", sceAbstractStorageUnknown_6Fp8jJ7A7CY_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_wBaVZTLTjkg_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Service18clear_storage_listEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("wBaVZTLTjkg", sceAbstractStorageUnknown_wBaVZTLTjkg_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_DyerRilxWZ4_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Service4LoadENS_11ServiceTypeE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("DyerRilxWZ4", sceAbstractStorageUnknown_DyerRilxWZ4_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_jCKhrVa9MEU_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Service4StopEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("jCKhrVa9MEU", sceAbstractStorageUnknown_jCKhrVa9MEU_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_tIIRbXa2ZOk_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Service5StartEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("tIIRbXa2ZOk", sceAbstractStorageUnknown_tIIRbXa2ZOk_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_vatp8UwS_nE_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7Service6UnLoadEPS0_() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("vatp8UwS+nE", sceAbstractStorageUnknown_vatp8UwS_nE_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_CrZTHC5yd64_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7ServiceC2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("CrZTHC5yd64", sceAbstractStorageUnknown_CrZTHC5yd64_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_IoNgvbVSw24_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7ServiceD0Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("IoNgvbVSw24", sceAbstractStorageUnknown_IoNgvbVSw24_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_PEuuRn6zhF4_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7ServiceD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("PEuuRn6zhF4", sceAbstractStorageUnknown_PEuuRn6zhF4_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_Ngfl7wTF7M0_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7ServiceD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Ngfl7wTF7M0", sceAbstractStorageUnknown_Ngfl7wTF7M0_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_tLtIJWgGINI_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7StorageC2ERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("tLtIJWgGINI", sceAbstractStorageUnknown_tLtIJWgGINI_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_4ns7J9OrJ5g_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7StorageD0Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("4ns7J9OrJ5g", sceAbstractStorageUnknown_4ns7J9OrJ5g_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_aKBqHZKouts_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7StorageD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("aKBqHZKouts", sceAbstractStorageUnknown_aKBqHZKouts_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_fydJOJjDxX4_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage7StorageD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("fydJOJjDxX4", sceAbstractStorageUnknown_fydJOJjDxX4_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_qtgEboQL0YE_nid_no_patch() { +int APS5_VABI _ZN15AbstractStorage9AbsMallocEm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("qtgEboQL0YE", sceAbstractStorageUnknown_qtgEboQL0YE_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_XBlKK5ijWI8_nid_no_patch() { +int APS5_VABI _ZNK15AbstractStorage14MemfileContent10GetServiceEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("XBlKK5ijWI8", sceAbstractStorageUnknown_XBlKK5ijWI8_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_xP3aCoP48XI_nid_no_patch() { +int APS5_VABI _ZNK15AbstractStorage14MemfileContent13GetCapabilityEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("xP3aCoP48XI", sceAbstractStorageUnknown_xP3aCoP48XI_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_FCSUVvY_0RM_nid_no_patch() { +int APS5_VABI _ZNK15AbstractStorage14MemfileContent7GetStatEPNS_4StatE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("FCSUVvY-0RM", sceAbstractStorageUnknown_FCSUVvY_0RM_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_oc6n6hdUrEM_nid_no_patch() { +int APS5_VABI _ZNK15AbstractStorage4Item12GetThumbnailEPSt10shared_ptrINS_7ContentEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("oc6n6hdUrEM", sceAbstractStorageUnknown_oc6n6hdUrEM_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_Cb7xWd9_7tc_nid_no_patch() { +int APS5_VABI _ZNK15AbstractStorage4Item15CreateThumbnailEPvPSt10shared_ptrINS_7ContentEE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Cb7xWd9-7tc", sceAbstractStorageUnknown_Cb7xWd9_7tc_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_mv9rtQAknH4_nid_no_patch() { +int APS5_VABI _ZNK15AbstractStorage6Folder7GetTypeEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("mv9rtQAknH4", sceAbstractStorageUnknown_mv9rtQAknH4_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_ka5TVf37Akg_nid_no_patch() { +int APS5_VABI _ZNK15AbstractStorage7Content7GetTypeEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("ka5TVf37Akg", sceAbstractStorageUnknown_ka5TVf37Akg_nid_no_patch); -int APS5_VABI sceAbstractStorageUnknown_1uryzAWFUuA_nid_no_patch() { +int APS5_VABI _ZNK15AbstractStorage7Storage7GetTypeEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("1uryzAWFUuA", sceAbstractStorageUnknown_1uryzAWFUuA_nid_no_patch); } diff --git a/core/libs/prx/libSceAbstractStorage/include/Api.hpp b/core/libs/prx/libSceAbstractStorage/include/Api.hpp new file mode 100644 index 0000000000..1c4f950e36 --- /dev/null +++ b/core/libs/prx/libSceAbstractStorage/include/Api.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI _ZN15AbstractStorage12Serializable12readExternalESt10shared_ptrINS_7ContentEE(); +int APS5_VABI _ZN15AbstractStorage12Serializable13writeExternalESt10shared_ptrINS_7ContentEE(); +int APS5_VABI _ZN15AbstractStorage12SerializableC1Ev(); +int APS5_VABI _ZN15AbstractStorage12SerializableC2Ev(); +int APS5_VABI _ZN15AbstractStorage12SerializableD1Ev(); +int APS5_VABI _ZN15AbstractStorage12SerializableD2Ev(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent12readExternalESt10shared_ptrINS_7ContentEE(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent13writeExternalESt10shared_ptrINS_7ContentEE(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent14CreateInstanceESbIcSt11char_traitsIcENS_8StlAllocIcEEEN3paf6common9SharedPtrINS6_6BufferEEE(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent14CreateInstanceESbIcSt11char_traitsIcENS_8StlAllocIcEEEPvm(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent4OpenEit(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent4ReadEPvlPl(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent5CloseEv(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent5FlushEv(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent5WriteEPKvlPl(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent6RemoveEv(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent8SeekByteEliPl(); +int APS5_VABI _ZN15AbstractStorage14MemfileContent8SeekTimeElPl(); +int APS5_VABI _ZN15AbstractStorage14MemfileContentC1ESbIcSt11char_traitsIcENS_8StlAllocIcEEEN3paf6common9SharedPtrINS6_6BufferEEE(); +int APS5_VABI _ZN15AbstractStorage14MemfileContentC2ESbIcSt11char_traitsIcENS_8StlAllocIcEEEN3paf6common9SharedPtrINS6_6BufferEEE(); +int APS5_VABI _ZN15AbstractStorage14MemfileContentD0Ev(); +int APS5_VABI _ZN15AbstractStorage14MemfileContentD1Ev(); +int APS5_VABI _ZN15AbstractStorage14MemfileContentD2Ev(); +int APS5_VABI _ZN15AbstractStorage14StorageManager10GetStorageERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEPSt10shared_ptrINS_7StorageEE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager10InitializeEv(); +int APS5_VABI _ZN15AbstractStorage14StorageManager11GetInstanceERNS_12MemAllocatorEPv(); +int APS5_VABI _ZN15AbstractStorage14StorageManager11GetInstanceEv(); +int APS5_VABI _ZN15AbstractStorage14StorageManager12NotifyCreateERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEb(); +int APS5_VABI _ZN15AbstractStorage14StorageManager12NotifyRemoveERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEb(); +int APS5_VABI _ZN15AbstractStorage14StorageManager15ReleaseInstanceEv(); +int APS5_VABI _ZN15AbstractStorage14StorageManager16NotifyWriteCloseERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager16RegisterListenerEPNS_17ItemEventListenerE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager16RegisterListenerEPNS_20StorageEventListenerE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager16get_storage_nameERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager17NotifySetMetadataERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager18GetStorageNameListEPSt6vectorISbIcSt11char_traitsIcENS_8StlAllocIcEEESaIS6_EE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager18UnregisterListenerEPKNS_17ItemEventListenerE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager18UnregisterListenerEPKNS_20StorageEventListenerE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager20get_service_instanceENS_11ServiceTypeE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager21NotifyStorageAttachedERKSbIcSt11char_traitsIcENS_8StlAllocIcEEENS_11ServiceTypeE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager21NotifyStorageDetachedERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager4FreeEPv(); +int APS5_VABI _ZN15AbstractStorage14StorageManager4LoadENS_11ServiceTypeE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager6MallocEm(); +int APS5_VABI _ZN15AbstractStorage14StorageManager6UnloadENS_11ServiceTypeE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager6UpdateENS_11ServiceTypeE(); +int APS5_VABI _ZN15AbstractStorage14StorageManager9TerminateEv(); +int APS5_VABI _ZN15AbstractStorage14StorageManagerC1Ev(); +int APS5_VABI _ZN15AbstractStorage14StorageManagerC2Ev(); +int APS5_VABI _ZN15AbstractStorage14StorageManagerD1Ev(); +int APS5_VABI _ZN15AbstractStorage14StorageManagerD2Ev(); +int APS5_VABI _ZN15AbstractStorage15HttpTransaction15SendHttpRequestERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEiRKSt3mapIS5_S5_St4lessIS5_ESaISt4pairIS6_S5_EEEmb(); +int APS5_VABI _ZN15AbstractStorage15HttpTransaction4InitEv(); +int APS5_VABI _ZN15AbstractStorage15HttpTransaction4TermEv(); +int APS5_VABI _ZN15AbstractStorage15HttpTransaction8SendDataEPKvm(); +int APS5_VABI _ZN15AbstractStorage15HttpTransactionC1Ev(); +int APS5_VABI _ZN15AbstractStorage15HttpTransactionC2Ev(); +int APS5_VABI _ZN15AbstractStorage15HttpTransactionD1Ev(); +int APS5_VABI _ZN15AbstractStorage15HttpTransactionD2Ev(); +int APS5_VABI _ZN15AbstractStorage4Item11GetPathNameEv(); +int APS5_VABI _ZN15AbstractStorage4Item11SetMetadataEPN23sceMetadataReaderWriter8MetadataE(); +int APS5_VABI _ZN15AbstractStorage4Item13ParseMetadataEPN23sceMetadataReaderWriter8MetadataES3_(); +int APS5_VABI _ZN15AbstractStorage4Item14GetStorageNameEv(); +int APS5_VABI _ZN15AbstractStorage4Item6RenameERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage4Item7GetNameERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage4Item7GetNameEv(); +int APS5_VABI _ZN15AbstractStorage4Item8set_metaERN23sceMetadataReaderWriter8MetadataERKSbIcSt11char_traitsIcENS_8StlAllocIcEEESA_(); +int APS5_VABI _ZN15AbstractStorage4Item8set_metaERN23sceMetadataReaderWriter8MetadataERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEm(); +int APS5_VABI _ZN15AbstractStorage4ItemC2ERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage4ItemD0Ev(); +int APS5_VABI _ZN15AbstractStorage4ItemD1Ev(); +int APS5_VABI _ZN15AbstractStorage4ItemD2Ev(); +int APS5_VABI _ZN15AbstractStorage4ItemdlEPv(); +int APS5_VABI _ZN15AbstractStorage4ItemnwEm(); +int APS5_VABI _ZN15AbstractStorage6FolderC2ERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage6FolderD0Ev(); +int APS5_VABI _ZN15AbstractStorage6FolderD1Ev(); +int APS5_VABI _ZN15AbstractStorage6FolderD2Ev(); +int APS5_VABI _ZN15AbstractStorage7AbsFreeEPv(); +int APS5_VABI _ZN15AbstractStorage7Content12GetExtensionERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage7Content12GetMimeTypesEv(); +int APS5_VABI _ZN15AbstractStorage7Content6RenameERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage7ContentC2ERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage7ContentD0Ev(); +int APS5_VABI _ZN15AbstractStorage7ContentD1Ev(); +int APS5_VABI _ZN15AbstractStorage7ContentD2Ev(); +int APS5_VABI _ZN15AbstractStorage7Service11NextStorageERSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage7Service13CreateStorageERKSbIcSt11char_traitsIcENS_8StlAllocIcEEEPSt10shared_ptrINS_7StorageEE(); +int APS5_VABI _ZN15AbstractStorage7Service18clear_storage_listEv(); +int APS5_VABI _ZN15AbstractStorage7Service4LoadENS_11ServiceTypeE(); +int APS5_VABI _ZN15AbstractStorage7Service4StopEv(); +int APS5_VABI _ZN15AbstractStorage7Service5StartEv(); +int APS5_VABI _ZN15AbstractStorage7Service6UnLoadEPS0_(); +int APS5_VABI _ZN15AbstractStorage7ServiceC2Ev(); +int APS5_VABI _ZN15AbstractStorage7ServiceD0Ev(); +int APS5_VABI _ZN15AbstractStorage7ServiceD1Ev(); +int APS5_VABI _ZN15AbstractStorage7ServiceD2Ev(); +int APS5_VABI _ZN15AbstractStorage7StorageC2ERKSbIcSt11char_traitsIcENS_8StlAllocIcEEE(); +int APS5_VABI _ZN15AbstractStorage7StorageD0Ev(); +int APS5_VABI _ZN15AbstractStorage7StorageD1Ev(); +int APS5_VABI _ZN15AbstractStorage7StorageD2Ev(); +int APS5_VABI _ZN15AbstractStorage9AbsMallocEm(); +int APS5_VABI _ZNK15AbstractStorage14MemfileContent10GetServiceEv(); +int APS5_VABI _ZNK15AbstractStorage14MemfileContent13GetCapabilityEv(); +int APS5_VABI _ZNK15AbstractStorage14MemfileContent7GetStatEPNS_4StatE(); +int APS5_VABI _ZNK15AbstractStorage4Item12GetThumbnailEPSt10shared_ptrINS_7ContentEE(); +int APS5_VABI _ZNK15AbstractStorage4Item15CreateThumbnailEPvPSt10shared_ptrINS_7ContentEE(); +int APS5_VABI _ZNK15AbstractStorage6Folder7GetTypeEv(); +int APS5_VABI _ZNK15AbstractStorage7Content7GetTypeEv(); +int APS5_VABI _ZNK15AbstractStorage7Storage7GetTypeEv(); + +} diff --git a/core/libs/prx/libSceAbstractStorage/libSceAbstractStorage.hpp b/core/libs/prx/libSceAbstractStorage/libSceAbstractStorage.hpp index 02bce84cd0..476baa2df9 100644 --- a/core/libs/prx/libSceAbstractStorage/libSceAbstractStorage.hpp +++ b/core/libs/prx/libSceAbstractStorage/libSceAbstractStorage.hpp @@ -1,3 +1,4 @@ #pragma once +#include "prx/libSceAbstractStorage/include/Api.hpp" #include "prx/libSceAbstractStorage/include/InventoryExports.hpp" #include "prx/libSceAbstractStorage/include/LegacyExports.hpp" diff --git a/core/libs/prx/libSceAbstractYoutube/Export.cpp b/core/libs/prx/libSceAbstractYoutube/Export.cpp index 6479276d85..ea799bd6e8 100644 --- a/core/libs/prx/libSceAbstractYoutube/Export.cpp +++ b/core/libs/prx/libSceAbstractYoutube/Export.cpp @@ -1,3 +1,6 @@ +#include "prx/libSceAbstractYoutube/include/LegacyExports.hpp" +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libSceAbstractYoutube/include/InventoryExports.hpp" #include #include #include "SceTypes.hpp" @@ -92,3 +95,267 @@ int APS5_VABI sceAbstractYoutubeUnknown_vJGnqFj6ygE_nid_no_patch() { APS5_EXPORT("vJGnqFj6ygE", sceAbstractYoutubeUnknown_vJGnqFj6ygE_nid_no_patch); } + +extern "C" { + +int APS5_VABI _ZN15AbstractStorage13YoutubeFolder6RemoveEb() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage13YoutubeFolderD0Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage13YoutubeFolderD1Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage13YoutubeFolderD2Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent11SetMetadataEPN23sceMetadataReaderWriter8MetadataE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent13ParseMetadataEPN23sceMetadataReaderWriter8MetadataES3_() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent4OpenEit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent4ReadEPvlPl() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent5CloseEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent5FlushEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent5WriteEPKvlPl() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent6RemoveEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent8SeekByteEliPl() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContent8SeekTimeElPl() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContentD0Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContentD1Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeContentD2Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeService13SearchStorageEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeService4StopEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeService5StartEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceC1Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceC2Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceD0Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceD1Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceD2Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeStorage14GetStorageSizeEPlS1_S1_() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeStorageD0Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeStorageD1Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN15AbstractStorage14YoutubeStorageD2Ev() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage13YoutubeFolder10GetServiceEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage13YoutubeFolder13GetCapabilityEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage13YoutubeFolder7GetStatEPNS_4StatE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage14YoutubeContent10GetServiceEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage14YoutubeContent13GetCapabilityEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage14YoutubeContent7GetStatEPNS_4StatE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage14YoutubeService14GetServiceTypeEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage14YoutubeStorage10GetServiceEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage14YoutubeStorage13GetCapabilityEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK15AbstractStorage14YoutubeStorage7GetStatEPNS_4StatE() { + NotImplemented_nid_no_patch(__func__); +} + +} + +extern "C" { + +int APS5_VABI abstractYoutubeLegacyUnknown__plus_XiOtdILEHI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+XiOtdILEHI", abstractYoutubeLegacyUnknown__plus_XiOtdILEHI_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_4orcGYZG2cE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4orcGYZG2cE", abstractYoutubeLegacyUnknown_4orcGYZG2cE_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_6svBaAI4Ggw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6svBaAI4Ggw", abstractYoutubeLegacyUnknown_6svBaAI4Ggw_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_CCv9CYanMNI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("CCv9CYanMNI", abstractYoutubeLegacyUnknown_CCv9CYanMNI_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_EEx55k0zSLo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("EEx55k0zSLo", abstractYoutubeLegacyUnknown_EEx55k0zSLo_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_HYDApQYDgcI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HYDApQYDgcI", abstractYoutubeLegacyUnknown_HYDApQYDgcI_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_IGnFlGNW_plus_Vo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("IGnFlGNW+Vo", abstractYoutubeLegacyUnknown_IGnFlGNW_plus_Vo_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_JzBDUIC5xok_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("JzBDUIC5xok", abstractYoutubeLegacyUnknown_JzBDUIC5xok_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_NPYN89qO2Ks_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NPYN89qO2Ks", abstractYoutubeLegacyUnknown_NPYN89qO2Ks_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_P_plus_ZjKSLi5lg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("P+ZjKSLi5lg", abstractYoutubeLegacyUnknown_P_plus_ZjKSLi5lg_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_RkaqMMDQJp4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("RkaqMMDQJp4", abstractYoutubeLegacyUnknown_RkaqMMDQJp4_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_ZpVRh3D86UY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZpVRh3D86UY", abstractYoutubeLegacyUnknown_ZpVRh3D86UY_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_ZwkYXHwQ7Mc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZwkYXHwQ7Mc", abstractYoutubeLegacyUnknown_ZwkYXHwQ7Mc_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_cktbTmca48I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cktbTmca48I", abstractYoutubeLegacyUnknown_cktbTmca48I_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_fUXj4GBxyHs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("fUXj4GBxyHs", abstractYoutubeLegacyUnknown_fUXj4GBxyHs_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_g58APdyJ_minus_5Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("g58APdyJ-5Y", abstractYoutubeLegacyUnknown_g58APdyJ_minus_5Y_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_hXCJIJZZ5MY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hXCJIJZZ5MY", abstractYoutubeLegacyUnknown_hXCJIJZZ5MY_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_kgeuOe7oln8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("kgeuOe7oln8", abstractYoutubeLegacyUnknown_kgeuOe7oln8_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_pR9ULw2VrSs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("pR9ULw2VrSs", abstractYoutubeLegacyUnknown_pR9ULw2VrSs_nid_no_patch); + +int APS5_VABI abstractYoutubeLegacyUnknown_tjbj7QysZwk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("tjbj7QysZwk", abstractYoutubeLegacyUnknown_tjbj7QysZwk_nid_no_patch); + +} diff --git a/core/libs/prx/libSceAbstractYoutube/include/InventoryExports.hpp b/core/libs/prx/libSceAbstractYoutube/include/InventoryExports.hpp new file mode 100644 index 0000000000..7fb585ade6 --- /dev/null +++ b/core/libs/prx/libSceAbstractYoutube/include/InventoryExports.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI _ZN15AbstractStorage13YoutubeFolder6RemoveEb(); +int APS5_VABI _ZN15AbstractStorage13YoutubeFolderD0Ev(); +int APS5_VABI _ZN15AbstractStorage13YoutubeFolderD1Ev(); +int APS5_VABI _ZN15AbstractStorage13YoutubeFolderD2Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent11SetMetadataEPN23sceMetadataReaderWriter8MetadataE(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent13ParseMetadataEPN23sceMetadataReaderWriter8MetadataES3_(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent4OpenEit(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent4ReadEPvlPl(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent5CloseEv(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent5FlushEv(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent5WriteEPKvlPl(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent6RemoveEv(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent8SeekByteEliPl(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContent8SeekTimeElPl(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContentD0Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContentD1Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeContentD2Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeService13SearchStorageEv(); +int APS5_VABI _ZN15AbstractStorage14YoutubeService4StopEv(); +int APS5_VABI _ZN15AbstractStorage14YoutubeService5StartEv(); +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceC1Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceC2Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceD0Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceD1Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeServiceD2Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeStorage14GetStorageSizeEPlS1_S1_(); +int APS5_VABI _ZN15AbstractStorage14YoutubeStorageD0Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeStorageD1Ev(); +int APS5_VABI _ZN15AbstractStorage14YoutubeStorageD2Ev(); +int APS5_VABI _ZNK15AbstractStorage13YoutubeFolder10GetServiceEv(); +int APS5_VABI _ZNK15AbstractStorage13YoutubeFolder13GetCapabilityEv(); +int APS5_VABI _ZNK15AbstractStorage13YoutubeFolder7GetStatEPNS_4StatE(); +int APS5_VABI _ZNK15AbstractStorage14YoutubeContent10GetServiceEv(); +int APS5_VABI _ZNK15AbstractStorage14YoutubeContent13GetCapabilityEv(); +int APS5_VABI _ZNK15AbstractStorage14YoutubeContent7GetStatEPNS_4StatE(); +int APS5_VABI _ZNK15AbstractStorage14YoutubeService14GetServiceTypeEv(); +int APS5_VABI _ZNK15AbstractStorage14YoutubeStorage10GetServiceEv(); +int APS5_VABI _ZNK15AbstractStorage14YoutubeStorage13GetCapabilityEv(); +int APS5_VABI _ZNK15AbstractStorage14YoutubeStorage7GetStatEPNS_4StatE(); + +} diff --git a/core/libs/prx/libSceAbstractYoutube/include/LegacyExports.hpp b/core/libs/prx/libSceAbstractYoutube/include/LegacyExports.hpp new file mode 100644 index 0000000000..bdf94b0e20 --- /dev/null +++ b/core/libs/prx/libSceAbstractYoutube/include/LegacyExports.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI abstractYoutubeLegacyUnknown__plus_XiOtdILEHI_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_4orcGYZG2cE_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_6svBaAI4Ggw_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_CCv9CYanMNI_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_EEx55k0zSLo_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_HYDApQYDgcI_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_IGnFlGNW_plus_Vo_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_JzBDUIC5xok_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_NPYN89qO2Ks_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_P_plus_ZjKSLi5lg_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_RkaqMMDQJp4_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_ZpVRh3D86UY_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_ZwkYXHwQ7Mc_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_cktbTmca48I_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_fUXj4GBxyHs_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_g58APdyJ_minus_5Y_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_hXCJIJZZ5MY_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_kgeuOe7oln8_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_pR9ULw2VrSs_nid_no_patch(); +int APS5_VABI abstractYoutubeLegacyUnknown_tjbj7QysZwk_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceAbstractYoutube/libSceAbstractYoutube.hpp b/core/libs/prx/libSceAbstractYoutube/libSceAbstractYoutube.hpp index 6f70f09bee..76c0ec1754 100644 --- a/core/libs/prx/libSceAbstractYoutube/libSceAbstractYoutube.hpp +++ b/core/libs/prx/libSceAbstractYoutube/libSceAbstractYoutube.hpp @@ -1 +1,3 @@ #pragma once +#include "prx/libSceAbstractYoutube/include/InventoryExports.hpp" +#include "prx/libSceAbstractYoutube/include/LegacyExports.hpp" diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index 2f7ff816d6..12ee608c54 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -91,6 +91,19 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_draw_setup_tests EXCLUDE_FROM_ALL tests/DrawSetup.cpp) +target_include_directories(agc_draw_setup_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_draw_setup_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_draw_setup_tests) +if(BUILD_TESTING) + set_target_properties(agc_draw_setup_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_draw_setup COMMAND agc_draw_setup_tests) + if(WIN32) + set_tests_properties(agc_draw_setup PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_rewind_patch_tests EXCLUDE_FROM_ALL tests/RewindPatch.cpp) target_include_directories(agc_rewind_patch_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_rewind_patch_tests PRIVATE libSceAgc libc) @@ -129,6 +142,19 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +add_test_executable(agc_copy_data_tests EXCLUDE_FROM_ALL tests/CopyData.cpp) +target_include_directories(agc_copy_data_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_copy_data_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_copy_data_tests) +if(BUILD_TESTING) + set_target_properties(agc_copy_data_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_copy_data COMMAND agc_copy_data_tests) + if(WIN32) + set_tests_properties(agc_copy_data PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() configure_windows_unwind(libSceAgc) add_test_executable(agc_static_buffer_tests EXCLUDE_FROM_ALL tests/StaticBuffer.cpp) @@ -170,6 +196,19 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(agc_nop_and_compute_event_tests EXCLUDE_FROM_ALL tests/NopAndComputeEvent.cpp) +target_include_directories(agc_nop_and_compute_event_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_nop_and_compute_event_tests PRIVATE libSceAgc libc) +configure_windows_unwind(agc_nop_and_compute_event_tests) +if(BUILD_TESTING) + set_target_properties(agc_nop_and_compute_event_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_nop_and_compute_event COMMAND agc_nop_and_compute_event_tests) + if(WIN32) + set_tests_properties(agc_nop_and_compute_event PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(agc_packet_info_tests EXCLUDE_FROM_ALL tests/PacketInfo.cpp) target_include_directories(agc_packet_info_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(agc_packet_info_tests PRIVATE libSceAgc libc) diff --git a/core/libs/prx/libSceAgc/Misc/include/ShaderFusion.hpp b/core/libs/prx/libSceAgc/Misc/include/ShaderFusion.hpp index d90e4d427e..3d7ee1dc21 100644 --- a/core/libs/prx/libSceAgc/Misc/include/ShaderFusion.hpp +++ b/core/libs/prx/libSceAgc/Misc/include/ShaderFusion.hpp @@ -18,7 +18,6 @@ extern "C" int APS5_VABI sceAgcGetFusedShaderSize_0080(SizeAlign* dst, const Sha 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); } diff --git a/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp b/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp index 0333b87862..56e5ed83ce 100644 --- a/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp +++ b/core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp @@ -354,12 +354,4 @@ int APS5_VABI sceAgcGetFusedShaderSize_0080(SizeAlign* dst, const Shader* front, return 0; } -int APS5_VABI sceAgcUpdateInterpolantMapping_0100(ShaderRegister* regs, const Shader* vs, const Shader* ps) { - (void)regs; - (void)vs; - (void)ps; - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceAgc/Shader/include/InterpolantMapping.hpp b/core/libs/prx/libSceAgc/Shader/include/InterpolantMapping.hpp index 7085d35fe0..66b549a9a4 100644 --- a/core/libs/prx/libSceAgc/Shader/include/InterpolantMapping.hpp +++ b/core/libs/prx/libSceAgc/Shader/include/InterpolantMapping.hpp @@ -7,6 +7,7 @@ extern "C" { int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Shader* gs, const Shader* ps); +int APS5_VABI sceAgcUpdateInterpolantMapping_0100(ShaderRegister* regs, const Shader* vs, 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); diff --git a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp index 65b337d335..6d05a7a681 100644 --- a/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp @@ -58,6 +58,12 @@ std::uint32_t CreateInterpolantValueFlatDefault(std::uint32_t psWord, const Shad return ((psWord & 0x00700000u) == 0x00400000u) ? (value | 0x00000020u) : value; } +std::uint32_t ApplyInterpolantDefaultValue0100(std::uint32_t value, std::uint32_t psWord) { + value &= ~0x00000300u; + value |= ((psWord >> 28u) & 0x3u) << 8u; + return value; +} + int CreateInterpolantMapping(const char* fn, ShaderRegister* regs, const Shader* gs, const Shader* ps, CreateInterpolantValueFn createValue, bool fillIdentityTail) { if (regs == nullptr) { throw std::runtime_error(std::string(fn) + ": regs is null"); @@ -124,6 +130,37 @@ int APS5_VABI sceAgcCreateInterpolantMapping_0100(ShaderRegister* regs, const Sh return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValue, true); } +int APS5_VABI sceAgcUpdateInterpolantMapping_0100(ShaderRegister* regs, const Shader* vs, const Shader* ps) { + if (regs == nullptr) { + throw std::runtime_error(std::string(__func__) + ": regs is null"); + } + if (ps == nullptr) { + throw std::runtime_error(std::string(__func__) + ": ps is null"); + } + if (ps->num_input_semantics > InterpolantRegisterCount) { + throw std::runtime_error(std::string(__func__) + ": input semantic count exceeds 32"); + } + if (ps->num_input_semantics == 0) { + return 0; + } + if (ps->input_semantics == nullptr) { + throw std::runtime_error(std::string(__func__) + ": ps->input_semantics is null but num_input_semantics != 0"); + } + if (vs == nullptr) { + throw std::runtime_error(std::string(__func__) + ": vs is null"); + } + if (vs->num_output_semantics != 0 && vs->output_semantics == nullptr) { + throw std::runtime_error(std::string(__func__) + ": vs->output_semantics is null but num_output_semantics != 0"); + } + for (std::uint32_t i = 0; i < ps->num_input_semantics; ++i) { + const ShaderSemantic& psSemantic = ps->input_semantics[i]; + const std::uint32_t psWord = ShaderSemanticWord(psSemantic); + const ShaderSemantic* vsSemantic = FindOutputSemantic(vs, psSemantic.semantic); + SetInterpolantRegister(regs, i, ApplyInterpolantDefaultValue0100(CreateInterpolantValue(psWord, vsSemantic), psWord)); + } + return 0; +} + int APS5_VABI sceAgcCreateInterpolantMapping_10000(ShaderRegister* regs, const Shader* gs, const Shader* ps) { return CreateInterpolantMapping(__func__, regs, gs, ps, CreateInterpolantValueSplitF16, true); } diff --git a/core/libs/prx/libSceAgc/tests/Commands.cpp b/core/libs/prx/libSceAgc/tests/Commands.cpp index 018e41bed2..72f3e4a785 100644 --- a/core/libs/prx/libSceAgc/tests/Commands.cpp +++ b/core/libs/prx/libSceAgc/tests/Commands.cpp @@ -30,6 +30,8 @@ #include "prx/libSceAgc/Acb/include/Sync.hpp" #include "prx/libSceAgc/Command/include/Packet.hpp" #include "prx/libSceAgc/Command/include/Memory.hpp" +#include "prx/libSceAgc/Acb/include/Memory.hpp" +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" #include "prx/libSceAgc/Command/include/RegisterDefaults.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -835,6 +837,45 @@ void testAtomicGds() { } } +void testCopyDataAndComputeEvents() { + Storage graphics; + const auto* copy = sceAgcDcbCopyData(&graphics.buffer, 4, 1, 0x1122334450ull, 0xb, 2, 0x12345678u, 0, 1); + const std::array graphicsExpected{0xc0044000u, 0x42104205u, 0x12345678u, 0, 0x22334450u, 0x11u}; + check(copy == graphics.words.data() && std::equal(graphicsExpected.begin(), graphicsExpected.end(), graphics.words.begin()), "graphics COPY_DATA packet mismatch"); + Storage wide; + sceAgcDcbCopyData(&wide.buffer, 4, 0, 0x1000, 0xa, 0, 0x100000000ull, 1, 0); + check(wide.words[1] == 0x00010205u && wide.words[2] == 0 && wide.words[3] == 1, "a 64-bit immediate COPY_DATA lost its high word"); + Storage compute; + sceAgcAcbCopyData(&compute.buffer, 2, 0, 0x4000, 5, 0, 7, 0, 0); + const std::array computeExpected{0xc0044000u, 0x205u, 7, 0, 0x4000u, 0}; + check(std::equal(computeExpected.begin(), computeExpected.end(), compute.words.begin()), "compute COPY_DATA packet mismatch"); + Storage rejected; + rejected.words.fill(0xa5a5a5a5u); + const auto rejectedWords = rejected.words; + const auto* rejectedCursor = rejected.buffer.cursor_up; + expectFailure([&] { sceAgcDcbCopyData(&rejected.buffer, 4, 4, 0x1000, 0xa, 0, 1, 0, 0); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + expectFailure([&] { sceAgcDcbCopyData(&rejected.buffer, 4, 0, 0x1000, 0xa, 4, 1, 0, 0); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + expectFailure([&] { sceAgcAcbCopyData(&rejected.buffer, 2, 0, 0x4000, 5, 0, 1, 2, 0); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + expectFailure([&] { sceAgcAcbCopyData(&rejected.buffer, 2, 0, 0x4000, 5, 0, 1, 0, 2); }); + check(rejected.words == rejectedWords && rejected.buffer.cursor_up == rejectedCursor, "a refused packet changed the command buffer"); + check(sceAgcDcbCopyDataGetSize() == 24 && sceAgcAcbCopyDataGetSize() == 24, "COPY_DATA packet sizes"); + Storage events; + sceAgcAcbEventWrite(&events.buffer, 7, nullptr); + sceAgcAcbEventWrite(&events.buffer, 0x16, nullptr); + const std::array eventsExpected{0xc0004600u, 0x407u, 0xc0004600u, 0x16u}; + check(std::equal(eventsExpected.begin(), eventsExpected.end(), events.words.begin()), "compute EVENT_WRITE packet mismatch"); + Storage refused; + refused.words.fill(0xa5a5a5a5u); + const auto refusedWords = refused.words; + const auto* refusedCursor = refused.buffer.cursor_up; + expectFailure([&] { sceAgcAcbEventWrite(&refused.buffer, 0x40, nullptr); }); + check(refused.words == refusedWords && refused.buffer.cursor_up == refusedCursor, "a refused packet changed the command buffer"); + check(sceAgcAcbEventWriteGetSize(7) == 8, "compute EVENT_WRITE packet size"); +} + void testDebugRaiseException() { int result = -1; try { @@ -1080,6 +1121,7 @@ int main(int argc, char** argv) { testEndOfPipePatches(); testAtomicGds(); testDebugRaiseException(); + testCopyDataAndComputeEvents(); testDefaults(); testDefaultsTypes(); LibcRunShutdown_nid_postfix(); diff --git a/core/libs/prx/libSceAgc/tests/CopyData.cpp b/core/libs/prx/libSceAgc/tests/CopyData.cpp new file mode 100644 index 0000000000..c5c9b958ab --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/CopyData.cpp @@ -0,0 +1,81 @@ +#include "prx/libSceAgc/Acb/include/Memory.hpp" +#include "prx/libSceAgc/Command/include/Packet.hpp" +#include "prx/libSceAgc/DcbFlow/include/Memory.hpp" + +#include +#include +#include +#include +#include + +namespace { + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; +}; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(Storage& storage, TAction action, const char* message) { + const auto before = storage.words; + const auto* cursor = storage.buffer.cursor_up; + try { + action(); + } catch (const std::runtime_error&) { + check(storage.words == before && storage.buffer.cursor_up == cursor, message); + return; + } + throw std::runtime_error(message); +} + +void testDrawQueueCopy() { + Storage storage; + const auto* packet = sceAgcDcbCopyData(&storage.buffer, 2, 1, 0x123456789ab0ull, 5, 2, 0xdeadbeef00000010ull, 1, 1); + const std::array expected{0xc0044000u, 0x42114102u, 0x10u, 0xdeadbeefu, 0x56789ab0u, 0x1234u}; + check(packet == storage.words.data() && std::equal(expected.begin(), expected.end(), packet), "DCB COPY_DATA packet mismatch"); + check(storage.buffer.cursor_up == storage.words.data() + expected.size(), "DCB COPY_DATA cursor advance mismatch"); + check(sceAgcDcbCopyDataGetSize() == expected.size() * sizeof(std::uint32_t), "DCB COPY_DATA size differs from its packet"); + expectFailure(storage, [&] { sceAgcDcbCopyData(&storage.buffer, 2, 4, 0x1000, 4, 0, 0x2000, 0, 0); }, "a DCB destination cache policy above 3 was accepted"); + expectFailure(storage, [&] { sceAgcDcbCopyData(&storage.buffer, 2, 0, 0x1000, 4, 0, 0x2000, 2, 0); }, "a DCB item size above 1 was accepted"); + expectFailure(storage, [&] { sceAgcDcbCopyData(&storage.buffer, 2, 0, 0x1000, 10, 0, 0x100000000ull, 0, 0); }, "a DCB 32-bit immediate wider than 32 bits was accepted"); +} + +void testComputeQueueCopy() { + Storage storage; + const auto* packet = sceAgcAcbCopyData(&storage.buffer, 1, 0, 0x1000, 5, 0, 0x1234, 0, 0); + const std::array expected{0xc0044000u, 0x105u, 0x1234u, 0, 0x1000u, 0}; + check(packet == storage.words.data() && std::equal(expected.begin(), expected.end(), packet), "ACB COPY_DATA packet mismatch"); + check(storage.buffer.cursor_up == storage.words.data() + expected.size(), "ACB COPY_DATA cursor advance mismatch"); + check(sceAgcAcbCopyDataGetSize() == expected.size() * sizeof(std::uint32_t), "ACB COPY_DATA size differs from its packet"); + const auto* wide = sceAgcAcbCopyData(&storage.buffer, 1, 0, 0x1000, 5, 0, 0x100000000ull, 1, 0); + check(wide == storage.words.data() + expected.size() && wide[1] == 0x10105u && wide[2] == 0 && wide[3] == 1, "ACB 64-bit immediate COPY_DATA packet mismatch"); + expectFailure(storage, [&] { sceAgcAcbCopyData(&storage.buffer, 1, 0, 0x1000, 5, 0, 0x100000000ull, 0, 0); }, "an ACB 32-bit immediate wider than 32 bits was accepted"); + expectFailure(storage, [&] { sceAgcAcbCopyData(&storage.buffer, 1, 0, 0x1000, 5, 0, 0, 0, 2); }, "an ACB write confirm above 1 was accepted"); +} + +void testWriteDataSize() { + Storage storage; + const std::array payload{1, 2, 3}; + const auto* packet = sceAgcDcbWriteData(&storage.buffer, 2, 0, 0x2000, payload.data(), static_cast(payload.size()), 1, 0); + const std::array expected{0xc0053700u, 0x10100u, 0x2000u, 0, 1, 2, 3}; + check(packet == storage.words.data() && std::equal(expected.begin(), expected.end(), packet), "DCB WRITE_DATA packet mismatch"); + check(sceAgcDcbWriteDataGetSize(static_cast(payload.size())) == expected.size() * sizeof(std::uint32_t), "DCB WRITE_DATA size differs from its packet"); + check(sceAgcDcbWriteDataGetSize(1) == 20 && sceAgcDcbWriteDataGetSize(0x3ffd) == 0x3ffd * 4 + 16, "DCB WRITE_DATA size is not four header words plus the payload"); +} + +} + +int main() { + try { + testDrawQueueCopy(); + testComputeQueueCopy(); + testWriteDataSize(); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgc/tests/DrawSetup.cpp b/core/libs/prx/libSceAgc/tests/DrawSetup.cpp new file mode 100644 index 0000000000..a97371bae9 --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/DrawSetup.cpp @@ -0,0 +1,88 @@ +#include "prx/libSceAgc/Command/include/Packet.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 +#include +#include +#include +#include + +namespace { + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; +}; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(Storage& storage, TAction action, const char* message) { + const auto before = storage.words; + const auto* cursor = storage.buffer.cursor_up; + try { + action(); + } catch (const std::runtime_error&) { + check(storage.words == before && storage.buffer.cursor_up == cursor, message); + return; + } + throw std::runtime_error(message); +} + +template +void expectPacket(const Storage& storage, const std::uint32_t* packet, const std::uint32_t* start, const std::array& expected, std::uint32_t size, const char* message) { + check(packet == start && std::equal(expected.begin(), expected.end(), packet), message); + check(storage.buffer.cursor_up == start + N, message); + check(size == N * sizeof(std::uint32_t), message); +} + +void testNumInstances() { + Storage storage; + const auto* packet = sceAgcDcbSetNumInstances(&storage.buffer, 7); + expectPacket(storage, packet, storage.words.data(), std::array{0xc0002f00u, 7}, sceAgcDcbSetNumInstancesGetSize(), "NUM_INSTANCES packet mismatch"); +} + +void testIndexSize() { + Storage storage; + const auto* packet = sceAgcDcbSetIndexSize(&storage.buffer, 1, 2); + expectPacket(storage, packet, storage.words.data(), std::array{0xc0017a00u, 0x20000243u, 0x481u}, sceAgcDcbSetIndexSizeGetSize(), "index size packet mismatch"); + const auto* objectId = sceAgcDcbSetIndexSize_0600(&storage.buffer, 2, 3, 1); + check(objectId == storage.words.data() + 3 && objectId[0] == 0xc0017a00u && objectId[1] == 0x20000243u && objectId[2] == (0x4c2u | 0x4000u), "index size packet with a per-instance object id mismatch"); + expectFailure(storage, [&] { sceAgcDcbSetIndexSize(&storage.buffer, 3, 0); }, "an index size above 2 was accepted"); + expectFailure(storage, [&] { sceAgcDcbSetIndexSize(&storage.buffer, 0, 4); }, "a cache policy above 3 was accepted"); + expectFailure(storage, [&] { sceAgcDcbSetIndexSize_0600(&storage.buffer, 0, 0, 2); }, "a per-instance object id above 1 was accepted"); +} + +void testDrawIndexOffset() { + Storage storage; + const auto* packet = sceAgcDcbDrawIndexOffset(&storage.buffer, 12, 36, 0); + expectPacket(storage, packet, storage.words.data(), std::array{0xc0033500u, 36, 12, 36, 0}, sceAgcDcbDrawIndexOffsetGetSize(), "DRAW_INDEX_OFFSET_2 packet mismatch"); + const auto* empty = sceAgcDcbDrawIndexOffset(&storage.buffer, 0, 0, 0); + check(empty == storage.words.data() + 5 && empty[1] == 1 && empty[3] == 0, "an empty indexed offset draw did not keep a maximum size of one"); +} + +void testDrawIndirect() { + Storage storage; + const auto* packet = sceAgcDcbDrawIndirect(&storage.buffer, 0x40, 0); + expectPacket(storage, packet, storage.words.data(), std::array{0xc0032400u, 0x40, 0x280, 0x280, 2}, sceAgcDcbDrawIndirectGetSize(), "DRAW_INDIRECT packet mismatch"); + expectFailure(storage, [&] { sceAgcDcbDrawIndirect(&storage.buffer, 2, 0); }, "a misaligned indirect argument offset was accepted"); +} + +} + +int main() { + try { + testNumInstances(); + testIndexSize(); + testDrawIndexOffset(); + testDrawIndirect(); + } 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 2fbd372623..697d292472 100644 --- a/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp +++ b/core/libs/prx/libSceAgc/tests/InterpolantMapping.cpp @@ -162,6 +162,81 @@ void testUpdateMapping() { check(update[1].value == 0x01380007u && update[3].value == 0x03680409u, "f16 DEFAULT_VAL does not come from the low default"); } +void testUpdate0100Mapping() { + const auto mappingsBefore = mappings; + const auto linksBefore = links; + Shader ps{}; + auto regs = filled(); + const auto untouched = filled(); + check(sceAgcUpdateInterpolantMapping_0100(regs.data(), nullptr, &ps) == 0, "_0100 update without inputs failed"); + check(std::memcmp(regs.data(), untouched.data(), sizeof(regs)) == 0, "_0100 update without inputs wrote registers"); + + std::array inputs{semantic(1, 0, 0), semantic(2, 0, 1), semantic(3, 0, 2), semantic(4, 0, 3)}; + inputs[0].is_custom = 1; + inputs[0].default_value = 2; + inputs[1].default_value = 1; + inputs[1].default_value_hi = 1; + inputs[2].default_value_hi = 2; + inputs[3].is_custom = 1; + inputs[3].default_value = 3; + inputs[3].default_value_hi = 3; + std::array outputs{semantic(1, 5, 0), semantic(2, 7, 1), semantic(4, 9, 3)}; + Shader vs{}; + vs.output_semantics = outputs.data(); + vs.num_output_semantics = static_cast(outputs.size()); + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + + regs = filled(); + check(sceAgcUpdateInterpolantMapping_0100(regs.data(), &vs, &ps) == 0, "_0100 update mapping failed"); + check(regs[0].value == 0x00000625u, "_0100 update does not encode default value 2"); + check(regs[1].value == 0x01380107u, "_0100 update does not encode default value 1"); + check(regs[2].value == 0x02580020u, "_0100 unmatched interpolant update changed"); + check(regs[3].value == 0x03680709u, "_0100 full f16 interpolant update changed"); + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "_0100 update register offset changed"); + } + const std::size_t written = inputs.size() * sizeof(ShaderRegister); + check(std::memcmp(regs.data() + inputs.size(), untouched.data(), sizeof(regs) - written) == 0, "_0100 update wrote past its inputs"); + check(mappings == mappingsBefore && links == linksBefore, "_0100 update resolved an ABI"); +} + +void testUpdate0100Boundary() { + const auto mappingsBefore = mappings; + const auto linksBefore = links; + std::array inputs{}; + std::array outputs{}; + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + inputs[i] = semantic(i + 1u, 0, 0); + inputs[i].default_value = i % 4u; + outputs[i] = semantic(inputs.size() - i, i, 0); + } + Shader ps{}; + ps.input_semantics = inputs.data(); + ps.num_input_semantics = static_cast(inputs.size()); + Shader vs{}; + std::array regs{}; + std::memset(regs.data(), 0xcc, sizeof(regs)); + const auto untouched = regs; + check(sceAgcUpdateInterpolantMapping_0100(regs.data(), &vs, &ps) == 0, "_0100 update rejected a producer without outputs"); + constexpr std::array defaults{0x000u, 0x100u, 0x200u, 0x300u}; + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "_0100 full default mapping register offset changed"); + check(regs[i].value == (0x20u | defaults[i % 4u]), "_0100 full default mapping changed"); + } + check(std::memcmp(®s.back(), &untouched.back(), sizeof(ShaderRegister)) == 0, "_0100 full default mapping wrote past 32 inputs"); + vs.output_semantics = outputs.data(); + vs.num_output_semantics = static_cast(outputs.size()); + regs = untouched; + check(sceAgcUpdateInterpolantMapping_0100(regs.data(), &vs, &ps) == 0, "_0100 update rejected 32 matched inputs"); + for (std::uint32_t i = 0; i < inputs.size(); ++i) { + check(regs[i].offset == ShaderRegs::SPI_PS_INPUT_CNTL_0 + i, "_0100 full matched mapping register offset changed"); + check(regs[i].value == ((31u - i) | defaults[i % 4u]), "_0100 full matched mapping changed"); + } + check(std::memcmp(®s.back(), &untouched.back(), sizeof(ShaderRegister)) == 0, "_0100 full matched mapping wrote past 32 inputs"); + check(mappings == mappingsBefore && links == linksBefore, "_0100 full mapping resolved an ABI"); +} + 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)}; @@ -207,6 +282,7 @@ void testRejections() { std::memset(regs.data(), 0xcc, sizeof(regs)); const auto saved = regs; expectFailure([&] { sceAgcCreateInterpolantMapping_0100(regs.data(), &gs, &ps); }); + expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), &gs, &ps); }); expectFailure([&] { sceAgcCreateInterpolantMapping_10000(regs.data(), &gs, &ps); }); expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(regs.data(), &gs, &ps); }); check(std::memcmp(regs.data(), saved.data(), sizeof(regs)) == 0, "rejected mapping wrote registers"); @@ -216,6 +292,15 @@ void testRejections() { expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(regs.data(), nullptr, &ps); }); expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(nullptr, &gs, &ps); }); expectFailure([&] { sceAgcUpdateInterpolantMapping_10000(regs.data(), &gs, nullptr); }); + expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), nullptr, &ps); }); + expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(nullptr, &gs, &ps); }); + expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), &gs, nullptr); }); + ps.input_semantics = nullptr; + expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), &gs, &ps); }); + ps.input_semantics = inputs.data(); + gs.output_semantics = nullptr; + gs.num_output_semantics = 1; + expectFailure([&] { sceAgcUpdateInterpolantMapping_0100(regs.data(), &gs, &ps); }); } } @@ -238,6 +323,8 @@ int main() { testIdentity(); testMapping(); testUpdateMapping(); + testUpdate0100Mapping(); + testUpdate0100Boundary(); testUnversionedMapping(); testRejections(); check(mappings == 10u && links == 8u, "mapping preparation call count changed"); diff --git a/core/libs/prx/libSceAgc/tests/NopAndComputeEvent.cpp b/core/libs/prx/libSceAgc/tests/NopAndComputeEvent.cpp new file mode 100644 index 0000000000..7a3e207b4f --- /dev/null +++ b/core/libs/prx/libSceAgc/tests/NopAndComputeEvent.cpp @@ -0,0 +1,70 @@ +#include "prx/libSceAgc/Acb/include/Sync.hpp" +#include "prx/libSceAgc/Cb/include/Sync.hpp" +#include "prx/libSceAgc/Command/include/Packet.hpp" + +#include +#include +#include +#include +#include + +namespace { + +struct Storage { + std::array words{}; + CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0}; +}; + +void check(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +template +void expectFailure(Storage& storage, TAction action, const char* message) { + const auto before = storage.words; + const auto* cursor = storage.buffer.cursor_up; + try { + action(); + } catch (const std::runtime_error&) { + check(storage.words == before && storage.buffer.cursor_up == cursor, message); + return; + } + throw std::runtime_error(message); +} + +void testNop() { + Storage storage; + storage.words.fill(0xaaaaaaaau); + const auto* single = sceAgcCbNop_nid_postfix(&storage.buffer, 1); + check(single == storage.words.data() && single[0] == 0xffff1000u && storage.words[1] == 0xaaaaaaaau, "one-dword NOP packet mismatch"); + check(sceAgcCbNopGetSize(1) == sizeof(std::uint32_t), "one-dword NOP size mismatch"); + const auto* padding = sceAgcCbNop_nid_postfix(&storage.buffer, 4); + const std::array expected{0xc0021000u, 0, 0, 0}; + check(padding == storage.words.data() + 1 && std::equal(expected.begin(), expected.end(), padding), "four-dword NOP packet mismatch"); + check(storage.buffer.cursor_up == storage.words.data() + 5 && storage.words[5] == 0xaaaaaaaau, "NOP cursor advance mismatch"); + check(sceAgcCbNopGetSize(4) == expected.size() * sizeof(std::uint32_t), "four-dword NOP size mismatch"); + expectFailure(storage, [&] { sceAgcCbNop_nid_postfix(&storage.buffer, 0); }, "an empty NOP was accepted"); +} + +void testComputeEventWrite() { + Storage storage; + const auto* packet = sceAgcAcbEventWrite(&storage.buffer, 0x16, nullptr); + check(packet == storage.words.data() && packet[0] == 0xc0004600u && packet[1] == 0x16u, "ACB EVENT_WRITE packet mismatch"); + check(sceAgcAcbEventWriteGetSize(0x16) == 2 * sizeof(std::uint32_t), "ACB EVENT_WRITE size differs from its packet"); + const auto* flush = sceAgcAcbEventWrite(&storage.buffer, 7, nullptr); + check(flush == storage.words.data() + 2 && flush[0] == 0xc0004600u && flush[1] == 0x407u, "ACB CS_PARTIAL_FLUSH does not carry event index 4"); + check(storage.buffer.cursor_up == storage.words.data() + 4, "ACB EVENT_WRITE cursor advance mismatch"); + expectFailure(storage, [&] { sceAgcAcbEventWrite(&storage.buffer, 0x40, nullptr); }, "an ACB event type above 0x3f was accepted"); +} + +} + +int main() { + try { + testNop(); + testComputeEventWrite(); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgc/tests/StaticBuffer.cpp b/core/libs/prx/libSceAgc/tests/StaticBuffer.cpp index c7a3c162d6..cddb4a2843 100644 --- a/core/libs/prx/libSceAgc/tests/StaticBuffer.cpp +++ b/core/libs/prx/libSceAgc/tests/StaticBuffer.cpp @@ -1,5 +1,8 @@ #include "prx/libSceAgc/Misc/include/StaticBuffer.hpp" #include "prx/libc/include/GuestArena.hpp" +#ifdef __APPLE__ +#include "prx/libSceAgcDriver/Execution/include/Driver/DriverMemory.hpp" +#endif #include #include @@ -9,7 +12,11 @@ namespace { +#ifdef __APPLE__ +constexpr std::uintptr_t StaticTable = AgcDriver::DriverDetail::RetargetPs5DriverMemory(0xfe0040000ull) + GuestArena::GuestAddressOffset; +#else constexpr std::uintptr_t StaticTable = 0xfe0040000ull + GuestArena::GuestAddressOffset; +#endif constexpr int InvalidStaticBuffer = static_cast(0x8a6c000bu); void check(bool condition, const char* message) { diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 476d738b21..7d9537f14a 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -1,3 +1,24 @@ +if(BUILD_TESTING) + add_test_executable(agc_preparation_inputs_tests + tests/PreparationInputs.cpp + Execution/src/Driver/Shaders/ShaderDiagnostics.cpp + ${CMAKE_SOURCE_DIR}/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp) + target_include_directories(agc_preparation_inputs_tests PRIVATE + ${LIBS_INCLUDE_DIR} + $ + ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) + target_link_libraries(agc_preparation_inputs_tests PRIVATE libc) + configure_windows_unwind(agc_preparation_inputs_tests) + foreach(mode enabled disabled selected failure concurrent write-failure capture-write-failure) + add_test(NAME agc_preparation_inputs_${mode} COMMAND agc_preparation_inputs_tests ${mode}) + set_tests_properties(agc_preparation_inputs_${mode} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(agc_preparation_inputs_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() +endif() + function(ensure_agc_glslang) if(TARGET glslang AND TARGET SPIRV AND TARGET glslang-standalone) return() @@ -31,6 +52,16 @@ endfunction() ensure_agc_glslang() +if(BUILD_TESTING AND WIN32) + add_test_executable(agc_process_sampler_stack_tests tests/ProcessSamplerStack.cpp Execution/src/WorkerSampler.cpp) + target_include_directories(agc_process_sampler_stack_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_process_sampler_stack_tests PRIVATE libc) + configure_windows_unwind(agc_process_sampler_stack_tests) + add_test(NAME agc_process_sampler_stack COMMAND agc_process_sampler_stack_tests) + set_tests_properties(agc_process_sampler_stack PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") +endif() + set(agcTextureDetileShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/TextureDetile.comp) set(agcTextureDetileGeneratedRoot ${CMAKE_CURRENT_BINARY_DIR}/generated) set(agcTextureDetileGeneratedDir ${agcTextureDetileGeneratedRoot}/prx/libSceAgcDriver/Graphics/shaders) @@ -56,6 +87,28 @@ add_custom_command( DEPENDS "${agcTextureDetileShaderSource}" glslang-standalone VERBATIM ) +set(agcTextureDetileWordsShaderSpv ${agcTextureDetileGeneratedDir}/TextureDetileWords.spv) +set(agcTextureDetileWordsShaderHeader ${agcTextureDetileGeneratedDir}/TextureDetileWords_spv.h) +add_custom_command( + OUTPUT "${agcTextureDetileWordsShaderHeader}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}" + COMMAND "$" -V --target-env vulkan1.1 -DWORD_GROUPS=1 -o "${agcTextureDetileWordsShaderSpv}" "${agcTextureDetileShaderSource}" + COMMAND ${CMAKE_COMMAND} -DINPUT=${agcTextureDetileWordsShaderSpv} -DOUTPUT=${agcTextureDetileWordsShaderHeader} + -DSYMBOL=TEXTURE_DETILE_WORDS_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" + DEPENDS "${agcTextureDetileShaderSource}" glslang-standalone + VERBATIM +) +set(agcTextureDetileImageWordsShaderSpv ${agcTextureDetileGeneratedDir}/TextureDetileImageWords.spv) +set(agcTextureDetileImageWordsShaderHeader ${agcTextureDetileGeneratedDir}/TextureDetileImageWords_spv.h) +add_custom_command( + OUTPUT "${agcTextureDetileImageWordsShaderHeader}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}" + COMMAND "$" -V --target-env vulkan1.1 -DLINEAR_IMAGE=1 -DWORD_GROUPS=1 -o "${agcTextureDetileImageWordsShaderSpv}" "${agcTextureDetileShaderSource}" + COMMAND ${CMAKE_COMMAND} -DINPUT=${agcTextureDetileImageWordsShaderSpv} -DOUTPUT=${agcTextureDetileImageWordsShaderHeader} + -DSYMBOL=TEXTURE_DETILE_IMAGE_WORDS_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" + DEPENDS "${agcTextureDetileShaderSource}" glslang-standalone + VERBATIM +) set(agcMeshArgumentsShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/MeshArguments.comp) foreach(agcReadbackKind IN ITEMS Uint Depth) @@ -103,6 +156,19 @@ add_custom_command( VERBATIM ) +set(agcStagingCopyBackShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/StagingCopyBack.comp) +set(agcStagingCopyBackShaderSpv ${agcTextureDetileGeneratedDir}/StagingCopyBack.spv) +set(agcStagingCopyBackShaderHeader ${agcTextureDetileGeneratedDir}/StagingCopyBack_spv.h) +add_custom_command( + OUTPUT "${agcStagingCopyBackShaderHeader}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${agcTextureDetileGeneratedDir}" + COMMAND "$" -V --target-env vulkan1.1 -o "${agcStagingCopyBackShaderSpv}" "${agcStagingCopyBackShaderSource}" + COMMAND ${CMAKE_COMMAND} -DINPUT=${agcStagingCopyBackShaderSpv} -DOUTPUT=${agcStagingCopyBackShaderHeader} + -DSYMBOL=STAGING_COPY_BACK_SPV -P "${CMAKE_CURRENT_SOURCE_DIR}/embed_spirv.cmake" + DEPENDS "${agcStagingCopyBackShaderSource}" glslang-standalone + VERBATIM +) + set(agcCmaskClearShaderSource ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/shaders/CmaskClear.comp) set(agcCmaskClearShaderSpv ${agcTextureDetileGeneratedDir}/CmaskClear.spv) set(agcCmaskClearShaderHeader ${agcTextureDetileGeneratedDir}/CmaskClear_spv.h) @@ -344,7 +410,9 @@ 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/MapImportGate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ResidentMirror.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Graphics/src/ReferenceStorage.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 @@ -370,9 +438,12 @@ set(agcDriverSources ${agcMultisampleUploadHeader} ${agcCmaskClearShaderHeader} ${agcMeshArgumentsShaderHeader} + ${agcStagingCopyBackShaderHeader} ${agcTextureDetileImageShaderHeader} ${agcTextureRepackShaderHeader} ${agcDispatchArgumentsShaderHeader} + ${agcTextureDetileWordsShaderHeader} + ${agcTextureDetileImageWordsShaderHeader} ${agcSampleCounterShaderHeader} ${agcSampleDumpsShaderHeader} ${agcPresentationShaderHeaders} @@ -398,6 +469,7 @@ set(agcZlibIncludeDir ${CMAKE_SOURCE_DIR}/3rdparty/libjpeg-turbo/src/spng/zlib) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/TraceFile.cpp PROPERTIES INCLUDE_DIRECTORIES ${agcZlibIncludeDir} ) +set_property(SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/Execution/src/VulkanDevice.cpp APPEND PROPERTY COMPILE_DEFINITIONS VK_ENABLE_BETA_EXTENSIONS) add_library(libSceAgcDriver SHARED EXCLUDE_FROM_ALL ${agcDriverSources} @@ -423,6 +495,10 @@ add_shader_recompiler(shader_recompiler_agc_driver libc ${agcShaderCacheGuardSources} ) +target_include_directories(libSceAgcDriver INTERFACE + $ +) + target_link_libraries(libSceAgcDriver PRIVATE shader_recompiler_agc_driver decoder_png @@ -662,12 +738,14 @@ foreach(agcTestFragment IN LISTS agcTestFragments) include(${agcTestFragment}) endforeach() +target_include_directories(agc_driver_gcn_compare_exec_writes_tests PRIVATE $) + if(BUILD_TESTING) set_tests_properties(agc_driver_msaa_dcc_state ${multisampledDccClearTests} PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") set_tests_properties(${multisampledDccClearTests} PROPERTIES SKIP_RETURN_CODE 77) if(WIN32) - set_tests_properties(agc_driver_dithered_alpha_to_coverage agc_driver_two_sample_dithered_alpha_to_coverage ${multisampledDccClearTests} PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_driver_dithered_alpha_to_coverage agc_driver_two_sample_dithered_alpha_to_coverage agc_driver_alpha_to_coverage_sample_mask_export agc_driver_two_sample_alpha_to_coverage_sample_mask_export ${multisampledDccClearTests} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() @@ -898,6 +976,22 @@ target_link_libraries(agc_compute_pipeline_replay PRIVATE libSceAgcDriver libc) set_target_properties(agc_compute_pipeline_replay PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_compute_pipeline_replay) +add_library(agc_oracle_support STATIC tools/OracleCodeObject.cpp tools/OracleDispatch.cpp) +target_include_directories(agc_oracle_support PUBLIC + ${LIBS_INCLUDE_DIR} + $ + ${CMAKE_SOURCE_DIR}/core/shader/recompiler + ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} + ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR} +) +target_link_libraries(agc_oracle_support PUBLIC libSceAgcDriver libc) +set_target_properties(agc_oracle_support PROPERTIES CXX_EXTENSIONS OFF) + +add_executable(agc_oracle_runner EXCLUDE_FROM_ALL tools/OracleRunner.cpp) +target_link_libraries(agc_oracle_runner PRIVATE agc_oracle_support) +set_target_properties(agc_oracle_runner PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_oracle_runner) + if(BUILD_TESTING AND ANYPS5_ENABLE_SPIRV_TOOLS) add_test(NAME agc_compute_pipeline_inspection COMMAND ${CMAKE_COMMAND} -DREPLAY=$ @@ -910,8 +1004,29 @@ if(BUILD_TESTING AND ANYPS5_ENABLE_SPIRV_TOOLS) endif() endif() +if(BUILD_TESTING) + add_test(NAME agc_driver_fill_completion_labels_cpu_overlap COMMAND agc_driver_fill_completion_labels_tests --cpu-overlap) + add_test(NAME agc_driver_fill_completion_labels_cpu_disjoint COMMAND agc_driver_fill_completion_labels_tests --cpu-disjoint) + add_test(NAME agc_driver_fill_completion_labels_submitted COMMAND agc_driver_fill_completion_labels_tests --submitted) + add_test(NAME agc_driver_fill_completion_labels_inline COMMAND agc_driver_fill_completion_labels_tests) + add_test(NAME agc_driver_fill_completion_labels_chain COMMAND agc_driver_fill_completion_labels_tests) + add_test(NAME agc_driver_fill_completion_labels_late COMMAND agc_driver_fill_completion_labels_tests) + set(agcFillCompletionLabelTests agc_driver_fill_completion_labels agc_driver_fill_completion_labels_cpu_overlap agc_driver_fill_completion_labels_cpu_disjoint agc_driver_fill_completion_labels_submitted agc_driver_fill_completion_labels_inline agc_driver_fill_completion_labels_chain agc_driver_fill_completion_labels_late) + set_tests_properties(${agcFillCompletionLabelTests} PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_OPPORTUNISTIC_REAP=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + set_property(TEST agc_driver_fill_completion_labels_inline APPEND PROPERTY ENVIRONMENT "APS5_LABEL_RUNS_INLINE=1") + set_property(TEST agc_driver_fill_completion_labels_chain APPEND PROPERTY ENVIRONMENT "APS5_FILL_CHAIN=1") + set_property(TEST agc_driver_fill_completion_labels_late APPEND PROPERTY ENVIRONMENT "APS5_LABEL_TRUST_LATE=1") + if(WIN32) + set_tests_properties(agc_driver_fill_completion_labels_cpu_overlap agc_driver_fill_completion_labels_cpu_disjoint agc_driver_fill_completion_labels_submitted agc_driver_fill_completion_labels_inline agc_driver_fill_completion_labels_chain agc_driver_fill_completion_labels_late PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +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|agc_pipeline_store_replay_growth)$") set_property(TEST ${agcRegisteredTests} APPEND PROPERTY ENVIRONMENT "APS5_NO_PIPELINE_JOURNAL=1") endif() + +target_sources(agc_driver_scattered_runs_tests PRIVATE tests/AllocationFailure.cpp) diff --git a/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp b/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp index 035952a617..055e83ecbe 100644 --- a/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp +++ b/core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp @@ -54,13 +54,14 @@ int AddEvent(KernelEqueue eq, std::uint32_t queue, std::uintptr_t ident, void* u } int DeleteEvent(KernelEqueue eq, std::uintptr_t ident, const char* caller) { - { - std::lock_guard lock(g_mutex); - std::erase_if(g_registrations, [&](const Registration& r) { return r.eq == eq && r.ident == ident; }); + if (eq == 0 || !EqueuePin_nid_postfix(eq)) { + throw std::runtime_error(std::string(caller) + ": invalid event queue"); } if (EqueueDeleteEvent_nid_postfix(eq, ident, EvfiltGraphicsCore) != EQUEUE_OK) { throw std::runtime_error(std::string(caller) + ": event is not registered"); } + std::lock_guard lock(g_mutex); + std::erase_if(g_registrations, [&](const Registration& r) { return r.eq == eq && r.ident == ident; }); return 0; } @@ -85,10 +86,16 @@ bool AgcDriverDeliverEopInterrupt(std::uint32_t queue) { extern "C" { int APS5_VABI sceAgcDriverAddEqEvent(KernelEqueue eq, int id, void* udata) { + if (id < 0) { + throw std::runtime_error(std::string(__func__) + ": negative event id"); + } return AddEvent(eq, static_cast(id), static_cast(id), udata, __func__); } int APS5_VABI sceAgcDriverDeleteEqEvent(KernelEqueue eq, int id) { + if (id < 0) { + throw std::runtime_error(std::string(__func__) + ": negative event id"); + } return DeleteEvent(eq, static_cast(id), __func__); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/BdaAtomics.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/BdaAtomics.hpp new file mode 100644 index 0000000000..4699f35d38 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/BdaAtomics.hpp @@ -0,0 +1,14 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_BDAATOMICS_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_BDAATOMICS_HPP + +#include + +namespace AgcDriver { + +inline bool BdaAtomicsUsable(VkDriverId driver) { + return driver != VK_DRIVER_ID_MOLTENVK; +} + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp index 501c86b224..308f4fd998 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -31,8 +30,11 @@ public: if (!Enabled()) return; std::array line{}; const auto size = std::snprintf(line.data(), line.size(), format, args...); - if (size < 0 || static_cast(size) >= line.size()) throw std::runtime_error("Capture trace event exceeds 2047 bytes"); static CaptureTrace& trace = instance(); + if (size < 0 || static_cast(size) >= line.size()) { + trace.drop(); + return; + } trace.append(std::string(line.data(), static_cast(size))); } @@ -45,6 +47,8 @@ public: } private: + friend struct CaptureTraceTestAccess; + static CaptureTrace& instance() { static CaptureTrace trace; return trace; @@ -57,7 +61,7 @@ private: std::fprintf(stderr, "Capture trace open failed for %s\n", path); return; } - output.exceptions(std::ios::badbit | std::ios::failbit); + active = true; worker = std::thread([this] { run(); }); } @@ -71,33 +75,72 @@ private: } 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"); - pending += std::to_string(++sequence) + " " + std::to_string(elapsed) + "us " + line + "\n"; + if (!active || stopping) return; + if (droppedPending != 0 || pending.size() + line.size() + 64 > 16 * 1024 * 1024) { + ++droppedPending; + ++droppedTotal; + return; + } + appendRecord(pending, line); if (line.starts_with("blit ")) flushRequested = true; if (pending.size() >= 65536 || flushRequested) changed.notify_one(); } + void drop() { + std::lock_guard lock(mutex); + if (!active || stopping) return; + ++droppedPending; + ++droppedTotal; + } + + void appendRecord(std::string& destination, const std::string& line) { + const auto elapsed = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); + destination += std::to_string(++sequence) + " " + std::to_string(elapsed) + "us " + line + "\n"; + } + + void fail(const char* reason) { + unsigned long long dropped; + { + std::lock_guard lock(mutex); + active = false; + pending.clear(); + dropped = droppedTotal; + } + std::fprintf(stderr, "Capture trace failed: %s (dropped records: %llu)\n", reason, dropped); + } + void run() { - try { - for (;;) { - std::string batch; - { - std::unique_lock lock(mutex); - changed.wait_for(lock, std::chrono::milliseconds(100), [this] { return stopping || flushRequested || pending.size() >= 65536; }); - if (stopping && pending.empty()) return; - batch.swap(pending); - flushRequested = false; + for (;;) { + std::string batch; + bool stop; + { + std::unique_lock lock(mutex); + changed.wait_for(lock, std::chrono::milliseconds(100), [this] { return stopping || flushRequested || pending.size() >= 65536; }); + batch.swap(pending); + if (droppedPending != 0) { + appendRecord(batch, "capture-trace-dropped records=" + std::to_string(droppedPending)); + droppedPending = 0; + } + flushRequested = false; + stop = stopping; + if (stop) { + appendRecord(batch, "capture-trace-summary dropped=" + std::to_string(droppedTotal)); + active = false; } - if (batch.empty()) continue; - output.write(batch.data(), static_cast(batch.size())); - output.flush(); } - } catch (const std::exception& error) { - std::fprintf(stderr, "Capture trace failed: %s\n", error.what()); - std::terminate(); + if (batch.empty()) continue; + output.write(batch.data(), static_cast(batch.size())); + if (!output) { + fail("write failed"); + return; + } + output.flush(); + if (!output) { + fail("flush failed"); + return; + } + if (stop) return; } } @@ -108,6 +151,9 @@ private: std::thread worker; std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); unsigned long long sequence = 0; + unsigned long long droppedPending = 0; + unsigned long long droppedTotal = 0; + bool active = false; bool stopping = false; bool flushRequested = false; }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp index b65f22d0d1..c7cf99a0a1 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Dispatch/DispatchCache.hpp @@ -3,6 +3,8 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/Recipe.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "IntermediateRepresentation/IrMetadata/ResourcePlan.hpp" #include #include #include @@ -15,12 +17,34 @@ #include #include #include +#include #include #include #include "prx/libc/include/general/AtomicSharedPtr.hpp" namespace AgcDriver::DriverDetail { +inline bool CaptureRegisteredViewsCurrent(std::span views) { + if (views.empty()) return true; + for (std::uint32_t attempt = 0; attempt < 2u; ++attempt) { + const auto before = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + bool current = true; + for (const auto& view : views) { + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast(view.address), false); + if (view.hasRange != (range != nullptr)) { + current = false; + break; + } + if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) { + current = false; + break; + } + } + if (before == GuestAllocations::GuestAllocationsGeneration_nid_postfix()) return current; + } + return false; +} + inline constexpr std::size_t MaxDispatchVariants = 8; struct DispatchVariant { @@ -30,6 +54,7 @@ struct DispatchVariant { std::vector shared; std::uint64_t forgetSerial = 0; std::shared_ptr compiled; + std::shared_ptr> registeredViews; std::array meshIndexDescriptor{}; std::shared_ptr shader; 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 e486345e11..e1357753b8 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp @@ -124,6 +124,7 @@ enum class DrawVerdict { Drawn, Nothing, Rejected }; struct StageCapture { std::shared_ptr compiled; std::shared_ptr capture; + std::shared_ptr> registeredViews; std::vector regions; std::vector> supplied; std::uint64_t forgetSerial = 0; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp index 3e1af24b66..9fffb25ded 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp @@ -121,8 +121,16 @@ public: static std::uint64_t DrawCacheBytes(); static ShaderMemory::PendingWrite QueryPendingSpan(std::uint64_t address, std::size_t bytes, std::span known); std::shared_ptr RegisteredShaders(); + static void SetCaptureCheckHook(void (*hook)()); private: + class RecaptureScope { + public: + RecaptureScope(); + ~RecaptureScope(); + RecaptureScope(const RecaptureScope&) = delete; + RecaptureScope& operator=(const RecaptureScope&) = delete; + }; friend class SampledReadScope; friend struct PendingView; friend struct PacketTimer; @@ -321,20 +329,21 @@ private: static void notePendingWait(std::span packet, std::uint32_t queue, std::uint64_t received); void landPendingWaits(std::uint32_t queue); struct ResolvedDispatch { - std::uint64_t address = 0, key = 0, indirectArguments = 0; + std::uint64_t address = 0, key = 0, indirectArguments = 0, forgetAtCapture = 0; std::array packet{}; std::shared_ptr registeredShader; std::shared_ptr compiledResult; std::shared_ptr keepVariant, attachVariant; + std::shared_ptr missedEntry; std::shared_ptr shaderMemory; std::vector captured; std::vector liveWords; std::shared_ptr capture; - bool dataHit = false, cached = false, validated = false; + bool dataHit = false, cached = false, validated = false, missedDiffering = false, noDispatchCache = false; std::chrono::steady_clock::time_point resolvedAt{}; }; struct GroupCaptureStats { - 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; + 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, unknownWrites = 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(); }; @@ -365,6 +374,8 @@ private: bool recordLabelsForPacket(VulkanDevice* localDevice, std::uint32_t queue); void recordQueuedLabelsBeforeRead(std::uint32_t queue); bool recordQueuedLabelsAfterCapture(std::uint32_t queue, std::span regions); + static void runCaptureCheckHook(); + static bool capturedMemoryChanged(std::span regions, std::span compared = {}); void noteQueuedLabels(std::uint32_t queue); void recordQueuedLabelsByTry(std::uint32_t queue, std::atomic& counter); static void recordQueuedLabelsFromHook(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/DriverMemory.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/DriverMemory.hpp new file mode 100644 index 0000000000..89c2dfd007 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/DriverMemory.hpp @@ -0,0 +1,22 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_DRIVERMEMORY_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DRIVER_DRIVERMEMORY_HPP + +#ifdef __APPLE__ + +#include + +namespace AgcDriver::DriverDetail { + +constexpr std::uint64_t Ps5DriverMemoryBase = 0xfe0000000ull; +constexpr std::uint64_t MacOsDriverMemoryBase = 0xee0000000ull; +constexpr std::uint64_t DriverMemorySpan = 2u << 20u; + +constexpr std::uint64_t RetargetPs5DriverMemory(std::uint64_t address) { + return address >= Ps5DriverMemoryBase && address - Ps5DriverMemoryBase < DriverMemorySpan ? address - Ps5DriverMemoryBase + MacOsDriverMemoryBase : address; +} + +} + +#endif + +#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 16dbff7bac..18b36ea0f9 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/QueueState.hpp" +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +27,7 @@ struct Shader; namespace ShaderRecompiler { class ShaderPreparationContext; +struct LinkedCalls; } namespace AgcDriver { @@ -35,6 +38,7 @@ namespace AgcDriver::DriverDetail { struct ShaderSnapshot; +struct UserDataCallSites; class ShaderRegistry { using Chunk = std::map>>; using Chunks = std::vector>; @@ -145,7 +149,18 @@ struct PreparedShaderState { std::weak_ptr registry; std::vector> stages; }; + struct UserDataLink { + std::size_t codeOffset = 0; + std::uint32_t userDataBaseRegister = 0; + std::uint32_t userDataCount = 0; + std::uint32_t waveSize = 0; + std::shared_ptr sites; + std::vector callees; + std::vector> code; + std::shared_ptr linked; + }; std::vector entries; + std::vector userDataLinks; std::vector> registeredAbis; std::vector graphicsAbis; std::vector rectangles; @@ -169,12 +184,105 @@ struct PreparedShaders : PreparedShaderState { std::atomic demanded{false}; bool pending = false; std::vector convertedSamples; + std::vector layerRowSamples; }; -struct RegisteredShaderState { +class SharedCode { +public: + SharedCode() = default; + SharedCode(std::vector values) : words(std::make_shared>(std::move(values))) {} + SharedCode(std::initializer_list values) : SharedCode(std::vector(values)) {} + template + SharedCode(TIterator first, TIterator last) : SharedCode(std::vector(first, last)) {} + + operator const std::vector&() const { return Words(); } + [[nodiscard]] const std::vector& Words() const { return words != nullptr ? *words : none(); } + [[nodiscard]] const std::uint32_t* data() const { return Words().data(); } + [[nodiscard]] std::size_t size() const { return Words().size(); } + [[nodiscard]] bool empty() const { return Words().empty(); } + [[nodiscard]] const std::uint32_t* begin() const { return Words().data(); } + [[nodiscard]] const std::uint32_t* end() const { return Words().data() + Words().size(); } + [[nodiscard]] std::uint32_t operator[](std::size_t index) const { return Words()[index]; } + [[nodiscard]] bool SharesWith(const SharedCode& other) const { return words != nullptr && words == other.words; } + bool operator==(const SharedCode& other) const { return words == other.words || Words() == other.Words(); } + bool operator==(const std::vector& other) const { return Words() == other; } + + std::vector& Mutable() { + if (words == nullptr) words = std::make_shared>(); + else if (words.use_count() > 1) words = std::make_shared>(*words); + return *words; + } + void resize(std::size_t count) { Mutable().resize(count); } + template + void assign(TIterator first, TIterator last) { Mutable().assign(first, last); } + +private: + static const std::vector& none() { + static const std::vector none; + return none; + } + + std::shared_ptr> words; +}; + +class RegisterList { +public: + using Entry = std::pair; + using const_iterator = std::vector::const_iterator; + + RegisterList() = default; + explicit RegisterList(const Registers& registers) { + entries.reserve(registers.size()); + registers.ForEach([&](std::uint32_t offset, std::uint32_t value) { entries.emplace_back(offset, value); }); + } + + [[nodiscard]] Registers Expand() const { + Registers registers; + if (!entries.empty()) registers.emplace(entries.back().first, entries.back().second); + for (const auto& [offset, value] : entries) registers.emplace(offset, value); + return registers; + } + [[nodiscard]] const_iterator begin() const { return entries.begin(); } + [[nodiscard]] const_iterator end() const { return entries.end(); } + [[nodiscard]] const_iterator find(std::uint32_t offset) const { + const auto found = std::lower_bound(entries.begin(), entries.end(), offset, [](const Entry& entry, std::uint32_t wanted) { return entry.first < wanted; }); + return found != entries.end() && found->first == offset ? found : entries.end(); + } + [[nodiscard]] std::uint32_t at(std::uint32_t offset) const { + const auto found = find(offset); + if (found == end()) throw std::out_of_range("register is not set"); + return found->second; + } + [[nodiscard]] std::size_t size() const { return entries.size(); } + [[nodiscard]] bool empty() const { return entries.empty(); } + template + void ForEach(TVisit&& visit) const { + for (const auto& [offset, value] : entries) visit(offset, value); + } + void emplace(std::uint32_t offset, std::uint32_t value) { + const auto found = std::lower_bound(entries.begin(), entries.end(), offset, [](const Entry& entry, std::uint32_t wanted) { return entry.first < wanted; }); + if (found == entries.end() || found->first != offset) entries.insert(found, {offset, value}); + } + + bool operator==(const RegisterList&) const = default; + +private: + std::vector entries; +}; + +struct DecodedShaderState { Registers shader; Registers context; Registers userConfig; +}; + +struct RegisteredShaderState { + RegisteredShaderState() = default; + RegisteredShaderState(const DecodedShaderState& decoded) : shader(decoded.shader), context(decoded.context), userConfig(decoded.userConfig) {} + + RegisterList shader; + RegisterList context; + RegisterList userConfig; bool operator==(const RegisteredShaderState&) const = default; }; @@ -183,7 +291,7 @@ struct ShaderSnapshot { std::uint64_t codeAddress; std::uint64_t headerAddress; std::uint8_t type; - std::vector code; + SharedCode code; std::vector header; std::shared_ptr prepared = std::make_shared(); std::shared_ptr registeredState; @@ -195,7 +303,7 @@ struct ShaderSnapshot { }; -std::shared_ptr ReadRawShader(std::uint64_t address); +std::shared_ptr ReadRawShader(std::uint64_t address, const std::shared_ptr& retained = {}); std::shared_ptr ReadUserDataTailContinuation(std::span code, std::uint32_t userDataBase, std::span userData); std::shared_ptr ProgramSnapshot(const ShaderRegistry& shaders, std::uint64_t address, std::initializer_list types); void NoteRawGraphicsDraw(const ShaderSnapshot& snapshot); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp index d807612d5d..4386551240 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp @@ -18,7 +18,7 @@ extern std::atomic labelFallbacks[5]; extern std::atomic queuedLabels, labelGroups, immediateLabels; -extern std::atomic packetLockRecords, packetLockSubmits, packetLockDeferred, packetSubmitDeferred, captureTryRecords, captureRetries, suspendPoints; +extern std::atomic packetLockRecords, packetLockSubmits, packetLockDeferred, packetSubmitDeferred, captureTryRecords, captureRetries, captureChangeRetries, suspendPoints; extern std::atomic recordTriesSkipped; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/FragmentBarycentric.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/FragmentBarycentric.hpp index 361aadb063..4dcd362276 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/FragmentBarycentric.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/FragmentBarycentric.hpp @@ -9,6 +9,10 @@ inline bool FragmentShaderBarycentricUsable(VkDriverId driver, bool reported) { return reported && driver != VK_DRIVER_ID_MOLTENVK; } +inline bool HostInterpolationPreferred(VkDriverId driver) { + return driver == VK_DRIVER_ID_MESA_RADV; +} + } #endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp index 1c0a88ac78..4ed8f6a873 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,7 @@ private: void record(FrameCall call, bool prologueCall, std::span shaders = {}, const std::function& accept = {}); void recordLocked(FrameCall call, bool prologueCall, std::span shaders); void emitDelta(); + static void collectShaderPages(std::span shaders, std::set& blocks, std::set& hostPages); void emitShaderDelta(std::span shaders); void writeDelta(std::span spans, std::span hostPages, std::span added = {}); bool triggered(); @@ -88,6 +90,7 @@ private: State state = State::Waiting; std::unique_ptr writer; std::vector prologue; + std::set prologueHostPages; std::deque reservations; std::vector ranges; std::map shaderMarks; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp index 347d9395ef..f7ab9e51cc 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp @@ -54,6 +54,7 @@ void Write(std::uint64_t address, std::span source, std::size_t void WriteChanged(std::uint64_t address, std::span current, std::span original); void WriteChanged(std::uint64_t address, std::span current, std::span original, std::uint32_t byteStoreLimit); std::uint32_t ChangedByteStoreLimit(); +void WriteChangedKeepingGuest(std::uint64_t address, std::span current, std::span original); // Write tracking. The guest arena is reserved with page write watching, so CollectWrites learns which // pages of a range the CPU wrote since they were last collected, stamps their 64 KiB blocks with a new @@ -67,6 +68,8 @@ 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 importMayWriteGuest = true); void SetUnwatchHook(void (*hook)(std::uint64_t address, std::size_t bytes)); +bool UnwatchWritesProven(); +void SetImportWalkFailureForTest(int walk, std::uint64_t span = 0); #ifdef _WIN32 using FreshCollectHook = bool (*)(std::uintptr_t address, std::size_t bytes, void (*written)(void*, std::uintptr_t, std::uintptr_t), void* context); void SetFreshCollectHook(FreshCollectHook hook); @@ -124,6 +127,7 @@ std::uint64_t CollectWritesUncached(std::uint64_t address, std::size_t bytes); // tracker mutex, so every collect that bumped before it has finished its walk and every later // collect stamps newer. std::uint64_t TrackerGeneration(); +void AdvanceTrackerGenerationForTest(std::uint64_t generation); std::uint64_t NewestStamp(std::uint64_t address, std::size_t bytes); std::uint64_t UntrackedHint(std::uint64_t address, std::size_t bytes); // Whether no CPU write touched the range since `generation`: an uncached resetting collect of the diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/KeyboardOverlay.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/KeyboardOverlay.hpp index 3553ee0cb9..1a58ce7149 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/KeyboardOverlay.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/KeyboardOverlay.hpp @@ -51,6 +51,8 @@ struct KeyboardGlyphEntry { }; inline constexpr char32_t KeyboardFallbackCode = 0xFFFD; +inline constexpr std::uint8_t KeyboardCompositionMark = 1; +inline constexpr std::uint8_t KeyboardSelectionMark = 2; std::span KeyboardGlyphTable(); bool KeyboardHasGlyph(char32_t code); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp index 0b9bc1f9f8..7403a695b2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Pm4.hpp @@ -14,6 +14,11 @@ namespace AgcDriver::Pm4 { constexpr std::size_t GdsBytes = 0x10000; std::uint64_t GdsAddress(); +void RequestImportableGds(bool requested); +bool ImportableGdsRequested(); +bool GdsShared(); +bool GdsImportable(); +void RejectGdsImport(); struct DrawParameters { std::uint64_t indexAddress; diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/RasterOrderGroup.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/RasterOrderGroup.hpp new file mode 100644 index 0000000000..e35ab537ce --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/RasterOrderGroup.hpp @@ -0,0 +1,14 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_RASTERORDERGROUP_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_RASTERORDERGROUP_HPP + +#include + +namespace AgcDriver { + +inline bool RasterOrderGroupUsable(VkDriverId driver) { + return driver != VK_DRIVER_ID_MOLTENVK; +} + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp index f0edd61fd1..3bec7a5126 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp @@ -91,6 +91,7 @@ public: private: std::shared_ptr capture(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle, const ShaderRecompiler::PreparedShaderInvocation* invocation); + std::shared_ptr captureWaitingOnFailure(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle, const ShaderRecompiler::PreparedShaderInvocation* invocation); static constexpr std::size_t PageBytes = 4096; static constexpr std::size_t PageWords = PageBytes / sizeof(std::uint32_t); @@ -169,6 +170,8 @@ private: std::size_t descriptorSpanBytes = 0; std::uint64_t rawInvalidations = 0; std::uint32_t forceSync = 0; + std::uint64_t branchReadsRefused = 0; + bool waitBranches = false; }; // The positions, among a dispatch-cache variant's stored words, of the pure flat-SRT leaves a diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp index d89d7cea43..c24c0e2e79 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/SubgroupBarrier.hpp @@ -6,7 +6,7 @@ namespace AgcDriver { inline bool SubgroupControlBarrierUsable(VkDriverId driver) { - return driver != VK_DRIVER_ID_AMD_PROPRIETARY; + return driver != VK_DRIVER_ID_AMD_PROPRIETARY && driver != VK_DRIVER_ID_MOLTENVK; } inline bool WorkgroupScopeFenceUsable(VkDriverId driver) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index 708765ac4e..d6f06ae2bb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -81,6 +81,8 @@ public: std::string DeviceName() const; std::string DriverInfo() const; bool SupportsImageFormat(VkFormat format, VkImageUsageFlags usage) const; + bool BdaAtomicsUsable() const; + bool RasterOrderGroupUsable() 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; @@ -287,6 +289,7 @@ public: double gpuBusyMs, gpuGapMs, lastSubmitAfterFlipMs, blitSubmitAfterFlipMs, keyWriterWaitMs; }; static PresentStatistics PresentCounts(); + static std::uint64_t FillCompletionSyncCount(); struct PresyncStatistics { std::uint64_t checked, presyncs, keyChecks, keyWaits; }; @@ -298,19 +301,19 @@ public: // records. Null when nothing is prepared: the resource cache may serve the dispatch (its // Revalidate stays under the mutex), or APS5_LOCKED_BUILD=1 keeps the whole build under it as // before. `shader` and `snapshots` must outlive the dispatch. - std::shared_ptr PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span snapshots, std::uint64_t dispatchThreads = 0, std::uint64_t programAddress = 0, std::optional> htileClear = std::nullopt); + std::shared_ptr PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span snapshots, std::uint64_t dispatchThreads = 0, std::uint64_t programAddress = 0, std::optional> htileClear = std::nullopt, const std::vector* registeredImageViews = nullptr); // APS5_PROFILE_DRAW: the parts of a PrepareDispatch in milliseconds, in the order key, find, // precollect, presync, stage A (the driver's 'prepare:' rows). static std::span PreparePhaseMs(const PreparedDispatch& prepared); // `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, std::optional> htileClear = std::nullopt); + 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, const std::vector* registeredImageViews = nullptr); struct IndirectOutcome { int cpuReason; double argumentReadMs; }; - IndirectOutcome DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span snapshots = {}, std::uint64_t programAddress = 0, std::shared_ptr prepared = nullptr, std::shared_ptr* recipe = nullptr, const std::array& threadGroup = {}); + IndirectOutcome DispatchIndirect(const ShaderRecompiler::RecompileResult& shader, std::uint64_t arguments, std::span snapshots = {}, std::uint64_t programAddress = 0, std::shared_ptr prepared = nullptr, std::shared_ptr* recipe = nullptr, const std::array& threadGroup = {}, const std::vector* registeredImageViews = nullptr); // A value-equal dispatch-cache hit with a recipe (design_cpu_final M4), in two steps. // PrepareRecipe, WITHOUT GuestMemory::GpuMutex: the pre-checks (recipes on, the recipe's // device is this one, its template and pipeline objects still alive), the template's @@ -385,11 +388,12 @@ public: private: VulkanDevice(const PresentationWindow* window, bool compileOnly); friend struct VulkanDeviceTestAccess; + std::pair scratchPoolHeader(const ShaderRecompiler::RecompileResult& shader) const; VkPipeline splitComputePipeline(const Graphics::Context& context, const ShaderRecompiler::RecompileResult& shader, ComputePipelineObjects& objects, const std::array& base); 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 = {}, std::optional> htileClear = std::nullopt); + 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, const std::vector* registeredImageViews = nullptr); 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); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp index 57ac083143..72854adb7b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp @@ -8,6 +8,7 @@ namespace AgcDriver { void StartWorkerSampler(std::uint32_t queue = 0); void StartCommitterSampler(); void StopWorkerSampler(); +bool ReadableStackRegion(std::uint64_t address, std::uint64_t& low, std::uint64_t& high); } 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 c443f79492..f6eaa8838a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp @@ -15,6 +15,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "Optimization/ResourceProgram.hpp" #include +#include #include #include #include @@ -140,10 +141,12 @@ void Driver::dispatch(QueueState& queue, std::span packet, 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 && (MatchDwordPatternFill(packet, std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchConstantDwordFill(packet, std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchClampedGlobalFill(packet, std::span(snapshot.code).subspan(codeOffset), userData, compute) || MatchMemoryValueBufferFill(packet, 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; + if (resolvedAhead().empty()) gateOpened() = true; return; } if (indirectArguments == 0 && (packet[4] & 0x20u) == 0) { @@ -151,6 +154,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, } if (!partialLastGroup && fillBuffer(queue, submission.queue, packet, std::span(snapshot.code).subspan(codeOffset), userData, compute, localDevice)) { pendingDispatchPhases().outcome = DispatchOutcome::FillHle; + if (resolvedAhead().empty()) gateOpened() = true; return; } if (DrawPipeline::Active() && !pipelineCandidate) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, 0x15); @@ -166,6 +170,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, } if (!partialLastGroup && indirectArguments == 0 && copyBuffer(queue, submission.queue, packet, std::span(snapshot.code).subspan(codeOffset), userData, compute, localDevice, address)) { pendingDispatchPhases().outcome = DispatchOutcome::CopyHle; + if (resolvedAhead().empty()) gateOpened() = true; return; } } @@ -188,9 +193,9 @@ void Driver::dispatch(QueueState& queue, std::span packet, {0, 0, 0, threadGroup != std::array{} ? 0u : 128u} }; static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; - static double captureMs = 0, keyMs = 0, recompileMs = 0, deviceMs = 0; - static std::uint64_t cacheHits = 0; - static std::uint64_t dispatches = 0; + static std::atomic captureMs{0}, keyMs{0}, recompileMs{0}, deviceMs{0}; + static std::atomic cacheHits{0}; + static std::atomic dispatches{0}; auto lap = std::chrono::steady_clock::now(); std::array phaseMs{}; @@ -302,7 +307,8 @@ void Driver::dispatch(QueueState& queue, std::span packet, } if (fromAhead) { if (const auto hook = aheadAdoptHook.load(std::memory_order_acquire)) hook(); - if (!CapturedCurrent(ahead.captured, snapshot)) { + const auto* aheadViews = ahead.capture != nullptr ? &ahead.capture->snapshot.registeredImages : ahead.keepVariant != nullptr && ahead.keepVariant->registeredViews != nullptr ? ahead.keepVariant->registeredViews.get() : nullptr; + if (!CapturedCurrent(ahead.captured, snapshot) || (aheadViews != nullptr && !CaptureRegisteredViewsCurrent(*aheadViews))) { fromAhead = false; ++dispatchesRejectedAhead; ++groupStats.memoryMismatches; @@ -365,12 +371,12 @@ void Driver::dispatch(QueueState& queue, std::span packet, } if (!pipelined && DrawPipeline::Queue0().Busy()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, 0x15); } + std::uint64_t forgetAtCapture = 0; if (cached) { require(keepVariant != nullptr && keepVariant->shader == registeredShader, "dispatch cache belongs to another registered shader"); - captureMs += phaseTiming.Elapsed(); + if (profile) captureMs.fetch_add(phaseTiming.Elapsed(), std::memory_order_relaxed); } else if (!fromAhead) { shaderMemory = std::make_shared(memory, &queryPendingWrite, &observePendingWrite, hookWaitCounter(), &QueryPendingSpan); - std::uint64_t forgetAtCapture = 0; static const bool dumpShaders = std::getenv("APS5_DUMP_SHADERS") != nullptr; try { @@ -386,7 +392,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, captured = shaderMemory->Regions(); request.context.memory = captured; if (traceCapSync()) traceCapture("dispatch-capture", address, submission.queue, captured, Graphics::Recorder::ThreadWaitedMs() - waitedBefore); - captureMs += phaseTiming.Elapsed(); + if (profile) captureMs.fetch_add(phaseTiming.Elapsed(), std::memory_order_relaxed); phaseTiming.Phase(PhaseCapture); if (dumpShaders) static_cast(dumpRequest(address, request)); const auto started = std::chrono::steady_clock::now(); @@ -395,11 +401,13 @@ void Driver::dispatch(QueueState& queue, std::span packet, timing.Mark("capture_resources"); compiledResult = invocation.Materialize(*capture); timing.Mark("materialize"); - if (compiledResult->cacheHit) ++cacheHits; + if (profile && compiledResult->cacheHit) cacheHits.fetch_add(1, std::memory_order_relaxed); const auto elapsed = std::chrono::duration(std::chrono::steady_clock::now() - started).count(); - static double totalMs = 0; - totalMs += elapsed; - if (profile && elapsed > 200) std::fprintf(stderr, "[gpu] compute shader 0x%llx recompile took %.0f ms (%zu SPIR-V words, %zu captured regions, total %.1f s)\n", static_cast(address), elapsed, compiledResult->spirv.size(), captured.size(), totalMs / 1000); + static std::atomic totalMs{0}; + if (profile) { + const auto total = totalMs.fetch_add(elapsed, std::memory_order_relaxed) + elapsed; + if (elapsed > 200) std::fprintf(stderr, "[gpu] compute shader 0x%llx recompile took %.0f ms (%zu SPIR-V words, %zu captured regions, total %.1f s)\n", static_cast(address), elapsed, compiledResult->spirv.size(), captured.size(), total / 1000); + } } catch (const std::exception& error) { std::string dump; if (dumpShaders) { @@ -416,21 +424,24 @@ void Driver::dispatch(QueueState& queue, std::span packet, else std::snprintf(where, sizeof(where), "compute shader 0x%llx (%s): ", static_cast(address), dump.c_str()); throw std::runtime_error(where + reason); } - recompileMs += phaseTiming.Elapsed(); + if (profile) recompileMs.fetch_add(phaseTiming.Elapsed(), std::memory_order_relaxed); phaseTiming.Phase(PhaseRecompile); - insertDispatch(address, key, noDispatchCache || !CacheableResult(*compiledResult), profile, registeredShader, forgetAtCapture, memory, shaderMemory, captured, capture, compiledResult, missedEntry, missedDiffering, attachVariant, phaseTiming); + if (!resolving) insertDispatch(address, key, noDispatchCache || !CacheableResult(*compiledResult), profile, registeredShader, forgetAtCapture, memory, shaderMemory, captured, capture, compiledResult, missedEntry, missedDiffering, attachVariant, phaseTiming); } + const auto* registeredViews = capture != nullptr ? &capture->snapshot.registeredImages : keepVariant != nullptr && keepVariant->registeredViews != nullptr ? keepVariant->registeredViews.get() : nullptr; if (resolving) { auto& parked = resolvedAhead()[currentPacketOffset()]; parked = ResolvedDispatch{}; parked.address = address; parked.key = key; parked.indirectArguments = indirectArguments; + parked.forgetAtCapture = forgetAtCapture; std::copy_n(packet.begin(), std::min(packet.size(), parked.packet.size()), parked.packet.begin()); parked.registeredShader = registeredShader; parked.compiledResult = compiledResult; parked.keepVariant = keepVariant; parked.attachVariant = attachVariant; + parked.missedEntry = missedEntry; parked.shaderMemory = shaderMemory; parked.captured = std::move(captured); parked.liveWords = std::move(liveWords); @@ -438,6 +449,8 @@ void Driver::dispatch(QueueState& queue, std::span packet, parked.dataHit = dataHit; parked.cached = cached; parked.validated = validated; + parked.missedDiffering = missedDiffering; + parked.noDispatchCache = noDispatchCache || (compiledResult != nullptr && !CacheableResult(*compiledResult)); parked.resolvedAt = std::chrono::steady_clock::now(); ++groupStats.resolved; if (cached) ++groupStats.resolvedHits; @@ -494,11 +507,17 @@ void Driver::dispatch(QueueState& queue, std::span packet, phaseTiming.Phase(PhaseSnapshots); if (pipelined) { + runCaptureCheckHook(); + if (capturedMemoryChanged(captured)) { + const RecaptureScope recapture; + dispatch(queue, packet, submission, indirectArguments); + return; + } const auto recipe = keepVariant->recipe.load(std::memory_order_acquire); 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, htileClearCompletion, htileMask] { + DrawPipeline::Queue0().Enqueue([this, localDevice = std::move(localDevice), result = compiledResult, capture, registeredViews, snapshots = std::move(snapshots), liveWords = std::move(liveWords), groups, address, queueId, recipe, keepVariant, dataHit, epoch, htileClearCompletion, htileMask] { DrawPipeline::FollowEpoch(epoch); GuestMemory::SetCurrentPacket(0x15, queueId); const auto& compiledCommit = *result; @@ -509,7 +528,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, bool writersNoted = false; for (;;) { std::shared_ptr prepared; - if (recipeHit == nullptr || VulkanDevice::VerifyRecipes() || htileClearCompletion.has_value()) prepared = localDevice->PrepareDispatch(compiledCommit, snapshots, 0, address, htileClearCompletion); + if (recipeHit == nullptr || VulkanDevice::VerifyRecipes() || htileClearCompletion.has_value()) prepared = localDevice->PrepareDispatch(compiledCommit, snapshots, 0, address, htileClearCompletion, registeredViews); GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Dispatch); std::lock_guard gpuLock(GuestMemory::GpuMutex()); recordLabelsForPacket(localDevice.get(), queueId); @@ -527,7 +546,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), htileClearCompletion.has_value() ? nullptr : &builtRecipe, htileClearCompletion); + localDevice->Dispatch(compiledCommit, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), htileClearCompletion.has_value() ? nullptr : &builtRecipe, htileClearCompletion, registeredViews); } if (htileMask.bytes != 0u) localDevice->NoteHtileMask(htileMask); break; @@ -577,13 +596,14 @@ void Driver::dispatch(QueueState& queue, std::span packet, } } } + bool captureChecked = fromAhead; for (;;) { std::shared_ptr prepared; if (recipeHit == nullptr || VulkanDevice::VerifyRecipes()) { try { - prepared = localDevice->PrepareDispatch(compiled, snapshots, dispatchThreads, address, htileClearCompletion); + prepared = localDevice->PrepareDispatch(compiled, snapshots, dispatchThreads, address, htileClearCompletion, registeredViews); } catch (const std::exception& error) { if (rethrow(error)) return; } @@ -605,8 +625,18 @@ void Driver::dispatch(QueueState& queue, std::span packet, } } GuestMemory::TagGpuLockSite(indirectArguments != 0 ? GuestMemory::GpuLockSite::Indirect : GuestMemory::GpuLockSite::Dispatch); - std::lock_guard gpuLock(GuestMemory::GpuMutex()); + std::unique_lock gpuLock(GuestMemory::GpuMutex()); phaseTiming.Phase(PhaseLockWait); + if (!captureChecked) { + captureChecked = true; + runCaptureCheckHook(); + if (capturedMemoryChanged(captured, snapshots)) { + gpuLock.unlock(); + const RecaptureScope recapture; + dispatch(queue, packet, submission, indirectArguments); + return; + } + } recordLabelsForPacket(localDevice.get(), submission.queue); phaseTiming.Phase(PhaseLabels); @@ -629,10 +659,10 @@ void Driver::dispatch(QueueState& queue, std::span packet, } if (indirectArguments != 0) countIndirect(outcome.cpuReason, outcome.argumentReadMs); } else if (indirectArguments != 0) { - const auto outcome = localDevice->DispatchIndirect(compiled, indirectArguments, snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr, threadGroup); + const auto outcome = localDevice->DispatchIndirect(compiled, indirectArguments, snapshots, address, std::move(prepared), attachTo != nullptr ? &builtRecipe : nullptr, threadGroup, registeredViews); countIndirect(outcome.cpuReason, outcome.argumentReadMs); } else { - localDevice->Dispatch(compiled, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), htileClearCompletion.has_value() ? nullptr : (attachTo != nullptr ? &builtRecipe : nullptr), htileClearCompletion); + localDevice->Dispatch(compiled, groups[0], groups[1], groups[2], snapshots, address, std::move(prepared), htileClearCompletion.has_value() ? nullptr : (attachTo != nullptr ? &builtRecipe : nullptr), htileClearCompletion, registeredViews); } } catch (const std::exception& error) { if (rethrow(error)) return; @@ -656,7 +686,7 @@ void Driver::dispatch(QueueState& queue, std::span packet, } phaseTiming.Phase(PhaseDevice); if (noteWrites && !writerKeyedEvidence()) noteWrittenBuffers(address, submission.queue, compiled); - deviceMs += phaseTiming.Elapsed(); + if (profile) deviceMs.fetch_add(phaseTiming.Elapsed(), std::memory_order_relaxed); phaseTiming.Phase(PhaseTail); if (profile) { auto& pending = pendingDispatchPhases(); @@ -667,7 +697,16 @@ void Driver::dispatch(QueueState& queue, std::span packet, pending.ms = phaseMs; pending.tailAt = phaseLap; } - if (profile && ++dispatches % 100 == 0) AgcDriver::ProfilePrint_nid_no_patch( "[gpu] %llu dispatches (%llu dispatch cache hits, %llu evictions, %llu recompile cache hits): capture %.1f s, cache key %.1f s, recompile %.1f s, device %.1f s\n", static_cast(dispatches), static_cast(dispatchCacheHits), static_cast(dispatchCacheEvictions), static_cast(cacheHits), captureMs / 1000, keyMs / 1000, recompileMs / 1000, deviceMs / 1000); + if (profile && dispatches.fetch_add(1, std::memory_order_relaxed) % 100 == 99) { + std::uint64_t hits = 0; + std::uint64_t evictions = 0; + { + std::lock_guard cacheLock(dispatchCacheMutex); + hits = dispatchCacheHits; + evictions = dispatchCacheEvictions; + } + AgcDriver::ProfilePrint_nid_no_patch( "[gpu] %llu dispatches (%llu dispatch cache hits, %llu evictions, %llu recompile cache hits): capture %.1f s, cache key %.1f s, recompile %.1f s, device %.1f s\n", static_cast(dispatches.load(std::memory_order_relaxed)), static_cast(hits), static_cast(evictions), static_cast(cacheHits.load(std::memory_order_relaxed)), captureMs.load(std::memory_order_relaxed) / 1000, keyMs.load(std::memory_order_relaxed) / 1000, recompileMs.load(std::memory_order_relaxed) / 1000, deviceMs.load(std::memory_order_relaxed) / 1000); + } } } 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 50a5eee57b..b7a09262cf 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchAhead.cpp @@ -2,6 +2,7 @@ #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/Memory/DrawWriteRanges.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" @@ -108,10 +109,6 @@ void NoteLabel(std::vector& labels, std::uint64_t address, std::span regions, const std::vector& labels) { for (const auto& label : labels) { for (const auto& region : regions) { @@ -136,7 +133,6 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l QueueState scratch = live; std::vector labels; std::vector> writes; - bool unknownWrites = false; for (const auto& label : deferredLabels().labels) NoteLabel(labels, label.address, std::span(label.bytes).first(label.size)); struct Resolving { std::size_t offset; @@ -277,7 +273,7 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l const auto argumentsEnd = arguments + 3u * sizeof(std::uint32_t); const bool written = std::any_of(writes.begin(), writes.end(), [&](const auto& range) { return range.first < argumentsEnd && arguments < range.second; }) || std::any_of(labels.begin(), labels.end(), [&](const RunLabel& label) { return label.address < argumentsEnd && arguments < label.address + label.size; }); - if ((unknownWrites || written) && indirectArgumentsPath(packet, arguments) != IndirectGpu) { + if (written && indirectArgumentsPath(packet, arguments) != IndirectGpu) { ++stats.writtenBefore; break; } @@ -302,10 +298,31 @@ void Driver::resolveGroupAhead(const Submission& submission, const QueueState& l ++stats.writtenBefore; break; } - if (found->second.compiledResult != nullptr) appendWrittenRanges(*found->second.compiledResult, writes); - if (found->second.compiledResult == nullptr || WritesThroughPointers(*found->second.compiledResult)) unknownWrites = true; + if (found->second.compiledResult == nullptr) { + ++stats.unknownWrites; + break; + } + if (!found->second.cached && found->second.registeredShader != nullptr) { + static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + const auto& snapshot = *found->second.registeredShader; + std::vector memory{{snapshot.codeAddress, std::as_bytes(std::span(snapshot.code))}}; + if (!snapshot.header.empty()) memory.push_back({snapshot.headerAddress, snapshot.header}); + auto lap = std::chrono::steady_clock::now(); + std::array phaseMs{}; + auto phaseLap = lap; + DispatchPhaseTiming phaseTiming{profile, lap, phaseMs, phaseLap}; + insertDispatch(found->second.address, found->second.key, found->second.noDispatchCache, profile, found->second.registeredShader, found->second.forgetAtCapture, memory, found->second.shaderMemory, found->second.captured, found->second.capture, found->second.compiledResult, found->second.missedEntry, found->second.missedDiffering, found->second.attachVariant, phaseTiming); + } + bool bounded = false; + const auto itemWrites = DispatchWriteRanges(*found->second.compiledResult, bounded); + if (!bounded) { + ++stats.unknownWrites; + break; + } + writes.insert(writes.end(), itemWrites.begin(), itemWrites.end()); } else { - unknownWrites = true; + ++stats.unknownWrites; + break; } } else if (opcode == 0x37 || opcode == 0x49) { const auto label = Pm4::DecodeLabelWrite(packet); @@ -334,10 +351,10 @@ void Driver::reportGroupCapture(std::uint32_t queue) { auto& stats = groupCaptureStats(); const auto now = std::chrono::steady_clock::now(); if (now - stats.reported < std::chrono::seconds(10)) return; - AgcDriver::ProfilePrint_nid_no_patch("[groupcap] queue 0x%x (10 s): %llu runs resolved %llu dispatches (%llu cache hits) in %.1f ms (max %.2f ms); %llu adopted %.2f ms after the resolve on average, max %.2f ms; mismatches: address %llu key %llu 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", + 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 stopped at unknown writes, %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.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)); + static_cast(stats.failures), static_cast(stats.stale), static_cast(stats.labelOverlaps), static_cast(stats.writtenBefore), static_cast(stats.unknownWrites), static_cast(stats.deviceRejects), static_cast(stats.argumentMismatches)); stats = GroupCaptureStats{}; stats.reported = now; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp index a691951f22..7bfa057a40 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchCache.cpp @@ -66,7 +66,7 @@ Driver::DispatchCacheFootprint Driver::DispatchCacheSize() { std::uint64_t Driver::variantBytes(const DispatchVariant& variant) { const auto held = [](const auto& values) { return static_cast(values.capacity() * sizeof(values[0])); }; - return sizeof(DispatchVariant) + held(variant.runs) + held(variant.words) + held(variant.shared) + held(variant.dataPositions) + held(variant.dataSlots) + held(variant.runBases) + held(variant.shaderDataPatches) + held(variant.captured) + held(variant.spans) + (variant.vertexInfo != nullptr ? sizeof(ShaderRecompiler::ShaderVertexStageInfo) : 0) + (variant.memory != nullptr ? variant.memory->Bytes() : 0); + return sizeof(DispatchVariant) + held(variant.runs) + held(variant.words) + held(variant.shared) + held(variant.dataPositions) + held(variant.dataSlots) + held(variant.runBases) + held(variant.shaderDataPatches) + held(variant.captured) + held(variant.spans) + (variant.registeredViews != nullptr ? held(*variant.registeredViews) : 0u) + (variant.vertexInfo != nullptr ? sizeof(ShaderRecompiler::ShaderVertexStageInfo) : 0) + (variant.memory != nullptr ? variant.memory->Bytes() : 0); } void Driver::accountVariant(const DispatchVariant& variant, bool added) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchInsert.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchInsert.cpp index e732e518ee..6f7607eb33 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchInsert.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchInsert.cpp @@ -18,6 +18,7 @@ void Driver::insertDispatch(std::uint64_t address, std::uint64_t key, bool noDis if (!noDispatchCache) { auto fresh = std::make_shared(); fresh->compiled = compiledResult; + if (capture != nullptr && !capture->snapshot.registeredImages.empty()) fresh->registeredViews = std::make_shared>(capture->snapshot.registeredImages); fresh->shader = registeredShader; fresh->forgetSerial = forgetAtCapture; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchLookup.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchLookup.cpp index b0b37b7900..6b9bdf10f2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchLookup.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchLookup.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "Optimization/ResourceProgram.hpp" #include namespace AgcDriver::DriverDetail { @@ -37,6 +38,7 @@ void Driver::lookupDispatch(std::uint64_t address, const Submission& submission, std::optional sampling; for (std::size_t i = 0; i < variants.size(); ++i) { + if (variants[i]->registeredViews != nullptr && !CaptureRegisteredViewsCurrent(*variants[i]->registeredViews)) continue; regions.clear(); appendEntryRegions(*variants[i], regions); ++compared; @@ -58,16 +60,20 @@ void Driver::lookupDispatch(std::uint64_t address, const Submission& submission, current = variant != nullptr; } else { variant = variants.front(); - compared = 1; - current = true; - for (const auto& [begin, bytes] : variant->spans) { - GuestMemory::CollectWrites(begin, bytes); - if (!GuestMemory::UnchangedSince(begin, bytes, generation)) { - current = false; - break; + current = variant->registeredViews == nullptr || CaptureRegisteredViewsCurrent(*variant->registeredViews); + if (!current) variant.reset(); + if (variant != nullptr) { + compared = 1; + current = true; + for (const auto& [begin, bytes] : variant->spans) { + GuestMemory::CollectWrites(begin, bytes); + if (!GuestMemory::UnchangedSince(begin, bytes, generation)) { + current = false; + break; + } } } - if (!current) { + if (!current && variant != nullptr) { std::uint64_t collected = 0; for (const auto& [begin, bytes] : variant->spans) collected = std::max(collected, GuestMemory::CollectWrites(begin, bytes)); 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 04568afd5b..03bdda6fdf 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "Optimization/ResourceProgram.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" @@ -657,6 +658,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span packe throw; } for (std::size_t i = 0; i < matched.size(); ++i) { + if (matched[i] != nullptr) stageCaptures[i].registeredViews = matched[i]->registeredViews; if (matched[i] == nullptr || (matched[i]->shaderDataPatches.empty() && matched[i]->descriptorPatches.empty())) continue; applyShaderDataPatches(*matched[i]); if (stageData[i].compiled != nullptr) stageData[i].compiled = patchDataWords(*matched[i], stageData[i].words); @@ -923,6 +925,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { const auto candidate = std::find_if(dataCandidates[i].begin(), dataCandidates[i].end(), [&](const auto& kept) { return kept.first->pushOffset == pushCursorBytes; }); if (candidate != dataCandidates[i].end() && !verifyDrawDataHits()) { matched[i] = candidate->first; + stageCaptures[i].registeredViews = matched[i]->registeredViews; matchedRegions[i] = std::move(candidate->second); reused[i] = true; programResults[i] = stageData[i].compiled != nullptr ? stageData[i].compiled.get() : matched[i]->compiled.get(); @@ -955,7 +958,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { 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}); + stages.push_back({program.binary.stage, &result, result.pushConstants.empty() ? 0u : pushCursorBytes, stageCaptures[i].registeredViews.get()}); pushCursorBytes += static_cast(result.pushConstants.size()); } catch (...) { TraceMaterializationFailure(trace.token, submission, packetOffset, "push-constant-validation", completedSubdraws, std::current_exception()); @@ -1058,6 +1061,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { if (!rejected.empty()) return DrawVerdict::Rejected; programResults[0] = compiledStages.back().get(); stages[0].program = programResults[0]; + stages[0].registeredImageViews = stageCaptures[0].registeredViews.get(); 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"); @@ -1130,6 +1134,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { for (std::size_t record = 0; record < records.size(); ++record) { if (drawable(records[record])) lastDrawable = record; } + bool recordedOne = false; for (std::uint32_t record = 0; record < records.size(); ++record) { const auto& arguments = records[record]; if (traceIndirect) std::fprintf(stderr, "[draw] record %u: count %u instances %u first %u vertexOffset %u startInstance %u\n", record, arguments.count, arguments.instances, arguments.firstVertexOrIndex, arguments.vertexOffset, arguments.firstInstance); @@ -1174,6 +1179,7 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { const auto pushBytes = result.pushConstants.size(); decodeVertexInfo(programIndex); 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); + for (auto& stage : stages) if (stage.program == &result) stage.registeredImageViews = stageCaptures[programIndex].registeredViews.get(); if (!rejected.empty()) return DrawVerdict::Rejected; require(result.pushConstants.size() == pushBytes, "patched program changed its push constant layout"); } @@ -1185,6 +1191,11 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { return DrawVerdict::Rejected; } if (pipelined && pipelinedIndirect && record == lastDrawable && !gpuLock.owns_lock() && deferredLabels().labels.empty()) { + runCaptureCheckHook(); + if (tail.queue != nullptr && capturedMemoryChanged(memory)) { + const RecaptureScope recapture; + return draw(*tail.queue, tail.packet, submission, tail.packetOffset, rejected); + } phaseTiming.Phase(DrawRowVectors); auto writes = drawWriteRanges(graphics, stages); const auto commitQueue = submission.queue; @@ -1203,6 +1214,15 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { } drainBeforeInline(); lockForDraw(); + if (!recordedOne) { + runCaptureCheckHook(); + if (tail.queue != nullptr && capturedMemoryChanged(memory, snapshots)) { + gpuLock.unlock(); + const RecaptureScope recapture; + return draw(*tail.queue, tail.packet, submission, tail.packetOffset, rejected); + } + } + recordedOne = true; noteDrawWriters(stages, submission.queue); phaseTiming.Phase(DrawRowVectors); localDevice->Draw(graphics, direct, stages, snapshots, nullptr, tracePtr); @@ -1239,6 +1259,11 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { } } if (pipelined && !drawParameters.indirect && (tail.queue == nullptr || deferredLabels().labels.empty())) { + runCaptureCheckHook(); + if (tail.queue != nullptr && capturedMemoryChanged(memory)) { + const RecaptureScope recapture; + return draw(*tail.queue, tail.packet, submission, tail.packetOffset, rejected); + } phaseTiming.Phase(DrawRowVectors); auto writes = drawWriteRanges(graphics, stages); const auto commitQueue = submission.queue; @@ -1258,6 +1283,12 @@ DrawVerdict Driver::finishDraw(DrawTail& tail, PerformanceTimer& timing) { } if (drawParameters.indirect) drainBeforeInline(); lockForDraw(); + runCaptureCheckHook(); + if (tail.queue != nullptr && capturedMemoryChanged(memory, snapshots)) { + gpuLock.unlock(); + const RecaptureScope recapture; + return draw(*tail.queue, tail.packet, submission, tail.packetOffset, rejected); + } noteDrawWriters(stages, submission.queue); phaseTiming.Phase(DrawRowVectors); if (recipe != nullptr) { 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 02fc76119e..bed17faa1b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp @@ -227,8 +227,8 @@ void Driver::eraseDrawEntry(std::uint64_t key, const std::shared_ptr& bool DecodeProgramsCurrent(const DrawDecode& decode, const ShaderRegistry&) { return std::all_of(decode.programs.begin(), decode.programs.end(), [&](const DrawProgram& program) { if (!program.snapshot->header.empty()) return true; - return ReadRawShader(program.snapshot->codeAddress + program.codeOffset * sizeof(std::uint32_t)) == program.snapshot && - (program.tailContinuation == nullptr || ReadRawShader(program.tailContinuation->codeAddress) == program.tailContinuation); + return ReadRawShader(program.snapshot->codeAddress + program.codeOffset * sizeof(std::uint32_t), program.snapshot) == program.snapshot && + (program.tailContinuation == nullptr || ReadRawShader(program.tailContinuation->codeAddress, program.tailContinuation) == program.tailContinuation); }); } 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 c33af99f90..735319763b 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCapture.cpp @@ -117,6 +117,7 @@ std::shared_ptr Driver::materializeDraw stageCapture.regions = shaderMemory.TakeRecentRegions(); stageCapture.supplied = shaderMemory.SuppliedBlocks(); stageCapture.userDataUse = capture->userDataUse; + stageCapture.registeredViews = std::make_shared>(capture->snapshot.registeredImages); recompiled[i] = true; memory = shaderMemory.Regions(); @@ -225,6 +226,7 @@ void Driver::cacheDrawStages(bool useDrawEntries, bool drawHit, const Pm4::DrawP if (stageCapture.compiled == nullptr || !CacheableResult(*stageCapture.compiled)) continue; auto variant = std::make_shared(); variant->compiled = stageCapture.compiled; + variant->registeredViews = stageCapture.registeredViews; if (programs[i].binary.stage == ShaderRecompiler::ShaderStage::Mesh) { std::copy_n(programs[i].userData.begin() + ShaderRecompiler::MeshIndexBufferUserWord, variant->meshIndexDescriptor.size(), variant->meshIndexDescriptor.begin()); } 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 3976823654..c61e3112c5 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawLookup.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "Optimization/ResourceProgram.hpp" #include #include @@ -141,6 +142,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptrregisteredViews != nullptr && !CaptureRegisteredViewsCurrent(*variant->registeredViews)) continue; if (variant->pushOffset != cursor) continue; if (programs[i].binary.stage == ShaderRecompiler::ShaderStage::Mesh && !std::equal(variant->meshIndexDescriptor.begin(), variant->meshIndexDescriptor.end(), programs[i].userData.begin() + ShaderRecompiler::MeshIndexBufferUserWord)) { if (!anyLayout) outcome = EntryOutcome::Differing; @@ -249,6 +251,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptrdecode->programs[i].userData; auto& candidates = dataCandidates[i]; for (const auto& variant : dataEntry->stages[i]) { + if (variant->registeredViews != nullptr && !CaptureRegisteredViewsCurrent(*variant->registeredViews)) continue; bool usedEqual = true; for (std::size_t w = 0; w < now.size() && usedEqual; ++w) { if (now[w] != was[w] && (w >= 64u || ((variant->userDataMask >> w) & 1u) != 0)) usedEqual = 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 53dcfc483e..62b29cb9c7 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPrecheck.cpp @@ -6,6 +6,7 @@ #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/DeferredLabels.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include namespace AgcDriver::DriverDetail { @@ -79,6 +80,10 @@ std::optional Driver::precheckDraw(const QueueState& queue, const S auto copyDraw = DepthCopyDraw(queue, *copy); return draw(copyDraw, packet, submission, packetOffset, rejected); } + if (const auto control = Graphics::DecodeDepthRenderControl(queue); control && ((control->control & 0x60u) != 0u || Graphics::DepthSurfaceAt(control->address))) { + if (DrawPipeline::Active()) DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Packet, (packet[0] >> 8u) & 0xffu); + Graphics::NoteDepthRenderControl(*control); + } if (Graphics::DepthResummarizeDrawsNothing(queue)) return DrawVerdict::Nothing; rejected = Graphics::DepthMaintenanceRejection(queue); if (!rejected.empty()) return DrawVerdict::Rejected; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp index e12649fac4..585f029413 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp @@ -33,6 +33,9 @@ #include #include #endif +#ifdef __APPLE__ +#include "prx/libSceAgcDriver/Execution/include/Driver/DriverMemory.hpp" +#endif namespace AgcDriver::DriverDetail { @@ -54,9 +57,15 @@ void PrintFailure(std::exception_ptr error) { std::fflush(stderr); } +#ifdef __APPLE__ +constexpr std::uint64_t DriverMemoryAddress = MacOsDriverMemoryBase; +constexpr std::size_t DriverMemoryBytes = DriverMemorySpan; +constexpr std::uint64_t FsTableAddress = RetargetPs5DriverMemory(0xfe0040000ull); +#else constexpr std::uint64_t DriverMemoryAddress = 0xfe0000000ull + GuestArena::GuestAddressOffset; constexpr std::size_t DriverMemoryBytes = 2u << 20u; constexpr std::uint64_t FsTableAddress = DriverMemoryAddress + 0x40000ull; +#endif constexpr std::uint64_t FsTableSlotBytes = 16u; constexpr std::uint64_t FsTableSlots = 6u; constexpr std::uint64_t FsRoutineRecords = 8u; @@ -73,6 +82,10 @@ void MapDriverMemory() { #ifdef _WIN32 mapped = reinterpret_cast(DriverMemoryAddress); GuestArena::GuestArenaCommit_nid_postfix(mapped, DriverMemoryBytes, PAGE_READWRITE, DriverMemoryBytes); +#elif defined(__APPLE__) + mapped = mmap(reinterpret_cast(DriverMemoryAddress), DriverMemoryBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (mapped == MAP_FAILED) throw std::system_error(errno, std::generic_category(), "map the AGC driver memory"); + if (reinterpret_cast(mapped) != DriverMemoryAddress) munmap(mapped, DriverMemoryBytes); #else mapped = mmap(reinterpret_cast(DriverMemoryAddress), DriverMemoryBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); if (mapped == MAP_FAILED) throw std::system_error(errno, std::generic_category(), "map the AGC driver memory"); @@ -106,6 +119,11 @@ bool StackHoldsImport() { std::uintptr_t low = 0; std::uintptr_t high = 0; Bounds() { +#ifdef __APPLE__ + const auto self = pthread_self(); + high = reinterpret_cast(pthread_get_stackaddr_np(self)); + low = high - pthread_get_stacksize_np(self); +#else pthread_attr_t attributes; if (pthread_getattr_np(pthread_self(), &attributes) != 0) return; void* base = nullptr; @@ -115,6 +133,7 @@ bool StackHoldsImport() { high = low + size; } pthread_attr_destroy(&attributes); +#endif } }; thread_local const Bounds bounds; 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 ca7300b601..a9063591bb 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp @@ -128,7 +128,7 @@ void Driver::noteCopyWriter(std::uint64_t program, std::uint64_t begin, std::uin knownValueEntries.fetch_add(1, std::memory_order_relaxed); } std::lock_guard lock(writtenBuffersMutex); - writtenBuffers.Push({program, begin, end, ++writtenBufferSerial, queue, false, std::move(known), generation}); + writtenBuffers.Push({known || !copyKnownValues() ? program : 0, begin, end, ++writtenBufferSerial, queue, false, std::move(known), generation}); } bool Driver::copyKnownValues() { 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 2fa2bcb3fa..93598b3b93 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WrittenBuffers.cpp @@ -53,11 +53,11 @@ void Driver::noteWrittenBuffers(std::uint64_t program, std::uint32_t queue, cons } std::vector> DispatchWriteRanges(const ShaderRecompiler::RecompileResult& compiled, bool& bounded) { - bounded = compiled.bdaAbiVersion == 0; + bounded = compiled.bdaAbiVersion == 0 || std::any_of(compiled.bindings.begin(), compiled.bindings.end(), [](const ShaderRecompiler::DescriptorBinding& binding) { return binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer; }); std::vector> ranges; constexpr std::uint64_t UnknownImageBytes = 64ull * 1024 * 1024; for (const auto& binding : compiled.bindings) { - if (binding.role == ShaderRecompiler::DescriptorRole::Gds || binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable || binding.role == ShaderRecompiler::DescriptorRole::FaultBuffer) bounded = false; + if (binding.role == ShaderRecompiler::DescriptorRole::Gds || binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable) bounded = false; if (binding.role != ShaderRecompiler::DescriptorRole::GuestImages || binding.kind != ShaderRecompiler::DescriptorKind::StorageImage) continue; for (std::uint32_t element = 0; element < binding.count; ++element) { const bool written = element >= binding.imageWritten.size() || binding.imageWritten[element]; @@ -111,7 +111,7 @@ std::vector> DrawWriteRanges(const Graph const bool written = element >= binding.imageWritten.size() || binding.imageWritten[element]; if (!written || binding.guestDescriptor.size() < (static_cast(element) + 1) * 8) continue; const auto words = std::span(binding.guestDescriptor).subspan(static_cast(element) * 8, 8); - if (words[0] == 0u && (words[1] & 0xffu) == 0u) continue; + if (Graphics::IsNullTextureResource(words)) continue; const auto resource = Graphics::DecodeTextureResource(words); if (resource.baseAddress == 0) continue; const auto surfaceBytes = Graphics::DescribeSurface(resource).guestBytes; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp index dd7c023bab..4a2d32e0ee 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp @@ -56,7 +56,7 @@ void Driver::validate(const Submission& submission, const std::uint32_t* guest) const auto header = commands[cursor]; if (Pm4::FillerPacket(header)) { ++cursor; continue; } if ((header & 0xc0000000u) != 0xc0000000u) { - char what[96]; + char what[128]; std::snprintf(what, sizeof(what), "unsupported PM4 packet type: header 0x%08x at DWORD %zu of %zu", header, cursor, commands.size()); dumpPackets(commands, guest); 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 db513b2c5e..4907b257e8 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp @@ -27,6 +27,24 @@ namespace { constexpr std::uint32_t eopSelfSignalFlipLimit = 4; +struct RingOrder { + std::uint32_t queue = 0xffffffffu; + std::uint64_t serial = 0; + bool afterDispatch = false; +}; + +thread_local RingOrder ringOrder; + +void noteRingPacket(std::uint32_t queueId, std::uint64_t serial, std::uint32_t opcode, std::uint32_t header) { + if (ringOrder.queue != queueId || ringOrder.serial != serial) ringOrder = {queueId, serial, false}; + if (opcode == 0x15 || opcode == 0x16 || opcode == 0x76) return; + if (opcode == 0x10) { + const auto sub = (header >> 2u) & 0x3fu; + if ((header & 0xfcu) == 0 || sub == 0x0b || sub == 0x0c) return; + } + ringOrder.afterDispatch = false; +} + bool orderedFills() { static const bool ordered = std::getenv("APS5_PIPELINE_DRAIN_FILLS") == nullptr; return ordered; @@ -63,12 +81,12 @@ 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 (destination == Pm4::DmaSelectGds) return true; const auto bytes = Pm4::DmaBytes(packet); if (bytes == 0) return true; const auto source = Pm4::DmaSource(packet); if ((source == 0 || source == 3) && DrawPipeline::Queue0().Overlaps(packet[2] | (static_cast(packet[3]) << 32u), bytes)) return false; - const auto address = packet[4] | (static_cast(packet[5]) << 32u); + if (source == Pm4::DmaSelectGds && DrawPipeline::Queue0().Overlaps(Pm4::GdsAddress() + packet[2], bytes)) return false; + const auto address = destination == Pm4::DmaSelectGds ? Pm4::GdsAddress() + packet[4] : packet[4] | (static_cast(packet[5]) << 32u); return !DrawPipeline::Queue0().Overlaps(address, bytes); } case 0x3c: case 0x93: { @@ -273,6 +291,7 @@ void Driver::execute(const Submission& submission) { } if (APS5_ENABLE_TIMING_LOG && submission.queue == 0 && pendingFrameTiming == nullptr) includeTimingSubmission(submission, false); PerformanceContext timingContext(submission.queue == 0 ? pendingFrameTiming.get() : FrameTiming::Async()); + if (submission.queue != 0 && Graphics::Recorder::DispatchOverlapEnabled()) noteRingPacket(submission.queue, submission.serial, opcode, header); PerformanceTimer timing("Driver.Packet"); if (!predicated) noteLabelIssue(packet, submission.queue); if (pipelined && !predicated && header != FlipPacketHeader && header != RenderingWaitPacketHeader && !submission.flips.contains(cursor) && (enqueueLabelPacket(packet, opcode, submission.queue) || enqueueDmaPacket(packet, opcode, submission.queue, queue))) { @@ -396,6 +415,7 @@ void Driver::execute(const Submission& submission) { CheckFailure(); std::uint64_t batchesAtFlip = 0, unsignaledAtFlip = 0; bool flipInterruptDeferred = false; + Graphics::StorageTexture::NoteFlip(); if (!drains) { GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush); @@ -450,6 +470,15 @@ void Driver::execute(const Submission& submission) { timed(&WorkerProfile::dispatchMs, [&] { resolveGroupAhead(submission, queue, cursor); }); } } + const bool ringOverlap = submission.queue != 0 && Graphics::Recorder::DispatchOverlapEnabled(); + if (ringOverlap) Graphics::Recorder::SetDispatchOrder(submission.queue + 1u, ringOrder.afterDispatch); + struct ClearOrder { + bool active; + ~ClearOrder() { + if (active) Graphics::Recorder::SetDispatchOrder(0, false); + } + } clearOrder{ringOverlap}; + if (ringOverlap) ringOrder.afterDispatch = true; if (opcode == 0x15) timed(&WorkerProfile::dispatchMs, [&] { dispatch(queue, packet, submission); }); else timed(&WorkerProfile::dispatchMs, [&] { dispatchIndirect(queue, packet, submission); }); Pm4::NoteDispatchRegisters(packet, queue); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketFlush.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketFlush.cpp index 410a98b3c8..8c0bda1fd1 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketFlush.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketFlush.cpp @@ -105,7 +105,7 @@ void Driver::flushBetweenPackets(std::uint32_t queue, std::uint32_t header, bool lastReport = reportNow; const auto stores = Graphics::Recorder::StoreCounts(); const auto failed = [](TrySite site) { return static_cast(triesFailed[site].load()); }; - AgcDriver::ProfilePrint_nid_no_patch("[labels] batch flushes between packets: %llu at the next packet, %llu by deadline, %llu by size cap (%llu well overdue, waited for the mutex); %llu boundary reaps; record tries skipped (self-locking next) %llu; failed tries: label %llu, flush %llu, reap %llu, poll %llu, idle %llu, capture %llu, poll-record %llu; at the packet's own lock: %llu label groups (%.0f ms), %llu submits (%.0f ms incl. queue 0's reaps), left to it by a failed try: %llu groups, %llu submits; after a capture: %llu groups recorded by a try, %llu packets redone for a queued label over the capture; %llu suspend points; stores: %llu in %llu runs (%llu joined, %llu replaced, %llu WAW barriers, %llu joins refused; %llu runs recorded at submit, %llu in place before a writer or reader); DCC key stores: %llu queued in %llu runs (%llu before a writer, %llu joined a queued range); queued labels: %llu noted (%llu over a recorded entry), %llu same-queue wait hits, %llu groups recorded from a poll loop, %llu from the flush hook (%llu hook accesses, %llu inside a completion skipped)\n", static_cast(packetFlushes.load()), static_cast(deadlineFlushes.load()), static_cast(capFlushes.load()), static_cast(overdueLocks.load()), static_cast(boundaryReaps.load()), static_cast(recordTriesSkipped.load()), failed(TryLabel), failed(TryFlush), failed(TryReap), failed(TryPoll), failed(TryIdle), failed(TryCapture), failed(TryPollRecord), static_cast(packetLockRecords.load()), packetLockRecordUs.load() / 1000.0, static_cast(packetLockSubmits.load()), packetLockSubmitUs.load() / 1000.0, static_cast(packetLockDeferred.load()), static_cast(packetSubmitDeferred.load()), static_cast(captureTryRecords.load()), static_cast(captureRetries.load()), static_cast(suspendPoints.load()), static_cast(stores.stores), static_cast(stores.runs), static_cast(stores.joined), static_cast(stores.replaced), static_cast(stores.wawBarriers), static_cast(stores.joinsRefused), static_cast(stores.runsAtSubmit), static_cast(stores.runsForced), static_cast(stores.keyStores), static_cast(stores.keyStoreRuns), static_cast(stores.keyStoreRunsForWriter), static_cast(stores.keyStoresJoined), static_cast(stores.queuedNoted), static_cast(stores.queuedOverRecorded), static_cast(stores.queuedHits), static_cast(pollLabelRecords.load()), static_cast(hookLabelRecords.load()), static_cast(stores.queuedHookRecords), static_cast(stores.queuedHookInCompletion)); + AgcDriver::ProfilePrint_nid_no_patch("[labels] batch flushes between packets: %llu at the next packet, %llu by deadline, %llu by size cap (%llu well overdue, waited for the mutex); %llu boundary reaps; record tries skipped (self-locking next) %llu; failed tries: label %llu, flush %llu, reap %llu, poll %llu, idle %llu, capture %llu, poll-record %llu; at the packet's own lock: %llu label groups (%.0f ms), %llu submits (%.0f ms incl. queue 0's reaps), left to it by a failed try: %llu groups, %llu submits; after a capture: %llu groups recorded by a try, %llu packets redone for a queued label over the capture, %llu for captured memory that changed before recording; %llu suspend points; stores: %llu in %llu runs (%llu joined, %llu replaced, %llu WAW barriers, %llu joins refused; %llu runs recorded at submit, %llu in place before a writer or reader); DCC key stores: %llu queued in %llu runs (%llu before a writer, %llu joined a queued range); queued labels: %llu noted (%llu over a recorded entry), %llu same-queue wait hits, %llu groups recorded from a poll loop, %llu from the flush hook (%llu hook accesses, %llu inside a completion skipped)\n", static_cast(packetFlushes.load()), static_cast(deadlineFlushes.load()), static_cast(capFlushes.load()), static_cast(overdueLocks.load()), static_cast(boundaryReaps.load()), static_cast(recordTriesSkipped.load()), failed(TryLabel), failed(TryFlush), failed(TryReap), failed(TryPoll), failed(TryIdle), failed(TryCapture), failed(TryPollRecord), static_cast(packetLockRecords.load()), packetLockRecordUs.load() / 1000.0, static_cast(packetLockSubmits.load()), packetLockSubmitUs.load() / 1000.0, static_cast(packetLockDeferred.load()), static_cast(packetSubmitDeferred.load()), static_cast(captureTryRecords.load()), static_cast(captureRetries.load()), static_cast(captureChangeRetries.load()), static_cast(suspendPoints.load()), static_cast(stores.stores), static_cast(stores.runs), static_cast(stores.joined), static_cast(stores.replaced), static_cast(stores.wawBarriers), static_cast(stores.joinsRefused), static_cast(stores.runsAtSubmit), static_cast(stores.runsForced), static_cast(stores.keyStores), static_cast(stores.keyStoreRuns), static_cast(stores.keyStoreRunsForWriter), static_cast(stores.keyStoresJoined), static_cast(stores.queuedNoted), static_cast(stores.queuedOverRecorded), static_cast(stores.queuedHits), static_cast(pollLabelRecords.load()), static_cast(hookLabelRecords.load()), static_cast(stores.queuedHookRecords), static_cast(stores.queuedHookInCompletion)); } } 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 2e9ce460eb..887e45d265 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketMemory.cpp @@ -16,9 +16,44 @@ #include #include #include +#include namespace AgcDriver::DriverDetail { +namespace { + +bool gdsWritesPending(std::span packet, const VulkanDevice* localDevice) { + const auto address = Pm4::GdsAddress() + packet[4]; + const auto bytes = static_cast(Pm4::DmaBytes(packet)); + const std::array, 1> range{{{address, address + bytes}}}; + if (Graphics::Recorder::SnapshotWriteOverlaps(address, bytes) || Graphics::StorageTexture::AnyPendingOverlaps(range) || Graphics::AnyShadowedOverlaps(address, bytes)) return true; + if (localDevice == nullptr || !Pm4::GdsImportable()) return false; + std::lock_guard gpuLock(GuestMemory::GpuMutex()); + return localDevice->StoresPendingOver(address, bytes); +} + +std::uint64_t dmaAddress(std::uint32_t low, std::uint32_t high) { + return low | (static_cast(high) << 32u); +} + +bool gdsSpan(std::uint64_t offset, std::size_t bytes) { + return offset <= Pm4::GdsBytes && bytes <= Pm4::GdsBytes - offset; +} + +std::optional decodeCopy(std::span packet) { + if (auto copy = Pm4::DecodeMemoryCopy(packet)) return copy; + if (packet.size() != 7 || !Pm4::GdsImportable()) return std::nullopt; + const auto source = Pm4::DmaSource(packet); + const auto destination = Pm4::DmaDestination(packet); + const std::size_t bytes = Pm4::DmaBytes(packet); + if (bytes == 0) return std::nullopt; + if (source == Pm4::DmaSelectGds && (destination == 0 || destination == 3) && gdsSpan(packet[2], bytes)) return Pm4::MemoryCopy{Pm4::GdsAddress() + packet[2], dmaAddress(packet[4], packet[5]), bytes}; + if ((source == 0 || source == 3) && destination == Pm4::DmaSelectGds && gdsSpan(packet[4], bytes)) return Pm4::MemoryCopy{dmaAddress(packet[2], packet[3]), Pm4::GdsAddress() + packet[4], bytes}; + return std::nullopt; +} + +} + bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue, std::span packet, std::uint32_t header, std::uint32_t opcode, bool& wroteOnGpu, bool& endOfPipeInterrupt, bool& interruptDeferred, bool& drawPacket, bool& sampleDump) { static const bool drainAll = std::getenv("APS5_DRAIN_ALL") != nullptr; static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; @@ -119,7 +154,7 @@ bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue if (!drainAll && !cpuStores && (opcode == 0x40 || opcode == 0x50 || opcode == 0x83)) { constexpr std::size_t gpuStoreLimit = 65536; bool drained = true; - const auto copy = opcode == 0x50 ? Pm4::DecodeMemoryCopy(packet) : std::nullopt; + const auto copy = opcode == 0x50 ? decodeCopy(packet) : std::nullopt; const auto copyBehindPendingSource = [&] { if (!copy.has_value() || copy->bytes > gpuStoreLimit || !GuestMemory::Accessible(reinterpret_cast(copy->source), copy->bytes) || !GuestMemory::Accessible(reinterpret_cast(copy->destination), copy->bytes, true)) return false; const auto end = copy->source + copy->bytes; @@ -137,11 +172,16 @@ bool Driver::preparePacketMemory(const Submission& submission, QueueState& queue if (outcome.path != 1 && outcome.path != 3) throw std::runtime_error("AGC driver: a DMA_DATA copy of up to 64 KiB that GpuCopyPossible accepted took copy path " + std::to_string(outcome.path)); return true; }; - if (copyBehindPendingSource()) { + const bool gdsDestination = opcode == 0x50 && packet.size() >= 7 && Pm4::DmaDestination(packet) == Pm4::DmaSelectGds; + const bool gdsOnGpu = gdsDestination && Pm4::GdsImportable(); + if ((!gdsDestination || gdsOnGpu) && copyBehindPendingSource()) { ++copiesBehindPendingSources; wroteOnGpu = true; drained = false; - } else if (const auto store = Pm4::ResolveStore(packet, queue, gpuStoreLimit)) { + } else if (gdsDestination && !gdsWritesPending(packet, device.Load().get())) { + orderedAlready = true; + drained = false; + } else if (const auto store = gdsDestination && !gdsOnGpu ? std::nullopt : Pm4::ResolveStore(packet, queue, gpuStoreLimit)) { const auto bytes = store->Bytes(); if (bytes.empty()) { @@ -297,6 +337,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) { 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); + if (Pm4::DmaDestination(packet) == Pm4::DmaSelectGds && gdsWritesPending(packet, device.Load().get())) return false; constexpr std::size_t immediateLimit = std::size_t{16} << 20u; constexpr std::size_t gpuStoreLimit = 65536; if (source == 2 && Pm4::DmaBytes(packet) > gpuStoreLimit && Pm4::DmaDestination(packet) != Pm4::DmaSelectGds) { @@ -307,7 +348,7 @@ bool Driver::enqueueDmaPacket(std::span packet, std::uint32 DrawPipeline::Queue0().Enqueue([this, queue, fillAddress, fillBytes, pattern] { GuestMemory::SetCurrentPacket(0x50, queue); if (const auto localDevice = device.Load()) { - storeBufferFill(queue, fillAddress, fillBytes, {pattern, pattern, pattern, pattern}, localDevice, false, false); + storeBufferFill(queue, fillAddress, fillBytes, {pattern, pattern, pattern, pattern}, localDevice, false, true); ++dmaFills; } }, {{fillAddress, fillAddress + fillBytes}}); @@ -319,12 +360,17 @@ bool Driver::enqueueDmaPacket(std::span packet, std::uint32 const auto address = store->address; std::vector bytes(store->Bytes().begin(), store->Bytes().end()); const auto size = bytes.size(); + const bool gds = Pm4::DmaDestination(packet) == Pm4::DmaSelectGds; const bool metadataFill = Pm4::DmaDestination(packet) != 1; const auto fillAddress = packet[4] | (static_cast(packet[5]) << 32u); const auto fillBytes = packet[6] & 0x3ffffffu; const auto pattern = packet[2]; - DrawPipeline::Queue0().Enqueue([this, queue, address, bytes = std::move(bytes), metadataFill, fillAddress, fillBytes, pattern] { + DrawPipeline::Queue0().Enqueue([this, queue, address, bytes = std::move(bytes), gds, metadataFill, fillAddress, fillBytes, pattern] { GuestMemory::SetCurrentPacket(0x50, queue); + if (gds) { + GuestMemory::Write(address, bytes, 1); + return; + } if (metadataFill) { Graphics::NoteDepthMetadataFill(fillAddress, fillBytes, pattern); Graphics::NoteColorMetadataFill(fillAddress, fillBytes, pattern); 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 7ce73fefc9..e30e7dfe0f 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp @@ -144,6 +144,7 @@ void Driver::run(std::uint32_t id) noexcept { StartWorkerSampler(id); Submission submission; static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + static const bool idleStore = std::getenv("APS5_NO_IDLE_STORE") == nullptr; auto& costs = submissionCosts(id); try { PerformanceTrace::BindThread(role); @@ -183,7 +184,7 @@ void Driver::run(std::uint32_t id) noexcept { && std::all_of(workers.begin(), workers.end(), [](const auto& item) { return item.second.pending.empty(); }); }; const auto idleResultsPending = [&] { - return id == 0 && Graphics::StorageTexture::AnyPending() && allQueuesIdle() && !completionsPending(); + return idleStore && id == 0 && Graphics::StorageTexture::AnyPending() && allQueuesIdle() && !completionsPending(); }; const auto poll = [&](const auto& done) { #ifdef _WIN32 @@ -221,13 +222,12 @@ void Driver::run(std::uint32_t id) noexcept { lock.lock(); if (ready()) break; if (id == 0 && allQueuesIdle() && !completionsPending() - && ((!storeIdleResults && Graphics::StorageTexture::AnyPending()) || idleEpoch.load(std::memory_order_acquire) != idleEpochAtCheck)) continue; + && ((idleStore && !storeIdleResults && Graphics::StorageTexture::AnyPending()) || idleEpoch.load(std::memory_order_acquire) != idleEpochAtCheck)) continue; if (id == 0) queue0Dormant.store(true, std::memory_order_relaxed); const auto idleEpochAtWait = idleEpoch.load(std::memory_order_acquire); changed.wait(lock, [&] { return ready() || (id == 0 && (idleEpoch.load(std::memory_order_acquire) != idleEpochAtWait || completionsPending())); }); if (id == 0) queue0Dormant.store(false, std::memory_order_relaxed); - if (id == 0 && !ready() && (idleEpoch.load(std::memory_order_acquire) != idleEpochAtWait || completionsPending())) continue; - break; + continue; } if (poll(ready)) break; lock.unlock(); 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 279a99c2b0..6dbe187d29 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp @@ -1,7 +1,16 @@ #include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "ControlFlow/RequestSerializer.hpp" +#include "Optimization/ResourceProgram.hpp" #include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif #include #include #include @@ -11,6 +20,15 @@ namespace AgcDriver::DriverDetail { namespace { +std::string captureSession() { +#ifdef _WIN32 + const auto process = _getpid(); +#else + const auto process = getpid(); +#endif + return std::to_string(process) + "-" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()); +} + std::optional> selectedPrograms() { const char* value = std::getenv("APS5_DUMP_SHADERS"); if (value == nullptr || std::string_view(value) == "1") return std::nullopt; @@ -32,6 +50,46 @@ std::optional> selectedPrograms() { } +std::shared_ptr PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation) { + std::exception_ptr failure; + std::string dump; + static const bool dumpInputs = std::getenv("APS5_DUMP_PREPARE_INPUTS") != nullptr; + std::string inputDump; + if (dumpInputs) { + try { + static const auto session = captureSession(); + static std::atomic next{0}; + const auto suffix = ".input-" + session + "-" + std::to_string(next.fetch_add(1)) + ".prepare"; + inputDump = Driver::dumpRequest(request.shader.codeAddress, request, suffix); + if (!inputDump.empty()) { + std::fprintf(stderr, "[prepare-input] begin %s\n", inputDump.c_str()); + std::fflush(stderr); + } + } catch (const std::exception& error) { + std::fprintf(stderr, "[prepare-input] capture failed: %s\n", error.what()); + } + } + try { + auto handle = ShaderRecompiler::PrepareShader(request, preparation); + if (!inputDump.empty()) std::fprintf(stderr, "[prepare-input] end %s\n", inputDump.c_str()); + return handle; + } catch (...) { + failure = std::current_exception(); + try { + if (std::getenv("APS5_DUMP_SHADERS") != nullptr) dump = Driver::dumpRequest(request.shader.codeAddress, request, ".prepare"); + } catch (...) {} + } + try { + std::rethrow_exception(failure); + } catch (const std::exception& error) { + std::ostringstream where; + where << "shader 0x" << std::hex << request.shader.codeAddress << " stage " << static_cast(request.shader.stage); + if (!dump.empty()) where << " (" << dump << ")"; + where << ": "; + throw std::runtime_error(where.str() + error.what()); + } +} + void Driver::reportSkip(const char* kind, const std::string& what) { static AgcDriver::Mutex reportedMutex; static std::set reported; @@ -96,14 +154,17 @@ std::string Driver::dumpRequest(std::uint64_t address, const ShaderRecompiler::R if (written != text.size() || closed != 0) { dumped.erase(name); std::fprintf(stderr, "[gpu] incomplete write to dump file %s (%zu of %zu bytes, close status %d)\n", name.c_str(), written, text.size(), closed); + return {}; } } else { dumped.erase(name); std::fprintf(stderr, "[gpu] could not open dump file %s for writing\n", name.c_str()); + return {}; } } catch (const std::exception& error) { dumped.erase(name); std::fprintf(stderr, "[gpu] could not serialize request for 0x%llx: %s\n", static_cast(address), error.what()); + return {}; } return name; } 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 7313bfa80d..f7e6b12fd0 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp @@ -60,41 +60,18 @@ namespace AgcDriver::DriverDetail { -std::shared_ptr PrepareShaderWithDiagnostics(const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::ShaderPreparationContext* preparation) { - std::exception_ptr failure; - std::string dump; - try { - auto handle = ShaderRecompiler::PrepareShader(request, preparation); - return handle; - } catch (...) { - failure = std::current_exception(); - try { - if (std::getenv("APS5_DUMP_SHADERS") != nullptr) dump = Driver::dumpRequest(request.shader.codeAddress, request, ".prepare"); - } catch (...) {} - } - try { - std::rethrow_exception(failure); - } catch (const std::exception& error) { - std::ostringstream where; - where << "shader 0x" << std::hex << request.shader.codeAddress << " stage " << static_cast(request.shader.stage); - if (!dump.empty()) where << " (" << dump << ")"; - where << ": "; - throw std::runtime_error(where.str() + error.what()); - } -} -std::shared_ptr ReadRawShader(std::uint64_t address) { +std::shared_ptr ReadRawShader(std::uint64_t address, const std::shared_ptr& retained) { const auto drain = [](std::uint64_t first, std::size_t bytes) { if (!DrawPipeline::Active()) return; auto& pipeline = DrawPipeline::Queue0(); if (pipeline.Busy() && pipeline.Overlaps(first, bytes)) pipeline.Drain(DrawPipeline::DrainReason::Capture); }; - GuestMemory::CheckRange(reinterpret_cast(address), sizeof(std::uint32_t), 256); static std::mutex cacheMutex; static std::list> cache; static std::size_t cacheBytes = 0; - std::shared_ptr cached; - { + std::shared_ptr cached = retained != nullptr && retained->codeAddress == address && retained->header.empty() && !retained->code.empty() ? retained : nullptr; + if (cached == nullptr) { std::lock_guard lock(cacheMutex); const auto found = std::find_if(cache.begin(), cache.end(), [address](const auto& entry) { return entry->codeAddress == address; }); if (found != cache.end()) cached = *found; @@ -110,6 +87,7 @@ std::shared_ptr ReadRawShader(std::uint64_t address) { return cached; } } + GuestMemory::CheckRange(reinterpret_cast(address), sizeof(std::uint32_t), 256); constexpr std::size_t limit = 1024 * 1024; const auto ranges = GuestMemory::CommittedRanges(address, limit); std::uint64_t end = address; @@ -124,7 +102,7 @@ std::shared_ptr ReadRawShader(std::uint64_t address) { snapshot.code.resize(std::min(available, std::max(64, previous * 2))); drain(address + previous * sizeof(std::uint32_t), (snapshot.code.size() - previous) * sizeof(std::uint32_t)); GuestMemory::Read(address + previous * sizeof(std::uint32_t), - std::as_writable_bytes(std::span(snapshot.code).subspan(previous)), alignof(std::uint32_t)); + std::as_writable_bytes(std::span(snapshot.code.Mutable()).subspan(previous)), alignof(std::uint32_t)); try { const auto decoded = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(snapshot.code); if (const auto tail = ShaderRecompiler::FindUserDataTail(decoded, 0u, 106u)) snapshot.userDataTailRegister = tail->userDataRegister; @@ -902,6 +880,10 @@ void ReportPreparedAtUse(const ShaderSnapshot& snapshot, const ShaderRecompiler: APS5_LOG_OUT_DEBUG("Shader 0x%llx samples a converted image; preparing its converted-sample variant at use", static_cast(request.shader.codeAddress)); return; } + if (request.context.layerRowSamples) { + APS5_LOG_OUT_DEBUG("Shader 0x%llx samples a 1D array past maxImageArrayLayers; preparing its layer-row variant at use", static_cast(request.shader.codeAddress)); + return; + } if (snapshot.codeAddress == NullPixelProgramAddress() && request.shader.stage == ShaderRecompiler::ShaderStage::Fragment && request.context.waveSize == 32u) { APS5_LOG_ERR("The null pixel program has no wave%u artifact for this draw; preparing it at draw", request.context.waveSize); return; @@ -969,8 +951,16 @@ ShaderRecompiler::RectListShaders DrawRectangle(const ShaderSnapshot& front, con return PreparedRectangle(front, vertexId, fragmentId); } +struct UserDataCallSites { + ShaderRecompiler::RdnaProgram program; + std::vector calls; + std::vector tableCalls; +}; + namespace { +constexpr std::size_t UserDataLinksPerProgram = 8u; + std::vector LinkGraphicsUserDataTail(const ShaderRecompiler::RecompileRequest& request, const ShaderSnapshot& snapshot) { using Stage = ShaderRecompiler::ShaderStage; if ((request.shader.stage != Stage::Vertex && request.shader.stage != Stage::Fragment) || !request.shader.header.empty() || !snapshot.userDataTailRegister) return {}; @@ -987,13 +977,13 @@ std::string hexText(std::uint64_t value) { return text; } -std::vector ReadUserDataCallee(std::uint64_t address, std::uint32_t linkRegister) { +std::vector ReadUserDataCallee(std::uint64_t address, std::uint32_t linkRegister, bool allowGetpc = false) { constexpr std::uint64_t page = 4096u; constexpr std::size_t limitWords = 1024u * 1024u / 4u; if (address % 4u != 0u) throw std::runtime_error("its callee address " + hexText(address) + " is not dword aligned"); std::vector words; const auto finish = [&] { - const auto length = ShaderRecompiler::UserDataCalleeLength(words, linkRegister); + const auto length = ShaderRecompiler::UserDataCalleeLength(words, linkRegister, allowGetpc); if (length) words.resize(*length); return length.has_value(); }; @@ -1044,24 +1034,72 @@ std::vector ReadTableCallees(const ShaderRecompil } if (targets.size() > limitCallees) throw std::runtime_error("its call table holds " + std::to_string(targets.size()) + " distinct targets; at most " + std::to_string(limitCallees) + " are linked"); std::vector callees; - for (const auto target : targets) callees.push_back({target, ReadUserDataCallee(target, call.linkRegister)}); + for (const auto target : targets) callees.push_back({target, ReadUserDataCallee(target, call.linkRegister, true)}); return callees; } -ShaderRecompiler::LinkedCalls LinkUserDataCallees(const ShaderRecompiler::RecompileRequest& request) { +std::shared_ptr LinkUserDataCallees(const ShaderRecompiler::RecompileRequest& request, const ShaderSnapshot& snapshot, std::size_t codeOffset) { const bool gcnVertex = ShaderRecompiler::GcnGuestShaders() && request.shader.stage == ShaderRecompiler::ShaderStage::Vertex; - if (request.shader.stage != ShaderRecompiler::ShaderStage::Compute && !gcnVertex) return {}; + if (request.shader.stage != ShaderRecompiler::ShaderStage::Compute && !gcnVertex) return nullptr; const auto code = request.shader.code; - if (std::none_of(code.begin(), code.end(), [](std::uint32_t word) { return (word & 0xff80ff00u) == 0xbe802100u; })) return {}; - const auto program = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(code); + if (std::none_of(code.begin(), code.end(), [](std::uint32_t word) { return (word & 0xff80ff00u) == 0xbe802100u; })) return nullptr; const ShaderRecompiler::SwappcInfo swappc{false, request.context.userDataBaseRegister, static_cast(request.context.userData.size()), request.context.waveSize}; - const auto calls = ShaderRecompiler::FindUserDataCalls(program, swappc); - const auto tableCalls = ShaderRecompiler::FindTableCalls(program, swappc); - if (calls.empty() && tableCalls.empty()) return {}; + const auto addressOf = [&](const ShaderRecompiler::UserDataCall& call) { + const auto slot = call.userDataRegister - swappc.userDataBaseRegister; + return static_cast(request.context.userData[slot]) | static_cast(request.context.userData[slot + 1u]) << 32u; + }; + const auto sameProgram = [&](const PreparedShaderState::UserDataLink& link) { + return link.codeOffset == codeOffset && link.userDataBaseRegister == swappc.userDataBaseRegister && link.userDataCount == swappc.userDataCount && link.waveSize == swappc.waveSize; + }; + std::vector current; + const auto unchanged = [&](const PreparedShaderState::UserDataLink& link) { + for (std::size_t index = 0; index < link.callees.size(); ++index) { + const auto address = addressOf(link.sites->calls[index]); + const auto& words = link.code[index]; + if (address != link.callees[index] || !GuestMemory::Accessible(reinterpret_cast(static_cast(address)), words.size() * 4u)) return false; + current.resize(words.size()); + GuestMemory::Read(address, std::as_writable_bytes(std::span(current))); + if (current != words) return false; + } + return true; + }; + auto& prepared = *snapshot.prepared; + std::vector candidates; + { + std::lock_guard lock(prepared.mutex); + std::copy_if(prepared.userDataLinks.begin(), prepared.userDataLinks.end(), std::back_inserter(candidates), sameProgram); + } + std::shared_ptr sites; + for (auto candidate = candidates.rbegin(); candidate != candidates.rend(); ++candidate) { + sites = candidate->sites; + if (candidate->linked != nullptr && unchanged(*candidate)) return candidate->linked; + } + if (sites == nullptr) { + auto found = std::make_shared(); + found->program = ShaderRecompiler::RdnaInstructionDecoder{}.Decode(code); + found->calls = ShaderRecompiler::FindUserDataCalls(found->program, swappc); + found->tableCalls = ShaderRecompiler::FindTableCalls(found->program, swappc); + sites = std::move(found); + } + const auto& program = sites->program; + const auto& calls = sites->calls; + const auto& tableCalls = sites->tableCalls; + PreparedShaderState::UserDataLink link{codeOffset, swappc.userDataBaseRegister, swappc.userDataCount, swappc.waveSize, sites, {}, {}, nullptr}; + const auto remember = [&] { + std::lock_guard lock(prepared.mutex); + auto& links = prepared.userDataLinks; + if (link.linked == nullptr && std::any_of(links.begin(), links.end(), sameProgram)) return; + if (std::count_if(links.begin(), links.end(), sameProgram) >= static_cast(UserDataLinksPerProgram)) links.erase(std::find_if(links.begin(), links.end(), sameProgram)); + links.push_back(link); + }; + if (calls.empty() && tableCalls.empty()) { + link.linked = std::make_shared(); + remember(); + return link.linked; + } std::vector> callees; for (const auto& call : calls) { - const auto slot = call.userDataRegister - swappc.userDataBaseRegister; - const auto address = static_cast(request.context.userData[slot]) | static_cast(request.context.userData[slot + 1u]) << 32u; + const auto address = addressOf(call); try { callees.push_back(ReadUserDataCallee(address, call.linkRegister)); } catch (const std::exception& error) { @@ -1077,7 +1115,16 @@ ShaderRecompiler::LinkedCalls LinkUserDataCallees(const ShaderRecompiler::Recomp throw std::runtime_error("AGC driver: compute shader " + hexText(request.shader.codeAddress) + " calls a target loaded from a table at program counter " + hexText(call.programCounter) + ", but " + error.what()); } } - return ShaderRecompiler::LinkCalls(program, calls, callees, tableCalls, tableCallees); + auto linked = std::make_shared(ShaderRecompiler::LinkCalls(program, calls, callees, tableCalls, tableCallees)); + if (!tableCalls.empty()) { + remember(); + return linked; + } + for (const auto& call : calls) link.callees.push_back(addressOf(call)); + link.code = std::move(callees); + link.linked = linked; + remember(); + return linked; } } @@ -1086,8 +1133,10 @@ ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& s require(codeOffset < snapshot.code.size(), "prepared shader code offset is outside the snapshot"); const auto code = std::span(snapshot.code).subspan(codeOffset); require(request.shader.code.data() == code.data() && request.shader.code.size() == code.size(), "prepared invocation does not refer to registered code"); - auto linked = LinkUserDataCallees(request); - if (linked.code.empty()) linked.code = LinkGraphicsUserDataTail(request, snapshot); + const auto shared = LinkUserDataCallees(request, snapshot, codeOffset); + ShaderRecompiler::LinkedCalls tail; + if (shared == nullptr || shared->code.empty()) tail.code = LinkGraphicsUserDataTail(request, snapshot); + const auto& linked = shared != nullptr && tail.code.empty() ? *shared : tail; auto source = request; if (!linked.code.empty()) { source.shader.code = linked.code; @@ -1096,6 +1145,7 @@ ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& s std::unique_lock lock(snapshot.prepared->mutex); AwaitRegisteredPreparation(*snapshot.prepared, lock); if (std::ranges::find(snapshot.prepared->convertedSamples, codeOffset) != snapshot.prepared->convertedSamples.end()) source.context.convertedSamples = true; + if (std::ranges::find(snapshot.prepared->layerRowSamples, codeOffset) != snapshot.prepared->layerRowSamples.end()) source.context.layerRowSamples = true; auto invocationRequest = source; struct PreparedKeyStorage {}; auto& key = HostThreadLocal, PreparedKeyStorage>(); @@ -1123,22 +1173,27 @@ ShaderRecompiler::PreparedShaderInvocation InvocationFor(const ShaderSnapshot& s } std::shared_ptr CaptureInvocation(ShaderMemory& memory, const ShaderSnapshot& snapshot, std::size_t codeOffset, const ShaderRecompiler::RecompileRequest& request, ShaderRecompiler::PreparedShaderInvocation& invocation) { - try { - return memory.Capture(invocation); - } catch (const ShaderRecompiler::ConvertedSampleRequired&) { - if (invocation.Request().context.convertedSamples) throw; - } - { + const auto record = [&](std::vector& offsets) { std::lock_guard lock(snapshot.prepared->mutex); - auto& offsets = snapshot.prepared->convertedSamples; if (std::ranges::find(offsets, codeOffset) == offsets.end()) offsets.push_back(codeOffset); + }; + while (true) { + try { + return memory.Capture(invocation); + } catch (const ShaderRecompiler::ConvertedSampleRequired&) { + if (invocation.Request().context.convertedSamples) throw; + record(snapshot.prepared->convertedSamples); + } catch (const ShaderRecompiler::LayerRowSamplesRequired&) { + if (invocation.Request().context.layerRowSamples) throw; + record(snapshot.prepared->layerRowSamples); + } + invocation = InvocationFor(snapshot, codeOffset, request); } - invocation = InvocationFor(snapshot, codeOffset, request); - return memory.Capture(invocation); } namespace { -std::optional DecodeFloatMode(const Registers& registers, std::uint8_t type) { +template +std::optional DecodeFloatMode(const TRegisters& registers, std::uint8_t type) { std::uint32_t rsrc1; std::uint32_t fp16OverflowBit; switch (type) { @@ -1186,7 +1241,7 @@ Shader ReadHeader(const ShaderSnapshot& snapshot) { return header; } -RegisteredShaderState DecodeRegisteredState(const ShaderSnapshot& snapshot, RegisteredShaderState state = {{}, InitialContextRegisters(), {{0x24a, 0}, {0x24b, 0}}}) { +DecodedShaderState DecodeRegisteredState(const ShaderSnapshot& snapshot, DecodedShaderState state = {{}, InitialContextRegisters(), {{0x24a, 0}, {0x24b, 0}}}) { const auto header = ReadHeader(snapshot); for (const auto reg : ReadHeaderArray(snapshot, header.sh_registers, header.num_sh_registers)) { state.shader.insert_or_assign(reg.offset, reg.value); @@ -1434,9 +1489,9 @@ PreparedAtUse PrepareDeferredAbis(const ShaderSnapshot& snapshot, std::unique_lo 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; + state.shader = snapshot.registeredState->shader.Expand(); + state.context = snapshot.registeredState->context.Expand(); + state.userConfig = snapshot.registeredState->userConfig.Expand(); for (const auto& [offset, value] : abi.context) state.context[offset] = value; for (const auto& [offset, value] : abi.primitive) state.userConfig[offset] = value; try { @@ -2142,9 +2197,9 @@ void Driver::ResolveShaderAbi(const Shader* shader, std::spanregisteredState != nullptr, "registered shader state is missing"); QueueState state{}; - state.shader = snapshot->registeredState->shader; - state.context = snapshot->registeredState->context; - state.userConfig = snapshot->registeredState->userConfig; + state.shader = snapshot->registeredState->shader.Expand(); + state.context = snapshot->registeredState->context.Expand(); + state.userConfig = snapshot->registeredState->userConfig.Expand(); for (const auto reg : context) state.context[reg.offset] = reg.value; for (const auto reg : primitive) state.userConfig[reg.offset] = reg.value; const auto localDevice = device.Load(); @@ -2395,9 +2450,9 @@ ShaderSnapshot PrepareNullPixelProgram(const VulkanDevice& device) { nullRegisteredState.context.insert_or_assign(0x1b4u, 0x2u); null.registeredState = std::make_shared(std::move(nullRegisteredState)); QueueState nullState{}; - nullState.shader = null.registeredState->shader; - nullState.context = null.registeredState->context; - nullState.userConfig = null.registeredState->userConfig; + nullState.shader = null.registeredState->shader.Expand(); + nullState.context = null.registeredState->context.Expand(); + nullState.userConfig = null.registeredState->userConfig.Expand(); null.prepared->entries = PrepareRegistered(null, device, nullState, true); return null; } @@ -2444,7 +2499,7 @@ void Driver::RegisterShader(const Shader* shader) { require(!RawGraphicsDrawnIn(reinterpret_cast(code), reinterpret_cast(code) + fields.shader_size), "a shader was registered over a graphics program that was drawn without registration"); ShaderSnapshot snapshot{reinterpret_cast(code), reinterpret_cast(shader), fields.type, {}, {}}; snapshot.code.resize(fields.shader_size / sizeof(std::uint32_t)); - std::memcpy(snapshot.code.data(), code, fields.shader_size); + std::memcpy(snapshot.code.Mutable().data(), code, fields.shader_size); snapshot.header.resize(fields.header_size); std::memcpy(snapshot.header.data(), static_cast(shader), fields.header_size); @@ -2479,7 +2534,9 @@ void Driver::RegisterShader(const Shader* shader) { 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; + if (current->code != snapshot.code) continue; + snapshot.code = current->code; + if (current->registeredState == nullptr || current->type != snapshot.type || *current->registeredState != *snapshot.registeredState) continue; snapshot.registeredState = current->registeredState; break; } @@ -2487,9 +2544,9 @@ void Driver::RegisterShader(const Shader* shader) { } } QueueState registered{}; - registered.shader = snapshot.registeredState->shader; - registered.context = snapshot.registeredState->context; - registered.userConfig = snapshot.registeredState->userConfig; + registered.shader = snapshot.registeredState->shader.Expand(); + registered.context = snapshot.registeredState->context.Expand(); + registered.userConfig = snapshot.registeredState->userConfig.Expand(); std::shared_ptr preparation; if (LazyShaderPreparation()) { static_cast(PlanRegistered(snapshot, *localDevice, registered, true, &snapshot.prepared->deferred)); 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 b43a5c49b2..6a08ad60ce 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp @@ -189,6 +189,45 @@ bool Driver::recordQueuedLabelsAfterCapture(std::uint32_t queue, std::span captureCheckHook{nullptr}; + +bool& recapturing() { + static thread_local bool active = false; + return active; +} + +} + +void Driver::SetCaptureCheckHook(void (*hook)()) { + captureCheckHook.store(hook, std::memory_order_release); +} + +void Driver::runCaptureCheckHook() { + if (const auto hook = captureCheckHook.load(std::memory_order_acquire)) hook(); +} + +bool Driver::capturedMemoryChanged(std::span regions, std::span compared) { + if (recapturing()) return false; + const bool mark = compared.size() == regions.size(); + for (std::size_t i = 0; i < regions.size(); ++i) { + const auto outcome = GuestMemory::CompareMapped(regions[i].guestAddress, regions[i].bytes); + if (outcome == GuestMemory::Compare::Differs) return true; + if (mark && outcome == GuestMemory::Compare::Equal) compared[i].compared = true; + } + return false; +} + +Driver::RecaptureScope::RecaptureScope() { + recapturing() = true; + ++captureChangeRetries; +} + +Driver::RecaptureScope::~RecaptureScope() { + recapturing() = false; +} + void Driver::noteQueuedLabels(std::uint32_t queue) { static const bool installed = (Graphics::Recorder::SetQueuedLabelRecorder(&Driver::recordQueuedLabelsFromHook), true); (void)installed; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/SynchronizationStatistics.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/SynchronizationStatistics.cpp index 5b1e22624b..3922197af8 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/SynchronizationStatistics.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/SynchronizationStatistics.cpp @@ -11,7 +11,7 @@ std::atomic labelFallbacks[5] = {}; std::atomic queuedLabels{0}, labelGroups{0}, immediateLabels{0}; -std::atomic packetLockRecords{0}, packetLockSubmits{0}, packetLockDeferred{0}, packetSubmitDeferred{0}, captureTryRecords{0}, captureRetries{0}, suspendPoints{0}; +std::atomic packetLockRecords{0}, packetLockSubmits{0}, packetLockDeferred{0}, packetSubmitDeferred{0}, captureTryRecords{0}, captureRetries{0}, captureChangeRetries{0}, suspendPoints{0}; std::atomic recordTriesSkipped{0}; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp index fdaf907681..152ba77688 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp @@ -285,6 +285,8 @@ void FrameRecorder::record(FrameCall call, bool prologueCall, std::span shaders) { if (state == State::Waiting) { if (!prologueCall) return; + std::set blocks; + collectShaderPages(shaders, blocks, prologueHostPages); call.ordinal = ++ordinal; prologue.push_back(std::move(call)); return; @@ -361,9 +363,7 @@ std::vector FrameRecorder::newRanges() { return added; } -void FrameRecorder::emitShaderDelta(std::span shaders) { - std::set blocks; - std::set hostPages; +void FrameRecorder::collectShaderPages(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); @@ -391,6 +391,12 @@ void FrameRecorder::emitShaderDelta(std::span shaders) { note(shader->user_data->direct_resource_offset); for (const auto* sharps : shader->user_data->sharp_resource_offset) note(sharps); } +} + +void FrameRecorder::emitShaderDelta(std::span shaders) { + std::set blocks; + std::set hostPages; + collectShaderPages(shaders, blocks, hostPages); if (blocks.empty() && hostPages.empty()) return; const auto mark = GuestMemory::WriteGeneration(); std::vector spans; @@ -538,6 +544,21 @@ void FrameRecorder::start() { snapshot(range); if (!writer) return; } + for (const auto page : prologueHostPages) { + const auto* address = reinterpret_cast(static_cast(page)); + if (!GuestMemory::Accessible(address, HostPageBytes)) continue; + const bool covered = std::ranges::any_of(ranges, [&](const auto& range) { + return range.readable && range.address <= page && page - range.address <= range.bytes && HostPageBytes <= range.bytes - (page - range.address); + }); + if (covered) continue; + const bool writable = GuestMemory::Accessible(address, HostPageBytes, true); + Range range{page, HostPageBytes, FrameRangeKind::Host, writable ? 0x04u : 0x02u, + FrameRangeAccess::Readable | (writable ? FrameRangeAccess::Writable : 0u), true}; + snapshot(range); + if (!writer) return; + ranges.push_back(range); + } + prologueHostPages.clear(); for (const auto& call : prologue) writer->WriteCall(call); std::uintptr_t ringBase = 0; std::uint32_t ringSize = 0; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index fdcf56d9b7..4f952498e6 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -49,13 +49,20 @@ #include #include #include +#ifndef __APPLE__ #include +#endif #include #include #include #include #include #include "prx/libc/include/specifics/linux/HostMapping.hpp" +#ifdef __APPLE__ +#include +#include +#include +#endif #endif namespace AgcDriver::GuestMemory { @@ -375,6 +382,7 @@ namespace { // callback), and address-based builds verify every image section on each build, so without the table // each of those verifications was a VirtualQuery. APS5_NO_IMAGE_PAGE_CACHE=1 leaves the image uncached. constexpr std::size_t PageBytes = 4096; +constexpr std::size_t BlockPages = 64; constexpr std::uint8_t PageReadable = 1; constexpr std::uint8_t PageWritable = 2; constexpr std::uint8_t PageInaccessible = 4; @@ -384,36 +392,48 @@ struct PageSpan { std::size_t size = 0; // Per page: access flags, zero while unknown. std::uint8_t* pages = nullptr; + std::uint8_t* blocks = nullptr; static constexpr std::size_t ChunkEntries = 65536; - struct alignas(8) Chunk { std::array entries{}; }; + struct alignas(8) Chunk { + std::array entries{}; + std::array blocks{}; + }; std::atomic* chunks = nullptr; - std::uint8_t* chunkData(std::size_t index, bool create) const { + Chunk* chunkAt(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(); + return chunk; } 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; + auto* chunk = chunkAt(index / ChunkEntries, create); + return chunk == nullptr ? nullptr : chunk->entries.data() + index % ChunkEntries; + } + + std::uint8_t* blockEntry(std::size_t block, bool create) const { + if (chunks == nullptr) return blocks + block; + auto* chunk = chunkAt(block * BlockPages / ChunkEntries, create); + return chunk == nullptr ? nullptr : chunk->blocks.data() + block % (ChunkEntries / BlockPages); } bool allocate() { if (size == 0) return false; - const auto count = (size / PageBytes + 8) & ~static_cast(7); + const auto count = (size / PageBytes + BlockPages) & ~(BlockPages - 1); #ifdef _WIN32 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)); + const auto blockCount = (count / BlockPages + 8) & ~static_cast(7); + pages = static_cast(std::calloc(count + blockCount, 1)); if (pages == nullptr) size = 0; + else blocks = pages + count; return pages != nullptr; #endif } @@ -422,24 +442,63 @@ struct PageSpan { return size != 0 && address >= base && address - base < size; } + static std::uint8_t loadByte(const std::uint8_t* at) { + return std::atomic_ref(*const_cast(at)).load(std::memory_order_relaxed); + } + + static void storeByte(std::uint8_t* at, std::uint8_t value) { + std::atomic_ref(*at).store(value, std::memory_order_relaxed); + } + + static std::uint64_t loadWide(const std::uint8_t* at) { + return std::atomic_ref(*reinterpret_cast(const_cast(at))).load(std::memory_order_relaxed); + } + std::uint8_t load(std::uintptr_t address) const { auto* value = entry((address - base) / PageBytes, false); - return value == nullptr ? 0 : std::atomic_ref(*value).load(std::memory_order_relaxed); + return value == nullptr ? 0 : loadByte(value); + } + + std::uint8_t common(std::size_t block) const { + const auto* at = entry(block * BlockPages, false); + if (at == nullptr) return 0; + const auto value = loadByte(at); + const std::uint64_t wide = 0x0101010101010101ull * value; + for (std::size_t offset = 0; offset < BlockPages; offset += 8) { + if (loadWide(at + offset) != wide) return 0; + } + return value; + } + + void settle(std::size_t first, std::size_t last) { + std::atomic_thread_fence(std::memory_order_seq_cst); + for (auto block = first / BlockPages; block <= (last - 1) / BlockPages; ++block) { + const auto value = common(block); + auto* summary = blockEntry(block, false); + if (summary == nullptr || loadByte(summary) == value) continue; + storeByte(summary, value); + if (value == 0) continue; + std::atomic_thread_fence(std::memory_order_seq_cst); + if (common(block) != value) storeByte(summary, 0); + } } void store(std::uintptr_t address, std::uint8_t value) { - if (auto* at = entry((address - base) / PageBytes, value != 0 && value != PageInaccessible)) std::atomic_ref(*at).store(value, std::memory_order_relaxed); + store(address, address + PageBytes, value); } 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 (first >= last) return; + const auto begin = (first - base) / PageBytes; + const auto end = (last - 1 - base) / PageBytes + 1; + auto index = begin; + while (index < end) { + const auto next = std::min(end, (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); + for (; index < next; ++index, ++at) storeByte(at, value); } else index = next; } + settle(begin, end); } // End of the run of pages equal to `value` that starts at `address`, at most `limit`. @@ -454,11 +513,23 @@ struct PageSpan { 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) { + if (index % BlockPages == 0) { + const auto block = index / BlockPages; + const auto* summary = blockEntry(block, false); + if (block % 8 == 0 && index + 8 * BlockPages <= stop && loadWide(summary) == wide) { + index += 8 * BlockPages; + continue; + } + if (index + BlockPages <= stop && loadByte(summary) == value) { + index += BlockPages; + continue; + } + } + if (index % 8 == 0 && index + 8 <= stop && loadWide(at) == wide) { index += 8; continue; } - if (std::atomic_ref(*at).load(std::memory_order_relaxed) != value) break; + if (loadByte(at) != value) break; ++index; } return std::min(limit, base + index * PageBytes); @@ -509,7 +580,7 @@ struct PageStates { if (span->size == 0 || bytes == 0 || address >= span->base + span->size || address + bytes <= span->base) continue; const auto last = std::min(address + bytes, span->base + span->size); for (auto at = std::max(address, span->base) & ~static_cast(PageBytes - 1); at < last; at += PageBytes) { - if (span->load(at) == PageInaccessible) span->store(at, 0); + if (span->load(at) == PageInaccessible) span->store(at, at + PageBytes, 0); } } } @@ -703,6 +774,41 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { } return false; } +#ifdef __APPLE__ + { + mach_vm_address_t region = cursor; + mach_vm_size_t regionSize = 0; + vm_region_basic_info_data_64_t info{}; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object = MACH_PORT_NULL; + const auto status = mach_vm_region(mach_task_self(), ®ion, ®ionSize, VM_REGION_BASIC_INFO_64, reinterpret_cast(&info), &count, &object); + if (status == KERN_INVALID_ADDRESS) { + static_cast(emit(PageRun{cursor, end, false, false})); + return true; + } + if (status != KERN_SUCCESS || regionSize == 0) return false; + if (region > cursor) { + const auto gapEnd = std::min(end, region); + if (!emit(PageRun{cursor, gapEnd, false, false})) return true; + cursor = gapEnd; + continue; + } + bool readable = (info.protection & VM_PROT_READ) != 0; + bool writable = readable && (info.protection & VM_PROT_WRITE) != 0; + auto next = std::min(end, region + regionSize); + std::uintptr_t watchedEnd = next; + const int logical = GuestWriteWatch::GuestWriteWatchProtection_nid_postfix(cursor, next, &watchedEnd); + if (logical >= 0) { + readable = readable && (logical & PROT_READ) != 0; + writable = readable && (logical & PROT_WRITE) != 0; + } + if (watchedEnd > cursor && watchedEnd < next) next = watchedEnd; + recordRun(next, readable, writable); + if (!emit(PageRun{cursor, next, readable, writable})) return true; + cursor = next; + continue; + } +#endif std::ifstream maps("/proc/self/maps"); if (!maps.is_open()) return false; std::string line; @@ -752,6 +858,10 @@ bool onOwnLiveStack(std::uintptr_t address, std::size_t bytes) { GetCurrentThreadStackLimits(&lowLimit, &highLimit); low = static_cast(lowLimit); high = static_cast(highLimit); +#elif defined(__APPLE__) + const auto self = pthread_self(); + high = reinterpret_cast(pthread_get_stackaddr_np(self)); + low = high - pthread_get_stacksize_np(self); #else pthread_attr_t attributes; if (pthread_getattr_np(pthread_self(), &attributes) != 0) return; @@ -947,11 +1057,11 @@ struct WriteTracker { static constexpr std::size_t CoarseBlocks = 64; static constexpr std::size_t LeafBlocks = std::size_t{1} << 16; struct Leaf { - std::array blocks{}; - std::array cpuBlocks{}; - std::array coarseBlocks{}; + std::array blocks{}; + std::array cpuBlocks{}; + std::array coarseBlocks{}; #ifdef _WIN32 - std::array hintBlocks{}; + std::array hintBlocks{}; #endif }; std::vector> leaves; @@ -966,8 +1076,8 @@ struct WriteTracker { // stamp still happen under it): a hit must return the current value, including MarkWritten // 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::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, @@ -1050,24 +1160,24 @@ struct WriteTracker { return !GuestArena::GuestArenaHostRegionOverlaps_nid_postfix(static_cast(first), static_cast(stop - first)); } - std::uint32_t newestHint(std::uint64_t first, std::uint64_t stop) const { - std::uint32_t newest = 0; + std::uint64_t newestHint(std::uint64_t first, std::uint64_t stop) const { + std::uint64_t newest = 0; for (auto block = blockOf(first); block <= blockOf(stop - 1); ++block) newest = std::max(newest, hintOf(block)); return newest; } #endif struct DriverPiece { - std::uint32_t generation = 0; + std::uint64_t generation = 0; std::uint32_t begin = 0; std::uint32_t end = 0; }; struct DriverPieces { - std::uint32_t whole = 0; - std::uint32_t dropped = 0; + std::uint64_t whole = 0; + std::uint64_t dropped = 0; std::array pieces{}; - std::uint32_t possibleWhole = 0; - std::uint32_t possibleDropped = 0; + std::uint64_t possibleWhole = 0; + std::uint64_t possibleDropped = 0; std::array possiblePieces{}; bool covers(std::uint32_t begin, std::uint32_t end, std::uint64_t generation) const { @@ -1087,21 +1197,21 @@ struct WriteTracker { std::unordered_map driverPieces; struct CpuPages { - std::array pages{}; + std::array pages{}; }; std::unordered_map cpuPages; std::unordered_map walkPages; - std::map refused; + std::map refused; - void noteCpuStore(std::uint64_t begin, std::uint64_t end, std::uint32_t stampGeneration) { + void noteCpuStore(std::uint64_t begin, std::uint64_t end, std::uint64_t stampGeneration) { notePages(cpuPages, begin, end, stampGeneration); } - void noteWalkStore(std::uint64_t begin, std::uint64_t end, std::uint32_t stampGeneration) { + void noteWalkStore(std::uint64_t begin, std::uint64_t end, std::uint64_t stampGeneration) { notePages(walkPages, begin, end, stampGeneration); } - void notePages(std::unordered_map& pages, std::uint64_t begin, std::uint64_t end, std::uint32_t stampGeneration) { + void notePages(std::unordered_map& pages, std::uint64_t begin, std::uint64_t end, std::uint64_t stampGeneration) { for (auto block = blockOf(begin); block <= blockOf(end - 1); ++block) { const auto blockStart = blockBegin(block); const auto from = std::max(begin, blockStart) - blockStart; @@ -1125,7 +1235,7 @@ struct WriteTracker { #endif } - void stampWatchedPart(std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration, bool complete) { + void stampWatchedPart(std::uint64_t address, std::uint64_t end, std::uint64_t stampGeneration, bool complete) { #ifdef _WIN32 address = std::max(address, base); end = std::min(end, base + size); @@ -1143,7 +1253,7 @@ struct WriteTracker { } } - void noteDriverStore(std::uint64_t block, std::uint64_t address, std::uint64_t end, std::uint32_t stampGeneration, bool complete) { + void noteDriverStore(std::uint64_t block, std::uint64_t address, std::uint64_t end, std::uint64_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); @@ -1191,19 +1301,19 @@ struct WriteTracker { leafFor(block).hintBlocks[block % LeafBlocks] = generation; } - std::uint32_t hintOf(std::uint64_t block) const { + std::uint64_t hintOf(std::uint64_t block) const { const auto& leaf = leaves[block / LeafBlocks]; return leaf != nullptr ? leaf->hintBlocks[block % LeafBlocks] : 0; } #endif - std::uint32_t stampOf(std::uint64_t block) const { + std::uint64_t stampOf(std::uint64_t block) const { const auto& leaf = leaves[block / LeafBlocks]; return leaf != nullptr ? leaf->blocks[block % LeafBlocks] : 0; } static constexpr std::size_t UnstampedSlots = 1u << 16; - std::vector unstampedStores; + std::vector unstampedStores; void noteUnstampedStore(std::uint64_t address, std::uint64_t end) { if (unstampedStores.empty()) unstampedStores.assign(UnstampedSlots, 0); @@ -1216,8 +1326,8 @@ struct WriteTracker { for (auto block = first; block <= last; ++block) unstampedStores[block % UnstampedSlots] = generation; } - std::uint32_t newestStamp(std::uint64_t address, std::uint64_t end) const { - std::uint32_t newest = 0; + std::uint64_t newestStamp(std::uint64_t address, std::uint64_t end) const { + std::uint64_t newest = 0; if (!unstampedStores.empty()) { const auto first = address / WriteBlockBytes; const auto last = (end - 1) / WriteBlockBytes; @@ -1236,14 +1346,14 @@ struct WriteTracker { return newest; } - std::uint32_t coarseStampOf(std::uint64_t block) const { + std::uint64_t coarseStampOf(std::uint64_t block) const { const auto& leaf = leaves[block / LeafBlocks]; return leaf != nullptr ? leaf->coarseBlocks[(block % LeafBlocks) / CoarseBlocks] : 0; } bool unchanged(std::uint64_t first, std::uint64_t last, std::uint64_t since) const { if (first == last) return stampOf(first) <= since; - const auto newer = [since](std::uint32_t stamp) { return stamp > since; }; + const auto newer = [since](std::uint64_t stamp) { return stamp > since; }; while (first <= last) { const auto& leaf = leaves[first / LeafBlocks]; if (leaf == nullptr) { @@ -1260,7 +1370,7 @@ struct WriteTracker { return true; } - std::uint32_t cpuStampOf(std::uint64_t block) const { + std::uint64_t cpuStampOf(std::uint64_t block) const { const auto& leaf = leaves[block / LeafBlocks]; return leaf != nullptr ? leaf->cpuBlocks[block % LeafBlocks] : 0; } @@ -1312,7 +1422,7 @@ struct WriteTracker { return unchanged(blockOf(address), blockOf(address + bytes - 1), since) || !storedOver(address, address + bytes, since, true); } - void stamp(std::uint64_t block, std::uint32_t stampGeneration, StampKind kind) { + void stamp(std::uint64_t block, std::uint64_t stampGeneration, StampKind kind) { const bool cpu = kind == StampKind::Cpu || kind == StampKind::ImportWindow; const bool stored = kind != StampKind::ImportWindow; auto& leaf = leafFor(block); @@ -1365,6 +1475,23 @@ public: lock.lock(); } + class Suspension { + public: + explicit Suspension(TrackerLock& owner) : owner(owner) { + owner.Suspend(); + } + + ~Suspension() { + owner.Resume(); + } + + Suspension(const Suspension&) = delete; + Suspension& operator=(const Suspension&) = delete; + + private: + TrackerLock& owner; + }; + private: void publish() { const auto current = tracker.generation.load(std::memory_order_relaxed); @@ -1389,7 +1516,7 @@ void watchPrivateMapping(std::uintptr_t address, std::size_t bytes, std::uint64_ tracker.noteCpuStore(address, address + bytes, tracker.generation); for (auto block = tracker.blockOf(address); block <= tracker.blockOf(address + bytes - 1); ++block) { if (const auto& leaf = tracker.leaves[block / WriteTracker::LeafBlocks]; leaf != nullptr) leaf->hintBlocks[block % WriteTracker::LeafBlocks] = 0; - if (const auto found = tracker.driverPieces.find(block); found != tracker.driverPieces.end()) found->second.dropped = std::max(found->second.dropped, tracker.generation); + if (const auto found = tracker.driverPieces.find(block); found != tracker.driverPieces.end()) found->second.dropped = std::max(found->second.dropped, tracker.generation); tracker.stamp(block, tracker.generation, StampKind::Cpu); } unwatchSerial.fetch_add(1, std::memory_order_release); @@ -1466,7 +1593,7 @@ struct StampRuns { std::vector> windowRuns; }; -void noteWrittenRun(StampRuns& runs, std::uintptr_t begin, std::uintptr_t end, std::uint32_t generation) { +void noteWrittenRun(StampRuns& runs, std::uintptr_t begin, std::uintptr_t end, std::uint64_t generation) { if (runs.kind != StampKind::Driver) runs.tracker.noteCpuStore(begin, end, generation); else runs.tracker.noteWalkStore(begin, end, generation); } @@ -1766,6 +1893,10 @@ namespace { std::atomic unwatchHook{nullptr}; +thread_local bool unwatchWritesProven = true; +std::atomic importWalkFailureForTest{0}; +std::atomic importWalkFailureSpanForTest{0}; + void beforeUnwatch(std::uint64_t address, std::size_t bytes) { const auto hook = unwatchHook.load(std::memory_order_acquire); if (hook == nullptr) return; @@ -1784,6 +1915,16 @@ void beforeUnwatch(std::uint64_t address, std::size_t bytes) { #endif } +void beforeUnwatchUnproven(std::uint64_t address, std::size_t bytes) { + struct Unproven { + Unproven() { unwatchWritesProven = false; } + ~Unproven() { unwatchWritesProven = true; } + Unproven(const Unproven&) = delete; + Unproven& operator=(const Unproven&) = delete; + } unproven; + beforeUnwatch(address, bytes); +} + void unwatchLocked(WriteTracker& tracker, std::uint64_t address, std::size_t bytes) { if (!tracker.watched) return; #ifdef _WIN32 @@ -1800,6 +1941,15 @@ void SetUnwatchHook(void (*hook)(std::uint64_t, std::size_t)) { unwatchHook.store(hook, std::memory_order_release); } +bool UnwatchWritesProven() { + return unwatchWritesProven; +} + +void SetImportWalkFailureForTest(int walk, std::uint64_t span) { + importWalkFailureSpanForTest.store(span, std::memory_order_release); + importWalkFailureForTest.store(walk, std::memory_order_release); +} + #ifdef _WIN32 void SetFreshCollectHook(FreshCollectHook hook) { freshCollectHook.store(hook, std::memory_order_release); @@ -1892,29 +2042,39 @@ bool ImportWatched(std::uint64_t address, std::size_t bytes, const std::function 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); HashPages(first, spans, hashes); - const auto saveBytes = [&](std::uint64_t begin, std::uint64_t end) { - lock.Suspend(); - beforeUnwatch(begin, static_cast(end - begin)); - lock.Resume(); + const auto failWalk = importWalkFailureForTest.load(std::memory_order_acquire); + const auto failSpan = importWalkFailureSpanForTest.load(std::memory_order_acquire); + const auto failsWalk = [&](int walk, std::uint64_t begin, std::uint64_t end) { + return failWalk == walk && (failSpan == 0 || (begin <= failSpan && failSpan < end)); + }; + const auto leave = [&](const std::vector>& ranges) { + if (ranges.empty()) return; + const TrackerLock::Suspension suspended{lock}; + for (const auto& [begin, end] : ranges) beforeUnwatchUnproven(begin, static_cast(end - begin)); }; std::vector> leaving; std::erase_if(spans, [&](const auto& span) { - if (walkWrites(tracker, span.first, span.second, StampKind::Cpu, true)) return false; + if (!failsWalk(1, span.first, span.second) && 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); + leave(leaving); if (!runImport()) return false; const WindowCopy window{first, hashes}; - auto unwatchedFrom = first; - for (const auto& [begin, end] : spans) { - if (!walkWrites(tracker, begin, end, StampKind::Cpu, true, &window)) { - saveBytes(begin, end); - continue; + leaving.clear(); + std::erase_if(spans, [&](const auto& span) { + if (failsWalk(2, span.first, span.second) || !walkWrites(tracker, span.first, span.second, StampKind::Cpu, true, &window)) { + leaving.push_back(span); + return true; } #ifdef _WIN32 - GuestArena::GuestArenaCompareResidentWhenShared_nid_no_patch(static_cast(begin), static_cast(end - begin)); + GuestArena::GuestArenaCompareResidentWhenShared_nid_no_patch(static_cast(span.first), static_cast(span.second - span.first)); #endif + return false; + }); + leave(leaving); + auto unwatchedFrom = first; + for (const auto& [begin, end] : spans) { if (unwatchedFrom < begin) unwatchLocked(tracker, unwatchedFrom, static_cast(begin - unwatchedFrom)); unwatchedFrom = end; } @@ -2066,9 +2226,10 @@ static std::uint64_t markWritten(std::uint64_t address, std::size_t bytes, bool const bool collected = GuestWriteWatch::GuestWriteWatchCollectFresh_nid_postfix(static_cast(blockFirst), static_cast(blockStop - blockFirst), &stampWrittenRun, &runs); #endif if (!collected) { - lock.Suspend(); - beforeUnwatch(blockFirst, static_cast(blockStop - blockFirst)); - lock.Resume(); + { + const TrackerLock::Suspension suspended{lock}; + beforeUnwatchUnproven(blockFirst, static_cast(blockStop - blockFirst)); + } unwatchLocked(tracker, blockFirst, static_cast(blockStop - blockFirst)); tracker.noteUnstampedStore(address, address + bytes); return 0; @@ -2138,6 +2299,12 @@ std::uint64_t TrackerGeneration() { return tracker.generation; } +void AdvanceTrackerGenerationForTest(std::uint64_t generation) { + auto& tracker = Tracker(); + const auto lock = lockTracker(tracker); + if (generation > tracker.generation.load(std::memory_order_relaxed)) tracker.generation.store(generation, std::memory_order_relaxed); +} + std::uint64_t NewestStamp(std::uint64_t address, std::size_t bytes) { auto& tracker = Tracker(); const auto lock = lockTracker(tracker); @@ -2614,8 +2781,13 @@ std::uint32_t GpuLockThreadTag() { } GpuMutexType& GpuMutex() { +#ifdef __APPLE__ + static auto* mutex = new GpuMutexType; + return *mutex; +#else static GpuMutexType mutex; return mutex; +#endif } void TagGpuLockThread(std::uint32_t queue) { @@ -2858,7 +3030,7 @@ void WriteChanged(std::uint64_t address, std::span current, std WriteChanged(address, current, original, ChangedByteStoreLimit()); } -void WriteChanged(std::uint64_t address, std::span current, std::span original, std::uint32_t byteStoreLimit) { +static void writeChanged(std::uint64_t address, std::span current, std::span original, std::uint32_t byteStoreLimit, bool keepGuest) { require(current.size() == original.size(), "write-back snapshot sizes differ"); BreakProofs(); if (current.empty()) return; @@ -2878,7 +3050,9 @@ void WriteChanged(std::uint64_t address, std::span current, std std::size_t at = 0; for (; at + 16 <= size; at += 16) { const auto bytes = _mm_loadu_si128(reinterpret_cast(current.data() + at)); - const auto same = _mm_cmpeq_epi8(bytes, _mm_loadu_si128(reinterpret_cast(original.data() + at))); + const auto uploaded = _mm_loadu_si128(reinterpret_cast(original.data() + at)); + auto same = _mm_cmpeq_epi8(bytes, uploaded); + if (keepGuest) same = _mm_or_si128(same, _mm_xor_si128(_mm_cmpeq_epi8(_mm_loadu_si128(reinterpret_cast(destination + at)), uploaded), _mm_set1_epi8(-1))); const auto equal = static_cast(_mm_movemask_epi8(same)); if (equal == 0xffffu) continue; const auto changed = ~equal & 0xffffu; @@ -2897,7 +3071,7 @@ void WriteChanged(std::uint64_t address, std::span current, std } if (masked) _mm_sfence(); for (; at < size; ++at) { - if (current[at] == original[at]) continue; + if (current[at] == original[at] || (keepGuest && destination[at] != original[at])) continue; destination[at] = current[at]; firstChanged = std::min(firstChanged, at); lastChanged = std::max(lastChanged, at + 1); @@ -2906,6 +3080,14 @@ void WriteChanged(std::uint64_t address, std::span current, std }); } +void WriteChanged(std::uint64_t address, std::span current, std::span original, std::uint32_t byteStoreLimit) { + writeChanged(address, current, original, byteStoreLimit, false); +} + +void WriteChangedKeepingGuest(std::uint64_t address, std::span current, std::span original) { + writeChanged(address, current, original, ChangedByteStoreLimit(), true); +} + void Write(std::uint64_t address, std::span source, std::size_t alignment) { if (source.empty()) return; // A store, as in WriteChanged. diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/HostReportSources.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/HostReportSources.cpp index a3a4257fd8..e9bdb9b331 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/HostReportSources.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/HostReportSources.cpp @@ -14,6 +14,7 @@ #include #include #include +extern char** environ; #endif namespace AgcDriver { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/KeyboardOverlay.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/KeyboardOverlay.cpp index 9bd1dd45b5..077b0ea7dd 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/KeyboardOverlay.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/KeyboardOverlay.cpp @@ -372,7 +372,20 @@ OverlayLayout LayOutKeyboard(const ImeKeyboardView& view, VkExtent2D extent) { const auto inner = gridWidth(view); rect(KeyboardMetrics::Padding, KeyboardMetrics::Padding, inner, KeyboardMetrics::FieldHeight, OverlayColor::Field); std::size_t caret = 0; - const auto characters = decode(view.text, view.caret, &caret); + auto characters = decode(view.text, view.caret, &caret); + std::vector marks(characters.size(), 0); + if (!view.preedit.empty()) { + Graphics::Require(view.preeditCursor <= view.preedit.size() && view.preeditSelection <= view.preedit.size() - view.preeditCursor, "the system keyboard composition cursor is past its text"); + std::size_t cursor = 0; + std::size_t selectionEnd = 0; + const auto composed = decode(view.preedit, view.preeditCursor, &cursor); + decode(view.preedit, view.preeditCursor + view.preeditSelection, &selectionEnd); + std::vector composedMarks(composed.size(), KeyboardCompositionMark); + std::fill(composedMarks.begin() + static_cast(cursor), composedMarks.begin() + static_cast(selectionEnd), KeyboardSelectionMark); + characters.insert(characters.begin() + static_cast(caret), composed.begin(), composed.end()); + marks.insert(marks.begin() + static_cast(caret), composedMarks.begin(), composedMarks.end()); + caret += cursor; + } const auto textLeft = KeyboardMetrics::Padding + KeyboardMetrics::FieldInset; const auto textTop = KeyboardMetrics::Padding + KeyboardMetrics::FieldInset; const auto visible = static_cast((inner - 2 * KeyboardMetrics::FieldInset + 1) / KeyboardMetrics::Advance); @@ -381,6 +394,8 @@ OverlayLayout LayOutKeyboard(const ImeKeyboardView& view, VkExtent2D extent) { for (auto index = first; index < last; ++index) { const auto column = static_cast(index - first); glyph(layout, KeyboardGlyph(characters[index]), left + (textLeft + column * KeyboardMetrics::Advance) * scale, top + textTop * scale, scale, OverlayColor::Text); + if (marks[index] == KeyboardCompositionMark) rect(textLeft + column * KeyboardMetrics::Advance, textTop + KeyboardMetrics::GlyphHeight, KeyboardMetrics::Advance, 1, OverlayColor::Accent); + else if (marks[index] == KeyboardSelectionMark) rect(textLeft + column * KeyboardMetrics::Advance, textTop + KeyboardMetrics::GlyphHeight, KeyboardMetrics::Advance, 2, OverlayColor::Text); } rect(textLeft + static_cast(caret - first) * KeyboardMetrics::Advance - 1, KeyboardMetrics::Padding + 1, 1, KeyboardMetrics::FieldHeight - 2, OverlayColor::Accent); for (const auto& key : view.keys) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index a4e136fdcf..c4fc580f50 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -16,10 +17,55 @@ namespace AgcDriver::Pm4 { +namespace { + +std::atomic importableGdsRequested{false}; +std::atomic gdsShared{false}; +std::atomic gdsImportable{false}; + +} + +void RequestImportableGds(bool requested) { + importableGdsRequested.store(requested, std::memory_order_release); +} + +bool ImportableGdsRequested() { + return importableGdsRequested.load(std::memory_order_acquire); +} + +bool GdsShared() { + static_cast(GdsAddress()); + return gdsShared.load(std::memory_order_acquire); +} + +bool GdsImportable() { + static_cast(GdsAddress()); + return gdsImportable.load(std::memory_order_acquire); +} + +void RejectGdsImport() { + gdsImportable.store(false, std::memory_order_release); +} + std::uint64_t GdsAddress() { static const std::uint64_t address = [] { constexpr int ReadWrite = 0x3; constexpr int PrivateAnonymous = 0x1002; + constexpr int Shared = 0x1; + constexpr int OpenReadWrite = 0x2; + const char* const anonymousSharedMemory = reinterpret_cast(1); + if (ImportableGdsRequested() && std::getenv("APS5_NO_SHARED_GDS") == nullptr) { + if (const int descriptor = shm_open_nid_postfix(anonymousSharedMemory, OpenReadWrite, 0600); descriptor >= 0) { + void* shared = ftruncate_nid_postfix(descriptor, GdsBytes) == 0 ? mmap_nid_postfix(nullptr, GdsBytes, ReadWrite, Shared, descriptor, 0) : nullptr; + close_nid_postfix(descriptor); + if (shared != nullptr && shared != reinterpret_cast(static_cast(-1))) { + std::memset(shared, 0, GdsBytes); + gdsShared.store(true, std::memory_order_release); + gdsImportable.store(true, std::memory_order_release); + return reinterpret_cast(shared); + } + } + } void* mapped = mmap_nid_postfix(nullptr, GdsBytes, ReadWrite, PrivateAnonymous, -1, 0); if (mapped == nullptr || mapped == reinterpret_cast(static_cast(-1))) throw std::runtime_error("cannot allocate the global data share"); return reinterpret_cast(mapped); @@ -289,7 +335,7 @@ void Validate(std::span packet, std::uint32_t queue) { // Header bit 1 selects the compute shader type and bit 2 (register writes) resets the filter CAM; // neither changes what the packet writes. const auto flags = header & 0xffu; - const bool registerWrite = opcode == 0x69 || opcode == 0x76 || opcode == 0x79 || opcode == 0x7a; + const bool registerWrite = opcode == 0x69 || opcode == 0x76 || opcode == 0x79 || opcode == 0x7a || (opcode == 0x37 && ((packet[1] >> 8u) & 0xfu) == 0); auto allowedFlags = opcode == 0x11 || opcode == 0x15 || opcode == 0x16 ? 2u : registerWrite ? 6u : opcode == 0x3c || opcode == 0x93 ? 2u : 0u; if (opcode != 0x20 && opcode != 0x22) allowedFlags |= 1u; if ((flags & ~allowedFlags) != 0) throw std::runtime_error("PM4 header flags 0x" + ToHex(flags) + " are not implemented"); @@ -650,10 +696,10 @@ std::optional ResolveStore(std::span packet, co return StoreWrite{destination, {}, copyDataSource(packet, queue, bytes)}; } case 0x50: { - if (packet.size() < 7 || dmaDestination(packet) == DmaSelectGds) return std::nullopt; + if (packet.size() < 7) return std::nullopt; if (dmaDestination(packet) == DmaDestinationNowhere) return StoreWrite{address(packet[2], packet[3]), {}, {}}; const std::size_t bytes = DmaBytes(packet); - const auto destination = address(packet[4], packet[5]); + const auto destination = dmaDestination(packet) == DmaSelectGds ? GdsAddress() + packet[4] : address(packet[4], packet[5]); if (!fits(destination, bytes)) return std::nullopt; return StoreWrite{destination, {}, dmaSourceBytes(packet)}; } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderDeviceProfile.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderDeviceProfile.cpp index f9b21ce96a..8170e78591 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderDeviceProfile.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderDeviceProfile.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/ShaderDeviceProfile.hpp" #include "BdaAbi.hpp" +#include "CacheKey.hpp" #include #include @@ -75,6 +76,7 @@ ShaderDeviceProfile::ShaderDeviceProfile(const ShaderRecompiler::SpirvTarget& ta Require(!target.mesh || meshEnabled, "shader device profile exposes disabled mesh shaders"); Require(!target.nonConstantImageOffsets || (maintenance8 != nullptr && maintenance8->maintenance8 == VK_TRUE), "shader device profile exposes disabled maintenance8"); Require(!target.tessellation || core.tessellationShader == VK_TRUE, "shader device profile exposes disabled tessellation"); + Require(!target.shaderImageMinLod || core.shaderResourceMinLod == VK_TRUE, "shader device profile exposes a disabled shaderResourceMinLod"); for (const auto extension : target.supportedExtensions) extensions.emplace_back(extension); std::ranges::sort(extensions); extensions.erase(std::unique(extensions.begin(), extensions.end()), extensions.end()); @@ -83,6 +85,7 @@ ShaderDeviceProfile::ShaderDeviceProfile(const ShaderRecompiler::SpirvTarget& ta Require(std::ranges::find(extensionViews, "SPV_KHR_terminate_invocation") == extensionViews.end() || (terminate != nullptr && terminate->shaderTerminateInvocation == VK_TRUE), "shader device profile advertises a disabled feature: shaderTerminateInvocation"); this->target.supportedCapabilities = capabilities; this->target.supportedExtensions = extensionViews; + ShaderRecompiler::RecompileCacheKey::SealFeatureDigest(this->target); } } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp index 91380e6132..b1b40100ef 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp @@ -2,10 +2,15 @@ #include "prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" #include "prx/libc/include/HostThreadLocal.hpp" #include "Optimization/RequestMemoryView.hpp" #include "Optimization/ResourceMaterializer.hpp" #include "Optimization/ResourceProgram.hpp" +#include "Optimization/SrtWalker.hpp" #include "ThreadOwned.hpp" #include #include @@ -35,6 +40,8 @@ struct CaptureProfile { // were read raw on the driver's evidence, or were read both ways (verify) and differed. std::atomic pagesWordwise{0}; std::atomic wordWaits{0}; + std::atomic branchReadsRefused{0}; + std::atomic branchCapturesRedone{0}; std::atomic wordWaitNanoseconds{0}; std::atomic wordsRaw{0}; std::atomic wordsVerified{0}; @@ -69,6 +76,55 @@ struct CaptureProfile { constexpr std::uint64_t WordWaitNanoseconds = 50000; +std::uint64_t imageViewGeneration(void*) { + return GuestAllocations::GuestAllocationsGeneration_nid_postfix(); +} + +bool normalizeImageView(void*, std::uint32_t maxArrayLayers, std::array& descriptor, ShaderRecompiler::SrtImageViewIdentity& identity) { + identity.generation = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + if (((descriptor[3] >> 28u) & 0xfu) != 12u) return false; + const auto resource = Graphics::DecodeTextureResource(descriptor); + identity.address = resource.baseAddress; + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast(resource.baseAddress), false); + if (range != nullptr) { + identity.hasRange = true; + identity.readable = range->readable; + identity.writable = range->writable; + identity.gpu = range->gpu; + identity.rangeAddress = range->address; + identity.rangeBytes = range->bytes; + identity.allocationAddress = range->allocationAddress; + identity.allocationBytes = range->allocationBytes; + identity.backingSerial = range->backingSerial; + } + if (maxArrayLayers == 0u || resource.dimension != Graphics::TextureDimension::k1DArray || range == nullptr) return false; + const auto rawCount = resource.depthOrLastArray >= resource.baseArray ? resource.depthOrLastArray - resource.baseArray + 1u : 0u; + const auto geometry = Graphics::DescribeSurface(resource); + if (geometry.layerBytes == 0u || geometry.thick || geometry.layers != resource.depthOrLastArray + 1u) return false; + if (const auto xorMode = Graphics::XorSwizzleMode(resource.tileMode); xorMode != 0u) { + const auto* equation = Graphics::FindTextureSwizzleEquation(xorMode, Graphics::BytesPerElement(resource.format)); + if (equation == nullptr || std::any_of(equation->bits.begin(), equation->bits.end(), [](std::uint32_t bit) { return (bit & 0xff000000u) != 0u; })) return false; + } + if (!range->readable || !range->gpu || range->bytes == 0u || range->address > std::numeric_limits::max() - range->bytes || range->allocationAddress > std::numeric_limits::max() - range->allocationBytes) return false; + const auto rangeEnd = range->address + range->bytes; + const auto allocationEnd = range->allocationAddress + range->allocationBytes; + if (resource.baseAddress < range->address || resource.baseAddress > rangeEnd || range->address < range->allocationAddress || rangeEnd > allocationEnd) return false; + const auto offset = resource.baseAddress - range->address; + if (offset % geometry.layerBytes != 0u) return false; + const auto coveredBytes = rangeEnd - resource.baseAddress; + if (coveredBytes % geometry.layerBytes != 0u) return false; + const auto completeSlices = coveredBytes / geometry.layerBytes; + if (completeSlices <= resource.baseArray) return false; + if (rawCount <= maxArrayLayers) return false; + const auto available = completeSlices - resource.baseArray; + const auto effectiveCount = std::min(rawCount, available); + if (effectiveCount == 0u || effectiveCount == rawCount) return false; + const auto lastArray = static_cast(resource.baseArray) + effectiveCount - 1u; + if (lastArray > 0x1fffu) return false; + descriptor[4] = (descriptor[4] & ~0x1fffu) | static_cast(lastArray); + return true; +} + bool WordwisePages() { static const bool wordwise = std::getenv("APS5_NO_WORDWISE_CAPTURE") == nullptr; return wordwise; @@ -94,6 +150,11 @@ CaptureProfile& CaptureTotals() { return profile; } +bool BranchesSkipPendingWords() { + static const bool skip = std::getenv("APS5_WAIT_PENDING_BRANCHES") == nullptr; + return skip; +} + std::atomic waitedMsProvider{nullptr}; double WaitedMs() { @@ -372,6 +433,10 @@ bool ShaderMemory::read(void* context, std::uint64_t address, std::uint32_t* val waited = page.wordwise && (self.hookWaits != nullptr ? self.hookWaits() != waitsBefore : true); word = verifiedWord; } + } else if (page.wordwise && policy == PendingWrite::Sync && !forcedRaw && !self.waitBranches && BranchesSkipPendingWords() && ShaderRecompiler::SrtEvaluatingBranch()) { + ++self.branchReadsRefused; + if (CaptureProfiled()) ++CaptureTotals().branchReadsRefused; + return false; } else { // On a word-wise page the bytes before the hook's wait are kept for the observer. const bool observed = page.wordwise && policy == PendingWrite::Sync && self.observe != nullptr && GuestMemory::Accessible(reinterpret_cast(address), sizeof(word)); @@ -722,11 +787,29 @@ std::vector> ShaderMemory } std::shared_ptr ShaderMemory::Capture(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle) { - return capture(request, handle, nullptr); + return captureWaitingOnFailure(request, handle, nullptr); } std::shared_ptr ShaderMemory::Capture(const ShaderRecompiler::PreparedShaderInvocation& invocation) { - return capture(invocation.Request(), nullptr, &invocation); + return captureWaitingOnFailure(invocation.Request(), nullptr, &invocation); +} + +std::shared_ptr ShaderMemory::captureWaitingOnFailure(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle, const ShaderRecompiler::PreparedShaderInvocation* invocation) { + branchReadsRefused = 0; + try { + return capture(request, handle, invocation); + } catch (const std::exception&) { + if (branchReadsRefused == 0u) throw; + } + if (CaptureProfiled()) ++CaptureTotals().branchCapturesRedone; + struct WaitBranches { + bool& wait; + explicit WaitBranches(bool& flag) : wait(flag) { wait = true; } + ~WaitBranches() { wait = false; } + WaitBranches(const WaitBranches&) = delete; + WaitBranches& operator=(const WaitBranches&) = delete; + } waiting(waitBranches); + return capture(request, handle, invocation); } std::shared_ptr ShaderMemory::capture(const ShaderRecompiler::RecompileRequest& request, const ShaderRecompiler::SourceHandle* handle, const ShaderRecompiler::PreparedShaderInvocation* invocation) { @@ -750,6 +833,8 @@ std::shared_ptr ShaderMemory::capture(c runtime.unchangedSince = &unchangedSince; runtime.supplyWords = &supplyWords; runtime.descriptorScope = &descriptorScope; + runtime.normalizeImageView = &normalizeImageView; + runtime.imageViewGeneration = &imageViewGeneration; auto capture = invocation != nullptr ? invocation->Capture(runtime) : handle != nullptr ? ShaderRecompiler::CaptureResources(request, runtime, *handle) : ShaderRecompiler::CaptureResources(request, runtime); if (profile) { totals.captureNanoseconds += NanosecondsSince(started); @@ -772,7 +857,7 @@ std::shared_ptr ShaderMemory::capture(c const auto materializeNs = ShaderRecompiler::ResourceMaterializer::MaterializeNanoseconds(); const auto evaluateNs = ShaderRecompiler::ResourceMaterializer::EvaluateNanoseconds(); const auto walkNs = captureNs > resolveNs + pageNs + wordNs + specializationNs ? captureNs - resolveNs - pageNs - wordNs - specializationNs : 0; - AgcDriver::ProfilePrint_nid_no_patch( "[capture] %llu captures: %llu word reads, %llu pages fetched (%llu read word by word over pending GPU writes: %llu words waited %.2f s, %llu read raw on evidence, %llu verified with %llu mismatches, %llu served from known values, %llu hook reads not observed: no GPU wait), capture (plan lookup + materialize) %.1f s, %llu KiB registered code this capture; sub-phases (s, us per capture): source resolve %.2f/%.1f, walk %.2f/%.1f, specialization (every materialize) %.2f/%.1f, page reads %.2f/%.1f, word reads %.2f/%.1f, hook GPU waits inside %.2f/%.1f, materialize %.2f/%.1f (SRT evaluate %.2f/%.1f); source handle memo %llu hits, %llu resolves\n", static_cast(totals.captures.load()), static_cast(totals.reads.load()), static_cast(totals.pages.load()), static_cast(totals.pagesWordwise.load()), static_cast(totals.wordWaits.load()), totals.wordWaitNanoseconds.load() / 1e9, static_cast(totals.wordsRaw.load()), static_cast(totals.wordsVerified.load()), static_cast(totals.wordMismatches.load()), static_cast(totals.wordsKnown.load()), static_cast(totals.observationsSkipped.load()), captureNs / 1e9, static_cast(initialBytes / 1024), resolveNs / 1e9, resolveNs / captures / 1e3, walkNs / 1e9, walkNs / captures / 1e3, specializationNs / 1e9, specializationNs / captures / 1e3, pageNs / 1e9, pageNs / captures / 1e3, wordNs / 1e9, wordNs / captures / 1e3, hookNs / 1e9, hookNs / captures / 1e3, materializeNs / 1e9, materializeNs / captures / 1e3, evaluateNs / 1e9, evaluateNs / captures / 1e3, static_cast(totals.handleHits.load()), static_cast(totals.handleMisses.load())); + AgcDriver::ProfilePrint_nid_no_patch( "[capture] %llu captures: %llu word reads, %llu pages fetched (%llu read word by word over pending GPU writes: %llu words waited %.2f s, %llu read raw on evidence, %llu verified with %llu mismatches, %llu served from known values, %llu hook reads not observed: no GPU wait, %llu branch condition reads over pending GPU writes left the branch unpruned instead of waiting, %llu captures redone waiting after one failed), capture (plan lookup + materialize) %.1f s, %llu KiB registered code this capture; sub-phases (s, us per capture): source resolve %.2f/%.1f, walk %.2f/%.1f, specialization (every materialize) %.2f/%.1f, page reads %.2f/%.1f, word reads %.2f/%.1f, hook GPU waits inside %.2f/%.1f, materialize %.2f/%.1f (SRT evaluate %.2f/%.1f); source handle memo %llu hits, %llu resolves\n", static_cast(totals.captures.load()), static_cast(totals.reads.load()), static_cast(totals.pages.load()), static_cast(totals.pagesWordwise.load()), static_cast(totals.wordWaits.load()), totals.wordWaitNanoseconds.load() / 1e9, static_cast(totals.wordsRaw.load()), static_cast(totals.wordsVerified.load()), static_cast(totals.wordMismatches.load()), static_cast(totals.wordsKnown.load()), static_cast(totals.observationsSkipped.load()), static_cast(totals.branchReadsRefused.load()), static_cast(totals.branchCapturesRedone.load()), captureNs / 1e9, static_cast(initialBytes / 1024), resolveNs / 1e9, resolveNs / captures / 1e3, walkNs / 1e9, walkNs / captures / 1e3, specializationNs / 1e9, specializationNs / captures / 1e3, pageNs / 1e9, pageNs / captures / 1e3, wordNs / 1e9, wordNs / captures / 1e3, hookNs / 1e9, hookNs / captures / 1e3, materializeNs / 1e9, materializeNs / captures / 1e3, evaluateNs / 1e9, evaluateNs / captures / 1e3, static_cast(totals.handleHits.load()), static_cast(totals.handleMisses.load())); } } return capture; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp index 606d6e0554..dcd319d36d 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/SubmissionCapture.cpp @@ -176,7 +176,7 @@ std::string_view SubmissionCapture::KindName(SubmissionKind kind) { std::string_view SubmissionCapture::QueueType(std::uint32_t queue) { if (queue == 0) return "graphics"; if (queue >= 0x20 && queue < DriverDetail::SubmissionQueueEnd) return "compute"; - char what[64]; + char what[96]; std::snprintf(what, sizeof(what), "queue 0x%x is neither the graphics queue nor a compute queue", queue); throw std::runtime_error(std::string("submission capture: ") + what); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 384bd0c139..f7a940b03c 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -19,6 +19,8 @@ #include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" #include "prx/libSceAgcDriver/Execution/include/HostReportSources.hpp" #include "prx/libSceAgcDriver/Execution/include/HostReportVulkan.hpp" +#include "prx/libSceAgcDriver/Execution/include/BdaAtomics.hpp" +#include "prx/libSceAgcDriver/Execution/include/RasterOrderGroup.hpp" #include "prx/libSceAgcDriver/Execution/include/DeviceFault.hpp" #include "prx/libSceAgcDriver/Execution/include/FragmentBarycentric.hpp" #include "prx/libSceAgcDriver/Execution/include/Float32FlushControl.hpp" @@ -140,6 +142,8 @@ DeviceQueuePlan PlanDeviceQueues(std::span famili namespace { +std::uint64_t fillCompletionSyncs = 0; + void check(VkResult result, const char* operation) { if (result == VK_ERROR_DEVICE_LOST) Graphics::ReportDeviceLost(); if (result != VK_SUCCESS) { @@ -364,6 +368,51 @@ struct WorkgroupMemory { } }; +struct ScratchPool { + VkDevice device = VK_NULL_HANDLE; + PFN_vkDestroyDescriptorPool destroyPool = nullptr; + std::unique_ptr buffer; + VkDescriptorPool pool = VK_NULL_HANDLE; + VkDescriptorSet set = VK_NULL_HANDLE; + std::uint64_t slots = 0; + std::uint64_t headerBytes = 0; + std::uint64_t bytes = 0; + std::uint64_t lastUse = 0; + ~ScratchPool() { + buffer.reset(); + if (pool != VK_NULL_HANDLE) destroyPool(device, pool, nullptr); + } +}; + +void ZeroScratchPoolHeader(const Graphics::Context& context, const ScratchPool& pool) { + Graphics::CommandBatch batch(context); + context.Resolved(&Graphics::DeviceFunctions::cmdFillBuffer, "vkCmdFillBuffer")(batch.Handle(), pool.buffer->Handle(), 0, pool.headerBytes, 0u); + Graphics::RecordMemoryBarrier(context, batch.Handle(), 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); + batch.SubmitAndWait(); +} + +std::uint64_t MebibytesFromEnvironment(const char* name, std::uint64_t fallback) { + std::uint64_t mebibytes = fallback; + if (const char* text = std::getenv(name)) { + char* end = nullptr; + const auto parsed = std::strtoull(text, &end, 10); + if (end != text && *end == 0 && parsed != 0) mebibytes = parsed; + } + return mebibytes << 20u; +} + +std::uint64_t ScratchPoolCapBytes() { + static const std::uint64_t bytes = MebibytesFromEnvironment("APS5_SCRATCH_POOL_MIB", 64); + return bytes; +} + +constexpr std::uint64_t ScratchPoolBufferLimit = std::uint64_t{1} << 31u; + +std::uint64_t ScratchPoolTotalBytes() { + static const std::uint64_t bytes = MebibytesFromEnvironment("APS5_SCRATCH_POOL_TOTAL_MIB", 256); + return bytes; +} + struct VulkanDevice::State { bool compileOnly = false; void* library = nullptr; @@ -420,6 +469,7 @@ struct VulkanDevice::State { bool shaderTerminateInvocation = false; bool fusedExactArithmetic = false; bool fragmentBarycentricBuiltins = false; + bool hostInterpolationFirst = false; bool geometryShader = false; bool largePoints = false; bool sampleRateShading = false; @@ -460,6 +510,8 @@ struct VulkanDevice::State { bool graphicsPipelineLibrary = false; bool pipelineLinkTimeOptimization = true; bool imageViewMinLod = false; + bool shaderResourceMinLod = false; + bool shaderImageMinLod = false; bool pipelineExecutableInfo = false; bool maintenance8 = false; bool maintenance5 = false; @@ -532,6 +584,9 @@ struct VulkanDevice::State { std::vector, std::shared_ptr>> patternBuffers; VkDescriptorSetLayout workgroupMemoryLayout = VK_NULL_HANDLE; std::shared_ptr workgroupMemory; + std::mutex scratchPoolsMutex; + std::map> scratchPools; + std::uint64_t scratchPoolUses = 0; // 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. @@ -550,6 +605,7 @@ struct VulkanDevice::State { std::unique_ptr pipelineCache; std::unique_ptr descriptorCache; std::unique_ptr vertexInputs; + Graphics::VertexBufferFormats vertexFormats; std::unique_ptr samplerCache; Graphics::ResourceCache& resourceCache = Graphics::SharedResourceCache(); // Recorded dispatches that write a copied buffer (their results reach guest memory by a CPU @@ -695,6 +751,65 @@ struct VulkanDevice::State { return workgroupMemory; } + std::shared_ptr ScratchPoolFor(const Graphics::Context& context, const ShaderRecompiler::PipelineIdentity& identity, std::uint32_t slotDwords, std::uint64_t groups) { + const auto range = std::min(properties.limits.maxStorageBufferRange, ScratchPoolBufferLimit); + const auto slotBytes = (static_cast(slotDwords) + 2u) * 4u; + if (slotBytes > range) { + throw std::runtime_error("Vulkan dispatch: one scratch pool slot of " + std::to_string(slotBytes) + " bytes is over the scratch pool buffer limit of " + std::to_string(range) + " bytes"); + } + const auto capSlots = std::max(std::min({ScratchPoolCapBytes(), ScratchPoolTotalBytes(), range}) / slotBytes, 1u); + const auto wanted = std::clamp(groups, 1u, capSlots); + std::lock_guard lock(scratchPoolsMutex); + const auto use = ++scratchPoolUses; + if (const auto found = scratchPools.find(identity); found != scratchPools.end() && found->second->slots >= wanted) { + found->second->lastUse = use; + return found->second; + } + const auto size = wanted * slotBytes; + EvictScratchPools(identity, size); + auto created = std::make_shared(); + created->device = device; + created->destroyPool = DeviceFunction("vkDestroyDescriptorPool"); + created->slots = wanted; + created->headerBytes = wanted * 2u * 4u; + created->bytes = size; + created->lastUse = use; + created->buffer = std::make_unique(context, static_cast(size), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + const VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}; + VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + poolInfo.maxSets = 1; + poolInfo.poolSizeCount = 1; + poolInfo.pPoolSizes = &poolSize; + check(DeviceFunction("vkCreateDescriptorPool")(device, &poolInfo, nullptr, &created->pool), "vkCreateDescriptorPool scratch pool"); + VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + allocation.descriptorPool = created->pool; + allocation.descriptorSetCount = 1; + allocation.pSetLayouts = &workgroupMemoryLayout; + check(DeviceFunction("vkAllocateDescriptorSets")(device, &allocation, &created->set), "vkAllocateDescriptorSets scratch pool"); + const VkDescriptorBufferInfo described{created->buffer->Handle(), 0, size}; + VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + write.dstSet = created->set; + write.dstBinding = 0; + write.descriptorCount = 1; + write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + write.pBufferInfo = &described; + DeviceFunction("vkUpdateDescriptorSets")(device, 1, &write, 0, nullptr); + ZeroScratchPoolHeader(context, *created); + scratchPools[identity] = created; + return created; + } + + void EvictScratchPools(const ShaderRecompiler::PipelineIdentity& identity, std::uint64_t needed) { + scratchPools.erase(identity); + std::uint64_t held = 0; + for (const auto& [key, pool] : scratchPools) held += pool->bytes; + while (!scratchPools.empty() && held + needed > ScratchPoolTotalBytes()) { + const auto oldest = std::min_element(scratchPools.begin(), scratchPools.end(), [](const auto& left, const auto& right) { return left.second->lastUse < right.second->lastUse; }); + held -= oldest->second->bytes; + scratchPools.erase(oldest); + } + } + void CreateMarkerBuffer() { bufferMarker = DeviceFunction("vkCmdWriteBufferMarkerAMD"); VkBufferCreateInfo buffer{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; @@ -956,6 +1071,7 @@ struct VulkanDevice::State { Graphics::ClearHostImports(device); patternBuffers.clear(); workgroupMemory.reset(); + scratchPools.clear(); if (workgroupMemoryLayout != VK_NULL_HANDLE) reinterpret_cast(deviceProc(device, "vkDestroyDescriptorSetLayout"))(device, workgroupMemoryLayout, nullptr); descriptorCache.reset(); vertexInputs.reset(); @@ -1177,6 +1293,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : APS5_LOG_OUT("VulkanDevice constructor window=%p", static_cast(window)); #ifdef _WIN32 state->library = SDL_LoadObject("vulkan-1.dll"); +#elif defined(__APPLE__) + state->library = SDL_LoadObject("libvulkan.1.dylib"); #else state->library = SDL_LoadObject("libvulkan.so.1"); #endif @@ -1217,9 +1335,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : instanceExtensions.push_back(VK_EXT_SWAPCHAIN_COLOR_SPACE_EXTENSION_NAME); state->swapchainColorSpaces = true; } - create.enabledExtensionCount = static_cast(instanceExtensions.size()); - create.ppEnabledExtensionNames = instanceExtensions.data(); } +#ifdef __APPLE__ + instanceExtensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + create.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; +#endif + create.enabledExtensionCount = static_cast(instanceExtensions.size()); + create.ppEnabledExtensionNames = instanceExtensions.empty() ? nullptr : instanceExtensions.data(); const VkResult instanceResult = state->InstanceFunction("vkCreateInstance")(&create, nullptr, &state->instance); if (instanceResult == VK_ERROR_INCOMPATIBLE_DRIVER) throw VulkanUnavailable("vkCreateInstance: Vulkan result " + std::to_string(instanceResult)); check(instanceResult, "vkCreateInstance"); @@ -1352,6 +1474,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : state->individuallyFreeDescriptorSets = driver.driverID == VK_DRIVER_ID_MESA_LLVMPIPE; state->fusedExactArithmetic = driver.driverID == VK_DRIVER_ID_MOLTENVK; state->tessellationPipelineCache = TessellationPipelineCacheUsable(driver.driverID); + state->hostInterpolationFirst = HostInterpolationPreferred(driver.driverID) && std::getenv("APS5_NO_HOST_INTERPOLATION_FIRST") == nullptr; + APS5_LOG_OUT("Pixel interpolation driver=%d hostInterpolationFirst=%d", static_cast(driver.driverID), state->hostInterpolationFirst ? 1 : 0); state->subgroupControlBarriers = SubgroupControlBarrierUsable(driver.driverID); state->workgroupScopeFences = WorkgroupScopeFenceUsable(driver.driverID); state->ldsLockFences = LdsLockFencesRequired(driver.driverID); @@ -1623,6 +1747,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : 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); + if (!compileOnly) Pm4::RequestImportableGds(state->properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU); #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) { @@ -1667,7 +1792,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : if (hasExtension(VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME)) { VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &minLodFeatures}; state->InstanceFunction("vkGetPhysicalDeviceFeatures2")(selected, &features); - state->imageViewMinLod = minLodFeatures.minLod == VK_TRUE; + state->imageViewMinLod = minLodFeatures.minLod == VK_TRUE && std::getenv("APS5_NO_IMAGE_VIEW_MIN_LOD") == nullptr; if (state->imageViewMinLod) deviceExtensions.push_back(VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME); } minLodFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_VIEW_MIN_LOD_FEATURES_EXT}; @@ -1696,6 +1821,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : if (state->nonSeamlessCubeMap) deviceExtensions.push_back(VK_EXT_NON_SEAMLESS_CUBE_MAP_EXTENSION_NAME); } nonSeamlessCubeFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NON_SEAMLESS_CUBE_MAP_FEATURES_EXT}; + VkPhysicalDevicePortabilitySubsetFeaturesKHR portabilityFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR}; + const bool portabilitySubset = hasExtension(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); + if (portabilitySubset) { + VkPhysicalDeviceFeatures2 features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &portabilityFeatures}; + state->InstanceFunction("vkGetPhysicalDeviceFeatures2")(selected, &features); + deviceExtensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); + } nonSeamlessCubeFeatures.nonSeamlessCubeMap = VK_TRUE; VkPhysicalDeviceCoverageReductionModeFeaturesNV coverageReductionFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COVERAGE_REDUCTION_MODE_FEATURES_NV}; if (hasExtension(VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME) && hasExtension(VK_NV_COVERAGE_REDUCTION_MODE_EXTENSION_NAME)) { @@ -1879,6 +2011,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : if (enabled.shaderImageGatherExtended) state->capabilities.push_back(spv::CapabilityImageGatherExtended); enabled.shaderResourceMinLod = available.shaderResourceMinLod; if (enabled.shaderResourceMinLod) state->capabilities.push_back(spv::CapabilityMinLod); + state->shaderResourceMinLod = enabled.shaderResourceMinLod == VK_TRUE; + state->shaderImageMinLod = state->shaderResourceMinLod && !state->imageViewMinLod && !state->graphicsPipelineLibrary; enabled.sampleRateShading = available.sampleRateShading; enabled.geometryShader = available.geometryShader; state->geometryShader = enabled.geometryShader == VK_TRUE; @@ -1984,6 +2118,10 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : nonSeamlessCubeFeatures.pNext = const_cast(deviceInfo.pNext); deviceInfo.pNext = &nonSeamlessCubeFeatures; } + if (portabilitySubset) { + portabilityFeatures.pNext = const_cast(deviceInfo.pNext); + deviceInfo.pNext = &portabilityFeatures; + } if (!state->mixedAttachmentSamples && (state->mergedCoverageSampleCounts != 0 || state->truncatedCoverageSampleCounts != 0)) { coverageReductionFeatures.pNext = const_cast(deviceInfo.pNext); deviceInfo.pNext = &coverageReductionFeatures; @@ -2069,10 +2207,8 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : byteFeatures.pNext = &subgroupSizeFeatures; APS5_LOG_OUT("Wave32 programs run on subgroups of %u: compute %d, mesh %d", 32u, state->computeWave32, state->meshWave32); } - if (std::getenv("APS5_SPANNED_WAVE") != nullptr) { - state->spannedWaves = state->computeFullSubgroups; - APS5_LOG_OUT("APS5_SPANNED_WAVE: single-wave wave64 compute spans two full 32-lane subgroups: %d", state->spannedWaves); - } + state->spannedWaves = state->computeFullSubgroups && std::getenv("APS5_NO_SPANNED_WAVE") == nullptr; + APS5_LOG_OUT("single-wave wave64 compute spans two full 32-lane subgroups: %d", state->spannedWaves); timelineFeatures = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR}; timelineFeatures.timelineSemaphore = VK_TRUE; if (state->timelineSemaphores) { @@ -2184,12 +2320,13 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : if (domains(selected, &domainCount, available.data()) != VK_SUCCESS) available.clear(); } #ifdef _WIN32 - const auto host = VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR; + const std::array hosts{VK_TIME_DOMAIN_QUERY_PERFORMANCE_COUNTER_KHR}; #else - const auto host = VK_TIME_DOMAIN_CLOCK_MONOTONIC_KHR; + const std::array hosts{VK_TIME_DOMAIN_CLOCK_MONOTONIC_KHR, VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_KHR}; #endif - if (std::ranges::find(available, VK_TIME_DOMAIN_DEVICE_KHR) != available.end() && std::ranges::find(available, host) != available.end()) { - state->hostTimeDomain = host; + const auto host = std::ranges::find_first_of(hosts, available); + if (std::ranges::find(available, VK_TIME_DOMAIN_DEVICE_KHR) != available.end() && host != hosts.end()) { + state->hostTimeDomain = *host; state->calibratedTimestamps = state->DeviceFunction(khr ? "vkGetCalibratedTimestampsKHR" : "vkGetCalibratedTimestampsEXT"); } } @@ -2238,6 +2375,7 @@ VulkanDevice::VulkanDevice(const PresentationWindow* window, bool compileOnly) : Graphics::PrepareImportWatch(graphicsContext()); state->bufferPool = std::make_shared(graphicsContext()); state->emptyBuffer = std::make_unique(graphicsContext(), Graphics::EmptyBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + if (!compileOnly && Pm4::GdsImportable() && Graphics::HostImportFor(graphicsContext(), Pm4::GdsAddress(), Pm4::GdsBytes, Graphics::ImportRequester::Map) == nullptr) Pm4::RejectGdsImport(); state->pipelineCache = std::make_unique(graphicsContext(), state->properties, window != nullptr || compileOnly); state->detiler = std::make_unique(graphicsContext()); state->textureCache = std::make_unique(graphicsContext()); @@ -2494,7 +2632,7 @@ void VulkanDevice::StoreIdleResults(bool reapFirst, const std::function& GuestMemory::TagGpuLockSite(GuestMemory::GpuLockSite::Flush); std::lock_guard gpuLock(GuestMemory::GpuMutex()); const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); - static_cast(Graphics::StorageTexture::FlushAllPendingUntil("idle", stop)); + static_cast(Graphics::StorageTexture::FlushAllPendingUntil("idle", stop, state->device)); SubmitRecorded(reapFirst); } @@ -2571,9 +2709,9 @@ 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 (storeAtLabel) Graphics::StorageTexture::FlushAllPending("label", state->device); if (queue == 0) static_cast(Graphics::StorageTexture::StoreDrawnLinear("label")); - Graphics::StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Graphics::StorageTexture::StoreReadbackBeforeLabel("readback before label", state->device); 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 @@ -2721,7 +2859,7 @@ bool VulkanDevice::FillBuffer(std::uint64_t address, std::size_t bytes, std::spa // rest) on a [fill-sync] line; Driver.cpp's [fill] line counts the fills and their bytes. // `labelSynced` counts syncs taken because a completion label was pending: over the range for // a small fill, anywhere at all for a larger one. - static std::uint64_t fills = 0, synced = 0, labelSynced = 0, ordered = 0; + static std::uint64_t fills = 0, synced = 0, ordered = 0; static double syncedMs = 0; // The [fill-sync] line's phases (APS5_PROFILE_DRAW): decide (the reap, the import lookup and // the pending-write tests up to the record), record (barriers and the fill or its doubling @@ -2745,8 +2883,8 @@ bool VulkanDevice::FillBuffer(std::uint64_t address, std::size_t bytes, std::spa if (!sync && Graphics::Recorder::PendingCompletionLabels() != 0) { // A label the CPU will store after a completion must not be overwritten by the fill: // only a label inside the filled range matters, whatever the range's size. - sync = recorder.PendingLabelIn(address, bytes); - if (sync) ++labelSynced; + sync = recorder.CompletionLabelIn(address, bytes); + if (sync) ++fillCompletionSyncs; } if (sync) { ++synced; @@ -2766,7 +2904,7 @@ bool VulkanDevice::FillBuffer(std::uint64_t address, std::size_t bytes, std::spa const auto now = std::chrono::steady_clock::now(); if (now - lastReport > std::chrono::seconds(10)) { lastReport = now; - AgcDriver::ProfilePrint_nid_no_patch("[fill-sync] fills (cumulative): %llu synced (%llu with a completion label pending) waited %.0f ms, %llu ordered by barrier, %llu with no pending write; phases ms: decide %.0f record %.0f notes %.0f; %llu uniform, %llu pattern (%llu doubling steps in place; pattern buffers reused %llu, made %llu)\n", static_cast(synced), static_cast(labelSynced), syncedMs, static_cast(ordered), static_cast(fills - synced - ordered), decideMs, recordMs, notesMs, static_cast(uniformFills), static_cast(patternFills), static_cast(doublingSteps), static_cast(patternBuffersReused), static_cast(patternBuffersMade)); + AgcDriver::ProfilePrint_nid_no_patch("[fill-sync] fills (cumulative): %llu synced (%llu with a completion label pending) waited %.0f ms, %llu ordered by barrier, %llu with no pending write; phases ms: decide %.0f record %.0f notes %.0f; %llu uniform, %llu pattern (%llu doubling steps in place; pattern buffers reused %llu, made %llu)\n", static_cast(synced), static_cast(fillCompletionSyncs), syncedMs, static_cast(ordered), static_cast(fills - synced - ordered), decideMs, recordMs, notesMs, static_cast(uniformFills), static_cast(patternFills), static_cast(doublingSteps), static_cast(patternBuffersReused), static_cast(patternBuffersMade)); } } auto phaseStart = fillStart; @@ -3535,7 +3673,7 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer, const Displ } } static const bool storeAtFlip = Graphics::StorageTexture::StoreAtFlipRequested(std::getenv("APS5_STORE_AT_FLIP")); - if (storeAtFlip) Graphics::StorageTexture::FlushAllPending("flip"); + if (storeAtFlip) Graphics::StorageTexture::FlushAllPending("flip", state->device); else Graphics::StorageTexture::FlushDumpSelected("flip"); auto& dumps = Dumps(); const bool passthrough = state->surfaceColorSpace == VK_COLOR_SPACE_HDR10_ST2084_EXT; @@ -3980,6 +4118,8 @@ std::size_t VulkanDevice::FlipInFlight() { return count; } +std::uint64_t VulkanDevice::FillCompletionSyncCount() { return fillCompletionSyncs; } + VulkanDevice::PresentStatistics VulkanDevice::PresentCounts() { return presentCounters; } @@ -4055,8 +4195,8 @@ void VulkanDevice::QueuePresent() { 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"); + if (presented == VK_ERROR_OUT_OF_DATE_KHR) state->extent = {0, 0}; + else if (presented != VK_SUBOPTIMAL_KHR) check(presented, "vkQueuePresentKHR"); timing.Mark("queue_present"); APS5_LOG_OUT_DEBUG("vkQueuePresentKHR queued imageIndex=%u", index); } @@ -4079,6 +4219,14 @@ std::string VulkanDevice::DriverInfo() const { return state->driverInfo.empty() ? std::to_string(state->properties.driverVersion) : state->driverInfo; } +bool VulkanDevice::BdaAtomicsUsable() const { + return AgcDriver::BdaAtomicsUsable(state->driverId); +} + +bool VulkanDevice::RasterOrderGroupUsable() const { + return AgcDriver::RasterOrderGroupUsable(state->driverId); +} + bool VulkanDevice::CalibrateClock(double& gpuNs, std::chrono::steady_clock::time_point& cpu) const { if (state->calibratedTimestamps == nullptr) return false; const std::array domains{VkCalibratedTimestampInfoKHR{VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_KHR, nullptr, VK_TIME_DOMAIN_DEVICE_KHR}, VkCalibratedTimestampInfoKHR{VK_STRUCTURE_TYPE_CALIBRATED_TIMESTAMP_INFO_KHR, nullptr, state->hostTimeDomain}}; @@ -4094,7 +4242,7 @@ bool VulkanDevice::CalibrateClock(double& gpuNs, std::chrono::steady_clock::time const double sinceNs = static_cast(static_cast(counter.QuadPart) - stamps[1]) * 1e9 / static_cast(frequency.QuadPart); #else timespec monotonic{}; - clock_gettime(CLOCK_MONOTONIC, &monotonic); + clock_gettime(state->hostTimeDomain == VK_TIME_DOMAIN_CLOCK_MONOTONIC_RAW_KHR ? CLOCK_MONOTONIC_RAW : CLOCK_MONOTONIC, &monotonic); const auto now = std::chrono::steady_clock::now(); const double sinceNs = static_cast(static_cast(monotonic.tv_sec) * 1000000000ull + static_cast(monotonic.tv_nsec) - stamps[1]); #endif @@ -4122,6 +4270,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::Target() const { auto target = state->shaderProfile->Target(); target.float32FlushToZero = state->float32FlushToZero; target.fusedExactArithmetic = state->fusedExactArithmetic; + target.hostInterpolationFirst = state->hostInterpolationFirst; target.float32DenormPreserve = state->float32DenormPreserve; return target; } @@ -4137,6 +4286,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const { target.fragmentShaderBarycentricEnabled = state->fragmentShaderBarycentric; target.fragmentBarycentricBuiltins = state->fragmentBarycentricBuiltins; target.nonConstantImageOffsets = state->maintenance8; + target.shaderImageMinLod = state->shaderImageMinLod; target.narrowSubgroupClock = state->narrowSubgroupClock; target.float64DenormPreserve = state->float64DenormPreserve; target.float16DenormPreserve = state->float16DenormPreserve; @@ -4153,6 +4303,7 @@ ShaderRecompiler::SpirvTarget VulkanDevice::buildTarget() const { target.ldsLockFences = state->ldsLockFences; target.meshWindowBuffer = state->driverId == VK_DRIVER_ID_NVIDIA_PROPRIETARY && std::getenv("APS5_NO_MESH_WINDOW_BUFFER") == nullptr; target.srgbDecodeFormats = state->srgbDecodeFormats & ~state->srgbCopyFormats; + target.maxImageArrayLayers = limits.maxImageArrayLayers; target.vertexSubgroups = (state->subgroup.supportedStages & VK_SHADER_STAGE_VERTEX_BIT) != 0; target.tessellationEvaluationSubgroups = (state->subgroup.supportedStages & VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT) != 0; if (state->meshShader) { @@ -4201,6 +4352,13 @@ bool VulkanDevice::GraphicsPipelineLibraries() const { return state->graphicsPipelineLibrary; } +std::pair VulkanDevice::scratchPoolHeader(const ShaderRecompiler::RecompileResult& shader) const { + std::lock_guard lock(state->scratchPoolsMutex); + const auto found = state->scratchPools.find(shader.Identity()); + if (found == state->scratchPools.end() || found->second == nullptr) return {VK_NULL_HANDLE, 0}; + return {found->second->buffer->Handle(), found->second->headerBytes}; +} + Graphics::Context VulkanDevice::graphicsContext() const { static const bool noCache = std::getenv("APS5_NO_CONTEXT_CACHE") != nullptr; if (state->contextReady && !noCache) return state->context; @@ -4254,6 +4412,7 @@ Graphics::Context VulkanDevice::buildContext() const { context.recorder = state->recorder.get(); context.descriptorCache = state->descriptorCache.get(); context.vertexInputs = state->vertexInputs.get(); + context.vertexFormats = &state->vertexFormats; context.samplerCache = state->samplerCache.get(); context.drawIndirectFirstInstance = state->drawIndirectFirstInstance; context.multiDrawIndirect = state->multiDrawIndirect; @@ -4310,6 +4469,8 @@ Graphics::Context VulkanDevice::buildContext() const { context.earlyFragmentSampleMaskTestBeforeSampleCounting = state->earlyFragmentSampleMaskTestBeforeSampleCounting; context.primitiveListRestart = state->primitiveListRestart; context.imageViewMinLod = state->imageViewMinLod; + context.shaderResourceMinLod = state->shaderResourceMinLod; + context.shaderImageMinLod = state->shaderImageMinLod; context.pipelineExecutableInfo = state->pipelineExecutableInfo; context.maintenance8 = state->maintenance8; context.srgbDecodeFormats = state->srgbDecodeFormats; @@ -5020,14 +5181,14 @@ VulkanDevice::PresyncStatistics VulkanDevice::PresyncCounts() { return {counters.checked.load(std::memory_order_relaxed), counters.presyncs.load(std::memory_order_relaxed), counters.keyChecks.load(std::memory_order_relaxed), counters.keyWaits.load(std::memory_order_relaxed)}; } -std::shared_ptr VulkanDevice::PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span snapshots, std::uint64_t dispatchThreads, std::uint64_t programAddress, std::optional> htileClear) { +std::shared_ptr VulkanDevice::PrepareDispatch(const ShaderRecompiler::RecompileResult& shader, std::span snapshots, std::uint64_t dispatchThreads, std::uint64_t programAddress, std::optional> htileClear, const std::vector* registeredImageViews) { ShaderRecompiler::RuntimeAbi::RequireVersion(shader.runtimeAbiVersion); // APS5_LOCKED_BUILD=1: the whole build under the mutex, as before the split. static const bool lockedBuild = std::getenv("APS5_LOCKED_BUILD") != nullptr; if (lockedBuild || shader.spirv.size() < 5 || shader.spirv[0] != 0x07230203u) return nullptr; static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; const auto start = std::chrono::steady_clock::now(); - const Graphics::CompiledShader compute{ShaderRecompiler::ShaderStage::Compute, &shader, 0}; + const Graphics::CompiledShader compute{ShaderRecompiler::ShaderStage::Compute, &shader, 0, registeredImageViews}; const auto context = graphicsContext(); auto prepared = std::make_shared(); prepared->dispatchThreads = dispatchThreads; @@ -5143,12 +5304,12 @@ std::shared_ptr VulkanDevice::PrepareRecipe(const std::shared_ptr 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)); +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, const std::vector* registeredImageViews) { + static_cast(dispatch(shader, x, y, z, 0, snapshots, programAddress, std::move(prepared), recipe, {}, htileClear, registeredImageViews)); } -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) { - return dispatch(shader, 0, 0, 0, arguments, snapshots, programAddress, std::move(prepared), recipe, threadGroup); +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, const std::vector* registeredImageViews) { + return dispatch(shader, 0, 0, 0, arguments, snapshots, programAddress, std::move(prepared), recipe, threadGroup, std::nullopt, registeredImageViews); } void VulkanDevice::decideIndirect(RecordedDispatch& record, IndirectOutcome& outcome, char* groupsText) { @@ -5262,8 +5423,23 @@ 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())); + std::shared_ptr pool; + if (const auto slotDwords = record.shader->program->scratchPoolSlotDwords; slotDwords != 0) { + const std::uint64_t groups = argumentImport != nullptr ? std::numeric_limits::max() : static_cast(record.x) * record.y * record.z; + pool = state->ScratchPoolFor(context, record.shader->program->Identity(), slotDwords, groups); + } + bool unordered = false; + const auto overlapQueue = Graphics::Recorder::TakeDispatchOrder(unordered); + const auto* groupLimit = state->properties.limits.maxComputeWorkGroupCount; + const bool overlapCapable = overlapQueue != 0 && Graphics::Recorder::DispatchOverlapEnabled() && arguments == 0 && argumentImport == nullptr && record.threadGroup == std::array{} && record.shader->program->workgroupMemoryDwords == 0 && state->checkpointMarker == nullptr && state->bufferMarker == nullptr && record.x <= groupLimit[0] && record.y <= groupLimit[1] && record.z <= groupLimit[2] && !resources.HasHostGpuCopies(); + std::vector runAccesses; + if (overlapCapable) resources.RunAccesses(runAccesses); VkAccessFlags covered = 0; - const auto commands = recorder.Commands(&covered); + bool overlapped = false; + const std::array runOwners{record.resources.get(), resources.MemoryOwner()}; + const auto commands = overlapCapable && unordered ? recorder.OverlapCommands(overlapQueue, runOwners, runAccesses, &covered, overlapped) : recorder.Commands(&covered); + constexpr VkAccessFlags runLeadingCovered = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + if (overlapCapable && !overlapped && (covered & runLeadingCovered) != runLeadingCovered) covered = 0; recordStep(PhaseRecordCommands); recorder.Keep(record.objects); if (record.resources != nullptr) record.resources->KeepStorageImageBytes(recorder); @@ -5330,12 +5506,19 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { } // Results of earlier recorded work are visible to this dispatch, its own to everything after. constexpr VkAccessFlags shaderAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; - if ((covered & shaderAccess) == shaderAccess && barrierElision) { + if (overlapped) { + if (record.refreshed) { + Graphics::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_UNIFORM_READ_BIT); + Graphics::Recorder::CountBarriers(CommandClass::TemplateDataRefresh); + } + } else if ((covered & shaderAccess) == shaderAccess && barrierElision) { ++d.preBarriersSkipped; Graphics::Recorder::CountMerged(CommandClass::DispatchLeading); } else { const auto timing = recorder.BeginGpuTiming(CommandClass::DispatchLeading); - Graphics::RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + const VkPipelineStageFlags leadingStages = overlapCapable ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT : VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + const VkAccessFlags leadingAccess = overlapCapable ? VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT : VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + Graphics::RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, leadingStages, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, leadingAccess); Graphics::Recorder::CountBarriers(CommandClass::DispatchLeading); recorder.EndGpuTiming(timing); ++d.preBarriersRecorded; @@ -5363,6 +5546,10 @@ void VulkanDevice::recordDispatch(RecordedDispatch& record) { context.Resolved(&Graphics::DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, record.objects->layout, ShaderRecompiler::WorkgroupMemoryDescriptorSet, 1, &memory->set, 0, nullptr); recorder.Keep(std::move(memory)); } + if (pool != nullptr) { + context.Resolved(&Graphics::DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, record.objects->layout, ShaderRecompiler::ScratchPoolDescriptorSet, 1, &pool->set, 0, nullptr); + recorder.Keep(std::move(pool)); + } if (record.pushStages != 0) { context.Resolved(&Graphics::DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, record.objects->layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, Graphics::ComputePushConstantBytes, record.pushBytes->data()); } @@ -5403,12 +5590,17 @@ 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); - Graphics::Recorder::CountBarriers(CommandClass::DispatchTrailing); - recorder.EndGpuTiming(trailingTiming); - recorder.MarkCovered(dispatchedAccess); + if (overlapCapable) { + recorder.DeferDispatchBarrier(overlapQueue, runOwners, runAccesses); + resources.CopyBacksAfterRun(record.resources); + } else { + 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); + Graphics::Recorder::CountBarriers(CommandClass::DispatchTrailing); + recorder.EndGpuTiming(trailingTiming); + recorder.MarkCovered(dispatchedAccess); + } recordStep(PhaseRecordBind); if (record.htileClear.has_value()) Graphics::ArmHtileDepthClear(record.htileClear->first, record.htileClear->second); resources.MarkGpuWrites(recorder); @@ -5456,10 +5648,11 @@ std::shared_ptr VulkanDevice::createComputePipeline(cons objects->destroyPipeline = state->DeviceFunction("vkDestroyPipeline"); objects->module = Graphics::CreateShaderModule(state->DeviceFunction("vkCreateShaderModule"), state->device, state->graphicsPipelineLibrary, shader.spirv.data(), shader.spirv.size() * sizeof(std::uint32_t), "vkCreateShaderModule"); objects->resourcesLayout = setLayout; - const std::array setLayouts{setLayout, shader.workgroupMemoryDwords != 0 ? state->workgroupMemoryLayout : VK_NULL_HANDLE}; + const bool scratchPool = shader.scratchPoolSlotDwords != 0; + const std::array setLayouts{setLayout, shader.workgroupMemoryDwords != 0 || scratchPool ? state->workgroupMemoryLayout : VK_NULL_HANDLE, scratchPool ? state->workgroupMemoryLayout : VK_NULL_HANDLE}; const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, Graphics::ComputePushConstantBytes}; VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; - layoutInfo.setLayoutCount = shader.workgroupMemoryDwords != 0 ? ShaderRecompiler::WorkgroupMemoryDescriptorSet + 1 : 1; + layoutInfo.setLayoutCount = scratchPool ? ShaderRecompiler::ScratchPoolDescriptorSet + 1 : shader.workgroupMemoryDwords != 0 ? ShaderRecompiler::WorkgroupMemoryDescriptorSet + 1 : 1; layoutInfo.pSetLayouts = setLayouts.data(); layoutInfo.pushConstantRangeCount = pushStages != 0 ? 1 : 0; layoutInfo.pPushConstantRanges = pushStages != 0 ? &push : nullptr; @@ -5479,7 +5672,8 @@ std::shared_ptr VulkanDevice::createComputePipeline(cons resourcesLayout.bindings.push_back({layoutKey[index], type, layoutKey[index + 2u], layoutKey[index + 3u], updateAfterBind && type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT : 0u}); } capture.sets.push_back(std::move(resourcesLayout)); - if (shader.workgroupMemoryDwords != 0) capture.sets.push_back({0, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, 0}}}); + if (shader.workgroupMemoryDwords != 0 || scratchPool) capture.sets.push_back({0, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, 0}}}); + if (scratchPool) capture.sets.push_back({0, {{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, 0}}}); if (pushStages != 0) capture.pushConstants.push_back(push); for (const auto& value : shader.specialization) capture.specialization.push_back({value.id, value.value}); capture.requiredSubgroupSize = subgroupSize; @@ -5565,7 +5759,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, std::optional> htileClear) { +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, const std::vector* registeredImageViews) { startJournalReplay(); for (const auto& binding : shader.bindings) { if (binding.role != ShaderRecompiler::DescriptorRole::GuestBuffers) continue; @@ -5590,7 +5784,7 @@ VulkanDevice::IndirectOutcome VulkanDevice::dispatch(const ShaderRecompiler::Rec if (shader.spirv.size() < 5 || shader.spirv[0] != 0x07230203u) { throw std::runtime_error("Vulkan dispatch: invalid SPIR-V"); } - const std::array shaders{{{ShaderRecompiler::ShaderStage::Compute, &shader, 0}}}; + const std::array shaders{{{ShaderRecompiler::ShaderStage::Compute, &shader, 0, registeredImageViews}}}; const auto pushStages = Graphics::PushConstantStages(shaders); if (pushStages != 0 && state->properties.limits.maxPushConstantsSize < Graphics::ComputePushConstantBytes) { throw std::runtime_error("Vulkan dispatch: compute push constant range exceeds device limit"); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp index 7eefc1cce6..bbcee6c7db 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp @@ -26,6 +26,16 @@ namespace AgcDriver { #ifdef _WIN32 +bool ReadableStackRegion(std::uint64_t address, std::uint64_t& low, std::uint64_t& high) { + constexpr DWORD readable = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY; + MEMORY_BASIC_INFORMATION memory{}; + if (VirtualQuery(reinterpret_cast(address), &memory, sizeof(memory)) == 0 || memory.State != MEM_COMMIT) return false; + if ((memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) != 0 || (memory.Protect & readable) == 0) return false; + low = reinterpret_cast(memory.BaseAddress); + high = low + memory.RegionSize; + return true; +} + namespace { std::jthread processSamplerThread; @@ -72,7 +82,7 @@ struct Sampler { } bool readStack(std::uint64_t address, std::uint64_t& value) const { - if (address < stackLow || address > stackHigh - sizeof(value)) return false; + if (stackHigh < stackLow + sizeof(value) || address < stackLow || address > stackHigh - sizeof(value)) return false; std::memcpy(&value, reinterpret_cast(address), sizeof(value)); return true; } @@ -104,6 +114,11 @@ struct Sampler { } int walk(CONTEXT& context, std::array& frames) { + if (context.Rsp < stackLow || context.Rsp >= stackHigh) { + stackLow = 0; + stackHigh = 0; + ReadableStackRegion(context.Rsp, stackLow, stackHigh); + } const bool dwarf = stackHigh != 0 && context.Rsp >= stackLow && context.Rsp < stackHigh; int count = 0; while (count < depth && context.Rip != 0) { @@ -364,6 +379,7 @@ void StopWorkerSampler() { if (committerSamplerThread.joinable()) committerSamplerThread.join(); } #else +bool ReadableStackRegion(std::uint64_t, std::uint64_t&, std::uint64_t&) { return false; } void StartWorkerSampler(std::uint32_t) {} void StartCommitterSampler() {} void StopWorkerSampler() {} diff --git a/core/libs/prx/libSceAgcDriver/Export.cpp b/core/libs/prx/libSceAgcDriver/Export.cpp index 199dad9656..83d21e8c7a 100644 --- a/core/libs/prx/libSceAgcDriver/Export.cpp +++ b/core/libs/prx/libSceAgcDriver/Export.cpp @@ -127,26 +127,6 @@ int APS5_VABI sceAgcDriverTmpInitIdhs(void) { return 0; } -int APS5_VABI sceAgcDriverUserDataWritePacket(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcDriverUserDataWritePopMarker(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcDriverUserDataWritePushMarker(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAgcDriverUserDataWriteSetMarker(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } extern "C" { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/BdaResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/BdaResources.hpp index 2a796be046..45aa0df8aa 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/BdaResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/BdaResources.hpp @@ -3,7 +3,10 @@ #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "Recompiler.hpp" +#include +#include #include +#include #include #include #include @@ -12,13 +15,30 @@ namespace AgcDriver::Graphics { bool LoopGuardTripped(); +struct BdaFaultChunk; + class BdaResources { public: - explicit BdaResources(const Context& context); - BdaResources(const Context& context, const GuestBufferMemory& memory); + explicit BdaResources(const Context& context, bool reportOnly = false); + BdaResources(const Context& context, const GuestBufferMemory& memory, bool reportOnly = false); + ~BdaResources(); + BdaResources(const BdaResources&) = delete; + BdaResources& operator=(const BdaResources&) = delete; VkDescriptorBufferInfo Table() const; VkDescriptorBufferInfo Fault() const; void CheckFault() const; + void NoteUse(std::uint64_t serial); + bool TakeFaultSlot(const Context& context); + bool HoldsFaultSlot() const { return faultChunk != nullptr; } + static void NoteFinished(VkDevice device, std::uint64_t serial); + struct FaultSlotCounts { + std::uint64_t chunks = 0; + std::uint64_t slots = 0; + std::uint64_t held = 0; + std::uint64_t reclaimed = 0; + std::uint64_t retaken = 0; + }; + static FaultSlotCounts FaultSlots(VkDevice device); struct ImageTableFaults { std::uint32_t stage = 0; std::uint64_t shader = 0; @@ -57,8 +77,13 @@ private: // BdaResources.cpp, which refers to it weakly). void markWrittenPages() const; void checkSrtGuard() const; + std::span faultBytes() const; std::shared_ptr table; - std::unique_ptr fault; + VkDevice faultDevice = VK_NULL_HANDLE; + std::shared_ptr faultChunk; + VkDeviceSize faultOffset = 0; + bool reportOnly = false; + std::uint64_t lastUse = 0; std::size_t tableBytes = 0; std::vector imageTables; std::vector> imageTableRanges; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp index 985840aef9..c9fe003269 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Context.hpp @@ -6,6 +6,7 @@ #endif #include #include +#include "prx/libc/include/PrimedSharedMutex.hpp" #include #include #include @@ -35,6 +36,7 @@ class SamplerCache; class ShaderResources; class PlaceholderImages; class VertexInputCache; +class VertexBufferFormats; class PipelineCacheSynchronization { public: @@ -46,7 +48,7 @@ public: SnapshotLock TryLockForSnapshot() const { return SnapshotLock(mutex, std::try_to_lock); } private: - mutable std::shared_mutex mutex; + mutable PrimedSharedMutex mutex; }; inline void Require(bool condition, const std::string& reason) { @@ -144,10 +146,7 @@ struct DeviceFunctions { }; // Per-thread count of vkGetDeviceProcAddr lookups made through Context::Function (the [vk] line). -inline std::uint64_t& DeviceProcLookups() { - thread_local std::uint64_t count = 0; - return count; -} +std::uint64_t& DeviceProcLookups(); inline constexpr std::size_t EmptyBufferBytes = 16; @@ -262,6 +261,8 @@ struct Context { bool earlyFragmentSampleMaskTestBeforeSampleCounting = false; bool primitiveListRestart = false; bool imageViewMinLod = false; + bool shaderResourceMinLod = false; + bool shaderImageMinLod = false; bool pipelineExecutableInfo = false; bool maintenance8 = false; bool graphicsPipelineLibrary = false; @@ -282,6 +283,7 @@ struct Context { return pipelineCacheSynchronization != nullptr ? pipelineCacheSynchronization->LockForPipelineUse() : PipelineCacheSynchronization::PipelineUseLock{}; } VertexInputCache* vertexInputs = nullptr; + VertexBufferFormats* vertexFormats = nullptr; template TFunction Function(const char* name) const { @@ -362,6 +364,20 @@ struct Context { return *host; } + bool DeviceLocalHeadroom(std::uint32_t type, VkDeviceSize bytes) const { + if (memoryProperties2 == nullptr || physical == VK_NULL_HANDLE || type >= memory.memoryTypeCount) return true; + const auto heap = memory.memoryTypes[type].heapIndex; + if ((memory.memoryHeaps[heap].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0) return true; + VkPhysicalDeviceMemoryBudgetPropertiesEXT reported{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; + VkPhysicalDeviceMemoryProperties2 properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2, &reported}; + memoryProperties2(physical, &properties); + const auto budget = reported.heapBudget[heap]; + if (budget == 0) return true; + const auto limit = budget - budget / 8u; + const auto usage = reported.heapUsage[heap]; + return usage < limit && bytes <= limit - usage; + } + std::optional HostMemoryType(std::uint32_t mask) const { for (std::uint32_t i = 0; i < memory.memoryTypeCount; ++i) { if ((mask & (1u << i)) != 0 && (memory.memoryHeaps[memory.memoryTypes[i].heapIndex].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0) return i; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp index d2d1eedb53..cf2e096403 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp @@ -17,7 +17,8 @@ namespace AgcDriver::Graphics { class Texture; class StorageTexture; -VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten = true); +VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten = true, bool stencilTested = false); +VkImageView DepthSurfaceViewWithStencilWriteIntent(const Context& context, const DepthTarget& target, bool depthWritten, bool stencilTested, bool stencilWritten); SampleOrderTarget DepthSurfaceOrderTarget(const Context& context, const DepthTarget& target); DepthSampleLocations DepthSurfaceSampleLocations(const Context& context, const DepthTarget& target); enum class DepthPlaneScope { Overlapped, Whole }; @@ -81,10 +82,12 @@ std::optional MatchMemoryValueFill(std::span DepthSurfaceTexture(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D); +std::shared_ptr DepthSurfaceFirstSample(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components); std::size_t DepthPlaneCopyCount(); VkDeviceSize DepthPlaneCopyBytes(); VkFormat HostDepthFormat(const Context& context, VkFormat format); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp index 016ca9a493..4f8c73b025 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Draw.hpp @@ -83,6 +83,13 @@ struct MeshArgumentRules { std::uint32_t maxTotal; }; MeshArgumentRules MeshArgumentRulesFor(const Context& context, const ShaderRecompiler::MeshConfiguration& mesh, std::uint32_t indexCount); +struct MeshGrid { + std::uint32_t groups; + std::uint32_t instances; + std::uint32_t layers; + std::uint32_t layerStride; +}; +std::optional MeshGridFor(const Context& context, std::uint32_t groups, std::uint32_t instances); MeshArguments ResolveMeshArguments(const Pm4::DrawArguments& record, const MeshArgumentRules& rules); // Why DrawWithRecipe did not record from the recipe (the caller then runs Draw): the draw is not diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index 2531eccf35..5186c230d5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -24,6 +24,7 @@ void ShutdownGuestBufferWorkers(); struct GuestMemorySnapshot { std::uint64_t address; std::span bytes; + bool compared = false; }; // A guest allocation imported with VK_EXT_external_memory_host, usable by the GPU in place. @@ -56,6 +57,15 @@ inline VkDeviceSize HostImportOffset(const HostImport& import, std::uint64_t add return address - import.base + import.lead; } +struct HostImportStart { + std::uint64_t guest; + VkDeviceAddress device; +}; + +inline HostImportStart HostImportBufferStart(const HostImport& import) { + return {import.base - import.lead, import.address - import.lead}; +} + enum class ImportWatch : std::uint8_t { Watch, Unwatch }; struct ImportProbe { @@ -69,6 +79,7 @@ struct ImportProbe { std::uint32_t writtenAfterSettle = 0; std::uint32_t writtenByCpu = 0; std::uint32_t writtenAfterFill = 0; + std::uint32_t staleAfterFill = 0; }; ImportProbe ProbeImportWriteProtection(const Context& context); @@ -104,6 +115,8 @@ 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); +void RequireGpuLockBeforeImports(const char* where); +bool ImportsHeldByThisThread(); 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). @@ -379,6 +392,16 @@ public: // regions was recorded (ShaderResources::MarkGpuWrites), under GuestMemory::GpuMutex. A use that // never calls it (a synchronous draw) stores the staging bytes from the CPU in WriteBack. void RecordCopyBacks(Recorder& recorder); + bool HasGpuCopies() const { + for (const auto& region : regions) if (region.gpuCopy) return true; + return false; + } + void RunAccesses(std::vector>& out) const; + bool HasHostGpuCopies() const { + for (const auto& region : regions) if (region.gpuCopy && !region.deviceLocal) return true; + return false; + } + void MarkStagedWrites() const; // Whether registered allocations are pinned until write-back (address-based shaders): by this // build's own lease, or by the cached address space it holds. bool HoldsLease() const { return !lease.empty() || space != nullptr; } @@ -453,7 +476,9 @@ private: bool gpuCopy = false; VkBuffer copySource = VK_NULL_HANDLE; std::uint64_t copySourceBase = 0; + VkDeviceAddress copySourceAddress = 0; bool copiedBack = false; + std::shared_ptr shadowCopy; // An element the shader updates atomically lies inside (AddWritable's `atomic`). bool atomic = false; bool swept = false; @@ -520,6 +545,7 @@ private: // the barriers that order them after earlier recorded writes and before the shaders reading them. void recordGpuCopies(std::span copies, bool addressable); void takeHeapReferences(); + void releaseHeapReferences() noexcept; Context context; bool stagingAllowed = false; bool addressStaging = true; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp index 55442662ba..8e1b012d47 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestSamplerResource.hpp @@ -35,6 +35,8 @@ struct GuestSamplerResource { GuestSamplerResource DecodeSamplerResource(std::span words, bool unnormalizedProven = false, bool forceDegammaPaired = false, bool integerBorder = false); std::optional> SingleLevelSamplerWords(std::span words, bool singleLevelImage, bool mipmappedImage); +bool HoldsLodAtZero(std::span words); +std::array PinnedLodSamplerWords(std::span words, std::uint32_t lod); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp index d69fcb7dc6..17ab69dd55 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp @@ -4,6 +4,7 @@ #include "Recompiler.hpp" #include #include +#include #include namespace AgcDriver::Graphics { @@ -109,6 +110,7 @@ struct GuestTextureResource { std::uint32_t gcnPitch = 0; bool gcnPow2Pad = false; bool gcnLinearGeneral = false; + std::uint32_t linearPitch = 0; }; constexpr bool IsGcnMicroTiled(TextureTileMode mode) { @@ -140,6 +142,8 @@ constexpr TextureTileMode GcnMicroTileMode(TextureTileMode mode) { float EffectiveMinLod(const GuestTextureResource& resource); bool IsNullTextureResource(std::span words); +std::optional TextureResourceAllocationAddress(std::span words); +bool IsNullSampledTextureResource(std::span words); GuestTextureResource DecodeTextureResource(std::span words); std::array EncodeTextureResource(const GuestTextureResource& resource); bool DescriptorSingleLevel(std::span words); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/MapImportGate.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/MapImportGate.hpp new file mode 100644 index 0000000000..4d0b728a40 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/MapImportGate.hpp @@ -0,0 +1,85 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MAPIMPORTGATE_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_MAPIMPORTGATE_HPP + +#include +#include + +namespace AgcDriver::Graphics::MapImportGate { + +constexpr std::uint32_t StripeShift = 21; +constexpr std::uint32_t StripeCount = 256; +using Stripes = std::bitset; + +Stripes StripesOf(std::uint64_t begin, std::uint64_t end); +bool LockOrderChecked(); +void NoteLeafLock() noexcept; +void NoteLeafUnlock() noexcept; +void RequireNoLeafLockHeld(const char* where); + +template class LeafMutex { +public: + void lock() noexcept { + mutex.lock(); + NoteLeafLock(); + } + + bool try_lock() noexcept { + if (!mutex.try_lock()) return false; + NoteLeafLock(); + return true; + } + + void unlock() noexcept { + NoteLeafUnlock(); + mutex.unlock(); + } + +private: + TMutex mutex; +}; + +class PublishHold { +public: + PublishHold(std::uint64_t address, std::uint64_t bytes); + ~PublishHold(); + PublishHold(const PublishHold&) = delete; + PublishHold& operator=(const PublishHold&) = delete; + +private: + enum class Mode : std::uint8_t { Nested, Fast, Striped }; + Mode mode = Mode::Nested; + Stripes added; +}; + +class ImportHold { +public: + explicit ImportHold(const Stripes& stripes); + ~ImportHold(); + ImportHold(const ImportHold&) = delete; + ImportHold& operator=(const ImportHold&) = delete; + bool Covers(std::uint64_t begin, std::uint64_t end) const; + const Stripes& Held() const { return held; } + +private: + Stripes held; +}; + +struct Counters { + std::uint64_t fastPublications = 0; + std::uint64_t stripedPublications = 0; + std::uint64_t waitedPublications = 0; + std::uint64_t importHolds = 0; + std::uint64_t drainWaits = 0; + std::uint64_t retries = 0; +}; + +Counters CountersForTest(); +std::uint32_t WaitingPublishersForTest(); +std::uint32_t DrainingImportsForTest(); +void CountRetry() noexcept; +void SetUnlockedHookForTest(void (*hook)()); +void RunUnlockedHookForTest(); + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp index 44c1a86246..3eb4367d1e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp @@ -48,6 +48,8 @@ public: void NoteFill(std::uint64_t fillAddress, std::size_t fillBytes, std::uint32_t pattern); std::optional TakeFill(); void Discard(); + std::optional Pending() const { return filled; } + const std::string& Refusal() const { return refusal; } private: std::uint64_t address = 0; @@ -56,6 +58,20 @@ private: std::string refusal; }; +enum class MultisampleColorContent { Unknown, Fill, FastClear, Clear, Drawn }; + +struct MultisampleColorAlias { + std::uint64_t address = 0; + std::uint64_t bytes = 0; + VkFormat format = VK_FORMAT_UNDEFINED; + std::uint32_t samples = 0; + MultisampleColorContent newest = MultisampleColorContent::Unknown; + std::optional value; + std::optional byte; +}; + +std::optional MultisampleColorTargetOver(std::uint64_t address, std::size_t bytes); + std::uint64_t MultisampledColorBytes(const ColorTarget& target); std::size_t MultisampledDccBytes(const ColorTarget& target); VkClearColorValue MultisampledDccClear(const ColorTarget& target, std::span dcc, std::span cmask); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp index 067d251c70..f40fd3d483 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Pipeline.hpp @@ -157,7 +157,7 @@ bool ShadersWriteMemory(std::span shaders); bool DeclaresEarlyFragmentTests(const ShaderRecompiler::RecompileResult& program); void ValidateSampleMask(const Context& context, const State& state, std::span shaders); // Returns the color attachment locations the pixel shader writes. -std::set ValidateShaders(std::span shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing = false, bool imageInt64Atomics = false, bool geometryShader = false, bool sampleRateShading = false, bool bufferInt64Atomics = false, bool shaderClipDistance = false, bool shaderCullDistance = false, bool viewportIndexLayer = false, bool runtimeDescriptorArray = false, bool demoteToHelperInvocation = false, bool shaderTerminateInvocation = false, bool meshShader = true); +std::set ValidateShaders(std::span shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing = false, bool imageInt64Atomics = false, bool geometryShader = false, bool sampleRateShading = false, bool bufferInt64Atomics = false, bool shaderClipDistance = false, bool shaderCullDistance = false, bool viewportIndexLayer = false, bool runtimeDescriptorArray = false, bool demoteToHelperInvocation = false, bool shaderTerminateInvocation = false, bool meshShader = true, bool shaderResourceMinLod = false); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp index 37ab9d1daa..d7628355bb 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/PipelineSpecialization.hpp @@ -52,9 +52,14 @@ public: } } - explicit PipelineSpecialization(const ShaderRecompiler::RecompileResult& shader) { + explicit PipelineSpecialization(const ShaderRecompiler::RecompileResult& shader, std::uint32_t layerBase = 0u, std::uint32_t layerCount = 0xffffffffu) { + values = shader.specialization; + for (auto& constant : values) { + if (constant.id == ShaderRecompiler::PipelineSpecialization::MeshLayerBase) constant.value = layerBase; + if (constant.id == ShaderRecompiler::PipelineSpecialization::MeshLayerCount) constant.value = layerCount; + } Validate(shader); - describe(shader.specialization); + describe(values); } PipelineSpecialization(const ShaderRecompiler::RecompileResult& shader, std::span replaced) : values(shader.specialization) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp index 66216c459b..3c8d324c0d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp @@ -28,6 +28,7 @@ namespace AgcDriver::Graphics { class Buffer; +class DeviceBuffer; // Accumulates GPU work across guest commands so the CPU does not wait for each one. Dispatches and the // copies that feed them record into one open batch; Submit sends it to the queue without waiting and @@ -64,6 +65,23 @@ public: // destination access mask. void MarkCovered(VkAccessFlags access); void MarkShaderReadsCovered() { MarkCovered(VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); } + static bool DispatchOverlapEnabled(); + static void SetDispatchOrder(std::uint32_t queueKey, bool unordered); + static std::uint32_t TakeDispatchOrder(bool& unordered); + static constexpr std::uint64_t RunAccessWritten = 1, RunAccessImage = 2; + using RunAccess = std::array; + static std::uint32_t PeekUnorderedDispatch(); + VkCommandBuffer OverlapCommands(std::uint32_t queueKey, std::span owners, std::span accesses, VkAccessFlags* coveredAccess, bool& overlapped); + void DeferDispatchBarrier(std::uint32_t queueKey, std::span owners, std::span accesses); + VkCommandBuffer RunCommands(std::uint32_t queueKey, const void* owner, std::span accesses, bool& inRun, VkAccessFlags* coveredAccess); + void OpenBatch() { ensureOpen(); } + bool RunOpen() const { return open != nullptr && open->deferredDispatchQueue != 0; } + void AfterRun(std::function work); + bool CloseRunOwing(const void* owner); + struct OverlapStatistics { + std::uint64_t deferred, overlapped, flushed; + }; + static OverlapStatistics OverlapCounts(); struct DrawPassStart { VkCommandBuffer commands = VK_NULL_HANDLE; bool continued = false; @@ -96,6 +114,9 @@ public: void Keep(std::shared_ptr object, std::size_t bytes = 0); void KeepThroughHold(std::shared_ptr object); void KeepBytes(const void* owner, std::size_t bytes); + enum class UploadScratch : std::uint8_t { Tiled = 0, Linear, Count }; + std::shared_ptr UploadScratchBuffer(UploadScratch kind, std::size_t bytes); + static constexpr VkPipelineStageFlags UploadScratchReuseStages = VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; std::size_t OpenKeptBytes() const { return open != nullptr ? open->keptBytes : 0; } void FlushDeferredReleases(); static constexpr std::size_t KeptBytesBudget = std::size_t{512} << 20u; @@ -153,7 +174,7 @@ public: // on the open batch, no snapshot: the only reader holds the mutex. `kind` names the reader for // the [recorder] hit counters. APS5_COPY_READ_TRACKING=0 notes nothing (ReadTracking() is then // false and the CPU copy falls back to Idle()). - enum class ReadKind : std::uint8_t { DispatchElement = 0, GpuCopy, AddressBased, Indirect, StorageUpload, CopySource, DrawInput, Count }; + enum class ReadKind : std::uint8_t { DispatchElement = 0, GpuCopy, AddressBased, Indirect, StorageUpload, CopySource, DrawInput, AddressBasedReadOnly, Count }; static bool ReadTracking(); void NotePendingRead(std::uint64_t address, std::size_t bytes, ReadKind kind); void NotePendingReads(std::span> ranges, ReadKind kind); @@ -186,6 +207,7 @@ public: 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; + std::uint64_t PendingLeaseSerial(std::uint64_t address, std::size_t bytes) const; // What SyncThrough(address, bytes) would wait for, for the [hooksync] attribution: the newest // batch whose noted range overlaps the access (the open batch counts first, with the serial it // will get), whether that batch's fence has already signaled (its GPU work is done, so the @@ -222,6 +244,7 @@ public: void CountSamples(); bool CountingSamples() const { return countingSamples; } bool RecordMeshArguments(VkCommandBuffer commands, VkDeviceAddress record, VkDeviceAddress arguments, std::span rules); + bool RecordChangedCopy(VkCommandBuffer commands, VkDeviceAddress result, VkDeviceAddress snapshot, VkDeviceAddress target, std::uint32_t dwords, VkDeviceAddress counter = 0); std::uint64_t SamplesTotal(); bool DumpSamples(VkDeviceAddress target, std::uint64_t address = 0); void NoteSampledDraw(VkCommandBuffer commands); @@ -372,6 +395,7 @@ public: // The function the flush hook calls, on the accessing thread (which may hold the GPU mutex), // when an access overlaps one of that thread's queued labels; it records them. Set once. static void SetQueuedLabelRecorder(void (*recorder)()); + static void FailNextSubmitsForTest(unsigned count); // Whether the lock-free pending-write snapshot (open, in-flight and finishing batches) overlaps // the range: false means no recorded work writes it, so a wait on it has nothing to submit. static bool SnapshotWriteOverlaps(std::uint64_t address, std::size_t bytes); @@ -573,6 +597,7 @@ private: std::vector> kept; std::size_t keptBytes = 0; std::unordered_set keptOwners; + std::array, static_cast(UploadScratch::Count)> uploadScratch; std::vector> completions; std::vector> writes; std::vector writeNotes; @@ -661,6 +686,10 @@ private: } run; // See Commands(): the accesses the last recorded command's trailing barrier covers. VkAccessFlags coveredAccess = 0; + std::uint32_t deferredDispatchQueue = 0; + std::vector deferredRun; + std::vector runAccess; + std::vector> afterRun; // The hazard tracker's accesses since its last simulated barrier (see NoteAccess). struct Tracker { struct Range { @@ -742,6 +771,9 @@ private: int meshArgumentState = 0; VkPipelineLayout meshArgumentLayout = VK_NULL_HANDLE; VkPipeline meshArgumentPipeline = VK_NULL_HANDLE; + int changedCopyState = 0; + VkPipelineLayout changedCopyLayout = VK_NULL_HANDLE; + VkPipeline changedCopyPipeline = VK_NULL_HANDLE; int sampleCounterState = 0; std::unique_ptr sampleCounter; VkPipelineLayout sampleLayout = VK_NULL_HANDLE; @@ -783,6 +815,9 @@ private: void recordBarrier(VkCommandBuffer commands, VkPipelineStageFlags sourceStage, VkPipelineStageFlags destinationStage, VkAccessFlags sourceAccess, VkAccessFlags destinationAccess) const; // Opens a batch when none is open, without closing a store run (Commands() does both). void ensureOpen(); + void flushDeferredDispatch(); + bool runConflict(std::span accesses) const; + bool joinsRun(std::uint32_t queueKey, std::span owners, std::span accesses) const; void dropStaleHoldKept(); // Records the run's pending store (vkCmdUpdateBuffer), if any. void flushPendingStore(); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ReferenceStorage.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ReferenceStorage.hpp new file mode 100644 index 0000000000..5acc836b1d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ReferenceStorage.hpp @@ -0,0 +1,46 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_REFERENCESTORAGE_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_REFERENCESTORAGE_HPP + +#include +#include +#include +#include +#include +#include + +namespace AgcDriver::Graphics { + +class ReferenceStorage { +public: + static constexpr std::size_t Slots = 8; + + explicit ReferenceStorage(std::size_t capacityBytes) noexcept; + ReferenceStorage(const ReferenceStorage&) = delete; + ReferenceStorage& operator=(const ReferenceStorage&) = delete; + + using Clock = std::chrono::steady_clock; + static constexpr std::chrono::milliseconds IdleLimit{2000}; + + std::vector Take(std::size_t bytes, Clock::time_point now = Clock::now()) noexcept; + void Return(std::vector&& storage, Clock::time_point now = Clock::now()) noexcept; + std::size_t ReleaseIdle(Clock::time_point now = Clock::now()) noexcept; + std::size_t IdleBytes() const noexcept; + std::size_t IdleCount() const noexcept; + + static ReferenceStorage& Shared() noexcept; + +private: + std::size_t releaseIdle(Clock::time_point now, std::array, Slots>& released) noexcept; + + mutable std::mutex mutex; + std::array, Slots> idle; + std::array returned{}; + std::array returnedAt{}; + std::uint64_t returns = 0; + std::size_t bytes = 0; + std::size_t capacityBytes; +}; + +} + +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp index 674cff0c47..6b0bf7a2cd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Resources.hpp @@ -12,6 +12,7 @@ public: ~Buffer(); Buffer(const Buffer&) = delete; Buffer& operator=(const Buffer&) = delete; + static std::shared_ptr WithinDeviceLocalHeadroom(const Context& context, std::size_t size, VkBufferUsageFlags usage); VkBuffer Handle() const; VkDeviceAddress DeviceAddress() const; // Host-visible buffers only: a device-local one (properties without HOST_VISIBLE, the staging @@ -21,6 +22,7 @@ public: void Invalidate(); private: + Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, bool headroom); void initializeAddress(VkBufferUsageFlags usage); void release() noexcept; Context context; @@ -91,6 +93,8 @@ private: VkImage image = VK_NULL_HANDLE; VkImageView view = VK_NULL_HANDLE; VkDeviceMemory memory = VK_NULL_HANDLE; + VkDeviceSize memoryBytes = 0; + std::uint32_t memoryType = 0; }; class CommandBatch { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp index 7212fb40c6..34dd9a2774 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Sampler.hpp @@ -68,10 +68,10 @@ private: std::uint64_t misses = 0; }; -void RequireFilterMinmax(const Context& context, VkFormat format, std::uint32_t samplerMask, std::span> samplers); -bool ForcesDegammaOn(std::uint32_t guestFormat, std::uint32_t samplerMask, std::span> samplers); -void RequireBorderSwizzle(std::uint32_t bcSwizzle, std::uint32_t samplerMask, std::span> samplers); -void RequireNonSeamlessCube(bool cube, std::uint32_t samplerMask, std::span> samplers); +void RequireFilterMinmax(const Context& context, VkFormat format, std::uint64_t samplerMask, std::span> samplers); +bool ForcesDegammaOn(std::uint32_t guestFormat, std::uint64_t samplerMask, std::span> samplers); +void RequireBorderSwizzle(std::uint32_t bcSwizzle, std::uint64_t samplerMask, std::span> samplers); +void RequireNonSeamlessCube(bool cube, std::uint64_t samplerMask, std::span> samplers); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 57e845667f..f503445efa 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -7,6 +7,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp" #include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "Recompiler.hpp" +#include "IntermediateRepresentation/IrMetadata/ResourcePlan.hpp" #include "prx/libSceAgcDriver/Graphics/include/Shaders.hpp" #include #include @@ -54,6 +55,7 @@ bool SampledTexturesShareEntry(std::span first, std::span words); +bool SpirvFaultReportOnly(std::span words, std::uint32_t set, std::uint32_t binding); bool ShaderStoresThroughAddresses(const ShaderRecompiler::SharedSpirv& spirv); void LastRecordIndices(std::span logged, std::span objects, std::vector& indices); @@ -236,6 +238,11 @@ public: bool NeedsCompletion() const { return (bda != nullptr && faultChecks) || guestMemory.HasCopiedWrites(); } // Whether a written buffer was copied (its results reach guest memory by the CPU write-back). bool HasCopiedWrites() const { return guestMemory.HasCopiedWrites(); } + bool HasGpuCopies() const { return guestMemory.HasGpuCopies(); } + bool HasHostGpuCopies() const { return guestMemory.HasHostGpuCopies(); } + const void* MemoryOwner() const { return &guestMemory; } + void CopyBacksAfterRun(std::shared_ptr self) { afterRunSelf = std::move(self); } + void RunAccesses(std::vector>& out) const; bool HoldsLease() const { return guestMemory.HoldsLease(); } bool BdaReadOnly() const { return usesBda && bdaReadOnly; } bool UsesAddressTable() const { return usesBda; } @@ -499,6 +506,7 @@ private: bool depthCompare = false; VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D; bool firstSample = false; + bool layerRows = false; std::shared_ptr texture; }; PreviousSampled previousSampled; @@ -508,7 +516,7 @@ private: // lock and the cache still holds it; null sends the element to cachedTexture. std::shared_ptr fastTexture(const ImageRecord& record); void resolveImageBinding(const ShaderRecompiler::DescriptorBinding& binding, Binding& item, std::span> shaderSamplers); - void applyAnisoOverride(); + void applySamplerVariants(); void forgetDeferredInputs(); void release() noexcept; void prepareAddressBindings(std::span shaders, std::span snapshots); @@ -575,11 +583,13 @@ private: Context context; std::vector layoutKey; GuestBufferMemory guestMemory; + std::shared_ptr afterRunSelf; std::size_t maxDrawSnapshotBytes = MaxDrawSnapshotBytes(); std::unique_ptr bda; std::vector srtGuardFaults; bool usesBda = false; bool bdaReadOnly = true; + bool faultReportOnly = true; std::vector multisampleSources; bool usesFaultBuffer = false; bool faultChecks = false; @@ -612,9 +622,16 @@ private: std::shared_ptr keptTable(std::size_t set, std::shared_ptr>>& held) noexcept; std::uint32_t setOf(const Binding& binding) const; std::vector allocations; - std::vector descriptorBuffers; + mutable std::vector descriptorBuffers; std::vector descriptorImages; std::vector descriptorWrites; + struct FaultDescriptor { + std::size_t write; + std::size_t buffer; + std::uint32_t element; + }; + std::vector faultDescriptors; + void prepareFaultSlot() const; mutable std::vector drawPoolSizes; // Guest buffer elements bound read-only: each use of this object skips that many pending-write // notes (counted in MarkGpuWrites for the [buffers] line). @@ -645,11 +662,13 @@ private: bool integerBorder = false; bool singleLevelImage = false; bool mipmappedImage = false; + std::optional pinnedLod; }; std::vector samplerSources; bool reusable = false; std::vector directRegions; std::vector validatedTextures; + std::vector registeredImageViews; // The pending registry's serial at the last Revalidate that proved this object, taken before // its checks and kept only when unchanged after them (0: none): while it is still the serial, // no image other than the object's own sources has results pending over its surfaces and diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp index 2ddac45e80..e2f279e8e4 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Shaders.hpp @@ -2,6 +2,7 @@ #define CORE_LIBS_PRX_LIBSCEAGCDRIVER_GRAPHICS_INCLUDE_SHADERS_HPP #include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include "IntermediateRepresentation/IrMetadata/ResourcePlan.hpp" #include "Recompiler.hpp" #include #include @@ -37,6 +38,7 @@ struct CompiledShader { ShaderRecompiler::ShaderStage stage; const ShaderRecompiler::RecompileResult* program; std::uint32_t pushConstantOffset; + const std::vector* registeredImageViews = nullptr; }; inline VkShaderStageFlagBits VulkanStage(ShaderRecompiler::ShaderStage stage) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp index 7e403c1dd8..5a4a1f20e9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/State.hpp @@ -146,6 +146,8 @@ struct State { std::uint32_t reversedBlend = 0; VkExtent2D renderExtent; std::uint32_t renderLayers = 1; + std::uint32_t meshLayerBase = 0; + std::uint32_t meshLayerCount = 0xffffffffu; std::uint32_t layerExports = 0; VkPrimitiveTopology topology; bool primitiveRestart = false; @@ -256,6 +258,12 @@ struct DepthCopyPass { std::optional DecodeDepthCopyPass(const QueueState& queue); std::string DepthMaintenanceRejection(const QueueState& queue); bool DepthResummarizeDrawsNothing(const QueueState& queue); +struct DepthRenderControl { + std::uint64_t address; + std::uint32_t samples; + std::uint32_t control; +}; +std::optional DecodeDepthRenderControl(const QueueState& queue); // The message DecodeState (or the pixel stage decode after it) would throw for the register rules // this precheck covers, evaluated without exceptions before the draw is decoded; empty when they // pass (DecodeState still checks everything). A register a rule needs that is absent is no verdict. diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp index 65f468d8e9..72581ec1ca 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp @@ -60,7 +60,7 @@ struct OwnedImage { class Texture { public: - Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span snapshot, bool depthCompare = false, const VkClearColorValue* clear = nullptr); + Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span snapshot, bool depthCompare = false, bool layerRows = false, const VkClearColorValue* clear = nullptr); Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components); // A view of a storage image's own VkImage: the sampled texture follows the image's content, so a // compute pass writing it and the next pass sampling it share one image and copy nothing. @@ -295,15 +295,20 @@ 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, bool waited = false); - static void FlushAllPending(const char* reason); - static bool FlushAllPendingUntil(const char* reason, const std::function& stop); + 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, VkDevice device = VK_NULL_HANDLE); + static void FlushAllPending(const char* reason, VkDevice device = VK_NULL_HANDLE); + static bool FlushAllPendingUntil(const char* reason, const std::function& stop, VkDevice device = VK_NULL_HANDLE); 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 bool UnwatchHookLocksGpu(std::span> ranges); + static void SetPublishHookForTest(void (*hook)()); static constexpr std::uint64_t SmallStoreBytes = 64 * 1024; + static constexpr std::uint64_t OccasionalStoreBytes = 1024 * 1024; + static constexpr std::uint64_t OccasionalWriteFlips = 8; static std::size_t StoreSmallPending(const char* reason); - static void StoreReadbackBeforeLabel(const char* reason); + static void StoreReadbackBeforeLabel(const char* reason, VkDevice device = VK_NULL_HANDLE); static std::size_t StoreDrawnLinear(const char* reason); + static void NoteFlip(); static std::size_t StoreDrawnLinearTouching(const char* reason, const std::function& touches, bool all); static bool AnyPending(); static void FlushDumpSelected(const char* reason); @@ -469,6 +474,7 @@ public: static std::uint64_t HandedOverSuperseded(); static void SetClearHandOver(bool enabled); static std::uint64_t UntrackedCompares(); + static std::uint64_t OccasionalLabelStores(); std::uint64_t GuestBytes() const; std::uint64_t BudgetBytes() const; std::size_t OriginalSnapshotBytes() const { return original.capacity(); } @@ -484,7 +490,26 @@ private: void upload(const std::vector* layers = nullptr); void clearImage(VkCommandBuffer commands, VkClearColorValue value, const VkImageSubresourceRange& range); std::vector cpuUploadLayers(const std::vector& units) const; - void captureGuestBytes(const std::vector* layers); + void captureGuestBytes(const std::vector* layers, bool keepWritten = false); + bool keepWrittenSince(std::uint32_t unit); + enum class BaselineMiss : std::uint8_t { + None, + LeftWatchWhilePending, + SaveInFlight, + SavedBytesDropped, + NoUploadCopy, + UploadCopyDiffers, + NoUnwatchHash, + UnwatchUnproven, + UnwatchSawCpuWrites, + UnwatchWhileSaved, + HashDiffers, + }; + static const char* BaselineMissText(BaselineMiss miss); + BaselineMiss baselineMiss(std::uint32_t unit) const; + std::array unitContentHash(std::uint32_t unit) const; + void hashBeforeUnwatch(std::uint64_t address, std::size_t bytes, bool proven); + void forgetUnwatchHash(std::uint32_t first, std::uint32_t count); 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; // Stores the pending tracked layers overlapping [address, address + bytes) to guest memory; in @@ -494,10 +519,14 @@ private: // APS5_BLOCK_WRITEBACK_EACH=1 stores the touched units only). void writeBack(std::uint64_t address, std::size_t bytes); void writeBackLayers(const std::vector& layers); + void releaseReadTraps(bool sharingPages); + void storeTrappedOverlaps(const char* reason); bool unchangedSinceBaseline(std::uint64_t from, std::uint64_t to) const; + bool cpuLeftUnchanged(std::uint32_t layer, std::uint64_t from, std::uint64_t to, bool edge) const; void saveBeforeUnwatch(); void forgetKeptAtUnwatch(std::uint32_t unit); bool keptAtUnwatchHolds(std::uint64_t from, std::uint32_t unit) const; + bool keptForCpu(std::uint32_t unit) const; // Drops the saved guest bytes of the units over [address, address + bytes) (another image's fill // or copy supersedes them there); the other units' bytes stay valid. void forgetSavedOver(std::uint64_t address, std::size_t bytes); @@ -662,10 +691,12 @@ private: // Whether the image is live (not released) and overlaps the fill: the test that gives ClassifyFill // its overlapping images, and that NoteKeysFill and ClearByKeysFill use to bound the key match. bool overlapsLive(std::uint64_t address, std::size_t bytes) const; + bool hashedAtUnwatch(std::uint64_t address, std::size_t bytes) const; // Whether a fill of [address, address + bytes) is a fill of this image's DCC keys, `overlapped` // saying whether any live image overlaps the fill (see Texture.cpp). bool keysFillMatches(std::uint64_t address, std::size_t bytes, bool overlapped) const; bool pendingUnitInside(std::uint64_t address, std::size_t bytes) const; + void appendStoredLayers(std::uint64_t address, std::size_t bytes, std::vector>& ranges) const; VkImageView createView(std::uint32_t mip, bool firstLayer, VkFormat format) const; bool singlePass(); VkImageView elementLayerView(std::uint32_t level, std::uint32_t layer); @@ -690,6 +721,14 @@ private: mutable std::array comparedRanges{}; mutable std::uint32_t nextComparedRange = 0; std::vector generationBaseline; + struct UnwatchHash { + std::array hash{}; + std::uint64_t generation = 0; + BaselineMiss skipped = BaselineMiss::None; + std::uint64_t skippedGeneration = 0; + }; + std::vector unwatchHash; + std::vector pendingMiss; // 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; @@ -714,6 +753,7 @@ private: std::uint32_t trackedLayers = 1; std::uint64_t trackedLayerBytes = 0; std::vector layerGeneration; + std::vector layerPendingSince; std::vector completionKept; UnitFlags layerPending; std::vector layerBehind; @@ -756,11 +796,14 @@ private: // Results are on the GPU only (guarded by the pending-write registry lock). bool dirty = false; bool drawnListed = false; + mutable std::atomic readTrapped{false}; bool drawnWhole = false; bool drawingTarget = false; bool pendingFromOthers = false; std::vector drawnRects; std::vector storeNotes; + std::uint64_t writtenFlip = 0; + std::atomic writtenOccasionally{false}; 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 93fa96c5eb..0914660be1 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp @@ -92,6 +92,8 @@ namespace AgcDriver::Graphics { VkShaderModule imageModule = VK_NULL_HANDLE; VkShaderModule repackModule = VK_NULL_HANDLE; VkPipeline repackPipeline = VK_NULL_HANDLE; + VkShaderModule imageWordModule = VK_NULL_HANDLE; + VkShaderModule wordModule = VK_NULL_HANDLE; DescriptorRing imageDescriptorSets; VkDescriptorSetLayout readbackDescriptorLayout = VK_NULL_HANDLE; VkPipelineLayout readbackPipelineLayout = VK_NULL_HANDLE; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp index 75a94a7d15..78a3bad270 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp @@ -26,7 +26,7 @@ struct TileMipLayout { std::uint32_t gcnSplitStride = 0; }; -std::vector ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount); +std::vector ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount, std::uint32_t linearPitch = 0u); std::uint64_t ComputeSurfaceSize(const std::vector& mips, std::uint32_t arrayLayers); // Mip chain of an uncompressed surface given its element size (color targets have no texture format). std::vector ComputeElementMipLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp index a6afbcd7d1..b559b75749 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp @@ -102,6 +102,7 @@ std::size_t PublishShadow(std::uint64_t address, std::size_t bytes, PublishScope // hook-free readers and a build's stage A. bool AnyShadowedOverlaps(std::uint64_t address, std::size_t bytes); bool AnyShadowedOverlaps(std::span> ranges); +std::vector> LiveShadowedRanges(std::uint64_t address, std::size_t bytes, PublishScope scope); // The sources of a storage upload's surface-relative runs: [begin, end) pieces of them, each read // from one buffer (a slab while the unit is fresh, else the import) at `offset` (byte x of the run diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp index cdb94e2f24..9157055443 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/VertexInput.hpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -128,14 +127,39 @@ inline std::array VertexDescriptorFlagsMessage(const ShaderRecompiler return message; } +inline bool QueryVertexBufferFormat(const Context& context, VkFormat format) { + VkFormatProperties properties{}; + context.formatProperties(context.physical, format, &properties); + return (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0; +} + +class VertexBufferFormats { +public: + bool Supported(const Context& context, VkFormat format) { + const auto index = static_cast(format); + if (index >= known.size()) return QueryVertexBufferFormat(context, format); + auto state = known[index].load(std::memory_order_relaxed); + if (state == 0) { + state = QueryVertexBufferFormat(context, format) ? 2 : 1; + known[index].store(state, std::memory_order_relaxed); + } + return state == 2; + } + +private: + std::array, VK_FORMAT_ASTC_12x12_SRGB_BLOCK + 1> known{}; +}; + 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; - std::set locations; + result.bindings.reserve(attributes.size()); + result.attributes.reserve(attributes.size()); for (const auto& attribute : attributes) { const auto& fields = attribute.resource.fields; const auto format = DecodeVertexFormat(attribute); - Require(attribute.location < context.limits.maxVertexInputAttributes && locations.insert(attribute.location).second, "invalid or duplicate vertex attribute location"); + const auto duplicate = std::any_of(result.attributes.begin(), result.attributes.end(), [&](const auto& described) { return described.location == attribute.location; }); + Require(attribute.location < context.limits.maxVertexInputAttributes && !duplicate, "invalid or duplicate vertex attribute location"); Require(attribute.fetchIndex <= 1, "unsupported vertex fetch index"); if ((fields[1] & 0x80000000u) != 0 || (fields[3] & 0x00800000u) != 0 || (fields[3] >> 30u) != 0) { Require(false, VertexDescriptorFlagsMessage(attribute).data()); @@ -146,9 +170,7 @@ inline VertexInputLayout BuildVertexInputLayout(const Context& context, std::spa Require(stride <= context.limits.maxVertexInputBindingStride, "vertex stride exceeds device limits"); Require(context.formatProperties != nullptr, "missing vertex format property query"); const auto supported = [&](VkFormat candidate) { - VkFormatProperties properties{}; - context.formatProperties(context.physical, candidate, &properties); - return (properties.bufferFeatures & VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT) != 0; + return context.vertexFormats != nullptr ? context.vertexFormats->Supported(context, candidate) : QueryVertexBufferFormat(context, candidate); }; auto host = format.format; auto hostSupported = supported(host); @@ -156,7 +178,7 @@ inline VertexInputLayout BuildVertexInputLayout(const Context& context, std::spa host = DecodeVertexFormat(attribute, components).format; hostSupported = supported(host); } - Require(hostSupported, "device does not support vertex format " + std::to_string(format.format)); + if (!hostSupported) Require(false, "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, host, 0}); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/StagingCopyBack.comp b/core/libs/prx/libSceAgcDriver/Graphics/shaders/StagingCopyBack.comp new file mode 100644 index 0000000000..a950cfef8d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/shaders/StagingCopyBack.comp @@ -0,0 +1,34 @@ +#version 450 +#extension GL_EXT_buffer_reference : require +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +layout(local_size_x = 64) in; +layout(buffer_reference, std430, buffer_reference_align = 4) buffer Words { uint words[]; }; +layout(push_constant) uniform Parameters { + Words result; + Words snapshot; + Words target; + Words counter; + uint count; + uint always; +} parameters; + +uint differingBytes(uint difference) { + uint folded = difference | (difference >> 1u); + folded |= folded >> 2u; + folded |= folded >> 4u; + return (folded & 0x01010101u) * 0xffu; +} + +void main() { + uint index = gl_GlobalInvocationID.x; + if (index >= parameters.count) return; + uint value = parameters.result.words[index]; + uint before = parameters.snapshot.words[index]; + uint held = parameters.target.words[index]; + uint changed = differingBytes(value ^ before); + uint kept = differingBytes(held ^ before); + uint stored = parameters.always != 0u ? 0xffffffffu : changed & ~kept; + if (stored != 0u) parameters.target.words[index] = (held & ~stored) | (value & stored); + if (uint64_t(parameters.counter) != 0ul && (changed & kept) != 0u) atomicAdd(parameters.counter.words[0], 1u); +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/shaders/TextureDetile.comp b/core/libs/prx/libSceAgcDriver/Graphics/shaders/TextureDetile.comp index 6ebceb5667..6c8e6b6a78 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/shaders/TextureDetile.comp +++ b/core/libs/prx/libSceAgcDriver/Graphics/shaders/TextureDetile.comp @@ -207,34 +207,27 @@ void copyElement(uint src, uint dst) { } #endif -void main() { - uvec2 p = gl_GlobalInvocationID.xy + uvec2(params.columnBegin, params.rowBegin); - if (p.x >= params.width || p.y >= params.height) { - return; - } - +bool locate(uvec2 p, out uint src, out uint dst, out uint ranged) { uint linearOffset = p.y * params.pitchBytes + p.x * ELEMENT_BYTES; if (TILE_FAMILY == 0u) { - // kLinear: guest and host layouts match; a straight copy at the - // same pitch, no swizzle. + ranged = linearOffset; if (linearOffset < params.rangeBegin || linearOffset >= params.rangeEnd) { - return; + return false; } #ifdef LINEAR_IMAGE - if (RETILE != 0u) { - loadElement(p, params.dstBase + linearOffset - params.tiledBase); - } else { - storeElement(params.srcBase + linearOffset - params.tiledBase, p); - } + src = params.srcBase + linearOffset - params.tiledBase; + dst = params.dstBase + linearOffset - params.tiledBase; #else if (RETILE != 0u) { - copyElement(params.srcBase + linearOffset - params.linearBase, params.dstBase + linearOffset - params.tiledBase); + src = params.srcBase + linearOffset - params.linearBase; + dst = params.dstBase + linearOffset - params.tiledBase; } else { - copyElement(params.srcBase + linearOffset - params.tiledBase, params.dstBase + linearOffset - params.linearBase); + src = params.srcBase + linearOffset - params.tiledBase; + dst = params.dstBase + linearOffset - params.linearBase; } #endif - return; + return true; } uvec2 swizzle = p; @@ -251,24 +244,106 @@ void main() { uint split = equationBit(SPLIT0, p) | (equationBit(SPLIT1, p) << 1) | (equationBit(SPLIT2, p) << 2) | (equationBit(SPLIT3, p) << 3); tiledOffset += split * params.splitStride; } + ranged = tiledOffset; if (tiledOffset < params.rangeBegin || tiledOffset >= params.rangeEnd) { - return; + return false; } tiledOffset -= params.tiledBase; linearOffset -= params.linearBase; #ifdef LINEAR_IMAGE - if (RETILE != 0u) { - loadElement(p, params.dstBase + tiledOffset); - } else { - storeElement(params.srcBase + tiledOffset, p); - } + src = params.srcBase + tiledOffset; + dst = params.dstBase + tiledOffset; #else if (RETILE != 0u) { - copyElement(params.srcBase + linearOffset, params.dstBase + tiledOffset); + src = params.srcBase + linearOffset; + dst = params.dstBase + tiledOffset; } else { - copyElement(params.srcBase + tiledOffset, params.dstBase + linearOffset); + src = params.srcBase + tiledOffset; + dst = params.dstBase + linearOffset; } +#endif + return true; +} + +void moveElement(uvec2 p) { + uint src; + uint dst; + uint ranged; + if (!locate(p, src, dst, ranged)) { + return; + } +#ifdef LINEAR_IMAGE + if (RETILE != 0u) { + loadElement(p, dst); + } else { + storeElement(src, p); + } +#else + copyElement(src, dst); #endif } +#ifdef WORD_GROUPS +shared uint groupWords[32]; +shared uint groupLanes[32]; +shared uint groupDestinations[64]; + +void main() { + uvec2 p = gl_GlobalInvocationID.xy + uvec2(params.columnBegin, params.rowBegin); + bool inside = p.x < params.width && p.y < params.height; + uint lane = gl_LocalInvocationID.y * gl_WorkGroupSize.x + gl_LocalInvocationID.x; + uint count = 4u / ELEMENT_BYTES; + uint slot = lane / count; + uint index = lane % count; + uint src = 0u; + uint dst = 0u; + uint ranged = 0u; + bool valid = inside && locate(p, src, dst, ranged); + if (index == 0u) { + groupWords[slot] = 0u; + groupLanes[slot] = 0u; + } + groupDestinations[lane] = dst; + barrier(); + if (valid) { + uint mask = ELEMENT_BYTES == 1u ? 0xffu : 0xffffu; +#ifdef LINEAR_IMAGE + uint value = imageLoad(linearImage, ivec2(p)).x & mask; +#else + uint value = (inputBuffer.data[src >> 2] >> ((src & 3u) * 8u)) & mask; +#endif + atomicOr(groupWords[slot], value << (index * ELEMENT_BYTES * 8u)); + atomicOr(groupLanes[slot], 1u << index); + } + barrier(); + uint first = groupDestinations[slot * count]; + bool whole = groupLanes[slot] == (1u << count) - 1u && (first & 3u) == 0u; + for (uint i = 1u; i < count; ++i) { + whole = whole && groupDestinations[slot * count + i] == first + i * ELEMENT_BYTES; + } + if (whole) { + if (index == 0u) { +#ifdef LINEAR_IMAGE + tiledBuffer.data[first >> 2] = groupWords[slot]; +#else + outputBuffer.data[first >> 2] = groupWords[slot]; +#endif + } + } else if (valid) { +#ifdef LINEAR_IMAGE + loadElement(p, dst); +#else + copyElement(src, dst); +#endif + } +} +#else +void main() { + uvec2 p = gl_GlobalInvocationID.xy + uvec2(params.columnBegin, params.rowBegin); + if (p.x >= params.width || p.y >= params.height) { + return; + } + moveElement(p); +} +#endif diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp index 3b0498e7b8..bfd803f262 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp @@ -4,6 +4,7 @@ #include "ImageTableAbi.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include +#include #include #include #include @@ -13,7 +14,12 @@ #include #include #include +#include #include +#include +#include +#include +#include namespace AgcDriver::Graphics { @@ -104,15 +110,207 @@ std::uint64_t hashRanges(const std::vector& ran } -BdaResources::BdaResources(const Context& context) { - static_assert(std::endian::native == std::endian::little); - Require(ShaderRecompiler::BdaAbi::FaultBufferBytes <= context.limits.maxStorageBufferRange, "BDA fault buffer exceeds storage buffer range limit"); - fault = std::make_unique(context, ShaderRecompiler::BdaAbi::FaultBufferBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); - std::memset(fault->Bytes().data(), 0, ShaderRecompiler::BdaAbi::WrittenListWord * sizeof(std::uint32_t)); - std::memset(fault->Bytes().data() + ShaderRecompiler::BdaAbi::SrtGuardWord * sizeof(std::uint32_t), 0, sizeof(std::uint32_t)); +struct BdaFaultChunk { + std::unique_ptr buffer; + std::vector free; + std::size_t slots = 0; + VkDeviceSize slotBytes = 0; +}; + +namespace { + +struct FaultArena { + std::vector> chunks; + std::unordered_set holders; + std::uint64_t finished = 0; + BdaResources::FaultSlotCounts counts; +}; + +struct FaultArenas { + std::mutex mutex; + std::unordered_map devices; +}; + +FaultArenas& faultArenas() { + static auto* arenas = new FaultArenas(); + return *arenas; } -BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memory) : BdaResources(context) { +bool sharedFaultSlots() { + static const bool shared = std::getenv("APS5_NO_SHARED_FAULT_SLOTS") == nullptr; + return shared; +} + +constexpr VkDeviceSize FaultChunkBytes = VkDeviceSize{512} * 1024; + +VkDeviceSize faultSlotBytes(bool reportOnly) { + return reportOnly ? sizeof(ShaderRecompiler::BdaAbi::Fault) : ShaderRecompiler::BdaAbi::FaultBufferBytes; +} + +VkDeviceSize faultStride(const Context& context, VkDeviceSize bytes) { + const auto alignment = std::max(context.limits.minStorageBufferOffsetAlignment, sizeof(std::uint32_t)); + return (bytes + alignment - 1) / alignment * alignment; +} + +bool faultSlotClean(std::span bytes) { + namespace Abi = ShaderRecompiler::BdaAbi; + if (bytes.size() < Abi::FaultBufferBytes) return std::all_of(bytes.begin(), bytes.end(), [](std::byte value) { return value == std::byte{0}; }); + std::array head{}; + std::memcpy(head.data(), bytes.data(), sizeof(head)); + std::uint32_t guard = 0; + std::memcpy(&guard, bytes.data() + Abi::SrtGuardWord * sizeof(std::uint32_t), sizeof(guard)); + return guard == 0 && std::all_of(head.begin(), head.end(), [](std::uint32_t word) { return word == 0; }); +} + +void clearFaultSlot(std::span bytes) { + namespace Abi = ShaderRecompiler::BdaAbi; + if (bytes.size() < Abi::FaultBufferBytes) { + std::memset(bytes.data(), 0, bytes.size()); + return; + } + std::memset(bytes.data(), 0, Abi::WrittenListWord * sizeof(std::uint32_t)); + std::memset(bytes.data() + Abi::SrtGuardWord * sizeof(std::uint32_t), 0, sizeof(std::uint32_t)); +} + +void dropChunk(FaultArena& arena, const std::shared_ptr& chunk, std::vector>& dropped) { + const auto found = std::find(arena.chunks.begin(), arena.chunks.end(), chunk); + if (found == arena.chunks.end()) return; + arena.counts.slots -= (*found)->slots; + --arena.counts.chunks; + dropped.push_back(std::move(*found)); + arena.chunks.erase(found); +} + +} + +BdaResources::BdaResources(const Context& context, bool reportOnly) : reportOnly(reportOnly) { + static_assert(std::endian::native == std::endian::little); + Require(ShaderRecompiler::BdaAbi::FaultBufferBytes <= context.limits.maxStorageBufferRange, "BDA fault buffer exceeds storage buffer range limit"); + TakeFaultSlot(context); +} + +BdaResources::~BdaResources() { + std::vector> dropped; + auto& arenas = faultArenas(); + std::lock_guard lock(arenas.mutex); + if (faultChunk == nullptr) return; + const auto bytes = faultBytes(); + if (!faultSlotClean(bytes)) clearFaultSlot(bytes); + const auto chunk = faultChunk; + dropped.push_back(std::move(faultChunk)); + const auto found = arenas.devices.find(faultDevice); + if (found == arenas.devices.end()) return; + auto& arena = found->second; + arena.holders.erase(this); + --arena.counts.held; + if (!sharedFaultSlots()) { + arena.counts.slots -= chunk->slots; + --arena.counts.chunks; + } else { + chunk->free.push_back(faultOffset); + if (chunk->free.size() == chunk->slots) dropChunk(arena, chunk, dropped); + } + if (arena.holders.empty()) { + while (!arena.chunks.empty()) dropChunk(arena, arena.chunks.back(), dropped); + arenas.devices.erase(found); + } +} + +std::span BdaResources::faultBytes() const { + return faultChunk->buffer->Bytes().subspan(static_cast(faultOffset), static_cast(faultChunk->slotBytes)); +} + +void BdaResources::NoteUse(std::uint64_t serial) { + auto& arenas = faultArenas(); + std::lock_guard lock(arenas.mutex); + lastUse = std::max(lastUse, serial); +} + +void BdaResources::NoteFinished(VkDevice device, std::uint64_t serial) { + auto& arenas = faultArenas(); + std::lock_guard lock(arenas.mutex); + const auto found = arenas.devices.find(device); + if (found != arenas.devices.end()) found->second.finished = serial; +} + +BdaResources::FaultSlotCounts BdaResources::FaultSlots(VkDevice device) { + auto& arenas = faultArenas(); + std::lock_guard lock(arenas.mutex); + const auto found = arenas.devices.find(device); + return found != arenas.devices.end() ? found->second.counts : FaultSlotCounts{}; +} + +bool BdaResources::TakeFaultSlot(const Context& context) { + if (faultChunk != nullptr) return false; + std::vector> dropped; + auto& arenas = faultArenas(); + std::lock_guard lock(arenas.mutex); + auto& arena = arenas.devices[context.device]; + const bool retake = faultDevice != VK_NULL_HANDLE; + if (!retake) lastUse = arena.finished + 1; + faultDevice = context.device; + const bool shared = sharedFaultSlots(); + const auto slotBytes = shared ? faultSlotBytes(reportOnly) : VkDeviceSize{ShaderRecompiler::BdaAbi::FaultBufferBytes}; + const auto withFree = [&] { return std::find_if(arena.chunks.rbegin(), arena.chunks.rend(), [&](const std::shared_ptr& chunk) { return chunk->slotBytes == slotBytes && !chunk->free.empty(); }); }; + auto source = shared ? withFree() : arena.chunks.rend(); + bool reclaimed = false; + if (shared && source == arena.chunks.rend() && GuestMemory::GpuMutex().HeldByThisThread()) { + std::vector idle; + for (auto* holder : arena.holders) { + if (holder->faultChunk->slotBytes == slotBytes && holder->lastUse <= arena.finished && faultSlotClean(holder->faultBytes())) idle.push_back(holder); + } + if (!idle.empty()) { + const auto reclaim = (idle.size() + 1) / 2; + std::nth_element(idle.begin(), idle.begin() + static_cast(reclaim - 1), idle.end(), [](const BdaResources* left, const BdaResources* right) { return left->lastUse < right->lastUse; }); + for (std::size_t index = 0; index < reclaim; ++index) { + auto& holder = *idle[index]; + holder.faultChunk->free.push_back(holder.faultOffset); + holder.faultChunk.reset(); + arena.holders.erase(&holder); + --arena.counts.held; + ++arena.counts.reclaimed; + } + reclaimed = true; + source = withFree(); + } + } + std::shared_ptr chunk; + if (source != arena.chunks.rend()) { + chunk = *source; + } else { + const auto stride = shared ? faultStride(context, slotBytes) : slotBytes; + const auto slots = shared ? std::max(1, FaultChunkBytes / stride) : 1; + chunk = std::make_shared(); + chunk->buffer = std::make_unique(context, static_cast(stride * slots), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + std::memset(chunk->buffer->Bytes().data(), 0, chunk->buffer->Bytes().size()); + chunk->slots = static_cast(slots); + chunk->slotBytes = slotBytes; + for (auto slot = slots; slot-- > 0;) chunk->free.push_back(slot * stride); + if (shared) arena.chunks.push_back(chunk); + arena.counts.slots += chunk->slots; + ++arena.counts.chunks; + } + faultOffset = chunk->free.back(); + chunk->free.pop_back(); + faultChunk = chunk; + arena.holders.insert(this); + ++arena.counts.held; + if (retake) ++arena.counts.retaken; + bool spare = false; + for (auto it = arena.chunks.begin(); reclaimed && it != arena.chunks.end();) { + if ((*it)->free.size() != (*it)->slots || !std::exchange(spare, true)) { + ++it; + continue; + } + arena.counts.slots -= (*it)->slots; + --arena.counts.chunks; + dropped.push_back(std::move(*it)); + it = arena.chunks.erase(it); + } + return true; +} + +BdaResources::BdaResources(const Context& context, const GuestBufferMemory& memory, bool reportOnly) : BdaResources(context, reportOnly) { const auto cached = memory.CachedAddressTable(); std::vector built; if (!cached.has_value()) built = memory.AddressRanges(); @@ -228,7 +426,7 @@ VkDescriptorBufferInfo BdaResources::Table() const { } VkDescriptorBufferInfo BdaResources::Fault() const { - return {fault->Handle(), 0, ShaderRecompiler::BdaAbi::FaultBufferBytes}; + return {faultChunk->buffer->Handle(), faultOffset, faultChunk->slotBytes}; } namespace { @@ -240,12 +438,13 @@ bool LoopGuardTripped() { } void BdaResources::CheckFault() const { - markWrittenPages(); + if (faultChunk == nullptr) return; + if (!reportOnly) markWrittenPages(); ShaderRecompiler::BdaAbi::Fault report{}; - std::memcpy(&report, fault->Bytes().data(), sizeof(report)); + std::memcpy(&report, faultBytes().data(), sizeof(report)); if (report.state == ShaderRecompiler::BdaAbi::FaultState::Empty) { Require(static_cast(report.reason) == 0 && report.address == 0 && report.bytes == 0 && report.stage == 0 && report.instruction == 0 && report.otherFaults == 0, "BDA fault record has data without publication"); - checkSrtGuard(); + if (!reportOnly) checkSrtGuard(); return; } Require(report.state == ShaderRecompiler::BdaAbi::FaultState::Ready && report.otherFaults <= 1, "incomplete or invalid BDA fault record"); @@ -254,7 +453,7 @@ void BdaResources::CheckFault() const { // APS5_LOOP_GUARD: the shader left a loop that ran past the guard; the dispatch result is kept. loopGuardTripped.store(true, std::memory_order_relaxed); std::fprintf(stderr, "[gpu] loop guard: the loop exit at pc 0x%x of shader 0x%llx ran past %u evaluations\n", report.instruction, static_cast(report.address), report.bytes); - std::memset(fault->Bytes().data(), 0, fault->Bytes().size()); + std::memset(faultBytes().data(), 0, faultBytes().size()); return; } if (report.reason == ShaderRecompiler::BdaAbi::FaultReason::ImageTable) { @@ -321,7 +520,7 @@ void BdaResources::SetSrtGuards(std::vector guards) { void BdaResources::checkSrtGuard() const { namespace Abi = ShaderRecompiler::BdaAbi; std::uint32_t guard = 0; - std::memcpy(&guard, fault->Bytes().data() + Abi::SrtGuardWord * sizeof(std::uint32_t), sizeof(guard)); + std::memcpy(&guard, faultBytes().data() + Abi::SrtGuardWord * sizeof(std::uint32_t), sizeof(guard)); if (guard == 0) return; const auto stage = guard >> Abi::SrtGuardStageShift; const auto record = guard & ((1u << Abi::SrtGuardStageShift) - 1u); @@ -337,7 +536,7 @@ void BdaResources::checkSrtGuard() const { void BdaResources::markWrittenPages() const { namespace Abi = ShaderRecompiler::BdaAbi; - auto* words = reinterpret_cast(fault->Bytes().data()); + auto* words = reinterpret_cast(faultBytes().data()); Require(words[Abi::WrittenOverflowWord] == 0, "more than " + std::to_string(Abi::WrittenPageSlots) + " chunks of " + std::to_string(Abi::WrittenChunkPages) + " pages stored to through the BDA table in one use are not implemented"); const auto count = words[Abi::WrittenCountWord]; if (count == 0) return; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp index 60a6fe7068..2526b73538 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp @@ -315,13 +315,18 @@ LandedKeys KeysAfterPendingWrites(const Recorder& recorder, std::uint64_t begin, continue; } covered = true; + if (write.value < 0 && write.word >= 0) { + const auto first = std::max(from, write.begin); + const auto last = std::min(std::min(to, write.end), first + 4u); + for (auto byte = first; byte < last; ++byte) see(static_cast((static_cast(write.word) >> (8u * ((byte - write.begin) % 4u))) & 0xffu)); + } if (from < write.begin) rest.emplace_back(from, write.begin); if (write.end < to) rest.emplace_back(write.end, to); } unwritten = std::move(rest); if (!covered) continue; if (write.value >= 0) see(write.value); - else landed.unknown = true; + else if (write.word < 0) landed.unknown = true; } for (const auto& [from, to] : unwritten) { if (landed.mixed) break; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp index 77566d0d96..339ac46be2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp @@ -2,16 +2,20 @@ #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/PrimedSharedMutex.hpp" #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" +#include "prx/libSceAgcDriver/Graphics/include/FirstSampleCopy.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.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/TextureTiling.hpp" #include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" #include "RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp" #include #include @@ -82,7 +86,7 @@ struct DepthGuard { }; std::shared_mutex& depthGuardMutex() { - static std::shared_mutex mutex; + static PrimedSharedMutex mutex; return mutex; } @@ -97,14 +101,43 @@ std::atomic& anyDepthGuard() { } struct DepthSamplingState { - std::array initial{}; + std::vector initial; + std::optional initialDepth; + std::optional initialStencil; std::string refusal; + std::optional aliasStencil; + std::optional unprovenStencil; void Refuse(const char* reason) { if (refusal.empty()) refusal = reason; } }; +constexpr const char* stencilAliasRefusal = "native multisample stencil plane aliases a multisampled colour target whose samples are only on the host"; +constexpr const char* depthAliasRefusal = "native multisample depth or HTILE aliases a multisampled colour target whose samples are only on the host"; + +std::optional aliasedStencil(const MultisampleColorAlias& alias, const GuestMemory::UnchangedQuery& plane, std::uint32_t samples) { + const bool r8 = alias.format == VK_FORMAT_R8_UNORM || alias.format == VK_FORMAT_R8_UINT; + const bool known = alias.newest == MultisampleColorContent::Fill || alias.newest == MultisampleColorContent::FastClear || alias.newest == MultisampleColorContent::Clear; + if (!r8 || !known || alias.samples != samples || alias.address != plane.address || alias.bytes != plane.bytes) return std::nullopt; + return alias.byte; +} + +void reportAlias(const MultisampleColorAlias& alias, const GuestMemory::UnchangedQuery& range, const char* plane) { + static std::atomic reports{0}; + if (reports.fetch_add(1) >= 8) return; + char newest[96]; + const char* operation = alias.newest == MultisampleColorContent::Fill ? "fill" : alias.newest == MultisampleColorContent::FastClear ? "CMASK fast clear" : alias.newest == MultisampleColorContent::Clear ? "clear" : alias.newest == MultisampleColorContent::Drawn ? "drawn" : "unknown"; + if (alias.byte) std::snprintf(newest, sizeof(newest), "%s 0x%02x", operation, *alias.byte); + else if (alias.value) std::snprintf(newest, sizeof(newest), "%s 0x%08x 0x%08x 0x%08x 0x%08x", operation, alias.value->uint32[0], alias.value->uint32[1], alias.value->uint32[2], alias.value->uint32[3]); + else std::snprintf(newest, sizeof(newest), "%s", operation); + std::fprintf(stderr, "[gpu] native multisample %s 0x%llx+0x%zx aliases multisampled colour target 0x%llx+0x%llx (vk format %d, %u samples), newest %s\n", plane, static_cast(range.address), range.bytes, static_cast(alias.address), static_cast(alias.bytes), static_cast(alias.format), alias.samples, newest); +} + +bool htileProvesStencilClear(std::uint32_t word) { + return ((word >> 8u) & 3u) == 0u; +} + bool htileProvesClear(std::uint32_t word, bool depthOnly, float clearDepth) { if ((word & 15u) == 15u) return false; const auto depth = depthOnly ? word : word & 0xfffff00fu; @@ -113,6 +146,78 @@ bool htileProvesClear(std::uint32_t word, bool depthOnly, float clearDepth) { return false; } +std::uint64_t fillNoteAfter(const Recorder& recorder, const GuestMemory::UnchangedQuery& range, std::uint64_t floor, std::uint32_t word) { + std::uint64_t found = 0u; + for (const auto& write : recorder.PendingWritesOver(range.address, range.bytes)) { + if (write.note <= floor) continue; + if (found != 0u || write.begin > range.address || write.end < range.address + range.bytes || write.word != static_cast(word)) return 0u; + found = write.note; + } + return found; +} + +bool uniformPlaneTarget(const DepthTarget& target) { + return target.format == VK_FORMAT_D32_SFLOAT_S8_UINT && target.stencilAddress != 0u && target.htileAddress == 0u; +} + +template +std::optional uniformPlaneValue(std::uint64_t address, std::size_t bytes) { + std::array chunk{}; + std::optional value; + for (std::size_t offset = 0u; offset < bytes;) { + const auto count = std::min(chunk.size(), (bytes - offset) / sizeof(Element)); + const auto elements = std::span(chunk).first(count); + GuestMemory::Read(address + offset, std::as_writable_bytes(elements)); + if (!value) value = elements.front(); + if (std::ranges::any_of(elements, [&](Element element) { return element != *value; })) return std::nullopt; + offset += count * sizeof(Element); + } + return value; +} + +DepthSamplingState prepareUniformPlaneSampling(const Context& context, const DepthTarget& target, DepthSamplingState state) { + const auto refuse = [&](const char* reason) { + state.refusal = reason; + return state; + }; + if (target.depthSwizzle != 24u || target.firstSlice != 0u || target.lastSlice != 0u) return refuse("native multisample depth arrays or non-Z_X layout are not implemented"); + if ((target.pipeBankXor & ~0xcf00u) != 0u) return refuse("unsupported native multisample depth XOR"); + if (target.address == 0u || (target.address & 65535u) != 0u || (target.stencilAddress & 65535u) != 0u) return refuse("native multisample depth base is not aligned"); + if (target.htileStencil) return refuse("native multisample stencil import is not implemented"); + state.initial = {{target.address, static_cast(MultisampledDepthBytes_nid_no_patch(target.extent, 4u, target.samples)), 0u}, {target.stencilAddress, static_cast(MultisampledDepthBytes_nid_no_patch(target.extent, 1u, target.samples)), 0u}}; + for (const auto& range : state.initial) { + if (range.address > std::numeric_limits::max() - range.bytes) return refuse("native depth import range overflows"); + if (!GuestMemory::Accessible(reinterpret_cast(range.address), range.bytes) || !GuestMemory::Watched(range.address, range.bytes)) return refuse("native multisample depth import requires watched accessible guest memory"); + } + const auto& depth = state.initial[0]; + const auto& stencil = state.initial[1]; + if (depth.address + depth.bytes > stencil.address && stencil.address + stencil.bytes > depth.address) return refuse("native depth and stencil initial ranges overlap"); + for (std::size_t index = 0u; index < state.initial.size(); ++index) { + const auto alias = MultisampleColorTargetOver(state.initial[index].address, state.initial[index].bytes); + if (!alias) continue; + reportAlias(*alias, state.initial[index], index == 1u ? "stencil plane" : "depth"); + return refuse(index == 1u ? stencilAliasRefusal : depthAliasRefusal); + } + for (auto& range : state.initial) { + if (auto* recorder = RecorderFor(context)) recorder->SyncThrough(range.address, range.bytes); + StorageTexture::FlushPending(range.address, range.bytes); + GuestMemory::FlushGpuWrites(range.address, range.bytes); + if (!GuestMemory::Accessible(reinterpret_cast(range.address), range.bytes) || !GuestMemory::Watched(range.address, range.bytes)) return refuse("native multisample depth import requires watched accessible guest memory"); + range.generation = GuestMemory::CollectWritesUncached(range.address, range.bytes); + if (range.generation == 0u) return refuse("native multisample depth import requires tracked generations"); + } + const auto depthWord = uniformPlaneValue(depth.address, depth.bytes); + const auto depthValue = depthWord ? std::bit_cast(*depthWord) : 0.0f; + if (!depthWord || !(depthValue >= 0.0f && depthValue <= 1.0f)) return refuse("native multisample depth plane without HTILE does not hold one depth value in [0, 1]"); + const auto stencilByte = uniformPlaneValue(stencil.address, stencil.bytes); + if (!stencilByte) return refuse("native multisample stencil plane without HTILE does not hold one stencil value"); + for (const auto& range : state.initial) GuestMemory::CollectWritesUncached(range.address, range.bytes); + if (!GuestMemory::UnchangedSinceAll(state.initial)) return refuse("guest writes raced native multisample depth import"); + state.initialDepth = depthValue; + state.initialStencil = *stencilByte; + return state; +} + DepthSamplingState prepareDepthSampling(const Context& context, const DepthTarget& target) { DepthSamplingState state; const auto refuse = [&](const char* reason) { @@ -120,21 +225,47 @@ DepthSamplingState prepareDepthSampling(const Context& context, const DepthTarge return state; }; if (target.samples != 2u && target.samples != 4u && target.samples != 8u) return refuse("unsupported native depth sample count"); - if (target.format != VK_FORMAT_D16_UNORM && target.format != VK_FORMAT_D32_SFLOAT) return refuse("native multisample stencil or depth format import is not implemented"); + if (uniformPlaneTarget(target)) return prepareUniformPlaneSampling(context, target, std::move(state)); + const bool stencil = target.format == VK_FORMAT_D32_SFLOAT_S8_UINT; + const bool stencilPlane = stencil && !target.htileStencil && target.htileDepthOnly; + if (target.format != VK_FORMAT_D16_UNORM && target.format != VK_FORMAT_D32_SFLOAT && !stencil) return refuse("native multisample stencil or depth format import is not implemented"); if (target.depthSwizzle != 24u || target.firstSlice != 0u || target.lastSlice != 0u) return refuse("native multisample depth arrays or non-Z_X layout are not implemented"); if ((target.pipeBankXor & ~0xcf00u) != 0u) return refuse("unsupported native multisample depth XOR"); if (target.address == 0u || (target.address & 65535u) != 0u) return refuse("native multisample depth base is not aligned"); - if (target.stencilAddress != 0u || target.htileStencil) return refuse("native multisample stencil import is not implemented"); - if (target.htileAddress == 0u || (target.htileAddress & 255u) != 0u || !target.htilePipeAligned || target.htileVrsEncoding != 0u) return refuse("native multisample depth initial import requires pipe-aligned HTILE without VRS"); + if (stencil != (target.stencilAddress != 0u) || (!stencil && target.htileStencil)) return refuse("native multisample stencil import is not implemented"); + if (stencil && !stencilPlane && (!target.htileStencil || target.htileDepthOnly)) return refuse("native multisample stencil import requires HTILE stencil metadata"); + if (stencil && (target.stencilAddress & 65535u) != 0u) return refuse("native multisample stencil base is not aligned"); + if (target.htileAddress == 0u || (target.htileAddress & 255u) != 0u || (!target.htilePipeAligned && !target.htileDepthOnly) || target.htileVrsEncoding != 0u) return refuse("native multisample depth initial import requires pipe-aligned or depth-only HTILE without VRS"); if (target.clearDepth != 0.0f && target.clearDepth != 1.0f) return refuse("native multisample depth initial clear value is not implemented"); - state.initial = {{{target.address, static_cast(MultisampledDepthBytes_nid_no_patch(target.extent, target.format == VK_FORMAT_D16_UNORM ? 2u : 4u, target.samples)), 0u}, {target.htileAddress, static_cast(HtileMetadataBytes(target.extent)), 0u}}}; + state.initial = {{target.address, static_cast(MultisampledDepthBytes_nid_no_patch(target.extent, target.format == VK_FORMAT_D16_UNORM ? 2u : 4u, target.samples)), 0u}, {target.htileAddress, static_cast(HtileMetadataBytes(target.extent)), 0u}}; + if (stencil) state.initial.push_back({target.stencilAddress, static_cast(MultisampledDepthBytes_nid_no_patch(target.extent, 1u, target.samples)), 0u}); for (const auto& range : state.initial) { if (range.address > std::numeric_limits::max() - range.bytes) return refuse("native depth import range overflows"); if (!GuestMemory::Accessible(reinterpret_cast(range.address), range.bytes) || !GuestMemory::Watched(range.address, range.bytes)) return refuse("native multisample depth import requires watched accessible guest memory"); } - const auto& depth = state.initial[0]; + for (std::size_t first = 0u; first < state.initial.size(); ++first) { + for (std::size_t second = first + 1u; second < state.initial.size(); ++second) { + const auto& a = state.initial[first]; + const auto& b = state.initial[second]; + if (a.address + a.bytes > b.address && b.address + b.bytes > a.address) return refuse("native depth, stencil and HTILE initial ranges overlap"); + } + } + for (std::size_t index = 0u; index < state.initial.size(); ++index) { + const auto& range = state.initial[index]; + const auto alias = MultisampleColorTargetOver(range.address, range.bytes); + if (!alias) continue; + const auto value = index == 2u && stencilPlane ? aliasedStencil(*alias, range, target.samples) : std::nullopt; + if (!value) { + reportAlias(*alias, range, index == 2u ? "stencil plane" : index == 1u ? "HTILE" : "depth"); + if (index == 2u && stencilPlane) { + state.unprovenStencil = alias; + continue; + } + return refuse(index == 2u ? stencilAliasRefusal : depthAliasRefusal); + } + state.aliasStencil = value; + } const auto& htile = state.initial[1]; - if (depth.address + depth.bytes > htile.address && htile.address + htile.bytes > depth.address) return refuse("native depth and HTILE initial ranges overlap"); for (auto& range : state.initial) { if (auto* recorder = RecorderFor(context)) recorder->SyncThrough(range.address, range.bytes); StorageTexture::FlushPending(range.address, range.bytes); @@ -145,8 +276,15 @@ DepthSamplingState prepareDepthSampling(const Context& context, const DepthTarge } std::vector metadata(htile.bytes / sizeof(std::uint32_t)); GuestMemory::Read(target.htileAddress, std::as_writable_bytes(std::span(metadata)), 256u); + if (!target.htilePipeAligned && std::adjacent_find(metadata.begin(), metadata.end(), [](std::uint32_t first, std::uint32_t second) { return first != second; }) != metadata.end()) return refuse("native multisample depth HTILE that is not pipe-aligned holds more than one word"); for (const auto word : metadata) { if (!htileProvesClear(word, target.htileDepthOnly, target.clearDepth)) return refuse("native multisample depth initial HTILE does not prove its clear value"); + if (stencil && !stencilPlane && !htileProvesStencilClear(word)) return refuse("native multisample stencil initial HTILE does not prove its clear value"); + } + if (stencilPlane && !state.aliasStencil && !state.unprovenStencil) { + std::vector plane(state.initial[2].bytes); + GuestMemory::Read(target.stencilAddress, std::as_writable_bytes(std::span(plane))); + if (std::any_of(plane.begin(), plane.end(), [&](std::uint8_t value) { return value != target.clearStencil; })) return refuse("native multisample stencil plane does not hold its clear value"); } for (const auto& range : state.initial) GuestMemory::CollectWritesUncached(range.address, range.bytes); if (!GuestMemory::UnchangedSinceAll(state.initial)) return refuse("guest writes raced native multisample depth import"); @@ -266,6 +404,10 @@ std::optional decodeOverwrite(const StorageImageClear& clea return overwrite; } +class DepthSurface; +std::pair staleStencilRange(const DepthSurface& surface); +void noteStalePlane(std::uint64_t begin, std::uint64_t end); + class DepthSurface { public: DepthSurface(const Context& context, const DepthTarget& target, DepthSamplingState sampling = {}) : context(context), target(target), hostFormat(HostDepthFormat(context, target.format)), sampling(std::move(sampling)) { @@ -339,7 +481,7 @@ public: toGeneral.subresourceRange = viewInfo.subresourceRange; const auto barrier = context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier"); barrier(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral); - const VkClearDepthStencilValue clear{target.clearDepth, target.clearStencil}; + const VkClearDepthStencilValue clear{this->sampling.initialDepth.value_or(target.clearDepth), this->sampling.initialStencil.value_or(this->sampling.aliasStencil.value_or(target.clearStencil))}; context.Function("vkCmdClearDepthStencilImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &clear, 1, &toGeneral.subresourceRange); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT); if (batch) batch->SubmitAndWait(); @@ -356,33 +498,76 @@ public: void CheckCurrent() { if (target.samples == 1u) return; - const auto clearLease = clearGeneration == 0u ? GuestAllocations::Lease{} : GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); + const auto clearLease = clearGeneration == 0u && stencilClearGeneration == 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) { + const auto* recorder = RecorderFor(context); + if (recorder != nullptr && clearGeneration != 0u && clearPattern.has_value() && clearFillNote == 0u) clearFillNote = fillNoteAfter(*recorder, sampling.initial[1], clearFillFloor, *clearPattern); + if (recorder != nullptr && stencilClearGeneration != 0u && stencilFillNote == 0u) stencilFillNote = fillNoteAfter(*recorder, sampling.initial[2], stencilFillFloor, *stencilClearValue * 0x01010101u); + const auto newestHtileNote = recorder != nullptr ? recorder->NewestWriteNote(sampling.initial[1].address, sampling.initial[1].bytes) : 0u; + const bool queuedFill = clearPattern.has_value() && clearFillNote != 0u && newestHtileNote == clearFillNote; + const auto newestStencilNote = recorder != nullptr && sampling.initial.size() > 2u ? recorder->NewestWriteNote(sampling.initial[2].address, sampling.initial[2].bytes) : 0u; + const bool queuedStencilFill = stencilClearValue.has_value() && stencilFillNote != 0u && newestStencilNote == stencilFillNote; + const bool separatePlane = target.stencilAddress != 0u && !target.htileStencil && target.htileDepthOnly && sampling.initial.size() > 2u; + bool planeAliased = false; + for (std::size_t index = 0u; index < sampling.initial.size(); ++index) { + const auto& range = sampling.initial[index]; 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"); + if (recorder != nullptr && !(index == 1u && queuedFill) && !(index == 2u && queuedStencilFill)) Require(!recorder->PendingWriteOverlaps(range.address, range.bytes), "external GPU writes over native multisample depth are not implemented"); GuestMemory::CollectWritesUncached(range.address, range.bytes); + const auto alias = MultisampleColorTargetOver(range.address, range.bytes); + if (!alias || (index == 2u && sampling.unprovenStencil && stencilClearGeneration == 0u)) continue; + const auto value = index == 2u && separatePlane ? aliasedStencil(*alias, range, target.samples) : std::nullopt; + const bool kept = value.has_value() && (stencilClearGeneration != 0u ? *value == *stencilClearValue : value == sampling.aliasStencil); + const bool stencilRange = index == 2u || (index == 1u && uniformPlaneTarget(target)); + if (!kept) reportAlias(*alias, range, stencilRange ? "stencil plane" : index == 1u ? "HTILE" : "depth"); + Require(kept, stencilRange ? stencilAliasRefusal : depthAliasRefusal); + planeAliased = true; + } + if (stencilClearGeneration != 0u) { + auto& plane = sampling.initial[2]; + const bool gpuFill = stencilFillNote != 0u && (queuedStencilFill || newestStencilNote == 0u); + Require(GuestMemory::UnchangedSinceCollected(plane.address, plane.bytes, stencilClearGeneration), "CPU writes raced native multisample stencil plane fill"); + if (planeAliased) sampling.aliasStencil = *stencilClearValue; + else Require(gpuFill || stencilStored || GuestMemory::StoredAllSince_nid_no_patch(plane.address, plane.bytes, stencilClearGeneration), "native multisample stencil plane fill has no complete tracked store"); + if (!queuedStencilFill && !planeAliased) { + const auto* bytes = reinterpret_cast(plane.address); + Require(std::all_of(bytes, bytes + plane.bytes, [&](std::uint8_t value) { return value == *stencilClearValue; }), "native multisample stencil plane does not hold its fill value"); + } + const auto generation = GuestMemory::CollectWritesUncached(plane.address, plane.bytes); + Require(GuestMemory::UnchangedSinceCollected(plane.address, plane.bytes, stencilClearGeneration), "CPU writes raced native multisample stencil plane fill"); + stencilFill = *stencilClearValue; + RecordClear(VK_IMAGE_ASPECT_STENCIL_BIT); + plane.generation = generation; + stencilClearGeneration = 0u; + sampling.unprovenStencil.reset(); } if (clearGeneration != 0u) { - const auto& depth = sampling.initial[0]; const auto& metadata = sampling.initial[1]; - Require(GuestMemory::UnchangedSince(depth.address, depth.bytes, depth.generation), "guest writes over native multisample depth are not implemented"); + const bool gpuFill = clearPattern.has_value() && clearFillNote != 0u && (queuedFill || newestHtileNote == 0u); + for (std::size_t index = 0u; index < sampling.initial.size(); ++index) { + const auto& plane = sampling.initial[index]; + if (index != 1u) Require(GuestMemory::UnchangedSince(plane.address, plane.bytes, plane.generation), "guest writes over native multisample depth are not implemented"); + } Require(GuestMemory::UnchangedSinceCollected(metadata.address, metadata.bytes, clearGeneration), "CPU writes raced native multisample depth clear"); - Require(GuestMemory::StoredAllSince_nid_no_patch(metadata.address, metadata.bytes, clearGeneration), "native multisample depth clear has no complete tracked HTILE store"); + Require(gpuFill || clearStored || GuestMemory::StoredAllSince_nid_no_patch(metadata.address, metadata.bytes, clearGeneration), "native multisample depth clear has no complete tracked HTILE store"); Require(clearDepth == 0.0f || clearDepth == 1.0f, "native multisample depth clear value is not implemented"); auto current = sampling.initial; current[1].generation = GuestMemory::CollectWritesUncached(metadata.address, metadata.bytes); - std::vector words(metadata.bytes / sizeof(std::uint32_t)); - std::memcpy(words.data(), reinterpret_cast(metadata.address), metadata.bytes); - for (const auto word : words) Require(htileProvesClear(word, target.htileDepthOnly, clearDepth), "native multisample depth completed HTILE does not prove its clear value"); + std::vector words(queuedFill ? 1u : metadata.bytes / sizeof(std::uint32_t), queuedFill ? *clearPattern : 0u); + if (!queuedFill) std::memcpy(words.data(), reinterpret_cast(metadata.address), metadata.bytes); + for (const auto word : words) { + Require(htileProvesClear(word, target.htileDepthOnly, clearDepth), "native multisample depth completed HTILE does not prove its clear value"); + Require((clearAspects & VK_IMAGE_ASPECT_STENCIL_BIT) == 0u || htileProvesStencilClear(word), "native multisample stencil completed HTILE does not prove its clear value"); + } for (const auto& range : current) GuestMemory::CollectWritesUncached(range.address, range.bytes); Require(GuestMemory::UnchangedSinceAll(current), "guest writes raced native multisample depth clear"); Require(GuestMemory::UnchangedSinceCollected(metadata.address, metadata.bytes, clearGeneration), "CPU writes raced native multisample depth clear"); - RecordClear(VK_IMAGE_ASPECT_DEPTH_BIT); + RecordClear(clearAspects); sampling.initial = current; clearGeneration = 0u; + clearAspects = 0u; pendingClear = 0u; } Require(GuestMemory::UnchangedSinceAll(sampling.initial), "guest writes over native multisample depth are not implemented"); @@ -390,9 +575,34 @@ public: void CheckResource(const GuestTextureResource& resource) const { if (target.samples == 1u) return; - Require(resource.baseAddress == target.address && resource.width == target.extent.width && resource.height == target.extent.height && resource.samples == target.samples && resource.format == (target.format == VK_FORMAT_D16_UNORM ? 7u : 22u), "native multisample depth sampled identity or format mismatch"); + const bool stencil = target.stencilAddress != 0u && resource.baseAddress == target.stencilAddress; + const bool identity = stencil ? resource.format == 5u : resource.baseAddress == target.address && resource.format == (target.format == VK_FORMAT_D16_UNORM ? 7u : 22u); + Require(identity && resource.width == target.extent.width && resource.height == target.extent.height && resource.samples == target.samples, "native multisample depth sampled identity or format mismatch"); Require(resource.dimension == TextureDimension::k2DMsaa && resource.tileMode == TextureTileMode::kZ64KBX && resource.depthOrLastArray == 0u && resource.baseArray == 0u && resource.baseLevel == 0u && resource.lastLevel == 0u && resource.mipCount == 1u && resource.allocatedMipCount == 1u && resource.minLod == 0u, "native multisample depth sampled array, mip or layout is not implemented"); - Require(resource.pipeBankXor == target.pipeBankXor && resource.dccAddress == target.htileAddress && resource.iterate256 == target.iterate256 && resource.dccPipeAligned == target.htilePipeAligned && !resource.dccAlphaOnMsb, "native multisample depth sampled metadata or XOR mismatch"); + if (uniformPlaneTarget(target)) { + Require(resource.pipeBankXor == target.pipeBankXor && resource.dccAddress == 0u && !resource.dccAlphaOnMsb, "native multisample depth sampled metadata or XOR mismatch"); + return; + } + const bool htile = resource.dccAddress == target.htileAddress && resource.dccPipeAligned == target.htilePipeAligned; + const bool bare = resource.dccAddress == 0u && !resource.dccPipeAligned; + if (stencil) { + Require(resource.pipeBankXor == target.pipeBankXor && bare && !resource.dccAlphaOnMsb && !target.htileStencil && target.htileDepthOnly, "native multisample depth sampled metadata or XOR mismatch"); + return; + } + const bool layout = resource.pipeBankXor == target.pipeBankXor && resource.iterate256 == target.iterate256 && !resource.dccAlphaOnMsb; + if (layout && bare && !htile && !expanded) { + static std::atomic reports{0}; + if (reports.fetch_add(1) < 8) std::fprintf(stderr, "[gpu] native multisample depth 0x%llx sampled through a descriptor without metadata before an in-place expand (last DB_RENDER_CONTROL 0x%08x)\n", static_cast(target.address), renderControl); + } + Require(layout && (htile || (bare && expanded)), "native multisample depth sampled metadata or XOR mismatch"); + } + + void RequireStencilProven() const { + if (sampling.unprovenStencil) { + const auto& plane = sampling.initial[2]; + reportAlias(MultisampleColorTargetOver(plane.address, plane.bytes).value_or(*sampling.unprovenStencil), plane, "unproven stencil plane"); + } + Require(!sampling.unprovenStencil, stencilAliasRefusal); } void ApplyFastClear() { @@ -464,7 +674,8 @@ public: const auto expected = stencil ? VK_FORMAT_R8_UINT : d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT; const bool depthBits = !stencil && words.size() >= 4 && ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) == (d16 ? 16u : 32u); if (multisampled) { - Require(!stencil && !depthBits && format == expected, "native multisampled depth sampling needs a numeric depth-plane format"); + Require(!depthBits && format == expected, "native multisampled depth sampling needs a numeric depth-plane format"); + Require(!stencil || (context.limits.sampledImageStencilSampleCounts & target.samples) != 0u, "the device cannot sample the native depth target's stencil at its sample count"); Require(resource.samples == target.samples, "the sampled depth descriptor's sample count differs from its native target"); Require(hostFormat == target.format, "sampling a native multisampled depth plane that the device stores in another depth format is not implemented"); } @@ -480,7 +691,7 @@ public: } if (multisampled && useSampleShadow) { if (sampleShadow == nullptr) createSampleShadow(); - refreshSampleShadow(drawn); + refreshSampleShadow(drawn, stencil); } if (const auto found = textures.find(key); found != textures.end()) return found->second; const auto viewType = multisampled ? multisampleViewType : resource.dimension == TextureDimension::k2DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; @@ -494,21 +705,48 @@ public: return texture; } + std::shared_ptr FirstSample(const Texture& source, VkComponentMapping components) { + auto* recorder = RecorderFor(context); + Require(recorder != nullptr, "a native multisampled depth target is sampled only by recorded draws and dispatches"); + const bool d16 = target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT; + const auto format = d16 ? VK_FORMAT_R16_UNORM : VK_FORMAT_R32_SFLOAT; + if (firstSample == nullptr) firstSample = std::make_shared(context, format, target.extent); + firstSample->copy.Record(*recorder, 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 = firstSample->textures[key]; + if (texture == nullptr) { + texture = std::make_shared(context, firstSample->copy.Image(), format, VK_IMAGE_ASPECT_COLOR_BIT, components); + texture->MarkRefreshedPerUse(); + } + return {firstSample, texture.get()}; + } + void ForgetClear(VkImageAspectFlags aspects) { pendingClear &= ~aspects; if ((aspects & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) depthFill.reset(); if ((aspects & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) stencilFill.reset(); } + void MarkStencilStale() { + if (!StencilMirrorable()) return; + stencilStale = true; + const auto [begin, end] = staleStencilRange(*this); + noteStalePlane(begin, end); + } + void NoteHtileClear(VkImageAspectFlags aspects) { ForgetClear(aspects); pendingClear |= aspects; + if ((aspects & VK_IMAGE_ASPECT_STENCIL_BIT) != 0u) MarkStencilStale(); } void NotePlaneFill(VkImageAspectFlagBits aspect, float depth, std::uint8_t stencil) { pendingClear |= aspect; if (aspect == VK_IMAGE_ASPECT_DEPTH_BIT) depthFill = depth; - else stencilFill = stencil; + else { + stencilFill = stencil; + MarkStencilStale(); + } } void CopyAspectFrom(const DepthSurface& source, VkImageAspectFlagBits aspect) { @@ -726,6 +964,7 @@ public: auto& plane = stencil ? stencilPlane : depthPlane; const auto source = std::const_pointer_cast(candidate.shared_from_this()); copyPlane(plane, source, stencil, false); + ForgetClear(stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT); written = ++depthWrites; NoteHostWrite(); return true; @@ -1046,10 +1285,23 @@ public: bool clearPending = false; std::uint64_t clearGeneration = 0u; bool clearCompletionQueued = false; + VkImageAspectFlags clearAspects = 0u; + std::optional clearPattern; + std::uint64_t clearFillNote = 0u; + std::uint64_t clearFillFloor = 0u; + bool clearStored = false; + std::uint64_t stencilClearGeneration = 0u; + std::optional stencilClearValue; + std::uint64_t stencilFillNote = 0u; + std::uint64_t stencilFillFloor = 0u; + bool stencilStored = false; + bool expanded = false; + std::uint32_t renderControl = 0u; std::shared_ptr owned; std::shared_ptr sampleShadow; bool useSampleShadow = false; std::uint64_t sampleShadowWritten = 0u; + bool sampleShadowStencil = false; private: struct PlaneStorage { @@ -1066,6 +1318,12 @@ private: bool filled = false; }; std::map, std::shared_ptr> textures; + struct FirstSampleTextures { + FirstSampleTextures(const Context& context, VkFormat format, VkExtent2D extent) : copy(context, format, extent) {} + FirstSampleCopy copy; + std::map, std::shared_ptr> textures; + }; + std::shared_ptr firstSample; PlaneStorage depthPlane; PlaneStorage stencilPlane; std::uint64_t depthMemory = 0; @@ -1110,7 +1368,7 @@ private: barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = sampleShadow->image; - barrier.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}; + barrier.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT | (HasStencil(hostFormat) ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u), 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, &barrier); if (batch) batch->SubmitAndWait(); else Recorder::CountBarriers(Recorder::CommandClass::Draw, 1); @@ -1121,8 +1379,12 @@ private: } } - void refreshSampleShadow(bool drawn) { + void refreshSampleShadow(bool drawn, bool stencilRead = false) { Require(sampleShadow != nullptr, "native multisample depth shadow was not created"); + if (stencilRead && !sampleShadowStencil) { + sampleShadowStencil = true; + sampleShadowWritten = 0u; + } if (!drawn && sampleShadowWritten == written) return; auto* recorder = RecorderFor(context); std::unique_ptr batch; @@ -1132,7 +1394,7 @@ private: constexpr VkAccessFlags accesses = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, accesses, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); VkImageCopy region{}; - region.srcSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0, 1}; + region.srcSubresource = {VK_IMAGE_ASPECT_DEPTH_BIT | (sampleShadowStencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u), 0, 0, 1}; region.dstSubresource = region.srcSubresource; region.extent = {target.extent.width, target.extent.height, 1}; context.Function("vkCmdCopyImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, sampleShadow->image, VK_IMAGE_LAYOUT_GENERAL, 1, ®ion); @@ -2283,7 +2545,11 @@ float HostDepthBiasConstant(const Context& context, VkFormat format, float const return constant * 256.0f; } -VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten) { +VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, bool depthWritten, bool stencilTested) { + return DepthSurfaceViewWithStencilWriteIntent(context, target, depthWritten, stencilTested, true); +} + +VkImageView DepthSurfaceViewWithStencilWriteIntent(const Context& context, const DepthTarget& target, bool depthWritten, bool stencilTested, bool stencilWritten) { const auto planeBytes = [&](std::uint32_t bytesPerTexel) { return target.samples == 1u ? DepthSliceBytes(target.extent, bytesPerTexel) : MultisampledDepthBytes_nid_no_patch(target.extent, bytesPerTexel, target.samples); }; ClaimMultisampleTargetMemory(target.address, planeBytes(target.format == VK_FORMAT_D16_UNORM || target.format == VK_FORMAT_D16_UNORM_S8_UINT ? 2u : 4u)); if (target.stencilAddress != 0u) ClaimMultisampleTargetMemory(target.stencilAddress, planeBytes(1u)); @@ -2334,7 +2600,10 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, const bool inherits = owner != depthOwners().end() && owner->second.surface->active->load(std::memory_order_acquire) && !owner->second.surface->retired; surface->clearDepth = target.clearDepth; surface->clearStencil = target.clearStencil; + const auto stencilOwner = stencilOwners().find({context.device, target.stencilAddress}); + const bool stencilRebound = stencilOwner == stencilOwners().end() || stencilOwner->second.surface != surface; takePlanes(*surface, created, htileCleared | (cleared ? VK_IMAGE_ASPECT_DEPTH_BIT : 0u)); + if (cleared || htileCleared != 0u) surface->expanded = false; if (created && !inherits && target.htileAddress == 0) surface->TakeStorage(); if (cleared && (!created || inherits)) { if (target.samples != 1u) surface->sampling.Refuse("native multisample depth fast-clear transition is not implemented"); @@ -2344,6 +2613,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, if (mirrorable(target)) surface->SyncFromMemory(mirrorResource(target)); if (surface->StencilMirrorable()) surface->SyncStencilFromMemory(stencilResource(target)); surface->ApplyFastClear(); + if (stencilTested && surface->samplingActive) surface->RequireStencilProven(); if (drawnDepth != nullptr && drawnDepth != surface) drawnDepth->written = ++depthWrites; surface->written = ++depthWrites; surface->NoteMemory(); @@ -2364,11 +2634,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target, surface->MarkAllStale(planeBytes); noteStalePlane(plane, plane + planeBytes); } - if (surface->StencilMirrorable()) { - surface->stencilStale = true; - const auto [begin, end] = staleStencilRange(*surface); - noteStalePlane(begin, end); - } + if (stencilWritten || created || stencilRebound) surface->MarkStencilStale(); drawnDepth = surface; return surface->view; } @@ -2673,13 +2939,19 @@ bool NoteHtileDepthClear(std::uint64_t htileAddress, std::size_t bytes, std::siz bool completionCandidate = false; for (const auto& surface : surfaces()) { if (!surface->active->load(std::memory_order_acquire)) continue; + if (surface->target.htileAddress != 0u && htileAddress < surface->target.htileAddress + HtileMetadataBytes(surface->target.extent) && surface->target.htileAddress < htileAddress + bytes) surface->expanded = false; 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"); + if (surface->target.stencilAddress != 0u) surface->sampling.Refuse("native multisample depth HTILE mask clear of a stencil surface is not implemented"); + else 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->clearPending || surface->clearGeneration != 0u) surface->sampling.Refuse("overlapping native multisample depth clear transitions are not implemented"); else { surface->clearPending = true; surface->clearCompletionQueued = false; + surface->clearAspects = VK_IMAGE_ASPECT_DEPTH_BIT; + surface->clearPattern.reset(); + surface->clearFillNote = 0u; + surface->clearStored = false; completionCandidate = true; } existing = true; @@ -2762,6 +3034,16 @@ thread_local bool depthCopySourceActive = false; } +std::shared_ptr DepthSurfaceFirstSample(const Context& context, std::span words, const GuestTextureResource& resource, VkComponentMapping components) { + Require(resource.dimension == TextureDimension::k2DMsaa, "reading sample 0 of a depth surface needs a multisampled descriptor"); + const auto source = DepthSurfaceTexture(context, words, resource, VkComponentMapping{}, VK_IMAGE_VIEW_TYPE_2D); + Require(source != nullptr, "reading sample 0 of a multisampled depth surface whose depth plane is not at the descriptor's address is not implemented"); + std::lock_guard lock(surfacesMutex()); + auto* found = planeSurface(context.device, resource.baseAddress); + Require(found != nullptr && found->target.address == resource.baseAddress, "reading sample 0 of a multisampled depth surface whose depth plane is not at the descriptor's address is not implemented"); + return found->FirstSample(*source, components); +} + 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)); @@ -2788,6 +3070,7 @@ std::shared_ptr DepthSurfaceTexture(const Context& context, std::spanApplyFastClear(); } else { found->CheckCurrent(); + if (stencil) found->RequireStencilProven(); found->CheckResource(resource); Require(found->pendingClear == 0u, "native multisample depth fast-clear transition is not implemented"); } @@ -2873,8 +3156,10 @@ VkImageAspectFlags HtileFillClears(std::uint32_t pattern, bool stencilInHtile) { } std::uint64_t HtileMetadataBytes(VkExtent2D extent) { - const auto blocks = static_cast((extent.width + 1023u) / 1024u) * ((extent.height + 511u) / 512u); - return blocks * 32768u; + const bool gcn = ShaderRecompiler::GcnGuestShaders(); + const std::uint32_t blockWidth = gcn ? 512u : 1024u; + const auto blocks = static_cast((extent.width + blockWidth - 1u) / blockWidth) * ((extent.height + 511u) / 512u); + return blocks * (gcn ? 16384u : 32768u); } bool HtileFillCovers(std::uint64_t htile, VkExtent2D extent, std::uint64_t address, std::size_t bytes) { @@ -2903,12 +3188,58 @@ std::optional DepthFillValue(std::uint32_t pattern, bool d16) { } void NoteDepthMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern) { + std::lock_guard gpu(GuestMemory::GpuMutex()); std::lock_guard lock(surfacesMutex()); forgetHtileReads(address, bytes); for (const auto& surface : surfaces()) { if (!surface->active->load(std::memory_order_acquire)) continue; const auto& target = surface->target; - if (target.samples != 1u && target.htileAddress != 0u && address < target.htileAddress + HtileMetadataBytes(target.extent) && target.htileAddress < address + bytes) surface->sampling.Refuse("native multisample depth metadata fill transition is not implemented"); + const bool touched = std::any_of(surface->sampling.initial.begin(), surface->sampling.initial.end(), [&](const auto& range) { return address < range.address + range.bytes && range.address < address + bytes; }); + if (touched || surface->Overlaps(address, bytes) || (target.htileAddress != 0u && address < target.htileAddress + HtileMetadataBytes(target.extent) && target.htileAddress < address + bytes)) surface->expanded = false; + if (surface->samplingActive && target.stencilAddress != 0u && !target.htileStencil && target.htileDepthOnly && surface->sampling.initial.size() > 2u) { + const auto& plane = surface->sampling.initial[2]; + if (address < plane.address + plane.bytes && plane.address < address + bytes) { + const auto value = StencilFillValue(pattern); + if (!PlaneFillCovers(plane.address, plane.bytes, address, bytes)) surface->sampling.Refuse("native multisample stencil plane partial fill transition is not implemented"); + else if (!value.has_value()) surface->sampling.Refuse("native multisample stencil plane fill of more than one byte value is not implemented"); + else if (!GuestMemory::Accessible(reinterpret_cast(plane.address), plane.bytes) || !GuestMemory::Watched(plane.address, plane.bytes)) surface->sampling.Refuse("native multisample stencil plane fill over unwatched memory is not implemented"); + else { + const auto* recorder = RecorderFor(surface->context); + const auto refill = surface->stencilClearGeneration != 0u && surface->stencilClearValue == value && surface->stencilFillNote == 0u && recorder != nullptr ? fillNoteAfter(*recorder, plane, surface->stencilFillFloor, *value * 0x01010101u) : 0u; + if (surface->stencilClearGeneration != 0u && refill == 0u) surface->sampling.Refuse("overlapping native multisample stencil plane fills are not implemented"); + else if (refill != 0u) surface->stencilFillNote = refill; + else { + surface->stencilClearGeneration = GuestMemory::CollectWritesUncached(plane.address, plane.bytes); + surface->stencilClearValue = *value; + surface->stencilFillNote = recorder != nullptr ? recorder->LastWriteNote(address, bytes) : 0u; + surface->stencilFillFloor = recorder != nullptr ? recorder->NewestWriteNote(plane.address, plane.bytes) : 0u; + surface->stencilStored = GuestMemory::StoredAllSince_nid_no_patch(plane.address, plane.bytes, plane.generation); + } + } + } + } + const bool overlaps = target.samples != 1u && target.htileAddress != 0u && address < target.htileAddress + HtileMetadataBytes(target.extent) && target.htileAddress < address + bytes; + if (overlaps && surface->samplingActive && HtileFillCovers(target.htileAddress, target.extent, address, bytes)) { + const auto& metadata = surface->sampling.initial[1]; + if (!GuestMemory::Accessible(reinterpret_cast(metadata.address), metadata.bytes) || !GuestMemory::Watched(metadata.address, metadata.bytes)) surface->sampling.Refuse("native multisample depth metadata fill over unwatched memory is not implemented"); + else { + const auto* recorder = RecorderFor(surface->context); + const auto refill = surface->clearGeneration != 0u && surface->clearPattern == pattern && surface->clearFillNote == 0u && recorder != nullptr ? fillNoteAfter(*recorder, metadata, surface->clearFillFloor, pattern) : 0u; + if (surface->clearPending || (surface->clearGeneration != 0u && !surface->clearPattern.has_value())) surface->sampling.Refuse("native multisample depth metadata fill over a pending HTILE clear dispatch is not implemented"); + else if (surface->clearGeneration != 0u && refill == 0u) surface->sampling.Refuse("overlapping native multisample depth clear transitions are not implemented"); + else if (refill != 0u) surface->clearFillNote = refill; + else { + surface->clearGeneration = GuestMemory::CollectWritesUncached(metadata.address, metadata.bytes); + surface->clearAspects = VK_IMAGE_ASPECT_DEPTH_BIT | (target.htileStencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u); + surface->clearPattern = pattern; + surface->clearFillNote = recorder != nullptr ? recorder->LastWriteNote(address, bytes) : 0u; + surface->clearFillFloor = recorder != nullptr ? recorder->NewestWriteNote(metadata.address, metadata.bytes) : 0u; + surface->clearStored = GuestMemory::StoredAllSince_nid_no_patch(metadata.address, metadata.bytes, metadata.generation); + } + } + continue; + } + if (overlaps) surface->sampling.Refuse("native multisample depth metadata fill transition is not implemented"); if (HtileFillCovers(target.htileAddress, target.extent, address, bytes)) { const VkImageAspectFlags written = VK_IMAGE_ASPECT_DEPTH_BIT | (target.htileStencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0u); surface->ForgetClear(written); @@ -2926,6 +3257,19 @@ void NoteDepthMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32 } } +void NoteDepthRenderControl(const DepthRenderControl& draw) { + std::lock_guard gpu(GuestMemory::GpuMutex()); + std::lock_guard lock(surfacesMutex()); + for (const auto& surface : surfaces()) { + if (!surface->active->load(std::memory_order_acquire) || surface->target.samples == 1u || surface->target.samples != draw.samples || surface->target.address != draw.address) continue; + surface->renderControl = draw.control; + const bool depthExpand = (draw.control & 0x40u) != 0u; + const bool stencilExpand = (draw.control & 0x20u) != 0u; + if (depthExpand && (stencilExpand || surface->target.stencilAddress == 0u)) surface->expanded = true; + else if ((draw.control & 3u) != 0u) surface->expanded = false; + } +} + bool DepthSurfaceAt(const Context& context, std::span words, const GuestTextureResource& resource) { std::lock_guard lock(surfacesMutex()); const auto* found = planeSurface(context.device, resource.baseAddress); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index 31a571dd78..38e8fb93c2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -388,8 +388,15 @@ GuestTextureResource ArraySurfaceForTarget(const ColorTarget& color) { namespace { +bool writesStencilTarget(const State& state) { + const auto keeps = [](const VkStencilOpState& face) { + return face.writeMask == 0u || (face.failOp == VK_STENCIL_OP_KEEP && face.passOp == VK_STENCIL_OP_KEEP && face.depthFailOp == VK_STENCIL_OP_KEEP); + }; + return state.stencilTest && (!keeps(state.stencilFront) || !keeps(state.stencilBack)); +} + bool writesDepthTarget(const State& state) { - return state.depthWrite || (state.stencilTest && (state.stencilFront.writeMask != 0u || state.stencilBack.writeMask != 0u)); + return state.depthWrite || writesStencilTarget(state); } std::size_t colorKeyCount(const ColorTarget& color, std::size_t bytes) { @@ -1092,7 +1099,7 @@ std::uint32_t CachedFragmentOutputs(const Context& context, std::span MeshGridFor(const Context& context, std::uint32_t groups, std::uint32_t instances) { + const auto& limits = context.meshLimits; + if (groups == 0 || instances > limits.maxMeshWorkGroupCount[1] || limits.maxMeshWorkGroupCount[0] == 0) return std::nullopt; + const auto across = groups <= limits.maxMeshWorkGroupCount[0] ? groups : std::min(limits.maxMeshWorkGroupCount[0], ShaderRecompiler::MeshLayerStrideLimit); + const auto layers = (groups - 1u) / across + 1u; + const auto columns = (groups - 1u) / layers + 1u; + if (layers > limits.maxMeshWorkGroupCount[2] || (instances != 0 && columns > limits.maxMeshWorkGroupTotalCount / instances / layers)) return std::nullopt; + return MeshGrid{columns, instances, layers, layers > 1u ? columns : 0u}; +} + MeshArguments ResolveMeshArguments(const Pm4::DrawArguments& record, const MeshArgumentRules& rules) { const auto first = record.firstVertexOrIndex; const auto effective = first < rules.indexCount ? std::min(record.count, rules.indexCount - first) : 0u; @@ -1436,6 +1453,8 @@ struct DrawInputs { std::uint32_t fragmentOutputs = 0; VkPipelineStageFlags shaderStages = 0; std::uint32_t meshGroups = 0; + std::uint32_t meshLayers = 1; + std::uint32_t meshLayerStride = 0; std::vector restartTable; std::uint32_t expandedIndexCount = 0; const VertexInputLayout& VertexLayout() const { return retainedVertexInput != nullptr ? *retainedVertexInput : vertexInput; } @@ -1454,7 +1473,7 @@ std::shared_ptr zeroVertexBuffer(const Context& context, std::size_t byt // Draw's validation, index and vertex phases. With `recipe` the fragment outputs, the pipeline // stages and the vertex input layout are the recipe's (derived from the same compiled stages) // instead of computed. -DrawInputs prepareDrawInputs(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, DrawOutcome& outcome, DrawTimer& timer, const DrawRecipe* recipe) { +DrawInputs prepareDrawInputs(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, DrawOutcome& outcome, DrawTimer& timer, PerformanceTimer& timing, const DrawRecipe* recipe) { DrawInputs inputs; APS5_LOG_OUT_DEBUG("Draw indices=%u instances=%u indexSize=%u flags=%u indexAddress=0x%llx", draw.indexCount, draw.instanceCount, draw.indexSize, draw.flags, static_cast(draw.indexAddress)); APS5_LOG_OUT_DEBUG("State colorTarget=%u render=%ux%u colorAddress=0x%llx colorBytes=%llu colorExtent=%ux%u", state.hasColorTarget ? 1u : 0u, state.renderExtent.width, state.renderExtent.height, static_cast(state.color.address), static_cast(state.color.bytes), state.color.extent.width, state.color.extent.height); @@ -1520,9 +1539,13 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P if (args == nullptr) { const auto step = mesh.inputPrimitive == 5 || mesh.inputPrimitive == 6 ? 1u : inputSize; const auto primitives = (draw.indexCount - inputSize) / step + 1u; - inputs.meshGroups = (primitives - 1u) / mesh.primitivesPerGroup + 1u; - APS5_LOG_OUT_DEBUG("Mesh primitives=%u groups=%u", primitives, inputs.meshGroups); - Require(inputs.meshGroups <= context.meshLimits.maxMeshWorkGroupCount[0] && draw.instanceCount <= context.meshLimits.maxMeshWorkGroupCount[1] && static_cast(inputs.meshGroups) * draw.instanceCount <= context.meshLimits.maxMeshWorkGroupTotalCount, "mesh draw exceeds workgroup count limits"); + const auto groups = (primitives - 1u) / mesh.primitivesPerGroup + 1u; + const auto grid = MeshGridFor(context, groups, draw.instanceCount); + Require(grid.has_value(), "mesh draw exceeds workgroup count limits"); + APS5_LOG_OUT_DEBUG("Mesh primitives=%u groups=%u as %u x %u", primitives, groups, grid->groups, grid->layers); + inputs.meshGroups = grid->groups; + inputs.meshLayers = grid->layers; + inputs.meshLayerStride = grid->layerStride; } } if (state.stages.tessellation) Require(draw.indexCount % state.stages.tessellation->inputControlPoints == 0, "incomplete tessellation patch"); @@ -1531,6 +1554,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P ValidateDepthBounds(context, state); ValidateSampleMask(context, state, shaders); timer.phase(PhaseValidate); + timing.Mark("validate"); inputs.maxIndex = draw.indexed ? 0u : draw.firstVertex + draw.indexCount - 1u; 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"); @@ -1629,7 +1653,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P const bool restartLeft = draw.indirect.has_value(); auto highest = HighestDrawIndex(kept, draw.indexSize, restartLeft); constexpr std::uint32_t ZeroFetchSlack = 0x10000u; - if (const auto zeroFrom = highest ? ZeroFetchVertex(shaders.front().program->vertexAttributes) : std::nullopt) { + if (const auto zeroFrom = highest && !shaders.front().program->vertexAttributes.empty() ? ZeroFetchVertex(shaders.front().program->vertexAttributes) : std::nullopt) { const auto limit = *zeroFrom > draw.firstVertex ? *zeroFrom - draw.firstVertex : 0u; if (*highest >= limit && *highest - limit >= ZeroFetchSlack) highest = ClampDrawIndices(kept, draw.indexSize, restartLeft, limit); } @@ -1669,7 +1693,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P return inputs; } constexpr std::uint32_t ZeroFetchSlack = 0x10000u; - if (const auto zeroFrom = state.stages.mesh ? std::nullopt : ZeroFetchVertex(shaders.front().program->vertexAttributes)) { + if (const auto zeroFrom = state.stages.mesh || shaders.front().program->vertexAttributes.empty() ? std::nullopt : ZeroFetchVertex(shaders.front().program->vertexAttributes)) { const auto limit = *zeroFrom > draw.firstVertex ? *zeroFrom - draw.firstVertex : 0u; if (*highest >= limit && *highest - limit >= ZeroFetchSlack) { GuestMemory::FlushGpuWrites(draw.indexAddress, static_cast(indexBytes)); @@ -1703,6 +1727,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P } } APS5_LOG_CHARS_OUT_DEBUG("Index validation OK"); + timing.Mark("index_upload"); const auto& attributes = shaders.front().program->vertexAttributes; // Validates the vertex descriptors; the layout also keys and builds the pipeline. if (recipe != nullptr) { @@ -1806,6 +1831,7 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P inputs.vertexOffsets[i] = rangeOffsets[plan.copyOf[j]] + plan.offsets[j]; } timer.phase(PhaseVertex); + timing.Mark("vertex_upload"); return inputs; } @@ -1961,8 +1987,8 @@ void recordDrawCommands(const Context& context, VkCommandBuffer commands, const 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); + APS5_LOG_OUT_DEBUG("vkCmdDrawMeshTasksEXT groups=%u instances=%u layers=%u", inputs.meshGroups, draw.instanceCount, inputs.meshLayers); + context.Function("vkCmdDrawMeshTasksEXT")(commands, inputs.meshGroups, draw.instanceCount, inputs.meshLayers); recorded(subdraw++); return; } @@ -2267,7 +2293,7 @@ void recordStencilReplay(const Context& context, VkCommandBuffer commands, const } } -void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, std::span shaders, const ShaderResources& resources, const std::array* bytes, VkShaderStageFlags stages, VkDeviceAddress meshArguments = 0, bool restartTable = false) { +void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, std::span shaders, const ShaderResources& resources, const std::array* bytes, VkShaderStageFlags stages, VkDeviceAddress meshArguments = 0, bool restartTable = false, std::uint32_t meshLayerStride = 0) { auto block = bytes != nullptr ? *bytes : AssemblePushConstants(shaders); if (bytes == nullptr) { resources.PatchPushConstants(block); @@ -2295,7 +2321,7 @@ void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const } const auto firstVertex = draw.indirect ? draw.indirect->vertexConstant : draw.firstVertex; const auto firstInstance = draw.indirect ? draw.indirect->instanceConstant : draw.firstInstance; - std::array words{draw.indexCount, firstVertex, firstInstance, draw.indexed ? draw.indexSize | (restartTable ? ShaderRecompiler::MeshIndexRestartTable : 0u) : 0u, static_cast(meshArguments), static_cast(meshArguments >> 32u)}; + std::array words{draw.indexCount, firstVertex, firstInstance, (draw.indexed ? draw.indexSize | (restartTable ? ShaderRecompiler::MeshIndexRestartTable : 0u) : 0u) | meshLayerStride << ShaderRecompiler::MeshLayerStrideShift, static_cast(meshArguments), static_cast(meshArguments >> 32u)}; if (state.tessellationCapture != nullptr) words = {static_cast(capturedItems), static_cast(capturedItems >> 32u), 0u, 0u, static_cast(meshArguments), static_cast(meshArguments >> 32u)}; static_assert(ShaderRecompiler::MeshDrawPushOffsetBytes + ShaderRecompiler::MeshDrawPushBytes == PipelinePushSlotBytes); std::memcpy(block.data() + ShaderRecompiler::MeshDrawPushOffsetBytes, words.data(), sizeof(words)); @@ -2562,7 +2588,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam if (std::ranges::any_of(shaders, [](const CompiledShader& shader) { return shader.program != nullptr && shader.program->functionLdsBuffer; })) BindPixelFunctionLds(context, commands, record.pipeline->Layout()); if (UsesMeshWindow(shaders)) BindMeshWindow(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()); + pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0, !inputs.restartTable.empty(), inputs.meshLayerStride); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); static const bool timeEachDraw = std::getenv("APS5_PROFILE_GPU_DRAWS") != nullptr; auto eachDrawTiming = Recorder::NoTiming; @@ -2873,6 +2899,10 @@ void rememberShape(const Context& context, std::uint64_t shape, std::span& image) { return image.first == resident.Image() && image.second; }); +} + 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; @@ -2919,12 +2949,10 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara auto& ownWaitedMs = prepared.ownWaitedMs; const auto report = [&](const char* suffix) { reportDrawEnd(state, timer, built, outcome, waitedBefore, ownWaitedMs, suffix); }; auto& inputs = prepared.inputs; - inputs = prepareDrawInputs(context, state, draw, shaders, outcome, timer, nullptr); + inputs = prepareDrawInputs(context, state, draw, shaders, outcome, timer, timing, 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"); const int dumpLimit = prepared.dumpLimit = DumpTargetLimit(); auto& targets = prepared.targets; targets.resize(state.colors.size()); @@ -3049,7 +3077,7 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara targetViews.push_back(binding.target->View()); } if (state.depth) { - targetViews.push_back(DepthSurfaceView(context, *state.depth, state.depthWrite)); + targetViews.push_back(DepthSurfaceViewWithStencilWriteIntent(context, *state.depth, state.depthWrite, state.stencilTest, writesStencilTarget(state))); if (writesDepthTarget(state)) NoteDepthSurfaceWrite(context, *state.depth); } timer.phase(PhasePrepare); @@ -3059,6 +3087,13 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara auto& resolved = prepared.resolved; resolved = resolveDrawResources(context, state, draw, shaders, snapshots, indexBytes, recordable, outcome, timer); auto& resources = resolved.resources; + for (std::size_t index = 0; index < targets.size(); ++index) { + auto& binding = targets[index]; + if (binding.color.partialWriteMask == 0 || binding.multisampled || binding.resident == nullptr || binding.proxied || storesIntoResident(*resources, *binding.resident)) continue; + const auto& color = binding.color; + binding.resident = refreshResidentTarget(context, state, color, outcome, profile, [&] { return color.arrayLastSlice != 0 ? CachedStorageLayers(context, ArraySurfaceForTarget(color), color.arraySlice, color.layers) : CachedStorageSurface(context, SurfaceForTarget(color)); }); + if (binding.resident != nullptr) targetViews[index] = binding.resident->AttachmentView(color.format, color.mip, color.depthSlice, color.layers, color.arraySlice); + } if (!stencilReferences.empty() && resources->ChecksFaults()) recordable = false; if (!stencilReferences.empty()) { const auto readImages = resources->StorageImages(); @@ -3208,8 +3243,7 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara 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"); + Require(storesIntoResident(*resources, *binding.resident), "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); @@ -3499,7 +3533,7 @@ void recordPreparedDraw(const Context& context, const State& state, const Pm4::D if (pixelLdsSubgroup != 0u) BindPixelFunctionLds(context, commands, pipeline->Layout()); if (UsesMeshWindow(shaders)) BindMeshWindow(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()); + pushDrawConstants(*pipeline, commands, state, draw, shaders, *resources, nullptr, 0, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0, !inputs.restartTable.empty(), inputs.meshLayerStride); APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded"); std::uint32_t subdraw = 0; if (stencilReplay.empty()) recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset, prepared.trace, subdraw); @@ -3732,6 +3766,28 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters& drawTessellationGeometry(context, state, draw, shaders, snapshots, trace); return; } + if (state.stages.mesh && state.layerExports != 0u && state.renderLayers > context.meshLimits.maxMeshOutputLayers && (draw.indirect || (draw.indexCount != 0u && draw.instanceCount != 0u))) { + const auto limit = context.meshLimits.maxMeshOutputLayers; + Require(limit != 0u && !state.depth, "mesh layer windows require a nonzero output limit and color-only layered rendering"); + Require(std::ranges::all_of(shaders, [](const CompiledShader& shader) { return shader.program != nullptr && shader.program->layerReplaySafe; }), "mesh layer windows refuse shaders with guest memory writes, clocks or unproven side effects"); + if (recipeOut != nullptr) recipeOut->reset(); + for (std::uint32_t base = 0; base < state.renderLayers;) { + auto window = state; + window.renderLayers = std::min(limit, state.renderLayers - base); + window.meshLayerBase = base; + window.meshLayerCount = window.renderLayers; + for (auto& color : window.colors) { + Require(color.layers == state.renderLayers, "mesh layer window targets have different layer counts"); + if (color.depth > 1u) color.depthSlice += base; + else color.arraySlice += base; + color.layers = window.renderLayers; + } + if (!window.colors.empty()) window.color = window.colors.front(); + Draw(context, window, draw, shaders, snapshots, nullptr, trace); + base += window.renderLayers; + } + return; + } static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; PreparedDraw prepared(profile); prepared.recipeOut = recipeOut; @@ -3767,7 +3823,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con 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"); - auto inputs = prepareDrawInputs(context, state, draw, shaders, outcome, timer, &recipe); + auto inputs = prepareDrawInputs(context, state, draw, shaders, outcome, timer, timing, &recipe); if (inputs.nothing) { result.recorded = true; return result; @@ -3796,7 +3852,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); + const auto depthView = DepthSurfaceViewWithStencilWriteIntent(context, *state.depth, state.depthWrite, state.stencilTest, writesStencilTarget(state)); if (writesDepthTarget(state)) NoteDepthSurfaceWrite(context, *state.depth); timer.phase(PhaseReadTarget); if (recipe.targetViews.back() != depthView) return miss(DrawRecipeMiss::TargetGone); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index 07d81c5c60..c4a70179f5 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Graphics/include/MemoryAccounting.hpp" #include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/MapImportGate.hpp" #include "prx/libSceAgcDriver/Graphics/include/AdaptiveStaging.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" @@ -10,6 +11,7 @@ #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/ReferenceStorage.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -23,13 +25,38 @@ #include #else #include +#ifdef __APPLE__ +struct udmabuf_create { + std::uint32_t memfd; + std::uint32_t flags; + std::uint64_t offset; + std::uint64_t size; +}; +struct udmabuf_create_item { + std::uint32_t memfd; + std::uint32_t pad; + std::uint64_t offset; + std::uint64_t size; +}; +struct udmabuf_create_list { + std::uint32_t flags; + std::uint32_t count; + udmabuf_create_item list[]; +}; +constexpr std::uint32_t UDMABUF_FLAGS_CLOEXEC = 0x01; +constexpr unsigned long UDMABUF_CREATE = 0; +constexpr unsigned long UDMABUF_CREATE_LIST = 0; +#else #include +#endif #include #include #include #include #endif #include +#include +#include #include #include #include @@ -255,6 +282,32 @@ struct ImportSettle { #endif }; +const bool importsLockOrderChecked = MapImportGate::LockOrderChecked(); + +thread_local std::uint32_t importsHeld = 0; + +class ImportsMutex { +public: + void lock() noexcept { + mutex.lock(); + if (importsLockOrderChecked) ++importsHeld; + } + + bool try_lock() noexcept { + if (!mutex.try_lock()) return false; + if (importsLockOrderChecked) ++importsHeld; + return true; + } + + void unlock() noexcept { + if (importsLockOrderChecked) --importsHeld; + mutex.unlock(); + } + +private: + AgcDriver::Mutex mutex; +}; + // 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 RefreshedRange { @@ -264,7 +317,7 @@ struct RefreshedRange { }; struct HostImports { - AgcDriver::Mutex mutex; + ImportsMutex mutex; VkDevice device = VK_NULL_HANDLE; PFN_vkDestroyBuffer destroyBuffer = nullptr; PFN_vkFreeMemory freeMemory = nullptr; @@ -293,6 +346,9 @@ struct HostImports { VkDevice watchDevice = VK_NULL_HANDLE; bool unwatchImports = false; bool unwatchDmaBufImports = false; + // dma-buf imports are assumed CPU-coherent until the probe measures otherwise, so the + // fallback keeps main's pre-probe behaviour and only an incoherent measurement skips it. + bool dmaBufImportsCoherent = true; bool watchPrivateImports = false; std::map settles; #ifdef _WIN32 @@ -444,6 +500,11 @@ struct RetiredImport { ImportNode node; }; +bool holdsRangeOver(const HostImport& entry, std::uint64_t begin, std::uint64_t end) { + const auto over = [&](const std::shared_ptr& range) { return range != nullptr && range->address < end && begin < range->address + range->bytes; }; + return (entry.base < end && begin < entry.base + entry.bytes) || over(entry.range) || std::any_of(entry.ranges.begin(), entry.ranges.end(), over); +} + const GuestAllocations::Range* containingRange(const GuestAllocations::Lease& lease, std::uint64_t begin, std::uint64_t end); bool runCovers(const GuestAllocations::Lease& lease, std::uint64_t begin, std::uint64_t end); bool joinedCovers(const GuestAllocations::Lease& lease, std::uint64_t begin, std::uint64_t end); @@ -479,7 +540,7 @@ bool releaseParkedImports(HostImports& state, std::uint64_t begin, std::uint64_t for (auto device = state.parked.begin(); device != state.parked.end();) { auto& list = device->second; for (auto it = list.begin(); it != list.end();) { - if ((*it)->node.mapped().base < end && begin < (*it)->node.mapped().base + (*it)->node.mapped().bytes) { + if (holdsRangeOver((*it)->node.mapped(), begin, end)) { released.push_back(std::move(*it)); it = list.erase(it); } else { @@ -499,7 +560,7 @@ bool releaseSettleRetired(HostImports& state, std::uint64_t begin, std::uint64_t std::lock_guard lock(state.mutex); for (auto& [device, settle] : state.settles) { for (auto& batch : settle.batches) { - const bool overlaps = std::any_of(batch.retired.begin(), batch.retired.end(), [&](const std::shared_ptr& holder) { return holder->node.mapped().base < end && begin < holder->node.mapped().base + holder->node.mapped().bytes; }); + const bool overlaps = std::any_of(batch.retired.begin(), batch.retired.end(), [&](const std::shared_ptr& holder) { return holdsRangeOver(holder->node.mapped(), begin, end); }); if (!overlaps) continue; if (settleBatchRunning(settle, device, batch)) { ++state.settleUnmapWaits; @@ -602,6 +663,9 @@ const char* createHostPointerImport(const Context& context, HostImport& entry, V #ifndef _WIN32 int udmabufDevice() { +#ifdef __APPLE__ + return -1; +#endif static const int device = [] { const int result = open("/dev/udmabuf", O_RDWR | O_CLOEXEC); if (result < 0) { @@ -614,18 +678,42 @@ int udmabufDevice() { return device; } -const char* createDmaBufImport(const Context& context, HostImport& entry, int file, std::uint64_t offset, VkResult& failure) { - udmabuf_create request{}; - request.memfd = static_cast(file); - request.flags = UDMABUF_FLAGS_CLOEXEC; - request.offset = offset; - request.size = entry.bytes; +struct OwnedDescriptor { + explicit OwnedDescriptor(int descriptor) : file(descriptor) {} + OwnedDescriptor(const OwnedDescriptor&) = delete; + OwnedDescriptor& operator=(const OwnedDescriptor&) = delete; + ~OwnedDescriptor() { + if (file >= 0) close(file); + } + int Release() { return std::exchange(file, -1); } + int file; +}; + +constexpr std::size_t DmaBufRunLimit = 1024; + +const char* createDmaBufImport(const Context& context, HostImport& entry, int file, std::span runs, VkResult& failure) { + OwnedDescriptor owned(file); const int device = udmabufDevice(); - const int buffer = device < 0 ? -1 : ioctl(device, UDMABUF_CREATE, &request); - close(file); + int buffer = -1; + if (device >= 0 && runs.size() == 1) { + udmabuf_create request{}; + request.memfd = static_cast(file); + request.flags = UDMABUF_FLAGS_CLOEXEC; + request.offset = runs[0].offset; + request.size = runs[0].bytes; + buffer = ioctl(device, UDMABUF_CREATE, &request); + } else if (device >= 0) { + std::vector storage(sizeof(udmabuf_create_list) + runs.size() * sizeof(udmabuf_create_item)); + auto* request = reinterpret_cast(storage.data()); + request->flags = UDMABUF_FLAGS_CLOEXEC; + request->count = static_cast(runs.size()); + for (std::size_t index = 0; index < runs.size(); ++index) request->list[index] = udmabuf_create_item{static_cast(file), 0, runs[index].offset, runs[index].bytes}; + buffer = ioctl(device, UDMABUF_CREATE_LIST, request); + } + close(owned.Release()); if (buffer < 0) { failure = VK_ERROR_INVALID_EXTERNAL_HANDLE; - return device < 0 ? "open /dev/udmabuf" : "UDMABUF_CREATE"; + return device < 0 ? "open /dev/udmabuf" : runs.size() == 1 ? "UDMABUF_CREATE" : "UDMABUF_CREATE_LIST"; } VkMemoryFdPropertiesKHR properties{VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR}; if (const auto result = context.Function("vkGetMemoryFdPropertiesKHR")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, buffer, &properties); result != VK_SUCCESS) { @@ -817,9 +905,9 @@ VkMemoryRequirements dmaBufBufferRequirements(const Context& context, const Host return requirements; } -bool createSparseDmaBufImport(const Context& context, HostImport& entry, int file, std::uint64_t offset) { - const auto lead = offset % SparseBlockBytes; - offset -= lead; +bool createSparseDmaBufImport(const Context& context, HostImport& entry, int file, std::span runs) { + if (runs.empty()) return false; + const auto lead = runs[0].offset % SparseBlockBytes; const bool ownQueue = context.sparseQueue != VK_NULL_HANDLE && context.sparseQueueMutex != nullptr; if (!context.sparseBinding || entry.bytes <= ImportChunkBytes || (!ownQueue && !GuestMemory::GpuMutex().HeldByThisThread()) || udmabufDevice() < 0) return false; struct stat backing {}; @@ -828,8 +916,23 @@ bool createSparseDmaBufImport(const Context& context, HostImport& entry, int fil VkBuffer buffer = VK_NULL_HANDLE; const auto requirements = dmaBufBufferRequirements(context, entry, VK_BUFFER_CREATE_SPARSE_BINDING_BIT, SparseImportUsage, lead + entry.bytes, &buffer); const auto alignment = requirements.alignment; - const bool chosen = alignment != 0 && offset % alignment == 0 && ImportChunkBytes % alignment == 0 && (chunkTypes & ~requirements.memoryTypeBits) == 0 && (requirements.memoryTypeBits & chunkTypes) != 0 && - (offset + requirements.size + ImportChunkBytes - 1) / ImportChunkBytes * ImportChunkBytes <= static_cast(backing.st_size); + struct Piece { + std::uint64_t fileOffset; + VkDeviceSize resource; + VkDeviceSize bytes; + }; + std::vector pieces; + bool chosen = alignment != 0 && ImportChunkBytes % alignment == 0 && (chunkTypes & ~requirements.memoryTypeBits) == 0 && (requirements.memoryTypeBits & chunkTypes) != 0; + VkDeviceSize resource = 0; + for (std::size_t index = 0; chosen && index < runs.size(); ++index) { + const auto fileOffset = index == 0 ? runs[0].offset - lead : runs[index].offset; + const VkDeviceSize bytes = index == 0 ? lead + runs[0].bytes : runs[index].bytes; + const auto length = index + 1 == runs.size() ? requirements.size - resource : bytes; + chosen = fileOffset % alignment == 0 && resource % alignment == 0 && (index + 1 == runs.size() || bytes % alignment == 0) && resource + bytes <= requirements.size && + (fileOffset + length + ImportChunkBytes - 1) / ImportChunkBytes * ImportChunkBytes <= static_cast(backing.st_size); + pieces.push_back({fileOffset, resource, length}); + resource += bytes; + } const auto fail = [&](const char* step, VkResult result) { context.Function("vkDestroyBuffer")(context.device, buffer, nullptr); throwSparseImport(entry, step, result); @@ -840,19 +943,21 @@ bool createSparseDmaBufImport(const Context& context, HostImport& entry, int fil } auto chunks = std::make_shared>>(); std::vector binds; - for (VkDeviceSize resource = 0; resource < requirements.size;) { - const auto position = offset + resource; - const auto index = position / ImportChunkBytes; - const auto within = position - index * ImportChunkBytes; - const auto size = std::min(ImportChunkBytes - within, requirements.size - resource); - const char* step = nullptr; - VkResult failure = VK_SUCCESS; - auto chunk = importChunk(context, file, index, requirements.memoryTypeBits, step, failure); - if (chunk == nullptr) fail(step, failure); - if (((requirements.memoryTypeBits >> chunk->memoryType) & 1u) == 0) fail("the memory type of a chunk import", VK_ERROR_FORMAT_NOT_SUPPORTED); - binds.push_back({resource, size, chunk->memory, within, 0}); - chunks->push_back(std::move(chunk)); - resource += size; + for (const auto& piece : pieces) { + for (VkDeviceSize done = 0; done < piece.bytes;) { + const auto position = piece.fileOffset + done; + const auto index = position / ImportChunkBytes; + const auto within = position - index * ImportChunkBytes; + const auto size = std::min(ImportChunkBytes - within, piece.bytes - done); + const char* step = nullptr; + VkResult failure = VK_SUCCESS; + auto chunk = importChunk(context, file, index, requirements.memoryTypeBits, step, failure); + if (chunk == nullptr) fail(step, failure); + if (((requirements.memoryTypeBits >> chunk->memoryType) & 1u) == 0) fail("the memory type of a chunk import", VK_ERROR_FORMAT_NOT_SUPPORTED); + binds.push_back({piece.resource + done, size, chunk->memory, within, 0}); + chunks->push_back(std::move(chunk)); + done += size; + } } const VkSparseBufferMemoryBindInfo bufferBinds{buffer, static_cast(binds.size()), binds.data()}; VkBindSparseInfo bind{VK_STRUCTURE_TYPE_BIND_SPARSE_INFO}; @@ -886,39 +991,59 @@ bool createSparseDmaBufImport(const Context& context, HostImport& entry, int fil #endif #ifndef _WIN32 -struct BackingFile { - BackingFile() = default; - BackingFile(const BackingFile&) = delete; - BackingFile& operator=(const BackingFile&) = delete; - ~BackingFile() { - if (file >= 0) close(file); - } - int file = -1; - std::uint64_t offset = 0; -}; - -bool sharedBacking(const Context& context, std::uint64_t base, std::uint64_t bytes, BackingFile& backing) { - return context.dmaBufImport && GuestArena::GuestArenaSharedBacking_nid_postfix(static_cast(base), static_cast(bytes), &backing.file, &backing.offset); -} - -bool sharedBacking(const Context& context, const HostImport& entry, BackingFile& backing) { - return sharedBacking(context, entry.base, entry.bytes, backing); -} - -const char* createBackingImport(const Context& context, HostImport& entry, BackingFile& backing, VkResult& failure) { +// The shared-backing resolver hands out a duplicated descriptor, so this function owns it on every +// exit. Neither import path below consumes it: a udmabuf holds its own reference to the memfd, so +// without closing the duplicate here every imported buffer leaks one descriptor. +const char* createBackingImport(const Context& context, HostImport& entry, int file, const GuestArena::SharedBackingRun* runs, std::size_t count, VkResult& failure) { bool fits = false; VkResult chunkFailure = VK_SUCCESS; - if (createChunkedDmaBufImport(context, entry, backing.file, backing.offset, fits, chunkFailure) == nullptr && fits) return nullptr; - if (!fits && createSparseDmaBufImport(context, entry, backing.file, backing.offset)) return nullptr; - return createDmaBufImport(context, entry, std::exchange(backing.file, -1), backing.offset, failure); + OwnedDescriptor owned(file); + if (count == 1 && createChunkedDmaBufImport(context, entry, owned.file, runs[0].offset, fits, chunkFailure) == nullptr && fits) return nullptr; + if ((count > 1 || !fits) && createSparseDmaBufImport(context, entry, owned.file, std::span(runs, count))) return nullptr; + return createDmaBufImport(context, entry, owned.Release(), std::span(runs, count), failure); +} + +bool resolveBackingRuns(std::uint64_t rangeBase, std::uint64_t rangeBytes, int& file, std::array& runs, std::size_t& count) { + const auto base = static_cast(rangeBase); + const auto bytes = static_cast(rangeBytes); + if (GuestArena::GuestArenaSharedBackingRuns_nid_postfix(base, bytes, &file, runs.data(), runs.size(), &count)) return true; + std::uint64_t offset = 0; + if (!GuestArena::GuestArenaSharedBacking_nid_postfix(base, bytes, &file, &offset)) return false; + runs[0] = GuestArena::SharedBackingRun{offset, bytes}; + count = 1; + return true; +} + +bool resolveBackingRuns(const HostImport& entry, int& file, std::array& runs, std::size_t& count) { + return resolveBackingRuns(entry.base, entry.bytes, file, runs, count); +} + +bool sharedBacked(const Context& context, std::uint64_t base, std::uint64_t bytes) { + if (!context.dmaBufImport) return false; + int file = -1; + std::array runs{}; + std::size_t count = 0; + if (!resolveBackingRuns(base, bytes, file, runs, count)) return false; + OwnedDescriptor owned(file); + return true; +} + +const char* createSharedBackingImport(const Context& context, HostImport& entry, VkResult& failure) { + int file = -1; + std::array runs{}; + std::size_t count = 0; + if (resolveBackingRuns(entry, file, runs, count)) return createBackingImport(context, entry, file, runs.data(), count, failure); + failure = VK_ERROR_INVALID_EXTERNAL_HANDLE; + return "shared backing runs"; } #endif -const char* createImport(const Context& context, HostImport& entry, VkResult& failure) { +const char* createImport(const Context& context, HostImport& entry, VkResult& failure, bool dmaBufFallback) { const char* step = createHostPointerImport(context, entry, failure); #ifndef _WIN32 - BackingFile backing; - if (step != nullptr && sharedBacking(context, entry, backing)) step = createBackingImport(context, entry, backing, failure); + if (step != nullptr && dmaBufFallback && context.dmaBufImport) step = createSharedBackingImport(context, entry, failure); +#else + static_cast(dmaBufFallback); #endif return step; } @@ -1293,7 +1418,23 @@ void decideImportWatch(const Context& context, HostImports& state) { state.watchDevice = context.device; state.unwatchImports = false; state.unwatchDmaBufImports = false; - if (context.hostImportAlignment == 0 || !GuestMemory::WriteWatched()) return; + if (context.hostImportAlignment == 0) return; + if (!GuestMemory::WriteWatched()) { + // Without a write watch the write-protection probes cannot measure, but the dma-buf + // coherence probe still runs: its stale measurement is a plain CPU read after a GPU + // fill. Leaving it unmeasured kept dmaBufImportsCoherent at its default and skipped + // the dma-buf fallback, which forced the copy path on these devices. + if (context.dmaBufImport) { + const auto dmaBuf = ProbeDmaBufImportWriteProtection(context); + if (dmaBuf.failure != nullptr) { + std::fprintf(stderr, "[write-watch] no write watch: host imports are compared; dma-buf coherence not measured (probe failed at %s, %d), dma-buf imports %s used\n", dmaBuf.failure, static_cast(dmaBuf.result), state.dmaBufImportsCoherent ? "are" : "are not"); + } else { + state.dmaBufImportsCoherent = dmaBuf.staleAfterFill == 0; + std::fprintf(stderr, "[write-watch] no write watch: host imports are compared; dma-buf imports %s CPU-coherent (%u of %u pages stale after a GPU store over CPU writes)\n", state.dmaBufImportsCoherent ? "are" : "are not", dmaBuf.staleAfterFill, dmaBuf.pages); + } + } + return; + } if (request == ImportWatchRequest::Watch) { std::fprintf(stderr, "[write-watch] host imports stay watched (APS5_WRITE_WATCH_IMPORTS=watch)\n"); return; @@ -1321,7 +1462,8 @@ void decideImportWatch(const Context& context, HostImports& state) { return; } state.unwatchDmaBufImports = dmaBuf.writtenAfterSubmit != 0 || dmaBuf.writtenAfterFill != 0 || dmaBuf.writtenByCpu == 0; - std::fprintf(stderr, "[write-watch] dma-buf imports keep write protection: %s (%u of %u scratch pages written after a GPU read, %u after a GPU write, %u after the import, %u seen after a CPU store); imported ranges %s\n", state.unwatchDmaBufImports ? "no" : "yes", dmaBuf.writtenAfterSubmit, dmaBuf.pages, dmaBuf.writtenAfterFill, dmaBuf.writtenAtImport, dmaBuf.writtenByCpu, state.unwatchDmaBufImports ? "are compared" : "stay watched"); + state.dmaBufImportsCoherent = dmaBuf.staleAfterFill == 0; + std::fprintf(stderr, "[write-watch] dma-buf imports keep write protection: %s (%u of %u scratch pages written after a GPU read, %u after a GPU write, %u after the import, %u seen after a CPU store, %u stale after a GPU store over CPU writes); imported ranges %s%s\n", state.unwatchDmaBufImports ? "no" : "yes", dmaBuf.writtenAfterSubmit, dmaBuf.pages, dmaBuf.writtenAfterFill, dmaBuf.writtenAtImport, dmaBuf.writtenByCpu, dmaBuf.staleAfterFill, state.unwatchDmaBufImports ? "are compared" : "stay watched", state.dmaBufImportsCoherent ? "" : "; dma-buf imports are not CPU-coherent and are not used"); #endif } @@ -1369,7 +1511,7 @@ ImportPages importPages(std::uint64_t base, std::uint64_t bytes, bool opening = } const auto protection = info.Protect & 0xffu; const bool pageWritable = (info.Type == MEM_PRIVATE || info.Type == MEM_IMAGE) && writableProtection(protection); - const bool pageReadOnly = info.Type != MEM_MAPPED && (protection == PAGE_READONLY || protection == PAGE_EXECUTE_READ); + const bool pageReadOnly = (info.Type != MEM_MAPPED || (info.AllocationProtect & 0xffu) == PAGE_READONLY) && (protection == PAGE_READONLY || protection == PAGE_EXECUTE_READ); if (!pageReadOnly) pages.readOnly = false; if (info.Type != MEM_MAPPED) pages.shared = false; if (info.Type != MEM_MAPPED && !pageWritable && !pageReadOnly && pages.refused.BaseAddress == nullptr) pages.refused = info; @@ -1410,8 +1552,7 @@ bool importStaysWatched(const Context& context, HostImports& state, const Import return importWatching(context, state, !pages.writable, pages.writable, run.bytes).hostPointer; #else const auto watching = importWatching(context, state, false, true, run.bytes); - BackingFile backing; - return watching.hostPointer && (watching.dmaBuf || !sharedBacking(context, run.base, run.bytes, backing)); + return watching.hostPointer && (watching.dmaBuf || !sharedBacked(context, run.base, run.bytes)); #endif } @@ -1778,6 +1919,12 @@ std::optional hostHeapIndex(const VkPhysicalDeviceMemoryPropertie for (std::uint32_t index = 0; index < memory.memoryHeapCount; ++index) { if ((memory.memoryHeaps[index].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0 && (!heap || memory.memoryHeaps[index].size > memory.memoryHeaps[*heap].size)) heap = index; } + if (heap) return heap; + constexpr VkMemoryPropertyFlags shared = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + for (std::uint32_t index = 0; index < std::min(memory.memoryTypeCount, VK_MAX_MEMORY_TYPES); ++index) { + const auto candidate = memory.memoryTypes[index].heapIndex; + if ((memory.memoryTypes[index].propertyFlags & shared) == shared && candidate < memory.memoryHeapCount && (!heap || memory.memoryHeaps[candidate].size > memory.memoryHeaps[*heap].size)) heap = candidate; + } return heap; } @@ -2177,7 +2324,7 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import const bool watchedHostPointer = watching.hostPointer; const bool watchedDmaBuf = watching.dmaBuf; GuestMemory::ImportWatched(base, bytes, [&] { - step = watchedDmaBuf == watchedHostPointer ? createImport(context, entry, result) : createHostPointerImport(context, entry, result); + step = watchedDmaBuf == watchedHostPointer ? createImport(context, entry, result, state.dmaBufImportsCoherent) : createHostPointerImport(context, entry, result); #ifdef _WIN32 if (step == nullptr && settled && (step = settleImport(context, state, entry, result)) != nullptr) { destroyImport(context, entry); @@ -2188,11 +2335,17 @@ const HostImport* importRunAt(const Context& context, HostImports& state, Import return step == nullptr; }, watchedHostPointer); #ifndef _WIN32 - if (BackingFile backing; step != nullptr && watchedDmaBuf != watchedHostPointer && sharedBacking(context, entry, backing)) { - GuestMemory::ImportWatched(base, bytes, [&] { - step = createBackingImport(context, entry, backing, result); - return step == nullptr; - }, watchedDmaBuf, false); + if (step != nullptr && watchedDmaBuf != watchedHostPointer && state.dmaBufImportsCoherent) { + int runsFile = -1; + std::array runs{}; + std::size_t count = 0; + if (resolveBackingRuns(entry, runsFile, runs, count)) { + OwnedDescriptor owned(runsFile); + GuestMemory::ImportWatched(base, bytes, [&] { + step = createBackingImport(context, entry, owned.Release(), runs.data(), count, result); + return step == nullptr; + }, 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; @@ -3314,6 +3467,7 @@ std::shared_ptr findMirror(const Context& context, std::uint64_t be } void sweepMirrors(std::uint64_t neededBytes, std::uint64_t protectAddress) { + ReferenceStorage::Shared().ReleaseIdle(); const bool released = releaseExpiredJoinedHeaps(); auto space = Spaces().current.load(); auto& state = Mirrors(); @@ -3545,12 +3699,11 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) { const auto end = static_cast(address) + bytes; auto& imports = Imports(); if (releaseParkedImports(imports, address, end)) retiredImport = true; - const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); + const auto lease = GuestAllocations::GuestAllocationsAcquireRegistered_nid_postfix(); { std::lock_guard importsLock(imports.mutex); for (auto it = imports.imports.begin(); it != imports.imports.end();) { - const auto base = it->first; - if (base >= end || address >= base + it->second.bytes) { + if (!holdsRangeOver(it->second, address, end)) { ++it; continue; } @@ -3598,7 +3751,7 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) { { std::lock_guard importsLock(imports.mutex); for (const auto& [base, entry] : imports.imports) { - if (base < end && address < base + entry.bytes) { + if (holdsRangeOver(entry, address, end)) { overlapsImport = true; break; } @@ -3611,12 +3764,11 @@ bool WaitForLeasesAndImports(std::uintptr_t address, std::size_t bytes) { synced = true; drained = true; } - const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); + const auto lease = GuestAllocations::GuestAllocationsAcquireRegistered_nid_postfix(); { std::lock_guard importsLock(imports.mutex); for (auto it = imports.imports.begin(); it != imports.imports.end();) { - const auto base = it->first; - if (base >= end || address >= base + it->second.bytes) { + if (!holdsRangeOver(it->second, address, end)) { ++it; continue; } @@ -3856,7 +4008,7 @@ void runImportProbe(const Context& context, std::uint64_t base, std::uint64_t by HostImport import{base, bytes, VK_NULL_HANDLE, VK_NULL_HANDLE, 0}; VkBuffer destination = VK_NULL_HANDLE; VkDeviceMemory destinationMemory = VK_NULL_HANDLE; - VkCommandBuffer commands[2]{}; + VkCommandBuffer commands[3]{}; VkFence fence = VK_NULL_HANDLE; bool submitted = false; const auto run = [&]() -> const char* { @@ -3887,7 +4039,7 @@ void runImportProbe(const Context& context, std::uint64_t base, std::uint64_t by VkCommandBufferAllocateInfo allocate{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; allocate.commandPool = context.pool; allocate.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocate.commandBufferCount = 2; + allocate.commandBufferCount = 3; if ((probe.result = context.Function("vkAllocateCommandBuffers")(context.device, &allocate, commands)) != VK_SUCCESS) return "vkAllocateCommandBuffers"; VkCommandBufferBeginInfo begin{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; if ((probe.result = context.Function("vkBeginCommandBuffer")(commands[0], &begin)) != VK_SUCCESS) return "vkBeginCommandBuffer"; @@ -3898,6 +4050,9 @@ void runImportProbe(const Context& context, std::uint64_t base, std::uint64_t by if ((probe.result = context.Function("vkBeginCommandBuffer")(commands[1], &begin)) != VK_SUCCESS) return "vkBeginCommandBuffer"; context.Function("vkCmdFillBuffer")(commands[1], import.buffer, 0, bytes, 0x02020202u); if ((probe.result = context.Function("vkEndCommandBuffer")(commands[1])) != VK_SUCCESS) return "vkEndCommandBuffer"; + if ((probe.result = context.Function("vkBeginCommandBuffer")(commands[2], &begin)) != VK_SUCCESS) return "vkBeginCommandBuffer"; + context.Function("vkCmdFillBuffer")(commands[2], import.buffer, 0, bytes, 0x05050505u); + if ((probe.result = context.Function("vkEndCommandBuffer")(commands[2])) != VK_SUCCESS) return "vkEndCommandBuffer"; const VkFenceCreateInfo fenceInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; if ((probe.result = context.Function("vkCreateFence")(context.device, &fenceInfo, nullptr, &fence)) != VK_SUCCESS) return "vkCreateFence"; const auto execute = [&](VkCommandBuffer buffer) -> const char* { @@ -3919,12 +4074,17 @@ void runImportProbe(const Context& context, std::uint64_t base, std::uint64_t by if (!collect(probe.writtenAfterFill)) return "the collect after the fill"; *reinterpret_cast(base) = 2; if (!collect(probe.writtenByCpu)) return "the collect after a CPU store"; + for (std::uint64_t offset = 0; offset < bytes; offset += 4096) reinterpret_cast(base)[offset] = 4; + if (const char* step = execute(commands[2])) return step; + for (std::uint64_t offset = 0; offset < bytes; offset += 4096) { + if (reinterpret_cast(base)[offset] != 5) ++probe.staleAfterFill; + } return nullptr; }; probe.failure = run(); if (submitted) context.Function("vkQueueWaitIdle")(context.queue); if (fence != VK_NULL_HANDLE) context.Function("vkDestroyFence")(context.device, fence, nullptr); - if (commands[0] != VK_NULL_HANDLE) context.Function("vkFreeCommandBuffers")(context.device, context.pool, 2, commands); + if (commands[0] != VK_NULL_HANDLE) context.Function("vkFreeCommandBuffers")(context.device, context.pool, 3, commands); if (destination != VK_NULL_HANDLE) context.Function("vkDestroyBuffer")(context.device, destination, nullptr); if (destinationMemory != VK_NULL_HANDLE) MemoryAccounting::Free(context, destinationMemory); if (import.buffer != VK_NULL_HANDLE) destroyImport(context, import); @@ -3969,7 +4129,7 @@ ImportProbe ProbeImportWriteProtection(const Context& context) { std::uint32_t touched = 0; if (!collect(touched)) probe.failure = "GetWriteWatch"; else if (context.settleQueue == VK_NULL_HANDLE || context.settleQueueMutex == nullptr) probe.failure = "the settle queue"; - else runImportProbe(context, base, bytes, [&](HostImport& import, VkResult& result) { return createImport(context, import, result); }, collect, probe, [&](HostImport& import, VkResult& result) { return settleImport(context, Imports(), import, result); }); + else runImportProbe(context, base, bytes, [&](HostImport& import, VkResult& result) { return createImport(context, import, result, false); }, collect, probe, [&](HostImport& import, VkResult& result) { return settleImport(context, Imports(), import, result); }); VirtualFree(raw, 0, MEM_RELEASE); return probe; #else @@ -3977,10 +4137,8 @@ ImportProbe ProbeImportWriteProtection(const Context& context) { probe.failure = "host import support"; return probe; } - if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { - probe.failure = "the write watch"; - return probe; - } + // Without a write watch the write-protection fields stay empty (the collect reports none), + // but the coherence measurement still runs: a plain CPU read after a GPU fill. constexpr std::uint64_t page = 4096; const std::uint64_t alignment = std::max(context.hostImportAlignment, page); const std::uint64_t bytes = (4 * page + alignment - 1) / alignment * alignment; @@ -3993,9 +4151,9 @@ ImportProbe ProbeImportWriteProtection(const Context& context) { const auto base = (reinterpret_cast(raw) + alignment - 1) / alignment * alignment; auto* scratch = reinterpret_cast(base); for (std::uint64_t offset = 0; offset < bytes; offset += page) scratch[offset] = 1; - GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(reinterpret_cast(base), bytes); - runImportProbe(context, base, bytes, [&](HostImport& import, VkResult& result) { return createImport(context, import, result); }, [&](std::uint32_t& pages) { return collectWriteWatch(base, bytes, pages); }, probe); - GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(reinterpret_cast(base), bytes); + if (GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(reinterpret_cast(base), bytes); + runImportProbe(context, base, bytes, [&](HostImport& import, VkResult& result) { return createImport(context, import, result, false); }, [&](std::uint32_t& pages) { return collectWriteWatch(base, bytes, pages); }, probe); + if (GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(reinterpret_cast(base), bytes); munmap(raw, bytes + alignment); return probe; #endif @@ -4007,15 +4165,15 @@ ImportProbe ProbeDmaBufImportWriteProtection(const Context& context) { static_cast(context); probe.failure = "the Linux write watch"; return probe; +#elif defined(__APPLE__) + static_cast(context); + probe.failure = "dma-buf import support"; + return probe; #else if (!context.dmaBufImport) { probe.failure = "dma-buf import support"; return probe; } - if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { - probe.failure = "the write watch"; - return probe; - } constexpr std::uint64_t page = 4096; const std::uint64_t alignment = std::max(context.hostImportAlignment, page); const std::uint64_t bytes = (4 * page + alignment - 1) / alignment * alignment; @@ -4035,16 +4193,17 @@ ImportProbe ProbeDmaBufImportWriteProtection(const Context& context) { const auto base = reinterpret_cast(mapped); auto* scratch = reinterpret_cast(base); for (std::uint64_t offset = 0; offset < bytes; offset += page) scratch[offset] = 1; - GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(reinterpret_cast(base), bytes); + if (GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(reinterpret_cast(base), bytes); runImportProbe(context, base, bytes, [&](HostImport& import, VkResult& result) -> const char* { const int copy = dup(file); if (copy < 0) { result = VK_ERROR_INITIALIZATION_FAILED; return "dup"; } - return createDmaBufImport(context, import, copy, 0, result); - }, [&](std::uint32_t& pages) { return collectWriteWatch(base, bytes, pages); }, probe); - GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(reinterpret_cast(base), bytes); + const GuestArena::SharedBackingRun whole{0, import.bytes}; + return createDmaBufImport(context, import, copy, std::span(&whole, 1), result); + }, [&](std::uint32_t& pages) { return GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix() ? collectWriteWatch(base, bytes, pages) : (pages = 0, true); }, probe); + if (GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(reinterpret_cast(base), bytes); munmap(mapped, bytes); close(file); return probe; @@ -4183,7 +4342,7 @@ std::uint64_t HostImportBudget(const VkPhysicalDeviceMemoryProperties& memory, c if ((memory.memoryHeaps[index].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) == 0) hostHeap = std::max(hostHeap, memory.memoryHeaps[index].size); } const auto fromMemory = std::max(6144ull << 20u, physicalBytes / 2); - const auto reserve = std::max(2048ull << 20u, hostHeap / 2); + const auto reserve = std::max(2048ull << 20u, std::min(hostHeap / 2, 4096ull << 20u)); return hostHeap > reserve ? std::min(fromMemory, hostHeap - reserve) : fromMemory; } @@ -4285,6 +4444,16 @@ const HostImport* HostImportFor(const Context& context, std::uint64_t address, s return importAccess(context, state, address, address + bytes, lease, requester); } +void RequireGpuLockBeforeImports(const char* where) { + if (!importsLockOrderChecked || importsHeld == 0 || GuestMemory::GpuMutex().HeldByThisThread()) return; + std::fprintf(stderr, "[lock] ASSERT: %s takes GuestMemory::GpuMutex while this thread holds the host imports mutex without it\n", where); + std::abort(); +} + +bool ImportsHeldByThisThread() { + return importsHeld != 0; +} + const HostImport* ExistingHostImportFor(const Context& context, std::uint64_t address, std::size_t bytes) { if (context.hostImportAlignment == 0 || bytes == 0 || bytes > std::numeric_limits::max() - address) return nullptr; auto& state = Imports(); @@ -4312,8 +4481,9 @@ bool WatchedAfterSettle(const Context& context, std::uint64_t address, std::size bool ImportMappedRanges(const Context& context, const GuestAllocations::Mapped& ranges, std::uint64_t generation, bool adoptDevice) { if (context.hostImportAlignment == 0) return true; ensurePinWaiter(); + const bool withoutGpu = !GuestMemory::GpuMutex().HeldByThisThread(); auto& state = Imports(); - std::lock_guard lock(state.mutex); + std::unique_lock lock(state.mutex); if (!adoptDevice && (state.device != context.device || state.watchDevice != context.device)) return false; const bool adopting = state.device != context.device; GuestAllocations::Lease current; @@ -4329,11 +4499,33 @@ bool ImportMappedRanges(const Context& context, const GuestAllocations::Mapped& if (current.empty()) current = GuestAllocations::GuestAllocationsAcquireMapped_nid_no_patch(ranges); if (leasedRangeOwner(current, range->address) != range) continue; try { - const auto run = importRun(current, range->address, std::numeric_limits::max(), effectiveBudget(context, state)); + auto run = importRun(current, range->address, std::numeric_limits::max(), effectiveBudget(context, state)); + std::optional hold; + if (withoutGpu) { + auto stripes = MapImportGate::StripesOf(run.base, run.base + run.bytes); + bool owned = true; + for (;;) { + lock.unlock(); + hold.reset(); + hold.emplace(stripes); + MapImportGate::RunUnlockedHookForTest(); + lock.lock(); + if (state.device != context.device || state.watchDevice != context.device) return false; + if (state.refreshedGeneration > generation) return true; + current = GuestAllocations::GuestAllocationsAcquireMapped_nid_no_patch(ranges); + owned = leasedRangeOwner(current, range->address) == range; + if (!owned) break; + run = importRun(current, range->address, std::numeric_limits::max(), effectiveBudget(context, state)); + if (hold->Covers(run.base, run.base + run.bytes)) break; + MapImportGate::CountRetry(); + stripes |= MapImportGate::StripesOf(run.base, run.base + run.bytes); + } + if (!owned) continue; + } const bool replacing = wouldRetireImport(state, run); - if (!GuestMemory::GpuMutex().HeldByThisThread()) { + if (withoutGpu) { const std::array, 1> span{{{run.base, run.base + run.bytes}}}; - if (replacing || StorageTexture::AnyPendingOverlaps(span)) return false; + if (replacing || StorageTexture::UnwatchHookLocksGpu(span)) return false; } else if (replacing) { current = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); if (leasedRangeOwner(current, range->address) != range) continue; @@ -4350,7 +4542,7 @@ bool ImportMappedRanges(const Context& context, const GuestAllocations::Mapped& bool RegisteredReadableCovers(std::uint64_t address, std::size_t bytes, std::vector* pieces) { if (pieces != nullptr) pieces->clear(); if (bytes == 0 || bytes > std::numeric_limits::max() - address) return false; - const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); + const auto lease = GuestAllocations::GuestAllocationsAcquireRegistered_nid_postfix(); const auto end = address + bytes; for (auto at = address; at < end;) { const auto* range = containingRange(lease, at, at + 1); @@ -4363,7 +4555,7 @@ bool RegisteredReadableCovers(std::uint64_t address, std::size_t bytes, std::vec std::uint64_t RegisteredReadableEnd(std::uint64_t address) { if (address == std::numeric_limits::max()) return 0; - const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); + const auto lease = GuestAllocations::GuestAllocationsAcquireRegistered_nid_postfix(); const auto* range = containingRange(lease, address, address + 1); return range != nullptr ? range->address + range->bytes : 0; } @@ -5080,7 +5272,7 @@ void GuestBufferMemory::AddSnapshot(const GuestMemorySnapshot& snapshot) { const bool live = owner->writable || owner->hostBacked || owner->mirror != nullptr; // A live region over a unit shadow's fresh results reads the import's stale bytes here // (the publish happens when the region is bound, under the device lock): not compared. - if (live && (!checkLive || AnyShadowedOverlaps(snapshot.address, snapshot.bytes.size()))) { + if (live && (!checkLive || snapshot.compared || AnyShadowedOverlaps(snapshot.address, snapshot.bytes.size()))) { if (profile) Snapshots().skipped.fetch_add(1, std::memory_order_relaxed); return; } @@ -5151,8 +5343,13 @@ std::uint64_t gpuCopyLimit() { return limit; } -VkBufferUsageFlags gpuCopyUsage(bool addressable) { - return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | (addressable ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT : 0u); +bool changedCopyBacks(const Context& context) { + static const bool disabled = std::getenv("APS5_NO_CHANGED_COPY_BACK") != nullptr; + return !disabled && context.bufferDeviceAddress; +} + +VkBufferUsageFlags gpuCopyUsage(const Context& context, bool addressable) { + return VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | (addressable || changedCopyBacks(context) ? VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT : 0u); } // Device-local staging. A written V# element bound in place makes the shader store into host @@ -5171,10 +5368,11 @@ VkBufferUsageFlags gpuCopyUsage(bool addressable) { // (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. +// Accepted trade-off, as for the misaligned copies above: the copy-back stores only the bytes the +// shader changed where the import still holds the copy-in (RecordChangedCopy), but a CPU write +// between the copy-in and the copy-back is rolled back at the unaligned ends of a range, in a region +// that is not 4-byte aligned and without a shadow copy; 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, 8192 on a discrete GPU) bound // the written window: on an integrated GPU a kernel streaming once through a large buffer (the @@ -5258,7 +5456,11 @@ CopyStats& Copies() { // region takes the path it would take without staging (Region::unstaged). std::shared_ptr stagingBuffer(const Context& context, std::size_t bytes, VkBufferUsageFlags usage) { try { - return std::make_shared(context, bytes, usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (auto buffer = Buffer::WithinDeviceLocalHeadroom(context, bytes, usage)) return buffer; + static std::atomic reported{0}; + if (reported.fetch_add(1, std::memory_order_relaxed) < 4) std::fprintf(stderr, "[buffers] staging shadow of %zu bytes refused: its device-local heap is within an eighth of its budget\n", bytes); + Copies().stagingRefused.fetch_add(1, std::memory_order_relaxed); + return nullptr; } catch (const std::exception& error) { static std::atomic reported{0}; if (reported.fetch_add(1, std::memory_order_relaxed) < 4) std::fprintf(stderr, "[buffers] staging shadow of %zu bytes refused: %s\n", bytes, error.what()); @@ -5333,6 +5535,7 @@ void ShutdownGuestBufferWorkers() { } GuestBufferMemory::~GuestBufferMemory() { + releaseHeapReferences(); static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (!profile) return; // A staged region whose last use recorded its copy-in but no copy-back (the use threw before @@ -5388,7 +5591,10 @@ void GuestBufferMemory::resolveOverlays() { if (regions[index].overlay) members.push_back(index); } if (members.empty()) return; - std::vector drop(regions.size(), space == nullptr); + std::vector drop(regions.size(), false); + if (space == nullptr) { + for (const auto member : members) drop[member] = true; + } 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}; @@ -5547,7 +5753,7 @@ void GuestBufferMemory::UploadPrepare(bool addressable) { const bool misaligned = !bindableInPlace(HostImportOffset(*entry, region.begin), addressable); if (staged) { const auto allocateStart = std::chrono::steady_clock::now(); - region.buffer = stagingBuffer(context, static_cast(region.end - region.begin), gpuCopyUsage(addressable)); + region.buffer = stagingBuffer(context, static_cast(region.end - region.begin), gpuCopyUsage(context, addressable)); if (profile) allocateUs.fetch_add(microsecondsSince(allocateStart), std::memory_order_relaxed); if (region.buffer == nullptr) { // No shadow to be had: in place when aligned, else the host copy below. @@ -5559,7 +5765,7 @@ void GuestBufferMemory::UploadPrepare(bool addressable) { if (!staged) { if (!gpuCopyEligible(region)) continue; const auto allocateStart = std::chrono::steady_clock::now(); - region.buffer = std::make_shared(context, static_cast(region.end - region.begin), gpuCopyUsage(addressable), copyBufferProperties(false)); + region.buffer = std::make_shared(context, static_cast(region.end - region.begin), gpuCopyUsage(context, addressable), copyBufferProperties(false)); if (profile) allocateUs.fetch_add(microsecondsSince(allocateStart), std::memory_order_relaxed); } region.deviceLocal = staged; @@ -5779,7 +5985,9 @@ void GuestBufferMemory::UploadFinish(bool addressable, std::optionalbuffer; - region.copySourceBase = entry->base - entry->lead; + const auto start = HostImportBufferStart(*entry); + region.copySourceBase = start.guest; + region.copySourceAddress = start.device; } } if (region.direct != nullptr) { @@ -5802,7 +6010,7 @@ void GuestBufferMemory::UploadFinish(bool addressable, std::optional(recorder->Commands()); + recorder->OpenBatch(); StorageTexture::FlushPending(region.begin, static_cast(bytes), nullptr, "copied buffer region"); region.gpuCopy = true; gpuCopies.push_back(®ion); @@ -5955,7 +6163,11 @@ void GuestBufferMemory::recordGpuCopies(std::span copies, bool ad recorder->FlushStoresOverlapping(region->begin, static_cast(region->end - region->begin)); } VkAccessFlags covered = 0; - const auto commands = recorder->Commands(&covered); + bool inRun = false; + std::vector runAccesses; + const auto runKey = Recorder::DispatchOverlapEnabled() && recorder->RunOpen() ? Recorder::PeekUnorderedDispatch() : 0u; + if (runKey != 0) RunAccesses(runAccesses); + const auto commands = runKey != 0 ? recorder->RunCommands(runKey, this, runAccesses, inRun, &covered) : recorder->Commands(&covered); const auto timing = recorder->BeginGpuTiming(StagingCopyInKey); if (Recorder::BarrierValidate()) { std::vector> reads; @@ -5963,7 +6175,7 @@ void GuestBufferMemory::recordGpuCopies(std::span copies, bool ad recorder->NoteAccess(Recorder::CommandClass::StagingIn, Recorder::Access{reads, {}, {}, VK_PIPELINE_STAGE_TRANSFER_BIT}); } constexpr BarrierMask leading{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT}; - if (!Recorder::ElideCovered(Recorder::CommandClass::StagingIn, leading, covered)) { + if (!inRun && !Recorder::ElideCovered(Recorder::CommandClass::StagingIn, leading, covered)) { RecordMemoryBarrier(context, commands, leading.srcStage, leading.dstStage, leading.srcAccess, leading.dstAccess); Recorder::CountBarriers(Recorder::CommandClass::StagingIn); } @@ -5975,12 +6187,12 @@ void GuestBufferMemory::recordGpuCopies(std::span copies, bool ad if (region->buffer == nullptr) { // Not made by UploadPrepare (the registry was stale then): made here, under the lock. const auto allocateStart = std::chrono::steady_clock::now(); - if (region->deviceLocal) region->buffer = stagingBuffer(context, static_cast(bytes), gpuCopyUsage(addressable)); + if (region->deviceLocal) region->buffer = stagingBuffer(context, static_cast(bytes), gpuCopyUsage(context, addressable)); if (region->buffer == nullptr) { // Or no shadow to be had: a host copy serves the region instead, aligned or not. region->deviceLocal = false; region->unstaged = true; - region->buffer = std::make_shared(context, static_cast(bytes), gpuCopyUsage(addressable), copyBufferProperties(false)); + region->buffer = std::make_shared(context, static_cast(bytes), gpuCopyUsage(context, addressable), copyBufferProperties(false)); } if (profile) allocateUs.fetch_add(microsecondsSince(allocateStart), std::memory_order_relaxed); } @@ -6043,6 +6255,28 @@ void GuestBufferMemory::recordGpuCopies(std::span copies, bool ad } } } + std::vector shadowed; + for (auto* region : copies) { + const auto bytes = static_cast(region->end - region->begin); + if (!changedCopyBacks(context) || region->buffer->DeviceAddress() == 0 || !WritesOverlap(region->begin, bytes)) { + region->shadowCopy = nullptr; + continue; + } + if (region->shadowCopy == nullptr) region->shadowCopy = stagingBuffer(context, bytes, gpuCopyUsage(context, true)); + if (region->shadowCopy == nullptr || region->shadowCopy->DeviceAddress() == 0) { + region->shadowCopy = nullptr; + continue; + } + shadowed.push_back(region); + } + if (!shadowed.empty()) { + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + Recorder::CountBarriers(Recorder::CommandClass::StagingIn); + for (auto* region : shadowed) { + CopyBuffer(context, commands, region->buffer->Handle(), 0, region->shadowCopy->Handle(), 0, region->end - region->begin); + recorder->Keep(region->shadowCopy); + } + } if (stagedAny) reportStaging(); // The copied bytes are visible to the shaders bound to the buffers, which may also store into // them. Every stage: dispatches and draws share this path, and draws run mesh, task and @@ -6101,7 +6335,7 @@ void GuestBufferMemory::RecordCopyBacks(Recorder& recorder) { commands = recorder.Commands(&covered); timing = recorder.BeginGpuTiming(StagingCopyBackKey); // The shader's stores into the buffer (whichever stage made them) precede the copy. - constexpr BarrierMask leading{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT}; + constexpr BarrierMask leading{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_SHADER_READ_BIT}; if (!Recorder::ElideCovered(Recorder::CommandClass::StagingOut, leading, covered)) { RecordMemoryBarrier(context, commands, leading.srcStage, leading.dstStage, leading.srcAccess, leading.dstAccess); Recorder::CountBarriers(Recorder::CommandClass::StagingOut); @@ -6110,7 +6344,34 @@ void GuestBufferMemory::RecordCopyBacks(Recorder& recorder) { } // Exactly the sub-range the shader may write, in place in the import: what the shader // would have stored there itself had the range been bindable. - CopyBuffer(context, commands, region.buffer->Handle(), from - region.begin, region.copySource, from - region.copySourceBase, to - from); + auto whole = from; + auto wholeEnd = to; + if (region.shadowCopy != nullptr && region.copySourceAddress != 0 && region.begin % 4u == 0 && region.copySourceBase % 4u == 0) { + const auto alignedFrom = (from + 3u) & ~std::uint64_t{3}; + const auto alignedTo = to & ~std::uint64_t{3}; + static const bool traceDivergence = std::getenv("APS5_TRACE_COPY_BACK") != nullptr; + std::shared_ptr counter; + if (traceDivergence && alignedFrom < alignedTo) { + counter = std::make_shared(context, 16, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + std::memset(counter->Bytes().data(), 0, 16); + } + if (alignedFrom < alignedTo && recorder.RecordChangedCopy(commands, region.buffer->DeviceAddress() + (alignedFrom - region.begin), region.shadowCopy->DeviceAddress() + (alignedFrom - region.begin), region.copySourceAddress + (alignedFrom - region.copySourceBase), static_cast((alignedTo - alignedFrom) / 4u), counter != nullptr ? counter->DeviceAddress() : 0)) { + if (counter != nullptr) { + recorder.Keep(counter); + recorder.OnComplete([counter, alignedFrom, alignedTo, regionBegin = region.begin, regionEnd = region.end] { + std::uint32_t diverged = 0; + std::memcpy(&diverged, counter->Bytes().data(), sizeof(diverged)); + static const bool alwaysCopy = std::getenv("APS5_CHANGED_COPY_ALWAYS") != nullptr; + if (diverged != 0) std::fprintf(stderr, "[copy-back] 0x%llx+0x%llx (region 0x%llx+0x%llx): %u dwords where the shader and the CPU changed the same bytes; %s\n", static_cast(alignedFrom), static_cast(alignedTo - alignedFrom), static_cast(regionBegin), static_cast(regionEnd - regionBegin), diverged, alwaysCopy ? "the shader's bytes were stored (APS5_CHANGED_COPY_ALWAYS)" : "the CPU's bytes were kept"); + }); + } + if (from < alignedFrom) CopyBuffer(context, commands, region.buffer->Handle(), from - region.begin, region.copySource, from - region.copySourceBase, alignedFrom - from); + if (alignedTo < to) CopyBuffer(context, commands, region.buffer->Handle(), alignedTo - region.begin, region.copySource, alignedTo - region.copySourceBase, to - alignedTo); + whole = wholeEnd; + recorder.Keep(region.shadowCopy); + } + } + if (whole < wholeEnd) CopyBuffer(context, commands, region.buffer->Handle(), whole - region.begin, region.copySource, whole - region.copySourceBase, wholeEnd - whole); copiedBytes += to - from; recorder.Keep(region.buffer); if (profile) { @@ -6130,7 +6391,7 @@ void GuestBufferMemory::RecordCopyBacks(Recorder& recorder) { // indirect dispatch's arguments) and to the host once the batch completed; the caller's // pending-write note makes CPU readers wait for that. constexpr VkAccessFlags copiedAccess = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_HOST_READ_BIT; - RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, copiedAccess); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, copiedAccess); Recorder::CountBarriers(Recorder::CommandClass::StagingOut); recorder.MarkCovered(copiedAccess); recorder.EndGpuTiming(timing, copiedBytes); @@ -6160,6 +6421,7 @@ VkDescriptorBufferInfo GuestBufferMemory::Descriptor(std::uint64_t address, std: Require(address - adjustment < end, "guest buffer view starts past its copy"); range = std::min(range, end - (address - adjustment)); } + if (bytes <= context.limits.maxStorageBufferRange) range = std::min(range, context.limits.maxStorageBufferRange & ~std::uint64_t{3}); 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 ? mirrorHandle(*region.mirror) : region.buffer->Handle(); @@ -6378,6 +6640,19 @@ bool GuestBufferMemory::CopiedWritesOverlap(std::uint64_t address, std::size_t b return false; } +void GuestBufferMemory::RunAccesses(std::vector>& out) const { + if (HoldsLease()) out.push_back({0, std::numeric_limits::max(), Recorder::RunAccessWritten}); + for (const auto& region : regions) out.push_back({region.begin, region.end, 0}); + for (const auto& [begin, end] : writes) out.push_back({begin, end, Recorder::RunAccessWritten}); +} + +void GuestBufferMemory::MarkStagedWrites() const { + for (const auto& [begin, end] : writes) { + const auto* found = owner(begin, end); + if (found != nullptr && found->gpuCopy && found->direct == nullptr) GuestMemory::MarkWritten(begin, static_cast(end - begin)); + } +} + void GuestBufferMemory::MarkDirectWrites() const { for (const auto& [begin, end] : writes) { const auto* found = owner(begin, end); @@ -6392,10 +6667,12 @@ void GuestBufferMemory::MarkDirectWrites() const { namespace { -void storeChanged(std::uint64_t address, std::span current, std::span original) { +using ChangedStore = void (*)(std::uint64_t, std::span, std::span); + +void storeChanged(std::uint64_t address, std::span current, std::span original, ChangedStore store) { const auto writable = GuestMemory::DescribeCommitted(address, current.size(), true); if (writable.whole) { - GuestMemory::WriteChanged(address, current, original); + store(address, current, original); return; } const auto unchanged = [&](std::uint64_t from, std::uint64_t to) { @@ -6410,7 +6687,7 @@ void storeChanged(std::uint64_t address, std::span current, std if (cursor < first) unchanged(cursor, first); const auto at = static_cast(first - address); const auto length = static_cast(last - first); - GuestMemory::WriteChanged(first, current.subspan(at, length), original.subspan(at, length)); + store(first, current.subspan(at, length), original.subspan(at, length)); cursor = last; } if (cursor < address + current.size()) unchanged(cursor, address + current.size()); @@ -6447,9 +6724,14 @@ std::vector> mergeWrites(std::vectormirror == nullptr || !found->mirror->heap || !found->mirror->writable) continue; @@ -6458,10 +6740,16 @@ void GuestBufferMemory::takeHeapReferences() { const auto served = std::min(end, servedEnd(*found)); if (begin >= served) continue; 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)); + const auto size = static_cast(served - begin); + auto storage = ReferenceStorage::Shared().Take(size); + if (mirror.resident != nullptr) { + storage.resize(size); + mirror.resident->Read(begin, storage); + } else { + const auto bytes = mirror.buffer->Bytes().subspan(static_cast(begin - mirror.base), size); + storage.assign(bytes.begin(), bytes.end()); + } + heapReferences.emplace_back(begin, std::move(storage)); } } @@ -6469,7 +6757,8 @@ void GuestBufferMemory::WriteBack() { Require(uploaded && !committed, "guest memory cannot be committed twice or before upload"); const auto merged = mergeWrites(writes); // The CPU keeps running while the GPU works, so storing whole ranges would roll back its writes to - // bytes the shader never touched. Only runs that differ from the uploaded copy are stored. + // bytes the shader never touched. Only runs that differ from the uploaded copy are stored, and a + // host copy leaves the bytes the CPU changed since its upload as they are. for (const auto& [begin, end] : merged) { const auto* found = owner(begin, end); Require(found != nullptr, "write-back range has no GPU owner"); @@ -6497,7 +6786,7 @@ void GuestBufferMemory::WriteBack() { 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); + else storeChanged(begin, current, reference->second, GuestMemory::WriteChanged); continue; } std::vector current(length); @@ -6509,7 +6798,7 @@ void GuestBufferMemory::WriteBack() { } mirror.resident->Store(begin, current); if (mirror.sparse) storeSparse(mirror, begin, current, reference->second); - else storeChanged(begin, current, reference->second); + else storeChanged(begin, current, reference->second, GuestMemory::WriteChanged); continue; } const auto current = mirror.buffer->Bytes().subspan(offset, length); @@ -6545,7 +6834,7 @@ void GuestBufferMemory::WriteBack() { if (!region.sparse) { const auto first = static_cast(begin - region.begin); const auto length = static_cast(end - begin); - storeChanged(begin, region.buffer->Bytes().subspan(first, length), std::span(region.uploaded).subspan(first, length)); + storeChanged(begin, region.buffer->Bytes().subspan(first, length), std::span(region.uploaded).subspan(first, length), GuestMemory::WriteChangedKeepingGuest); continue; } std::size_t packed = 0; @@ -6554,7 +6843,7 @@ void GuestBufferMemory::WriteBack() { const auto to = std::min(last, end); if (from < to) { const auto length = static_cast(to - from); - storeChanged(from, region.buffer->Bytes().subspan(static_cast(from - region.begin), length), std::span(region.uploaded).subspan(packed + static_cast(from - first), length)); + storeChanged(from, region.buffer->Bytes().subspan(static_cast(from - region.begin), length), std::span(region.uploaded).subspan(packed + static_cast(from - first), length), GuestMemory::WriteChangedKeepingGuest); } packed += static_cast(last - first); } @@ -6562,8 +6851,7 @@ void GuestBufferMemory::WriteBack() { committed = true; lease.clear(); space.reset(); - heapReferences.clear(); - heapReadbacks.clear(); + releaseHeapReferences(); } std::optional>> GuestBufferMemory::DirectRegions() const { @@ -6664,6 +6952,7 @@ void GuestBufferMemory::RecordStagingCopies(Recorder& recorder) { if (!uploaded || committed) return; static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; std::vector copies; + if (std::any_of(regions.begin(), regions.end(), [](const Region& region) { return region.gpuCopy && region.deviceLocal && !region.copiedBack; })) recorder.CloseRunOwing(this); for (auto& region : regions) { if (!region.gpuCopy || !region.deviceLocal) continue; // The previous use's copy-back was recorded by its MarkGpuWrites (or never, when that use diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp index 50c81d226f..76ac17848e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp @@ -91,8 +91,8 @@ GuestSamplerResource DecodeSamplerResource(std::span words, Require(!forceUnormCoords || unnormalizedProven, "guest sampler descriptor uses unnormalized coordinates which are not implemented"); if (forceUnormCoords) { Require(!isAnisoFilter(xyMagFilter) && !isAnisoFilter(xyMinFilter), "guest sampler descriptor uses unnormalized coordinates with anisotropic filtering, which is not implemented"); - Require(clampX == 0u || clampX == 2u || clampX == 6u, "guest sampler descriptor uses unnormalized coordinates with clamp mode " + std::to_string(clampX) + " on X; only clamp-to-last-texel and clamp-to-border are implemented"); - Require(clampY == 0u || clampY == 2u || clampY == 6u, "guest sampler descriptor uses unnormalized coordinates with clamp mode " + std::to_string(clampY) + " on Y; only clamp-to-last-texel and clamp-to-border are implemented"); + Require(clampX <= 3u || clampX == 6u, "guest sampler descriptor uses unnormalized coordinates with clamp mode " + std::to_string(clampX) + " on X; only wrap, mirror, clamp-to-last-texel, mirror-once and clamp-to-border are implemented"); + Require(clampY <= 3u || clampY == 6u, "guest sampler descriptor uses unnormalized coordinates with clamp mode " + std::to_string(clampY) + " on Y; only wrap, mirror, clamp-to-last-texel, mirror-once and clamp-to-border are implemented"); Require(!mcCoordTrunc, "guest sampler descriptor uses unnormalized coordinates with MC_COORD_TRUNC, which is not implemented"); } else { Require(!unnormalizedProven, "guest sampler descriptor is bound as unnormalized without FORCE_UNNORMALIZED"); @@ -172,6 +172,16 @@ GuestSamplerResource DecodeSamplerResource(std::span words, return result; } +bool HoldsLodAtZero(std::span words) { + return (words[0] & (1u << 15u)) == 0u && (((words[2] >> 26u) & 3u) == 0u || ((words[1] >> 12u) & 0xfffu) == 0u); +} + +std::array PinnedLodSamplerWords(std::span words, std::uint32_t lod) { + std::array pinned{words[0], (words[1] & ~0xffffffu) | lod | (lod << 12u), words[2], words[3]}; + if (((pinned[2] >> 26u) & 3u) == 0u) pinned[2] |= 1u << 26u; + return pinned; +} + std::optional> SingleLevelSamplerWords(std::span words, bool singleLevelImage, bool mipmappedImage) { const auto filters = words[2]; if (((filters >> 29u) & 1u) == 0 || !singleLevelImage || (!isAnisoFilter((filters >> 20u) & 3u) && !isAnisoFilter((filters >> 22u) & 3u))) return std::nullopt; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp index 7e47d60a47..400d8bc588 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp @@ -64,6 +64,26 @@ TextureTileMode resolveGcnTileMode(std::uint32_t index) { } } +TextureTileMode resolveDescriptorTileMode(std::span words, bool gcn) { + return gcn ? resolveGcnTileMode(ShaderRecompiler::GcnImageTilingIndex(words[7])) : resolveTileMode((words[3] >> 20u) & 0x1fu); +} + +std::uint64_t descriptorBaseAddress(std::span words) { + Require(words.size() == 8, "guest texture descriptor must contain 8 dwords"); + const auto base40 = (static_cast(words[0]) | (static_cast(words[1]) << 32u)) & 0xffffffffffull; + return base40 << 8u; +} + +std::uint64_t normalizedDescriptorBaseAddress(std::uint64_t baseAddress, TextureTileMode tileMode, std::uint32_t* pipeBankXor) { + auto xorBytes = 0u; + if (XorSwizzleMode(tileMode) != 0) { + xorBytes = static_cast(baseAddress & (tileMode == TextureTileMode::kS4KBX || tileMode == TextureTileMode::kD4KBX ? 0xfffu : 0xffffu)); + baseAddress -= xorBytes; + } + if (pipeBankXor != nullptr) *pipeBankXor = xorBytes; + return baseAddress; +} + TextureDimension resolveDimension(std::uint32_t raw) { switch (raw) { case 8: return TextureDimension::k1D; @@ -118,6 +138,21 @@ std::optional> R128ResidentLevelWords(std::span TextureResourceAllocationAddress(std::span words) { + const auto baseAddress = descriptorBaseAddress(words); + if (baseAddress == 0) return std::nullopt; + const bool gcn = ShaderRecompiler::GcnGuestShaders(); + TextureTileMode tileMode; + try { + tileMode = resolveDescriptorTileMode(words, gcn); + } catch (const std::runtime_error&) { + return std::nullopt; + } + const auto normalized = normalizedDescriptorBaseAddress(baseAddress, tileMode, nullptr); + if (normalized == 0) return std::nullopt; + return normalized; +} + bool IsNullTextureResource(std::span words) { Require(words.size() == 8, "guest texture descriptor must contain 8 dwords"); const auto base40 = (static_cast(words[0]) | (static_cast(words[1]) << 32u)) & 0xffffffffffull; @@ -125,6 +160,14 @@ bool IsNullTextureResource(std::span words) { return base40 == 0 || type <= 1; } +bool IsNullSampledTextureResource(std::span words) { + if (IsNullTextureResource(words)) return true; + const auto type = static_cast((words[3] >> 28u) & 0xfu); + if (type < ShaderRecompiler::ImageType::Color1D || type > ShaderRecompiler::ImageType::Color2DMsaa) return false; + const auto format = (words[1] >> 20u) & 0x1ffu; + return format == 0u || format == 0x1ffu; +} + GuestTextureResource DecodeTextureResource(std::span descriptor) { Require(descriptor.size() == 8, "guest texture descriptor must contain 8 dwords"); const bool gcn = ShaderRecompiler::GcnGuestShaders(); @@ -134,8 +177,7 @@ GuestTextureResource DecodeTextureResource(std::span descri if (gcn) local[6] = local[7] = 0u; const std::span words(local); - const auto base40 = (static_cast(words[0]) | (static_cast(words[1]) << 32u)) & 0xffffffffffull; - auto baseAddress = base40 << 8u; + auto baseAddress = descriptorBaseAddress(words); Require(baseAddress != 0, "guest texture descriptor has a null base address"); const auto minLod = (words[1] >> 8u) & 0xfffu; @@ -225,7 +267,7 @@ GuestTextureResource DecodeTextureResource(std::span descri TextureTileMode tileMode; TextureDimension dimension; try { - tileMode = gcn ? resolveGcnTileMode(ShaderRecompiler::GcnImageTilingIndex(gcnTiling)) : resolveTileMode(tileModeRaw); + tileMode = resolveDescriptorTileMode(descriptor, gcn); dimension = resolveDimension(typeRaw); } catch (const std::exception& error) { throw std::runtime_error(std::string(error.what()) + describe()); @@ -241,21 +283,22 @@ GuestTextureResource DecodeTextureResource(std::span descri // hardware never addresses them, so the view ends at the surface's last level. if (baseLevel <= maxMip) lastLevel = std::min(lastLevel, maxMip); std::uint32_t pipeBankXor = 0; - if (XorSwizzleMode(tileMode) != 0) { - pipeBankXor = static_cast(baseAddress & (tileMode == TextureTileMode::kS4KBX || tileMode == TextureTileMode::kD4KBX ? 0xfffu : 0xffffu)); - baseAddress -= pipeBankXor; - Require(baseAddress != 0, "guest texture descriptor has a null base address"); - } + baseAddress = normalizedDescriptorBaseAddress(baseAddress, tileMode, &pipeBankXor); + Require(baseAddress != 0, "guest texture descriptor has a null base address"); + std::uint32_t linearPitch = 0; switch (dimension) { case TextureDimension::k1D: Require(height == 1 && depth == 0 && baseArray == 0, "guest 1D texture descriptor has a nonzero height, depth or base array"); break; case TextureDimension::k2D: if (depth != 0 && tileMode == TextureTileMode::kLinear) { + Require(!IsBlockCompressed(format) && maxMip == 0 && baseArray == 0, "guest 2D linear texture descriptor sets a pitch on a block-compressed, mipmapped or base-array surface, which is not implemented" + describe()); const auto elementsPerPitchUnit = 256u / BytesPerElement(format); const auto defaultPitch = (width + elementsPerPitchUnit - 1u) / elementsPerPitchUnit * elementsPerPitchUnit; - Require(!IsBlockCompressed(format) && maxMip == 0 && baseArray == 0 && depth + 1u == defaultPitch, "guest 2D linear texture descriptor sets a custom pitch other than its default pitch, which is not implemented" + describe()); + Require(!ShaderRecompiler::GcnGuestShaders() || depth + 1u == defaultPitch, "guest 2D linear texture descriptor of a PS4 guest sets a custom pitch other than its default pitch, which is not implemented" + describe()); + const auto pitch = (std::max(depth + 1u, width) + elementsPerPitchUnit - 1u) / elementsPerPitchUnit * elementsPerPitchUnit; + linearPitch = pitch == defaultPitch ? 0u : pitch; depth = 0; } Require(depth == 0 && baseArray == 0, "guest 2D texture descriptor has a nonzero depth or base array"); @@ -314,6 +357,7 @@ GuestTextureResource DecodeTextureResource(std::span descri result.gcnLinearGeneral = ShaderRecompiler::GcnImageTilingIndex(gcnTiling) == 31u; Require(result.gcnPitch >= width, "PS4 texture descriptor has a pitch below its width" + describe()); } + result.linearPitch = linearPitch; if (baseLevel > maxMip) { const bool fits = LevelsFitAllocation(result, lastLevel + 1u); if (!fits && ZeroTail(words)) { @@ -328,7 +372,7 @@ GuestTextureResource DecodeTextureResource(std::span descri 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.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 && resource.linearPitch == 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"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/MapImportGate.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/MapImportGate.cpp new file mode 100644 index 0000000000..ff6a247913 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/MapImportGate.cpp @@ -0,0 +1,247 @@ +#include "prx/libSceAgcDriver/Graphics/include/MapImportGate.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver::Graphics::MapImportGate { + +namespace { + +struct alignas(64) Stripe { + std::shared_mutex mutex; +}; + +std::array& StripeLocks() { + static std::array stripes; + return stripes; +} + +alignas(64) std::atomic fastPublishers{0}; +alignas(64) std::atomic activeImports{0}; +alignas(64) std::atomic waitingPublishers{0}; +std::atomic drainingImports{0}; +std::atomic fastPublications{0}; +std::atomic stripedPublications{0}; +std::atomic waitedPublications{0}; +std::atomic importHolds{0}; +std::atomic drainWaits{0}; +std::atomic retries{0}; +std::atomic unlockedHookForTest{nullptr}; + +struct PublisherState { + std::uint32_t depth = 0; + bool fast = false; + Stripes held; +}; + +thread_local PublisherState publisher; +thread_local std::uint32_t leafLocksHeld = 0; + +std::uint32_t Lowest(const Stripes& stripes) { + for (std::uint32_t index = 0; index < StripeCount; ++index) { + if (stripes[index]) return index; + } + return StripeCount; +} + +std::uint32_t Highest(const Stripes& stripes) { + for (std::uint32_t index = StripeCount; index-- > 0;) { + if (stripes[index]) return index; + } + return 0; +} + +void LockShared(const Stripes& stripes) { + auto& locks = StripeLocks(); + for (std::uint32_t index = 0; index < StripeCount; ++index) { + if (!stripes[index]) continue; + if (locks[index].mutex.try_lock_shared()) continue; + waitingPublishers.fetch_add(1, std::memory_order_acq_rel); + locks[index].mutex.lock_shared(); + waitingPublishers.fetch_sub(1, std::memory_order_acq_rel); + waitedPublications.fetch_add(1, std::memory_order_relaxed); + } +} + +void UnlockShared(const Stripes& stripes) { + auto& locks = StripeLocks(); + for (std::uint32_t index = StripeCount; index-- > 0;) { + if (stripes[index]) locks[index].mutex.unlock_shared(); + } +} + +void Backoff(std::uint32_t round) { + if (round < 16) { + std::this_thread::yield(); + return; + } + const auto shift = std::min(round - 16, 6); + std::this_thread::sleep_for(std::chrono::microseconds(16u << shift)); +} + +} + +Stripes StripesOf(std::uint64_t begin, std::uint64_t end) { + Stripes stripes; + if (end <= begin) return stripes; + const auto first = begin >> StripeShift; + const auto last = (end - 1) >> StripeShift; + if (last - first + 1 >= StripeCount) return stripes.set(); + for (auto index = first; index <= last; ++index) stripes.set(static_cast(index % StripeCount)); + return stripes; +} + +bool LockOrderChecked() { +#ifdef NDEBUG + static const bool checked = std::getenv("APS5_ASSERT_GPU_LOCK") != nullptr; + return checked; +#else + return true; +#endif +} + +void NoteLeafLock() noexcept { + if (LockOrderChecked()) ++leafLocksHeld; +} + +void NoteLeafUnlock() noexcept { + if (LockOrderChecked()) --leafLocksHeld; +} + +void RequireNoLeafLockHeld(const char* where) { + if (!LockOrderChecked()) return; + if (leafLocksHeld == 0 && !ImportsHeldByThisThread()) return; + std::fprintf(stderr, "[lock] ASSERT: %s waits for the map-time import gate while this thread holds %s\n", where, leafLocksHeld != 0 ? "the live or pending storage image mutex" : "the host imports mutex"); + std::abort(); +} + +PublishHold::PublishHold(std::uint64_t address, std::uint64_t bytes) { + if (bytes == 0) return; + const auto wanted = StripesOf(address, address + bytes); + if (publisher.depth != 0) { + if (!publisher.fast) { + const auto missing = wanted & ~publisher.held; + if (missing.any()) { + Require(Lowest(missing) > Highest(publisher.held), "a nested storage image publication needs a map-time import gate stripe below one its thread already holds"); + RequireNoLeafLockHeld("a storage image publication"); + LockShared(missing); + publisher.held |= missing; + added = missing; + } + } + ++publisher.depth; + return; + } + RequireNoLeafLockHeld("a storage image publication"); + publisher.depth = 1; + fastPublishers.fetch_add(1, std::memory_order_seq_cst); + if (activeImports.load(std::memory_order_seq_cst) == 0) { + publisher.fast = true; + mode = Mode::Fast; + fastPublications.fetch_add(1, std::memory_order_relaxed); + return; + } + fastPublishers.fetch_sub(1, std::memory_order_release); + try { + LockShared(wanted); + } catch (...) { + publisher.depth = 0; + throw; + } + publisher.held = wanted; + added = wanted; + mode = Mode::Striped; + stripedPublications.fetch_add(1, std::memory_order_relaxed); +} + +PublishHold::~PublishHold() { + switch (mode) { + case Mode::Fast: + publisher.fast = false; + publisher.depth = 0; + fastPublishers.fetch_sub(1, std::memory_order_release); + return; + case Mode::Striped: + UnlockShared(added); + publisher.held.reset(); + publisher.depth = 0; + return; + case Mode::Nested: + if (publisher.depth == 0) return; + --publisher.depth; + if (added.none()) return; + UnlockShared(added); + publisher.held &= ~added; + return; + } +} + +ImportHold::ImportHold(const Stripes& stripes) : held(stripes) { + Require(publisher.depth == 0, "a map-time import without the GPU lock runs inside a storage image publication of its own thread"); + activeImports.fetch_add(1, std::memory_order_seq_cst); + if (fastPublishers.load(std::memory_order_seq_cst) != 0) { + drainWaits.fetch_add(1, std::memory_order_relaxed); + drainingImports.fetch_add(1, std::memory_order_acq_rel); + for (std::uint32_t round = 0; fastPublishers.load(std::memory_order_seq_cst) != 0; ++round) Backoff(round); + drainingImports.fetch_sub(1, std::memory_order_acq_rel); + } + auto& locks = StripeLocks(); + for (std::uint32_t index = 0; index < StripeCount; ++index) { + if (held[index]) locks[index].mutex.lock(); + } + importHolds.fetch_add(1, std::memory_order_relaxed); +} + +ImportHold::~ImportHold() { + auto& locks = StripeLocks(); + for (std::uint32_t index = StripeCount; index-- > 0;) { + if (held[index]) locks[index].mutex.unlock(); + } + activeImports.fetch_sub(1, std::memory_order_release); +} + +bool ImportHold::Covers(std::uint64_t begin, std::uint64_t end) const { + const auto wanted = StripesOf(begin, end); + return (wanted & ~held).none(); +} + +Counters CountersForTest() { + Counters counters; + counters.fastPublications = fastPublications.load(std::memory_order_relaxed); + counters.stripedPublications = stripedPublications.load(std::memory_order_relaxed); + counters.waitedPublications = waitedPublications.load(std::memory_order_relaxed); + counters.importHolds = importHolds.load(std::memory_order_relaxed); + counters.drainWaits = drainWaits.load(std::memory_order_relaxed); + counters.retries = retries.load(std::memory_order_relaxed); + return counters; +} + +std::uint32_t WaitingPublishersForTest() { + return waitingPublishers.load(std::memory_order_acquire); +} + +std::uint32_t DrainingImportsForTest() { + return drainingImports.load(std::memory_order_acquire); +} + +void CountRetry() noexcept { + retries.fetch_add(1, std::memory_order_relaxed); +} + +void SetUnlockedHookForTest(void (*hook)()) { + unlockedHookForTest.store(hook, std::memory_order_release); +} + +void RunUnlockedHookForTest() { + if (const auto hook = unlockedHookForTest.load(std::memory_order_acquire)) hook(); +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp index ea52e8d76c..44f96ea533 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp @@ -13,15 +13,19 @@ #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/MultisampleTextureLayout.hpp" #include "IntermediateRepresentation/IrMetadata/BufferFormat.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/PrimedSharedMutex.hpp" #include #include #include #include +#include #include #include +#include #include #include #include @@ -133,6 +137,28 @@ VkClearColorValue fastClearColor(const ColorTarget& target) { return clear; } +std::uint64_t guardedColorBytes(const ColorTarget& target) { + 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); + const auto log2Pixels = 16u - std::countr_zero(target.elementBytes) - log2Samples; + const auto log2Width = (log2Pixels + ((log2Samples & 1u) == 0u ? 1u : 0u)) / 2u; + const auto blockWidth = 1u << log2Width; + const auto blockHeight = 1u << (log2Pixels - log2Width); + const auto align = [](std::uint32_t extent, std::uint32_t block) { return (static_cast(extent) + block - 1u) / block * block; }; + bytes = align(target.extent.width, blockWidth) * align(target.extent.height, blockHeight) * target.elementBytes * target.samples; + } + return bytes; +} + +std::optional texelByte(VkFormat format, const VkClearColorValue& value) { + if (format == VK_FORMAT_R8_UINT) return value.uint32[0] <= 255u ? std::optional(static_cast(value.uint32[0])) : std::nullopt; + if (format != VK_FORMAT_R8_UNORM) return std::nullopt; + const auto code = std::lround(value.float32[0] * 255.0f); + if (code < 0 || code > 255 || static_cast(code) / 255.0f != value.float32[0]) return std::nullopt; + return static_cast(code); +} + template void recordCommands(const Context& context, Record&& record) { auto* recorder = Recorder::Active(); @@ -151,10 +177,11 @@ struct TargetUse { class MultisampleTarget : public std::enable_shared_from_this { public: - MultisampleTarget(const Context& context, const ColorTarget& target) : context(context), target(target), cmask(target.cmaskAddress, target.cmaskBytes), colorFill(target) { + MultisampleTarget(const Context& context, const ColorTarget& target, bool guestSamples = false) : context(context), target(target), cmask(target.cmaskAddress, target.cmaskBytes), colorFill(target) { this->context.bufferPool.reset(); if (target.dccAddress != 0) metadataValid = std::make_shared>(true); const auto initial = initialDccClear(target); + if (target.dccAddress != 0) cleared = initial; const auto samples = static_cast(target.samples); Require((context.limits.framebufferColorSampleCounts & samples) != 0, "the device cannot render color with the target's sample count"); VkFormatProperties properties{}; @@ -183,25 +210,64 @@ public: return (uintProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0 && storageSupports(uintFormat, VK_IMAGE_USAGE_STORAGE_BIT, 0u); }; 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) || !uintStorageSupported())) storageFormat = VK_FORMAT_UNDEFINED; + VkImageCreateFlags flags = orderFlags; + std::shared_ptr upload; + const auto elementFormat = guestSamples ? TextureDetiler::ImageElementFormat(target.elementBytes) : VK_FORMAT_UNDEFINED; + const auto alignment = std::max({4u, target.elementBytes, context.limits.minStorageBufferOffsetAlignment}); + const auto stride = context.limits.maxStorageBufferRange / alignment * alignment; + if (guestSamples) { + Require(context.detiler != nullptr && context.storageImageMultisample && context.storageImageWriteWithoutFormat, "uploading multisampled guest color requires formatless multisampled storage image writes"); + Require(stride != 0, "uploading multisampled guest color needs a storage range of at least one aligned element"); + flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT; + Require(storageSupports(elementFormat, VK_IMAGE_USAGE_STORAGE_BIT, 0u) && storageSupports(target.format, usage | VK_IMAGE_USAGE_STORAGE_BIT, flags), "the device cannot upload multisampled guest color through an unsigned storage view"); + usage |= VK_IMAGE_USAGE_STORAGE_BIT; + upload = guestSampleUpload(target); + } 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; + const VkImageCreateFlags twinFlags = flags | 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); + const auto created = allocate(usage, flags); image = created.image; memory = created.memory; view = created.view; allocationBytes = created.bytes; + if (upload) { + VkImageViewUsageCreateInfo storageUsage{VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO}; + storageUsage.usage = VK_IMAGE_USAGE_STORAGE_BIT; + VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + info.pNext = &storageUsage; + info.image = image; + info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + info.format = elementFormat; + info.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + Check(context.Function("vkCreateImageView")(context.device, &info, nullptr, &uploadView), "vkCreateImageView multisampled guest color upload"); + } recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) { - recordToGeneral(commands, image); - const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - context.Resolved(&DeviceFunctions::cmdClearColorImage, "vkCmdClearColorImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &initial, 1, &range); - RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); - if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 2); + if (!upload) { + recordToGeneral(commands, image); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + context.Resolved(&DeviceFunctions::cmdClearColorImage, "vkCmdClearColorImage")(commands, image, VK_IMAGE_LAYOUT_GENERAL, &initial, 1, &range); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 2); + return; + } + recordToGeneral(commands, image, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + const auto bytes = static_cast(target.extent.width) * target.extent.height * target.samples * target.elementBytes; + for (std::uint64_t offset = 0; offset < bytes; offset += stride) { + const auto count = std::min(stride, bytes - offset); + context.detiler->DispatchMultisampleImage(commands, target.elementBytes, upload->Handle(), offset, (count + 3u) & ~std::uint64_t{3}, uploadView, target.extent.width, target.extent.height, 1u, target.samples, static_cast(offset / target.elementBytes), static_cast(count / target.elementBytes)); + } + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + if (recorder != nullptr) { + recorder->Keep(upload, upload->Bytes().size()); + Recorder::CountBarriers(Recorder::CommandClass::Draw, 3); + } }); } catch (...) { release(); @@ -275,16 +341,16 @@ public: if (allocation.memory) MemoryAccounting::Free(context, allocation.memory); } - void recordToGeneral(VkCommandBuffer commands, VkImage destination) { + void recordToGeneral(VkCommandBuffer commands, VkImage destination, VkPipelineStageFlags destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT, VkAccessFlags destinationAccess = VK_ACCESS_TRANSFER_WRITE_BIT) { VkImageMemoryBarrier toGeneral{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; - toGeneral.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + toGeneral.dstAccessMask = destinationAccess; toGeneral.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; toGeneral.newLayout = VK_IMAGE_LAYOUT_GENERAL; toGeneral.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toGeneral.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toGeneral.image = destination; toGeneral.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 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); + context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &toGeneral); } void makeStorable(Recorder& recorder) { @@ -354,6 +420,8 @@ public: RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 2); }); + cleared = clear; + drawn = false; } void Recreate(const ColorTarget& use) { @@ -368,6 +436,8 @@ public: fmaskCopy = {}; metadataValid = std::make_shared>(true); const auto initial = initialDccClear(use); + cleared = initial; + drawn = false; recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) { RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; @@ -418,6 +488,7 @@ public: if (recorder != nullptr) Recorder::CountBarriers(Recorder::CommandClass::Draw, 1); }); storageWritten = true; + drawn = true; return selectedView; } @@ -557,6 +628,32 @@ public: std::uint64_t htileAddress = 0; std::uint64_t htileBytes = 0; std::string htileRefusal; + std::optional cleared; + bool drawn = false; + + MultisampleColorAlias Alias() const { + MultisampleColorAlias alias{target.address, guardedColorBytes(target), target.format, target.samples}; + const auto newest = [&](MultisampleColorContent content, std::optional value) { + alias.newest = content; + alias.value = value; + alias.byte = value ? texelByte(target.format, *value) : std::nullopt; + return alias; + }; + if (!cmask.Refusal().empty() || !colorFill.Refusal().empty() || !htileRefusal.empty() || dccFill || cmaskFill) return newest(MultisampleColorContent::Unknown, std::nullopt); + if (cmask.Keys() == DccKeys::Clear0000) { + try { + return newest(MultisampleColorContent::FastClear, fastClearColor(target)); + } catch (const std::exception&) { + return newest(MultisampleColorContent::Unknown, std::nullopt); + } + } + if (const auto fill = colorFill.Pending()) { + VkClearColorValue clear{}; + return MultisampledFillClearColor(target, *fill, clear) ? newest(MultisampleColorContent::Fill, clear) : newest(MultisampleColorContent::Unknown, std::nullopt); + } + if (drawn) return newest(MultisampleColorContent::Drawn, std::nullopt); + return newest(cleared ? MultisampleColorContent::Clear : MultisampleColorContent::Unknown, cleared); + } private: bool sampleable = false; @@ -643,6 +740,22 @@ private: return true; } + VkImageView uploadView = VK_NULL_HANDLE; + + std::shared_ptr guestSampleUpload(const ColorTarget& use) { + Require(use.cmaskAddress == 0 && use.dccAddress == 0, "uploading multisampled guest color under compression metadata is not implemented"); + Require(use.layers == 1u && use.arraySlice == 0u && use.arrayLastSlice == 0u && use.depth == 1u && use.depthSlice == 0u && use.mipCount == 1u && use.mip == 0u && !use.mipTail, "uploading multisampled guest color of arrays, volumes or mips is not implemented"); + Require(use.tileMode == ColorTileMode::RenderTarget || use.tileMode == ColorTileMode::Z64KBX, "non-uniform multisampled guest color tiling is not implemented"); + const MultisampleTextureLayout layout(use.tileMode == ColorTileMode::Z64KBX ? TextureTileMode::kZ64KBX : TextureTileMode::kR64KBX, use.elementBytes, use.samples, use.extent.width, use.extent.height, 1u, use.pipeBankXor); + Require(layout.Bytes() == MultisampledColorBytes(use) && layout.Bytes() <= std::numeric_limits::max(), "the multisampled guest color layout and footprint differ"); + Require(layout.LinearBytes() / use.elementBytes <= std::numeric_limits::max(), "the multisampled guest color element count is unsupported"); + std::vector guest(static_cast(layout.Bytes())); + GuestMemory::Read(use.address, guest); + auto staging = std::make_shared(context, static_cast((layout.LinearBytes() + 3u) & ~std::uint64_t{3}), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + layout.Detile(guest, staging->Bytes().first(static_cast(layout.LinearBytes()))); + return staging; + } + VkClearColorValue initialDccClear(const ColorTarget& use) { if (use.dccAddress == 0) return {}; const auto dccBytes = MultisampledDccBytes(use); @@ -754,6 +867,7 @@ private: firstSampleTextures.clear(); firstSampleCopy.reset(); for (const auto& [format, resolvePass] : resolvePasses) context.Function("vkDestroyRenderPass")(context.device, resolvePass, nullptr); + if (uploadView) context.Function("vkDestroyImageView")(context.device, uploadView, 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); @@ -774,14 +888,6 @@ struct GuardedRange { bool fmask = false; }; -class PrimedSharedMutex : public std::shared_mutex { -public: - PrimedSharedMutex() { - lock(); - unlock(); - } -}; - std::shared_mutex& guardedMutex() { static PrimedSharedMutex mutex; return mutex; @@ -855,20 +961,6 @@ void retireUnmapped(std::uintptr_t address, std::size_t bytes) { anyGuarded().store(!guarded().empty(), std::memory_order_release); } -std::uint64_t guardedColorBytes(const ColorTarget& target) { - 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); - const auto log2Pixels = 16u - std::countr_zero(target.elementBytes) - log2Samples; - const auto log2Width = (log2Pixels + ((log2Samples & 1u) == 0u ? 1u : 0u)) / 2u; - const auto blockWidth = 1u << log2Width; - const auto blockHeight = 1u << (log2Pixels - log2Width); - const auto align = [](std::uint32_t extent, std::uint32_t block) { return (static_cast(extent) + block - 1u) / block * block; }; - bytes = align(target.extent.width, blockWidth) * align(target.extent.height, blockHeight) * target.elementBytes * target.samples; - } - return bytes; -} - void claimOverlapping(std::uint64_t address, std::uint64_t bytes, const TargetUse* claimant) { for (const auto& range : guarded()) { if (!range.fmask && range.use.get() != claimant && address < range.address + range.bytes && range.address < address + bytes) range.use->claimed.store(true, std::memory_order_release); @@ -1025,21 +1117,16 @@ MultisampleTarget& createFilled(const Context& context, const GuestTextureResour if (!htileCleared) std::memcpy(&value, memory, sizeof(value)); char held[16]; std::snprintf(held, sizeof(held), "0x%08x", value); - const auto repeated = htileCleared ? memory + bytes : std::mismatch(memory + sizeof(value), memory + bytes, memory).first; - if (repeated != memory + bytes) { - char offset[32]; - std::snprintf(offset, sizeof(offset), "+0x%llx", static_cast(repeated - memory)); - refuse(std::string("whose color memory ") + range + " does not hold one repeated 32-bit value (" + held + " until " + offset + ")"); - } + const bool uniform = htileCleared || std::equal(memory + sizeof(value), memory + bytes, memory); VkClearColorValue clear{}; - if (!MultisampledFillClearColor(use, value, clear)) refuse(std::string("whose color memory ") + range + " holds " + held + ", which has no exact clear value in its format"); - auto target = std::make_shared(context, use); + if (uniform && !MultisampledFillClearColor(use, value, clear)) refuse(std::string("whose color memory ") + range + " holds " + held + ", which has no exact clear value in its format"); + auto target = std::make_shared(context, use, !uniform); target->Keep(); targets().push_back(std::move(target)); auto& created = *targets().back(); created.use->hostOnly.store(written, std::memory_order_release); guard(context, use, created.active, created.metadataValid, created.use); - created.colorFill.NoteFill(use.address, static_cast(bytes), value); + if (uniform) created.colorFill.NoteFill(use.address, static_cast(bytes), value); created.fromTexture = true; created.htileAddress = resource.dccAddress; created.htileBytes = htileBytes; @@ -1203,6 +1290,7 @@ VkClearColorValue MultisampledDccClear(const ColorTarget& target, std::span(dcc.front())) { + case 0x20u: return fastClearColor(target); case 0x00u: keys = DccKeys::Clear0000; break; case 0x40u: keys = DccKeys::Clear0001; break; case 0x80u: keys = DccKeys::Clear1110; break; @@ -1276,12 +1364,14 @@ bool TakeMultisampledClear(MultisampledCmask& cmask, MultisampledColorFill& colo SampleOrderTarget MultisampleTargetOrderTarget(const Context& context, const ColorTarget& target) { std::lock_guard lock(targetsMutex()); auto& found = acquire(context, target); + found.drawn = true; return {found.image, found.OrderView(), found.orderFormat, target.extent, target.samples, SampleOrderMethod::Storage, found.shared_from_this()}; } VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, VkImage* image) { std::lock_guard lock(targetsMutex()); auto& acquired = acquire(context, target); + acquired.drawn = true; if (image != nullptr) *image = acquired.image; return acquired.View(target.format); } @@ -1289,6 +1379,7 @@ VkImageView MultisampleTargetView(const Context& context, const ColorTarget& tar VkImageView MultisampleTargetView(const Context& context, const ColorTarget& target, std::shared_ptr& owner, VkImage* image) { std::lock_guard lock(targetsMutex()); auto& acquired = acquire(context, target); + acquired.drawn = true; owner = acquired.shared_from_this(); if (image != nullptr) *image = acquired.image; return acquired.View(target.format); @@ -1637,4 +1728,18 @@ std::vector> HostOnlySampleRanges(std::u return ranges; } +std::optional MultisampleColorTargetOver(std::uint64_t address, std::size_t bytes) { + if (bytes == 0 || !anyGuarded().load(std::memory_order_acquire)) return std::nullopt; + std::lock_guard lock(targetsMutex()); + std::shared_lock guardLock(guardedMutex()); + for (const auto& range : guarded()) { + if (address >= range.address + range.bytes || range.address >= address + bytes) continue; + for (const auto& target : targets()) { + if (target->active == range.active) return target->Alias(); + } + return MultisampleColorAlias{range.address, range.bytes, VK_FORMAT_UNDEFINED, range.samples}; + } + return std::nullopt; +} + } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp index 0063fd6382..144e2f4325 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -67,6 +68,12 @@ void LogPipelineStatistics_nid_no_patch(const Context& context, VkPipeline pipel std::vector properties(count, {VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_PROPERTIES_KHR}); Check(getProperties(context.device, &pipelineInfo, &count, properties.data()), "vkGetPipelineExecutablePropertiesKHR"); properties.resize(count); + std::string report; + const auto append = [&report](const char* format, auto... values) { + std::array line{}; + std::snprintf(line.data(), line.size(), format, values...); + report += line.data(); + }; for (std::uint32_t index = 0; index < properties.size(); ++index) { VkPipelineExecutableInfoKHR executable{VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR}; executable.pipeline = pipeline; @@ -77,28 +84,27 @@ void LogPipelineStatistics_nid_no_patch(const Context& context, VkPipeline pipel Check(getStatistics(context.device, &executable, &count, statistics.data()), "vkGetPipelineExecutableStatisticsKHR"); statistics.resize(count); const auto& property = properties[index]; - std::fprintf(stderr, "[pipeline-stats] executable=%u stages=0x%x subgroup=%u name=%s\n", index, property.stages, property.subgroupSize, property.name); + append("[pipeline-stats] executable=%u stages=0x%x subgroup=%u name=%s\n", index, property.stages, property.subgroupSize, property.name); for (const auto& statistic : statistics) { - std::fprintf(stderr, "[pipeline-stats] executable=%u %s=", index, statistic.name); switch (statistic.format) { case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_BOOL32_KHR: - std::fprintf(stderr, "%s", statistic.value.b32 ? "true" : "false"); + append("[pipeline-stats] executable=%u %s=%s (%s)\n", index, statistic.name, statistic.value.b32 ? "true" : "false", statistic.description); break; case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_INT64_KHR: - std::fprintf(stderr, "%lld", static_cast(statistic.value.i64)); + append("[pipeline-stats] executable=%u %s=%lld (%s)\n", index, statistic.name, static_cast(statistic.value.i64), statistic.description); break; case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_UINT64_KHR: - std::fprintf(stderr, "%llu", static_cast(statistic.value.u64)); + append("[pipeline-stats] executable=%u %s=%llu (%s)\n", index, statistic.name, static_cast(statistic.value.u64), statistic.description); break; case VK_PIPELINE_EXECUTABLE_STATISTIC_FORMAT_FLOAT64_KHR: - std::fprintf(stderr, "%.17g", statistic.value.f64); + append("[pipeline-stats] executable=%u %s=%.17g (%s)\n", index, statistic.name, statistic.value.f64, statistic.description); break; default: throw std::runtime_error("unsupported pipeline statistic format"); } - std::fprintf(stderr, " (%s)\n", statistic.description); } } + std::fputs(report.c_str(), stderr); } bool HasStencil(VkFormat format) { @@ -233,7 +239,6 @@ void ValidateProvokingVertex(const Context& context, const State& state, std::sp 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_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; @@ -340,6 +345,8 @@ bool libraryKeys(PipelineLibraryKeys& keys, const Context& context, const State& appendKey(key, identity.low); appendKey(key, identity.high); appendKey(key, shader.pushConstantOffset); + appendKey(key, state.meshLayerBase); + appendKey(key, state.meshLayerCount); if (resources.split) { auto& sets = shader.stage == Stage::Fragment ? keys.fragmentSets : keys.preRasterizationSets; for (const auto& binding : shader.program->bindings) { @@ -509,7 +516,7 @@ Pipeline::Pipeline(const Context& context, const State& state, const VertexInput stages[i].module = _modules[i]; stages[i].pName = "main"; if (shaders[i].stage == ShaderRecompiler::ShaderStage::Mesh && context.meshWave32 && shader.hostSubgroupSize == 32u) stages[i].pNext = &meshSubgroup; - specializations.emplace_back(shader); + specializations.emplace_back(shader, state.meshLayerBase, state.meshLayerCount); stages[i].pSpecializationInfo = specializations.back().Info(); } if (std::ranges::any_of(shaders, [](const CompiledShader& shader) { return shader.program != nullptr && !shader.program->stencilReferences.empty(); })) { @@ -1016,6 +1023,8 @@ void pipelineKey(std::vector& key, const Context& context, const Stat // Where the stage's push constants sit in the block (AssemblePushConstants). append(key, shader.pushConstantOffset); } + append(key, state.meshLayerBase); + append(key, state.meshLayerCount); append(key, PushConstantStages(shaders)); append(key, input.bindings.size()); for (const auto& binding : input.bindings) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp index e6718d9473..93a27b16a9 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp @@ -41,7 +41,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) == 920, "State changed: update CanonicalPipelineState and the recipe codec"); +static_assert(sizeof(State) == 928, "State changed: update CanonicalPipelineState and the recipe codec"); static_assert(sizeof(ShaderStages) == 164, "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"); @@ -50,7 +50,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 = 13u; +constexpr std::uint32_t FileFormat = 15u; constexpr std::uint32_t ModuleRecord = 1u; constexpr std::uint32_t GraphicsRecord = 2u; constexpr std::uint32_t ComputeRecord = 3u; @@ -180,6 +180,8 @@ void encodeState(Writer& writer, const State& state) { writer.U32(static_cast(state.stages.path)); writer.U32(state.stages.vertexWaveSize); writer.U32(state.stages.fragmentWaveSize); + writer.U32(state.meshLayerBase); + writer.U32(state.meshLayerCount); encodeOptional(writer, state.stages.mesh, encodeMesh); encodeOptional(writer, state.stages.tessellation, encodeTessellation); writer.U32(state.stages.hullWaveSize); @@ -244,6 +246,8 @@ void decodeState(Reader& reader, State& state) { state.stages.path = static_cast(reader.U32()); state.stages.vertexWaveSize = reader.U32(); state.stages.fragmentWaveSize = reader.U32(); + state.meshLayerBase = reader.U32(); + state.meshLayerCount = reader.U32(); if (reader.Bool()) decodeMesh(reader, state.stages.mesh.emplace()); if (reader.Bool()) decodeTessellation(reader, state.stages.tessellation.emplace()); state.stages.hullWaveSize = reader.U32(); @@ -1385,8 +1389,26 @@ private: std::call_once(loadOnce, [&] { try { auto journal = Journal::Instance().Load(); - 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)); + const auto graphicsLimit = configured("APS5_PIPELINE_JOURNAL_ENTRIES", 4096u); + const auto computeLimit = configured("APS5_PIPELINE_JOURNAL_COMPUTE_ENTRIES", 1024u); + const auto compute = static_cast(std::ranges::count_if(journal.entries, [](const LoadedEntry& entry) { return entry.kind == ComputeRecord; })); + const auto graphics = journal.entries.size() - compute; + auto graphicsLeft = graphics > graphicsLimit ? graphics - graphicsLimit : 0u; + auto computeLeft = compute > computeLimit ? compute - computeLimit : 0u; + if (graphicsLeft + computeLeft != 0) { + std::fprintf(stderr, "[pipeline-journal] replaying the newest %llu of %llu graphics and %llu of %llu compute pipelines (APS5_PIPELINE_JOURNAL_ENTRIES, APS5_PIPELINE_JOURNAL_COMPUTE_ENTRIES)\n", static_cast(graphics - graphicsLeft), static_cast(graphics), static_cast(compute - computeLeft), static_cast(compute)); + std::vector kept; + kept.reserve(static_cast(journal.entries.size() - graphicsLeft - computeLeft)); + for (auto& entry : journal.entries) { + auto& left = entry.kind == ComputeRecord ? computeLeft : graphicsLeft; + if (left != 0) { + --left; + continue; + } + kept.push_back(std::move(entry)); + } + journal.entries = std::move(kept); + } 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()); @@ -1518,6 +1540,8 @@ State CanonicalPipelineState(const State& state) { canonical.stages.path = state.stages.path; canonical.stages.vertexWaveSize = state.stages.vertexWaveSize; canonical.stages.fragmentWaveSize = state.stages.fragmentWaveSize; + canonical.meshLayerBase = state.meshLayerBase; + canonical.meshLayerCount = state.meshLayerCount; canonical.stages.mesh = state.stages.mesh; canonical.stages.tessellation = state.stages.tessellation; canonical.stages.hullWaveSize = state.stages.hullWaveSize; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index 0d0f474245..90805553cf 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -4,6 +4,8 @@ #include "prx/libSceAgcDriver/Execution/include/Mutex.hpp" #include "prx/libc/include/HostThreadLocal.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ReferenceStorage.hpp" #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libkernel/Time/include/SleepWake.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" @@ -12,6 +14,7 @@ #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/StagingCopyBack_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/SampleCounter_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/SampleDumps_spv.h" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -551,6 +554,7 @@ bool ProcTable() { // 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). AgcDriver::Mutex labelTableMutex; +std::atomic injectedSubmitFailures{0}; 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 @@ -1234,6 +1238,8 @@ Recorder::~Recorder() { } if (meshArgumentPipeline != VK_NULL_HANDLE) context.Function("vkDestroyPipeline")(context.device, meshArgumentPipeline, nullptr); if (meshArgumentLayout != VK_NULL_HANDLE) context.Function("vkDestroyPipelineLayout")(context.device, meshArgumentLayout, nullptr); + if (changedCopyPipeline != VK_NULL_HANDLE) context.Function("vkDestroyPipeline")(context.device, changedCopyPipeline, nullptr); + if (changedCopyLayout != VK_NULL_HANDLE) context.Function("vkDestroyPipelineLayout")(context.device, changedCopyLayout, nullptr); if (sampleCounterState > 0) { samplesPassed.store(SamplesTotal(), std::memory_order_release); if (samplePools != nullptr) std::static_pointer_cast(samplePools)->close(); @@ -1427,6 +1433,124 @@ std::uint64_t Recorder::ThreadHookWaits() { return hookRealWaits; } +namespace { + +struct DispatchOrder { + std::uint32_t queueKey = 0; + bool unordered = false; +}; + +thread_local DispatchOrder nextDispatchOrder; +std::atomic overlapDeferred{0}, overlapJoined{0}, overlapFlushed{0}; + +} + +bool Recorder::DispatchOverlapEnabled() { + static const bool enabled = std::getenv("APS5_NO_DISPATCH_OVERLAP") == nullptr; + return enabled; +} + +void Recorder::SetDispatchOrder(std::uint32_t queueKey, bool unordered) { + nextDispatchOrder = {queueKey, unordered}; +} + +std::uint32_t Recorder::PeekUnorderedDispatch() { + return nextDispatchOrder.unordered ? nextDispatchOrder.queueKey : 0u; +} + +std::uint32_t Recorder::TakeDispatchOrder(bool& unordered) { + const auto order = std::exchange(nextDispatchOrder, DispatchOrder{}); + unordered = order.unordered; + return order.queueKey; +} + +Recorder::OverlapStatistics Recorder::OverlapCounts() { + return OverlapStatistics{overlapDeferred.load(std::memory_order_relaxed), overlapJoined.load(std::memory_order_relaxed), overlapFlushed.load(std::memory_order_relaxed)}; +} + +void Recorder::flushDeferredDispatch() { + if (open == nullptr || open->deferredDispatchQueue == 0) return; + open->deferredDispatchQueue = 0; + open->deferredRun.clear(); + open->runAccess.clear(); + auto after = std::move(open->afterRun); + open->afterRun.clear(); + 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; + recordBarrier(open->commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, dispatchedAccess); + CountBarriers(CommandClass::DispatchTrailing); + open->coveredAccess = dispatchedAccess; + overlapFlushed.fetch_add(1, std::memory_order_relaxed); + std::exception_ptr failure; + for (auto& work : after) { + try { + work(*this); + } catch (...) { + if (failure == nullptr) failure = std::current_exception(); + } + } + if (failure != nullptr) std::rethrow_exception(failure); +} + +bool Recorder::runConflict(std::span accesses) const { + for (const auto& access : accesses) { + for (const auto& member : open->runAccess) { + if (((access[2] ^ member[2]) & RunAccessImage) != 0) continue; + if (!(access[0] < member[1] && member[0] < access[1])) continue; + if (((access[2] | member[2]) & RunAccessWritten) != 0) return true; + } + } + return false; +} + +bool Recorder::joinsRun(std::uint32_t queueKey, std::span owners, std::span accesses) const { + if (queueKey == 0 || open == nullptr || open->deferredDispatchQueue != queueKey || open->renderPass.open || open->run.open) return false; + const auto taken = [&](const void* owner) { return std::find(open->deferredRun.begin(), open->deferredRun.end(), owner) != open->deferredRun.end(); }; + return std::none_of(owners.begin(), owners.end(), taken) && !runConflict(accesses); +} + +VkCommandBuffer Recorder::RunCommands(std::uint32_t queueKey, const void* owner, std::span accesses, bool& inRun, VkAccessFlags* coveredAccess) { + GuestMemory::AssertGpuLockHeld("Recorder::RunCommands"); + inRun = joinsRun(queueKey, std::span(&owner, 1), accesses); + if (!inRun) return Commands(coveredAccess); + if (coveredAccess != nullptr) *coveredAccess = 0; + return open->commands; +} + +void Recorder::AfterRun(std::function work) { + Require(RunOpen(), "work after a dispatch run without an open run"); + open->afterRun.push_back(std::move(work)); +} + +bool Recorder::CloseRunOwing(const void* owner) { + GuestMemory::AssertGpuLockHeld("Recorder::CloseRunOwing"); + if (!RunOpen() || std::find(open->deferredRun.begin(), open->deferredRun.end(), owner) == open->deferredRun.end()) return false; + flushDeferredDispatch(); + return true; +} + +VkCommandBuffer Recorder::OverlapCommands(std::uint32_t queueKey, std::span owners, std::span accesses, VkAccessFlags* coveredAccess, bool& overlapped) { + GuestMemory::AssertGpuLockHeld("Recorder::OverlapCommands"); + overlapped = joinsRun(queueKey, owners, accesses); + if (!overlapped) return Commands(coveredAccess); + if (coveredAccess != nullptr) *coveredAccess = 0; + return open->commands; +} + +void Recorder::DeferDispatchBarrier(std::uint32_t queueKey, std::span owners, std::span accesses) { + Require(open != nullptr && queueKey != 0, "a deferred dispatch barrier has no batch"); + if (open->deferredDispatchQueue == 0) { + open->deferredRun.clear(); + open->runAccess.clear(); + } else { + overlapJoined.fetch_add(1, std::memory_order_relaxed); + } + open->deferredRun.insert(open->deferredRun.end(), owners.begin(), owners.end()); + open->runAccess.insert(open->runAccess.end(), accesses.begin(), accesses.end()); + open->deferredDispatchQueue = queueKey; + open->coveredAccess = 0; + overlapDeferred.fetch_add(1, std::memory_order_relaxed); +} + bool Recorder::SnapshotWriteOverlaps(std::uint64_t address, std::size_t bytes) { return AgcDriver::Graphics::SnapshotOverlaps(address, bytes); } @@ -1460,6 +1584,7 @@ std::uint64_t Recorder::ReapsWithWork() { VkCommandBuffer Recorder::Commands(VkAccessFlags* coveredAccess) { ensureOpen(); + flushDeferredDispatch(); // Work recorded after a draw's render pass or an inline store run must see their writes: the // pass's end and the run's trailing barrier go in first (a per-batch run waits for Submit, or // for a caller whose ranges overlap a queued store: FlushStores). @@ -1476,6 +1601,7 @@ void Recorder::MarkCovered(VkAccessFlags access) { Recorder::DrawPassStart Recorder::StartDrawPass(std::uint64_t key, bool forced, bool fenced, const PassAccess& access) { ensureOpen(); + flushDeferredDispatch(); auto& pass = open->renderPass; if (!pass.open || forced || (countingSamples && segmentMode() && !segmentSlotReady(*open))) { const auto reason = !pass.open ? pass.closedBy : forced ? PassBreak::Forced : PassBreak::Segment; @@ -1641,6 +1767,7 @@ void Recorder::ensureOpen() { void Recorder::RecordStore(VkBuffer buffer, VkDeviceSize offset, std::span bytes, std::uint64_t address) { if (bytes.empty()) return; ensureOpen(); + flushDeferredDispatch(); auto& run = open->run; const VkDeviceSize end = offset + bytes.size(); storeCount.fetch_add(1, std::memory_order_relaxed); @@ -1731,6 +1858,7 @@ void Recorder::flushPendingStore() { } bool Recorder::closeStoreRun(bool atSubmit) { + flushDeferredDispatch(); auto& run = open->run; if (LabelRunsPerBatch()) { auto stores = std::move(run.queued); @@ -1753,7 +1881,7 @@ bool Recorder::closeStoreRun(bool atSubmit) { // pollers read results after the label); in place, later work sees the stores as well. std::uint32_t barriers = 2; if (atSubmit) { - recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT); + recordBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_HOST_READ_BIT); } else if (passEndLast && BarrierBatch() && Covers(PassEndBarrier, LabelLeadingBarrier)) { CountMerged(CommandClass::LabelRun); barriers = 1; @@ -2024,6 +2152,7 @@ void Recorder::NoteAccess(CommandClass which, const Access& access) { void Recorder::CountPresent() { timingPresents.fetch_add(1, std::memory_order_relaxed); presentSerial.fetch_add(1, std::memory_order_relaxed); + ReferenceStorage::Shared().ReleaseIdle(); } std::uint64_t Recorder::Presents() { @@ -2350,6 +2479,7 @@ void Recorder::PrepareSampleSlot() { GuestMemory::AssertGpuLockHeld("Recorder::PrepareSampleSlot"); if (!countingSamples || !segmentMode()) return; ensureOpen(); + flushDeferredDispatch(); Require(!open->renderPass.open, "a sample slot is prepared inside a render pass"); if (!segmentSlotReady(*open)) takeSegmentPool(*open); } @@ -2554,6 +2684,7 @@ bool Recorder::DumpSamples(VkDeviceAddress target, std::uint64_t address) { GuestMemory::AssertGpuLockHeld("Recorder::DumpSamples"); if (target == 0 || !gpuSampleCounter()) return false; countingSamples = true; + flushDeferredDispatch(); if (segmentMode()) { ensureOpen(); if (address != 0 && LabelRunsPerBatch() && pendingSamples.size() < SegmentFoldCapacity && open->queuedDumps < DumpFoldCapacity) { @@ -2629,6 +2760,58 @@ bool Recorder::RecordMeshArguments(VkCommandBuffer commands, VkDeviceAddress rec return true; } +bool Recorder::RecordChangedCopy(VkCommandBuffer commands, VkDeviceAddress result, VkDeviceAddress snapshot, VkDeviceAddress target, std::uint32_t dwords, VkDeviceAddress counter) { + constexpr std::uint32_t PushBytes = 40; + constexpr std::uint32_t GroupSize = 64; + if (changedCopyState == 0) { + changedCopyState = -1; + VkShaderModule module = VK_NULL_HANDLE; + try { + const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes}; + VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + layoutInfo.pushConstantRangeCount = 1; + layoutInfo.pPushConstantRanges = &push; + Check(context.Function("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &changedCopyLayout), "vkCreatePipelineLayout changed copy"); + VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + moduleInfo.codeSize = sizeof(STAGING_COPY_BACK_SPV); + moduleInfo.pCode = STAGING_COPY_BACK_SPV; + Check(context.Function("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module), "vkCreateShaderModule changed 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 = module; + pipelineInfo.stage.pName = "main"; + pipelineInfo.layout = changedCopyLayout; + Check(context.Function("vkCreateComputePipelines")(context.device, context.pipelineCache, 1, &pipelineInfo, nullptr, &changedCopyPipeline), "vkCreateComputePipelines changed copy"); + context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + changedCopyState = 1; + } catch (const std::exception& error) { + if (module != VK_NULL_HANDLE) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + std::fprintf(stderr, "[gpu] staged write-backs copy their whole ranges: %s\n", error.what()); + } + } + if (changedCopyState < 0) return false; + static const bool alwaysCopy = std::getenv("APS5_CHANGED_COPY_ALWAYS") != nullptr; + function(cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, changedCopyPipeline); + const auto perDispatch = static_cast(std::min(std::uint64_t{context.limits.maxComputeWorkGroupCount[0]} * GroupSize, std::numeric_limits::max() - GroupSize)); + for (std::uint32_t done = 0; done < dwords;) { + const auto count = std::min(dwords - done, perDispatch); + struct { + VkDeviceAddress result; + VkDeviceAddress snapshot; + VkDeviceAddress target; + VkDeviceAddress counter; + std::uint32_t count; + std::uint32_t always; + } parameters{result + VkDeviceAddress{done} * 4u, snapshot + VkDeviceAddress{done} * 4u, target + VkDeviceAddress{done} * 4u, counter, count, alwaysCopy ? 1u : 0u}; + static_assert(sizeof(parameters) == PushBytes); + function(cmdPushConstants, "vkCmdPushConstants")(commands, changedCopyLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, PushBytes, ¶meters); + function(cmdDispatch, "vkCmdDispatch")(commands, (count + GroupSize - 1) / GroupSize, 1, 1); + done += count; + } + return true; +} + void Recorder::beginSamples(Batch& batch) { auto cache = std::static_pointer_cast(samplePools); if (sampleCounterState > 0 && cache == nullptr) { @@ -2778,6 +2961,18 @@ std::size_t Recorder::UnsignaledBatches() const { return static_cast(std::count_if(inFlight.begin(), inFlight.end(), [&](const auto& batch) { return !signaled(*batch); })); } +std::shared_ptr Recorder::UploadScratchBuffer(UploadScratch kind, std::size_t bytes) { + Require(kind < UploadScratch::Count && bytes != 0, "invalid texture upload scratch request"); + ensureOpen(); + auto& scratch = open->uploadScratch[static_cast(kind)]; + if (scratch == nullptr || scratch->Size() < bytes) { + const VkBufferUsageFlags usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | (kind == UploadScratch::Tiled ? VK_BUFFER_USAGE_TRANSFER_DST_BIT : VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + scratch = std::make_shared(context, bytes, usage); + Keep(scratch, bytes); + } + return scratch; +} + void Recorder::Keep(std::shared_ptr object, std::size_t bytes) { ensureOpen(); open->kept.push_back(std::move(object)); @@ -3208,6 +3403,17 @@ std::uint64_t Recorder::PendingWriterSerial(std::uint64_t address, std::size_t b return 0; } +std::uint64_t Recorder::PendingLeaseSerial(std::uint64_t address, std::size_t bytes) const { + if (bytes == 0) return 0; + const auto end = address + bytes; + const auto leases = [&](const Batch& batch) { return readOverlap(batch, address, end, ReadKind::AddressBased).has_value(); }; + if (open != nullptr && leases(*open)) return OpenSerial(); + for (auto it = inFlight.rbegin(); it != inFlight.rend(); ++it) { + if (leases(**it)) return (*it)->serial; + } + return 0; +} + bool Recorder::signaled(const Batch& batch) const { return batch.submitted && fenceStatus(batch.fence) == VK_SUCCESS; } @@ -3661,6 +3867,10 @@ void Recorder::HoldNextSubmit(VkSemaphore semaphore, std::uint64_t value) { submitHoldValue = value; } +void Recorder::FailNextSubmitsForTest(unsigned count) { + injectedSubmitFailures.store(count, std::memory_order_relaxed); +} + void Recorder::Submit() { PerformanceTimer frameTiming("Recorder.Submit"); // The work count is cleared even when nothing is open: the driver counts a dispatch after its @@ -3671,6 +3881,7 @@ void Recorder::Submit() { dropStaleHoldKept(); if (open == nullptr) return; GuestMemory::AssertGpuLockHeld("Recorder::Submit"); + flushDeferredDispatch(); if (!open->keyStores.empty()) recordKeyStores(false); if (open->renderPass.open) endOpenRenderPass(PassBreak::NewBatch); const bool segments = segmentMode(); @@ -3703,6 +3914,7 @@ void Recorder::Submit() { } } discardUnsubmitted{*this, batch}; Check(function(endCommandBuffer, "vkEndCommandBuffer")(batch->commands), "vkEndCommandBuffer recorder"); + if (injectedSubmitFailures.load(std::memory_order_relaxed) != 0 && injectedSubmitFailures.fetch_sub(1, std::memory_order_relaxed) != 0) throw std::runtime_error("vkQueueSubmit recorder: injected failure"); VkSubmitInfo submission{VK_STRUCTURE_TYPE_SUBMIT_INFO}; submission.commandBufferCount = 1; submission.pCommandBuffers = &batch->commands; @@ -4178,6 +4390,10 @@ void Recorder::finish(std::unique_ptr batch, bool wait, int source, bool recorder.publishPendingWrites(); } catch (...) { } + try { + BdaResources::NoteFinished(recorder.context.device, batch->serial); + } catch (...) { + } } } finishingScope{*this, batch.get()}; finishing.push_back(batch.get()); @@ -4214,7 +4430,7 @@ void Recorder::finish(std::unique_ptr batch, bool wait, int source, bool AgcDriver::ProfilePrint_nid_no_patch("[recorder] completion label stores: %llu run, %llu skipped (no CPU write-back overlapped them); %llu counted pending at a write-back; %llu write-backs over a tracked label; %llu write-back completions pending\n", static_cast(completionStoresRun.load()), static_cast(completionStoresSkipped.load()), static_cast(completionLabelsCountedLate.load()), static_cast(writeBacksOverLabels.load()), static_cast(writeBackCompletions.load())); AgcDriver::ProfilePrint_nid_no_patch("[recorder] submits %llu, vkQueueSubmit mean %.1f us, max %.1f us; command buffers freed at batch release %llu, fences kept for reuse %llu\n", static_cast(submitCount), submitCount != 0 ? submitUs / static_cast(submitCount) : 0.0, submitMaxUs, static_cast(commandBuffersFreed.load()), static_cast(fencesKept.load())); const auto reads = Recorder::ReadCounts(); - AgcDriver::ProfilePrint_nid_no_patch("[recorder] in-place reads: %llu noted, %llu queries, hits by reader: dispatch element %llu, gpu copy %llu, address-based %llu, indirect %llu, storage upload %llu, copy source %llu, draw input %llu; %llu hits on signaled batches ignored\n", static_cast(reads.noted), static_cast(reads.queries), static_cast(reads.hits[0]), static_cast(reads.hits[1]), static_cast(reads.hits[2]), static_cast(reads.hits[3]), static_cast(reads.hits[4]), static_cast(reads.hits[5]), static_cast(reads.hits[6]), static_cast(reads.staleIgnored)); + AgcDriver::ProfilePrint_nid_no_patch("[recorder] in-place reads: %llu noted, %llu queries, hits by reader: dispatch element %llu, gpu copy %llu, address-based %llu, indirect %llu, storage upload %llu, copy source %llu, draw input %llu, address-based read-only %llu; %llu hits on signaled batches ignored\n", static_cast(reads.noted), static_cast(reads.queries), static_cast(reads.hits[0]), static_cast(reads.hits[1]), static_cast(reads.hits[2]), static_cast(reads.hits[3]), static_cast(reads.hits[4]), static_cast(reads.hits[5]), static_cast(reads.hits[6]), static_cast(reads.hits[7]), static_cast(reads.staleIgnored)); } } } report{profile, source, waitStart, *this, signaledAtStart}; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ReferenceStorage.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ReferenceStorage.cpp new file mode 100644 index 0000000000..6ef11ade91 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ReferenceStorage.cpp @@ -0,0 +1,89 @@ +#include "prx/libSceAgcDriver/Graphics/include/ReferenceStorage.hpp" + +#include + +namespace AgcDriver::Graphics { + +ReferenceStorage::ReferenceStorage(std::size_t capacityBytes) noexcept : capacityBytes(capacityBytes) {} + +std::size_t ReferenceStorage::releaseIdle(Clock::time_point now, std::array, Slots>& released) noexcept { + std::size_t freed = 0; + for (std::size_t slot = 0; slot < Slots; ++slot) { + if (idle[slot].capacity() == 0 || now - returnedAt[slot] < IdleLimit) continue; + freed += idle[slot].capacity(); + bytes -= idle[slot].capacity(); + released[slot] = std::move(idle[slot]); + idle[slot] = std::vector(); + } + return freed; +} + +std::size_t ReferenceStorage::ReleaseIdle(Clock::time_point now) noexcept { + std::array, Slots> released; + std::lock_guard lock(mutex); + return releaseIdle(now, released); +} + +std::vector ReferenceStorage::Take(std::size_t requested, Clock::time_point now) noexcept { + std::array, Slots> released; + std::lock_guard lock(mutex); + releaseIdle(now, released); + std::size_t best = Slots; + for (std::size_t slot = 0; slot < Slots; ++slot) { + const auto capacity = idle[slot].capacity(); + if (capacity == 0 || capacity < requested) continue; + if (best == Slots || capacity < idle[best].capacity()) best = slot; + } + if (best == Slots) return {}; + auto storage = std::move(idle[best]); + idle[best] = std::vector(); + bytes -= storage.capacity(); + return storage; +} + +void ReferenceStorage::Return(std::vector&& storage, Clock::time_point now) noexcept { + auto kept = std::move(storage); + const auto capacity = kept.capacity(); + if (capacity == 0 || capacity > capacityBytes) return; + std::array, Slots> evicted; + std::array, Slots> released; + std::lock_guard lock(mutex); + releaseIdle(now, released); + std::size_t free = Slots; + for (std::size_t slot = 0; slot < Slots; ++slot) { + if (idle[slot].capacity() == 0) free = slot; + } + while (free == Slots || bytes + capacity > capacityBytes) { + std::size_t oldest = Slots; + for (std::size_t slot = 0; slot < Slots; ++slot) { + if (idle[slot].capacity() != 0 && (oldest == Slots || returned[slot] < returned[oldest])) oldest = slot; + } + bytes -= idle[oldest].capacity(); + evicted[oldest] = std::move(idle[oldest]); + idle[oldest] = std::vector(); + free = oldest; + } + bytes += capacity; + returned[free] = ++returns; + returnedAt[free] = now; + idle[free] = std::move(kept); +} + +std::size_t ReferenceStorage::IdleBytes() const noexcept { + std::lock_guard lock(mutex); + return bytes; +} + +std::size_t ReferenceStorage::IdleCount() const noexcept { + std::lock_guard lock(mutex); + std::size_t count = 0; + for (const auto& storage : idle) count += storage.capacity() != 0 ? 1 : 0; + return count; +} + +ReferenceStorage& ReferenceStorage::Shared() noexcept { + static ReferenceStorage storage(std::size_t{2} << 30u); + return storage; +} + +} diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ResidentMirror.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ResidentMirror.cpp index 74144e49fd..cd0c5df699 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ResidentMirror.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ResidentMirror.cpp @@ -29,6 +29,7 @@ namespace AgcDriver::Graphics { namespace { constexpr std::uint64_t BlockSize = ResidentBuffer::BlockBytes; +constexpr VkDeviceSize StagingChunkBytes = VkDeviceSize{32} << 20u; 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; @@ -573,18 +574,34 @@ void ResidentBuffer::flush() { cursor = std::max(cursor, last); } if (regions.empty()) return; - auto staging = std::make_shared(context, static_cast(total), VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + struct Chunk { + std::shared_ptr staging; + std::vector copies; + }; + std::vector chunks; + VkDeviceSize filled = StagingChunkBytes; for (const auto& region : regions) { for (VkDeviceSize done = 0; done < region.size;) { + if (filled == StagingChunkBytes) { + const auto remaining = total - (region.srcOffset + done); + chunks.push_back({std::make_shared(context, static_cast(std::min(StagingChunkBytes, remaining)), VK_BUFFER_USAGE_TRANSFER_SRC_BIT), {}}); + filled = 0; + } + auto& chunk = chunks.back(); 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)); + const auto length = std::min({region.size - done, BlockSize - (at - origin) % BlockSize, StagingChunkBytes - filled}); + std::memcpy(chunk.staging->Bytes().data() + filled, Block(at), static_cast(length)); + if (!chunk.copies.empty() && chunk.copies.back().srcOffset + chunk.copies.back().size == filled && chunk.copies.back().dstOffset + chunk.copies.back().size == region.dstOffset + done) chunk.copies.back().size += length; + else chunk.copies.push_back({filled, region.dstOffset + done, length}); + filled += 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); + for (const auto& chunk : chunks) { + context.Function("vkCmdCopyBuffer")(commands, chunk.staging->Handle(), buffer, static_cast(chunk.copies.size()), chunk.copies.data()); + kept.push_back(chunk.staging); + } kept.push_back(shared_from_this()); }); } catch (...) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp index b51a76e05a..b627c5409a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp @@ -49,7 +49,14 @@ VkBuffer BindExactBuffer(const Context& context, std::size_t size, VkBufferUsage } -Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) : context(context), size(size), capacity(BufferPool::Capacity(size)), usage(BufferPool::Usage(usage, properties)), properties(properties) { +Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) : Buffer(context, size, usage, properties, false) {} + +std::shared_ptr Buffer::WithinDeviceLocalHeadroom(const Context& context, std::size_t size, VkBufferUsageFlags usage) { + std::shared_ptr buffer(new Buffer(context, size, usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, true)); + return buffer->ready ? buffer : nullptr; +} + +Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, bool headroom) : context(context), size(size), capacity(BufferPool::Capacity(size)), usage(BufferPool::Usage(usage, properties)), properties(properties) { Require(size != 0, "zero-sized GPU buffer"); const bool addressable = (usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0; Require(!addressable || context.bufferDeviceAddress, "buffer device address is not enabled"); @@ -98,6 +105,11 @@ Buffer::Buffer(const Context& context, std::size_t size, VkBufferUsageFlags usag // write-combined memory, so the default host properties prefer cached host memory. const bool useSlab = (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0 && BufferPool::SlabEligible(capacity, requirements.alignment, requirements.size, context.limits.nonCoherentAtomSize); allocation.memoryTypeIndex = context.BufferMemoryType(requirements.memoryTypeBits, properties, useSlab ? BufferPool::SlabBlockBytes(capacity) : allocation.allocationSize); + if (headroom && !context.DeviceLocalHeadroom(allocation.memoryTypeIndex, useSlab ? BufferPool::SlabBlockBytes(capacity) : allocation.allocationSize)) { + context.Function("vkDestroyBuffer")(context.device, buffer, nullptr); + buffer = VK_NULL_HANDLE; + return; + } if (useSlab) { const auto slot = cache->TakeSlot(context, allocation.memoryTypeIndex, capacity, addressable); memory = slot.memory; @@ -312,7 +324,8 @@ RenderTarget::RenderTarget(const Context& context, const ColorTarget& target, bo 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 render target"); + memory = AllocateImageMemory(context, allocation, requirements.memoryTypeBits, "vkAllocateMemory render target", &memoryType); + memoryBytes = allocation.allocationSize; Check(context.Function("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory"); VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; viewInfo.image = image; @@ -333,7 +346,7 @@ RenderTarget::~RenderTarget() { void RenderTarget::release() noexcept { if (view) context.Function("vkDestroyImageView")(context.device, view, nullptr); if (image) context.Function("vkDestroyImage")(context.device, image, nullptr); - if (memory) MemoryAccounting::Free(context, memory); + ReleaseImageMemory(context, memory, memoryBytes, memoryType); } VkImage RenderTarget::Image() const { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp index d7b1fb0056..80d99fcf93 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Sampler.cpp @@ -1,6 +1,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Sampler.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include +#include #include #include @@ -102,10 +103,10 @@ namespace AgcDriver::Graphics { return sampler; } - void RequireFilterMinmax(const Context& context, VkFormat format, std::uint32_t samplerMask, std::span> samplers) { + void RequireFilterMinmax(const Context& context, VkFormat format, std::uint64_t samplerMask, std::span> samplers) { bool filtered = false; - for (std::uint32_t element = 0; element < 32u; ++element) { - if (((samplerMask >> element) & 1u) == 0u) continue; + for (auto mask = samplerMask; mask != 0u; mask &= mask - 1u) { + const auto element = static_cast(std::countr_zero(mask)); if (element >= samplers.size()) Require(false, "a sampled texture is paired with sampler element " + std::to_string(element) + ", which its shader does not bind"); filtered = filtered || samplers[element]->RequiresFilterMinmax(); } @@ -123,29 +124,31 @@ namespace AgcDriver::Graphics { return requiresNonSeamlessCube; } - bool ForcesDegammaOn(std::uint32_t guestFormat, std::uint32_t samplerMask, std::span> samplers) { + bool ForcesDegammaOn(std::uint32_t guestFormat, std::uint64_t samplerMask, std::span> samplers) { if (IsSrgbTextureFormat(guestFormat) || IgnoresForcedDegamma(guestFormat)) return false; - for (std::uint32_t element = 0; element < 32u && element < samplers.size(); ++element) { - if (((samplerMask >> element) & 1u) != 0u && samplers[element]->ForcesDegamma()) return true; + for (auto mask = samplerMask; mask != 0u; mask &= mask - 1u) { + const auto element = static_cast(std::countr_zero(mask)); + if (element < samplers.size() && samplers[element]->ForcesDegamma()) return true; } return false; } - void RequireBorderSwizzle(std::uint32_t bcSwizzle, std::uint32_t samplerMask, std::span> samplers) { + void RequireBorderSwizzle(std::uint32_t bcSwizzle, std::uint64_t samplerMask, std::span> samplers) { constexpr std::uint32_t BcSwizzleXyzw = 0u; constexpr std::uint32_t BcSwizzleZyxw = 4u; if (bcSwizzle == BcSwizzleXyzw || bcSwizzle == BcSwizzleZyxw) return; - for (std::uint32_t element = 0; element < 32u; ++element) { - if (((samplerMask >> element) & 1u) == 0u) continue; + for (auto mask = samplerMask; mask != 0u; mask &= mask - 1u) { + const auto element = static_cast(std::countr_zero(mask)); if (element >= samplers.size()) Require(false, "a sampled texture is paired with sampler element " + std::to_string(element) + ", which its shader does not bind"); if (samplers[element]->ReadsOpaqueBlackBorder()) Require(false, "a sampled texture whose BC swizzle " + std::to_string(bcSwizzle) + " moves the alpha channel is sampled through an opaque black border, which is not implemented"); } } - void RequireNonSeamlessCube(bool cube, std::uint32_t samplerMask, std::span> samplers) { + void RequireNonSeamlessCube(bool cube, std::uint64_t samplerMask, std::span> samplers) { if (!cube) return; - for (std::uint32_t element = 0; element < 32u && element < samplers.size(); ++element) { - if (((samplerMask >> element) & 1u) != 0u && samplers[element]->RequiresNonSeamlessCube()) Require(false, "a cube texture is sampled through a sampler that disables seamless cube filtering, which needs VK_EXT_non_seamless_cube_map"); + for (auto mask = samplerMask; mask != 0u; mask &= mask - 1u) { + const auto element = static_cast(std::countr_zero(mask)); + if (element < samplers.size() && samplers[element]->RequiresNonSeamlessCube()) Require(false, "a cube texture is sampled through a sampler that disables seamless cube filtering, which needs VK_EXT_non_seamless_cube_map"); } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp index 01cdc5661d..84de0fb00a 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderAddressResources.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -65,6 +67,37 @@ bool definesNoPointerValue(std::uint32_t opcode) { } } +bool referencesNoValue(std::uint32_t opcode) { + switch (static_cast(opcode)) { + case spv::OpCapability: + case spv::OpExtension: + case spv::OpExtInstImport: + case spv::OpMemoryModel: + case spv::OpEntryPoint: + case spv::OpExecutionMode: + case spv::OpExecutionModeId: + case spv::OpSource: + case spv::OpSourceExtension: + case spv::OpString: + case spv::OpLine: + case spv::OpNoLine: + case spv::OpModuleProcessed: + case spv::OpConstantTrue: + case spv::OpConstantFalse: + case spv::OpConstant: + case spv::OpConstantComposite: + case spv::OpConstantNull: + case spv::OpSpecConstantTrue: + case spv::OpSpecConstantFalse: + case spv::OpSpecConstant: + case spv::OpSpecConstantComposite: + case spv::OpSpecConstantOp: + return true; + default: + return definesNoPointerValue(opcode) || (opcode >= spv::OpTypeVoid && opcode <= spv::OpTypeForwardPointer); + } +} + template bool forEachInstruction(std::span words, const TVisit& visit) { for (std::size_t at = 5; at < words.size();) { @@ -91,6 +124,21 @@ bool storesThroughAddressesCached(const ShaderRecompiler::SharedSpirv& spirv) { return stores; } +bool faultReportOnlyCached(const ShaderRecompiler::SharedSpirv& spirv, std::uint32_t set, std::uint32_t binding) { + static std::mutex mutex; + static std::map, bool> known; + const auto hash = spirv.ContentHash(); + const std::tuple key{hash.low, hash.high, set, binding}; + { + std::lock_guard lock(mutex); + if (const auto found = known.find(key); found != known.end()) return found->second; + } + const bool reportOnly = SpirvFaultReportOnly(spirv.Words(), set, binding); + std::lock_guard lock(mutex); + known.emplace(key, reportOnly); + return reportOnly; +} + } bool ShaderStoresThroughAddresses(const ShaderRecompiler::SharedSpirv& spirv) { @@ -118,6 +166,45 @@ bool SpirvStoresThroughAddresses(std::span words) { return stores; } +bool SpirvFaultReportOnly(std::span words, std::uint32_t set, std::uint32_t binding) { + constexpr std::uint32_t reportWords = sizeof(ShaderRecompiler::BdaAbi::Fault) / sizeof(std::uint32_t); + if (words.size() < 5 || words[0] != spv::MagicNumber) return false; + std::unordered_map sets; + std::unordered_map bindings; + std::unordered_map constants; + const bool readable = forEachInstruction(words, [&](std::uint32_t opcode, std::span instruction) { + if (opcode == spv::OpDecorate && instruction.size() == 4 && instruction[2] == spv::DecorationDescriptorSet) sets[instruction[1]] = instruction[3]; + if (opcode == spv::OpDecorate && instruction.size() == 4 && instruction[2] == spv::DecorationBinding) bindings[instruction[1]] = instruction[3]; + if (opcode == spv::OpConstant && instruction.size() == 4) constants[instruction[2]] = instruction[3]; + }); + if (!readable) return false; + std::unordered_set buffers; + for (const auto& [id, number] : bindings) { + if (const auto found = sets.find(id); number == binding && found != sets.end() && found->second == set) buffers.insert(id); + } + const auto constantBelow = [&](std::uint32_t id, std::uint32_t limit) { + const auto found = constants.find(id); + return found != constants.end() && found->second < limit; + }; + const auto chainsBuffer = [&](std::uint32_t opcode, std::span instruction) { + return (opcode == spv::OpAccessChain || opcode == spv::OpInBoundsAccessChain) && instruction.size() > 3 && buffers.contains(instruction[3]); + }; + bool reportOnly = !buffers.empty(); + std::unordered_set reportPointers; + forEachInstruction(words, [&](std::uint32_t opcode, std::span instruction) { + if (!chainsBuffer(opcode, instruction)) return; + reportOnly = reportOnly && instruction.size() == 6 && constantBelow(instruction[4], 1) && constantBelow(instruction[5], reportWords); + reportPointers.insert(instruction[2]); + }); + forEachInstruction(words, [&](std::uint32_t opcode, std::span instruction) { + if (!reportOnly || referencesNoValue(opcode) || chainsBuffer(opcode, instruction) || (opcode == spv::OpVariable && instruction.size() > 2 && buffers.contains(instruction[2]))) return; + const auto accessed = opcode == spv::OpLoad || opcode == spv::OpAtomicLoad ? (instruction.size() > 3 ? instruction[3] : 0u) : pointerOperandStored(opcode, instruction); + const auto references = std::count_if(instruction.begin() + 1, instruction.end(), [&](std::uint32_t word) { return buffers.contains(word) || reportPointers.contains(word); }); + reportOnly = references == (reportPointers.contains(accessed) ? 1 : 0); + }); + return reportOnly; +} + void ShaderResources::prepareAddressBindings(std::span shaders, std::span snapshots) { static const bool loopGuarded = std::getenv("APS5_LOOP_GUARD") != nullptr; for (const auto& shader : shaders) { @@ -127,8 +214,12 @@ void ShaderResources::prepareAddressBindings(std::span sha for (const auto& binding : shader.program->bindings) { if (binding.role != ShaderRecompiler::DescriptorRole::BdaPagetable && binding.role != ShaderRecompiler::DescriptorRole::FaultBuffer) continue; Require(binding.kind == ShaderRecompiler::DescriptorKind::StorageBuffer && binding.count == 1 && binding.guestDescriptor.empty() && !binding.readOnly, "invalid BDA descriptor contract"); - if (binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable) ++tables; - else ++faults; + if (binding.role == ShaderRecompiler::DescriptorRole::BdaPagetable) { + ++tables; + } else { + ++faults; + faultReportOnly = faultReportOnly && faultReportOnlyCached(shader.program->spirv, binding.descriptorSet, binding.binding); + } } const bool faultOnly = tables == 0 && faults == 1 && shader.program->bdaAbiVersion == ShaderRecompiler::BdaAbi::Version; Require((tables == faults || faultOnly) && tables <= 1 && faults <= 1, "invalid BDA table and fault descriptors"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index 78ec8ee522..4a71f7f52d 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -16,6 +16,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestAllocations.hpp" #include "Optimization/include/Optimization/ShaderStageInputInfo.hpp" #include "RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp" #include "IntermediateRepresentation/IrMetadata/ShaderStage.hpp" @@ -54,6 +56,28 @@ bool overlap(std::uint64_t first, std::size_t firstSize, std::uint64_t second, s return first < second + secondSize && second < first + firstSize; } +bool registeredImageViewsCurrent(std::span views) { + if (views.empty()) return true; + for (std::uint32_t attempt = 0; attempt < 2u; ++attempt) { + const auto before = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + bool current = true; + for (const auto& view : views) { + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast(view.address), false); + if (view.hasRange != (range != nullptr)) { + current = false; + break; + } + if (range != nullptr && (range->address != view.rangeAddress || range->bytes != view.rangeBytes || range->allocationAddress != view.allocationAddress || range->allocationBytes != view.allocationBytes || range->backingSerial != view.backingSerial || range->readable != view.readable || range->writable != view.writable || range->gpu != view.gpu)) { + current = false; + break; + } + } + const auto after = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + if (before == after) return current; + } + return false; +} + VkComponentSwizzle ComponentSwizzleFor(std::uint8_t dstSel) { switch (dstSel) { case 0: return VK_COMPONENT_SWIZZLE_ZERO; @@ -75,11 +99,30 @@ VkComponentMapping ViewComponents(const GuestTextureResource& resource) { // Sampled textures are reused across draws and dispatches while their guest bytes are unchanged; a // byte copy of the guest surface validates each reuse, so CPU or GPU writes to it force a re-upload. // The key is everything a lookup matches: the device, the eight descriptor words and the swizzle. +struct AllocationIdentity { + std::uint64_t rangeAddress = 0; + std::uint64_t rangeBytes = 0; + std::uint64_t allocationAddress = 0; + std::uint64_t allocationBytes = 0; + std::uint64_t backingSerial = 0; + std::uint32_t flags = 0; + bool operator==(const AllocationIdentity&) const = default; +}; + +AllocationIdentity ImageAllocationIdentity(std::span words) { + const auto address = TextureResourceAllocationAddress(words); + if (!address) return {}; + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(static_cast(*address), false); + if (range == nullptr) return {}; + return {range->address, range->bytes, range->allocationAddress, range->allocationBytes, range->backingSerial, (range->readable ? 1u : 0u) | (range->writable ? 2u : 0u) | (range->gpu ? 4u : 0u)}; +} + struct TextureKey { VkDevice device; std::array words; std::array components; bool depthCompare = false; + AllocationIdentity allocation; bool operator==(const TextureKey&) const = default; }; @@ -92,6 +135,7 @@ struct TextureKeyHash { std::size_t operator()(const TextureKey& key) const noexcept { auto hash = 14695981039346656037ull ^ reinterpret_cast(key.device); hash = hashWords(hash, key.words.data(), key.words.size()) ^ static_cast(key.depthCompare); + for (const auto value : {key.allocation.rangeAddress, key.allocation.rangeBytes, key.allocation.allocationAddress, key.allocation.allocationBytes, key.allocation.backingSerial, static_cast(key.allocation.flags)}) hash = (hash ^ value) * 1099511628211ull; return static_cast(hashWords(hash, key.components.data(), key.components.size())); } }; @@ -99,6 +143,7 @@ struct TextureKeyHash { TextureKey MakeTextureKey(VkDevice device, std::span words, VkComponentMapping components, bool depthCompare = false) { TextureKey key{device, {}, {static_cast(components.r), static_cast(components.g), static_cast(components.b), static_cast(components.a)}}; key.depthCompare = depthCompare; + key.allocation = ImageAllocationIdentity(words); std::copy(words.begin(), words.end(), key.words.begin()); key.words[5] &= ~((0xfffu << 8u) | (1u << 25u)); key.words[6] &= ~0xffu; @@ -349,6 +394,7 @@ struct ImageMemoEntry { std::array words{}; GuestTextureResource resource{}; std::uint64_t guestBytes = 0; + AllocationIdentity allocation; VkComponentMapping components{}; bool hasEntry = false; std::list::iterator entry{}; @@ -367,10 +413,12 @@ ImageMemoEntry& imageMemoSlot(std::span words) { const ImageMemoEntry& decodedImage(VkDevice device, std::span words) { auto& slot = imageMemoSlot(words); - if (slot.device == device && std::equal(words.begin(), words.end(), slot.words.begin())) return slot; + const auto allocation = ImageAllocationIdentity(words); + if (slot.device == device && slot.allocation == allocation && std::equal(words.begin(), words.end(), slot.words.begin())) return slot; slot.device = VK_NULL_HANDLE; slot.resource = DecodeTextureResource(words); slot.guestBytes = DescribeSurface(slot.resource).guestBytes; + slot.allocation = allocation; slot.components = ViewComponents(slot.resource); std::copy(words.begin(), words.end(), slot.words.begin()); slot.hasEntry = false; @@ -380,7 +428,7 @@ const ImageMemoEntry& decodedImage(VkDevice device, std::span::iterator findSampledEntry(TextureCache& cache, VkDevice device, const std::array& words, VkComponentMapping components) { auto& slot = imageMemoSlot(words); - const bool same = slot.device == device && slot.words == words && slot.components.r == components.r && slot.components.g == components.g && slot.components.b == components.b && slot.components.a == components.a; + const bool same = slot.device == device && slot.allocation == ImageAllocationIdentity(words) && slot.words == words && slot.components.r == components.r && slot.components.g == components.g && slot.components.b == components.b && slot.components.a == components.a; if (same && slot.hasEntry && slot.erasures == cache.erasures) return slot.entry; const auto it = findTexture(cache, MakeTextureKey(device, words, components)); if (same) { @@ -617,7 +665,8 @@ std::uint64_t forcedCacheBudget() { return forced; } -std::uint64_t storageBudget(const Context& context, std::uint64_t required = 0) { +std::uint64_t storageBudget(const Context& context, std::uint64_t required = 0, std::optional* calculatedAt = nullptr) { + if (calculatedAt != nullptr) calculatedAt->reset(); const auto forced = forcedCacheBudget(); if (forced != 0) return forced; if (context.memoryProperties2 == nullptr || context.physical == VK_NULL_HANDLE) return TextureCacheBudget(context.memory); @@ -627,6 +676,7 @@ std::uint64_t storageBudget(const Context& context, std::uint64_t required = 0) std::chrono::steady_clock::time_point read{}; std::uint64_t budget{0}; std::uint64_t ceiling{0}; + std::chrono::steady_clock::time_point calculatedAt{}; } cached; const auto now = std::chrono::steady_clock::now(); std::lock_guard lock(cached.mutex); @@ -644,10 +694,12 @@ std::uint64_t storageBudget(const Context& context, std::uint64_t required = 0) storage = storageCacheBytes(); cached.budget = StorageTextureBudget(context.memory, &*reported, sampled, storage); cached.ceiling = StorageTextureCeiling(context.memory, &*reported); + cached.calculatedAt = std::chrono::steady_clock::now(); }; 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(); + if (calculatedAt != nullptr) *calculatedAt = cached.calculatedAt; if (reported) MemoryAccounting::Report(context, *reported, sampled, storage, cached.budget); return cached.budget; } @@ -665,15 +717,21 @@ std::uint64_t sampledBudget(const Context& context, TextureCache& cache) { const auto storage = storageCacheBytes(); const auto budget = SampledTextureBudget(context.memory, &reported, sampled + storage, storage); const bool fresh = cache.budgetDevice != context.physical; - const auto storageLimit = storageBudget(context); + std::optional storageCalculatedAt; + const auto storageLimit = storageBudget(context, 0, &storageCalculatedAt); if (const auto heap = largestDeviceLocalHeap(context.memory); heap && (fresh || SampledBudgetReportDue(cache.reportedBudget, budget, now - cache.reportedAt) || SampledBudgetReportDue(cache.reportedStorage, storageLimit, now - cache.reportedAt))) { char was[48] = ""; char wasStorage[48] = ""; + char storageLimitAge[32] = "unavailable"; + if (storageCalculatedAt) { + const auto age = std::chrono::duration_cast(std::chrono::steady_clock::now() - *storageCalculatedAt); + std::snprintf(storageLimitAge, sizeof(storageLimitAge), "%llu", static_cast(age.count())); + } if (!fresh) { std::snprintf(was, sizeof(was), " (was %llu MiB)", static_cast(cache.reportedBudget >> 20u)); std::snprintf(wasStorage, sizeof(wasStorage), " (was %llu MiB)", static_cast(cache.reportedStorage >> 20u)); } - std::fprintf(stderr, "[gpu] sampled texture cache budget %llu MiB%s: device-local heap %u has a VK_EXT_memory_budget budget of %llu MiB and uses %llu MiB, %llu MiB of it sampled textures (%llu MiB in the cache) and %llu MiB cached storage images; storage image cache budget %llu MiB%s\n", static_cast(budget >> 20u), was, *heap, static_cast(reported.heapBudget[*heap] >> 20u), static_cast(reported.heapUsage[*heap] >> 20u), static_cast(sampled >> 20u), static_cast(cache.bytes >> 20u), static_cast(storage >> 20u), static_cast(storageLimit >> 20u), wasStorage); + std::fprintf(stderr, "[gpu] sampled texture cache budget %llu MiB%s: device-local heap %u has a VK_EXT_memory_budget budget of %llu MiB and uses %llu MiB, %llu MiB of it sampled textures (%llu MiB in the cache) and %llu MiB cached storage images; active storage image cache budget %llu MiB%s; storage_limit_age_ms=%s\n", static_cast(budget >> 20u), was, *heap, static_cast(reported.heapBudget[*heap] >> 20u), static_cast(reported.heapUsage[*heap] >> 20u), static_cast(sampled >> 20u), static_cast(cache.bytes >> 20u), static_cast(storage >> 20u), static_cast(storageLimit >> 20u), wasStorage, storageLimitAge); cache.reportedBudget = budget; cache.reportedStorage = storageLimit; cache.reportedAt = now; @@ -698,7 +756,7 @@ std::array untrackedKey(std::uint64_t address, std::size_t byt } std::span firstLayerWords(std::span words, std::optional shape, std::array& narrowed, bool report) { - if (shape != ShaderRecompiler::DescriptorImageShape::Image2D || words.size() != narrowed.size() || IsNullTextureResource(words)) return words; + if (shape != ShaderRecompiler::DescriptorImageShape::Image2D || words.size() != narrowed.size() || IsNullSampledTextureResource(words)) return words; try { if (!FirstLayerDescriptor(words, narrowed)) return words; } catch (const std::runtime_error&) { @@ -715,6 +773,10 @@ std::span firstLayerWords(std::span wo return narrowed; } +bool layerRowsBinding(std::optional shape, const GuestTextureResource& resource, bool depthCompare) { + return !depthCompare && shape == ShaderRecompiler::DescriptorImageShape::Image2D && resource.dimension == TextureDimension::k1DArray; +} + void reportGuestAllocation(const GuestTextureResource& resource, std::uint64_t guestBytes) { const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(resource.baseAddress, false); const auto geometry = DescribeSurface(resource); @@ -735,11 +797,12 @@ struct PendingLookup { bool repackCopy = false; }; -PendingLookup pendingLookupSource(const Context& context, const GuestTextureResource& resource, std::uint64_t guestBytes, bool depthCompare) { +PendingLookup pendingLookupSource(const Context& context, const GuestTextureResource& resource, std::uint64_t guestBytes, bool depthCompare, bool layerRows = false) { PendingLookup lookup; lookup.depthCopy = depthCompare && guestBytes >= DepthCopyMinBytes() && Texture::DepthCopyFormat(ResolveTextureFormat(resource.format)) != VK_FORMAT_UNDEFINED && resource.dimension != TextureDimension::k3D; lookup.srgbExpand = !depthCompare && SrgbExpandsToRgba8(context, resource.format); lookup.source = StorageTexture::FindPending(resource.baseAddress, guestBytes); + if (layerRows) lookup.source.reset(); auto& source = lookup.source; lookup.blockCopy = source != nullptr && !depthCompare && !lookup.srgbExpand && Texture::CanBlockCopyFrom(*source, resource); lookup.repackCopy = source != nullptr && !depthCompare && Texture::CanRepackCopyFrom(*source, resource); @@ -749,8 +812,9 @@ PendingLookup pendingLookupSource(const Context& context, const GuestTextureReso } // `guestBytes` is the surface size when the caller described the surface already (0: described here). -std::shared_ptr cachedTexture(const Context& context, std::span words, const GuestTextureResource& requested, VkComponentMapping components, std::uint64_t guestBytes = 0, bool depthCompare = false, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D) { - const auto resource = ArrayLayerWindow(requested, context.limits.maxImageArrayLayers); +std::shared_ptr cachedTexture(const Context& context, std::span words, const GuestTextureResource& requested, VkComponentMapping components, std::uint64_t guestBytes = 0, bool depthCompare = false, VkImageViewType multisampleViewType = VK_IMAGE_VIEW_TYPE_2D, bool layerRows = false) { + Require(!layerRows || (requested.dimension == TextureDimension::k1DArray && !depthCompare), "only a sampled 1D array is laid out as rows of a 2D image"); + const auto resource = layerRows ? requested : ArrayLayerWindow(requested, context.limits.maxImageArrayLayers); if (resource.baseAddress != requested.baseAddress) guestBytes = 0; if (resource.dimension == TextureDimension::k2DMsaa) { Require(!depthCompare, "native multisampled depth comparison sampling is not implemented"); @@ -773,7 +837,8 @@ 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(); - if (auto depth = DepthSurfaceTexture(context, words, resource, components)) { + Require(!layerRows || !DepthSurfaceAt(resource.baseAddress), "sampling a native depth surface as a 1D array past maxImageArrayLayers is not implemented"); + if (auto depth = layerRows ? nullptr : DepthSurfaceTexture(context, words, resource, components)) { logLookup({depth.get(), resource, 0, DccKeys::Uncompressed, 0, nullptr, true}); return depth; } @@ -782,13 +847,13 @@ std::shared_ptr cachedTexture(const Context& context, std::span(ShaderRecompiler::IrBufferFormat::Format16UNorm); if (depthBitsWidth == 16u && resource.format != unorm16) { auto normalized = resource; normalized.format = unorm16; - return cachedTexture(context, words, normalized, components, guestBytes, depthCompare); + return cachedTexture(context, words, normalized, components, guestBytes, depthCompare, VK_IMAGE_VIEW_TYPE_2D, layerRows); } const auto viewedWindow = [&] { const auto window = ViewedArrayWindow(resource, false); @@ -801,7 +866,7 @@ 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)); - return cachedTexture(context, words, window, components, 0, depthCompare); + return cachedTexture(context, words, window, components, 0, depthCompare, VK_IMAGE_VIEW_TYPE_2D, layerRows); } if (guestBytes == 0) guestBytes = DescribeSurface(resource).guestBytes; if (guestBytes >= (1ull << 30u)) { @@ -831,7 +896,7 @@ std::shared_ptr cachedTexture(const Context& context, std::span cachedTexture(const Context& context, std::span cachedTexture(const Context& context, std::span(context, source, resource, components); - if (const auto clear = SampledClearColor(context, resource, *keys, depthCompare)) return std::make_shared(context, *context.detiler, resource, components, std::span{}, false, &*clear); + if (const auto clear = SampledClearColor(context, resource, *keys, depthCompare)) return std::make_shared(context, *context.detiler, resource, components, std::span{}, false, layerRows, &*clear); std::vector snapshot(bytes); ReadTextureSurface(resource, *keys, snapshot); - return std::make_shared(context, *context.detiler, resource, components, snapshot, depthCompare); + return std::make_shared(context, *context.detiler, resource, components, snapshot, depthCompare, layerRows); } auto snapshotGeneration = generation; std::optional>> committed; @@ -1043,7 +1108,7 @@ std::shared_ptr cachedTexture(const Context& context, std::spanCached() ? 0u : heldBytes(*source); counters.fromStorage.fetch_add(1, std::memory_order_relaxed); } else if (clear) { - entry.texture = createTexture(*context.detiler, resource, components, std::span{}, false, &*clear); + entry.texture = createTexture(*context.detiler, resource, components, std::span{}, false, layerRows, &*clear); entry.accounted = entry.texture->AllocationBytes(); counters.cleared.fetch_add(1, std::memory_order_relaxed); } else { @@ -1058,7 +1123,7 @@ 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 = createTexture(*context.detiler, resource, components, entry.bytes, depthCompare); + entry.texture = createTexture(*context.detiler, resource, components, entry.bytes, depthCompare, layerRows); entry.accounted = entry.texture->AllocationBytes(); constexpr std::size_t SnapshotCompareBytes = 256u << 10u; if (entry.bytes.size() > SnapshotCompareBytes && (entry.generation != 0 || entry.untracked[0] != 0)) parkSnapshot(cache, entry.bytes); @@ -1923,7 +1988,7 @@ struct ReusedTable { std::vector elements; std::vector queries; std::vector> ranges; - std::vector imageSamplers; + std::vector imageSamplers; std::vector samplers; std::vector bound; bool replayable = false; @@ -2058,6 +2123,9 @@ void ValidateRuntimeResources(const CompiledShader& shader, std::span shaders, const ColorTarget* target, std::uint32_t colorAttachments, std::uint64_t indexAddress, std::size_t indexBytes) { + registeredImageViews.clear(); + for (const auto& shader : shaders) if (shader.registeredImageViews != nullptr) registeredImageViews.insert(registeredImageViews.end(), shader.registeredImageViews->begin(), shader.registeredImageViews->end()); + Require(registeredImageViewsCurrent(registeredImageViews), "AGC graphics: registered image range changed after resource capture"); PerformanceTimer frameTiming("Resources.Prepare"); const auto stageStart = std::chrono::steady_clock::now(); phaseStart = stageStart; @@ -2454,17 +2522,21 @@ void ShaderResources::buildComplete() { PerformanceTimer frameTiming("Resources.Complete"); BuildLookups lookups; Require(!completed, "cannot update a published descriptor heap"); + Require(registeredImageViewsCurrent(registeredImageViews), "AGC graphics: registered image range changed after resource preparation"); const auto stageStart = std::chrono::steady_clock::now(); phaseStart = stageStart; try { // The lookups run in plan order: Revalidate walks the bindings the same way, and consecutive // storage elements of one mip chain share the previous element's image. previousSampled = {}; - for (auto& source : samplerSources) source.singleLevelImage = source.mipmappedImage = false; + for (auto& source : samplerSources) { + source.singleLevelImage = source.mipmappedImage = false; + source.pinnedLod.reset(); + } for (const auto& deferred : deferredImages) resolveImageBinding(*deferred.binding, bindings[deferred.index], std::span>(samplers).subspan(deferred.firstSampler, deferred.samplerCount)); previousSampled = {}; deferredImages.clear(); - applyAnisoOverride(); + applySamplerVariants(); // The records served their purpose: each holds the cache entry's objects as of stage A, // which would otherwise keep a replaced texture or an evicted storage image (and its device // memory) alive, outside the caches' budgets, for as long as this object is cached. @@ -2473,12 +2545,13 @@ void ShaderResources::buildComplete() { nextImageRecord = 0; Require(textures.size() == plannedSampledImages && storageTextures.size() == plannedStorageImages, "image lookups disagree with the descriptor plan"); resolveImageTables(); + Require(registeredImageViewsCurrent(registeredImageViews), "AGC graphics: registered image range changed during resource materialization"); timing.bindingsMs += phase(BuildPhase::Images); requireGuestBuffers(); guestMemory.UploadFinish(usesBda, htileClear); timing.uploadMs += phase(BuildPhase::Upload); - if (usesBda) bda = std::make_unique(context, guestMemory); - else if (usesFaultBuffer) bda = std::make_unique(context); + if (usesBda) bda = std::make_unique(context, guestMemory, faultReportOnly); + else if (usesFaultBuffer) bda = std::make_unique(context, faultReportOnly); if (bda) bda->SetSrtGuards(std::move(srtGuardFaults)); finishImageTables(); phase(BuildPhase::Bda); @@ -2557,7 +2630,9 @@ void ShaderResources::buildComplete() { switch (binding.layout.descriptorType) { case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: write.pBufferInfo = buffers.data() + buffers.size(); - for (const auto index : binding.allocations) { + for (std::size_t element = 0; element < binding.allocations.size(); ++element) { + const auto index = binding.allocations[element]; + if (allocations[index].role == ShaderRecompiler::DescriptorRole::FaultBuffer) faultDescriptors.push_back({writes.size(), buffers.size(), static_cast(element)}); buffers.push_back(descriptor(allocations[index])); } break; @@ -2782,7 +2857,10 @@ void VisitContentKey(const CompiledShader& shader, bool dataWords, bool movableB // must not serve a build with another (the variant implies it, this makes it explicit). packBits(binding.bufferWritten); visit(static_cast(binding.imageSamplers.size())); - for (const auto sampler : binding.imageSamplers) visit(sampler); + for (const auto sampler : binding.imageSamplers) { + visit(static_cast(sampler)); + visit(static_cast(sampler >> 32u)); + } visit(static_cast(binding.imageShapes.size())); for (const auto shape : binding.imageShapes) visit(static_cast(shape)); } @@ -2803,7 +2881,7 @@ void ShaderResources::AppendContentKey(const CompiledShader& shader, bool dataWo }; for (const auto& binding : shader.program->bindings) { addWords(20); - addWords(binding.imageSamplers.size()); + addWords(binding.imageSamplers.size() * 2u); addWords(binding.imageShapes.size()); if ((dataWords && !movableBuffers) || !DataRole(binding.role)) addWords(binding.guestDescriptor.size()); for (const auto* bits : {&binding.imageWritten, &binding.samplerDepthCompare, &binding.imageDepthCompare, &binding.imageAtomic, &binding.samplerUnnormalized, &binding.samplerIntegerBorder, &binding.samplerCoordinatesIgnored, &binding.imageUnnormalized, &binding.imageUnnormalizedBaseLevel, &binding.bufferWritten}) addWords(bits->size() / 32u + (bits->size() % 32u != 0 ? 1u : 0u)); @@ -3506,7 +3584,7 @@ ShaderResources::OwnRefreshFallback ShaderResources::refreshOwnObjects(std::span if (binding.kind == ShaderRecompiler::DescriptorKind::SampledImage) { for (std::uint32_t element = 0; element < binding.count; ++element) { const auto words = std::span(binding.guestDescriptor).subspan(static_cast(element) * 8u, 8u); - if (IsNullTextureResource(words)) continue; + if (IsNullSampledTextureResource(words)) continue; if (textureIndex >= textures.size()) return OwnRefreshFallback::Uncached; const auto index = textureIndex++; const auto overlap = listed(index, false); @@ -3556,7 +3634,7 @@ bool CompletePendingCpuWrites(const Context& context, std::uint64_t address, std const auto overlaps = [&](const auto& writers) { return std::any_of(writers.begin(), writers.end(), [&](const auto& writer) { return writer->CopiedWritesOverlap(address, bytes); }); }; - if ((context.copiedWriters != nullptr && overlaps(*context.copiedWriters)) || overlaps(*drawWriters) || (labels && recorder->PendingLabelIn(address, bytes))) { + if ((context.copiedWriters != nullptr && overlaps(*context.copiedWriters)) || overlaps(*drawWriters) || (labels && recorder->CompletionLabelIn(address, bytes))) { Recorder::CountSync(4); recorder->SyncThrough(address, bytes); return true; @@ -3591,6 +3669,11 @@ void noteEpochProof(bool reused, bool verified, bool failed) { } bool ShaderResources::Revalidate(std::span shaders, ProofReport* report) { + if (!registeredImageViewsCurrent(registeredImageViews)) { + if (report != nullptr) *report = {ProofPath::Full, ProofFailure::Other}; + provedEpoch = 0; + return false; + } if (EpochProofs()) { const auto epoch = GuestMemory::CollectEpoch(); const auto proof = GuestMemory::ProofSerial(); @@ -3669,11 +3752,12 @@ bool ShaderResources::revalidateFull(std::span shaders, Pr const auto raw = std::span(binding.guestDescriptor).subspan(static_cast(element) * elementWords, elementWords); std::array narrowed{}; const auto words = firstLayerWords(raw, binding.imageShape, narrowed, false); - if (IsNullTextureResource(words)) continue; + if (IsNullSampledTextureResource(words)) continue; const auto resource = DecodeTextureResource(words); const VkComponentMapping components = ViewComponents(resource); const VkImageViewType multisampleViewType = binding.imageShape == ShaderRecompiler::DescriptorImageShape::Image2DMultisampleArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; - if (textureIndex >= textures.size() || cachedTexture(context, words, resource, components, 0, !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element), multisampleViewType) != textures[textureIndex]) return false; + const bool depthCompare = !binding.imageDepthCompare.empty() && binding.imageDepthCompare.at(element); + if (textureIndex >= textures.size() || cachedTexture(context, words, resource, components, 0, depthCompare, multisampleViewType, layerRowsBinding(binding.imageShape, resource, depthCompare)) != textures[textureIndex]) return false; ++textureIndex; } } else if (binding.kind == ShaderRecompiler::DescriptorKind::StorageImage) { @@ -4894,7 +4978,7 @@ bool ShaderResources::precollectImages() { } ImageRecord record; record.sampled = sampled; - if (!IsNullTextureResource(words)) { + if (sampled ? !IsNullSampledTextureResource(words) : !IsNullTextureResource(words)) { const auto* memo = words.size() == 8 ? &decodedImage(context.device, words) : nullptr; record.resource = memo != nullptr ? memo->resource : DecodeTextureResource(words); record.guestBytes = memo != nullptr ? memo->guestBytes : DescribeSurface(record.resource).guestBytes; @@ -4978,12 +5062,12 @@ std::shared_ptr ShaderResources::fastTexture(const ImageRecord& record) return record.texture; } -void ShaderResources::applyAnisoOverride() { +void ShaderResources::applySamplerVariants() { for (std::size_t index = 0; index < samplerSources.size(); ++index) { const auto& source = samplerSources[index]; const auto variant = SingleLevelSamplerWords(source.words, source.singleLevelImage, source.mipmappedImage); - if (!variant.has_value()) continue; - const auto& words = *variant; + if (!variant.has_value() && !source.pinnedLod.has_value()) continue; + const auto words = source.pinnedLod.has_value() ? PinnedLodSamplerWords(variant.has_value() ? *variant : source.words, *source.pinnedLod) : *variant; if (context.samplerCache != nullptr && !SamplerCacheDisabled()) { samplers[index] = context.samplerCache->Get(context, words, source.compareEnable, source.unnormalized, true, source.integerBorder); } else { @@ -5006,35 +5090,47 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind std::array narrowed{}; const auto words = firstLayerWords(raw, binding.imageShape, narrowed, true); const auto* record = nextRecord(); - if (IsNullTextureResource(words)) { + if (IsNullSampledTextureResource(words)) { item.imageAllocations.push_back(std::numeric_limits::max()); --plannedSampledImages; continue; } const auto resource = record != nullptr && record->decoded ? record->resource : DecodeTextureResource(words); + const auto minLod = context.shaderImageMinLod && resource.minLod > resource.baseLevel * 256u && resource.lastLevel > resource.baseLevel ? std::min(resource.minLod - resource.baseLevel * 256u, (resource.lastLevel - resource.baseLevel) * 256u) : 0u; + if (minLod != 0u && !shaderSamplers.empty()) { + const auto firstSampler = static_cast(shaderSamplers.data() - samplers.data()); + for (std::uint32_t sampler = 0; sampler < shaderSamplers.size() && sampler < 32u; ++sampler) { + auto& source = samplerSources.at(firstSampler + sampler); + if (((binding.imageSamplers[element] >> sampler) & 1u) == 0 || !HoldsLodAtZero(source.words)) continue; + Require(!source.pinnedLod.has_value() || *source.pinnedLod == minLod, "an S# that holds the LOD at 0 is paired with images of different minimum LODs"); + source.pinnedLod = minLod; + } + } const bool forcedSrgb = ForcesDegammaOn(resource.format, binding.imageSamplers[element], shaderSamplers); RequireNonSeamlessCube(resource.dimension == TextureDimension::kCube, binding.imageSamplers[element], shaderSamplers); 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)); 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 bool layerRows = layerRowsBinding(binding.imageShape, resource, depthCompare); + Require(!layerRows || (resource.baseArray <= resource.depthOrLastArray && resource.depthOrLastArray - resource.baseArray >= context.limits.maxImageArrayLayers), "a 1D array within maxImageArrayLayers was bound as rows of a 2D image"); + if (!firstLayer && !depth1D && !firstSample && !layerRows && !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 && previousSampled.firstSample == firstSample && words.size() == 8 && std::equal(words.begin(), words.end(), previousSampled.words.begin())) texture = previousSampled.texture; + if (previousSampled.texture != nullptr && previousSampled.depthCompare == depthCompare && previousSampled.multisampleViewType == multisampleViewType && previousSampled.firstSample == firstSample && previousSampled.layerRows == layerRows && 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); + Require(!depthCompare, "reading sample 0 of a multisampled texture with a depth comparison through a 2D image is not implemented"); + texture = DepthSurfaceAt(resource.baseAddress) ? DepthSurfaceFirstSample(context, words, resource, components) : MultisampleTargetFirstSample(context, resource, components); } - if (texture == nullptr) texture = cachedTexture(context, words, resource, components, guestBytes, depthCompare, multisampleViewType); + if (texture == nullptr) texture = cachedTexture(context, words, resource, components, guestBytes, depthCompare, multisampleViewType, layerRows); if (resource.dimension == TextureDimension::k2DMsaa) multisampleSources.push_back(resource.baseAddress); bool baseLevel = false; if (element < binding.imageUnnormalized.size() && binding.imageUnnormalized[element]) { @@ -5051,8 +5147,9 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind if (!shaderSamplers.empty()) { const bool singleLevel = DescriptorSingleLevel(words); const auto firstSampler = static_cast(shaderSamplers.data() - samplers.data()); - for (std::uint32_t sampler = 0; sampler < shaderSamplers.size() && sampler < 32u; ++sampler) { - if (((binding.imageSamplers[element] >> sampler) & 1u) == 0) continue; + for (auto mask = binding.imageSamplers[element]; mask != 0u; mask &= mask - 1u) { + const auto sampler = static_cast(std::countr_zero(mask)); + if (sampler >= shaderSamplers.size()) break; auto& source = samplerSources.at(firstSampler + sampler); (singleLevel ? source.singleLevelImage : source.mipmappedImage) = true; } @@ -5062,6 +5159,7 @@ void ShaderResources::resolveImageBinding(const ShaderRecompiler::DescriptorBind previousSampled.depthCompare = depthCompare; previousSampled.multisampleViewType = multisampleViewType; previousSampled.firstSample = firstSample; + previousSampled.layerRows = layerRows; previousSampled.texture = texture; } textures.push_back(std::move(texture)); @@ -5171,6 +5269,11 @@ std::uint32_t SamplerTableCapacityFor(const Context& context, bool updateAfterBi } +std::uint64_t& DeviceProcLookups() { + thread_local std::uint64_t count = 0; + return count; +} + std::uint32_t ImageTableCapacity(const Context& context) { const auto capacity = ImageTableCapacityFor(context, false); return capacity != 0u ? capacity : ImageTableCapacityFor(context, true); @@ -5300,8 +5403,9 @@ void ShaderResources::resolveImageTables() { RequireNonSeamlessCube(resource.dimension == TextureDimension::kCube, binding.imageSamplers.at(element), stageSamplers); const auto numeric = ShaderRecompiler::DepthBitsTextureWidth(words[1], words[3]) != 0u ? ShaderRecompiler::IrTextureNumericClass::Float : ShaderRecompiler::SampledTextureNumericClass(static_cast(resource.format)); const bool integerImage = numeric == ShaderRecompiler::IrTextureNumericClass::Uint || numeric == ShaderRecompiler::IrTextureNumericClass::Sint; - for (std::uint32_t sampler = 0; sampler < stageSamplers.size() && sampler < 32u; ++sampler) { - if (((binding.imageSamplers.at(element) >> sampler) & 1u) == 0) continue; + for (auto mask = binding.imageSamplers.at(element); mask != 0u; mask &= mask - 1u) { + const auto sampler = static_cast(std::countr_zero(mask)); + if (sampler >= stageSamplers.size()) break; const auto& source = samplerSources.at(table.firstSampler + sampler); const auto descriptor = DecodeSamplerResource(source.words, source.unnormalized, true, source.integerBorder); const bool border = descriptor.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || descriptor.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER || descriptor.addressModeW == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; @@ -5432,6 +5536,7 @@ void ShaderResources::resolveImageTables() { const bool depthCompare = binding.imageDepthCompare.at(element); const VkImageViewType multisampleViewType = shape == ShaderRecompiler::DescriptorImageShape::Image2DMultisampleArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D; try { + Require(context.imageViewMinLod || EffectiveMinLod(resource) == 0.0f, "a sampled image table entry has a minimum LOD clamp, which needs VK_EXT_image_view_min_lod"); texture = cachedTexture(context, sampledWords, resource, components, guestBytes, depthCompare, multisampleViewType); } catch (const std::runtime_error& error) { table.map[faults + element] = rejectTableElement(table, words, error.what()); @@ -5795,6 +5900,7 @@ std::size_t ShaderResources::DrawUploadLimit() { std::shared_ptr ShaderResources::PrepareDrawBindings(Recorder& recorder, std::span moved, bool movedOnly) const { requireGuestBuffers(moved); if ((_set == VK_NULL_HANDLE && fragment.set == VK_NULL_HANDLE) || usesBda) return {}; + prepareFaultSlot(); thread_local std::vector>* readsSlot = nullptr; auto& reads = ShaderRecompiler::ThreadOwned(readsSlot); guestMemory.InPlaceReads(reads); @@ -5942,6 +6048,24 @@ std::shared_ptr ShaderResources::PrepareDrawBindi return result; } +void ShaderResources::prepareFaultSlot() const { + if (bda == nullptr) return; + const auto* recorder = Recorder::Active(); + bda->NoteUse(recorder != nullptr && recorder->Device() == context.device ? recorder->OpenSerial() : std::numeric_limits::max()); + if (!bda->TakeFaultSlot(context)) return; + std::vector writes; + writes.reserve(faultDescriptors.size()); + for (const auto& fault : faultDescriptors) { + descriptorBuffers[fault.buffer] = bda->Fault(); + auto write = descriptorWrites[fault.write]; + write.dstArrayElement = fault.element; + write.descriptorCount = 1; + write.pBufferInfo = &descriptorBuffers[fault.buffer]; + writes.push_back(write); + } + if (!writes.empty()) context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); +} + void ShaderResources::requireGuestBuffers(std::span moved) const { for (std::size_t index = 0; index < allocations.size(); ++index) { const auto& item = allocations[index]; @@ -5956,6 +6080,7 @@ void ShaderResources::requireGuestBuffers(std::span moved) co void ShaderResources::Bind(VkCommandBuffer commands, VkPipelineBindPoint bindPoint, VkPipelineLayout layout) const { requireGuestBuffers(); + prepareFaultSlot(); if (_set != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, bindPoint, layout, 0, 1, &_set, 0, nullptr); if (fragment.set != VK_NULL_HANDLE) context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, bindPoint, layout, ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet, 1, &fragment.set, 0, nullptr); } @@ -5987,16 +6112,19 @@ void ShaderResources::WriteBack() { } void ShaderResources::MarkGpuWrites(Recorder& recorder) { + if (bda != nullptr && recorder.Device() == context.device) bda->NoteUse(recorder.OpenSerial()); // The ranges this use reads in place through their host imports (read-only and written elements // alike, and an address-based build's whole leased heaps), before the writes: a CPU store into // one of them (the copy HLE) must not land before the recorded work read it. - const auto kind = guestMemory.HoldsLease() ? Recorder::ReadKind::AddressBased : Recorder::ReadKind::DispatchElement; + static const bool readOnlyLeaseSkip = std::getenv("APS5_NO_READ_ONLY_LEASE_SKIP") == nullptr; + const auto kind = !guestMemory.HoldsLease() ? Recorder::ReadKind::DispatchElement : readOnlyLeaseSkip && usesBda && bdaReadOnly ? Recorder::ReadKind::AddressBasedReadOnly : Recorder::ReadKind::AddressBased; if (auto split = guestMemory.SplitInPlaceReads()) { recorder.NotePendingReadList(std::move(split->space), kind); recorder.NotePendingReads(split->own, kind); } else { recorder.NotePendingReads(guestMemory.InPlaceReads(), kind); } + const auto inRun = std::exchange(afterRunSelf, nullptr); if (SkipWriteBack()) return; for (std::size_t index = 0; index < storageTextures.size(); ++index) { if (!storageWritten[index] || storageTextures[index] == nullptr) continue; @@ -6006,7 +6134,12 @@ void ShaderResources::MarkGpuWrites(Recorder& recorder) { // Written sub-ranges of buffers the GPU copied out of a host import go back into it by the GPU, // recorded here after the work: those regions then need no CPU write-back (HasCopiedWrites), // and the note and mark below cover them like direct writes. - guestMemory.RecordCopyBacks(recorder); + if (inRun != nullptr && recorder.RunOpen() && guestMemory.HasGpuCopies()) { + recorder.AfterRun([inRun](Recorder& after) { inRun->guestMemory.RecordCopyBacks(after); }); + guestMemory.MarkStagedWrites(); + } else { + guestMemory.RecordCopyBacks(recorder); + } // Only the written elements' ranges (AddWritable): a read-only element is neither noted here // nor marked as a direct write, so CPU reads of its memory never wait for this work. recorder.NotePendingWrites(guestMemory.Writes()); @@ -6046,6 +6179,16 @@ bool ShaderResources::ReadsOverlap(std::uint64_t address, std::size_t bytes) con return std::any_of(reads.begin(), reads.end(), [&](const auto& range) { return address < range.second && range.first < address + bytes; }); } +void ShaderResources::RunAccesses(std::vector>& out) const { + out.clear(); + guestMemory.RunAccesses(out); + if (usesBda) out.push_back({0, std::numeric_limits::max(), Recorder::RunAccessWritten}); + for (const auto& [image, written] : StorageImages()) { + const auto handle = reinterpret_cast(image); + out.push_back({handle, handle + 1, Recorder::RunAccessImage | (written ? Recorder::RunAccessWritten : 0u)}); + } +} + std::vector> ShaderResources::StorageImages() const { std::vector> images; for (std::size_t index = 0; index < storageTextures.size(); ++index) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp index 8b8bd0a0d4..a29670c0c3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp @@ -168,7 +168,7 @@ struct Module { } }; -Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing, bool imageInt64Atomics, bool geometryShader, bool sampleRateShading, bool bufferInt64Atomics, bool shaderClipDistance, bool shaderCullDistance, bool viewportIndexLayer, bool runtimeDescriptorArray, bool demoteToHelperInvocation, bool shaderTerminateInvocation, std::span primitiveLocations = {}) { +Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing, bool imageInt64Atomics, bool geometryShader, bool sampleRateShading, bool bufferInt64Atomics, bool shaderClipDistance, bool shaderCullDistance, bool viewportIndexLayer, bool runtimeDescriptorArray, bool demoteToHelperInvocation, bool shaderTerminateInvocation, bool shaderResourceMinLod, std::span primitiveLocations = {}) { using Stage = ShaderRecompiler::ShaderStage; Require(compiled.program != nullptr, "missing compiled shader"); const auto& shader = *compiled.program; @@ -282,6 +282,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi capability == spv::CapabilityImage1D || capability == spv::CapabilityImageGatherExtended || capability == spv::CapabilityImageQuery || + (shaderResourceMinLod && capability == spv::CapabilityMinLod) || ((vertex || control || evaluation || mesh) && shaderClipDistance && capability == spv::CapabilityClipDistance) || (vertex && capability == spv::CapabilityDrawParameters) || (fragment && geometryShader && capability == spv::CapabilityGeometry) || @@ -668,7 +669,7 @@ bool DeclaresEarlyFragmentTests(const ShaderRecompiler::RecompileResult& program return false; } -std::set ValidateShaders(std::span shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing, bool imageInt64Atomics, bool geometryShader, bool sampleRateShading, bool bufferInt64Atomics, bool shaderClipDistance, bool shaderCullDistance, bool viewportIndexLayer, bool runtimeDescriptorArray, bool demoteToHelperInvocation, bool shaderTerminateInvocation, bool meshShader) { +std::set ValidateShaders(std::span shaders, const State& state, const VkPhysicalDeviceSubgroupProperties& subgroup, bool fragmentShaderBarycentric, bool descriptorIndexing, bool imageInt64Atomics, bool geometryShader, bool sampleRateShading, bool bufferInt64Atomics, bool shaderClipDistance, bool shaderCullDistance, bool viewportIndexLayer, bool runtimeDescriptorArray, bool demoteToHelperInvocation, bool shaderTerminateInvocation, bool meshShader, bool shaderResourceMinLod) { using Stage = ShaderRecompiler::ShaderStage; const bool tessellation = state.stages.path == ShaderPath::Tessellation; const bool mesh = state.stages.path == ShaderPath::Geometry; @@ -706,7 +707,7 @@ std::set ValidateShaders(std::span shaders, Require(shaders[i].program != nullptr, "missing compiled shader"); Require(shaders[i].stage == expected, "graphics stage order disagrees"); for (const auto& binding : shaders[i].program->bindings) Require(binding.descriptorSet == ShaderRecompiler::RuntimeAbi::DescriptorSet || (shaders[i].stage == ShaderRecompiler::ShaderStage::Fragment && binding.descriptorSet == ShaderRecompiler::RuntimeAbi::FragmentDescriptorSet), "graphics resource uses a descriptor set other than its stage's"); - const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric, descriptorIndexing, imageInt64Atomics, geometryShader, sampleRateShading, bufferInt64Atomics, shaderClipDistance, shaderCullDistance, viewportIndexLayer, runtimeDescriptorArray, demoteToHelperInvocation, shaderTerminateInvocation, primitiveLocations); + const auto current = Inspect(shaders[i], state, subgroup, fragmentShaderBarycentric, descriptorIndexing, imageInt64Atomics, geometryShader, sampleRateShading, bufferInt64Atomics, shaderClipDistance, shaderCullDistance, viewportIndexLayer, runtimeDescriptorArray, demoteToHelperInvocation, shaderTerminateInvocation, shaderResourceMinLod, primitiveLocations); if (i != 0) { for (const auto& [location, signature] : current.inputs) { const auto output = previous.outputs.find(location); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp index 003f005bbc..d554809e07 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp @@ -342,7 +342,7 @@ VkStencilOpState stencilFace(std::uint32_t compare, std::uint32_t ops, std::uint 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]; + char detail[128]; 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); @@ -1010,12 +1010,22 @@ struct DecodedColorFormat { ShaderRecompiler::ColorExportPacking packing = ShaderRecompiler::ColorExportPacking::None; }; +bool UintPacked(ShaderRecompiler::ColorExportPacking packing) { + return packing == ShaderRecompiler::ColorExportPacking::Uint || packing == ShaderRecompiler::ColorExportPacking::UintClamp8 || packing == ShaderRecompiler::ColorExportPacking::UintClamp16; +} + const char* PackedColorTargetName(ShaderRecompiler::ColorExportPacking packing) { if (packing == ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4) return "4_4_4_4 unorm"; - if (packing == ShaderRecompiler::ColorExportPacking::Uint) return "unsigned integer"; + if (UintPacked(packing)) return "unsigned integer"; return packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 ? "11_11_10 float" : "10_11_11 unorm"; } +ShaderRecompiler::ColorExportPacking HalfExportPacking(VkFormat format) { + if (format == VK_FORMAT_R8_UINT || format == VK_FORMAT_R8G8_UINT || format == VK_FORMAT_R8G8B8A8_UINT) return ShaderRecompiler::ColorExportPacking::UintClamp8; + if (format == VK_FORMAT_R16_UINT || format == VK_FORMAT_R16G16_UINT || format == VK_FORMAT_R16G16B16A16_UINT) return ShaderRecompiler::ColorExportPacking::UintClamp16; + return ShaderRecompiler::ColorExportPacking::Uint; +} + bool WordPacked(ShaderRecompiler::ColorExportPacking packing) { return packing == ShaderRecompiler::ColorExportPacking::Unorm10_11_11 || packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 || packing == ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4; } @@ -1274,7 +1284,7 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) { const auto group = read(queue.userConfig, 0x25b, RegisterBank::UserConfig); const auto vertices = (group >> 9u) & 0x1ffu; const auto primitives = group & 0x1ffu; - validate(!fastLaunch || (!vertexWorkgroup && inputPrimitive == 1 && vertices == 1 && primitives == 1), "fast launch other than one point per subgroup is unsupported"); + validate(!fastLaunch || (!vertexWorkgroup && vertices == 1 && primitives == 1), "fast launch other than one vertex and one primitive per subgroup is unsupported"); const auto maxVertices = read(queue.context, 0x1ff); const auto verticesPerPrimitive = vertexWorkgroup ? 3u : read(queue.context, 0x2ce); const auto outputType = read(queue.context, 0x29b); @@ -1329,7 +1339,7 @@ ShaderStages DecodeShaderStages(const QueueState& queue, bool vertexWorkgroup) { } else { const bool fastLaunch = ((value >> 19u) & 3u) == 1u; validate((value & ~(fastLaunch ? 0x004fec30u : 0x0047ec30u)) == 0, "unsupported geometry routing, fast launch or wave-ID state"); - result.mesh = meshConfiguration(primitive, fastLaunch); + result.mesh = meshConfiguration(fastLaunch ? 1u : primitive, fastLaunch); } return result; } @@ -1377,6 +1387,16 @@ std::string DepthMaintenanceRejection(const QueueState& queue) { return zeroMessage(0x000, control->second, "DB_RENDER_CONTROL depth copy, resummarize or decompress"); } +std::optional DecodeDepthRenderControl(const QueueState& queue) { + const auto& cx = queue.context; + const auto control = find(cx, 0x000); + const auto zInfo = find(cx, 0x010); + if (control == cx.end() || (control->second & 0x63u) == 0u || zInfo == cx.end() || (zInfo->second & 3u) == 0u) return std::nullopt; + const auto high = find(cx, 0x01a); + const auto address = (high == cx.end() ? 0ull : static_cast(high->second & 0xffu) << 40u) | (static_cast(read(cx, 0x012)) << 8u); + return DepthRenderControl{address & ~std::uint64_t{65535u}, 1u << ((zInfo->second >> 2u) & 3u), control->second}; +} + ReversedBlend TranslateReversedBlend(std::uint32_t blendControl, std::uint8_t componentMapping, VkFormat format, bool forceDestinationAlphaOne) { const auto refuse = [&](const char* reason) { char text[320]; @@ -1829,11 +1849,12 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { if ((weighted(state.colorBlendOp) && (constantColor(state.srcColorBlendFactor) || constantColor(state.dstColorBlendFactor))) || (weighted(state.alphaBlendOp) && (constantColor(state.srcAlphaBlendFactor) || constantColor(state.dstAlphaBlendFactor)))) throw std::runtime_error("AGC graphics: blending into an 8_8 color target with the alternate component order with a constant color factor, which Vulkan applies to the second channel with the green blend constant instead of the alpha one, is not implemented"); } } - if (color.packing == ShaderRecompiler::ColorExportPacking::Uint) state.blendEnable = VK_FALSE; + if (UintPacked(color.packing)) state.blendEnable = VK_FALSE; if (WordPacked(color.packing)) { const std::string packed = std::string(color.packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 ? "an " : "a ") + PackedColorTargetName(color.packing); 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"); + if (state.srcColorBlendFactor == VK_BLEND_FACTOR_ONE && state.dstColorBlendFactor == VK_BLEND_FACTOR_ZERO && state.colorBlendOp == VK_BLEND_OP_ADD) state.blendEnable = VK_FALSE; Require(!state.blendEnable, "blending into " + packed + " color target is unsupported"); const bool nibbles = color.packing == ShaderRecompiler::ColorExportPacking::Unorm4_4_4_4; const std::uint32_t packedMask = nibbles ? 0xfu : 7u; @@ -1857,7 +1878,8 @@ State DecodeState(const QueueState& queue, bool vertexWorkgroup) { } if (color.packing == ShaderRecompiler::ColorExportPacking::Uint) { const auto slotExport = (exportFormat >> (4u * color.exportIndex)) & 0xfu; - if (slotExport != 1 && slotExport != 2 && slotExport != 3 && slotExport != 9) throw std::runtime_error("AGC graphics: color export format " + std::to_string(slotExport) + " into an unsigned integer target is unsupported"); + if (slotExport == 4) color.packing = HalfExportPacking(color.format); + else if (slotExport != 1 && slotExport != 2 && slotExport != 3 && slotExport != 9) throw std::runtime_error("AGC graphics: color export format " + std::to_string(slotExport) + " into an unsigned integer target is unsupported"); } 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; }; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index 0a31225ffe..50cf7281b3 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -14,12 +14,16 @@ #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/MapImportGate.hpp" // For the declaration of PendingStorageOverlaps, defined below. #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" +#include "ShaderCacheDirectory.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include "prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#include "prx/libkernel/include/Api.hpp" #include "RdnaDecoder/include/RdnaDecoder/RdnaDescriptorFormat.hpp" #include "IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/BufferFormat.hpp" #include @@ -44,6 +48,9 @@ #include #include #include +#if defined(__APPLE__) +#include +#endif namespace AgcDriver::Graphics { @@ -60,6 +67,11 @@ void OwnedImage::Allocate(const Context& device, const VkMemoryAllocateInfo& all namespace { +Recorder* recorderFor(const Context& context) { + auto* recorder = Recorder::Active(); + return recorder != nullptr && recorder->Device() == context.device ? recorder : nullptr; +} + // APS5_PROFILE_DRAW: accumulate texture setup phases and report every 200 textures. struct TextureProfile { double allocate = 0, read = 0, gpu = 0, view = 0; @@ -77,6 +89,8 @@ struct TextureProfile { // batch of their own (APS5_NO_RECORDED_CLEAR=1, or no recorder). std::uint64_t storageRecordedClears = 0; std::uint64_t storageWaitedClears = 0; + std::uint64_t layerRows = 0; + double layerRowsMs = 0; }; TextureProfile& Profile() { @@ -214,10 +228,12 @@ void countStorageWriteBack(std::uint64_t bytes, bool direct, bool drawn = false) // 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 { - AgcDriver::Mutex mutex; + MapImportGate::LeafMutex mutex; std::vector textures; }; +std::atomic publishHookForTest{nullptr}; + LiveImages& Live() { static LiveImages live; return live; @@ -238,6 +254,11 @@ bool BlockTrackingEnabled() { return !disabled; } +bool BlockWriteBackEach() { + static const bool each = std::getenv("APS5_BLOCK_WRITEBACK_EACH") != nullptr || std::getenv("APS5_BLOCK_WRITEBACK_WIDEN") == nullptr; + return each; +} + struct PhaseTimer { std::chrono::steady_clock::time_point last = std::chrono::steady_clock::now(); double lap() { @@ -274,7 +295,8 @@ namespace { void ChainMinLod(const Context& context, const GuestTextureResource& descriptor, VkImageViewCreateInfo& viewInfo, VkImageViewMinLodCreateInfoEXT& minLod) { const auto clamp = EffectiveMinLod(descriptor); if (clamp == 0.0f) return; - Require(context.imageViewMinLod, "guest texture descriptor uses a minimum LOD clamp, which needs VK_EXT_image_view_min_lod"); + Require(context.imageViewMinLod || context.shaderImageMinLod, "guest texture descriptor uses a minimum LOD clamp, which needs VK_EXT_image_view_min_lod, or shaderResourceMinLod without graphics pipeline libraries"); + if (!context.imageViewMinLod) return; minLod.minLod = clamp; minLod.pNext = viewInfo.pNext; viewInfo.pNext = &minLod; @@ -338,10 +360,11 @@ std::uint64_t SampledTextureMemory() { return SampledMemoryCounter().load(std::memory_order_relaxed); } -Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span snapshot, bool depthCompare, const VkClearColorValue* clear) : context(context) { +Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTextureResource& descriptor, VkComponentMapping components, std::span snapshot, bool depthCompare, bool layerRows, const VkClearColorValue* clear) : context(context) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; PhaseTimer timer; + const auto started = profile && layerRows ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; try { const auto colorFormat = SampledTextureFormat(context, descriptor.format); const bool srgbExpand = !depthCompare && SrgbExpandsToRgba8(context, descriptor.format); @@ -352,6 +375,12 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex if (IsBlockCompressed(descriptor.format)) { Require(context.textureCompressionBC, "device does not support BC compressed textures"); } + if (layerRows) { + Require(!depthCompare && descriptor.dimension == TextureDimension::k1DArray, "only a sampled 1D array is laid out as rows of a 2D image"); + Require(!srgbExpand, "a 1D array laid out as rows of a 2D image in a format expanded to RGBA8 is not implemented"); + Require(!IsBlockCompressed(descriptor.format), "a block-compressed 1D array laid out as rows of a 2D image is not implemented"); + Require(descriptor.baseLevel == 0u && std::min(descriptor.lastLevel, descriptor.mipCount - 1u) == 0u, "a 1D array past maxImageArrayLayers viewed at mip levels other than level 0 alone is not implemented"); + } const auto geometry = DescribeSurface(descriptor); const auto& mips = geometry.mips; @@ -372,14 +401,16 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex const auto linearBytes = sliceLinearBytes * arrayLayers; const bool depth1D = depthCompare && (descriptor.dimension == TextureDimension::k1D || descriptor.dimension == TextureDimension::k1DArray); + const auto rowLayers = layerRows ? geometry.imageLayers - descriptor.baseArray : 0u; + Require(!layerRows || (descriptor.baseArray < geometry.imageLayers && rowLayers <= context.limits.maxImageDimension2D / 2u), "a 1D array past maxImageArrayLayers whose viewed slices need more than maxImageDimension2D rows is not implemented"); VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; imageInfo.flags = (descriptor.dimension == TextureDimension::kCube ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0u) | (SrgbTwinFormat(context, vkFormat) != VK_FORMAT_UNDEFINED ? VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT : 0u); - imageInfo.imageType = depth1D ? VK_IMAGE_TYPE_2D : ImageTypeFor(descriptor.dimension); + imageInfo.imageType = depth1D || layerRows ? VK_IMAGE_TYPE_2D : ImageTypeFor(descriptor.dimension); imageInfo.format = vkFormat; - imageInfo.extent = {descriptor.width, depth1D ? 1u : descriptor.height, geometry.imageDepth}; - imageInfo.mipLevels = descriptor.mipCount; - Require(geometry.imageLayers <= context.limits.maxImageArrayLayers, "a texture array with more layers than maxImageArrayLayers is not implemented"); - imageInfo.arrayLayers = geometry.imageLayers; + imageInfo.extent = {descriptor.width, layerRows ? rowLayers * 2u : depth1D ? 1u : descriptor.height, geometry.imageDepth}; + imageInfo.mipLevels = layerRows ? 1u : descriptor.mipCount; + Require(layerRows || geometry.imageLayers <= context.limits.maxImageArrayLayers, "a texture array with more layers than maxImageArrayLayers is not implemented"); + imageInfo.arrayLayers = layerRows ? 1u : geometry.imageLayers; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | (srgbExpand ? VK_IMAGE_USAGE_TRANSFER_SRC_BIT : 0u); @@ -419,7 +450,7 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex batch = std::make_unique(context); commands = batch->Handle(); } - const VkImageSubresourceRange range{aspect, 0, descriptor.mipCount, 0, geometry.imageLayers}; + const VkImageSubresourceRange range{aspect, 0, imageInfo.mipLevels, 0, imageInfo.arrayLayers}; VkImageMemoryBarrier toTransferDst{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; toTransferDst.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toTransferDst.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; @@ -453,10 +484,10 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex // Entries may also be extents ("3840x2160"), since heap addresses change between runs. dumpWanted = dumpList.find(address) != std::string::npos || dumpList.find(extent) != std::string::npos; } + static const bool syncUploads = std::getenv("APS5_SYNC_TEXTURE_UPLOAD") != nullptr; + auto* recorder = dumpWanted || syncUploads ? nullptr : Recorder::Active(); auto staging = std::make_shared(context, static_cast(guestBytes), VK_BUFFER_USAGE_TRANSFER_SRC_BIT); std::memcpy(staging->Bytes().data(), snapshot.data(), snapshot.size()); - auto tiled = std::make_shared(context, static_cast(guestBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); - auto linear = std::make_shared(context, static_cast(linearBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); if (profile) Profile().allocate += timer.lap(); if (profile) Profile().read += timer.lap(); @@ -467,23 +498,25 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex // the batch (the cache may drop the texture before it completes). The dump aid needs the // linear bytes on the CPU, so it keeps the waiting batch, as does a build without a // recorder (tests). APS5_SYNC_TEXTURE_UPLOAD=1 restores the waiting batch for every upload. - static const bool syncUploads = std::getenv("APS5_SYNC_TEXTURE_UPLOAD") != nullptr; - auto* recorder = dumpWanted || syncUploads ? nullptr : Recorder::Active(); std::unique_ptr batch; VkCommandBuffer commands = VK_NULL_HANDLE; + std::shared_ptr tiled; + std::shared_ptr linear; if (recorder != nullptr) { commands = recorder->Commands(); + tiled = recorder->UploadScratchBuffer(Recorder::UploadScratch::Tiled, static_cast(guestBytes)); + linear = recorder->UploadScratchBuffer(Recorder::UploadScratch::Linear, static_cast(linearBytes)); recorder->Keep(staging, static_cast(guestBytes)); - recorder->Keep(tiled, static_cast(guestBytes)); - recorder->Keep(linear, static_cast(linearBytes)); recorder->Keep(owned); if (uploadOwned != nullptr) recorder->Keep(uploadOwned); } else { batch = std::make_unique(context); commands = batch->Handle(); + tiled = std::make_shared(context, static_cast(guestBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + linear = std::make_shared(context, static_cast(linearBytes), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); } - RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT | Recorder::UploadScratchReuseStages, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); CopyBuffer(context, commands, staging->Handle(), 0, tiled->Handle(), 0, guestBytes); 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); @@ -523,12 +556,22 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex toTransferDst.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toTransferDst.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; toTransferDst.image = uploadImage; - toTransferDst.subresourceRange = {aspect, 0, descriptor.mipCount, 0, geometry.imageLayers}; + toTransferDst.subresourceRange = layerRows ? VkImageSubresourceRange{aspect, 0, 1, 0, 1} : VkImageSubresourceRange{aspect, 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, &linearReadBarrier, 1, &toTransferDst); std::vector regions; regions.reserve(static_cast(arrayLayers) * mips.size()); - for (std::uint32_t layer = 0; layer < arrayLayers; ++layer) { + for (std::uint32_t row = 0; row < rowLayers * 2u; ++row) { + const auto layer = descriptor.baseArray + row / 2u; + VkBufferImageCopy region{}; + region.bufferOffset = static_cast(layer) * sliceLinearBytes + mips[0].linearOffset; + region.bufferRowLength = mips[0].pitchBytes / BytesPerElement(descriptor.format) * BlockWidth(descriptor.format); + region.imageSubresource = {aspect, 0, 0, 1}; + region.imageOffset = {0, static_cast(row), 0}; + region.imageExtent = {descriptor.width, 1u, 1u}; + regions.push_back(region); + } + for (std::uint32_t layer = 0; layer < (layerRows ? 0u : arrayLayers); ++layer) { const auto linearLayerOffset = static_cast(layer) * sliceLinearBytes; for (std::uint32_t level = 0; level < descriptor.mipCount; ++level) { if (!geometry.HasLayer(level, layer)) continue; @@ -605,11 +648,11 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; viewInfo.image = image; - viewInfo.viewType = depth1D ? (descriptor.dimension == TextureDimension::k1DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D) : ViewTypeFor(descriptor.dimension, viewLayerCount); + viewInfo.viewType = layerRows ? VK_IMAGE_VIEW_TYPE_2D : depth1D ? (descriptor.dimension == TextureDimension::k1DArray ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D) : ViewTypeFor(descriptor.dimension, viewLayerCount); viewInfo.format = srgbExpand ? VK_FORMAT_R8G8B8A8_SRGB : vkFormat; viewFormat = viewInfo.format; viewInfo.components = depthCompare ? VkComponentMapping{} : components; - viewInfo.subresourceRange = {aspect, descriptor.baseLevel, viewLevelCount, descriptor.baseArray, viewLayerCount}; + viewInfo.subresourceRange = layerRows ? VkImageSubresourceRange{aspect, 0, 1, 0, 1} : VkImageSubresourceRange{aspect, descriptor.baseLevel, viewLevelCount, descriptor.baseArray, viewLayerCount}; VkImageViewMinLodCreateInfoEXT minLod{VK_STRUCTURE_TYPE_IMAGE_VIEW_MIN_LOD_CREATE_INFO_EXT}; ChainMinLod(context, descriptor, viewInfo, minLod); @@ -622,6 +665,11 @@ Texture::Texture(const Context& context, TextureDetiler& detiler, const GuestTex auto& totals = Profile(); totals.view += timer.lap(); if (++totals.count % 200 == 0) std::fprintf(stderr, "[texture] %llu textures (%llu copied from storage images, %llu uploads recorded): allocate+image %.0f ms, guest read %.0f ms, detile+copy %.0f ms, view+buffer release %.0f ms\n", static_cast(totals.count), static_cast(totals.fromStorage), static_cast(totals.recordedUploads), totals.allocate, totals.read, totals.gpu, totals.view); + if (layerRows) { + const auto ms = std::chrono::duration(std::chrono::steady_clock::now() - started).count(); + totals.layerRowsMs += ms; + std::fprintf(stderr, "[texture] layer rows 0x%llx: %u slices of %u texels as a %ux%u image in %.2f ms (%llu layer-row textures, %.0f ms in all)\n", static_cast(descriptor.baseAddress), rowLayers, descriptor.width, descriptor.width, rowLayers * 2u, ms, static_cast(++totals.layerRows), totals.layerRowsMs); + } } countedBytes = allocationBytes; SampledMemoryCounter().fetch_add(countedBytes, std::memory_order_relaxed); @@ -1089,7 +1137,7 @@ bool Texture::CanCopyFrom(const StorageTexture& source, const GuestTextureResour // Same memory, same layout, same texel size: the GPU copy reinterprets the texels exactly as a // guest read through the sampled descriptor would. if (IsRepackedTextureFormat(descriptor.format)) return false; - return descriptor.baseAddress == from.baseAddress && descriptor.pipeBankXor == from.pipeBankXor && descriptor.width == from.width && descriptor.height == from.height && ArrayLayersServeView(from, descriptor) && descriptor.tileMode == from.tileMode && descriptor.mipCount <= from.mipCount && BytesPerElement(descriptor.format) == BytesPerElement(from.format) && BlockWidth(descriptor.format) == BlockWidth(from.format); + return descriptor.baseAddress == from.baseAddress && descriptor.pipeBankXor == from.pipeBankXor && descriptor.width == from.width && descriptor.height == from.height && descriptor.linearPitch == from.linearPitch && ArrayLayersServeView(from, descriptor) && descriptor.tileMode == from.tileMode && descriptor.mipCount <= from.mipCount && BytesPerElement(descriptor.format) == BytesPerElement(from.format) && BlockWidth(descriptor.format) == BlockWidth(from.format); } Texture::Texture(const Context& context, const std::shared_ptr& source, const GuestTextureResource& descriptor, VkComponentMapping components) : context(context), storageSource(source) { @@ -1189,7 +1237,7 @@ void Texture::prepareSrgbView(const GuestTextureResource& descriptor, VkImageVie viewInfo.pNext = nullptr; viewInfo.format = format; srgbInfo = viewInfo; - srgbMinLod = EffectiveMinLod(descriptor); + srgbMinLod = context.imageViewMinLod ? EffectiveMinLod(descriptor) : 0.0f; } VkImageView Texture::SrgbView(bool firstLayer, bool baseLevel) const { @@ -1306,21 +1354,21 @@ VkFormat StorageFormatOrUndefined(const Context& context, VkFormat format) { }; VkFormat storage = VK_FORMAT_UNDEFINED; const auto packed = PackedStorageViewFormat(format); + VkFormat linear = VK_FORMAT_UNDEFINED; + switch (format) { + case VK_FORMAT_R8G8B8A8_SRGB: linear = VK_FORMAT_R8G8B8A8_UNORM; break; + case VK_FORMAT_B8G8R8A8_SRGB: linear = VK_FORMAT_B8G8R8A8_UNORM; break; + case VK_FORMAT_A8B8G8R8_SRGB_PACK32: linear = VK_FORMAT_A8B8G8R8_UNORM_PACK32; break; + case VK_FORMAT_R8_SRGB: linear = VK_FORMAT_R8_UNORM; break; + case VK_FORMAT_R8G8_SRGB: linear = VK_FORMAT_R8G8_UNORM; break; + default: break; + } if (packed != VK_FORMAT_UNDEFINED) { if (supports(packed)) storage = format; + } else if (linear != VK_FORMAT_UNDEFINED) { + if (supports(linear)) storage = linear; } else if (supports(format)) { storage = format; - } else { - VkFormat linear = VK_FORMAT_UNDEFINED; - switch (format) { - case VK_FORMAT_R8G8B8A8_SRGB: linear = VK_FORMAT_R8G8B8A8_UNORM; break; - case VK_FORMAT_B8G8R8A8_SRGB: linear = VK_FORMAT_B8G8R8A8_UNORM; break; - case VK_FORMAT_A8B8G8R8_SRGB_PACK32: linear = VK_FORMAT_A8B8G8R8_UNORM_PACK32; break; - case VK_FORMAT_R8_SRGB: linear = VK_FORMAT_R8_UNORM; break; - case VK_FORMAT_R8G8_SRGB: linear = VK_FORMAT_R8G8_UNORM; break; - default: break; - } - if (linear != VK_FORMAT_UNDEFINED && supports(linear)) storage = linear; } std::lock_guard lock(table.mutex); table.formats.emplace(key, storage); @@ -1453,11 +1501,13 @@ std::shared_ptr StorageTexture::Create(const Context& context, T return true; }(); static_cast(unwatchHooked); + const MapImportGate::PublishHold publication(descriptor.baseAddress, DescribeSurface(descriptor).guestBytes); auto texture = std::shared_ptr(new StorageTexture(context, detiler, descriptor, mipLevel)); uploadReason = "first"; texture->upload(); texture->defaultMip = mipLevel; texture->view = texture->createView(mipLevel, false, texture->storageFormat); + if (const auto hook = publishHookForTest.load(std::memory_order_acquire)) hook(); { auto& live = Live(); std::lock_guard lock(live.mutex); @@ -1508,6 +1558,9 @@ StorageTexture::StorageTexture(const Context& context, TextureDetiler& detiler, trackedLayers = blockUnits ? static_cast((guestBytes + 65535) / 65536) : layered && LayerTrackingEnabled() ? geometry.layers : 1u; trackedLayerBytes = blockUnits ? 65536 : guestBytes / trackedLayers; layerGeneration.assign(trackedLayers, 0); + layerPendingSince.assign(trackedLayers, 0); + unwatchHash.assign(trackedLayers, {}); + pendingMiss.assign(trackedLayers, BaselineMiss::None); layerPending.assign(trackedLayers, false); layerOriginal.assign(trackedLayers, true); savedTicket.assign(trackedLayers, 0); @@ -1619,7 +1672,7 @@ private: // Storage images whose results have not reached guest memory yet. struct PendingWrites { - SlimMutex mutex; + MapImportGate::LeafMutex mutex; PendingList textures; // Images taken out of `textures` by a FlushPending still storing them (see adjacentPendingUnchanged). std::vector flushing; @@ -2508,7 +2561,10 @@ bool StorageTexture::refresh(std::uint32_t firstLayer, std::uint32_t lastLayer) uploadReason = "alias"; borrowUnits(*alias, borrowed); for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) { - if (borrowed[unit]) layerGeneration[unit] = currentFor(unit); + if (borrowed[unit]) { + layerGeneration[unit] = currentFor(unit); + forgetUnwatchHash(unit, 1); + } } refreshGeneration(); } @@ -2623,21 +2679,141 @@ void traceKeyStore(const char* path, const GuestTextureResource& descriptor, std } -void StorageTexture::captureGuestBytes(const std::vector* layers) { +void StorageTexture::captureGuestBytes(const std::vector* layers, bool keepWritten) { original.resize(static_cast(guestBytes)); 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)); + const auto runs = unitRuns(selected); + for (const auto& [begin, end] : runs) GuestMemory::FlushGpuWrites(descriptor.baseAddress + begin, static_cast(end - begin)); for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { if (!selected[layer]) continue; const auto offset = static_cast(layerBegin(layer) - descriptor.baseAddress); GuestMemory::ReadCommittedUnsynced(layerBegin(layer), std::span(original).subspan(offset, static_cast(layerBytes(layer)))); + } + if (keepWritten) { + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { + if (!selected[layer] || layerGeneration[layer] == 0) continue; + GuestMemory::CollectWritesUncached(layerBegin(layer), static_cast(layerBytes(layer))); + keepWrittenSince(layer); + } + } + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { + if (!selected[layer]) continue; layerOriginal[layer] = true; cancelStoredSave(layer); } } +bool StorageTexture::keepWrittenSince(std::uint32_t unit) { + constexpr std::uint64_t block = 65536; + const auto begin = layerBegin(unit); + const auto end = begin + layerBytes(unit); + const auto spanBegin = begin & ~(block - 1); + const auto spanBlocks = static_cast((end - spanBegin + block - 1) / block); + 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)) return false; + forgetKeptAtUnwatch(unit); + for (auto at = spanBegin; at < end; at += block) { + 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), unitBlockGeneration(unit, at))) && !cpuLeftUnchanged(unit, from, to, edge)) keptAtUnwatch.emplace(from, layerGeneration[unit]); + } + return true; +} + +constexpr std::uint64_t GenerationBaselineBytes = 1ull << 20; + +std::array StorageTexture::unitContentHash(std::uint32_t unit) const { + constexpr std::size_t chunkBytes = 65536; + std::array hash{0x5eed7e0d00000001ull, 0x5eed7e0d00000002ull}; + const auto begin = layerBegin(unit); + const auto bytes = static_cast(layerBytes(unit)); + std::vector chunk(std::min(chunkBytes, bytes)); + for (std::size_t at = 0; at < bytes; at += chunkBytes) { + const auto part = std::span(chunk).first(std::min(chunkBytes, bytes - at)); + GuestMemory::ReadCommittedUnsynced(begin + at, part); + hash[0] = ShaderRecompiler::HashBytes(part, hash[0]); + hash[1] = ShaderRecompiler::HashBytes(part, hash[1]); + } + if (hash[0] == 0 && hash[1] == 0) hash[0] = 1; + return hash; +} + +void StorageTexture::hashBeforeUnwatch(std::uint64_t address, std::size_t bytes, bool proven) { + if (released || unwatchHash.size() != trackedLayers || generationBaseline.size() == guestBytes) return; + for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) { + const auto begin = layerBegin(unit); + const auto unitBytes = static_cast(layerBytes(unit)); + if (begin >= address + bytes || address >= begin + unitBytes || layerGeneration[unit] == 0) continue; + const auto skip = [&](BaselineMiss miss) { + unwatchHash[unit].skipped = miss; + unwatchHash[unit].skippedGeneration = layerGeneration[unit]; + }; + if (layerPending[unit] || layerOriginal[unit] || savedTicket[unit] != 0) { + skip(BaselineMiss::UnwatchWhileSaved); + continue; + } + if (!proven || GuestMemory::CollectWritesUncached(begin, unitBytes) == 0) { + skip(BaselineMiss::UnwatchUnproven); + continue; + } + if (!GuestMemory::UnchangedSince(begin, unitBytes, layerGeneration[unit])) { + skip(BaselineMiss::UnwatchSawCpuWrites); + continue; + } + unwatchHash[unit] = {unitContentHash(unit), layerGeneration[unit]}; + } +} + +void StorageTexture::forgetUnwatchHash(std::uint32_t first, std::uint32_t count) { + for (auto unit = first; unit < first + count && unit < unwatchHash.size(); ++unit) unwatchHash[unit] = {}; +} + +const char* StorageTexture::BaselineMissText(BaselineMiss miss) { + switch (miss) { + case BaselineMiss::None: + return "no cause was recorded"; + case BaselineMiss::LeftWatchWhilePending: + return "its results became pending while its range was watched, and its bytes were not saved when the range left the watch"; + case BaselineMiss::SaveInFlight: + return "a save of its bytes was in flight when its results became pending, and it did not complete"; + case BaselineMiss::SavedBytesDropped: + return "its saved bytes were dropped while its results were pending"; + case BaselineMiss::NoUploadCopy: + return "no copy of its bytes was taken at its upload"; + case BaselineMiss::UploadCopyDiffers: + return "its bytes differ from the copy taken at its upload"; + case BaselineMiss::NoUnwatchHash: + return "no hash of its bytes was taken for its current generation: its range left the watch without the unwatch hook, or its generation changed after the hash"; + case BaselineMiss::UnwatchUnproven: + return "its range left the watch through the unwatch hook without its CPU writes proven (a failed collect or import page walk)"; + case BaselineMiss::UnwatchSawCpuWrites: + return "the write watch saw CPU writes to it after its generation, before its range left the watch"; + case BaselineMiss::UnwatchWhileSaved: + return "it had pending results or saved bytes when its range left the watch, so the unwatch hook did not hash it"; + case BaselineMiss::HashDiffers: + return "its bytes no longer match the hash taken when its range left the watch"; + } + throw std::runtime_error("AGC graphics: an unknown cause for a storage image unit without saved guest bytes"); +} + +StorageTexture::BaselineMiss StorageTexture::baselineMiss(std::uint32_t unit) const { + if (generationBaseline.size() == guestBytes) { + const auto offset = static_cast(layerBegin(unit) - descriptor.baseAddress); + return GuestMemory::EqualsCommittedUnsynced(layerBegin(unit), std::span(generationBaseline).subspan(offset, static_cast(layerBytes(unit)))) ? BaselineMiss::None : BaselineMiss::UploadCopyDiffers; + } + if (guestBytes <= GenerationBaselineBytes) return BaselineMiss::NoUploadCopy; + if (unwatchHash.size() != trackedLayers) return BaselineMiss::NoUnwatchHash; + const auto& entry = unwatchHash[unit]; + if (entry.generation != 0 && entry.generation == layerGeneration[unit]) return unitContentHash(unit) == entry.hash ? BaselineMiss::None : BaselineMiss::HashDiffers; + if (entry.skipped != BaselineMiss::None && entry.skippedGeneration == layerGeneration[unit]) return entry.skipped; + return BaselineMiss::NoUnwatchHash; +} + bool StorageTexture::originalHolds(std::uint64_t address, std::size_t bytes) const { for (auto layer = (address - descriptor.baseAddress) / trackedLayerBytes; layer < trackedLayers && layer * trackedLayerBytes < address - descriptor.baseAddress + bytes; ++layer) { if (!layerOriginal[static_cast(layer)]) return false; @@ -2690,8 +2866,6 @@ bool StorageTexture::compareUntracked(std::uint64_t address, std::size_t bytes, return true; } -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); @@ -3482,6 +3656,7 @@ std::uint64_t StorageTexture::writeBackWindows(std::span se std::vector slabPieces; std::vector> importCopies(segments.size()); std::vector seeds; + const bool spanWatched = GuestMemory::Watched(firstStored, static_cast(lastStored - firstStored)); struct AddedPiece { std::size_t pieces; std::size_t copy; @@ -3499,7 +3674,7 @@ std::uint64_t StorageTexture::writeBackWindows(std::span se const auto& import = *segments[segment].import; auto pieceEnd = std::min(guestEnd, segments[segment].end); if (UnitShadowEnabled()) pieceEnd = std::min(pieceEnd, SlabBoundary(import, guestBegin)); - const auto destination = guestBytes <= SmallStoreBytes ? std::optional{} : ShadowDestinationFor(context, import, guestBegin, pieceEnd); + const auto destination = spanWatched && guestBytes > SmallStoreBytes ? ShadowDestinationFor(context, import, guestBegin, pieceEnd) : std::nullopt; if (destination.has_value()) { if (slabPieces.empty() || slabPieces.back().slab != destination->slab) slabPieces.push_back({destination->slab, destination->pin, {}}); slabPieces.back().copies.push_back({scratchOffset, destination->offset, pieceEnd - guestBegin}); @@ -3661,7 +3836,7 @@ std::uint64_t StorageTexture::writeBackWindows(std::span se const bool scratchUsed = std::any_of(directRanges.begin(), directRanges.end(), [](const auto& range) { return range.first == range.second; }) || std::any_of(slabPieces.begin(), slabPieces.end(), [](const SlabPieces& pieces) { return !pieces.copies.empty(); }) || std::any_of(importCopies.begin(), importCopies.end(), [](const auto& copies) { return !copies.empty(); }) || !saves.empty() || !padding.copies.empty() || !seeds.empty(); auto tiledScratch = scratchUsed ? std::make_shared(context, static_cast(scratchTotal), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) : nullptr; detiler.BeginBatch(); - auto* recorder = Recorder::Active(); + auto* recorder = recorderFor(context); std::unique_ptr batch; VkCommandBuffer commands = VK_NULL_HANDLE; VkAccessFlags covered = 0; @@ -3920,26 +4095,64 @@ void StorageTexture::noteLayersBehind(std::uint32_t firstLayer, std::uint32_t la if (std::none_of(layerBehind.begin(), layerBehind.end(), [](bool behind) { return behind; })) layerBehind.clear(); } +namespace { +std::atomic flipSerial{0}; +std::atomic occasionalLabelStores{0}; +} + +void StorageTexture::NoteFlip() { + flipSerial.fetch_add(1, std::memory_order_relaxed); +} + +std::uint64_t StorageTexture::OccasionalLabelStores() { + return occasionalLabelStores.load(std::memory_order_relaxed); +} + 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; + if (const auto flip = flipSerial.load(std::memory_order_relaxed); writes == 1u || flip != writtenFlip) { + writtenOccasionally = writes == 1u || flip - writtenFlip > OccasionalWriteFlips; + writtenFlip = flip; + } + std::optional publication; + if (!layerPending.Any()) publication.emplace(descriptor.baseAddress, guestBytes); if (!layerPending.AllIn(first, count)) { std::vector unsaved; bool anyUnsaved = false; 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; + for (std::uint32_t layer = first; layer < first + count && !anyUnsaved; ++layer) anyUnsaved = !layerPending[layer] && !layerOriginal[layer] && (layerGeneration[layer] == 0 || savedTicket[layer] == 0 || keptForCpu(layer)); } 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; + if (layerPending[layer]) continue; + if (layerOriginal[layer]) { + pendingMiss[layer] = BaselineMiss::SavedBytesDropped; + continue; + } + auto miss = BaselineMiss::LeftWatchWhilePending; + if (layerGeneration[layer] != 0 && savedTicket[layer] != 0) miss = BaselineMiss::SaveInFlight; + else if (layerGeneration[layer] != 0 && !GuestMemory::Watched(layerBegin(layer), static_cast(layerBytes(layer)))) miss = baselineMiss(layer); + unsaved[layer] = keptForCpu(layer) || miss == BaselineMiss::LeftWatchWhilePending || miss == BaselineMiss::None; + pendingMiss[layer] = miss == BaselineMiss::None ? BaselineMiss::SavedBytesDropped : miss; anyUnsaved = anyUnsaved || unsaved[layer]; } + } else { + for (std::uint32_t layer = first; layer < first + count; ++layer) { + if (!layerPending[layer]) pendingMiss[layer] = layerOriginal[layer] ? BaselineMiss::SavedBytesDropped : BaselineMiss::SaveInFlight; + } } - if (anyUnsaved && !GuestMemory::Watched(descriptor.baseAddress, static_cast(guestBytes))) captureGuestBytes(&unsaved); + if (anyUnsaved && !GuestMemory::Watched(descriptor.baseAddress, static_cast(guestBytes))) { + captureGuestBytes(&unsaved, true); + for (std::uint32_t layer = first; layer < first + count; ++layer) { + if (unsaved[layer]) pendingMiss[layer] = BaselineMiss::SavedBytesDropped; + } + } + GuestMemory::CollectWritesUncached(layerBegin(first), static_cast(layerBegin(first + count - 1) + layerBytes(first + count - 1) - layerBegin(first))); if (!completionKept.empty()) { for (std::uint32_t layer = first; layer < first + count; ++layer) { if (layerPending[layer]) continue; @@ -3949,6 +4162,10 @@ void StorageTexture::markLayersPending(std::uint32_t first, std::uint32_t count) } layerPending.SetRange(first, count, true); } + const auto pendingSince = GuestMemory::TrackerGeneration(); + for (std::uint32_t layer = first; layer < first + count; ++layer) { + if (!keptForCpu(layer)) layerPendingSince[layer] = pendingSince; + } // 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. @@ -3966,6 +4183,7 @@ void StorageTexture::markLayersPending(std::uint32_t first, std::uint32_t count) } if (!handedOver.empty() || !handedUnits.empty()) supersedeHandedOver(first, count); if (eager) { + publication.reset(); WriteBack(); return; } @@ -4223,6 +4441,7 @@ std::uint64_t StorageTexture::storeDrawnRect(const DrawnArea& area, std::span>& ranges) const { + constexpr std::uint64_t block = 65536; + constexpr std::size_t runs = 8; + const auto start = ranges.size(); + const bool all = readTrapped.load(std::memory_order_acquire) || (descriptor.dccAddress != 0 && (uploadedKeys != DccKeys::Uncompressed || descriptor.mipCount > 1)) || (blockUnits && !BlockWriteBackEach()); + for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { + if (!layerPending[layer]) continue; + const auto begin = layerBegin(layer); + const auto end = begin + layerBytes(layer); + if (!all && !(address < end && begin < address + bytes)) continue; + const auto first = begin / block * block; + const auto last = (end + block - 1) / block * block; + if (ranges.size() > start && ranges.back().first <= first && first <= ranges.back().second) ranges.back().second = std::max(ranges.back().second, last); + else ranges.emplace_back(first, last); + } + if (ranges.size() - start <= runs) return; + auto hull = ranges[start]; + for (auto index = start + 1; index < ranges.size(); ++index) { + hull.first = std::min(hull.first, ranges[index].first); + hull.second = std::max(hull.second, ranges[index].second); + } + ranges.resize(start); + ranges.push_back(hull); +} + // Whether a pending unit of the image lies inside the range (writeBack's own selection): an image // listed by FlushPending without one stored nothing and only cost the GPU mutex. bool StorageTexture::pendingUnitInside(std::uint64_t address, std::size_t bytes) const { @@ -4259,6 +4503,29 @@ bool StorageTexture::pendingUnitInside(std::uint64_t address, std::size_t bytes) namespace { +#if defined(__APPLE__) +std::atomic labelTrapsSet{false}; + +std::uint64_t TrapPage() { + static const auto page = static_cast(::getpagesize()); + return page; +} + +void ResolveLabelTrap(std::uintptr_t begin, std::uintptr_t end) { + const auto bytes = static_cast(end - begin); + GuestMemory::FlushGpuWrites(begin, bytes); + while (AgcDriver::DriverDetail::DrawPipeline::HeldReads(begin, bytes)) { + std::this_thread::sleep_for(std::chrono::microseconds(50)); + GuestMemory::FlushGpuWrites(begin, bytes); + } +} + +bool LabelTrapsEnabled() { + static const bool enabled = std::getenv("APS5_NO_LABEL_READ_TRAP") == nullptr && GuestWriteWatch::GuestWriteWatchSetReadTrapResolver_nid_postfix(&ResolveLabelTrap); + return enabled; +} +#endif + void waitForIdlePublish(const char* reason) { if (reason == nullptr || std::strcmp(reason, "idle") != 0 || Recorder::InCompletion()) return; if (auto* recorder = Recorder::Active()) { @@ -4269,7 +4536,7 @@ void waitForIdlePublish(const char* reason) { } -bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except, const char* reason, PublishScope scope, bool* published, bool waited) { +bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, const StorageTexture* except, const char* reason, PublishScope scope, bool* published, bool waited, VkDevice device) { static_cast(FlushDepthPlanes(address, bytes)); if (published != nullptr) *published = false; const auto nothingToStore = [&] { @@ -4310,7 +4577,7 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons const auto* exempt = refreshing; for (auto it = pending.textures.MayOverlap(address, bytes) ? pending.textures.begin() : pending.textures.end(); it != pending.textures.end();) { auto* texture = *it; - if (texture != except && texture != exempt && texture->overlaps(address, bytes)) { + if (texture != except && texture != exempt && (device == VK_NULL_HANDLE || texture->context.device == device) && texture->overlaps(address, bytes)) { if (!texture->pendingUnitInside(address, bytes)) { pretestSkipped.fetch_add(1, std::memory_order_relaxed); ++it; @@ -4327,6 +4594,22 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons ++it; } } +#if defined(__APPLE__) + if (!flush.empty() && labelTrapsSet.load(std::memory_order_relaxed)) { + const auto selected = flush.size(); + for (auto it = pending.textures.begin(); it != pending.textures.end();) { + auto* texture = *it; + const auto covered = [&](const std::shared_ptr& stored) { return stored->overlaps(texture->descriptor.baseAddress, static_cast(texture->guestBytes)); }; + if (texture == except || texture == exempt || !texture->readTrapped.load(std::memory_order_acquire) || std::none_of(flush.begin(), flush.begin() + static_cast(selected), covered)) { + ++it; + continue; + } + texture->dirty = false; + if (auto alive = texture->weak_from_this().lock()) flush.push_back(std::move(alive)); + it = pending.textures.erase(it); + } + } +#endif // Still pending for the adjacency rule of writeBack until each one is stored. for (const auto& texture : flush) pending.flushing.push_back(texture.get()); pending.flushingCount.store(pending.flushing.size(), std::memory_order_release); @@ -4361,13 +4644,51 @@ bool StorageTexture::FlushPending(std::uint64_t address, std::size_t bytes, cons } std::lock_guard gpu(GuestMemory::GpuMutex()); 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) { + if (auto* recorder = Recorder::Active(); recorder != nullptr && !Recorder::InCompletion()) { try { - recorder->Reap(); - 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) { + static const bool wholeRange = std::getenv("APS5_FLUSH_WAIT_WHOLE_RANGE") != nullptr; + const bool publishes = !wholeRange && scope != PublishScope::None; + std::vector> stored; + const auto waitRanges = [&] { + stored.clear(); + if (wholeRange) { + stored.emplace_back(address, address + bytes); + return; + } + for (const auto& texture : flush) texture->appendStoredLayers(address, bytes, stored); + if (publishes) { + for (const auto& range : LiveShadowedRanges(address, bytes, scope)) stored.push_back(range); + } + }; + const auto pendingWriter = [&](const Recorder& active) { + constexpr std::uint64_t block = 65536; + std::uint64_t writer = 0; + if (publishes) { + constexpr auto top = std::numeric_limits::max(); + const auto first = address / block * block; + const auto end = bytes > top - address ? top : address + bytes; + const auto last = end > top - block ? end : (end + block - 1) / block * block; + writer = active.PendingLeaseSerial(first, static_cast(last - first)); + } + for (const auto& [begin, end] : stored) { + if (writer == active.OpenSerial()) break; + writer = std::max(writer, active.PendingWriterSerial(begin, static_cast(end - begin), ownNotes)); + } + return writer; + }; + const auto openWrites = [&stored](const Recorder& active) { + return std::any_of(stored.begin(), stored.end(), [&active](const auto& range) { return active.OpenWriteOverlaps(range.first, static_cast(range.second - range.first)); }); + }; + for (waitRanges(); pendingWriter(*recorder) != 0; waitRanges()) { + recorder->Reap(); + waitRanges(); + const auto writer = pendingWriter(*recorder); + if (writer == recorder->OpenSerial()) { + Recorder::CountSync(openWrites(*recorder) ? 1 : 3); + recorder->Sync(); + break; + } + if (writer == 0) break; recorder->FinishUpTo(writer); } } catch (...) { @@ -4412,6 +4733,28 @@ bool StorageTexture::WaitForFlushing(std::uint64_t address, std::size_t bytes, c } } +bool StorageTexture::hashedAtUnwatch(std::uint64_t address, std::size_t bytes) const { + return guestBytes > GenerationBaselineBytes && overlapsLive(address, bytes); +} + +void StorageTexture::SetPublishHookForTest(void (*hook)()) { + publishHookForTest.store(hook, std::memory_order_release); +} + +bool StorageTexture::UnwatchHookLocksGpu(std::span> ranges) { + if (AnyPendingOverlaps(ranges)) return true; + auto& live = Live(); + std::lock_guard lock(live.mutex); + for (const auto& range : ranges) { + if (range.second <= range.first) continue; + const auto bytes = static_cast(range.second - range.first); + for (const auto* texture : live.textures) { + if (texture->hashedAtUnwatch(range.first, bytes)) return true; + } + } + return false; +} + void StorageTexture::SaveBeforeUnwatch(std::uint64_t address, std::size_t bytes) { std::vector> overlapping; { @@ -4424,8 +4767,20 @@ void StorageTexture::SaveBeforeUnwatch(std::uint64_t address, std::size_t bytes) for (auto* texture : pending.textures) take(texture); for (auto* texture : pending.flushing) take(texture); } - if (overlapping.empty()) return; + std::vector> live; + const bool proven = GuestMemory::UnwatchWritesProven(); + { + auto& images = Live(); + std::lock_guard lock(images.mutex); + for (auto* texture : images.textures) { + if (!texture->hashedAtUnwatch(address, bytes)) continue; + if (auto alive = texture->weak_from_this().lock()) live.push_back(std::move(alive)); + } + } + if (overlapping.empty() && live.empty()) return; + RequireGpuLockBeforeImports("the unwatch hook"); std::lock_guard gpu(GuestMemory::GpuMutex()); + for (const auto& texture : live) texture->hashBeforeUnwatch(address, bytes, proven); for (const auto& texture : overlapping) texture->saveBeforeUnwatch(); } @@ -4439,6 +4794,15 @@ bool StorageTexture::keptAtUnwatchHolds(std::uint64_t from, std::uint32_t unit) return found != keptAtUnwatch.end() && layerOriginal[unit] && found->second == layerGeneration[unit]; } +bool StorageTexture::keptForCpu(std::uint32_t unit) const { + const auto begin = layerBegin(unit); + const auto end = begin + layerBytes(unit); + for (auto it = keptAtUnwatch.lower_bound(begin); it != keptAtUnwatch.end() && it->first < end; ++it) { + if (it->second == layerGeneration[unit]) return true; + } + return false; +} + void StorageTexture::forgetSavedOver(std::uint64_t address, std::size_t bytes) { // Only these units' bytes changed under the saved copy. Units outside keep it: results still // pending there (the flush before the fill or copy stored only the units it overlaps) are @@ -4460,7 +4824,6 @@ void StorageTexture::saveBeforeUnwatch() { 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; for (std::uint32_t unit = 0; unit < trackedLayers; ++unit) { if (!units[unit]) continue; if (layerGeneration[unit] == 0) { @@ -4470,21 +4833,7 @@ void StorageTexture::saveBeforeUnwatch() { cancelStoredSave(unit); continue; } - const auto begin = layerBegin(unit); - const auto end = begin + layerBytes(unit); - const auto spanBegin = begin & ~(block - 1); - const auto spanBlocks = static_cast((end - spanBegin + block - 1) / block); - 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); - for (auto at = spanBegin; at < end; at += block) { - 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), unitBlockGeneration(unit, at))) && !unchangedSinceBaseline(from, to)) keptAtUnwatch.emplace(from, layerGeneration[unit]); - } + if (!keepWrittenSince(unit)) continue; layerOriginal[unit] = true; cancelStoredSave(unit); } @@ -4503,7 +4852,7 @@ void StorageTexture::commitStoredSave(const std::vector& units) { if (units.empty()) return; const auto ticket = ++lastSaveTicket; for (const auto unit : units) savedTicket[unit] = ticket; - auto* recorder = Recorder::Active(); + auto* recorder = recorderFor(context); if (recorder == nullptr) { applyStoredSave(ticket, units); return; @@ -4544,7 +4893,8 @@ void StorageTexture::awaitStoredSave(const std::vector* layers) { last = std::max(last, layerBegin(unit) + layerBytes(unit)); } if (last <= first) return; - auto* recorder = Recorder::Active(); + auto* recorder = recorderFor(context); + if (recorder == nullptr) recorder = context.recorder; Require(recorder != nullptr, "a write-back's saved guest bytes are pending without a recorder"); recorder->SyncThrough(first, static_cast(last - first)); const auto outstanding = [&] { @@ -4676,26 +5026,28 @@ void StorageTexture::FlushDumpSelected(const char* reason) { for (const auto& [address, bytes] : ranges) static_cast(FlushPending(address, bytes, nullptr, reason)); } -void StorageTexture::FlushAllPending(const char* reason) { +void StorageTexture::FlushAllPending(const char* reason, VkDevice device) { std::optional onCpu; if (reason != nullptr && std::strcmp(reason, "idle") == 0) onCpu.emplace(); - static_cast(FlushPending(0, std::numeric_limits::max(), nullptr, reason)); + static_cast(FlushPending(0, std::numeric_limits::max(), nullptr, reason, PublishScope::Whole, nullptr, false, device)); } -bool StorageTexture::FlushAllPendingUntil(const char* reason, const std::function& stop) { +bool StorageTexture::FlushAllPendingUntil(const char* reason, const std::function& stop, VkDevice device) { std::optional onCpu; if (reason != nullptr && std::strcmp(reason, "idle") == 0) onCpu.emplace(); std::vector> ranges; { auto& pending = Pending(); std::lock_guard lock(pending.mutex); - for (const auto* texture : pending.textures) ranges.emplace_back(texture->descriptor.baseAddress, static_cast(texture->guestBytes)); + for (const auto* texture : pending.textures) { + if (device == VK_NULL_HANDLE || texture->context.device == device) ranges.emplace_back(texture->descriptor.baseAddress, static_cast(texture->guestBytes)); + } } constexpr std::size_t piece = std::size_t{4} << 20u; for (const auto& [address, bytes] : ranges) { for (std::size_t offset = 0; offset < bytes; offset += piece) { if (stop()) return false; - static_cast(FlushPending(address + offset, std::min(piece, bytes - offset), nullptr, reason)); + static_cast(FlushPending(address + offset, std::min(piece, bytes - offset), nullptr, reason, PublishScope::Whole, nullptr, false, device)); } } return true; @@ -4721,18 +5073,46 @@ std::size_t StorageTexture::StoreSmallPending(const char* reason) { return stored; } -void StorageTexture::StoreReadbackBeforeLabel(const char* reason) { +void StorageTexture::StoreReadbackBeforeLabel(const char* reason, VkDevice device) { static const bool disabled = std::getenv("APS5_NO_LABEL_READBACK_STORE") != nullptr; if (disabled) return; std::vector> ranges; + std::vector> occasional; { auto& pending = Pending(); std::lock_guard lock(pending.mutex); +#if defined(__APPLE__) + const bool trap = LabelTrapsEnabled(); + if (trap) labelTrapsSet.store(true, std::memory_order_relaxed); + const auto page = TrapPage(); + const auto sharesPage = [&](const StorageTexture* texture) { + const auto first = texture->descriptor.baseAddress & ~(page - 1); + const auto bytes = static_cast(((texture->descriptor.baseAddress + texture->guestBytes + page - 1) & ~(page - 1)) - first); + const auto shares = [&](const StorageTexture* other) { return other != texture && other->overlaps(first, bytes); }; + const auto sharesStored = [&](const StorageTexture* other) { return shares(other) && (other->descriptor.tileMode == TextureTileMode::kLinear || other->guestBytes <= SmallStoreBytes); }; + return std::any_of(pending.textures.begin(), pending.textures.end(), sharesStored) || std::any_of(pending.flushing.begin(), pending.flushing.end(), shares); + }; +#endif 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)); + if (texture->descriptor.tileMode != TextureTileMode::kLinear && texture->guestBytes > SmallStoreBytes) continue; +#if defined(__APPLE__) + if (trap && !sharesPage(texture) && (texture->readTrapped.load(std::memory_order_acquire) || !DirectMemoryAliased_nid_postfix(static_cast(texture->descriptor.baseAddress), static_cast(texture->guestBytes))) && GuestWriteWatch::GuestWriteWatchTrapReads_nid_postfix(static_cast(texture->descriptor.baseAddress), static_cast(texture->guestBytes))) { + texture->readTrapped.store(true, std::memory_order_release); + continue; + } +#endif + ranges.emplace_back(texture->descriptor.baseAddress, static_cast(texture->guestBytes)); + } + for (auto* texture : pending.textures) { + if (texture->descriptor.tileMode == TextureTileMode::kLinear || texture->guestBytes <= SmallStoreBytes || texture->guestBytes > OccasionalStoreBytes) continue; + if (device != VK_NULL_HANDLE && texture->context.device != device) continue; + if (!texture->writtenOccasionally.exchange(false, std::memory_order_relaxed)) continue; + occasionalLabelStores.fetch_add(1, std::memory_order_relaxed); + occasional.emplace_back(texture->descriptor.baseAddress, static_cast(texture->guestBytes)); } } for (const auto& [address, bytes] : ranges) static_cast(FlushPending(address, bytes, nullptr, reason)); + for (const auto& [address, bytes] : occasional) static_cast(FlushPending(address, bytes, nullptr, reason, PublishScope::Whole, nullptr, false, device)); } std::size_t StorageTexture::StoreDrawnLinear(const char* reason) { @@ -5402,6 +5782,7 @@ bool StorageTexture::clearByKeysFill(DccKeys keys, std::uint8_t key) { filledKeys = DccKeys::Uncompressed; keyProof = {}; forgetBorrowed(0, trackedLayers); + forgetUnwatchHash(0, trackedLayers); 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(); @@ -5573,6 +5954,7 @@ bool StorageTexture::FillClear(std::span pattern, std::u } void StorageTexture::WriteBack() { + storeTrappedOverlaps("explicit"); const auto previous = std::exchange(flushReason, "explicit"); writeBack(descriptor.baseAddress, static_cast(guestBytes)); flushReason = previous; @@ -5592,7 +5974,7 @@ std::shared_ptr StorageTexture::FindLive(std::uint64_t address, bool StorageTexture::SameSurfaceShape(const StorageTexture& other) const { const auto& mine = descriptor; const auto& theirs = other.descriptor; - return guestBytes == other.guestBytes && mine.width == theirs.width && mine.height == theirs.height && mine.depthOrLastArray == theirs.depthOrLastArray && mine.baseArray == theirs.baseArray && mine.mipCount == theirs.mipCount && mine.tileMode == theirs.tileMode && mine.dimension == theirs.dimension && mine.format == theirs.format && storageFormat == other.storageFormat && geometry.imageLayers == other.geometry.imageLayers && geometry.imageDepth == other.geometry.imageDepth; + return guestBytes == other.guestBytes && mine.width == theirs.width && mine.height == theirs.height && mine.linearPitch == theirs.linearPitch && mine.depthOrLastArray == theirs.depthOrLastArray && mine.baseArray == theirs.baseArray && mine.mipCount == theirs.mipCount && mine.tileMode == theirs.tileMode && mine.dimension == theirs.dimension && mine.format == theirs.format && storageFormat == other.storageFormat && geometry.imageLayers == other.geometry.imageLayers && geometry.imageDepth == other.geometry.imageDepth; } bool StorageTexture::ImageCoversGuestBytes() const { @@ -5999,7 +6381,53 @@ std::uint64_t StorageTexture::borrowUnits(StorageTexture& source, const std::vec return bytes; } +void StorageTexture::releaseReadTraps(bool sharingPages) { +#if defined(__APPLE__) + if (!labelTrapsSet.load(std::memory_order_relaxed)) return; + const auto release = [](const StorageTexture* texture) { + if (texture->readTrapped.exchange(false, std::memory_order_acq_rel)) GuestWriteWatch::GuestWriteWatchReleaseReads_nid_postfix(static_cast(texture->descriptor.baseAddress), static_cast(texture->guestBytes)); + }; + auto& pending = Pending(); + std::lock_guard lock(pending.mutex); + release(this); + if (!sharingPages) return; + const auto page = TrapPage(); + const auto first = descriptor.baseAddress & ~(page - 1); + const auto bytes = static_cast(((descriptor.baseAddress + guestBytes + page - 1) & ~(page - 1)) - first); + for (const auto* texture : pending.textures) { + if (texture != this && texture->overlaps(first, bytes)) release(texture); + } + for (const auto* texture : pending.flushing) { + if (texture != this && texture->overlaps(first, bytes)) release(texture); + } +#else + static_cast(sharingPages); +#endif +} + +void StorageTexture::storeTrappedOverlaps(const char* reason) { +#if defined(__APPLE__) + if (!labelTrapsSet.load(std::memory_order_relaxed)) return; + std::vector> ranges; + { + auto& pending = Pending(); + std::lock_guard lock(pending.mutex); + if (!dirty) return; + for (const auto* texture : pending.textures) { + if (texture != this && texture->readTrapped.load(std::memory_order_acquire) && texture->overlaps(descriptor.baseAddress, static_cast(guestBytes))) ranges.emplace_back(texture->descriptor.baseAddress, static_cast(texture->guestBytes)); + } + } + for (const auto& [address, bytes] : ranges) static_cast(FlushPending(address, bytes, nullptr, reason)); +#else + static_cast(reason); +#endif +} + void StorageTexture::writeBack(std::uint64_t address, std::size_t bytes) { + if (readTrapped.load(std::memory_order_acquire)) { + address = descriptor.baseAddress; + bytes = static_cast(guestBytes); + } // Block units are stored per access; an image whose units keep being asked for in pieces (a // consumer touching it through many small ranges, each piece re-arming the pending memos of // the next dispatch) stores every pending unit once more than `pieces` partial stores fall @@ -6008,7 +6436,7 @@ void StorageTexture::writeBack(std::uint64_t address, std::size_t bytes) { // widening them stored whole images again (movie stage: 39 vs 35 ms of GPU per present, 188 vs // 214 presents per 10 s); APS5_BLOCK_WRITEBACK_WIDEN=1 enables the widening, and // APS5_BLOCK_WRITEBACK_EACH=1 stores the touched units only, every time. - static const bool each = std::getenv("APS5_BLOCK_WRITEBACK_EACH") != nullptr || std::getenv("APS5_BLOCK_WRITEBACK_WIDEN") == nullptr; + const bool each = BlockWriteBackEach(); static const std::uint32_t pieces = [] { const char* text = std::getenv("APS5_BLOCK_WRITEBACK_PIECES"); return text != nullptr ? static_cast(std::strtoul(text, nullptr, 10)) : 4u; }(); static const std::uint64_t window = [] { const char* text = std::getenv("APS5_BLOCK_WRITEBACK_FRAMES"); return text != nullptr ? std::strtoull(text, nullptr, 10) : 8ull; }(); std::vector layers(trackedLayers, false); @@ -6055,6 +6483,16 @@ bool StorageTexture::unchangedSinceBaseline(std::uint64_t from, std::uint64_t to return GuestMemory::EqualsCommittedUnsynced(from, std::span(baseline).subspan(offset, static_cast(to - from))); } +bool StorageTexture::cpuLeftUnchanged(std::uint32_t layer, std::uint64_t from, std::uint64_t to, bool edge) const { + if (!unchangedSinceBaseline(from, to)) return false; + if (edge || layerPendingSince[layer] == 0) return true; + const std::array since{layerPendingSince[layer]}; + std::array changed{}; + std::array cpu{}; + GuestMemory::ChangedBlocks(from, static_cast(to - from), since, changed, cpu); + return cpu[0] == 0; +} + void StorageTexture::writeBackLayers(const std::vector& layers) { CaptureTrace::Log("writeback image=%llx generation=%llu reason=%s units=%zu", static_cast(descriptor.baseAddress), static_cast(generation), flushReason, static_cast(std::count(layers.begin(), layers.end(), true))); static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; @@ -6075,6 +6513,7 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { // may have reused the memory for something else entirely (see layerGeneration). A generation of // zero means no tracking, and the layer is stored whole. std::vector> keep; + std::vector> keptBlocks; std::vector skippedLayer(trackedLayers, false); bool skippedAny = false; std::size_t skipped = 0; @@ -6086,6 +6525,10 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { lastStored = std::max(lastStored, layerBegin(layer) + layerBytes(layer)); } if (lastStored <= firstStored) return; + struct ReleaseReadTrap { + StorageTexture& texture; + ~ReleaseReadTrap() { texture.releaseReadTraps(false); } + } releaseReadTrap{*this}; constexpr std::uint64_t block = 65536; const auto spanBegin = firstStored & ~(block - 1); const auto spanBlocks = static_cast((lastStored - spanBegin + block - 1) / block); @@ -6124,12 +6567,14 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { } }; bool unsavedOutsideWatch = false; + std::string unsavedCause; for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { if (!layers[layer]) continue; const auto begin = layerBegin(layer); const auto end = begin + layerBytes(layer); const bool unknown = tracked ? layerGeneration[layer] == 0 : !compared[layer]; if (unknown && !drawn.rect) { + if (!tracked && !unsavedOutsideWatch) unsavedCause = std::string("storing storage image results pending over memory outside the write watch without the guest bytes saved before them is not implemented (unit ") + std::to_string(layer) + " of " + std::to_string(trackedLayers) + ": " + BaselineMissText(pendingMiss[layer]) + ")"; unsavedOutsideWatch = unsavedOutsideWatch || !tracked; keep.emplace_back(begin, end); continue; @@ -6141,10 +6586,11 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { const bool edge = from != at || to != at + block; 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)); + const bool written = kept || unknown || (changedBlocks[index] == GuestMemory::BlockWritten && (compared[layer] || !edge || GuestMemory::StoredOver(from, static_cast(to - from), unitBlockGeneration(layer, at))) && !cpuLeftUnchanged(layer, from, to, edge)); if (written && (!drawn.rect || (tracked && !kept && !unknown && cpuBlocks[index] == 0))) { skippedAny = true; skippedLayer[layer] = true; + keptBlocks.emplace_back(from, layer); ++skipped; continue; } @@ -6156,6 +6602,7 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { for (std::uint32_t layer = 0; layer < trackedLayers; ++layer) { if (layers[layer]) forgetKeptAtUnwatch(layer); } + for (const auto& [from, layer] : keptBlocks) keptAtUnwatch.emplace(from, layerGeneration[layer]); // Debug aid: APS5_TRACE_FLUSH also names the blocks a write-back leaves to the CPU. static const bool traceKept = std::getenv("APS5_TRACE_FLUSH") != nullptr; if (traceKept && skippedAny) { @@ -6201,7 +6648,10 @@ 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] = collectedLayerGeneration(layer, now, !memoizedCollect); + if (layers[layer] && !skippedLayer[layer]) { + layerGeneration[layer] = collectedLayerGeneration(layer, now, !memoizedCollect); + forgetUnwatchHash(layer, 1); + } } refreshGeneration(); advanceAdjacent(adjacent, now, firstBlock, lastBlock); @@ -6220,7 +6670,7 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { else segments = importSegments(); if (flushReason != nullptr && std::strcmp(flushReason, "idle") == 0 && GuestMemory::Watched(firstStored, static_cast(lastStored - firstStored))) segments.clear(); if (!segments.empty()) { - Require(!unsavedOutsideWatch, "storing storage image results pending over memory outside the write watch without the guest bytes saved before them is not implemented"); + Require(!unsavedOutsideWatch, unsavedCause); // Outside the write watch the stored bytes are what the next store compares with. const auto saveUnits = GuestMemory::Watched(firstStored, static_cast(lastStored - firstStored)) ? std::vector{} : prepareStoredSave(layers, skippedLayer); if (blockUnits) { @@ -6279,7 +6729,7 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { padding = {}; } detiler.BeginBatch(); - auto* recorder = Recorder::Active(); + auto* recorder = recorderFor(context); std::unique_ptr batch; VkCommandBuffer commands = VK_NULL_HANDLE; auto timing = Recorder::NoTiming; @@ -6392,6 +6842,7 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { ++Profile().storageDirectWriteBacks; return; } + releaseReadTraps(true); // A surface whose memory is no longer registered (freed, or re-registered under another // allocation: no readable registered range contains it) has nothing to receive its results; // the round trip through the CPU below would store stale texels over whatever the memory @@ -6430,7 +6881,7 @@ void StorageTexture::writeBackLayers(const std::vector& layers) { // them (a GPU clear, a dropped or kept unit). Reading them says nothing about the image: a // stale unit (dropped, or kept for the CPU) still differs from them, so `original` vouches for // the content again only where it did before, or once every unit is stored. - Require(!unsavedOutsideWatch, "storing storage image results pending over memory outside the write watch without the guest bytes saved before them is not implemented"); + Require(!unsavedOutsideWatch, unsavedCause); std::vector unsaved = layerOriginal; unsaved.flip(); for (const auto& [begin, end] : unitRuns(unsaved)) { diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureCache.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureCache.cpp index 2e31ea6c75..8f8bb3c414 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureCache.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureCache.cpp @@ -38,7 +38,7 @@ std::shared_ptr TextureCache::Get(std::span words, entries.erase(it); break; } - const auto mips = ComputeMipLayout(resource.tileMode, resource.format, resource.width, resource.height, resource.mipCount); + const auto mips = ComputeMipLayout(resource.tileMode, resource.format, resource.width, resource.height, resource.mipCount, resource.linearPitch); const auto layers = resource.dimension == TextureDimension::k2DArray || resource.dimension == TextureDimension::k1DArray || resource.dimension == TextureDimension::kCube ? resource.depthOrLastArray + 1u : 1u; const auto bytes = ComputeSurfaceSize(mips, layers); Require(bytes != 0 && bytes <= std::numeric_limits::max(), "texture cache surface size overflow"); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp index 3c35882958..246720ca34 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetiler.cpp @@ -6,6 +6,8 @@ #include "prx/libSceAgcDriver/Graphics/shaders/CmaskClear_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/TextureDetileImage_spv.h" #include "prx/libSceAgcDriver/Graphics/shaders/TextureRepack_spv.h" +#include "prx/libSceAgcDriver/Graphics/shaders/TextureDetileImageWords_spv.h" +#include "prx/libSceAgcDriver/Graphics/shaders/TextureDetileWords_spv.h" #include #include #include @@ -122,6 +124,8 @@ void TextureDetiler::release() noexcept { 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); + if (imageWordModule) context.Function("vkDestroyShaderModule")(context.device, imageWordModule, nullptr); + if (wordModule) context.Function("vkDestroyShaderModule")(context.device, wordModule, nullptr); if (imagePipelineLayout) context.Function("vkDestroyPipelineLayout")(context.device, imagePipelineLayout, nullptr); if (imageDescriptorLayout) context.Function("vkDestroyDescriptorSetLayout")(context.device, imageDescriptorLayout, nullptr); for (const auto& entry : pipelines) context.Function("vkDestroyPipeline")(context.device, entry.second, nullptr); @@ -176,8 +180,12 @@ VkPipeline TextureDetiler::pipeline(TextureTileMode tileMode, std::uint32_t elem values[2] = 2u; std::copy(equation->bits.begin(), equation->bits.end(), values.begin() + 4); } - if (image && imageModule == VK_NULL_HANDLE) { - imageModule = CreateShaderModule(context, TEXTURE_DETILE_IMAGE_SPV, sizeof(TEXTURE_DETILE_IMAGE_SPV), "vkCreateShaderModule"); + const bool words = elementBytes < 4u && (image ? retile && values[2] != 2u : !retile || values[2] != 2u); + auto& shader = image ? (words ? imageWordModule : imageModule) : (words ? wordModule : module); + if (shader == VK_NULL_HANDLE) { + if (!image) shader = CreateShaderModule(context, TEXTURE_DETILE_WORDS_SPV, sizeof(TEXTURE_DETILE_WORDS_SPV), "vkCreateShaderModule detile words"); + else if (words) shader = CreateShaderModule(context, TEXTURE_DETILE_IMAGE_WORDS_SPV, sizeof(TEXTURE_DETILE_IMAGE_WORDS_SPV), "vkCreateShaderModule detile image words"); + else shader = CreateShaderModule(context, TEXTURE_DETILE_IMAGE_SPV, sizeof(TEXTURE_DETILE_IMAGE_SPV), "vkCreateShaderModule"); } std::array entries{}; for (std::uint32_t index = 0; index < entries.size(); ++index) entries[index] = {index, index * 4u, 4}; @@ -188,7 +196,7 @@ VkPipeline TextureDetiler::pipeline(TextureTileMode tileMode, std::uint32_t elem specialization.pData = values.data(); VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; - stage.module = image ? imageModule : module; + stage.module = shader; stage.pName = "main"; stage.pSpecializationInfo = &specialization; VkComputePipelineCreateInfo createInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; @@ -256,9 +264,9 @@ void TextureDetiler::Dispatch(VkCommandBuffer commands, TextureTileMode tileMode writes[1].descriptorCount = 1; writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; writes[1].pBufferInfo = &destinationInfo; - context.Function("vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); - context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target); - context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr); + context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); + context.Resolved(&DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target); + context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr); Push push{}; push.srcBase = static_cast(sourceBase); push.dstBase = static_cast(destinationBase); @@ -279,10 +287,10 @@ void TextureDetiler::Dispatch(VkCommandBuffer commands, TextureTileMode tileMode push.rowBegin = window.rowBegin; push.pipeBankXor = window.pipeBankXor ^ layerBankXor(tileMode, elementBytes, layout, slice); push.splitStride = layout.gcnSplitStride; - context.Function("vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(Push), &push); + context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(Push), &push); const auto groupsX = (columnEnd - window.columnBegin + 7u) / 8u; const auto groupsY = (rowEnd - window.rowBegin + 7u) / 8u; - context.Function("vkCmdDispatch")(commands, groupsX, groupsY, 1); + context.Resolved(&DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, groupsX, groupsY, 1); } void TextureDetiler::DispatchRepack(VkCommandBuffer commands, VkBuffer source, std::uint64_t sourceOffset, VkBuffer destination, std::uint64_t destinationOffset, std::uint64_t bytes) { @@ -322,12 +330,12 @@ void TextureDetiler::DispatchRepack(VkCommandBuffer commands, VkBuffer source, s writes[index].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; writes[index].pBufferInfo = index == 0 ? &sourceInfo : &destinationInfo; } - context.Function("vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); - context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, repackPipeline); - context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr); + context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); + context.Resolved(&DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, repackPipeline); + context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipelineLayout, 0, 1, &set, 0, nullptr); const std::array push{static_cast(sourceOffset - sourceDescriptorOffset), static_cast(destinationOffset - destinationDescriptorOffset), static_cast(words), static_cast(std::min(words, rowWords))}; - context.Function("vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data()); - context.Function("vkCmdDispatch")(commands, static_cast((std::min(words, rowWords) + 63u) / 64u), static_cast(rows), 1); + context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data()); + context.Resolved(&DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, static_cast((std::min(words, rowWords) + 63u) / 64u), static_cast(rows), 1); } std::uint32_t TextureDetiler::CmaskErrors() { @@ -395,24 +403,24 @@ void TextureDetiler::DispatchCmaskClear(VkCommandBuffer commands, VkBuffer cmask writes[0].pBufferInfo = &cmaskInfo; writes[1].pBufferInfo = &errorInfo; writes[2].pImageInfo = &imageInfo; - context.Function("vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); + context.Resolved(&DeviceFunctions::updateDescriptorSets, "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); + context.Resolved(&DeviceFunctions::cmdPipelineBarrier, "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_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_HOST_READ_BIT | VK_ACCESS_INDIRECT_COMMAND_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, cmaskPipeline); - context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, cmaskPipelineLayout, 0, 1, &set, 0, nullptr); + context.Resolved(&DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, cmaskPipeline); + context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, cmaskPipelineLayout, 0, 1, &set, 0, nullptr); const std::uint32_t mask = elementBytes >= 4 ? 0xffffffffu : (1u << (elementBytes * 8u)) - 1u; const std::array push{static_cast(base), width, height, (width + 1023u) / 1024u, clearWords[0] & mask, elementBytes == 8 ? clearWords[1] : 0u, write ? 1u : 0u}; - context.Function("vkCmdPushConstants")(commands, cmaskPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data()); - context.Function("vkCmdDispatch")(commands, (width + 7u) / 8u, (height + 7u) / 8u, 1); + context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, cmaskPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(push), push.data()); + context.Resolved(&DeviceFunctions::cmdDispatch, "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 | VK_ACCESS_SHADER_READ_BIT, anyAccess); - context.Function("vkCmdFillBuffer")(commands, cmask, cmaskOffset, cmaskBytes, 0xffffffffu); + context.Resolved(&DeviceFunctions::cmdFillBuffer, "vkCmdFillBuffer")(commands, cmask, cmaskOffset, cmaskBytes, 0xffffffffu); barrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, anyAccess); } @@ -447,9 +455,9 @@ void TextureDetiler::DispatchImage(VkCommandBuffer commands, TextureTileMode til writes[1].descriptorCount = 1; writes[1].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; writes[1].pImageInfo = &imageInfo; - context.Function("vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); - context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target); - context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, imagePipelineLayout, 0, 1, &set, 0, nullptr); + context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); + context.Resolved(&DeviceFunctions::cmdBindPipeline, "vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, target); + context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, imagePipelineLayout, 0, 1, &set, 0, nullptr); Push push{}; push.srcBase = static_cast(retile ? 0 : base); push.dstBase = static_cast(retile ? base : 0); @@ -470,10 +478,10 @@ void TextureDetiler::DispatchImage(VkCommandBuffer commands, TextureTileMode til push.rowBegin = window.rowBegin; push.pipeBankXor = window.pipeBankXor ^ layerBankXor(tileMode, elementBytes, layout, slice); push.splitStride = layout.gcnSplitStride; - context.Function("vkCmdPushConstants")(commands, imagePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(Push), &push); + context.Resolved(&DeviceFunctions::cmdPushConstants, "vkCmdPushConstants")(commands, imagePipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(Push), &push); const auto groupsX = (columnEnd - window.columnBegin + 7u) / 8u; const auto groupsY = (rowEnd - window.rowBegin + 7u) / 8u; - context.Function("vkCmdDispatch")(commands, groupsX, groupsY, 1); + context.Resolved(&DeviceFunctions::cmdDispatch, "vkCmdDispatch")(commands, groupsX, groupsY, 1); } } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetilerDescriptors.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetilerDescriptors.cpp index 5f78f5729c..4f6d535f67 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetilerDescriptors.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureDetilerDescriptors.cpp @@ -58,7 +58,7 @@ VkDescriptorSet TextureDetiler::allocateFrom(DescriptorRing& ring, VkDescriptorS allocation.descriptorSetCount = 1; allocation.pSetLayouts = &layout; VkDescriptorSet set = VK_NULL_HANDLE; - Check(context.Function("vkAllocateDescriptorSets")(context.device, &allocation, &set), allocateWhat); + Check(context.Resolved(&DeviceFunctions::allocateDescriptorSets, "vkAllocateDescriptorSets")(context.device, &allocation, &set), allocateWhat); ++ring.used; ring.serials[ring.current] = recorder != nullptr ? recorder->OpenSerial() : std::numeric_limits::max(); return set; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp index a3a487e766..119a994cd2 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/TextureTiling.cpp @@ -163,8 +163,9 @@ std::uint32_t TexelLevelDimension(std::uint32_t guestDimension, std::uint32_t le return std::max((std::max(guestDimension >> level, 1u) + texelScale - 1u) / texelScale, 1u); } -std::vector ComputeLinearMipLayout(std::uint32_t bytesPerElement, std::uint32_t texelWidth, std::uint32_t texelHeight, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) { +std::vector ComputeLinearMipLayout(std::uint32_t bytesPerElement, std::uint32_t texelWidth, std::uint32_t texelHeight, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount, std::uint32_t linearPitch) { const auto compressed = texelWidth != 1u || texelHeight != 1u; + Require(linearPitch == 0u || (!compressed && mipCount == 1u && linearPitch >= width), "a linear pitch needs a single-level uncompressed surface no wider than the pitch"); const auto elementsWidth0 = (width + texelWidth - 1u) / texelWidth; const auto elementsHeight0 = (height + texelHeight - 1u) / texelHeight; const auto blockWidth = CalcLinearBlockWidth(bytesPerElement); @@ -174,7 +175,7 @@ std::vector ComputeLinearMipLayout(std::uint32_t bytesPerElement, for (auto level = mipCount; level-- > 0;) { const auto elementsLevelWidth = std::max(ShiftCeil(elementsWidth0, level), 1u); const auto elementsLevelHeight = std::max(ShiftCeil(elementsHeight0, level), 1u); - const auto paddedElementsWidth = AlignUp(elementsLevelWidth, blockWidth); + const auto paddedElementsWidth = linearPitch != 0u ? linearPitch : AlignUp(elementsLevelWidth, blockWidth); const auto size = static_cast(paddedElementsWidth) * elementsLevelHeight * bytesPerElement; Require(size != 0, "computed a zero-sized linear texture mip level"); @@ -277,7 +278,7 @@ std::vector ComputeTiledMipLayout(TextureTileMode tileMode, std:: } -std::vector ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) { +std::vector ComputeMipLayout(TextureTileMode tileMode, std::uint32_t format, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount, std::uint32_t linearPitch) { Require(width != 0 && height != 0, "cannot compute mip layout for a zero-sized texture"); Require(mipCount != 0 && mipCount <= 16u, "texture mip count is out of range"); @@ -286,7 +287,7 @@ std::vector ComputeMipLayout(TextureTileMode tileMode, std::uint3 const auto texelWidth = BlockWidth(format); const auto texelHeight = BlockHeight(format); - if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, texelWidth, texelHeight, width, height, mipCount); + if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, texelWidth, texelHeight, width, height, mipCount, linearPitch); return ComputeTiledMipLayout(tileMode, bytesPerElement, texelWidth, texelHeight, width, height, mipCount); } @@ -430,10 +431,8 @@ std::vector ComputeGcnMipLayout(TextureTileMode tileMode, std::ui for (std::uint32_t level = 0; level < mipCount; ++level) { auto levelWidth = std::max(pitch >> level, 1u); auto levelHeight = std::max(height >> level, 1u); - if (pow2Pad && level != 0) { - levelWidth = std::bit_ceil(levelWidth); - levelHeight = std::bit_ceil(levelHeight); - } + if (pow2Pad && level != 0) levelWidth = std::bit_ceil(levelWidth); + if (pow2Pad && (level != 0 || mipCount > 1)) levelHeight = std::bit_ceil(levelHeight); auto rowElements = (levelWidth + texelWidth - 1u) / texelWidth; auto rows = (levelHeight + texelHeight - 1u) / texelHeight; bool macro = false; @@ -637,7 +636,7 @@ SurfaceGeometry DescribeSurface(const GuestTextureResource& descriptor) { geometry.mips.push_back({0u, geometry.guestBytes, 0u, geometry.sliceLinearBytes, descriptor.width, descriptor.height, static_cast(align(descriptor.width, blockWidth) / blockWidth), descriptor.width * descriptor.samples * bytesPerElement, false, 0u, 0u}); return geometry; } - geometry.mips = ComputeMipLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, descriptor.mipCount); + geometry.mips = ComputeMipLayout(descriptor.tileMode, descriptor.format, descriptor.width, descriptor.height, descriptor.mipCount, descriptor.linearPitch); const bool layered = descriptor.dimension == TextureDimension::k2DArray || descriptor.dimension == TextureDimension::k1DArray || descriptor.dimension == TextureDimension::kCube; geometry.layers = layered ? descriptor.depthOrLastArray + 1u : 1u; geometry.imageLayers = geometry.layers; @@ -711,7 +710,7 @@ GuestTextureResource ArrayLayerWindow(const GuestTextureResource& viewed, std::u std::vector ComputeElementMipLayout(TextureTileMode tileMode, std::uint32_t bytesPerElement, std::uint32_t width, std::uint32_t height, std::uint32_t mipCount) { Require(width != 0 && height != 0 && mipCount != 0 && mipCount <= 16u, "invalid surface mip chain"); Require(!IsGcnTiled(tileMode), "PS4 surfaces are laid out by ComputeGcnMipLayout from their pitch"); - if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, 1u, 1u, width, height, mipCount); + if (tileMode == TextureTileMode::kLinear) return ComputeLinearMipLayout(bytesPerElement, 1u, 1u, width, height, mipCount, 0u); return ComputeTiledMipLayout(tileMode, bytesPerElement, 1u, 1u, width, height, mipCount); } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp index 3a859308ae..c9a8e2e6a6 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp @@ -601,6 +601,29 @@ bool AnyShadowedOverlaps(std::span return false; } +std::vector> LiveShadowedRanges(std::uint64_t address, std::size_t bytes, PublishScope scope) { + std::vector> ranges; + if (!UnitShadowEnabled() || scope == PublishScope::None || bytes == 0) return ranges; + const auto end = bytes > std::numeric_limits::max() - address ? std::numeric_limits::max() : address + bytes; + auto& registry = Registry(); + std::lock_guard lock(registry.mutex); + for (const auto& shadow : overlapping(registry, address, end)) { + if (shadow->liveUnits == 0) continue; + const auto begin = std::max(address, shadow->importBase); + const auto stop = std::min(end, shadow->ImportEnd()); + if (stop <= begin) continue; + for (auto unit = shadow->UnitOf(begin); unit <= shadow->UnitOf(stop - 1); ++unit) { + if (!shadow->Live(unit)) continue; + const auto [unitBegin, unitEnd] = shadow->UnitRange(unit); + if (unitEnd <= unitBegin || (scope == PublishScope::PartialUnits && address <= unitBegin && end >= unitEnd)) continue; + if (!ranges.empty() && ranges.back().second == unitBegin) ranges.back().second = unitEnd; + else ranges.emplace_back(unitBegin, unitEnd); + } + } + if (ranges.size() > 8) ranges.assign(1, {ranges.front().first, ranges.back().second}); + return ranges; +} + std::uint64_t SlabBoundary(const HostImport& import, std::uint64_t address) { const auto base = import.base & ~(UnitBytes - 1); const auto unit = (address - base) / UnitBytes; diff --git a/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp b/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp index a2c4534fdc..f2de301ece 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp +++ b/core/libs/prx/libSceAgcDriver/Submit/src/CommandBuffer.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include "SceTypes.hpp" @@ -15,7 +16,8 @@ extern "C" { int APS5_VABI sceAgcDriverSubmitCommandBuffer(void* queue_context, const Packet* packet) { if (!queue_context || !packet) return EINVAL; - const auto queue = *reinterpret_cast(static_cast(queue_context) + 4); + std::uint32_t queue = 0; + std::memcpy(&queue, static_cast(queue_context) + 4, sizeof(queue)); if (queue != 0 && (queue < 0x20 || queue >= AgcDriver::DriverDetail::UnmeasuredSubmissionQueueEnd)) { throw std::runtime_error(std::string(__func__) + ": unsupported queue"); } @@ -26,7 +28,8 @@ int APS5_VABI sceAgcDriverSubmitCommandBuffer(void* queue_context, const Packet* int APS5_VABI sceAgcDriverSubmitMultiCommandBuffers(void* queue_context, uint32_t* const* command_buffers, const uint32_t* sizes_in_dwords, uint32_t count) { if (count == 0) return 0; if (!queue_context || !command_buffers || !sizes_in_dwords) return EINVAL; - const auto queue = *reinterpret_cast(static_cast(queue_context) + 4); + std::uint32_t queue = 0; + std::memcpy(&queue, static_cast(queue_context) + 4, sizeof(queue)); if (queue != 0 && (queue < 0x20 || queue >= AgcDriver::DriverDetail::UnmeasuredSubmissionQueueEnd)) { throw std::runtime_error(std::string(__func__) + ": unsupported queue"); } diff --git a/core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp b/core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp index f07d5c39c5..c2c1c6b4ce 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp +++ b/core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp @@ -3,6 +3,7 @@ #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include +#include #include #include #include @@ -42,6 +43,53 @@ std::uint32_t WorkloadDataPacketDwords(std::uint32_t bytes) { return payload == 1 ? 4 : payload + 7; } +constexpr std::uint32_t UserDataRegisterPacket = 0xC0017900u; +constexpr std::uint32_t UserDataInlineDword = 0x10000u; +constexpr std::uint32_t UserDataRegister = 0x342u; +constexpr std::uint32_t UserDataWritePacket = 0xC0003700u; +constexpr std::uint32_t UserDataWriteControl = 0x06010000u; +constexpr std::uint32_t UserDataWriteRegister = 0xC343u; +constexpr std::uint32_t UserDataDrawQueue = 4u; +constexpr std::uint32_t UserDataPopMarker = 0xC2000000u; +constexpr std::uint32_t UserDataPushMarker = 0xC1000000u; +constexpr std::uint32_t UserDataSetMarker = 0xC0000000u; + +std::uint32_t WriteUserData(std::uint32_t* commands, std::uint32_t asyncCompute, std::uint32_t value, const std::uint8_t* data, std::uint32_t size) { + const bool inlineData = data != nullptr && size != 0; + AgcDriver::GuestMemory::CheckRange(commands, std::size_t{inlineData ? WorkloadDataPacketDwords(size) : 3u} * sizeof(std::uint32_t), alignof(std::uint32_t), true); + if (inlineData) AgcDriver::GuestMemory::CheckRange(data, size, 1); + const std::uint32_t queue = asyncCompute == 0 ? UserDataDrawQueue : 0u; + commands[0] = UserDataRegisterPacket + (inlineData ? UserDataInlineDword : 0u) + queue; + commands[1] = UserDataRegister; + commands[2] = value; + auto* next = commands + 3; + if (inlineData) { + const auto first = std::min(size, 4u); + commands[3] = 0; + std::memcpy(commands + 3, data, first); + size -= first; + data += first; + next = commands + 4; + } + if (data == nullptr || size == 0) return static_cast(next - commands); + const std::uint64_t padded = std::uint64_t{size} + 3u; + next[3] = 0; + next[4] = 0; + next[0] = UserDataWritePacket + queue + (((static_cast(padded) << 14u) + 0x20000u) & 0x3FFF0000u); + next[1] = UserDataWriteControl; + next[2] = UserDataWriteRegister; + next[4 + static_cast((padded >> 2u) - 1u)] = 0; + std::memcpy(next + 4, data, size); + next += 4 + (padded >> 2u); + return static_cast(next - commands); +} + +std::uint32_t MarkerValue(std::uint32_t kind, std::uint32_t nameSize, std::uint32_t color) { + const auto dwords = ((nameSize << 13u) + 0x6000u) & 0x7F8000u; + const auto packedColor = ((color >> 1u) & 0x7C00u) | ((color >> 14u) & 0x3E0u) | (color >> 27u); + return kind + dwords + packedColor; +} + } extern "C" { @@ -74,6 +122,22 @@ std::uint32_t APS5_VABI sceAgcDriverUserDataGetPacketSize(std::uint32_t bytes) { return WorkloadDataPacketDwords(bytes); } +std::uint32_t APS5_VABI sceAgcDriverUserDataWritePacket(std::uint32_t* commands, std::uint32_t asyncCompute, std::uint32_t value, const void* data, std::uint32_t size) { + return WriteUserData(commands, asyncCompute, value, static_cast(data), size); +} + +std::uint32_t APS5_VABI sceAgcDriverUserDataWritePushMarker(std::uint32_t* commands, std::uint32_t asyncCompute, const char* name, std::uint32_t nameSize, std::uint32_t color) { + return WriteUserData(commands, asyncCompute, MarkerValue(UserDataPushMarker, nameSize, color), reinterpret_cast(name), nameSize); +} + +std::uint32_t APS5_VABI sceAgcDriverUserDataWriteSetMarker(std::uint32_t* commands, std::uint32_t asyncCompute, const char* name, std::uint32_t nameSize, std::uint32_t color) { + return WriteUserData(commands, asyncCompute, MarkerValue(UserDataSetMarker, nameSize, color), reinterpret_cast(name), nameSize); +} + +std::uint32_t APS5_VABI sceAgcDriverUserDataWritePopMarker(std::uint32_t* commands, std::uint32_t asyncCompute) { + return WriteUserData(commands, asyncCompute, UserDataPopMarker, nullptr, 0); +} + std::uint32_t APS5_VABI sceAgcDriverWaitUntilSafeForRendering(std::uint32_t** command, std::uint32_t capacity, std::uint32_t mode, std::uint32_t handle, int index) { if (!command || capacity < AgcDriver::RenderingWaitPacketWords || mode != 0 || index < 0) diff --git a/core/libs/prx/libSceAgcDriver/include/AgcDriver.hpp b/core/libs/prx/libSceAgcDriver/include/AgcDriver.hpp index 6c6e8fea9b..73600d9ffb 100644 --- a/core/libs/prx/libSceAgcDriver/include/AgcDriver.hpp +++ b/core/libs/prx/libSceAgcDriver/include/AgcDriver.hpp @@ -35,10 +35,10 @@ 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); +std::uint32_t APS5_VABI sceAgcDriverUserDataWritePacket(std::uint32_t* commands, std::uint32_t asyncCompute, std::uint32_t value, const void* data, std::uint32_t size); +std::uint32_t APS5_VABI sceAgcDriverUserDataWritePopMarker(std::uint32_t* commands, std::uint32_t asyncCompute); +std::uint32_t APS5_VABI sceAgcDriverUserDataWritePushMarker(std::uint32_t* commands, std::uint32_t asyncCompute, const char* name, std::uint32_t nameSize, std::uint32_t color); +std::uint32_t APS5_VABI sceAgcDriverUserDataWriteSetMarker(std::uint32_t* commands, std::uint32_t asyncCompute, const char* name, std::uint32_t nameSize, std::uint32_t color); int APS5_VABI sceAgcDriverGetShaderDebuggingStatus(); int APS5_VABI sceAgcDriverRegisterDefaultOwner(uint32_t owner); int APS5_VABI sceAgcDriverSetValidationErrorOutputFrequency(uint32_t frequency); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaAtomics.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaAtomics.cpp new file mode 100644 index 0000000000..f4c8ad5798 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/BdaAtomics.cpp @@ -0,0 +1,33 @@ +#include "prx/libSceAgcDriver/Execution/include/BdaAtomics.hpp" +#include + +namespace { + +struct Case { + VkDriverId driver; + bool usable; +}; + +constexpr Case Cases[]{ + {VK_DRIVER_ID_AMD_PROPRIETARY, true}, + {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, false}, +}; + +} + +int main() { + int failures = 0; + for (const auto& item : Cases) { + if (AgcDriver::BdaAtomicsUsable(item.driver) != item.usable) { + std::fprintf(stderr, "driver %d: expected atomics on buffer device addresses 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/BdaContracts.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp index ad805c3f66..3a51ca5e7f 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaContracts.cpp @@ -68,6 +68,75 @@ bool ScanStores(const std::function& main, const std::function return AgcDriver::Graphics::SpirvStoresThroughAddresses(module.words); } +enum FaultScanId : std::uint32_t { + FaultVoid = 1, FaultVoidFunction, FaultUint, FaultWords, FaultBlock, FaultBlockPointer, FaultWordPointer, FaultHelperFunction, FaultVariable, FaultZero, FaultReportLast, FaultOverflowWord, FaultScope, FaultSemantics, FaultMain, FaultMainLabel, FaultHelper, FaultHelperParameter, FaultHelperLabel, FaultBody, +}; + +bool ScanFaultReport(const std::function& main, const std::function& after = {}) { + ScanWords module{{spv::MagicNumber, 0x00010300u, 0u, 100u, 0u}}; + module.Op(spv::OpCapability, {spv::CapabilityShader}); + module.Op(spv::OpMemoryModel, {spv::AddressingModelLogical, spv::MemoryModelGLSL450}); + module.OpString(spv::OpEntryPoint, {spv::ExecutionModelGLCompute, FaultMain}, "main"); + module.Op(spv::OpDecorate, {FaultVariable, spv::DecorationDescriptorSet, 0u}); + module.Op(spv::OpDecorate, {FaultVariable, spv::DecorationBinding, 5u}); + module.Op(spv::OpTypeVoid, {FaultVoid}); + module.Op(spv::OpTypeFunction, {FaultVoidFunction, FaultVoid}); + module.Op(spv::OpTypeInt, {FaultUint, 32u, 0u}); + module.Op(spv::OpTypeRuntimeArray, {FaultWords, FaultUint}); + module.Op(spv::OpTypeStruct, {FaultBlock, FaultWords}); + module.Op(spv::OpTypePointer, {FaultBlockPointer, spv::StorageClassStorageBuffer, FaultBlock}); + module.Op(spv::OpTypePointer, {FaultWordPointer, spv::StorageClassStorageBuffer, FaultUint}); + module.Op(spv::OpTypeFunction, {FaultHelperFunction, FaultVoid, FaultBlockPointer}); + module.Op(spv::OpConstant, {FaultUint, FaultZero, 0u}); + module.Op(spv::OpConstant, {FaultUint, FaultReportLast, 7u}); + module.Op(spv::OpConstant, {FaultUint, FaultOverflowWord, ShaderRecompiler::BdaAbi::WrittenOverflowWord}); + module.Op(spv::OpConstant, {FaultUint, FaultScope, spv::ScopeDevice}); + module.Op(spv::OpConstant, {FaultUint, FaultSemantics, 0u}); + module.Op(spv::OpVariable, {FaultBlockPointer, FaultVariable, spv::StorageClassStorageBuffer}); + module.Op(spv::OpFunction, {FaultVoid, FaultMain, spv::FunctionControlMaskNone, FaultVoidFunction}); + module.Op(spv::OpLabel, {FaultMainLabel}); + main(module); + module.Op(spv::OpReturn, {}); + module.Op(spv::OpFunctionEnd, {}); + if (after) after(module); + return AgcDriver::Graphics::SpirvFaultReportOnly(module.words, 0, 5); +} + +void RunFaultReportScanTests() { + using AgcDriver::Graphics::Require; + Require(ScanFaultReport([](ScanWords& m) { + m.Op(spv::OpAccessChain, {FaultWordPointer, FaultBody, FaultVariable, FaultZero, FaultZero}); + m.Op(spv::OpAtomicCompareExchange, {FaultUint, FaultBody + 1u, FaultBody, FaultScope, FaultSemantics, FaultSemantics, FaultReportLast, FaultZero}); + m.Op(spv::OpAccessChain, {FaultWordPointer, FaultBody + 2u, FaultVariable, FaultZero, FaultReportLast}); + m.Op(spv::OpStore, {FaultBody + 2u, FaultZero}); + m.Op(spv::OpAtomicOr, {FaultUint, FaultBody + 3u, FaultBody + 2u, FaultScope, FaultSemantics, FaultReportLast}); + m.Op(spv::OpLoad, {FaultUint, FaultBody + 4u, FaultBody + 2u}); + }), "fault scan: loads, stores and atomics on the report words were taken for writes past the report"); + Require(!ScanFaultReport([](ScanWords& m) { + m.Op(spv::OpAccessChain, {FaultWordPointer, FaultBody, FaultVariable, FaultZero, FaultOverflowWord}); + m.Op(spv::OpStore, {FaultBody, FaultZero}); + }), "fault scan: a store to the written-page overflow word was missed"); + Require(!ScanFaultReport([](ScanWords& m) { + m.Op(spv::OpIAdd, {FaultUint, FaultBody, FaultZero, FaultReportLast}); + m.Op(spv::OpAccessChain, {FaultWordPointer, FaultBody + 1u, FaultVariable, FaultZero, FaultBody}); + m.Op(spv::OpStore, {FaultBody + 1u, FaultZero}); + }), "fault scan: a store at a computed word was taken for a report store"); + Require(!ScanFaultReport([](ScanWords& m) { + m.Op(spv::OpAccessChain, {FaultWordPointer, FaultBody, FaultVariable, FaultZero, FaultZero}); + m.Op(spv::OpCopyObject, {FaultWordPointer, FaultBody + 1u, FaultBody}); + m.Op(spv::OpStore, {FaultBody + 1u, FaultZero}); + }), "fault scan: a copied pointer to a report word was not followed"); + Require(!ScanFaultReport([](ScanWords& m) { m.Op(spv::OpFunctionCall, {FaultVoid, FaultBody, FaultHelper, FaultVariable}); }, [](ScanWords& m) { + m.Op(spv::OpFunction, {FaultVoid, FaultHelper, spv::FunctionControlMaskNone, FaultHelperFunction}); + m.Op(spv::OpFunctionParameter, {FaultBlockPointer, FaultHelperParameter}); + m.Op(spv::OpLabel, {FaultHelperLabel}); + m.Op(spv::OpAccessChain, {FaultWordPointer, FaultBody + 1u, FaultHelperParameter, FaultZero, FaultOverflowWord}); + m.Op(spv::OpStore, {FaultBody + 1u, FaultZero}); + m.Op(spv::OpReturn, {}); + m.Op(spv::OpFunctionEnd, {}); + }), "fault scan: the fault buffer handed to a function was not followed"); +} + void RunAddressStoreScanTests() { using AgcDriver::Graphics::Require; Require(!ScanStores([](ScanWords& m) { m.Op(spv::OpLoad, {IdUint, IdBody, IdPointer}); }), "store scan: a load through a physical pointer was taken for a store"); @@ -143,6 +212,7 @@ void RunBdaContractTests() { using namespace ShaderRecompiler; using AgcDriver::Graphics::SpirvStoresThroughAddresses; RunAddressStoreScanTests(); + RunFaultReportScanTests(); AgcDriver::Graphics::Require(!SpirvStoresThroughAddresses(MakeBdaTestShader(0x1000, 32)), "a BDA read was taken for a store through an address"); AgcDriver::Graphics::Require(!SpirvStoresThroughAddresses(MakeBdaDwordReadTestShader(0x1000, 4, false, false)), "BDA dword reads were taken for a store through an address"); AgcDriver::Graphics::Require(SpirvStoresThroughAddresses(MakeBdaStoreTestShader(0x1000, 7, 32, false)), "a BDA store was not seen"); @@ -156,6 +226,10 @@ void RunBdaContractTests() { auto unparsed = MakeBdaStoreTestShader(0x1000, 7, 32, false); unparsed[5] |= 0xffff0000u; AgcDriver::Graphics::Require(SpirvStoresThroughAddresses(unparsed), "a module with an instruction past its end was taken for one without stores"); + using AgcDriver::Graphics::SpirvFaultReportOnly; + AgcDriver::Graphics::Require(SpirvFaultReportOnly(MakeBdaTestShader(0x1000, 32), 0, 1) && SpirvFaultReportOnly(MakeBdaDwordReadTestShader(0x1000, 4, false, false), 0, 1) && SpirvFaultReportOnly(MakeBdaFaultSequenceTestShader({BdaAbi::FaultReason::Unmapped, BdaAbi::FaultReason::Unaligned}), 0, 1), "a BDA read program, which writes only the fault report, was given a full fault buffer"); + AgcDriver::Graphics::Require(!SpirvFaultReportOnly(MakeBdaStoreTestShader(0x1000, 7, 32, false), 0, 1) && !SpirvFaultReportOnly(MakeBdaWriteTestShader(0x1000, 7, 32, false), 0, 1), "a BDA store program, which records written pages, was given a report-only fault buffer"); + AgcDriver::Graphics::Require(!SpirvFaultReportOnly(MakeBdaTestShader(0x1000, 32), 0, 3) && !SpirvFaultReportOnly(headerOnly, 0, 1) && !SpirvFaultReportOnly(truncated, 0, 1) && !SpirvFaultReportOnly(unparsed, 0, 1), "a module without the fault buffer, or one that cannot be read, was taken for a report-only one"); const std::array capabilities{spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess}; const std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage"}; SpirvTargetOptions target{0x00401000u, 0x00010300u, 32, BdaAbi::Version, capabilities, extensions}; diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp index 0231d44364..561fba4fd5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp @@ -265,6 +265,8 @@ int main(int argc, char** argv) { } RunBdaStoreExecutionTests(device->GetContext()); std::cout << "BDA store boundary tests passed\n"; + RunBdaFaultSlotExecutionTests(device->GetContext()); + std::cout << "BDA report-only fault slot tests passed\n"; RunColorTransferTests(device->GetContext()); std::cout << "Vulkan BDA allocation and execution tests passed\n"; return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaExecution.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaExecution.cpp index 098d311465..13f0c33c83 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaExecution.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaExecution.cpp @@ -1,6 +1,7 @@ #include "BdaShader.hpp" #include "BdaAbi.hpp" #include "IntermediateRepresentation/IrMetadata/ShaderStage.hpp" +#include "prx/libSceAgcDriver/Graphics/include/BdaResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include #include @@ -26,7 +27,9 @@ using namespace AgcDriver::Graphics; class Pipeline { public: - Pipeline(const Context& context, std::span code, const std::array& buffers) : context(context) { + Pipeline(const Context& context, std::span code, const std::array& buffers) : Pipeline(context, code, std::array{whole(*buffers[0]), whole(*buffers[1]), whole(*buffers[2])}) {} + + Pipeline(const Context& context, std::span code, const std::array& buffers) : context(context) { try { std::array bindings{}; for (std::uint32_t i = 0; i < bindings.size(); ++i) bindings[i] = {i, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}; @@ -43,13 +46,12 @@ public: VkDescriptorSetAllocateInfo allocation{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, nullptr, pool, 1, &setLayout}; Check(context.Function("vkAllocateDescriptorSets")(context.device, &allocation, &set), "vkAllocateDescriptorSets"); for (std::uint32_t i = 0; i < buffers.size(); ++i) { - const VkDescriptorBufferInfo buffer{buffers[i]->Handle(), 0, buffers[i]->Bytes().size()}; VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; write.dstSet = set; write.dstBinding = i; write.descriptorCount = 1; write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - write.pBufferInfo = &buffer; + write.pBufferInfo = &buffers[i]; context.Function("vkUpdateDescriptorSets")(context.device, 1, &write, 0, nullptr); } VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -69,20 +71,26 @@ public: ~Pipeline() { release(); } - void Run(std::uint32_t groups) { + void Run(std::uint32_t groups) { RunTogether(context, {this}, groups); } + + static void RunTogether(const Context& context, std::initializer_list pipelines, std::uint32_t groups = 1) { CommandBatch batch(context); const auto commands = batch.Handle(); VkMemoryBarrier upload{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT}; context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &upload, 0, nullptr, 0, nullptr); - context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); - context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, 1, &set, 0, nullptr); - context.Function("vkCmdDispatch")(commands, groups, 1, 1); + for (const auto* pipeline : pipelines) { + context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->pipeline); + context.Function("vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline->layout, 0, 1, &pipeline->set, 0, nullptr); + context.Function("vkCmdDispatch")(commands, groups, 1, 1); + } VkMemoryBarrier download{VK_STRUCTURE_TYPE_MEMORY_BARRIER, nullptr, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT}; context.Function("vkCmdPipelineBarrier")(commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &download, 0, nullptr, 0, nullptr); batch.SubmitAndWait(); } private: + static VkDescriptorBufferInfo whole(Buffer& buffer) { return {buffer.Handle(), 0, buffer.Bytes().size()}; } + void release() noexcept { if (pipeline) context.Function("vkDestroyPipeline")(context.device, pipeline, nullptr); if (module) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); @@ -610,3 +618,30 @@ void RunBdaStoreExecutionTests(const Context& context) { run("split aligned dword", guest + 3, 32, coherent, {0xcd, 0xcd, 0xcd, 0xcd, 0xcd}, {}, Abi::FaultReason::Unmapped, guest + 3, 4); } } + +void RunBdaFaultSlotExecutionTests(const Context& context) { + namespace Abi = ShaderRecompiler::BdaAbi; + Buffer table(context, sizeof(Abi::Header), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const Abi::Header header{Abi::Version, 0, sizeof(Abi::Range), 0}; + std::memcpy(table.Bytes().data(), &header, sizeof(header)); + Buffer output(context, 4, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const VkDescriptorBufferInfo tableInfo{table.Handle(), 0, table.Bytes().size()}; + const VkDescriptorBufferInfo outputInfo{output.Handle(), 0, output.Bytes().size()}; + BdaResources first(context, true); + BdaResources second(context, true); + const auto firstSlot = first.Fault(); + const auto secondSlot = second.Fault(); + Require(firstSlot.buffer == secondSlot.buffer && firstSlot.range == sizeof(Abi::Fault) && secondSlot.range == sizeof(Abi::Fault) && firstSlot.offset != secondSlot.offset, "two report-only BdaResources did not take 32-byte slots of one fault chunk"); + Pipeline firstPipeline(context, MakeBdaFaultSequenceTestShader({Abi::FaultReason::Unmapped}, 0x7fff11110000ULL), {tableInfo, firstSlot, outputInfo}); + Pipeline secondPipeline(context, MakeBdaFaultSequenceTestShader({Abi::FaultReason::Unmapped}, 0x7fff22220000ULL), {tableInfo, secondSlot, outputInfo}); + Pipeline::RunTogether(context, {&firstPipeline, &secondPipeline}); + const auto reports = [](const BdaResources& resources, const char* address) { + try { + resources.CheckFault(); + } catch (const std::runtime_error& error) { + return std::string(error.what()).find(address) != std::string::npos; + } + return false; + }; + Require(reports(first, "address=0x7fff11110000") && reports(second, "address=0x7fff22220000"), "two dispatches of one batch faulting into neighbouring report-only fault slots did not each report their own address"); +} diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp index a00cc6e740..8b01cc19db 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaResources.cpp @@ -7,6 +7,7 @@ #include "prx/libc/include/GuestArena.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" +#include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX @@ -24,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -813,6 +815,69 @@ void failedJoinWrittenViewTests(const Context& context, const BdaTestAccess& acc ::operator delete(block, std::align_val_t{2 * piece}); } +void failedJoinDissolvedOverlayTests(const Context& context, const BdaTestAccess& access) { + constexpr std::size_t piece = 65536; + constexpr std::size_t large = 3 * piece; + constexpr std::uint32_t stored = 0x0b5e55ed; + auto* block = static_cast(::operator new(2 * large, std::align_val_t{2 * piece})); + std::memset(block, 0x5a, 2 * large); + const auto first = reinterpret_cast(block); + const auto second = first + large; + const auto written = second + 128; + auto importing = context; + importing.hostImportAlignment = piece; + ClearHostImports(importing.device); + SetHostImportWindow(2 * piece); + SetHostImportBudget(2 * large); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, large, true, false, true); + mutation.Add(block + large, large, true, false, true); + } + Require(HostImportFor(importing, first, 16) != nullptr && HostImportFor(importing, second, 16) != nullptr && HostImportLiveBytes() == 2 * large, "the failed join overlay fixture did not import its two ranges apart"); + access.limitImports(large); + { + GuestBufferMemory memory(importing); + memory.AddReadable(second - 16, 32); + memory.Upload(false); + Require(ExistingHostImportFor(importing, first, 16) == nullptr && ExistingHostImportFor(importing, second, 16) == nullptr, "the join over the import limit did not retire the imports it replaced"); + memory.WriteBack(); + } + SetHostImportBudget(2 * large); + Require(HostImportFor(importing, first, 16) != nullptr && HostImportFor(importing, second, 16) != nullptr, "the ranges of the failed join were not imported apart again"); + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Recorder recorder(importing); + recorder.Activate(); + GuestBufferMemory memory(importing); + memory.AllowDeviceStaging(); + memory.AcquireRegistered(); + memory.AddWritable(written, 32); + memory.AddWritable(second - 16, 32); + memory.Upload(true); + std::uint32_t adjustment = 0; + const auto view = memory.Descriptor(written, 32, adjustment); + std::memcpy(access.bytes(view.buffer).data() + view.offset + adjustment, &stored, sizeof(stored)); + std::uint32_t crossing = 0; + const auto spanning = memory.Descriptor(second - 16, 32, crossing); + Require(spanning.range >= 32, "the view across the two ranges of the failed join has no owner covering it"); + memory.WriteBack(); + } + std::uint32_t kept = 0; + std::memcpy(&kept, block + large + 128, sizeof(kept)); + Require(kept == stored, "a write through a view staged in a space that a crossing view dissolved was lost"); + access.limitImports(std::nullopt); + ClearHostImports(importing.device); + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + mutation.Remove(block + large); + } + SetHostImportBudget(0); + SetHostImportWindow(0); + ::operator delete(block, std::align_val_t{2 * piece}); +} + // One registered range of `cells` import windows of `window` bytes, with the window and the import budget set for the // test and reset after it. class WindowedRange { @@ -2360,6 +2425,111 @@ ShaderRecompiler::DescriptorBinding binding(Role role, std::uint32_t slot) { return {ShaderRecompiler::DescriptorKind::StorageBuffer, role, 0, slot, 1, {}, false}; } +namespace { + +std::span slotBytes(const BdaTestAccess& access, const VkDescriptorBufferInfo& descriptor) { + return access.bytes(descriptor.buffer).subspan(static_cast(descriptor.offset), static_cast(descriptor.range)); +} + +std::span descriptorBytes(const BdaTestAccess& access, std::uint32_t slot) { + return slotBytes(access, access.descriptor(slot)); +} + +void writeUnmappedFault(std::span bytes, std::uint64_t address) { + const ShaderRecompiler::BdaAbi::Fault report{ShaderRecompiler::BdaAbi::FaultState::Ready, ShaderRecompiler::BdaAbi::FaultReason::Unmapped, address, 4, 0, 0x40, 0}; + std::memcpy(bytes.data(), &report, sizeof(report)); +} + +bool sameFaultMemory(const VkDescriptorBufferInfo& left, const VkDescriptorBufferInfo& right) { + return left.buffer == right.buffer && left.offset < right.offset + right.range && right.offset < left.offset + left.range; +} + +void faultSlotTests(const Context& shared, const BdaTestAccess& access) { + const Context context = shared; + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + constexpr std::size_t cached = 256; + std::uint64_t serial = 1000; + std::vector> idle; + for (std::size_t index = 0; index < cached; ++index) { + idle.push_back(std::make_unique(context)); + idle.back()->NoteUse(++serial); + BdaResources::NoteFinished(context.device, serial); + } + const auto counts = BdaResources::FaultSlots(context.device); + char message[256]; + std::snprintf(message, sizeof(message), "%zu idle cached BdaResources hold %llu fault slots in %llu chunks (%llu KiB of host-visible memory)", cached, static_cast(counts.held), static_cast(counts.chunks), static_cast(counts.slots * ShaderRecompiler::BdaAbi::FaultBufferBytes / 1024)); + Require(counts.held < cached / 4 && counts.slots < cached / 4 && counts.reclaimed != 0, message); + auto& first = *idle[0]; + auto& second = *idle[1]; + Require(!first.HoldsFaultSlot() && !second.HoldsFaultSlot(), "the oldest idle BdaResources kept their fault slots"); + first.NoteUse(serial + 1); + second.NoteUse(serial + 1); + Require(first.TakeFaultSlot(context) && second.TakeFaultSlot(context), "a reused BdaResources did not take a fault slot again"); + for (std::size_t index = 0; index < 64; ++index) idle.push_back(std::make_unique(context)); + Require(first.HoldsFaultSlot() && second.HoldsFaultSlot(), "a BdaResources whose batch is in flight lost its fault slot"); + const auto firstSlot = first.Fault(); + const auto secondSlot = second.Fault(); + Require(!sameFaultMemory(firstSlot, secondSlot), "two BdaResources in one batch share fault memory"); + writeUnmappedFault(slotBytes(access, firstSlot), 0x7fff11110000ULL); + writeUnmappedFault(slotBytes(access, secondSlot), 0x7fff22220000ULL); + reject([&] { first.CheckFault(); }, "address=0x7fff11110000"); + reject([&] { second.CheckFault(); }, "address=0x7fff22220000"); + idle.clear(); + ShaderRecompiler::RecompileResult control; + control.bdaAbiVersion = ShaderRecompiler::BdaAbi::Version; + control.bindings = {binding(Role::FaultBuffer, 5)}; + const std::array stages{{{ShaderRecompiler::ShaderStage::TessellationControl, &control, 0}}}; + auto disabled = context; + disabled.bufferDeviceAddress = false; + ShaderResources resources(disabled, stages, ColorTarget{}, 0, 0, 0, {}); + const auto built = access.descriptor(5); + Require(built.range == ShaderRecompiler::BdaAbi::FaultBufferBytes, "a build whose program could not be read was bound less than a full fault buffer"); + BdaResources::NoteFinished(context.device, 1); + std::vector> pressure; + const auto reclaimedBefore = BdaResources::FaultSlots(context.device).reclaimed; + while (BdaResources::FaultSlots(context.device).reclaimed == reclaimedBefore && pressure.size() < cached) pressure.push_back(std::make_unique(context)); + Require(BdaResources::FaultSlots(context.device).reclaimed != reclaimedBefore, "an idle fault-only build kept its fault slot under pressure"); + Require(sameFaultMemory(pressure.back()->Fault(), built), "the reclaimed fault slot did not serve the build that needed one"); + resources.Bind(VK_NULL_HANDLE, VK_PIPELINE_BIND_POINT_GRAPHICS, VK_NULL_HANDLE); + const auto rebound = access.descriptor(5); + Require(!sameFaultMemory(rebound, built), "a reused build was bound to the fault slot it gave back"); + writeUnmappedFault(slotBytes(access, built), 0x7fff33330000ULL); + writeUnmappedFault(slotBytes(access, rebound), 0x7fff44440000ULL); + reject([&] { resources.CheckFaults(); }, "address=0x7fff44440000"); + reject([&] { pressure.back()->CheckFault(); }, "address=0x7fff33330000"); + std::vector> reports; + std::set reportChunks; + for (std::size_t index = 0; index < 64; ++index) { + reports.push_back(std::make_unique(context, true)); + reportChunks.insert(reports.back()->Fault().buffer); + } + std::snprintf(message, sizeof(message), "64 report-only BdaResources hold %zu fault chunks with %llu-byte slots", reportChunks.size(), static_cast(reports.front()->Fault().range)); + Require(reportChunks.size() == 1 && reports.front()->Fault().range == sizeof(ShaderRecompiler::BdaAbi::Fault), message); + Require(!sameFaultMemory(reports[0]->Fault(), reports[1]->Fault()), "two report-only BdaResources share fault memory"); + writeUnmappedFault(slotBytes(access, reports[0]->Fault()), 0x7fff55550000ULL); + writeUnmappedFault(slotBytes(access, reports[1]->Fault()), 0x7fff66660000ULL); + reject([&] { reports[0]->CheckFault(); }, "address=0x7fff55550000"); + reject([&] { reports[1]->CheckFault(); }, "address=0x7fff66660000"); + ShaderRecompiler::RecompileResult vertex; + ShaderRecompiler::RecompileResult fragment; + const std::array capabilities{spv::CapabilityShader, spv::CapabilityTessellation}; + ShaderRecompiler::SpirvTarget target{}; + target.vulkanVersion = VK_API_VERSION_1_1; + target.spirvVersion = 0x00010300u; + target.supportedCapabilities = capabilities; + target.tessellation = ShaderRecompiler::TessellationTargetLimits{32, 128, 128, 120, 4096, 128, 128}; + const auto rectList = ShaderRecompiler::BuildRectListShaders(vertex, fragment, target); + const std::array rectStages{{{ShaderRecompiler::ShaderStage::TessellationControl, &rectList.control, 0}}}; + ShaderResources rectResources(disabled, rectStages, ColorTarget{}, 0, 0, 0, {}); + const auto rectSlot = access.descriptor(rectList.control.bindings.front().binding); + std::snprintf(message, sizeof(message), "the rect-list check, which writes only the fault report, was bound %llu bytes of fault buffer", static_cast(rectSlot.range)); + Require(rectSlot.range == sizeof(ShaderRecompiler::BdaAbi::Fault), message); + writeUnmappedFault(slotBytes(access, rectSlot), 0x7fff77770000ULL); + reject([&] { rectResources.CheckFaults(); }, "address=0x7fff77770000"); +} + +} + 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; @@ -3585,6 +3755,67 @@ void RunSparseHeapMirrorBudgetTests(const Context& context, const BdaTestAccess& std::cout << "a sparse heap whose mirror would pass the heap mirror budget is copied\n"; } +void RunHeapMirrorReferenceSizeTests(const Context& context, const BdaTestAccess& access) { + constexpr std::size_t block = 65536; + constexpr std::size_t heapBytes = 4 * block; +#ifdef _WIN32 + if (!GuestArena::GuestArenaAvailable_nid_postfix() || !GuestArena::GuestArenaWriteWatched_nid_postfix()) { + std::cout << "guest arena unavailable or not write-watched: heap mirror reference sizes not tested\n"; + return; + } + auto* guest = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(heapBytes, block)); + Require(guest != nullptr, "cannot allocate the heap in the guest arena"); + GuestArena::GuestArenaCommit_nid_postfix(guest, heapBytes, PAGE_READWRITE, heapBytes); +#else + if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { + std::cout << "write watch unavailable: heap mirror reference sizes not tested\n"; + return; + } + void* mapping = mmap(nullptr, heapBytes + block, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(mapping != MAP_FAILED, "cannot map the heap"); + auto* guest = reinterpret_cast((reinterpret_cast(mapping) + block - 1) & ~std::uintptr_t{block - 1}); + GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(guest, heapBytes); +#endif + std::memset(guest, 0x11, heapBytes); + const auto address = reinterpret_cast(guest); + const auto registry = [&](bool add) { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + if (add) GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, guest, heapBytes, true, true, true); + else GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, guest); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + }; + registry(true); + const auto before = MirrorCounters(); + const auto store = [&](std::uint64_t written, std::size_t bytes, std::size_t at, std::byte value) { + GuestBufferMemory leased(context); + leased.AcquireRegistered(); + leased.AddWritable(written, bytes); + leased.Upload(true); + std::uint32_t adjustment = 0; + const auto view = leased.Descriptor(written, static_cast(bytes), adjustment); + access.bytes(view.buffer)[view.offset + adjustment + at] = value; + leased.WriteBack(); + }; + store(address + 4096, 8192, 8000, std::byte{0x7a}); + Require(MirrorCounters().heapMirrors == before.heapMirrors + 1, "the writable heap was not mirrored"); + Require(guest[4096 + 8000] == 0x7a, "the larger write-back lost the GPU's store"); + store(address + 16384, 2048, 2000, std::byte{0x7b}); + Require(guest[16384 + 2000] == 0x7b && guest[4096 + 8000] == 0x7a, "a smaller write-back after a larger one lost the GPU's store"); + store(address + 32768, 8192, 100, std::byte{0x7c}); + Require(guest[32768 + 100] == 0x7c && guest[16384 + 2000] == 0x7b, "a write-back after a smaller one lost the GPU's store"); + registry(false); + ClearImageMirrors(context.device); + ClearSnapshotCopies(context.device); +#ifdef _WIN32 + GuestArena::GuestArenaReset_nid_postfix(guest, heapBytes); + GuestArena::GuestArenaRelease_nid_postfix(guest, heapBytes); +#else + GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(guest, heapBytes); + munmap(mapping, heapBytes + block); +#endif + std::cout << "a writable heap mirror writes back a smaller range after a larger one\n"; +} + void RunHeapMirrorRunUnwatchTests(const Context& context, const BdaTestAccess& access) { constexpr std::size_t block = 65536; constexpr std::size_t heapBytes = 32 * block; @@ -4312,6 +4543,7 @@ void sparseHeapMirrorTests(const Context& context, const BdaTestAccess& access) } void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { + faultSlotTests(context, access); alignas(64) std::array guest{}; guest[0] = 123; const auto address = reinterpret_cast(guest.data()); @@ -4362,7 +4594,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { Require(!ShaderResources::NeverReusable(stages), "a fault-buffer build is refused the resource cache"); Require(!resources.Reusable(), "a fault-buffer build with copied writes is reusable"); Require(resources.NeedsCompletion() && !resources.UsesAddressTable(), "a fault-buffer build reaches guest memory through the BDA table"); - const auto fault = access.bytes(access.descriptor(5).buffer); + const auto fault = descriptorBytes(access, 5); for (const auto byte : fault) Require(byte == std::byte{}, "rect-list fault buffer was not initialized"); const ShaderRecompiler::BdaAbi::Fault report{ShaderRecompiler::BdaAbi::FaultState::Ready, ShaderRecompiler::BdaAbi::FaultReason::InvalidRectangle, 0, 0, 0, 0, 0}; std::memcpy(fault.data(), &report, sizeof(report)); @@ -4381,7 +4613,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { ShaderResources resources(disabled, stages, ColorTarget{}, 0, 0, 0, {}); Require(!ShaderResources::NeverReusable(stages) && resources.Reusable() && resources.ChecksFaults(), "a fault-only build is not reusable"); resources.CheckFaults(); - const auto fault = access.bytes(access.descriptor(5).buffer); + const auto fault = descriptorBytes(access, 5); const ShaderRecompiler::BdaAbi::Fault report{ShaderRecompiler::BdaAbi::FaultState::Ready, ShaderRecompiler::BdaAbi::FaultReason::InvalidRectangle, 0, 0, 0, 0, 0}; std::memcpy(fault.data(), &report, sizeof(report)); reject([&] { resources.CheckFaults(); }, "rect-list requires"); @@ -4401,7 +4633,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { Require(header.version == ShaderRecompiler::BdaAbi::Version && header.count == 1 && header.entryBytes == sizeof(range), "BDA header layout mismatch"); std::memcpy(&range, table.data() + sizeof(header), sizeof(range)); Require(range.begin == snapshots[0].address && range.end == range.begin + source.size(), "64-bit guest address was truncated"); - const auto fault = access.bytes(access.descriptor(5).buffer); + const auto fault = descriptorBytes(access, 5); for (const auto byte : fault) Require(byte == std::byte{}, "fault buffer was not initialized"); const ShaderRecompiler::BdaAbi::Fault denied{ShaderRecompiler::BdaAbi::FaultState::Ready, ShaderRecompiler::BdaAbi::FaultReason::Permission, snapshots[0].address + 4, 4, 0, 0x88, 0}; std::memcpy(fault.data(), &denied, sizeof(denied)); @@ -4417,11 +4649,11 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { changed = 999; std::memcpy(access.bytes(access.descriptor(6).buffer).data(), &changed, sizeof(changed)); const ShaderRecompiler::BdaAbi::Fault report{ShaderRecompiler::BdaAbi::FaultState::Ready, ShaderRecompiler::BdaAbi::FaultReason::Unmapped, 0x7fff99880000ULL, 4, 0, 0x44, 0}; - std::memcpy(access.bytes(access.descriptor(5).buffer).data(), &report, sizeof(report)); + std::memcpy(descriptorBytes(access, 5).data(), &report, sizeof(report)); reject([&] { resources.WriteBack(); }, "BDA access failed"); auto invalidRectangle = report; invalidRectangle.reason = ShaderRecompiler::BdaAbi::FaultReason::InvalidRectangle; - std::memcpy(access.bytes(access.descriptor(5).buffer).data(), &invalidRectangle, sizeof(invalidRectangle)); + std::memcpy(descriptorBytes(access, 5).data(), &invalidRectangle, sizeof(invalidRectangle)); reject([&] { resources.WriteBack(); }, "rect-list requires"); Require(guest[0] == 123, "failed GPU command published writes"); } @@ -4430,7 +4662,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { ShaderResources resources(context, compiled); std::memcpy(access.bytes(access.descriptor(6).buffer).data() + word * sizeof(std::uint32_t), &value, sizeof(value)); const ShaderRecompiler::BdaAbi::Fault report{ShaderRecompiler::BdaAbi::FaultState::Ready, reason, 0x7fff99880002ULL, 4, 0, 0x5c8, otherFaults}; - std::memcpy(access.bytes(access.descriptor(5).buffer).data(), &report, sizeof(report)); + std::memcpy(descriptorBytes(access, 5).data(), &report, sizeof(report)); try { resources.WriteBack(); } catch (const BdaUnalignedAccess& error) { @@ -4551,6 +4783,7 @@ void RunBdaResourceTests(const Context& context, const BdaTestAccess& access) { importCoverageHintBudgetTests(context, access); failedImportJoinProbeBudgetTests(context, access); failedJoinWrittenViewTests(context, access); + failedJoinDissolvedOverlayTests(context, access); importWholeRangeOverWindowTests(context); importMappedFailureGenerationTests(context); importStaleProbeTests(context, access); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp b/core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp index 9ca39a5b01..924122965c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaShader.cpp @@ -134,9 +134,9 @@ std::vector MakeBdaWriteTestShader(std::uint64_t address, std::ui return shader; } -std::vector MakeBdaFaultSequenceTestShader(std::initializer_list reasons) { +std::vector MakeBdaFaultSequenceTestShader(std::initializer_list reasons, std::uint64_t address) { using namespace ShaderRecompiler; - return MakeShader(0u, false, true, [&](SpirvValueEmitContext& ctx, const IrValue&, std::uint32_t base) { + return MakeShader(address, false, true, [&](SpirvValueEmitContext& ctx, const IrValue&, std::uint32_t base) { for (const auto reason : reasons) RecordBdaFault(ctx.state, base, ConstantU32(ctx.state, 4u), ConstantU32(ctx.state, 0x1234u), reason); return std::vector{ConstantU32(ctx.state, 1u)}; }); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaShader.hpp b/core/libs/prx/libSceAgcDriver/tests/BdaShader.hpp index e31020caf7..87c236c922 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaShader.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaShader.hpp @@ -10,7 +10,7 @@ std::vector MakeBdaTestShader(std::uint64_t address, std::uint32_t bits, std::int64_t offset = 0); std::vector MakeBdaDwordReadTestShader(std::uint64_t address, std::uint32_t dwords, bool coherent, bool stops); std::vector MakeBdaSpanReadTestShader(std::uint64_t address, std::uint32_t offset, std::uint32_t extracted, bool coherent, bool stops); -std::vector MakeBdaFaultSequenceTestShader(std::initializer_list reasons); +std::vector MakeBdaFaultSequenceTestShader(std::initializer_list reasons, std::uint64_t address = 0); std::vector MakeBdaDwordWriteTestShader(std::uint64_t address, std::uint32_t dwords, const std::uint32_t* values); std::vector MakeBdaStoreTestShader(std::uint64_t address, std::uint32_t value, std::uint32_t bits, bool coherent); std::vector MakeBdaWriteTestShader(std::uint64_t address, std::uint32_t value, std::uint32_t bits, bool coherent); @@ -32,6 +32,7 @@ struct BdaAnchoredReadProgram { inline constexpr std::uint32_t BdaAnchoredReadSentinel = 0xdeadbeefu; std::vector MakeBdaAnchoredReadTestShader(const BdaAnchoredReadProgram& reads, bool anchored); void RunBdaStoreExecutionTests(const AgcDriver::Graphics::Context& context); +void RunBdaFaultSlotExecutionTests(const AgcDriver::Graphics::Context& context); void RunBdaExecutionTests(const AgcDriver::Graphics::Context& context); void RunBdaContractTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp index 08ac6006e9..1e99411ad3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp +++ b/core/libs/prx/libSceAgcDriver/tests/BdaTests.hpp @@ -22,12 +22,14 @@ void RunSparseHeapMirrorUnwatchTests(const AgcDriver::Graphics::Context& context 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, int frames); +void RunHeapMirrorReferenceSizeTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access); void RunAddressCopyCapMirrorTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access, int frames); void RunUnwatchedMirrorImportTests(const AgcDriver::Graphics::Context& context, const BdaTestAccess& access, int frames); void RunGuestAllocationTests(); void RunGuestLeaseWaitTests(); void RunUnmappedGapTests(); void RunVanishedPageReadTests(); +void RunPageBlockTests(); void RunColorTargetLayoutTests(); void RunLiveStackAccessTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp b/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp index cfcaed20ae..030ef14c70 100644 --- a/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/BufferPool.cpp @@ -54,6 +54,7 @@ struct MockDevice { std::map memoryTypes; std::map> bound; std::set liveBuffers; + std::map imageBytes; std::deque results; VkDeviceSize limit = 0; VkResult pressureResult = VK_ERROR_OUT_OF_DEVICE_MEMORY; @@ -63,6 +64,7 @@ struct MockDevice { unsigned binds = 0; unsigned maps = 0; unsigned unmaps = 0; + std::uint32_t typeBits = 3; }; MockDevice mock; @@ -76,7 +78,7 @@ VKAPI_ATTR VkResult VKAPI_CALL mockCreateBuffer(VkDevice, const VkBufferCreateIn } VKAPI_ATTR void VKAPI_CALL mockGetBufferMemoryRequirements(VkDevice, VkBuffer buffer, VkMemoryRequirements* requirements) { - *requirements = {mock.sizes.at(buffer), 256, 3}; + *requirements = {mock.sizes.at(buffer), 256, mock.typeBits}; } VKAPI_ATTR VkResult VKAPI_CALL mockAllocateMemory(VkDevice, const VkMemoryAllocateInfo* info, const VkAllocationCallbacks*, VkDeviceMemory* memory) { @@ -139,6 +141,41 @@ VKAPI_ATTR VkDeviceAddress VKAPI_CALL mockGetBufferDeviceAddress(VkDevice, const return 0x100000000000ULL + reinterpret_cast(info->buffer) * 0x10000; } +VKAPI_ATTR VkResult VKAPI_CALL mockCreateImage(VkDevice, const VkImageCreateInfo* info, const VkAllocationCallbacks*, VkImage* image) { + *image = reinterpret_cast(static_cast(mock.next++)); + mock.imageBytes[*image] = VkDeviceSize{info->extent.width} * info->extent.height * 4; + return VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL mockGetImageMemoryRequirements(VkDevice, VkImage image, VkMemoryRequirements* requirements) { + *requirements = {mock.imageBytes.at(image), 4096, 1}; +} + +VKAPI_ATTR VkResult VKAPI_CALL mockBindImageMemory(VkDevice, VkImage image, VkDeviceMemory memory, VkDeviceSize offset) { + return offset + mock.imageBytes.at(image) <= mock.memoryBytes.at(memory) ? VK_SUCCESS : VK_ERROR_OUT_OF_DEVICE_MEMORY; +} + +VKAPI_ATTR VkResult VKAPI_CALL mockCreateImageView(VkDevice, const VkImageViewCreateInfo*, const VkAllocationCallbacks*, VkImageView* view) { + *view = reinterpret_cast(static_cast(mock.next++)); + return VK_SUCCESS; +} + +VKAPI_ATTR void VKAPI_CALL mockDestroyImageView(VkDevice, VkImageView, const VkAllocationCallbacks*) {} + +VKAPI_ATTR void VKAPI_CALL mockDestroyImage(VkDevice, VkImage image, const VkAllocationCallbacks*) { + Expect(mock.imageBytes.erase(image) == 1, "an unknown image was destroyed"); +} + +VKAPI_ATTR void VKAPI_CALL mockFormatProperties(VkPhysicalDevice, VkFormat, VkFormatProperties* properties) { + *properties = {}; + properties->optimalTilingFeatures = VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT | VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT | VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT; +} + +VKAPI_ATTR VkResult VKAPI_CALL mockImageFormatProperties(VkPhysicalDevice, VkFormat, VkImageType, VkImageTiling, VkImageUsageFlags, VkImageCreateFlags, VkImageFormatProperties* properties) { + *properties = {{16384, 16384, 1}, 1, 1, VK_SAMPLE_COUNT_1_BIT, VkDeviceSize{1} << 32u}; + return VK_SUCCESS; +} + PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) { static const std::map table{ {"vkCreateBuffer", reinterpret_cast(mockCreateBuffer)}, @@ -150,6 +187,12 @@ PFN_vkVoidFunction VKAPI_CALL mockProc(VkDevice, const char* name) { {"vkDestroyBuffer", reinterpret_cast(mockDestroyBuffer)}, {"vkFreeMemory", reinterpret_cast(mockFreeMemory)}, {"vkGetBufferDeviceAddressKHR", reinterpret_cast(mockGetBufferDeviceAddress)}, + {"vkCreateImage", reinterpret_cast(mockCreateImage)}, + {"vkGetImageMemoryRequirements", reinterpret_cast(mockGetImageMemoryRequirements)}, + {"vkBindImageMemory", reinterpret_cast(mockBindImageMemory)}, + {"vkCreateImageView", reinterpret_cast(mockCreateImageView)}, + {"vkDestroyImageView", reinterpret_cast(mockDestroyImageView)}, + {"vkDestroyImage", reinterpret_cast(mockDestroyImage)}, }; const auto it = table.find(name); return it == table.end() ? nullptr : it->second; @@ -176,11 +219,15 @@ Context mockContext() { VkDeviceSize reportedBudget = 0; VkDeviceSize reportedUsage = 0; +VkDeviceSize reportedSecondBudget = 0; +VkDeviceSize reportedSecondUsage = 0; VKAPI_ATTR void VKAPI_CALL mockMemoryProperties2(VkPhysicalDevice, VkPhysicalDeviceMemoryProperties2* properties) { auto* reported = static_cast(properties->pNext); reported->heapBudget[0] = reportedBudget; reported->heapUsage[0] = reportedUsage; + reported->heapBudget[2] = reportedSecondBudget; + reported->heapUsage[2] = reportedSecondUsage; } std::uint64_t diagnosticBudgetQueries = 0; @@ -257,6 +304,58 @@ void RefusedDevicePlacement() { Expect(mock.liveBuffers.empty(), "a failed device buffer left its VkBuffer alive"); } +void StagingHeadroom() { + mock = MockDevice{}; + { + auto context = budgetContext(); + constexpr auto usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + reportedBudget = 2048 * MiB; + reportedUsage = 1000 * MiB; + VkDeviceMemory retained = VK_NULL_HANDLE; + { + const auto shadow = Buffer::WithinDeviceLocalHeadroom(context, 24 * MiB, usage); + Expect(shadow != nullptr && mock.memoryTypes.at(MemoryOf(shadow->Handle())) == 0, "a staging buffer with room in its heap's budget was not made device-local"); + if (shadow) retained = MemoryOf(shadow->Handle()); + } + Expect(mock.memoryBytes.contains(retained), "a released staging buffer was not retained by the pool"); + reportedUsage = 1900 * MiB; + const auto made = mock.allocations; + const auto buffers = mock.liveBuffers.size(); + { + const auto reused = Buffer::WithinDeviceLocalHeadroom(context, 24 * MiB, usage); + Expect(reused != nullptr && MemoryOf(reused->Handle()) == retained && mock.allocations == made, "a retained staging buffer was refused within the last eighth of the budget, although reusing it allocates nothing"); + } + const auto refused = Buffer::WithinDeviceLocalHeadroom(context, 40 * MiB, usage); + Expect(refused == nullptr && mock.allocations == made, "a staging buffer that needs a new allocation within the last eighth of its heap's budget was not refused"); + Expect(mock.liveBuffers.size() == buffers, "a refused staging buffer left its VkBuffer alive"); + } + mock = MockDevice{}; + { + auto context = budgetContext(); + context.memory.memoryHeapCount = 3; + context.memory.memoryHeaps[2] = {2048 * MiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}; + context.memory.memoryTypeCount = 3; + context.memory.memoryTypes[2] = {VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 2}; + constexpr auto usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + reportedBudget = 2048 * MiB; + reportedSecondBudget = 2048 * MiB; + mock.typeBits = 6; + const auto make = [&](const char* what, VkDeviceSize firstUsage, VkDeviceSize secondUsage, std::size_t bytes, bool expected) { + reportedUsage = firstUsage; + reportedSecondUsage = secondUsage; + const auto made = mock.allocations; + const auto shadow = Buffer::WithinDeviceLocalHeadroom(context, bytes, usage); + Expect((shadow != nullptr) == expected, std::string(what) + (expected ? ": refused" : ": made")); + if (shadow) Expect(mock.memoryTypes.at(MemoryOf(shadow->Handle())) == 2, std::string(what) + ": not allocated from the second device-local type"); + else Expect(mock.allocations == made, std::string(what) + ": allocated although refused"); + }; + make("the first device-local heap is full, the buffer's mask allows only the second", 1900 * MiB, 1000 * MiB, 24 * MiB + 4096, true); + make("the second device-local heap is full, the buffer's mask allows only the second", 1000 * MiB, 1900 * MiB, 40 * MiB, false); + } + reportedSecondBudget = 0; + reportedSecondUsage = 0; +} + void AttachmentReclaim() { mock = MockDevice{}; auto context = budgetContext(); @@ -1074,6 +1173,25 @@ void ImageMemoryReclaim() { reclaimImageContext = nullptr; } +void RenderTargetMemoryReuse() { + mock = MockDevice{}; + auto context = mockContext(); + context.formatProperties = mockFormatProperties; + context.imageFormatProperties = mockImageFormatProperties; + context.limits.maxFramebufferWidth = 16384; + context.limits.maxFramebufferHeight = 16384; + GetBufferPool(context); + ColorTarget color{}; + color.extent = {3072, 2048}; + color.format = VK_FORMAT_R8G8B8A8_UNORM; + color.bytes = 3072 * 2048 * 4; + { RenderTarget first(context, color, true); } + Expect(mock.frees == 0 && context.bufferPool->RetainedImages() == 1, "a released render target freed its image memory instead of returning it to the image pool"); + { RenderTarget second(context, color, true); } + Expect(mock.allocations == 1, "a second render target of the same size allocated new image memory instead of the pooled one"); + Expect(mock.imageBytes.empty(), "a render target leaked its image"); +} + void ImageMemoryOverBudget() { mock = MockDevice{}; auto context = budgetContext(); @@ -1413,9 +1531,11 @@ int main(int argc, char** argv) { ImageCacheReclaim(); ImageMemoryReuse(); ImageMemoryReclaim(); + RenderTargetMemoryReuse(); ImageMemoryOverBudget(); HostPlacement(); RefusedDevicePlacement(); + StagingHeadroom(); AttachmentReclaim(); } catch (const std::exception& error) { std::fprintf(stderr, "FAIL: %s\n", error.what()); diff --git a/core/libs/prx/libSceAgcDriver/tests/CaptureTrace.cpp b/core/libs/prx/libSceAgcDriver/tests/CaptureTrace.cpp index b263f9e7b1..9d74b2a5a8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/CaptureTrace.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/CaptureTrace.cpp @@ -4,15 +4,30 @@ #include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" +#include #include +#include #include #include +#include #include #include #include +#include #include +#include +#include #include #include +#include + +namespace AgcDriver { + +struct CaptureTraceTestAccess { + static void QueueDrop(bool shutdownOnly, bool cumulative = false); +}; + +} namespace { @@ -127,6 +142,82 @@ void BestEffortOverflow() { std::remove(configured); } +void FormatOverflow() { + const char* configured = RequiredPathVariable(); + const std::string oversized(4096, 'x'); + AgcDriver::CaptureTrace::Log("capture-trace-format payload=%s", oversized.c_str()); + Check(WaitForRecord(configured, "capture-trace-dropped records=1\n"), + "an oversized record must be counted instead of throwing or writing a partial record"); + AgcDriver::CaptureTrace::Log("%s", "capture-trace-format-recovered"); + Check(WaitForRecord(configured, "capture-trace-format-recovered\n"), + "capture tracing did not recover after an oversized record"); + Check(ReadFile(configured).find("capture-trace-format payload=") == std::string::npos, + "the trace contains a truncated oversized record"); + std::remove(configured); +} + +class PausedTraceBuffer final : public std::streambuf { +public: + bool WaitForWrite() { + std::unique_lock lock(mutex); + return changed.wait_for(lock, std::chrono::seconds(2), [this] { return entered; }); + } + + void Release() { + { + std::lock_guard lock(mutex); + released = true; + } + changed.notify_all(); + } + + void Pause() { + std::lock_guard lock(mutex); + entered = false; + released = false; + } + + std::string Contents() { + std::lock_guard lock(mutex); + return contents; + } + + bool WaitForRecord(const std::string& record) { + std::unique_lock lock(mutex); + return changed.wait_for(lock, std::chrono::seconds(2), [this, &record] { + return contents.find(record) != std::string::npos; + }); + } + +protected: + std::streamsize xsputn(const char* text, std::streamsize size) override { + std::unique_lock lock(mutex); + entered = true; + changed.notify_all(); + changed.wait(lock, [this] { return released; }); + contents.append(text, static_cast(size)); + changed.notify_all(); + return size; + } + + int sync() override { + return 0; + } + +private: + std::mutex mutex; + std::condition_variable changed; + std::string contents; + bool entered = false; + bool released = false; +}; + +std::string QueueRecord(std::size_t producer, std::size_t item) { + char prefix[64]; + std::snprintf(prefix, sizeof(prefix), "queue producer=%02zu item=%08zu payload=", producer, item); + return std::string(prefix) + std::string(1900, 'x'); +} + void Disabled() { RemoveEnvironmentVariable("APS5_CAPTURE_TRACE"); Check(!AgcDriver::CaptureTrace::Enabled(), @@ -138,6 +229,163 @@ void Disabled() { } +void AgcDriver::CaptureTraceTestAccess::QueueDrop(bool shutdownOnly, bool cumulative) { + constexpr std::size_t Capacity = 16u * 1024u * 1024u; + constexpr std::size_t Producers = 4; + const char* configured = RequiredPathVariable(); + PausedTraceBuffer sink; + std::exception_ptr failure; + std::size_t initialItems = 0; + std::atomic bounded = true; + const std::size_t rounds = cumulative ? 2 : 1; + std::size_t lostTotal = 0; + std::vector expectedMarkers; + std::vector queuedByRound; + for (std::size_t round = 0; round < rounds; ++round) { + const auto lost = Producers * (round == 0 ? 64u : 17u) + (shutdownOnly ? 0u : 1u); + lostTotal += lost; + expectedMarkers.push_back("capture-trace-dropped records=" + std::to_string(lost)); + } + const auto summary = "capture-trace-summary dropped=" + std::to_string(lostTotal); + { + CaptureTrace trace; + Check(trace.output.is_open(), "the queue test trace path must be writable"); + auto& output = static_cast(trace.output); + output.rdbuf(&sink); + auto append = [&](std::size_t producer, std::size_t item) { + trace.append(QueueRecord(producer, item)); + std::lock_guard lock(trace.mutex); + if (trace.pending.size() > Capacity) bounded.store(false); + }; + for (std::size_t round = 0; round < rounds && !failure; ++round) { + if (round != 0) sink.Pause(); + const std::size_t itemsPerProducer = round == 0 ? 64 : 17; + std::vector producers; + std::vector producerErrors(Producers); + std::mutex stateMutex; + std::condition_variable stateChanged; + std::size_t attempted = 0; + std::size_t finished = 0; + try { + for (std::size_t item = 0; item < 64; ++item) append(0, initialItems++); + Check(sink.WaitForWrite(), "the trace worker did not reach the paused sink"); + for (;;) { + const auto line = QueueRecord(0, initialItems); + { + std::lock_guard lock(trace.mutex); + if (trace.pending.size() + line.size() + 64 > Capacity) break; + } + append(0, initialItems++); + } + queuedByRound.push_back(initialItems); + for (std::size_t producer = 0; producer < Producers; ++producer) { + producers.emplace_back([&, producer] { + { + std::lock_guard lock(stateMutex); + ++attempted; + } + stateChanged.notify_all(); + try { + for (std::size_t item = 0; item < itemsPerProducer; ++item) append(producer + 1, item); + } catch (...) { + producerErrors[producer] = std::current_exception(); + } + { + std::lock_guard lock(stateMutex); + ++finished; + } + stateChanged.notify_all(); + }); + } + { + std::unique_lock lock(stateMutex); + Check(stateChanged.wait_for(lock, std::chrono::seconds(2), [&] { return attempted == Producers; }), + "the queue producers did not start"); + Check(stateChanged.wait_for(lock, std::chrono::seconds(2), [&] { return finished == Producers; }), + "a full capture queue must return from every caller while its writer is paused"); + } + if (!shutdownOnly) trace.append("queue-short-during-loss"); + } catch (...) { + failure = std::current_exception(); + } + sink.Release(); + for (auto& producer : producers) producer.join(); + if (!failure) { + for (const auto& error : producerErrors) { + if (error) { + failure = error; + break; + } + } + } + if (!failure && !shutdownOnly) { + try { + Check(sink.WaitForRecord(expectedMarkers[round] + "\n"), + "the recovered writer did not report the exact dropped-record count"); + append(Producers + 1, round); + Check(sink.WaitForRecord(QueueRecord(Producers + 1, round) + "\n"), + "the recovered writer did not accept a new record"); + } catch (...) { + failure = std::current_exception(); + } + } + } + } + if (failure) std::rethrow_exception(failure); + Check(bounded.load(), "the pending capture queue exceeded 16 MiB"); + std::istringstream records(sink.Contents()); + std::string record; + std::size_t sequence = 0; + std::size_t queued = 0; + std::size_t recovered = 0; + std::size_t markers = 0; + std::size_t summaries = 0; + while (std::getline(records, record)) { + std::istringstream fields(record); + unsigned long long foundSequence = 0; + std::string elapsed; + std::string label; + std::string producerField; + std::string itemField; + fields >> foundSequence >> elapsed >> label >> producerField >> itemField; + Check(foundSequence == ++sequence, "the recovered trace lost or reordered sequence numbers"); + Check(elapsed.ends_with("us"), "the recovered trace lost its elapsed-time field"); + Check(summaries == 0, "the trace contains a record after its shutdown summary"); + const auto prefix = std::to_string(foundSequence) + " " + elapsed + " "; + const auto body = record.substr(prefix.size()); + if (body.starts_with("capture-trace-dropped records=")) { + Check(markers < rounds && body == expectedMarkers[markers], + "the trace did not report each dropped-record batch exactly once"); + Check(queued == queuedByRound[markers] && recovered == markers, + "the dropped marker did not follow the queued records and precede recovery"); + ++markers; + continue; + } + if (body == summary) { + Check(markers == rounds, "the shutdown total appeared before the loss markers"); + ++summaries; + continue; + } + Check(label == "queue" && producerField.starts_with("producer=") && itemField.starts_with("item="), + "the recovered trace contains an incomplete record"); + const auto producer = std::stoull(producerField.substr(9)); + const auto item = std::stoull(itemField.substr(5)); + if (producer == 0) { + Check(markers == recovered && item == queued++, "the trace lost or reordered an accepted queued record"); + } else { + Check(!shutdownOnly && producer == Producers + 1 && item == recovered && markers == recovered + 1, + "the trace retained a dropped record or lost recovery ordering"); + ++recovered; + } + Check(record == prefix + QueueRecord(producer, item), "the recovered trace changed a record's payload"); + } + Check(queued == initialItems, "the recovered trace lost the initial accepted records"); + Check(recovered == (shutdownOnly ? 0u : rounds), "the trace lost or duplicated its recovery records"); + Check(markers == rounds && summaries == 1, "the trace did not report each loss marker and one shutdown total"); + Check(sequence == initialItems + recovered + rounds + 1, "the trace contains an unexpected number of records"); + std::remove(configured); +} + int main(int argc, char** argv) { try { if (argc != 2) throw std::runtime_error("expected exactly one test mode argument"); @@ -149,6 +397,10 @@ int main(int argc, char** argv) { else if (mode == "default-writes") DefaultWrites(); else if (mode == "open-failure") OpenFailure(); else if (mode == "best-effort-overflow") BestEffortOverflow(); + else if (mode == "format-overflow") FormatOverflow(); + else if (mode == "queue-drop") AgcDriver::CaptureTraceTestAccess::QueueDrop(false); + else if (mode == "queue-drop-shutdown") AgcDriver::CaptureTraceTestAccess::QueueDrop(true); + else if (mode == "queue-drop-cumulative") AgcDriver::CaptureTraceTestAccess::QueueDrop(false, true); else if (mode == "disabled") Disabled(); else throw std::runtime_error("unknown capture trace test mode: " + mode); std::cout << "capture trace tests passed\n"; diff --git a/core/libs/prx/libSceAgcDriver/tests/CaptureTraceFailure.cmake b/core/libs/prx/libSceAgcDriver/tests/CaptureTraceFailure.cmake new file mode 100644 index 0000000000..7cca64b3c3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/CaptureTraceFailure.cmake @@ -0,0 +1,45 @@ +if(NOT CMAKE_ARGC EQUAL 6) + message(FATAL_ERROR "Expected fixture, mode and output root arguments") +endif() +set(FIXTURE "${CMAKE_ARGV3}") +set(MODE "${CMAKE_ARGV4}") +set(OUTPUT_ROOT "${CMAKE_ARGV5}") +foreach(required IN ITEMS FIXTURE MODE OUTPUT_ROOT) + if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "") + message(FATAL_ERROR "Missing ${required}") + endif() +endforeach() +if(NOT MODE STREQUAL "write" AND NOT MODE STREQUAL "flush") + message(FATAL_ERROR "Expected write or flush mode, got ${MODE}") +endif() +string(RANDOM LENGTH 16 ALPHABET 0123456789abcdef runId) +set(runDirectory "${OUTPUT_ROOT}/capture-trace-${MODE}-${runId}") +file(MAKE_DIRECTORY "${runDirectory}") +set(ENV{APS5_CAPTURE_TRACE} "1") +set(ENV{APS5_CAPTURE_TRACE_PATH} "${runDirectory}/trace.log") +execute_process( + COMMAND "${FIXTURE}" "${MODE}" + WORKING_DIRECTORY "${runDirectory}" + RESULT_VARIABLE result OUTPUT_VARIABLE output ERROR_VARIABLE error TIMEOUT 8 +) +file(WRITE "${runDirectory}/failure.log" "result=${result}\n${output}\n${error}") +set(armed "capture-trace-failure armed mode=${MODE} queueFull=1 attempted=4 finished=4") +if(MODE STREQUAL "write") + set(sinkFailure "capture-trace-failure sink-short-write") +else() + set(sinkFailure "capture-trace-failure sink-flush-failed") +endif() +set(survived "capture-trace-failure survived mode=${MODE} queueFull=1 attempted=4 finished=4 inactive=1 empty=1 sinkFailed=1") +string(FIND "${error}" "${armed}" armedAt) +string(FIND "${error}" "${sinkFailure}" sinkAt) +string(FIND "${error}" "Capture trace failed:" failedAt) +string(FIND "${error}" "${survived}" survivedAt) +string(REGEX MATCHALL "Capture trace failed:" failureMessages "${error}") +list(LENGTH failureMessages failureCount) +if(NOT result STREQUAL "0" + OR error MATCHES "setup-failed|watchdog|unexpected-terminate" + OR NOT failureCount EQUAL 1 + OR armedAt LESS 0 OR NOT sinkAt GREATER armedAt OR NOT failedAt GREATER sinkAt OR NOT survivedAt GREATER failedAt) + message(FATAL_ERROR "Capture trace ${MODE} failure did not stop only tracing and exit normally: ${result}\n${output}\n${error}") +endif() +message(STATUS "Capture trace ${MODE} failure stopped tracing, released all callers before the sink failed and exited normally") diff --git a/core/libs/prx/libSceAgcDriver/tests/CaptureTraceFailure.cpp b/core/libs/prx/libSceAgcDriver/tests/CaptureTraceFailure.cpp new file mode 100644 index 0000000000..2bfef87ac8 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/CaptureTraceFailure.cpp @@ -0,0 +1,223 @@ +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +struct CaptureTraceTestAccess { + static void WriterFailure(bool flush); +}; + +} + +namespace { + +using namespace std::chrono_literals; + +[[noreturn]] void SetupFailure(const char* message) { + std::fprintf(stderr, "capture-trace-failure setup-failed: %s\n", message); + std::fflush(stderr); + std::_Exit(87); +} + +void Check(bool condition, const char* message) { + if (!condition) SetupFailure(message); +} + +class FailingBuffer final : public std::streambuf { +public: + explicit FailingBuffer(bool failFlush) : failFlush(failFlush) {} + + bool WaitForWriter() { + std::unique_lock lock(mutex); + return changed.wait_for(lock, 2s, [this] { return entered; }); + } + + void Fail() { + { + std::lock_guard lock(mutex); + released = true; + } + changed.notify_all(); + } + + unsigned WriteCalls() const { + return writeCalls.load(); + } + + unsigned FlushCalls() const { + return flushCalls.load(); + } + + bool Failed() const { + return failed.load(); + } + +protected: + std::streamsize xsputn(const char*, std::streamsize size) override { + writeCalls.fetch_add(1); + if (failFlush) return size; + wait(); + failed.store(true); + std::fputs("capture-trace-failure sink-short-write\n", stderr); + std::fflush(stderr); + return 0; + } + + int sync() override { + flushCalls.fetch_add(1); + if (!failFlush) return 0; + wait(); + failed.store(true); + std::fputs("capture-trace-failure sink-flush-failed\n", stderr); + std::fflush(stderr); + return -1; + } + +private: + void wait() { + std::unique_lock lock(mutex); + entered = true; + changed.notify_all(); + changed.wait(lock, [this] { return released; }); + } + + std::mutex mutex; + std::condition_variable changed; + const bool failFlush; + std::atomic writeCalls{0}; + std::atomic flushCalls{0}; + std::atomic failed{false}; + bool entered = false; + bool released = false; +}; + +std::string Record(unsigned producer, unsigned item) { + char prefix[64]; + std::snprintf(prefix, sizeof(prefix), "failure producer=%02u item=%08u payload=", producer, item); + return std::string(prefix) + std::string(1900, 'x'); +} + +} + +void AgcDriver::CaptureTraceTestAccess::WriterFailure(bool flush) { + constexpr std::size_t Capacity = 16u * 1024u * 1024u; + FailingBuffer sink(flush); + { + CaptureTrace trace; + Check(trace.output.is_open(), "the trace output did not open"); + auto& output = static_cast(trace.output); + auto* original = output.rdbuf(&sink); + unsigned item = 0; + for (; item < 64; ++item) trace.append(Record(0, item)); + Check(sink.WaitForWriter(), "the writer did not reach the failing sink"); + const auto line = Record(1, 0); + for (;;) { + { + std::lock_guard lock(trace.mutex); + if (trace.pending.size() + line.size() + 64 > Capacity) break; + } + trace.append(Record(0, item++)); + } + std::size_t queuedBytes = 0; + { + std::lock_guard lock(trace.mutex); + Check(trace.pending.size() <= Capacity, "the queue exceeded its bound"); + Check(trace.droppedTotal == 0, "filling the queue unexpectedly dropped a record"); + queuedBytes = trace.pending.size(); + } + std::atomic attempted{0}; + std::mutex stateMutex; + std::condition_variable stateChanged; + unsigned finished = 0; + std::vector producers; + for (unsigned producer = 1; producer <= 4; ++producer) { + producers.emplace_back([&, producer] { + attempted.fetch_add(1); + try { + trace.append(Record(producer, 0)); + } catch (...) { + SetupFailure("a producer threw before the writer failed"); + } + { + std::lock_guard lock(stateMutex); + ++finished; + } + stateChanged.notify_all(); + }); + } + { + std::unique_lock lock(stateMutex); + Check(stateChanged.wait_for(lock, 2s, [&] { return finished == 4; }), + "full-queue producers did not return while the sink was paused"); + } + for (auto& producer : producers) producer.join(); + { + std::lock_guard lock(trace.mutex); + Check(trace.pending.size() == queuedBytes, "a full-queue producer added data to the pending queue"); + Check(trace.droppedTotal == 4, "the four rejected records were not counted exactly once"); + } + Check(attempted.load() == 4, "the four producers did not run"); + Check(!sink.Failed(), "the sink failed before the full-queue producers returned"); + std::fprintf(stderr, "capture-trace-failure armed mode=%s queueFull=1 attempted=4 finished=4\n", flush ? "flush" : "write"); + std::fflush(stderr); + sink.Fail(); + trace.worker.join(); + Check(sink.Failed(), "the writer did not reach the requested stream failure"); + { + std::lock_guard lock(trace.mutex); + Check(!trace.active, "tracing remained active after the stream failure"); + Check(trace.pending.empty(), "failed tracing retained queued records"); + } + const auto writes = sink.WriteCalls(); + const auto flushes = sink.FlushCalls(); + Check(writes == 1, "the failed sink received another write"); + Check(flushes == (flush ? 1u : 0u), "the failed sink received an unexpected flush"); + for (unsigned producer = 1; producer <= 4; ++producer) trace.append(Record(producer, 1)); + { + std::lock_guard lock(trace.mutex); + Check(!trace.active && trace.pending.empty(), "logging after failure reactivated or queued tracing"); + Check(trace.droppedTotal == 4, "logging after failure changed the queue-loss count"); + } + Check(sink.WriteCalls() == writes && sink.FlushCalls() == flushes, + "logging after failure attempted more stream I/O"); + output.exceptions(std::ios::goodbit); + output.clear(); + output.rdbuf(original); + } + std::fprintf(stderr, "capture-trace-failure survived mode=%s queueFull=1 attempted=4 finished=4 inactive=1 empty=1 sinkFailed=1\n", + flush ? "flush" : "write"); + std::fflush(stderr); +} + +int main(int argc, char** argv) { + std::set_terminate([] { + std::fputs("capture-trace-failure unexpected-terminate\n", stderr); + std::fflush(stderr); + std::_Exit(86); + }); + std::thread([] { + std::this_thread::sleep_for(6s); + std::fputs("capture-trace-failure watchdog\n", stderr); + std::fflush(stderr); + std::_Exit(99); + }).detach(); + Check(argc == 2, "expected write or flush mode"); + const std::string mode = argv[1]; + Check(mode == "write" || mode == "flush", "unknown failure mode"); + Check(AgcDriver::CaptureTrace::Enabled(), "capture tracing must be enabled"); + const char* path = std::getenv("APS5_CAPTURE_TRACE_PATH"); + Check(path != nullptr && path[0] != '\0', "the test requires an explicit trace path"); + AgcDriver::CaptureTraceTestAccess::WriterFailure(mode == "flush"); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/ConstantPhi.cpp b/core/libs/prx/libSceAgcDriver/tests/ConstantPhi.cpp index 09ef9b6dac..d576b5ffc0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ConstantPhi.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ConstantPhi.cpp @@ -174,7 +174,7 @@ void CheckLongAncillaryForwarding() { IrValue* forwarded = &ancillary; for (std::uint32_t link = 0; link < 20000u; ++link) forwarded = &builder.Emit(IrOpcode::SelectU32, IrType::U32, {&condition, &zero, forwarded}); auto& field = builder.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {forwarded, &builder.Constant(8u), &builder.Constant(4u)}); - builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&field}); + static_cast(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&field})); struct Run { IrProgram& program; std::string error; diff --git a/core/libs/prx/libSceAgcDriver/tests/DenormalFlushElimination.cpp b/core/libs/prx/libSceAgcDriver/tests/DenormalFlushElimination.cpp index 5dd5cfc4ba..9db5f8bcd4 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DenormalFlushElimination.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DenormalFlushElimination.cpp @@ -260,6 +260,33 @@ int main() { return b.eliminate() == 0u && flushed.HasUses(); }); + passed &= run("a rounded to integral value, a square root or a reciprocal square root needs no flush", [] { + Builder b; + auto& unknown = b.emit(IrOpcode::BitCastF32U32, IrType::F32, {&b.unknown()}); + std::uint32_t kept = 0u; + for (const auto opcode : {IrOpcode::FPFloor32, IrOpcode::FPCeil32, IrOpcode::FPTrunc32, IrOpcode::FPRoundEven32, IrOpcode::FPSqrt, IrOpcode::FPRecipSqrt32}) { + b.keep(b.flush(b.emit(IrOpcode::BitCastU32F32, IrType::U32, {&b.emit(opcode, IrType::F32, {&unknown})}))); + ++kept; + } + auto& fraction = b.flush(b.emit(IrOpcode::BitCastU32F32, IrType::U32, {&b.emit(IrOpcode::FPFract32, IrType::F32, {&unknown})})); + auto& reciprocal = b.flush(b.emit(IrOpcode::BitCastU32F32, IrType::U32, {&b.emit(IrOpcode::FPRecip32, IrType::F32, {&unknown})})); + b.keep(fraction); + b.keep(reciprocal); + return b.eliminate() == kept && fraction.HasUses() && reciprocal.HasUses(); + }); + + passed &= run("a sign bit alone or a value with an exponent bit set needs no flush, other masks do", [] { + Builder b; + auto& sign = b.flush(b.emit(IrOpcode::BitwiseAnd32, IrType::U32, {&b.unknown(), &b.constant(0x80000000u)})); + auto& infinity = b.flush(b.emit(IrOpcode::BitwiseOr32, IrType::U32, {&b.constant(0x7f800000u), &b.unknown()})); + auto& lowExponent = b.flush(b.emit(IrOpcode::BitwiseOr32, IrType::U32, {&b.unknown(), &b.constant(0x00800000u)})); + auto& quiet = b.flush(b.emit(IrOpcode::BitwiseOr32, IrType::U32, {&b.flush(b.unknown()), &b.constant(0x00400000u)})); + auto& unknownOr = b.flush(b.emit(IrOpcode::BitwiseOr32, IrType::U32, {&b.unknown(), &b.unknown()})); + auto& mantissa = b.flush(b.emit(IrOpcode::BitwiseAnd32, IrType::U32, {&b.unknown(), &b.constant(0x807fffffu)})); + for (auto* value : {&sign, &infinity, &lowExponent, &quiet, &unknownOr, &mantissa}) b.keep(*value); + return b.eliminate() == 3u && !sign.HasUses() && !infinity.HasUses() && !lowExponent.HasUses() && quiet.HasUses() && unknownOr.HasUses() && mantissa.HasUses(); + }); + passed &= run("a phi of flushed values needs no flush", [] { Builder b; IrBlock& head = *b.block; diff --git a/core/libs/prx/libSceAgcDriver/tests/DeviceMemoryFallback.cpp b/core/libs/prx/libSceAgcDriver/tests/DeviceMemoryFallback.cpp index 02d00ffcdf..1f89e602d8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/DeviceMemoryFallback.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/DeviceMemoryFallback.cpp @@ -18,12 +18,15 @@ void Expect(bool condition, const std::string& what) { constexpr std::uint32_t HostType = 0; constexpr std::uint32_t DeviceType = 1; +constexpr std::uint32_t SecondDeviceType = 2; constexpr std::uint64_t MiB = 1ull << 20u; std::uint64_t reportedBudget = 0; std::uint64_t reportedUsage = 0; std::uint64_t reportedHostBudget = 0; std::uint64_t reportedHostUsage = 0; +std::uint64_t reportedSecondBudget = 0; +std::uint64_t reportedSecondUsage = 0; VKAPI_ATTR void VKAPI_CALL mockMemoryProperties2(VkPhysicalDevice, VkPhysicalDeviceMemoryProperties2* properties) { auto* reported = static_cast(properties->pNext); @@ -31,6 +34,8 @@ VKAPI_ATTR void VKAPI_CALL mockMemoryProperties2(VkPhysicalDevice, VkPhysicalDev reported->heapUsage[0] = reportedUsage; reported->heapBudget[1] = reportedHostBudget; reported->heapUsage[1] = reportedHostUsage; + reported->heapBudget[2] = reportedSecondBudget; + reported->heapUsage[2] = reportedSecondUsage; } Context mockContext(bool budgetExtension) { @@ -138,6 +143,52 @@ void BufferPlacementTests() { choose("no physical device retains cached preference", 15, coherent, 0); } +void HeadroomTests() { + Context context = mockContext(true); + const auto check = [&](const char* what, std::uint32_t type, std::uint64_t budget, std::uint64_t usage, std::uint64_t bytes, bool expected) { + reportedBudget = budget; + reportedUsage = usage; + Expect(context.DeviceLocalHeadroom(type, bytes) == expected, std::string(what) + ": expected " + (expected ? "room" : "no room")); + }; + check("room below seven eighths of the budget", DeviceType, 4096 * MiB, 3000 * MiB, 64 * MiB, true); + check("allocation ends at seven eighths exactly", DeviceType, 4096 * MiB, 3520 * MiB, 64 * MiB, true); + check("allocation crosses seven eighths", DeviceType, 4096 * MiB, 3521 * MiB, 64 * MiB, false); + check("usage already past seven eighths", DeviceType, 4096 * MiB, 3700 * MiB, 1 * MiB, false); + check("budget not reported", DeviceType, 0, 4000 * MiB, 64 * MiB, true); + check("a host memory type is not held to the device-local budget", HostType, 4096 * MiB, 4000 * MiB, 64 * MiB, true); + check("unknown memory type", 7, 4096 * MiB, 4000 * MiB, 64 * MiB, true); + context.memoryProperties2 = nullptr; + check("no VK_EXT_memory_budget", DeviceType, 4096 * MiB, 4000 * MiB, 64 * MiB, true); + context.memoryProperties2 = mockMemoryProperties2; + context.memory.memoryHeaps[0].flags = 0; + check("no device-local heap", DeviceType, 4096 * MiB, 4000 * MiB, 64 * MiB, true); +} + +void SecondHeapHeadroomTests() { + Context context = mockContext(true); + context.memory.memoryHeapCount = 3; + context.memory.memoryHeaps[2] = {4096 * MiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}; + context.memory.memoryTypeCount = 3; + context.memory.memoryTypes[SecondDeviceType] = {VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 2}; + reportedBudget = 4096 * MiB; + reportedSecondBudget = 4096 * MiB; + const auto check = [&](const char* what, std::uint32_t mask, std::uint64_t firstUsage, std::uint64_t secondUsage, std::uint32_t expectedType, bool expected) { + reportedUsage = firstUsage; + reportedSecondUsage = secondUsage; + const auto type = context.BufferMemoryType(mask, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 64 * MiB); + Expect(type == expectedType, std::string(what) + ": chose memory type " + std::to_string(type) + ", expected " + std::to_string(expectedType)); + Expect(context.DeviceLocalHeadroom(type, 64 * MiB) == expected, std::string(what) + ": expected " + (expected ? "room" : "no room")); + }; + const std::uint32_t secondOnly = (1u << HostType) | (1u << SecondDeviceType); + const std::uint32_t all = secondOnly | (1u << DeviceType); + check("mask excludes the first device-local type, first heap under pressure", secondOnly, 3700 * MiB, 1000 * MiB, SecondDeviceType, true); + check("mask excludes the first device-local type, second heap under pressure", secondOnly, 1000 * MiB, 3700 * MiB, SecondDeviceType, false); + check("mask allows both device-local types, first heap under pressure", all, 3700 * MiB, 1000 * MiB, DeviceType, false); + check("mask allows both device-local types, second heap under pressure", all, 1000 * MiB, 3700 * MiB, DeviceType, true); + reportedSecondBudget = 0; + reportedSecondUsage = 0; +} + } int main() { @@ -153,6 +204,8 @@ int main() { BufferPlacementTests(); RefusedFallbackTests(); ReclaimTests(); + HeadroomTests(); + SecondHeapHeadroomTests(); } catch (const std::exception& error) { std::fprintf(stderr, "FAIL: %s\n", error.what()); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/FragmentBarycentric.cpp b/core/libs/prx/libSceAgcDriver/tests/FragmentBarycentric.cpp index 6b67d8629f..e65077a142 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FragmentBarycentric.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FragmentBarycentric.cpp @@ -29,5 +29,9 @@ int main() { ++failures; } } + if (!AgcDriver::HostInterpolationPreferred(VK_DRIVER_ID_MESA_RADV) || AgcDriver::HostInterpolationPreferred(VK_DRIVER_ID_NVIDIA_PROPRIETARY) || AgcDriver::HostInterpolationPreferred(VK_DRIVER_ID_AMD_PROPRIETARY)) { + std::fprintf(stderr, "host interpolation must be preferred on RADV only\n"); + ++failures; + } return failures == 0 ? 0 : 1; } diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameLimit.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameLimit.cpp index 9337c47873..1b84c99965 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameLimit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameLimit.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/FrameLimit.hpp" #include +#include #include #include #include @@ -15,6 +16,11 @@ void require(bool condition, const std::string& reason) { if (!condition) throw std::runtime_error(reason); } +template +void require(bool condition, Reason&& reason) { + if (!condition) throw std::runtime_error(reason()); +} + Nanos exactNanos(std::uint64_t frames, std::uint64_t milliFps) { return static_cast(static_cast(frames) * 1'000'000'000'000ull / milliFps); } @@ -26,7 +32,7 @@ void deadlineArithmeticTests() { require(pacer.Admit(now) == 0, "the first frame of a run is delayed at " + std::to_string(milliFps)); for (std::uint64_t frame = 1; frame <= 1'000'000; ++frame) { now += pacer.Admit(now); - require(now == exactNanos(frame, milliFps), "frame " + std::to_string(frame) + " at limit " + std::to_string(milliFps) + " is presented at " + std::to_string(now) + " instead of " + std::to_string(exactNanos(frame, milliFps))); + require(now == exactNanos(frame, milliFps), [&] { return "frame " + std::to_string(frame) + " at limit " + std::to_string(milliFps) + " is presented at " + std::to_string(now) + " instead of " + std::to_string(exactNanos(frame, milliFps)); }); } } } @@ -42,7 +48,7 @@ void overshootDoesNotAccumulateTests() { const Nanos overshoot = static_cast((seed >> 33) % 2'000'000); now += pacer.Admit(now) + overshoot; const Nanos late = now - exactNanos(frame, milliFps); - require(late >= 0 && late < 2'000'000, "a sleep that overshoots by under 2 ms leaves frame " + std::to_string(frame) + " " + std::to_string(late) + " ns after its deadline"); + require(late >= 0 && late < 2'000'000, [&] { return "a sleep that overshoots by under 2 ms leaves frame " + std::to_string(frame) + " " + std::to_string(late) + " ns after its deadline"; }); } } @@ -91,7 +97,7 @@ void slowGameIsNeverDelayedTests() { const Nanos period = exactNanos(1, milliFps); Nanos now = 5'000'000'000; for (int frame = 0; frame < 10'000; ++frame) { - require(pacer.Admit(now) == 0, "a game slower than the limit " + std::to_string(milliFps) + " is delayed at frame " + std::to_string(frame)); + require(pacer.Admit(now) == 0, [&] { return "a game slower than the limit " + std::to_string(milliFps) + " is delayed at frame " + std::to_string(frame); }); now += period + period / 1000 + 1; } } diff --git a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp index 29c816ebcd..b8e502c97b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/FrameReplayState.cpp @@ -3,6 +3,7 @@ #undef main #include "prx/libSceAgcDriver/Execution/include/FrameRecorder.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" +#include #include namespace { @@ -232,7 +233,11 @@ void testBlockingSubmission(const std::filesystem::path& directory) { AgcDriverUnregisterVideoOutput_nid_postfix(7, output); } -void testShaderCode(const std::filesystem::path& directory, bool host) { +void testShaderCode(const std::filesystem::path& directory, bool host, std::string_view mode = {}) { + const bool prologue = mode.starts_with("shader_prologue_"); + const bool readOnly = mode.ends_with("readonly"); + const bool hostHeader = readOnly || mode.ends_with("header"); + std::byte* hostMemory = nullptr; auto* bytes = allocate(1u << 20); auto* shader = reinterpret_cast(bytes); auto* code = bytes + (1u << 19) + 256; @@ -246,8 +251,12 @@ void testShaderCode(const std::filesystem::path& directory, bool host) { auto* memory = mapped != MAP_FAILED ? static_cast(mapped) : nullptr; #endif check(memory != nullptr, "host shader allocation failed"); - GuestAllocations::Mutation mutation; - mutation.Add(memory, CodeBytes + 4096, true, true, true); + if (!prologue) { + GuestAllocations::Mutation mutation; + mutation.Add(memory, CodeBytes + 4096, true, true, true); + } + hostMemory = memory; + if (hostHeader) shader = reinterpret_cast(memory); code = memory + 256; } #endif @@ -259,20 +268,36 @@ void testShaderCode(const std::filesystem::path& directory, bool host) { shader->code = code; auto* recorder = AgcDriver::FrameRecorder::Active(); check(recorder != nullptr, "frame recorder is disabled"); - recorder->FrameBoundary(); + if (!prologue) 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()); + std::vector expectedHeader(sizeof(Shader)); + std::memcpy(expectedHeader.data(), shader, expectedHeader.size()); + if (readOnly) { +#ifdef _WIN32 + DWORD previous = 0; + check(VirtualProtect(hostMemory, CodeBytes + 4096, PAGE_READONLY, &previous) != 0, "cannot protect host shader pages"); +#elif defined(__linux__) + check(mprotect(hostMemory, CodeBytes + 4096, PROT_READ) == 0, "cannot protect host shader pages"); +#endif + } recorder->RegisterShader(shader); recorder->FrameBoundary(); + if (prologue) 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); + std::vector capturedHeader(sizeof(Shader)); + const auto applyBytes = [&](const FrameMemoryChunk& chunk, std::uint64_t target, std::span bytes) { + const auto begin = std::max(target, chunk.address); + const auto end = std::min(target + bytes.size(), 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); + std::memcpy(bytes.data() + begin - target, data.data() + begin - chunk.address, end - begin); + }; + const auto apply = [&](const FrameMemoryChunk& chunk) { + applyBytes(chunk, address(code), captured); + if (hostHeader) applyBytes(chunk, address(shader), capturedHeader); }; for (const auto& chunk : reader.Chunks()) if (chunk.kind == FrameMemoryKind::Snapshot) apply(chunk); bool registered = false; @@ -286,6 +311,14 @@ void testShaderCode(const std::filesystem::path& directory, bool host) { if (chunk.kind == FrameMemoryKind::Delta && chunk.group == call.values.at(0)) apply(chunk); } } + if (hostHeader && captured == expected) check(capturedHeader == expectedHeader, "the host shader header was not captured before registration"); + if (readOnly && captured == expected) { + const bool protectedHeader = std::ranges::any_of(reader.Chunks(), [&](const auto& chunk) { + return chunk.kind == FrameMemoryKind::Range && chunk.address <= address(shader) && + address(shader) - chunk.address < chunk.bytes && (chunk.access & AgcDriver::FrameRangeAccess::Writable) == 0; + }); + check(protectedHeader, "the read-only host shader was recorded with write access"); + } check(registered && captured == expected, "the complete separate shader code was not captured before registration"); } @@ -298,6 +331,7 @@ int main(int argc, char** argv) { const std::filesystem::path directory = argv[2]; if (mode == "order") testMemoryOrder(directory); else if (mode == "zero") testReusedMemory(directory); + else if (mode.starts_with("shader_prologue_")) testShaderCode(directory, true, mode); else if (mode == "shared_page") testSharedPage(directory); else if (mode == "host_neighbors") testHostNeighbors(directory); else if (mode == "unwatched") testUnwatched(); diff --git a/core/libs/prx/libSceAgcDriver/tests/GcnDecode.cpp b/core/libs/prx/libSceAgcDriver/tests/GcnDecode.cpp index 855c0aecdb..3499fa7ed7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GcnDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GcnDecode.cpp @@ -31,11 +31,32 @@ void SetregImmKeepsItsLiteral() { Check(Decode(movk).wordCount == 1u, "s_movk_i32 took a literal word"); } +void Vop3FormsOfMappedVop2AndVopcDecode() { + const std::array subb{0xd2520401u, 0x001a0702u, 0xbf810000u}; + const auto borrow = Decode(subb); + Check(borrow.op == RdnaOpcode::VSubCoCiU32, "VOP3 v_subb_u32 did not decode as v_sub_co_ci_u32"); + Check(borrow.wordCount == 2u, "VOP3 v_subb_u32 is not two words"); + Check(borrow.destination.kind == RdnaOperandKind::VectorRegister && borrow.destination.reg == 1u, "VOP3 v_subb_u32 lost its vector destination"); + Check(borrow.destination2.kind == RdnaOperandKind::ScalarRegister && borrow.destination2.reg == 4u, "VOP3 v_subb_u32 lost its borrow-out SGPR pair"); + Check(borrow.source2.kind == RdnaOperandKind::ScalarRegister && borrow.source2.reg == 6u, "VOP3 v_subb_u32 lost its borrow-in SGPR pair"); + const std::array subbrev{0xd2540401u, 0x001a0702u, 0xbf810000u}; + Check(Decode(subbrev).op == RdnaOpcode::VSubrevCoCiU32, "VOP3 v_subbrev_u32 did not decode as v_subrev_co_ci_u32"); + const std::array truF32{0xd01e0004u, 0x00020300u, 0xbf810000u}; + const auto always = Decode(truF32); + Check(always.op == RdnaOpcode::VCmpTruF32, "VOP3 v_cmp_tru_f32 did not decode as its RDNA opcode"); + Check(always.destination.kind == RdnaOperandKind::ScalarRegister && always.destination.reg == 4u, "VOP3 v_cmp_tru_f32 lost its scalar destination"); + const std::array cmpsLtF32{0xd0820004u, 0x00020300u, 0xbf810000u}; + Check(Decode(cmpsLtF32).op == RdnaOpcode::VCmpLtF32, "VOP3 v_cmps_lt_f32 did not decode as v_cmp_lt_f32"); + const std::array cmpsTruF64{0xd0de0004u, 0x00020500u, 0xbf810000u}; + Check(Decode(cmpsTruF64).op == RdnaOpcode::VCmpTruF64, "VOP3 v_cmps_tru_f64 did not decode as v_cmp_tru_f64"); +} + } int main() { try { SetregImmKeepsItsLiteral(); + Vop3FormsOfMappedVop2AndVopcDecode(); } catch (const std::exception& error) { std::fprintf(stderr, "%s\n", error.what()); return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp index 6b0a2927b5..941a074032 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Graphics.cpp @@ -1,12 +1,15 @@ #include "prx/libc/include/Api.hpp" +#include "RdnaDecoder/RdnaInstructionDecoder.hpp" #include "BdaTests.hpp" #include "GraphicsTests.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/DrawWriteRanges.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" #include "prx/libSceAgcDriver/Graphics/include/PipelineJournal.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp" #include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/BufferPool.hpp" @@ -1177,10 +1180,14 @@ void ShaderStageTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "unsupported geometry routing, fast launch or wave-ID state"); fast.context[0x2d5] = 0x482030; fast.userConfig[0x25b] = 0x402; - expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "fast launch other than one point per subgroup"); + expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "fast launch other than one vertex and one primitive per subgroup"); fast.userConfig[0x25b] = 0x201; - fast.userConfig[0x242] = 4; - expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "fast launch other than one point per subgroup"); + for (const std::uint32_t type : {2u, 3u, 4u, 5u, 6u, 21u}) { + fast.userConfig[0x242] = type; + const auto typed = AgcDriver::Graphics::DecodeState(fast); + const auto& typedMesh = *typed.stages.mesh; + Require(typedMesh.inputPrimitive == 1 && typedMesh.primitivesPerGroup == 1 && typedMesh.verticesPerGroup == 1 && typedMesh.maxPrimitives == 64, "a fast-launch draw with a line or triangle input type did not decode as one point per subgroup"); + } fast.userConfig[0x242] = 1; fast.context[0x2d3] = 0; expectFailure([&] { AgcDriver::Graphics::DecodeState(fast); }, "amplifies to no primitives"); @@ -2509,6 +2516,10 @@ void DepthStencilTests() { // 260, AMD PAL), so a fill of the words a linear layout would hold covers nothing. Require(!HtileFillCovers(0x10000, {16, 16}, 0x10000, 16) && !HtileFillCovers(0x10000, {16, 16}, 0x10000, 264), "a fill of the linear HTILE size of a 16x16 surface misses its pipe-aligned words"); Require(AgcDriver::Graphics::HtileMetadataBytes({1024, 512}) == 0x8000u && AgcDriver::Graphics::HtileMetadataBytes({1025, 512}) == 0x10000u && AgcDriver::Graphics::HtileMetadataBytes({3840, 2160}) == 20u * 0x8000u, "the HTILE metadata is 32 KiB per 1024x512 block"); + ShaderRecompiler::SetGcnGuestShaders(true); + Require(AgcDriver::Graphics::HtileMetadataBytes({512, 512}) == 0x4000u && AgcDriver::Graphics::HtileMetadataBytes({256, 256}) == 0x4000u && AgcDriver::Graphics::HtileMetadataBytes({2048, 1152}) == 0x30000u && AgcDriver::Graphics::HtileMetadataBytes({4096, 4096}) == 0x100000u, "a PS4 HTILE is 16 KiB per 512x512 block"); + Require(HtileFillCovers(0x10000, {512, 512}, 0x10000, 0x4000) && !HtileFillCovers(0x10000, {512, 512}, 0x10000, 0x3ffc), "a 16 KiB fill covers the HTILE of a 512x512 PS4 depth target"); + ShaderRecompiler::SetGcnGuestShaders(false); } Require(state.renderExtent.width == 4 && state.renderExtent.height == 2, "render extent ignores the depth surface"); Require(!state.depthTest && !state.depthWrite && state.stencilTest, "depth/stencil enables changed"); @@ -6303,6 +6314,42 @@ void oversizedDescriptorTests() { ::operator delete(block, std::align_val_t{blockBytes}); } +void viewAtRangeLimitTests() { + constexpr std::size_t blockBytes = 65536; + void* block = ::operator new(blockBytes, std::align_val_t{blockBytes}); + std::memset(block, 0x5a, blockBytes); + { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + GuestAllocations::GuestAllocationsAdd_nid_postfix(mutation, block, blockBytes, true, true, true); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + } + { + ShaderRecompiler::RecompileResult vertex; + ShaderRecompiler::RecompileResult fragment; + mock = MockVulkan{}; + auto context = mockContext(); + context.limits.minStorageBufferOffsetAlignment = 16; + const auto* view = static_cast(block) + 12; + vertex.bindings.push_back(makeBinding(Role::GuestBuffers, 0, 1, vsharp(block, 64))); + vertex.bindings.push_back(makeBinding(Role::GuestBuffers, 1, 1, vsharp(view, static_cast(context.limits.maxStorageBufferRange)))); + vertex.bindings.push_back(makeBinding(Role::ShaderData, 5, 1, ShaderDataWords({}))); + vertex.shaderDataDwords = ShaderRecompiler::RuntimeAbi::ShaderDataDwords; + const auto color = AgcDriver::Graphics::DecodeState(makeState()).color; + { + AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0); + const auto& write = findWrite(1); + Require(write.buffers.size() == 1 && write.buffers[0].range == context.limits.maxStorageBufferRange, "a V# of exactly the range limit at an unaligned address was not bound within the limit"); + } + Require(mock.live == 0, "a V# of exactly the range limit leaked Vulkan objects"); + } + { + auto* mutation = GuestAllocations::GuestAllocationsBegin_nid_postfix(); + GuestAllocations::GuestAllocationsRemove_nid_postfix(mutation, block); + GuestAllocations::GuestAllocationsEnd_nid_postfix(mutation); + } + ::operator delete(block, std::align_val_t{blockBytes}); +} + void guestBufferViewBytesTests() { using AgcDriver::Graphics::GuestBufferMemory; for (const std::uint64_t bytes : {2ull, 3ull, 4ull, 6ull, 7ull, 16ull, 4096ull, 4098ull}) { @@ -6937,6 +6984,161 @@ void resourceTests() { } } +void unsetFormatStorageImageTests() { + using ShaderRecompiler::DescriptorBinding; + using ShaderRecompiler::DescriptorImageShape; + using ShaderRecompiler::DescriptorKind; + using ShaderRecompiler::DescriptorRole; + constexpr std::uint32_t Format8888UNorm = 56; + constexpr std::uint32_t Type2D = 9; + alignas(256) std::array guest{}; + const auto base = reinterpret_cast(guest.data()); + const auto words = [](std::uint32_t format, std::uint64_t baseAddress) { + return std::vector{ + static_cast(baseAddress >> 8u), + static_cast((baseAddress >> 40u) & 0xffu) | ((format & 0x1ffu) << 20u) | (3u << 30u), + 3u | (15u << 14u), + 0xfacu | (Type2D << 28u), + 0u, + 0u, + 0u, + 0u, + }; + }; + const auto storage = [&](std::uint32_t format, std::uint64_t baseAddress) { + DescriptorBinding binding{}; + binding.kind = DescriptorKind::StorageImage; + binding.role = DescriptorRole::GuestImages; + binding.binding = ShaderRecompiler::RuntimeAbi::FirstStorageImageBinding; + binding.count = 1; + binding.imageWritten = {true}; + binding.guestDescriptor = words(format, baseAddress); + return binding; + }; + for (const std::uint32_t format : {0u, 0x1ffu}) { + mock = MockVulkan{}; + auto context = mockContext(); + context.device = makeHandle(); + context.limits.maxPerStageDescriptorStorageImages = 8; + context.limits.maxDescriptorSetStorageImages = 8; + ShaderRecompiler::RecompileResult vertex; + ShaderRecompiler::RecompileResult fragment; + vertex.bindings.push_back(storage(format, base)); + const auto color = AgcDriver::Graphics::DecodeState(makeState()).color; + { + AgcDriver::Graphics::TextureDetiler detiler(context); + context.detiler = &detiler; + expectFailure([&] { AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0); }, "unsupported guest texture format"); + } + Require(mock.live == 0, "a rejected unset-format storage image leaked Vulkan objects"); + } + { + mock = MockVulkan{}; + auto context = mockContext(); + context.device = makeHandle(); + context.limits.maxPerStageDescriptorSampledImages = 8; + context.limits.maxDescriptorSetSampledImages = 8; + ShaderRecompiler::RecompileResult vertex; + ShaderRecompiler::RecompileResult fragment; + auto sampled = storage(0u, base); + sampled.kind = DescriptorKind::SampledImage; + sampled.binding = ShaderRecompiler::RuntimeAbi::FirstImageBinding; + sampled.imageWritten.clear(); + sampled.imageShape = DescriptorImageShape::Image2D; + sampled.imageSamplers = {0}; + vertex.bindings.push_back(sampled); + const auto color = AgcDriver::Graphics::DecodeState(makeState()).color; + { + AgcDriver::Graphics::TextureDetiler detiler(context); + context.detiler = &detiler; + AgcDriver::Graphics::TextureCache textureCache(context); + context.textureCache = &textureCache; + AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0); + } + Require(mock.live == 0, "a sampled unset-format image leaked Vulkan objects"); + } + { + mock = MockVulkan{}; + auto context = mockContext(); + context.device = makeHandle(); + context.limits.maxPerStageDescriptorSampledImages = 8; + context.limits.maxDescriptorSetSampledImages = 8; + ShaderRecompiler::RecompileResult vertex; + ShaderRecompiler::RecompileResult fragment; + auto sampled = storage(0u, base); + sampled.kind = DescriptorKind::SampledImage; + sampled.binding = ShaderRecompiler::RuntimeAbi::FirstImageBinding; + sampled.imageWritten.clear(); + sampled.imageShape = DescriptorImageShape::Image1DArray; + sampled.imageSamplers = {0}; + sampled.guestDescriptor[3] = (sampled.guestDescriptor[3] & ~(0xfu << 28u)) | (12u << 28u); + vertex.bindings.push_back(sampled); + const auto color = AgcDriver::Graphics::DecodeState(makeState()).color; + { + AgcDriver::Graphics::TextureDetiler detiler(context); + context.detiler = &detiler; + AgcDriver::Graphics::TextureCache textureCache(context); + context.textureCache = &textureCache; + AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0); + } + Require(mock.live == 0, "a sampled TYPE 12 image with an unset format leaked Vulkan objects"); + } + for (const std::uint32_t format : {0u, 0x1ffu}) { + mock = MockVulkan{}; + auto context = mockContext(); + context.device = makeHandle(); + context.limits.maxPerStageDescriptorSampledImages = 8; + context.limits.maxDescriptorSetSampledImages = 8; + ShaderRecompiler::RecompileResult vertex; + ShaderRecompiler::RecompileResult fragment; + auto sampled = storage(format, base); + sampled.kind = DescriptorKind::SampledImage; + sampled.binding = ShaderRecompiler::RuntimeAbi::FirstImageBinding; + sampled.imageWritten.clear(); + sampled.imageShape = DescriptorImageShape::Image2D; + sampled.imageSamplers = {0}; + sampled.guestDescriptor[3] = (sampled.guestDescriptor[3] & ~(0xfu << 28u)) | (2u << 28u); + vertex.bindings.push_back(sampled); + const auto color = AgcDriver::Graphics::DecodeState(makeState()).color; + { + AgcDriver::Graphics::TextureDetiler detiler(context); + context.detiler = &detiler; + AgcDriver::Graphics::TextureCache textureCache(context); + context.textureCache = &textureCache; + expectFailure([&] { AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0); }, "unsupported image type 2"); + } + Require(mock.live == 0, "a rejected reserved-type sampled image leaked Vulkan objects"); + } + { + mock = MockVulkan{}; + auto context = mockContext(); + context.device = makeHandle(); + context.limits.maxPerStageDescriptorStorageImages = 8; + context.limits.maxDescriptorSetStorageImages = 8; + ShaderRecompiler::RecompileResult vertex; + ShaderRecompiler::RecompileResult fragment; + vertex.bindings.push_back(storage(0u, 0)); + const auto color = AgcDriver::Graphics::DecodeState(makeState()).color; + { + AgcDriver::Graphics::TextureDetiler detiler(context); + context.detiler = &detiler; + AgcDriver::Graphics::ShaderResources resources(context, vertex, fragment, color, 0, 0); + } + Require(mock.live == 0, "a null storage image with an unset format leaked Vulkan objects"); + } + { + ShaderRecompiler::RecompileResult compiled; + compiled.bindings.push_back(storage(Format8888UNorm, base)); + bool bounded = false; + const auto ranges = AgcDriver::DriverDetail::DispatchWriteRanges(compiled, bounded); + Require(ranges.size() == 1 && ranges[0].first == base && ranges[0].second == base + 4096u, "a written storage image did not contribute its whole surface to the dispatch write ranges"); + compiled.bindings.front().guestDescriptor = words(0u, base); + expectFailure([&] { static_cast(AgcDriver::DriverDetail::DispatchWriteRanges(compiled, bounded)); }, "unsupported guest texture format"); + compiled.bindings.front().guestDescriptor = words(0u, 0u); + Require(AgcDriver::DriverDetail::DispatchWriteRanges(compiled, bounded).empty(), "a null written storage image contributed dispatch write ranges"); + } +} + void descriptorCacheTests() { mock = MockVulkan{}; auto context = mockContext(); @@ -7292,9 +7494,11 @@ void hostImportBudgetTests() { memory.memoryHeaps[2] = {256 * MiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}; Require(HostImportBudget(memory, 16 * GiB) == 4064 * MiB, "imports on a 16 GiB machine take more than half its 8128 MiB host heap"); memory.memoryHeaps[1].size = 16 * GiB; - Require(HostImportBudget(memory, 32 * GiB) == 8 * GiB, "imports on a 32 GiB machine take more than half its 16 GiB host heap"); + Require(HostImportBudget(memory, 32 * GiB) == 12 * GiB, "a 32 GiB machine does not keep 4 GiB of its 16 GiB host heap from imports"); + memory.memoryHeaps[1].size = 15911 * MiB; + Require(HostImportBudget(memory, 32 * GiB) == 11815 * MiB, "imports on a 32 GiB machine leave no room for the 10466 MiB a title holds at once"); memory.memoryHeaps[1].size = 32 * GiB; - Require(HostImportBudget(memory, 64 * GiB) == 16 * GiB, "imports on a 64 GiB machine take more than half its 32 GiB host heap"); + Require(HostImportBudget(memory, 64 * GiB) == 28 * GiB, "a 64 GiB machine does not keep 4 GiB of its 32 GiB host heap from imports"); memory.memoryHeaps[1].size = 3 * GiB; Require(HostImportBudget(memory, 16 * GiB) == 1 * GiB, "a 3 GiB host heap does not keep its 2 GiB reserve"); memory.memoryHeaps[1].size = 8128 * MiB; @@ -7304,6 +7508,49 @@ void hostImportBudgetTests() { Require(HostImportBudget(memory, 16 * GiB) == 8 * GiB, "a host heap past memoryHeapCount limits the import budget"); } +void unifiedHeapImportBudgetTests() { + using AgcDriver::Graphics::HostImportBudgetFor; + constexpr std::uint64_t MiB = 1ull << 20u; + constexpr std::uint64_t GiB = 1ull << 30u; + VkPhysicalDeviceMemoryProperties unified{}; + unified.memoryHeapCount = 1; + unified.memoryHeaps[0] = {24 * GiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}; + unified.memoryTypeCount = 3; + unified.memoryTypes[0] = {VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0}; + unified.memoryTypes[1] = {VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT, 0}; + unified.memoryTypes[2] = {VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT, 0}; + VkPhysicalDeviceMemoryBudgetPropertiesEXT report{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; + report.heapBudget[0] = 18 * GiB; + report.heapUsage[0] = 0; + Require(HostImportBudgetFor(unified, &report, 12 * GiB, 0) == 12 * GiB, "an idle unified heap lowered the import budget"); + report.heapUsage[0] = 8 * GiB; + Require(HostImportBudgetFor(unified, &report, 12 * GiB, 0) == 7936 * MiB, "a unified heap used by other allocations did not lower the import budget"); + Require(HostImportBudgetFor(unified, &report, 12 * GiB, 4 * GiB) == 12032 * MiB, "a unified heap counted its own imports as other allocations"); + report.heapUsage[0] = 17 * GiB; + Require(HostImportBudgetFor(unified, &report, 12 * GiB, 0) == 0, "a unified heap past its budget left room for imports"); + Require(HostImportBudgetFor(unified, nullptr, 12 * GiB, 0) == 12 * GiB, "a missing budget report lowered the import budget"); + auto deviceOnly = unified; + deviceOnly.memoryTypeCount = 2; + deviceOnly.memoryTypes[1].propertyFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; + report.heapUsage[0] = 8 * GiB; + Require(HostImportBudgetFor(deviceOnly, &report, 12 * GiB, 0) == 12 * GiB, "a heap without host-coherent memory was taken as the host heap"); + + VkPhysicalDeviceMemoryProperties discrete{}; + discrete.memoryHeapCount = 2; + discrete.memoryHeaps[0] = {8 * GiB, VK_MEMORY_HEAP_DEVICE_LOCAL_BIT}; + discrete.memoryHeaps[1] = {16 * GiB, 0}; + discrete.memoryTypeCount = 3; + discrete.memoryTypes[0] = {VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0}; + discrete.memoryTypes[1] = {VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, 0}; + discrete.memoryTypes[2] = {VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, 1}; + VkPhysicalDeviceMemoryBudgetPropertiesEXT split{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; + split.heapBudget[0] = 6 * GiB; + split.heapUsage[0] = 6 * GiB; + split.heapBudget[1] = 12 * GiB; + split.heapUsage[1] = 0; + Require(HostImportBudgetFor(discrete, &split, 20 * GiB, 0) == 10752 * MiB, "a device with a separate host heap used its device-local heap for the import budget"); +} + void tableUpdateAfterBindTests() { namespace Abi = ShaderRecompiler::ImageTableAbi; using namespace AgcDriver::Graphics; @@ -10312,7 +10559,7 @@ void ProvokingVertexTests() { 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& drawn : {std::span{}, std::span(flatShaders), std::span(flatP10Shaders), std::span(smoothShaders), std::span(slotShaders)}) ValidateProvokingVertex(supported, meshDraw, drawn); for (const auto topology : {VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN}) { auto firstDraw = meshDraw; firstDraw.topology = topology; @@ -10635,7 +10882,11 @@ void uintTargetTests() { Require(uint16.color.packing == ColorExportPacking::None && uint16.color.uintExport, "a UINT16_ABGR export into a UINT target was retyped twice"); auto halves = queue; halves.context[0x1c5] = 4u; - expectFailure([&] { static_cast(AgcDriver::Graphics::DecodeState(halves)); }, "color export format 4 into an unsigned integer target is unsupported"); + for (const auto& [format, vkFormat, packing] : std::array, 5>{{{4u, VK_FORMAT_R32_UINT, ColorExportPacking::Uint}, {1u, VK_FORMAT_R8_UINT, ColorExportPacking::UintClamp8}, {10u, VK_FORMAT_R8G8B8A8_UINT, ColorExportPacking::UintClamp8}, {2u, VK_FORMAT_R16_UINT, ColorExportPacking::UintClamp16}, {12u, VK_FORMAT_R16G16B16A16_UINT, ColorExportPacking::UintClamp16}}}) { + halves.context[0x31c] = (queue.context.at(0x31c) & ~0x1f7cu) | (format << 2u) | (4u << 8u); + const auto half = AgcDriver::Graphics::DecodeState(halves); + Require(half.color.format == vkFormat && half.color.packing == packing && !half.color.uintExport && AgcDriver::Graphics::ExportPackings(half)[0] == packing && !half.blends.at(0).blendEnable, "an FP16_ABGR export into a UINT target did not get the packing that clamps to its channel width"); + } auto unorm = queue; unorm.context[0x31c] = (unorm.context.at(0x31c) & ~0x1f7cu) | (4u << 2u) | (7u << 8u); Require(AgcDriver::Graphics::DecodeState(unorm).color.packing == ColorExportPacking::None, "a 32-bit float target got the unsigned integer export packing"); @@ -10659,8 +10910,15 @@ void unorm10_11_11TargetTests() { changed.context[0x31c] |= 1u << 8u; expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "unsupported color format 6 number type 1"); changed = queue; - changed.context[0x1e0] = 0x40010001u; for (std::uint32_t i = 0; i < 4; ++i) changed.context[0x105 + i] = 0; + for (const auto replace : {0x40010001u, 0x60050001u}) { + changed.context[0x1e0] = replace; + const auto replaced = AgcDriver::Graphics::DecodeState(changed); + Require(replaced.blends.size() == 1 && !replaced.blends[0].blendEnable && replaced.color.packing == ColorExportPacking::Unorm10_11_11, "a blend that writes the source color into a 10_11_11 unorm target was refused or kept blending"); + } + changed.context[0x1e0] = 0x40010101u; + expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "blending into a 10_11_11 unorm color target"); + changed.context[0x1e0] = 0x40010005u; expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "blending into a 10_11_11 unorm color target"); changed = queue; changed.context[0x8e] = 3; @@ -11082,9 +11340,16 @@ void float11_11_10TargetTests() { expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "unsupported color format 7 number type " + std::to_string(number) + " component swap 2"); } changed = queue; - changed.context[0x1e0] = 0x60010101u; for (std::uint32_t i = 0; i < 4; ++i) changed.context[0x105 + i] = 0; - expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "blending into an 11_11_10 float color target"); + for (const auto replace : {0x40010001u, 0x60010001u, 0x60050001u}) { + changed.context[0x1e0] = replace; + const auto replaced = AgcDriver::Graphics::DecodeState(changed); + Require(replaced.blends.size() == 1 && !replaced.blends[0].blendEnable && replaced.color.packing == ColorExportPacking::Float11_11_10 && replaced.color.componentMapping == 0xc6u, "a blend that writes the source color into an 11_11_10 float target was refused or kept blending"); + } + for (const auto blend : {0x60010101u, 0x40010005u}) { + changed.context[0x1e0] = blend; + expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "blending into an 11_11_10 float color target"); + } changed = queue; changed.context[0x8e] = 6; expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "partial writes of an 11_11_10 float color target"); @@ -11118,7 +11383,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(5); + invalid.context.pixel->targetExportPacking[0] = static_cast(7); expectFailure([&] { static_cast(ShaderRecompiler::Recompile(invalid)); }, "invalid prepared fragment export packing"); } @@ -11601,6 +11866,15 @@ void validationTests() { expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "unsupported device capability 12"); AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, true); } + { + ShaderRecompiler::RecompileResult vertex; + vertex.spirv = makeModule({}); + vertex.spirv.insert(vertex.spirv.begin() + 5, {(2u << 16u) | spv::OpCapability, static_cast(spv::CapabilityMinLod)}); + const std::array shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &fragment, 0}}}; + const VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}; + expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "unsupported device capability 42"); + AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, false, false, false, false, false, false, true, true); + } { ShaderRecompiler::RecompileResult vertex; vertex.spirv = makeModule({.layerOutput = true}); @@ -11811,6 +12085,40 @@ void meshArgumentTests() { Require(same(ResolveMeshArguments(record(96 * 6, 600, 0), rules(triangles, 96 * 6)), {6, 600, 1, 96 * 6, 0}), "groups times instances at the device limit"); Require(same(ResolveMeshArguments(record(0xffffffffu, 1, 0xfffffff0u), rules(points, 0xffffffffu)), {15, 1, 1, 15, 0xfffffff0u}), "first index near the end of a huge index buffer"); Require(same(ResolveMeshArguments(record(0xffffffffu, 1, 0), rules(points, 0xffffffffu)), {0, 0, 0, 0xffffffffu, 0}), "groups over the device limit draw nothing"); + context.meshLimits.maxMeshWorkGroupCount[2] = 3; + const auto grid = [&](std::uint32_t groups, std::uint32_t instances, std::array expected) { + const auto value = AgcDriver::Graphics::MeshGridFor(context, groups, instances); + return value.has_value() && value->groups == expected[0] && value->instances == expected[1] && value->layers == expected[2] && value->layerStride == expected[3]; + }; + const auto refused = [&](std::uint32_t groups, std::uint32_t instances) { return !AgcDriver::Graphics::MeshGridFor(context, groups, instances).has_value(); }; + Require(grid(1000, 1, {1000, 1, 1, 0}), "groups at the X limit stay in one layer"); + Require(grid(1, 1, {1, 1, 1, 0}) && grid(999, 3, {999, 3, 1, 0}), "a draw in one layer numbers its groups by X alone"); + Require(grid(1001, 1, {501, 1, 2, 501}), "one group past the X limit splits the groups evenly over two layers"); + Require(grid(1001, 2, {501, 2, 2, 501}), "instances keep Y when the groups take two layers"); + Require(grid(2999, 1, {1000, 1, 3, 1000}), "three layers at the X limit"); + Require(refused(3001, 1), "more layers than the Z limit are refused"); + context.meshLimits.maxMeshWorkGroupCount[2] = 10; + Require(grid(4000, 1, {1000, 1, 4, 1000}), "groups at the total limit"); + Require(refused(4001, 1), "groups whose layers pass the total limit are refused"); + Require(refused(1001, 4), "groups times instances past the total limit are refused"); + Require(refused(1, 601), "instances over the Y limit are refused"); + Require(refused(0, 1), "no groups are refused"); + Require(grid(1, 0, {1, 0, 1, 0}) && grid(1001, 0, {501, 0, 2, 501}), "no instances keep the split and draw nothing, as before"); + context.meshLimits.maxMeshWorkGroupCount[0] = 4194304u; + context.meshLimits.maxMeshWorkGroupCount[1] = 65535u; + context.meshLimits.maxMeshWorkGroupCount[2] = 65535u; + context.meshLimits.maxMeshWorkGroupTotalCount = 4194304u; + Require(grid(65575, 1, {65575, 1, 1, 0}) && grid(4194304, 1, {4194304, 1, 1, 0}), "a device whose X limit holds the draw keeps it in one layer"); + Require(refused(4194305, 1), "groups past an X limit equal to the total limit are refused"); + context.meshLimits.maxMeshWorkGroupCount[0] = 100000u; + context.meshLimits.maxMeshWorkGroupTotalCount = 0xffffffffu; + Require(grid(100000, 1, {100000, 1, 1, 0}), "groups at an X limit past the pushed stride's range stay in one layer"); + Require(grid(100001, 1, {50001, 1, 2, 50001}) && grid(196605, 1, {65535, 1, 3, 65535}), "layers of a draw past such an X limit fit the pushed stride"); + context.meshLimits.maxMeshWorkGroupCount[0] = 800000000u; + context.meshLimits.maxMeshWorkGroupCount[1] = 0xffffffffu; + context.meshLimits.maxMeshWorkGroupCount[2] = 0xffffffffu; + context.meshLimits.maxMeshWorkGroupTotalCount = 0xffffffffu; + Require(refused(0xffffffffu, 0xffffffffu), "a product of groups and instances past 64 bits is refused"); } void meshLimitTests() { @@ -12596,6 +12904,16 @@ void pipelineJournalTests(bool longPath = false, bool extended = false) { 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 layeredPartialState = partialState; + layeredPartialState.meshLayerBase = 2048u; + layeredPartialState.meshLayerCount = 32u; + GraphicsPipelineRecipe layeredPartialDecoded; + Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(layeredPartialState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)), layeredPartialDecoded), "the layered partial write recipe does not decode"); + Require(layeredPartialDecoded.state.meshLayerBase == 2048u && layeredPartialDecoded.state.meshLayerCount == 32u && layeredPartialDecoded.state.colors[0].partialWriteMask == 0xf000u, "the recipe lost mesh layers or the partial write mask"); + std::vector layeredPartialKey, layeredPartialReplayed; + BuildPipelineStoreKey(layeredPartialKey, context, layeredPartialState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL); + BuildPipelineStoreKey(layeredPartialReplayed, context, layeredPartialDecoded.state, layeredPartialDecoded.vertexInput, layouts.View(), shaders, layeredPartialDecoded.attachmentLayout); + Require(layeredPartialKey != partialKey && layeredPartialKey == layeredPartialReplayed, "the decoded layered partial write state keys another pipeline"); auto movedState = state; movedState.viewport.x = 5; movedState.colors[0].address = 0x9000; @@ -12606,6 +12924,18 @@ void pipelineJournalTests(bool longPath = false, bool extended = false) { mappedState.colors[0].componentMapping = 0x24; BuildPipelineStoreKey(mapped, context, mappedState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL); Require(mapped != original, "the component mapping did not enter the key"); + auto legacyState = state; + legacyState.stages.legacyVertex = true; + auto legacyVertex = module(4); + legacyVertex.bindings = vertex.bindings; + const std::array legacyShaders{{{Stage::Vertex, &legacyVertex, 0}, {Stage::Fragment, &fragment, PipelinePushSlotBytes}}}; + GraphicsPipelineRecipe legacyDecoded; + Require(DecodeGraphicsPipelineRecipe(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(legacyState, input, layouts.View(), legacyShaders, VK_IMAGE_LAYOUT_GENERAL)), legacyDecoded), "the legacy vertex recipe does not decode"); + std::vector legacyFlag, legacyKey, legacyReplayed; + BuildPipelineStoreKey(legacyFlag, context, legacyState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL); + BuildPipelineStoreKey(legacyKey, context, legacyState, input, layouts.View(), legacyShaders, VK_IMAGE_LAYOUT_GENERAL); + BuildPipelineStoreKey(legacyReplayed, context, legacyDecoded.state, legacyDecoded.vertexInput, layouts.View(), legacyShaders, legacyDecoded.attachmentLayout); + Require(legacyFlag == original && legacyKey != original && legacyKey == legacyReplayed, "the legacy vertex selection keyed a pipeline by the state flag instead of by its vertex module"); std::vector unclamped, clamped, biased; auto clampedState = state; clampedState.depthBiasClamp = 1.0f; @@ -12724,17 +13054,21 @@ void pipelineJournalTests(bool longPath = false, bool extended = false) { Require(cancelled->Done() && after.replayFailed == 0 && after.replayRejected == 0 && after.replayedGraphics + after.replayPresent <= before.replayedGraphics + before.replayPresent + 1, "a cancelled replay did not stop cleanly"); } { - const std::uint32_t meshWithoutOutputSize = 2u; - std::fstream file(journalFile, std::ios::in | std::ios::out | std::ios::binary); - file.seekp(4); - file.write(reinterpret_cast(&meshWithoutOutputSize), sizeof(meshWithoutOutputSize)); - file.close(); - Require(file.good(), "the journal header could not be rewritten"); - auto stale = StartPipelineJournalReplay(context, fingerprint, hooks); - Require(stale != nullptr, "the replay of a journal in the previous format did not start"); - stale->Wait(); - const auto discarded = PipelineJournalTotals(); - Require(discarded.loadedRecipes == 0 && discarded.loadedModules == 0, "a journal in format 2, whose mesh records lack the output primitive size, was loaded"); + std::vector currentJournal; + Require(ShaderRecompiler::ReadWholeFile(journalFile, currentJournal), "cannot read the current journal for version rejection"); + for (const std::uint32_t previousFormat : {14u, 13u, 12u, 10u, 2u}) { + std::fstream file(journalFile, std::ios::in | std::ios::out | std::ios::binary | std::ios::trunc); + file.write(reinterpret_cast(currentJournal.data()), static_cast(currentJournal.size())); + file.seekp(4); + file.write(reinterpret_cast(&previousFormat), sizeof(previousFormat)); + file.close(); + Require(file.good(), "the journal header could not be rewritten"); + auto stale = StartPipelineJournalReplay(context, fingerprint, hooks); + Require(stale != nullptr, "the replay of a journal in the previous format did not start"); + stale->Wait(); + const auto discarded = PipelineJournalTotals(); + Require(discarded.loadedRecipes == 0 && discarded.loadedModules == 0, "a journal in previous format " + std::to_string(previousFormat) + " was loaded"); + } } ClearCachedPipelines(context.device); } @@ -12884,6 +13218,27 @@ int main(int argc, char** argv) { 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-reference-size") { + { + mock = MockVulkan{}; + auto context = mockContext(); + context.bufferDeviceAddress = true; + context.limits.maxStorageBufferRange = 1u << 27; + RunHeapMirrorReferenceSizeTests(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); + }, + {}, + {} + }); + } + Require(mock.live == 0, "the heap mirror reference size test leaked Vulkan objects"); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--heap-mirror-run-unwatch") { { mock = MockVulkan{}; @@ -13148,7 +13503,9 @@ int main(int argc, char** argv) { descriptorPoolTests(); pushConstantTests(); resourceTests(); + unsetFormatStorageImageTests(); oversizedDescriptorTests(); + viewAtRangeLimitTests(); guestBufferViewBytesTests(); descriptorSnapshotTests(); dispatchMoveTests(); @@ -13163,6 +13520,7 @@ int main(int argc, char** argv) { imageTableTemplateRefreshTests(); debugBranchTests(); hostImportBudgetTests(); + unifiedHeapImportBudgetTests(); textureCacheBudgetTests(); sampledTextureBudgetTests(); storageTextureBudgetTests(); @@ -13216,6 +13574,7 @@ int main(int argc, char** argv) { RunGuestAllocationTests(); RunUnmappedGapTests(); RunVanishedPageReadTests(); + RunPageBlockTests(); RunColorTargetLayoutTests(); RunLiveStackAccessTests(); RunTextureFormatTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp b/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp index 1aa3987714..057323c8c7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuardedMemoryLoads.cpp @@ -6,6 +6,7 @@ #include "SpirvBackend/SpirvEmitter.hpp" #include "SpirvBackend/SpirvAnalysis.hpp" #include "PipelineSpecialization.hpp" +#include #include #include #include @@ -19,6 +20,9 @@ namespace { +constexpr std::uint32_t PoolDwords = 514u; +constexpr std::uint32_t PoolHeader = 2u; + using namespace ShaderRecompiler; void require(bool condition, const std::string& message) { @@ -145,6 +149,15 @@ public: std::vector reads; std::uint32_t previous = 0; std::uint32_t current = 0; + const bool pooled = std::ranges::any_of(names, [](const auto& entry) { return entry.second == "scratch_pool"; }); + if (pooled) { + std::size_t scan = cursor; + for (; scan < words.size() && (words[scan] & 0xffff) != spv::OpControlBarrier; scan += words[scan] >> 16) { + if ((words[scan] & 0xffff) == spv::OpLabel) current = words[scan + 1]; + } + require(scan < words.size(), "scratch pool slot acquisition has no workgroup barrier"); + cursor = scan + (words[scan] >> 16); + } for (std::size_t steps = 0; steps < 4096; ++steps) { const auto instruction = std::span(words).subspan(cursor, words[cursor] >> 16); const auto opcode = static_cast(instruction[0] & 0xffff); @@ -161,6 +174,7 @@ public: require((pointer[0] & 0xffff) == spv::OpAccessChain, "guest read is not an indexed access"); reads.push_back(value(pointer.back())); if (rootName(instruction[3]) == "scratch_dwords") require(pointerStorage(instruction[3]) == spv::StorageClassFunction, "scratch selection accessed non-private storage"); + if (rootName(instruction[3]) == "scratch_pool") require(pointerStorage(instruction[3]) == spv::StorageClassStorageBuffer, "scratch pool accessed outside a storage buffer"); if (rootName(instruction[3]).starts_with("buffers")) require(instruction.size() == 5 && (instruction[4] & spv::MemoryAccessVolatileMask), "coherent buffer read lost its volatile access"); } if (opcode == spv::OpBranch || opcode == spv::OpBranchConditional) { @@ -200,7 +214,7 @@ private: bool isGuestPointer(std::uint32_t id) const { const auto name = rootName(id); - return name == "lds_dwords" || name == "gds" || name == "scratch_dwords" || name.starts_with("buffers"); + return name == "lds_dwords" || name == "gds" || name == "scratch_dwords" || name == "scratch_pool" || name.starts_with("buffers"); } std::uint32_t value(std::uint32_t id) const { @@ -247,10 +261,12 @@ private: case spv::OpLogicalNot: return !argument(3); case spv::OpArrayLength: require(isGuestPointer(instruction[3]), "unknown array length in access fixture"); - return 16; + return rootName(instruction[3]) == "scratch_pool" ? PoolDwords : 16; + case spv::OpUDiv: return argument(3) / argument(4); + case spv::OpAtomicIDecrement: return 0; case spv::OpLoad: require(!isGuestPointer(instruction[3]), "guest data unexpectedly controls a fixture access"); - require(rootName(instruction[3]) == "vsharp" || rootName(instruction[3]).find("subgroup") != std::string::npos, "unknown input to access condition"); + require(rootName(instruction[3]) == "vsharp" || rootName(instruction[3]) == "scratch_pool_slot" || rootName(instruction[3]) == "gl_LocalInvocationIndex" || rootName(instruction[3]).find("subgroup") != std::string::npos, "unknown input to access condition"); return 0; default: throw std::runtime_error("unsupported opcode in access condition: " + std::to_string(opcode)); } @@ -461,9 +477,9 @@ int main() { run("private scratch selection", [] { const LoadCase test{ResourceKind::Scratch, IrOpcode::LoadAddressU32, IrType::U32}; for (const bool robust : {false, true}) { - expectReads(test, robust, false, 8, {0}); - expectReads(test, robust, true, 64, {0}); - expectReads(test, robust, true, 8, {2}); + expectReads(test, robust, false, 8, {PoolHeader}); + expectReads(test, robust, true, 64, {PoolHeader}); + expectReads(test, robust, true, 8, {PoolHeader + 2u}); } }); if (failures != 0) { diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp index fd1412e0fd..1820f092e5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestAllocations.cpp @@ -417,3 +417,94 @@ void RunVanishedPageReadTests() { GuestArena::GuestArenaRelease_nid_postfix(block, 2 * page); #endif } + +void RunPageBlockTests() { +#if defined(__linux__) + namespace GuestMemory = AgcDriver::GuestMemory; + using Ranges = std::vector>; + if (!GuestArena::GuestArenaAvailable_nid_postfix()) return; + const auto page = static_cast(sysconf(_SC_PAGESIZE)); + if (page != 4096) return; + constexpr std::size_t count = 1536; + const auto bytes = count * page; + auto* guest = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(bytes, 512 * page)); + Require(guest != nullptr && mmap(guest, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == guest, "cannot map the page block test pages"); + const auto base = reinterpret_cast(guest); + { + GuestAllocations::Mutation mutation; + mutation.Add(guest, bytes, true, true, true); + } + const auto protect = [&](std::size_t index, bool writable) { + GuestAllocations::Mutation mutation; + mutation.Protect(guest + index * page, page, true, writable, true, [&] { Require(mprotect(guest + index * page, page, writable ? PROT_READ | PROT_WRITE : PROT_READ) == 0, "cannot change a page block test page"); }); + }; + Require(GuestMemory::Accessible(guest, bytes, true), "mapped arena pages are not writable"); + Require(mprotect(guest + 700 * page, page, PROT_READ) == 0, "cannot protect a page block test page behind the registry"); + Require(GuestMemory::Accessible(guest, bytes, true), "a long run of checked arena pages was queried again instead of read from the page table"); + Require(mprotect(guest + 700 * page, page, PROT_READ | PROT_WRITE) == 0, "cannot restore a page block test page behind the registry"); + for (const std::size_t index : {std::size_t{700}, std::size_t{512}, std::size_t{1535}}) { + protect(index, false); + Require(!GuestMemory::Accessible(guest, bytes, true) && GuestMemory::Accessible(guest, bytes), "a page protected inside a long run of cached pages kept its old access"); + Ranges expected{{base, base + index * page}}; + if (index + 1 < count) expected.emplace_back(base + (index + 1) * page, base + bytes); + Require(GuestMemory::CommittedRanges(base, bytes, true) == expected, "the writable pages around a protected page inside a long run were misdescribed"); + Require(GuestMemory::CommittedRanges(base, bytes) == Ranges{{base, base + bytes}}, "the readable pages of a long run with one read-only page were misdescribed"); + protect(index, true); + Require(GuestMemory::DescribeCommitted(base, bytes, true).whole, "a long run of pages made writable again was not described whole"); + } + std::atomic done{false}; + std::atomic described{0}; + std::vector readers; + for (int reader = 0; reader < 2; ++reader) { + readers.emplace_back([&] { + while (!done.load(std::memory_order_relaxed)) { + static_cast(GuestMemory::DescribeCommitted(base, bytes, true)); + described.fetch_add(1, std::memory_order_relaxed); + } + }); + } + for (std::size_t round = 0; round < 2000 || described.load(std::memory_order_relaxed) < 2000; ++round) { + const auto index = 640 + round % 128; + protect(index, false); + protect(index, true); + } + protect(700, false); + done = true; + for (auto& reader : readers) reader.join(); + Require(GuestMemory::CommittedRanges(base, bytes, true) == Ranges{{base, base + 700 * page}, {base + 701 * page, base + bytes}}, "a page protected while other threads described its run is still reported writable"); + protect(700, true); + for (std::size_t round = 0; round < 256; ++round) { + const auto index = (1 + round % 22) * 64 + (round * 37) % 64; + Require(GuestMemory::DescribeCommitted(base, bytes, true).whole, "the pages of a block were not described whole before one of them flipped"); + std::atomic stop{false}; + std::array, 2> scans{}; + std::vector scanners; + for (auto& scanned : scans) { + scanners.emplace_back([&] { + while (!stop.load(std::memory_order_relaxed)) { + static_cast(GuestMemory::DescribeCommitted(base, bytes, true)); + scanned.fetch_add(1, std::memory_order_release); + } + }); + } + for (const auto& scanned : scans) { + while (scanned.load(std::memory_order_acquire) == 0) std::this_thread::yield(); + } + protect(index, false); + for (const auto& scanned : scans) { + const auto flipped = scanned.load(std::memory_order_acquire); + while (scanned.load(std::memory_order_acquire) < flipped + 2) std::this_thread::yield(); + } + stop = true; + for (auto& scanner : scanners) scanner.join(); + Require(GuestMemory::CommittedRanges(base, bytes, true) == Ranges{{base, base + index * page}, {base + (index + 1) * page, base + bytes}}, "a page flipped in a marked block while other threads ran over the block is still reported writable"); + protect(index, true); + } + { + GuestAllocations::Mutation mutation; + mutation.Unmap(guest, bytes, [](const void* pointer, std::size_t length, const void*, bool) { Require(munmap(const_cast(pointer), length) == 0, "cannot unmap the page block test pages"); }); + } + Require(!GuestMemory::Accessible(guest, page) && GuestMemory::CommittedRanges(base, bytes).empty(), "unmapped pages of a long run are still accessible"); + GuestArena::GuestArenaRelease_nid_postfix(guest, bytes); +#endif +} diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp index 88a51361de..5a37fd3fc5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestSamplerResource.cpp @@ -428,22 +428,20 @@ void RunGuestSamplerResourceTests() { unnormalizedAniso.xyMagFilter = 2; unnormalizedAniso.xyMinFilter = 2; rejectUnnormalized(unnormalizedAniso, "unnormalized coordinates with anisotropic filtering"); - for (const std::uint32_t clamp : {1u, 3u, 4u, 5u, 7u}) { - Fields clampX = unnormalizedBase; - clampX.clampX = clamp; - rejectUnnormalized(clampX, "unnormalized coordinates with clamp mode " + std::to_string(clamp) + " on X"); - } - for (const std::uint32_t clamp : {1u, 3u, 4u, 5u, 7u}) { - Fields clampY = unnormalizedBase; - clampY.clampY = clamp; - rejectUnnormalized(clampY, "unnormalized coordinates with clamp mode " + std::to_string(clamp) + " on Y"); - } Fields clampZero = unnormalizedBase; clampZero.clampX = 0; clampZero.clampY = 0; const auto zeroDecoded = DecodeSamplerResource(pack(clampZero), true); requireUnnormalized(zeroDecoded, "unnormalized S# with clamp mode 0"); Require(zeroDecoded.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && zeroDecoded.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "unnormalized clamp mode 0 must decode to clamp-to-edge"); + for (const std::uint32_t clamp : {1u, 3u}) { + Fields mirrored = unnormalizedBase; + mirrored.clampX = clamp; + mirrored.clampY = clamp; + const auto decoded = DecodeSamplerResource(pack(mirrored), true); + requireUnnormalized(decoded, clamp == 1u ? "unnormalized S# with clamp mode 1" : "unnormalized S# with clamp mode 3"); + Require(decoded.addressModeU == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE && decoded.addressModeV == VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, "unnormalized clamp mode " + std::to_string(clamp) + " must decode to clamp-to-edge after the shader mirrors the coordinate"); + } Fields unnormalizedBiased = unnormalizedBase; unnormalizedBiased.lodBiasRaw = 256; unnormalizedBiased.lodBiasSec = 8; diff --git a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp index 22eb2c0eb2..2854acd563 100644 --- a/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/GuestTextureResource.cpp @@ -155,6 +155,26 @@ void RunGuestTextureResourceTests() { Require(IsNullTextureResource(pack(nullType)), "a type-1 texture with a nonzero base was not classified as null"); nullType.typeRaw = 2; Require(!IsNullTextureResource(pack(nullType)), "a reserved type-2 texture was classified as null"); + const std::array reportedStorage{0x04efd000u, 0x00040011u, 0x00017a00u, 0x00005204u, 0x0af00000u, 0u, 0x00000612u, 0x00fff000u}; + Require(IsNullTextureResource(reportedStorage), "the type-0 words of a written storage slot were not classified as null"); + reject([&] { static_cast(DecodeTextureResource(reportedStorage)); }, "guest texture descriptor"); + Fields unsetFormat = base; + unsetFormat.format = 0u; + unsetFormat.typeRaw = 12u; + Require(!IsNullTextureResource(pack(unsetFormat)), "a zero-format texture was classified as null for every consumer"); + Require(IsNullSampledTextureResource(pack(unsetFormat)), "a zero-format image-type texture was not classified as null for a sampled consumer"); + reject([] { ResolveTextureFormat(0u); }, "unsupported guest texture format"); + unsetFormat.format = 0x1ffu; + Require(!IsNullTextureResource(pack(unsetFormat)), "an all-ones-format texture was classified as null for every consumer"); + Require(IsNullSampledTextureResource(pack(unsetFormat)), "an all-ones-format image-type texture was not classified as null for a sampled consumer"); + reject([] { ResolveTextureFormat(0x1ffu); }, "unsupported guest texture format"); + unsetFormat.typeRaw = 2u; + unsetFormat.format = 0u; + Require(!IsNullSampledTextureResource(pack(unsetFormat)), "a reserved type-2 texture with a zero format was classified as null for a sampled consumer"); + unsetFormat.format = 0x1ffu; + Require(!IsNullSampledTextureResource(pack(unsetFormat)), "a reserved type-2 texture with an all-ones format was classified as null for a sampled consumer"); + unsetFormat.typeRaw = 15u; + Require(!IsNullSampledTextureResource(pack(unsetFormat)), "a type-15 texture with an unset format was classified as null for a sampled consumer"); auto result = DecodeTextureResource(pack(base)); Require(result.baseAddress == (base.base40 << 8u), "decoded base address changed"); Require(result.width == 16 && result.height == 16, "decoded width or height changed"); @@ -286,7 +306,8 @@ void RunGuestTextureResourceTests() { Fields twoD = base; twoD.depth = 1; - rejectFields(twoD, "custom pitch other than its default pitch"); + result = DecodeTextureResource(pack(twoD)); + Require(result.depthOrLastArray == 0 && result.linearPitch == 0, "linear 2D descriptor with a pitch below its width did not keep the default pitch"); twoD.tileModeRaw = 0x09; rejectFields(twoD, "nonzero depth or base array"); Fields pitched = base; @@ -294,18 +315,35 @@ void RunGuestTextureResourceTests() { pitched.height = 72; pitched.depth = 127; result = DecodeTextureResource(pack(pitched)); - Require(result.dimension == TextureDimension::k2D && result.width == 72 && result.depthOrLastArray == 0, "linear 2D descriptor with its default pitch in DEPTH decoded incorrectly"); + Require(result.dimension == TextureDimension::k2D && result.width == 72 && result.depthOrLastArray == 0 && result.linearPitch == 0, "linear 2D descriptor with its default pitch in DEPTH decoded incorrectly"); pitched.width = 8; pitched.height = 8; pitched.depth = 63; result = DecodeTextureResource(pack(pitched)); - Require(result.width == 8 && result.depthOrLastArray == 0, "linear 2D descriptor with a one-unit default pitch decoded incorrectly"); - pitched.depth = 127; - rejectFields(pitched, "custom pitch other than its default pitch"); + Require(result.width == 8 && result.depthOrLastArray == 0 && result.linearPitch == 0, "linear 2D descriptor with a one-unit default pitch decoded incorrectly"); + for (const auto& [depth, pitch] : std::array, 4>{{{64u, 128u}, {127u, 128u}, {128u, 192u}, {319u, 320u}}}) { + pitched.depth = depth; + result = DecodeTextureResource(pack(pitched)); + Require(result.width == 8 && result.depthOrLastArray == 0 && result.linearPitch == pitch, "linear 2D descriptor did not round its DEPTH pitch up to 256 bytes"); + const auto geometry = DescribeSurface(result); + Require(geometry.mips.size() == 1 && geometry.mips[0].pitchBytes == pitch * 4u && geometry.mips[0].blocksPerRow == pitch && geometry.guestBytes == pitch * 4u * 8u, "linear 2D surface with a custom pitch has the wrong layout"); + auto defaultPitch = result; + defaultPitch.linearPitch = 0; + static_cast(EncodeTextureResource(defaultPitch)); + } + Fields narrow = base; + narrow.format = 5; + narrow.width = 300; + narrow.depth = 511; + Require(DecodeTextureResource(pack(narrow)).linearPitch == 0, "linear 2D 8-bit descriptor with its default pitch in DEPTH set a custom pitch"); + narrow.depth = 767; + Require(DecodeTextureResource(pack(narrow)).linearPitch == 768, "linear 2D 8-bit descriptor did not keep its DEPTH pitch"); + narrow.depth = 768; + Require(DecodeTextureResource(pack(narrow)).linearPitch == 1024, "linear 2D 8-bit descriptor did not round its DEPTH pitch up to 256 bytes"); pitched.depth = 63; pitched.maxMip = 1; pitched.lastLevel = 1; - rejectFields(pitched, "custom pitch other than its default pitch"); + rejectFields(pitched, "pitch on a block-compressed, mipmapped or base-array surface"); twoD = base; twoD.baseArray = 1; rejectFields(twoD, "nonzero depth or base array"); diff --git a/core/libs/prx/libSceAgcDriver/tests/HostImportLifetime.cpp b/core/libs/prx/libSceAgcDriver/tests/HostImportLifetime.cpp index 603d4bb0a2..cdd0c44f52 100644 --- a/core/libs/prx/libSceAgcDriver/tests/HostImportLifetime.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/HostImportLifetime.cpp @@ -206,11 +206,16 @@ public: tracking = nullptr; } - std::shared_ptr Import(std::uint64_t address, std::size_t bytes) { + std::shared_ptr TryImport(std::uint64_t address, std::size_t bytes) { std::lock_guard gpu(GpuMutex()); const auto* imported = HostImportFor(context, address, bytes); + return imported != nullptr ? calls.Add(*imported) : nullptr; + } + + std::shared_ptr Import(std::uint64_t address, std::size_t bytes) { + auto imported = TryImport(address, bytes); Require(imported != nullptr, "host pointer import was refused for the lifetime test"); - return calls.Add(*imported); + return imported; } void Cleanup() { @@ -701,6 +706,7 @@ struct MutationRace { Allocation* allocation; std::shared_ptr replacement; unsigned calls = 0; + bool refused = false; }; MutationRace* mutationRace = nullptr; @@ -710,7 +716,10 @@ bool raceWaiter(std::uintptr_t address, std::size_t bytes) { Require(address == race.allocation->Address() && bytes == race.allocation->bytes, "pin waiter received the wrong mutation extent"); std::lock_guard gpu(GpuMutex()); race.fixture->Cleanup(); - if (++race.calls == 1) race.replacement = race.fixture->Import(address, bytes); + if (++race.calls == 1) { + race.replacement = race.fixture->TryImport(address, bytes); + race.refused = race.replacement == nullptr; + } return true; } @@ -749,7 +758,7 @@ void waiterFailure(Fixture& fixture) { Require(!imported->freed.load(), "a failed pin waiter retired an import"); } -void reimportBeforeRelock(Fixture& fixture) { +void reimportRefusedWhileWaiting(Fixture& fixture) { Allocation allocation(fixture); const auto imported = fixture.Import(allocation.Address(), allocation.bytes); MutationRace race{&fixture, &allocation}; @@ -758,9 +767,8 @@ void reimportBeforeRelock(Fixture& fixture) { { GuestAllocations::Mutation mutation; mutation.Unmap(allocation.data, allocation.bytes, [&](const void*, std::size_t, const void*, bool) { - Require(race.calls == 2 && race.replacement != nullptr, "guest mutation did not recheck an import acquired before registry relock"); + Require(race.refused && race.calls == 1 && race.replacement == nullptr, "an import of an extent a guest mutation waits on was not refused"); requireFreed(imported); - requireFreed(race.replacement); allocation.Release(); }); allocation.registered = false; @@ -801,7 +809,7 @@ int RunHostImportMutationRaceTests(const Context& context) { } Fixture fixture(context); waiterFailure(fixture); - reimportBeforeRelock(fixture); + reimportRefusedWhileWaiting(fixture); std::puts("host import mutation race tests passed"); return 0; } diff --git a/core/libs/prx/libSceAgcDriver/tests/HostImportSparseDmaBuf.cpp b/core/libs/prx/libSceAgcDriver/tests/HostImportSparseDmaBuf.cpp index 6f127df513..fbdd762285 100644 --- a/core/libs/prx/libSceAgcDriver/tests/HostImportSparseDmaBuf.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/HostImportSparseDmaBuf.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "AllocationFailure.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libc/include/GuestAllocations.hpp" #ifndef _WIN32 @@ -7,6 +8,8 @@ #include #include #endif +#include +#include #include #include #include @@ -14,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -203,12 +207,20 @@ struct Backing { }; std::vector backings; +// The descriptor the resolver last handed out: the import owns it, so after the import it is closed. +int lastBackingFile = -1; +bool failAfterRunsResolved = false; + +void ExpectBackingClosed(const char* what) { + Expect(lastBackingFile >= 0 && fcntl(lastBackingFile, F_GETFD) == -1 && errno == EBADF, what); +} bool Resolve(std::uintptr_t address, std::size_t bytes, int* file, std::uint64_t* offset) { for (const auto* backing : backings) { const auto begin = reinterpret_cast(backing->base); if (address < begin || address + bytes > begin + backing->bytes) continue; *file = dup(backing->file); + lastBackingFile = *file; *offset = backing->fileOffset + (address - begin); return *file >= 0; } @@ -274,6 +286,7 @@ void SettlingSurvivesDeviceReplacement(bool reverse) { std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); for (const auto& context : {first, second, first}) { const auto* imported = HostImportFor(context, range.Address(), 4096); + ExpectBackingClosed("a chunk import retained the backing descriptor"); Expect(imported != nullptr && imported->dmaBuf, "replacement device did not settle its dma-buf import"); } ClearHostImports(reverse ? second.device : first.device); @@ -294,6 +307,7 @@ void ImportsALargeRangeAsOneSparseBuffer() { mock.binds.clear(); std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); const auto* import = HostImportFor(context, range.Address(), static_cast(bytes)); + ExpectBackingClosed("a sparse import retained the backing descriptor"); Expect(import != nullptr, "a shared range larger than udmabuf's size limit was not imported"); if (import == nullptr) return; Expect(import->base == range.Address() && import->bytes == bytes && import->dmaBuf, "the import does not cover the whole range as a dma-buf import"); @@ -311,6 +325,144 @@ void ImportsALargeRangeAsOneSparseBuffer() { Expect(memories.size() == chunks, "two parts of the range are bound to the same chunk import"); } +struct ScatteredRange { + int file = -1; + std::uint8_t* base = nullptr; + void* reservation = nullptr; + std::uint64_t reserved = 0; + std::vector runs; + + ScatteredRange(std::vector pieces) : runs(std::move(pieces)) { + std::uint64_t bytes = 0; + std::uint64_t fileEnd = 0; + for (const auto& run : runs) { + bytes += run.bytes; + fileEnd = std::max(fileEnd, run.offset + run.bytes); + } + file = memfd_create("anyps5-sparse-runs-mock", MFD_CLOEXEC | MFD_ALLOW_SEALING); + const auto fileBytes = (fileEnd + ChunkBytes - 1) / ChunkBytes * ChunkBytes; + if (file < 0 || ftruncate(file, static_cast(fileBytes)) != 0 || fcntl(file, F_ADD_SEALS, F_SEAL_SHRINK) != 0) throw std::runtime_error("cannot make the scattered backing file"); + reserved = bytes + Alignment; + reservation = mmap(nullptr, reserved, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (reservation == MAP_FAILED) throw std::runtime_error("cannot reserve the scattered guest range"); + auto cursor = (reinterpret_cast(reservation) + Alignment - 1) / Alignment * Alignment; + base = reinterpret_cast(cursor); + for (const auto& run : runs) { + if (mmap(reinterpret_cast(cursor), run.bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, file, static_cast(run.offset)) == MAP_FAILED) throw std::runtime_error("cannot map a scattered run"); + cursor += run.bytes; + } + GuestAllocations::Mutation mutation; + mutation.Add(base, static_cast(bytes), true, true, true); + } + + std::uint64_t Bytes() const { + std::uint64_t bytes = 0; + for (const auto& run : runs) bytes += run.bytes; + return bytes; + } +}; + +std::vector scattered; + +bool ResolveRuns(std::uintptr_t address, std::size_t bytes, int* file, GuestArena::SharedBackingRun* runs, std::size_t capacity, std::size_t* count) { + *count = 0; + for (const auto* range : scattered) { + const auto begin = reinterpret_cast(range->base); + if (address < begin || address + bytes > begin + range->Bytes()) continue; + auto cursor = begin; + for (const auto& run : range->runs) { + const auto first = std::max(cursor, address); + const auto last = std::min(cursor + run.bytes, address + bytes); + if (first < last) { + if (*count == capacity) return false; + runs[(*count)++] = GuestArena::SharedBackingRun{run.offset + (first - cursor), static_cast(last - first)}; + } + cursor += run.bytes; + } + *file = dup(range->file); + lastBackingFile = *file; + if (failAfterRunsResolved && *file >= 0) { + failAfterRunsResolved = false; + AgcDriver::Tests::SetAllocationFailureCountdown(0); + } + return *file >= 0; + } + std::uint64_t offset = 0; + if (!Resolve(address, bytes, file, &offset)) return false; + runs[0] = GuestArena::SharedBackingRun{offset, bytes}; + *count = 1; + return true; +} + +void ScatteredRunsAboveTheLimitBindTheirChunks() { + const auto total = RangeBytes(); + const auto first = total / 2 / Alignment * Alignment; + const auto second = total - first; + const std::uint64_t firstOffset = 3 * ChunkBytes + 3 * Alignment; + const std::uint64_t secondOffset = 5 * Alignment; + ScatteredRange range({{firstOffset, static_cast(first)}, {secondOffset, static_cast(second)}}); + scattered.push_back(&range); + const auto context = mockContext(); + mock.binds.clear(); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto* import = HostImportFor(context, reinterpret_cast(range.base), static_cast(total)); + ExpectBackingClosed("a scattered sparse import retained the backing descriptor"); + Expect(import != nullptr, "a range of two scattered runs larger than udmabuf's size limit was not imported"); + if (import != nullptr) { + Expect(import->dmaBuf && mock.sparse.contains(import->buffer) && import->lead == 0, "the scattered range was not imported as one sparse buffer"); + VkDeviceSize resource = 0; + bool mapped = true; + for (const auto& bind : mock.binds) { + const auto file = resource < first ? firstOffset + resource : secondOffset + (resource - first); + mapped = mapped && bind.resourceOffset == resource && bind.memoryOffset == file % ChunkBytes && bind.memoryOffset + bind.size <= ChunkBytes && (resource >= first || resource + bind.size <= first); + resource += bind.size; + } + Expect(mapped, "a bind does not map the chunk of its run's file offset to its place in the range"); + Expect(resource == total, "the binds do not cover the scattered range exactly"); + } + std::erase(scattered, &range); +} + +void RunListAllocationFailureClosesTheBacking() { + ScatteredRange range({{0, Alignment}, {2 * Alignment, Alignment}}); + scattered.push_back(&range); + auto context = mockContext(); + context.sparseBinding = false; + failAfterRunsResolved = true; + bool threw = false; + try { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + HostImportFor(context, reinterpret_cast(range.base), 2 * Alignment); + } catch (const std::bad_alloc&) { + threw = true; + } + AgcDriver::Tests::SetAllocationFailureCountdown(-1); + Expect(threw && !failAfterRunsResolved, "the run-list allocation failure was not injected"); + failAfterRunsResolved = false; + ExpectBackingClosed("the run-list allocation failure retained the backing descriptor"); + if (lastBackingFile >= 0 && fcntl(lastBackingFile, F_GETFD) >= 0) close(lastBackingFile); + std::erase(scattered, &range); +} + +void UnalignedInteriorRunKeepsTheFallback() { + const auto total = RangeBytes(); + const auto first = (total / 2 / (2 * Alignment)) * (2 * Alignment) + Alignment; + const auto second = total - first; + ScatteredRange range({{3 * ChunkBytes, static_cast(first)}, {0, static_cast(second)}}); + scattered.push_back(&range); + const auto context = mockContext(); + mock.binds.clear(); + mock.sparseAlignment = 2 * Alignment; + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto* import = HostImportFor(context, reinterpret_cast(range.base), static_cast(total)); + Expect(import == nullptr || !mock.sparse.contains(import->buffer), "a run list whose interior run is off the sparse block size took the sparse path"); + Expect(mock.binds.empty(), "a chunk was bound for a run list that does not take the sparse path"); + } + mock.sparseAlignment = Alignment; + std::erase(scattered, &range); +} + void UnalignedOffsetStartsAtTheBlockBelow() { const auto bytes = RangeBytes(); constexpr std::uint64_t lead = 4096; @@ -386,6 +538,7 @@ void FailureAfterTheDecisionThrows(bool sealed, VkResult bind, const char* step) thrown = error.what(); } mock.bindResult = VK_SUCCESS; + ExpectBackingClosed("a throwing sparse import retained the backing descriptor"); Expect(thrown.find("sparse dma-buf import") != std::string::npos && thrown.find(step) != std::string::npos, std::string("a failure at ") + step + " after the sparse path was chosen did not throw (got \"" + thrown + "\")"); Expect(mock.buffers.size() == live, std::string("the sparse buffer outlived the failure at ") + step); } @@ -406,12 +559,16 @@ int main() { setenv("APS5_WRITE_WATCH_IMPORTS", "unwatch", 1); SetHostImportBudget(~std::uint64_t{0}); GuestArena::GuestArenaSetSharedBacking_nid_postfix(Resolve); + GuestArena::GuestArenaSetSharedBackingRuns_nid_postfix(ResolveRuns); try { SettlingSurvivesDeviceReplacement(false); SettlingSurvivesDeviceReplacement(true); ImportsALargeRangeAsOneSparseBuffer(); UnalignedOffsetStartsAtTheBlockBelow(); LargerSparseBlockIsDecidedBeforeImporting(); + ScatteredRunsAboveTheLimitBindTheirChunks(); + RunListAllocationFailureClosesTheBacking(); + UnalignedInteriorRunKeepsTheFallback(); FailureAfterTheDecisionThrows(false, VK_SUCCESS, "UDMABUF_CREATE"); FailureAfterTheDecisionThrows(true, VK_ERROR_DEVICE_LOST, "vkQueueBindSparse"); RetainsFailedWindowAcrossUnrelatedMutation(); diff --git a/core/libs/prx/libSceAgcDriver/tests/KeyboardOverlay.cpp b/core/libs/prx/libSceAgcDriver/tests/KeyboardOverlay.cpp index ab213f5f9b..c698247b25 100644 --- a/core/libs/prx/libSceAgcDriver/tests/KeyboardOverlay.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/KeyboardOverlay.cpp @@ -296,6 +296,95 @@ void caretTests() { std::printf("caret: one bar per field, after its character, the text scrolled to keep it in the 44-character field\n"); } +ImeKeyboardView composing(const std::u16string& text, std::size_t caret, const std::u16string& preedit, std::size_t cursor, std::size_t selection) { + ImeKeyboardView view = viewOf(ImeDialog::PadKeyboardOpen({}), text, caret); + view.preedit = preedit; + view.preeditCursor = cursor; + view.preeditSelection = selection; + return view; +} + +bool sameRects(const std::vector& left, const std::vector& right) { + if (left.size() != right.size()) return false; + for (std::size_t i = 0; i < left.size(); ++i) { + if (left[i].x != right[i].x || left[i].y != right[i].y || left[i].width != right[i].width || left[i].height != right[i].height || left[i].color != right[i].color) return false; + } + return true; +} + +OverlayRect underline(const OverlayLayout& layout, std::size_t column, std::int32_t rows, OverlayColor color) { + const auto scale = layout.scale; + const auto x = KeyboardMetrics::Padding + KeyboardMetrics::FieldInset + static_cast(column) * KeyboardMetrics::Advance; + const auto y = KeyboardMetrics::Padding + KeyboardMetrics::FieldInset + KeyboardMetrics::GlyphHeight; + return {layout.bounds.x + x * scale, layout.bounds.y + y * scale, KeyboardMetrics::Advance * scale, rows * scale, colorOf(color)}; +} + +std::vector without(const std::vector& rects, const std::vector& removed) { + std::vector kept; + for (const auto& rect : rects) { + bool drop = false; + for (const auto& gone : removed) { + if (rect.x == gone.x && rect.y == gone.y && rect.width == gone.width && rect.height == gone.height && rect.color == gone.color) drop = true; + } + if (!drop) kept.push_back(rect); + } + return kept; +} + +void compositionTests() { + const VkExtent2D extent{1280, 720}; + const auto plain = AgcDriver::LayOutKeyboard(composing(u"ab", 1, u"", 0, 0), extent); + const auto ignored = AgcDriver::LayOutKeyboard(composing(u"ab", 1, u"", 3, 2), extent); + require(sameRects(plain.rects, ignored.rects), "a cursor and a selection with no composition changed the layout"); + const auto baseline = AgcDriver::LayOutKeyboard(viewOf(ImeDialog::PadKeyboardOpen({}), u"ab", 1), extent); + require(sameRects(plain.rects, baseline.rects), "a view with an empty composition does not lay out as before"); + + const auto inserted = AgcDriver::LayOutKeyboard(composing(u"ab", 1, u"X", 1, 0), extent); + const auto flat = AgcDriver::LayOutKeyboard(viewOf(ImeDialog::PadKeyboardOpen({}), u"aXb", 2), extent); + const auto extra = std::vector{underline(inserted, 1, 1, OverlayColor::Accent)}; + require(inserted.rects.size() == flat.rects.size() + 1, "a one-character composition adds " + std::to_string(inserted.rects.size() - flat.rects.size()) + " rectangles, not one underline"); + require(sameRects(without(inserted.rects, extra), flat.rects), "the composition is not drawn between the text before and after the caret, or its caret is not after it"); + + const auto selected = AgcDriver::LayOutKeyboard(composing(u"", 0, u"abc", 1, 1), extent); + const auto flatSelected = AgcDriver::LayOutKeyboard(viewOf(ImeDialog::PadKeyboardOpen({}), u"abc", 1), extent); + const std::vector marks{underline(selected, 0, 1, OverlayColor::Accent), underline(selected, 1, 2, OverlayColor::Text), underline(selected, 2, 1, OverlayColor::Accent)}; + require(sameRects(without(selected.rects, marks), flatSelected.rects), "the selected segment is not the second character underlined twice in the text colour"); + require(selected.rects.size() == flatSelected.rects.size() + 3, "a three-character composition does not add three underlines"); + + const auto pair = AgcDriver::LayOutKeyboard(composing(u"", 0, u"a\U0001F600b", 3, 0), extent); + const auto flatPair = AgcDriver::LayOutKeyboard(viewOf(ImeDialog::PadKeyboardOpen({}), u"a\U0001F600b", 3), extent); + const std::vector pairMarks{underline(pair, 0, 1, OverlayColor::Accent), underline(pair, 1, 1, OverlayColor::Accent), underline(pair, 2, 1, OverlayColor::Accent)}; + require(sameRects(without(pair.rects, pairMarks), flatPair.rects), "a surrogate pair in the composition is not one character"); + + require(!AgcDriver::KeyboardHasGlyph(0x3053), "the overlay font gained a hiragana glyph; the composition test below assumes none"); + const auto cjk = AgcDriver::LayOutKeyboard(composing(u"", 0, u"\u3053", 1, 0), extent); + const auto box = AgcDriver::LayOutKeyboard(viewOf(ImeDialog::PadKeyboardOpen({}), u"\uFFFD", 1), extent); + require(cjk.rects.size() == box.rects.size() + 1, "a character the font lacks does not draw as the replacement box"); + + std::u16string text(60, u'a'); + const auto scrolled = AgcDriver::LayOutKeyboard(composing(text, 60, u"xyz", 3, 0), extent); + const auto scale = scrolled.scale; + const OverlayRect field{scrolled.bounds.x + KeyboardMetrics::Padding * scale, scrolled.bounds.y + KeyboardMetrics::Padding * scale, (scrolled.bounds.width / scale - 2 * KeyboardMetrics::Padding) * scale, KeyboardMetrics::FieldHeight * scale, 0}; + for (const auto& rect : scrolled.rects) { + if (rect.color == colorOf(OverlayColor::Accent) && rect.width == scale && contains(field, rect)) { + const auto column = (rect.x + scale - field.x) / scale - KeyboardMetrics::FieldInset; + require(column == 44 * KeyboardMetrics::Advance, "a composition past the field does not keep the caret at the right edge"); + } + } + require(inside(scrolled, field, OverlayColor::Accent) == 1 + 3, "a long composition does not draw one caret and three underlines"); + + for (const auto& bad : {composing(u"ab", 1, u"x", 2, 0), composing(u"ab", 1, u"xy", 1, 2), composing(u"ab", 1, u"x", 0, 5)}) { + bool threw = false; + try { + static_cast(AgcDriver::LayOutKeyboard(bad, extent)); + } catch (const std::exception&) { + threw = true; + } + require(threw, "a composition cursor or selection past its text is laid out"); + } + std::printf("composition: laid out between the text around the caret, underlined, selection marked, cursor kept in view, bad offsets rejected\n"); +} + double linear(double encoded) { return encoded <= 0.04045 ? encoded / 12.92 : std::pow((encoded + 0.055) / 1.055, 2.4); } @@ -482,6 +571,7 @@ int main() { labelTests(); fallbackTests(); caretTests(); + compositionTests(); paletteTests(); systemOverlayTests(); dialogWrapTests(); diff --git a/core/libs/prx/libSceAgcDriver/tests/LdsLockBudget.cpp b/core/libs/prx/libSceAgcDriver/tests/LdsLockBudget.cpp index 55c6fc91b1..ed44ad0080 100644 --- a/core/libs/prx/libSceAgcDriver/tests/LdsLockBudget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/LdsLockBudget.cpp @@ -117,10 +117,55 @@ void verify(std::uint32_t waveSize, std::uint32_t limit, bool int64Atomics) { 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)); } +alignas(256) std::array Texture{}; + +void verifySpecializedImageWithoutBuffers() { + alignas(256) static constexpr std::array code{ + 0x7e020280u, 0x7e080218u, 0x343c0885u, 0x4a3c3d00u, 0x34063c84u, 0x2c3e3c87u, 0x363c3cffu, 0x0000007fu, + 0x7e140280u, 0x7e160280u, 0x7e180280u, 0x7e1a0280u, 0xf0001f08u, 0x00010a1eu, + 0xbf8c3f70u, 0xd8340000u, 0x00000a01u, 0xbf810000u + }; + static constexpr std::array capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityGroupNonUniformBallot, spv::CapabilityGroupNonUniformShuffle}; + const auto textureAddress = reinterpret_cast(Texture.data()); + std::array userData{}; + userData[4] = static_cast(textureAddress >> 8u); + userData[5] = static_cast((textureAddress >> 40u) & 0xffu) | (6u << 20u) | (3u << 30u); + userData[6] = 31u | (127u << 14u); + userData[7] = 0x24cu | (9u << 28u); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(std::span(code))}}}; + RecompileRequest request{}; + request.shader = {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}; + request.context.waveSize = 32u; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 16u, {true, false, false}, false, 1u}; + request.context.memory = memory; + request.target.vulkanVersion = 0x00403000u; + request.target.spirvVersion = 0x00010600u; + request.target.subgroupSize = 32; + request.target.supportedCapabilities = capabilities; + request.target.maxWorkgroupSize = {1024u, 1024u, 64u}; + request.target.maxWorkgroupInvocations = 1024; + request.target.maxWorkgroupSharedMemoryBytes = 64u; + request.layout = {0, 0, 0, 128}; + request.useCache = false; + const auto result = Recompile(request); + require(!result.spirv.empty(), "image program with device-memory LDS did not compile"); + require(result.workgroupMemoryDwords == 18u, "image program did not place its LDS in device memory"); + require(result.specializationId != 0u, "image program was not specialized"); +} + } int main() { unsigned failures = 0; + try { + verifySpecializedImageWithoutBuffers(); + std::cout << "LDS lock budget: specialized image program without buffers passed\n"; + } catch (const std::exception& error) { + ++failures; + const std::string message(error.what()); + std::cerr << "LDS lock budget: specialized image program without buffers: " << message.substr(0, message.find("RecompileRequest:")) << '\n'; + } for (const auto waveSize : {32u, 64u}) { for (const auto limit : {64u, 68u, 72u}) { for (const bool int64Atomics : {false, true}) { diff --git a/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp index ed1d9185b3..7fe2419b3e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/MaskedSelect.cpp @@ -1268,6 +1268,54 @@ int main() { passed &= run("an optional wave body may merge a lane's own bit of the predicate's ballot with the skipped predicate", [&] { return optionalPredicateBody(false); }); passed &= run("an optional wave body cannot merge a value that differs from the skipped predicate on inactive lanes", [&] { return !optionalPredicateBody(true); }); + + enum class BodyLanes { OwnBits, SameLanes, SameLanesCovered, OtherLanes }; + const auto optionalBallotWords = [&](bool keepsActive, BodyLanes shape = BodyLanes::OwnBits) { + Builder b; + b.program.Resources().stage = IrShaderStage::Pixel; + auto& entry = *b.block; + auto& predicate = pixelPredicate(b); + auto& real = helperPredicate(b, false); + auto& other = pixelPredicate(b); + auto [low, high] = b.ballotWords(real); + if (shape == BodyLanes::SameLanesCovered) { + const auto [everyLow, everyHigh] = b.ballotWords(b.truth()); + low = &b.emit(IrOpcode::BitwiseAnd32, IrType::U32, {low, everyLow}); + high = &b.emit(IrOpcode::BitwiseAnd32, IrType::U32, {everyHigh, high}); + } + auto& condition = b.emit(IrOpcode::LogicalNot, IrType::Bool, {&predicate}); + auto& body = b.newBlock(); + auto& merge = b.newBlock(); + entry.AddBranch(&body); + entry.AddBranch(&merge); + body.AddBranch(&merge); + auto& entryInfo = b.program.Metadata().blockInfo[0u]; + entryInfo.terminator.kind = TerminatorKind::ConditionalBranch; + entryInfo.terminator.condition = BranchCondition::ExecZero; + entryInfo.terminator.trueBlock = merge.Id(); + entryInfo.terminator.falseBlock = body.Id(); + entryInfo.condition = &condition; + auto& bodyInfo = b.program.Metadata().blockInfo[1u]; + bodyInfo.terminator.kind = TerminatorKind::Branch; + bodyInfo.terminator.trueBlock = merge.Id(); + b.block = &body; + auto& lanes = b.logicalAnd(shape == BodyLanes::OwnBits ? b.threadBit(*low, *high) : shape == BodyLanes::OtherLanes ? other : real, keepsActive ? predicate : b.emit(IrOpcode::LogicalNot, IrType::Bool, {&predicate})); + const auto [takenLow, takenHigh] = b.ballotWords(lanes); + b.block = &merge; + auto& mergedLow = b.phi(IrType::U32, entry, *low); + mergedLow.AddPhiOperand(&body, takenLow); + auto& mergedHigh = b.phi(IrType::U32, entry, *high); + mergedHigh.AddPhiOperand(&body, takenHigh); + exportUnder(b, b.threadBit(mergedLow, mergedHigh), true, true); + return StencilReplaySafe(b.program); + }; + + passed &= run("an optional wave body may merge a ballot of the skipped lanes' own mask bits with that mask", [&] { return optionalBallotWords(false); }); + passed &= run("an optional wave body cannot merge a ballot that clears inactive lanes' mask bits", [&] { return !optionalBallotWords(true); }); + passed &= run("an optional wave body may merge a ballot of the same lanes less the body's with the skipped ballot", [&] { return optionalBallotWords(false, BodyLanes::SameLanes); }); + passed &= run("an optional wave body may merge that ballot with the skipped ballot under a mask of every lane", [&] { return optionalBallotWords(false, BodyLanes::SameLanesCovered); }); + passed &= run("an optional wave body cannot merge a ballot of the same lanes and the body's with the skipped ballot", [&] { return !optionalBallotWords(true, BodyLanes::SameLanes); }); + passed &= run("an optional wave body cannot merge a ballot of other lanes with the skipped ballot", [&] { return !optionalBallotWords(false, BodyLanes::OtherLanes); }); passed &= run("an optional wave body preserves inactive values consumed only within a lane", [&] { return optionalPixelBody(false); }); passed &= run("an optional wave body cannot change helper coordinates for later implicit samples", [&] { return !optionalPixelBody(true); }); passed &= run("an optional wave body cannot change helper coordinates for LOD queries", [&] { return !optionalPixelBody(true, IrOpcode::ImageQueryLod); }); @@ -1364,6 +1412,56 @@ int main() { passed &= run("an optional wave body spanning several blocks preserves inactive values through its own phis", [&] { return optionalPixelRegion(false); }); passed &= run("an optional wave body spanning several blocks cannot write an inactive lane on any of its paths", [&] { return !optionalPixelRegion(true); }); + const auto optionalPixelExit = [&](bool exitKeepsLanes) { + Builder b; + b.program.Resources().stage = IrShaderStage::Pixel; + auto& entry = *b.block; + auto& predicate = pixelPredicate(b); + auto& zero = b.constant(0u); + auto& condition = b.emit(IrOpcode::LogicalNot, IrType::Bool, {&predicate}); + auto& body = b.newBlock(); + auto& exit = b.newBlock(); + auto& rest = b.newBlock(); + auto& merge = b.newBlock(); + entry.AddBranch(&body); + entry.AddBranch(&merge); + body.AddBranch(&exit); + body.AddBranch(&rest); + rest.AddBranch(&merge); + auto& entryInfo = b.program.Metadata().blockInfo[0u]; + entryInfo.terminator.kind = TerminatorKind::ConditionalBranch; + entryInfo.terminator.condition = BranchCondition::ExecZero; + entryInfo.terminator.trueBlock = merge.Id(); + entryInfo.terminator.falseBlock = body.Id(); + entryInfo.condition = &condition; + b.block = &body; + auto& uniformBit = b.emit(IrOpcode::INotEqual32, IrType::Bool, {&b.emit(IrOpcode::GetUserData, IrType::U32, {}), &zero}); + auto& bodyInfo = b.program.Metadata().blockInfo[1u]; + bodyInfo.terminator.kind = TerminatorKind::ConditionalBranch; + bodyInfo.terminator.condition = BranchCondition::SccNonZero; + bodyInfo.terminator.trueBlock = exit.Id(); + bodyInfo.terminator.falseBlock = rest.Id(); + bodyInfo.condition = &uniformBit; + b.program.Metadata().blockInfo[2u].terminator.kind = TerminatorKind::Return; + b.program.Metadata().blockInfo[3u].terminator.kind = TerminatorKind::Branch; + b.program.Metadata().blockInfo[3u].terminator.trueBlock = merge.Id(); + b.block = &exit; + auto& keepsNone = b.program.CreateValue(IrOpcode::Void, IrType::Bool); + keepsNone.SetImmediateBool(false); + exportUnder(b, exitKeepsLanes ? b.truth() : keepsNone, true, true); + b.block = &rest; + auto& restValue = b.select(predicate, b.constant(42u), zero); + b.block = &merge; + auto& merged = b.phi(IrType::U32, entry, zero); + merged.AddPhiOperand(&rest, &restValue); + b.keep(merged); + exportUnder(b, b.truth(), true, true); + return StencilReplaySafe(b.program); + }; + + passed &= run("an optional wave body may end the wave through an exit that keeps no lane", [&] { return optionalPixelExit(false); }); + passed &= run("an optional wave body cannot end the wave through an exit that keeps lanes", [&] { return !optionalPixelExit(true); }); + const auto nestedOptionalGuard = [&](bool unsafeInner, bool ballotEscape) { Builder b; b.program.Resources().stage = IrShaderStage::Pixel; @@ -1737,5 +1835,45 @@ int main() { return small.removedSelects == 500u && smallKept == 500u && large.removedSelects == 4000u && largeKept == 4000u && small.steps != 0u && large.steps <= 9u * small.steps && large.steps < 90'000u; }); + passed &= run("layer replay permits local LDS but rejects GDS access", [] { + Builder b; + b.program.Resources().stage = IrShaderStage::Mesh; + MemoryInfo memory{}; + memory.kind = ResourceKind::Lds; + b.program.Resources().memoryInfo.push_back(memory); + b.emit(IrOpcode::StoreShared, IrType::Void, {&b.constant(0u), &b.constant(1u)}); + if (!LayerReplaySafe(b.program)) return false; + b.program.Resources().memoryInfo.front().kind = ResourceKind::Gds; + return !LayerReplaySafe(b.program); + }); + + passed &= run("layer replay distinguishes read-only address access from address writes", [] { + Builder b; + b.program.Info().usesDma = true; + auto& address = b.emit(IrOpcode::GetAddressResource, IrType::AddressResource, {}); + auto& offset = b.constant(0u); + auto& active = b.truth(); + b.emit(IrOpcode::LoadAddressU32, IrType::U32, {&address, &offset, &offset, &active}); + if (!LayerReplaySafe(b.program)) return false; + b.program.Info().bdaWrites = true; + if (LayerReplaySafe(b.program)) return false; + b.program.Info().bdaWrites = false; + b.emit(IrOpcode::StoreAddressU32, IrType::Void, {&address, &offset, &offset, &b.constant(1u), &active}); + return !LayerReplaySafe(b.program); + }); + + passed &= run("layer replay rejects guest buffer stores and shader clocks", [] { + Builder written; + auto& buffer = written.emit(IrOpcode::GetBufferResource, IrType::BufferResource, {}); + written.emit(IrOpcode::StoreBuffer, IrType::Void, {&buffer, &written.constant(0u), &written.constant(1u)}); + if (LayerReplaySafe(written.program)) return false; + for (const auto opcode : {IrOpcode::ShaderClock, IrOpcode::RealtimeClock}) { + Builder clock; + clock.emit(opcode, IrType::U64, {}); + if (LayerReplaySafe(clock.program)) return false; + } + return true; + }); + return passed ? 0 : 1; } diff --git a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp index ce6f2d65f3..e952346650 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Mesh.cpp @@ -92,6 +92,8 @@ std::size_t CellPixel(std::uint32_t triangle) { alignas(256) std::array Scrambled{}; alignas(256) std::array Indices{}; alignas(256) std::array Ordered{}; +constexpr std::uint32_t WideFastLaunchSkipped = 65535; +alignas(256) std::array WideFastLaunch{}; alignas(256) std::array Strip{}; constexpr std::uint32_t LineStrips = 4; @@ -383,6 +385,10 @@ int main() { const bool drawn = triangle >= Triangles / 4u && triangle < Triangles / 4u + Triangles / 2u; Require(PixelIs(CellPixel(triangle), drawn ? TriangleColor(triangle) : Background), "fast-launch subgroups from a start vertex: triangle " + std::to_string(triangle) + " pixel " + PixelText(CellPixel(triangle))); } + std::copy(Ordered.begin(), Ordered.end(), WideFastLaunch.begin() + 3u * WideFastLaunchSkipped); + ClearPixels(); + DrawMesh(device, {FastLaunchSubgroup, {0, WideFastLaunchSkipped + Triangles, 0, 1, 0, false}, VertexBufferDescriptor(WideFastLaunch.data(), static_cast(WideFastLaunch.size())), ShaderRecompiler::MeshDrawPushOffsetBytes, false, 32u, VK_CULL_MODE_NONE, FastLaunchCode, 0x482030u}); + CheckTriangles("fast-launch draw of 65,575 one-primitive groups"); bool fastLaunchIndexedRefused = false; try { DrawMesh(device, {FastLaunchSubgroup, {reinterpret_cast(Indices.data()), Triangles, 2, 1, 0, true}, VertexBufferDescriptor(Ordered.data(), static_cast(Ordered.size())), ShaderRecompiler::MeshDrawPushOffsetBytes, false, 32u, VK_CULL_MODE_NONE, FastLaunchCode, 0x482030u}); diff --git a/core/libs/prx/libSceAgcDriver/tests/OracleCodeObject.cpp b/core/libs/prx/libSceAgcDriver/tests/OracleCodeObject.cpp new file mode 100644 index 0000000000..d0e0d05bb7 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/OracleCodeObject.cpp @@ -0,0 +1,206 @@ +#include "../tools/OracleCodeObject.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::array Text{ + 0xf4080100, 0xfa000000, 0x34020084, 0x34040086, 0xbf8cc07f, 0xdc388000, 0x04040001, 0xbf8c3f70, + 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, + 0x7e240280, 0x7e260280, 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, + 0x64140b04, 0xdc788000, 0x00060a02, 0xdc788010, 0x00060e02, 0xdc788020, 0x00061202, 0xdc788030, + 0x00061602, 0xbf810000, +}; + +struct Spec { + std::uint16_t machine = 224; + bool probe = true; + bool descriptor = true; + std::uint64_t descriptorSize = 64; + std::uint64_t probeSize = Text.size() * 4; + std::uint64_t textSectionSize = Text.size() * 4; + std::uint32_t group = 4096; + std::uint32_t privateBytes = 0; + std::uint32_t kernarg = 16; + std::int64_t entry = 4288; + std::uint32_t rsrc1 = 0x642ed00f; + std::uint32_t rsrc2 = 0x84; + std::uint16_t properties = 0x0408; +}; + +constexpr std::uint64_t RodataAddress = 0x1000; +constexpr std::uint64_t TextAddress = RodataAddress + 4288; + +template +void Put(std::vector& bytes, std::size_t offset, T value) { + if (bytes.size() < offset + sizeof(T)) bytes.resize(offset + sizeof(T)); + std::memcpy(bytes.data() + offset, &value, sizeof(T)); +} + +std::size_t AddString(std::vector& table, const std::string& text) { + const auto offset = table.size(); + table.insert(table.end(), text.begin(), text.end()); + table.push_back('\0'); + return offset; +} + +std::vector Build(const Spec& spec) { + std::vector bytes(64); + const auto append = [&](const void* data, std::size_t size) { + const auto offset = bytes.size(); + bytes.resize(offset + size); + if (size != 0) std::memcpy(bytes.data() + offset, data, size); + return offset; + }; + const auto textOffset = append(Text.data(), Text.size() * 4); + std::vector descriptor(64); + Put(descriptor, 0, spec.group); + Put(descriptor, 4, spec.privateBytes); + Put(descriptor, 8, spec.kernarg); + Put(descriptor, 16, spec.entry); + Put(descriptor, 48, spec.rsrc1); + Put(descriptor, 52, spec.rsrc2); + Put(descriptor, 56, spec.properties); + const auto rodataOffset = append(descriptor.data(), descriptor.size()); + + std::vector strings{'\0'}; + std::vector symbols(24); + const auto addSymbol = [&](const std::string& name, std::uint8_t type, std::uint16_t section, std::uint64_t value, std::uint64_t size) { + const auto at = symbols.size(); + symbols.resize(at + 24); + Put(symbols, at, static_cast(AddString(strings, name))); + Put(symbols, at + 4, static_cast(0x10u | type)); + Put(symbols, at + 6, section); + Put(symbols, at + 8, value); + Put(symbols, at + 16, size); + }; + if (spec.probe) addSymbol("probe", 2, 1, TextAddress, spec.probeSize); + if (spec.descriptor) addSymbol("probe.kd", 1, 2, RodataAddress, spec.descriptorSize); + const auto symtabOffset = append(symbols.data(), symbols.size()); + const auto strtabOffset = append(strings.data(), strings.size()); + + std::vector names{'\0'}; + const auto textName = AddString(names, ".text"); + const auto rodataName = AddString(names, ".rodata"); + const auto symtabName = AddString(names, ".symtab"); + const auto strtabName = AddString(names, ".strtab"); + const auto shstrtabName = AddString(names, ".shstrtab"); + const auto namesOffset = append(names.data(), names.size()); + while (bytes.size() % 8 != 0) bytes.push_back(std::byte{0}); + const auto headersOffset = bytes.size(); + bytes.resize(headersOffset + 6 * 64); + const auto header = [&](std::size_t index, std::size_t name, std::uint32_t type, std::uint64_t address, std::uint64_t offset, std::uint64_t size, std::uint32_t link) { + const auto at = headersOffset + index * 64; + Put(bytes, at, static_cast(name)); + Put(bytes, at + 4, type); + Put(bytes, at + 16, address); + Put(bytes, at + 24, offset); + Put(bytes, at + 32, size); + Put(bytes, at + 40, link); + }; + header(1, textName, 1, TextAddress, textOffset, spec.textSectionSize, 0); + header(2, rodataName, 1, RodataAddress, rodataOffset, 64, 0); + header(3, symtabName, 2, 0, symtabOffset, symbols.size(), 4); + header(4, strtabName, 3, 0, strtabOffset, strings.size(), 0); + header(5, shstrtabName, 3, 0, namesOffset, names.size(), 0); + + std::memcpy(bytes.data(), "\x7f" "ELF", 4); + Put(bytes, 4, 2); + Put(bytes, 5, 1); + Put(bytes, 6, 1); + Put(bytes, 16, 3); + Put(bytes, 18, spec.machine); + Put(bytes, 20, 1); + Put(bytes, 40, headersOffset); + Put(bytes, 52, 64); + Put(bytes, 58, 64); + Put(bytes, 60, 6); + Put(bytes, 62, 5); + return bytes; +} + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +void Rejects(const std::string& label, const std::vector& bytes, const std::string& expected) { + try { + ParseOracleCodeObject(bytes); + } catch (const std::runtime_error& error) { + Require(std::string(error.what()).find(expected) != std::string::npos, label + ": message \"" + error.what() + "\" does not name \"" + expected + "\""); + return; + } + throw std::runtime_error(label + ": accepted"); +} + +void Rejects(const std::string& label, const std::function& change, const std::string& expected) { + Spec spec; + change(spec); + Rejects(label, Build(spec), expected); +} + +} + +int main() { + try { + const auto kernel = ParseOracleCodeObject(Build(Spec{})); + Require(kernel.text.size() == Text.size(), "text word count differs"); + for (std::size_t index = 0; index < Text.size(); ++index) Require(kernel.text[index] == Text[index], "text word " + std::to_string(index) + " differs"); + Require(kernel.ldsBytes == 4096, "ldsBytes is not 4096"); + Require(kernel.waveSize == 32, "waveSize is not 32"); + Require(kernel.floatMode.floatMode == 237, "floatMode is not 237"); + Require(kernel.floatMode.dx10Clamp && !kernel.floatMode.ieeeMode && kernel.floatMode.fp16Overflow, "dx10Clamp, ieeeMode or fp16Overflow differs"); + Require(kernel.groupIdEnable == (std::array{true, false, false}), "groupIdEnable differs"); + Require(!kernel.tgSizeEnable, "tgSizeEnable is set"); + Require(kernel.threadIdComponentCount == 1, "threadIdComponentCount is not 1"); + + Spec wide; + wide.properties = 0x0008; + wide.rsrc2 = 0x84u | (1u << 8) | (1u << 9) | (1u << 10) | (2u << 11); + const auto wave64 = ParseOracleCodeObject(Build(wide)); + Require(wave64.waveSize == 64, "waveSize is not 64"); + Require(wave64.groupIdEnable == (std::array{true, true, true}), "groupIdEnable differs for XYZ"); + Require(wave64.tgSizeEnable && wave64.threadIdComponentCount == 3, "tgSizeEnable or threadIdComponentCount differs"); + + Rejects("machine", [](Spec& spec) { spec.machine = 62; }, "e_machine"); + Rejects("probe", [](Spec& spec) { spec.probe = false; }, "probe"); + Rejects("probe.kd", [](Spec& spec) { spec.descriptor = false; }, "probe.kd"); + Rejects("descriptor size", [](Spec& spec) { spec.descriptorSize = 63; }, "probe.kd size"); + Rejects("kernarg_size", [](Spec& spec) { spec.kernarg = 24; }, "kernarg_size"); + Rejects("private segment", [](Spec& spec) { spec.privateBytes = 16; }, "private_segment_fixed_size"); + Rejects("group segment", [](Spec& spec) { spec.group = 65540; }, "group_segment_fixed_size"); + Rejects("private segment enable", [](Spec& spec) { spec.rsrc2 |= 1u; }, "ENABLE_PRIVATE_SEGMENT"); + Rejects("user sgpr count", [](Spec& spec) { spec.rsrc2 = 0x86; }, "USER_SGPR_COUNT"); + Rejects("kernarg pointer", [](Spec& spec) { spec.properties = 0x0400; }, "ENABLE_SGPR_KERNARG_SEGMENT_PTR"); + Rejects("dispatch pointer", [](Spec& spec) { spec.properties = 0x040c; }, "user SGPR"); + Rejects("entry offset", [](Spec& spec) { spec.entry = 4292; }, "kernel_code_entry_byte_offset"); + Rejects("text size", [](Spec& spec) { spec.probeSize = 135; }, "multiple of 4"); + Rejects("zero text size", [](Spec& spec) { spec.probeSize = 0; }, "size 0"); + Rejects("symbol outside section", [](Spec& spec) { spec.textSectionSize = 64; }, "outside section"); + const auto whole = Build(Spec{}); + Rejects("truncated", std::vector(whole.begin(), whole.end() - 100), "truncated"); + Rejects("truncated header", std::vector(whole.begin(), whole.begin() + 30), "truncated"); + + for (const auto& [text, expected] : std::initializer_list>{{"0", 0u}, {"1", 1u}, {"16", 16u}, {"1024", 1024u}, {"007", 7u}, {"4294967295", 0xffffffffu}}) { + Require(ParseOracleCount(text, "threads") == expected, std::string("count \"") + text + "\" was not accepted as " + std::to_string(expected)); + } + for (const auto& [text, field] : std::initializer_list>{{"-18446744073709551615", "threads"}, {"-18446744073709551600", "out_dwords"}, {"-1", "threads"}, {"-0", "out_dwords"}, {"+1", "threads"}, {"", "out_dwords"}, {" 1", "threads"}, {"1 ", "out_dwords"}, {"1a", "threads"}, {"0x10", "out_dwords"}, {"4294967296", "threads"}, {"18446744073709551616", "out_dwords"}, {"99999999999999999999999", "threads"}}) { + try { + ParseOracleCount(text, field); + } catch (const std::runtime_error& error) { + Require(std::string(error.what()).find(field) != std::string::npos, std::string("count \"") + text + "\": message \"" + error.what() + "\" does not name " + field); + continue; + } + throw std::runtime_error(std::string("count \"") + text + "\" was accepted for " + field); + } + std::printf("oracle code object tests passed\n"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/PageStates.cpp b/core/libs/prx/libSceAgcDriver/tests/PageStates.cpp index fb92db485c..d4e6add733 100644 --- a/core/libs/prx/libSceAgcDriver/tests/PageStates.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/PageStates.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #ifndef NOMINMAX #define NOMINMAX @@ -41,12 +42,12 @@ public: GuestArena::GuestArenaRelease_nid_postfix(data, bytes); } - std::byte* Commit(std::size_t offset) { + std::byte* Commit(std::size_t offset, std::size_t length = Unit) { auto* at = data + offset; - GuestArena::GuestArenaCommit_nid_postfix(at, Unit, PAGE_READWRITE, Unit); - std::memset(at, 0x5b, Unit); + GuestArena::GuestArenaCommit_nid_postfix(at, length, PAGE_READWRITE, Unit); + std::memset(at, 0x5b, length); GuestAllocations::Mutation mutation; - mutation.Add(at, Unit, true, true, true); + mutation.Add(at, length, true, true, true); committed.push_back(offset); return at; } @@ -130,12 +131,115 @@ void CheckBoundaries() { Require(Accessible(block, Unit, true), "access observer did not invalidate inaccessible state after commit"); std::puts("page-state boundaries, publication, holes and invalidation passed"); } + +void CheckRuns() { + constexpr std::size_t page = 4096; + constexpr std::size_t count = 1536; + constexpr auto bytes = count * page; + using Ranges = std::vector>; + Range range(3 * ChunkGuestBytes); + auto* guest = range.Commit(ChunkGuestBytes - 512 * page, bytes); + const auto base = reinterpret_cast(guest); + const auto expected = [&](std::size_t index, std::size_t length = 1) { + Ranges result; + if (index != 0) result.emplace_back(base, base + index * page); + if (index + length < count) result.emplace_back(base + (index + length) * page, base + bytes); + return result; + }; + const auto protect = [&](std::size_t index, bool writable) { + auto* at = guest + index * page; + GuestAllocations::Mutation mutation; + mutation.Protect(at, page, true, writable, true, [&] { + const DWORD protection = writable ? PAGE_READWRITE : PAGE_READONLY; + DWORD previous = 0; + Require(VirtualProtect(at, page, protection, &previous) != 0, "cannot protect run fixture page"); + GuestArena::GuestArenaSetProtection_nid_postfix(reinterpret_cast(at), page, protection); + }); + }; + const Ranges whole{{base, base + bytes}}; + Require(DescribeCommitted(base, bytes, true).whole, "run fixture was not fully writable"); + for (const std::size_t index : {0, 63, 64, 511, 512, 513, 700, 1023, 1024, 1535}) { + protect(index, false); + Require(!Accessible(guest, bytes, true) && Accessible(guest, bytes), "a protected page retained writable access in a summarized run"); + Require(CommittedRanges(base, bytes, true) == expected(index), "a block or chunk boundary retained a stale summary"); + Require(CommittedRanges(base, bytes) == whole, "readable ranges changed around a read-only page"); + Require(CommittedRanges(base + 1, bytes - 2, true) == [&] { + auto clipped = expected(index); + if (!clipped.empty() && clipped.front().first == base) ++clipped.front().first; + if (!clipped.empty() && clipped.back().second == base + bytes) --clipped.back().second; + return clipped; + }(), "an unaligned run crossed a protected page"); + protect(index, true); + Require(DescribeCommitted(base, bytes, true).whole, "restored run was not writable"); + } + auto* hole = guest + 512 * page; + GuestArena::GuestArenaReset_nid_postfix(hole, Unit); + GuestAllocations::GuestAllocationsInvalidate_nid_postfix(reinterpret_cast(hole), Unit); + Require(!Accessible(hole, page), "a decommitted page retained cached access"); + Require(CommittedRanges(base, bytes) == expected(512, Unit / page), "decommitted pages retained a block summary"); + GuestArena::GuestArenaCommit_nid_postfix(hole, Unit, PAGE_READWRITE, Unit); + Require(DescribeCommitted(base, bytes, true).whole, "recommit did not clear inaccessible state and its summary"); + std::atomic done{false}; + std::atomic epoch{0}; + std::atomic changed{0}; + std::array, 2> observed{}; + std::atomic errors{0}; + std::vector readers; + for (std::size_t reader = 0; reader < observed.size(); ++reader) { + readers.emplace_back([&, reader] { + while (!done.load(std::memory_order_acquire)) { + const auto phase = epoch.load(std::memory_order_acquire); + if (phase > observed[reader].load(std::memory_order_relaxed)) { + const auto index = changed.load(std::memory_order_relaxed); + if (CommittedRanges(base, bytes, true) != ((phase & 1) != 0 ? expected(index) : whole)) errors.fetch_add(1, std::memory_order_relaxed); + observed[reader].store(phase, std::memory_order_release); + } else static_cast(DescribeCommitted(base, bytes, true)); + } + }); + } + const auto publish = [&](std::size_t phase, std::size_t index) { + changed.store(index, std::memory_order_relaxed); + epoch.store(phase, std::memory_order_release); + for (auto& seen : observed) { + while (seen.load(std::memory_order_acquire) < phase) std::this_thread::yield(); + } + }; + for (std::size_t round = 0; round < 128; ++round) { + const auto index = (round * 37 + 511) % count; + protect(index, false); + publish(2 * round + 1, index); + protect(index, true); + publish(2 * round + 2, index); + } + for (std::size_t round = 0; round < 2000; ++round) { + const auto index = 448 + round % 128; + protect(index, false); + protect(index, true); + } + protect(512, false); + publish(257, 512); + done.store(true, std::memory_order_release); + for (auto& reader : readers) reader.join(); + Require(errors.load() == 0 && CommittedRanges(base, bytes, true) == expected(512), "concurrent scans retained a stale block summary"); + protect(512, true); + std::array copy{}; + Read(base, copy); + Require(std::all_of(copy.begin(), copy.end(), [](std::byte value) { return value == std::byte{0x5b}; }), "summary scans changed guest data"); + Range edge(2 * ChunkGuestBytes); + constexpr std::size_t edgePages = 512; + const auto edgeFirst = reinterpret_cast(edge.Commit(ChunkGuestBytes - edgePages * page, edgePages * page)); + const Ranges edgeRun{{edgeFirst, edgeFirst + edgePages * page}}; + Require(CommittedRanges(edgeFirst, edgePages * page, true) == edgeRun, "the run before an unallocated chunk was not writable"); + Require(!DescribeCommitted(edgeFirst, edgePages * page + Unit, true).whole && CommittedRanges(edgeFirst, edgePages * page + Unit, true) == edgeRun, "a run into an unallocated chunk was reported past its committed pages"); + std::puts("page-state block scans across 64-page, 512-page and sparse-chunk boundaries 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 if (argc == 2 && std::string_view(argv[1]) == "--runs") CheckRuns(); else CheckBoundaries(); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index cd642d38e9..adb2819f08 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -1,4 +1,5 @@ #include "prx/libkernel/include/Api.hpp" +#include "prx/libSceAgcDriver/include/AgcDriver.hpp" #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" @@ -637,7 +638,10 @@ void testCopies() { std::size_t flexibleBefore = 0; check(sceKernelAvailableFlexibleMemorySize(&flexibleBefore) == 0, "cannot query flexible memory"); const auto toGds = makePacket(0x50, {0x60100000 | cachePolicies, low(source.data()), high(source.data()), 0x100, 0, 16}); - check(!AgcDriver::Pm4::ResolveStore(toGds, state, 64).has_value(), "DMA_DATA to GDS resolved as a memory store"); + const auto gdsStore = AgcDriver::Pm4::ResolveStore(toGds, state, 64); + check(gdsStore.has_value() && gdsStore->address == AgcDriver::Pm4::GdsAddress() + 0x100 && gdsStore->Bytes().size() == 16 && std::memcmp(gdsStore->Bytes().data(), source.data(), 16) == 0, "DMA_DATA to GDS did not resolve as a store at its GDS offset"); + const auto gdsFill = AgcDriver::Pm4::ResolveStore(makePacket(0x50, {0x40100000, 0x5eed1234, 0, 0x200, 0, 8}), state, 64); + check(gdsFill.has_value() && gdsFill->address == AgcDriver::Pm4::GdsAddress() + 0x200 && gdsFill->Bytes().size() == 8 && gdsFill->Bytes()[0] == std::byte{0x34} && gdsFill->Bytes()[7] == std::byte{0x5e}, "an immediate DMA_DATA fill of GDS did not resolve as a store at its GDS offset"); execute(state, toGds); std::size_t flexibleAfter = 0; check(sceKernelAvailableFlexibleMemorySize(&flexibleAfter) == 0 && flexibleAfter == flexibleBefore, "the GDS was charged to the flexible memory budget"); @@ -704,6 +708,40 @@ void testTagMarkers() { expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x79, {0x343u, 0xc6000005u}), 0x20u); }, "graphics packet in compute queue"); } +void testUserDataWriters() { + constexpr std::uint32_t untouched = 0xa5a5a5a5u; + const std::uint8_t bytes[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + std::uint32_t commands[16]; + const auto run = [&](std::uint32_t written, std::initializer_list expected, const char* reason) { + check(written == expected.size() && std::equal(expected.begin(), expected.end(), commands) && commands[written] == untouched, reason); + AgcDriver::QueueState state; + for (std::uint32_t offset = 0; offset < written;) { + const auto length = ((commands[offset] >> 16u) & 0x3fffu) + 2u; + execute(state, std::vector(commands + offset, commands + offset + length)); + offset += length; + } + if (written > 4) check(state.userConfig.at(0x343u) == commands[written - 1], "user data payload did not reach the last user data register"); + }; + std::fill(std::begin(commands), std::end(commands), untouched); + run(sceAgcDriverUserDataWritePopMarker(commands, 0), {0xc0017904u, 0x342u, 0xc2000000u}, "draw queue pop marker"); + run(sceAgcDriverUserDataWritePopMarker(commands, 1), {0xc0017900u, 0x342u, 0xc2000000u}, "compute queue pop marker"); + run(sceAgcDriverUserDataWritePacket(commands, 1, 0x12345678u, bytes, 0), {0xc0017900u, 0x342u, 0x12345678u}, "user data packet without bytes"); + run(sceAgcDriverUserDataWritePacket(commands, 1, 0x12345678u, nullptr, 9), {0xc0017900u, 0x342u, 0x12345678u}, "user data packet without a source"); + run(sceAgcDriverUserDataWritePacket(commands, 1, 0x12345678u, bytes, 2), {0xc0027900u, 0x342u, 0x12345678u, 0x00000201u}, "user data packet with an inline dword"); + std::fill(std::begin(commands), std::end(commands), untouched); + run(sceAgcDriverUserDataWritePacket(commands, 1, 0x12345678u, bytes, 9), + {0xc0027900u, 0x342u, 0x12345678u, 0x04030201u, 0xc0043700u, 0x06010000u, 0xc343u, 0u, 0x08070605u, 0x00000009u}, "user data packet with a payload"); + std::fill(std::begin(commands), std::end(commands), untouched); + run(sceAgcDriverUserDataWritePushMarker(commands, 0, "frame", 6, 0x11223344u), + {0xc0027904u, 0x342u, 0xc1011882u, 0x6d617266u, 0xc0033704u, 0x06010000u, 0xc343u, 0u, 0x00000065u}, "push marker"); + std::fill(std::begin(commands), std::end(commands), untouched); + run(sceAgcDriverUserDataWriteSetMarker(commands, 1, "frame", 6, 0xffffffffu), + {0xc0027900u, 0x342u, 0xc0017fffu, 0x6d617266u, 0xc0033700u, 0x06010000u, 0xc343u, 0u, 0x00000065u}, "set marker"); + std::uint32_t target = 0; + AgcDriver::QueueState state; + expectFailure([&] { execute(state, makePacket(0x37, {0x500u, low(&target), high(&target), 1u}, 4u)); }, "header flags 0x4"); +} + void testMemoryCopyDecode() { using AgcDriver::Pm4::DecodeMemoryCopy; constexpr std::uint64_t source = 0x1120000000ull, destination = 0x403d7b400ull; @@ -854,6 +892,29 @@ void testWriteChangedStoresExactlyTheChangedRuns() { } } +void testWriteChangedKeepingGuestKeepsCpuWrites() { + alignas(256) static std::uint8_t guest[1024 + 13]; + std::vector original(sizeof(guest)), current(sizeof(guest)); + std::uint32_t seed = 777; + const auto next = [&] { return seed = seed * 1103515245u + 12345u, seed >> 16u; }; + std::vector cpu(sizeof(guest)); + for (std::size_t i = 0; i < sizeof(guest); ++i) { + original[i] = static_cast(next()); + const auto kind = next() % 4; + current[i] = kind >= 2 ? static_cast(static_cast(original[i]) ^ (1u + next() % 255u)) : original[i]; + cpu[i] = kind % 2 == 1 ? static_cast(static_cast(original[i]) ^ (1u + next() % 255u)) : static_cast(original[i]); + guest[i] = cpu[i]; + } + AgcDriver::GuestMemory::WriteChangedKeepingGuest(reinterpret_cast(guest), current, original); + for (std::size_t i = 0; i < sizeof(guest); ++i) { + const auto expected = cpu[i] != static_cast(original[i]) ? cpu[i] : static_cast(current[i]); + check(guest[i] == expected, "write-back overwrote a byte the CPU changed since the upload, or skipped one it did not"); + } + for (std::size_t i = 0; i < sizeof(guest); ++i) guest[i] = static_cast(original[i]); + AgcDriver::GuestMemory::WriteChangedKeepingGuest(reinterpret_cast(guest), current, original); + for (std::size_t i = 0; i < sizeof(guest); ++i) check(guest[i] == static_cast(current[i]), "write-back skipped a changed byte although the guest still held the upload"); +} + void testWriteChangedChunksOfEveryDensity() { alignas(64) static std::uint8_t guest[128]; constexpr std::size_t Tail = 5; @@ -2145,6 +2206,7 @@ int main(int argc, char** argv) { testWriteChangedKeepsUntouchedBytes(); testWriteChangedChunksOfEveryDensity(); testWriteChangedStoresExactlyTheChangedRuns(); + testWriteChangedKeepingGuestKeepsCpuWrites(); testRegisters(); testRegisterFile(); testContextAndBases(); @@ -2156,6 +2218,7 @@ int main(int argc, char** argv) { testMemory(); testTraceRegisterWrites(); testTagMarkers(); + testUserDataWriters(); testCopies(); testMemoryCopyDecode(); testMemorySynchronization(); diff --git a/core/libs/prx/libSceAgcDriver/tests/PreparationInputs.cpp b/core/libs/prx/libSceAgcDriver/tests/PreparationInputs.cpp new file mode 100644 index 0000000000..723cec94c3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/PreparationInputs.cpp @@ -0,0 +1,219 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" +#include "ControlFlow/RequestSerializer.hpp" +#include "Optimization/ResourceProgram.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace { + +std::string mode; +std::atomic entered{0}; + +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +void SetEnvironment(const char* name, const char* value) { +#ifdef _WIN32 + Require(_putenv_s(name, value) == 0, "cannot set test environment"); +#else + Require(value != nullptr ? setenv(name, value, 1) == 0 : unsetenv(name) == 0, "cannot set test environment"); +#endif +} + +std::vector Inputs(std::uint64_t address) { + std::ostringstream prefix; + prefix << "shader_" << std::hex << address << ".input-"; + std::vector result; + for (const auto& entry : std::filesystem::directory_iterator(".")) { + const auto name = entry.path().filename().string(); + if (entry.is_regular_file() && name.starts_with(prefix.str()) && name.ends_with(".prepare.req")) result.push_back(entry.path()); + } + return result; +} + +ShaderRecompiler::DeserializedRequest Read(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + Require(file.is_open(), "capture cannot be opened"); + const std::string text((std::istreambuf_iterator(file)), {}); + return ShaderRecompiler::RequestSerializer{}.Deserialize(text); +} + +bool Matches(const ShaderRecompiler::RecompileRequest& left, const ShaderRecompiler::RecompileRequest& right) { + if (left.shader.codeAddress != right.shader.codeAddress || left.shader.stage != right.shader.stage || + !std::ranges::equal(left.shader.code, right.shader.code) || + !std::ranges::equal(left.context.userData, right.context.userData) || + left.context.memory.size() != right.context.memory.size()) return false; + for (std::size_t i = 0; i < left.context.memory.size(); ++i) { + if (left.context.memory[i].guestAddress != right.context.memory[i].guestAddress || + !std::ranges::equal(left.context.memory[i].bytes, right.context.memory[i].bytes)) return false; + } + return true; +} + +void Invoke(unsigned index, std::uint64_t address = 0x12340000u) { + const std::array code{0xbf810000u}; + const std::array users{index, index + 100u}; + const std::array bytes{std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}}; + const std::array memory{{{0x100000u, bytes}}}; + ShaderRecompiler::RecompileRequest request{}; + request.shader = {ShaderRecompiler::ShaderStage::Compute, address, code, 0, {}}; + request.context.waveSize = 64; + request.context.userData = users; + request.context.memory = memory; + request.context.compute = ShaderRecompiler::ShaderComputeStageInfo{{1, 1, 1}, 0, {}, false, 0, {}}; + request.useCache = false; + const auto handle = AgcDriver::DriverDetail::PrepareShaderWithDiagnostics(request); + Require(handle == nullptr, "compiler return value was changed"); +} + +void WriteFailure() { + const std::array code{0xbf810000u}; + ShaderRecompiler::RecompileRequest request{}; + request.shader = {ShaderRecompiler::ShaderStage::Compute, 0x12340000u, code, 0, {}}; + request.context.waveSize = 64; + const std::string name = "shader_12340000.retry.req"; + Require(std::filesystem::create_directory(name), "cannot block capture path"); + Require(AgcDriver::DriverDetail::Driver::dumpRequest(request.shader.codeAddress, request, ".retry").empty(), "failed save reported a filename"); + Require(std::filesystem::remove(name), "cannot unblock capture path"); + Require(AgcDriver::DriverDetail::Driver::dumpRequest(request.shader.codeAddress, request, ".retry") == name, "failed save was not retried"); + Require(Matches(request, Read(name).request), "retried capture changed its input"); + Require(AgcDriver::DriverDetail::Driver::dumpRequest(request.shader.codeAddress, request, ".retry") == name, "successful capture was not retained"); +} + +struct Directory { + std::filesystem::path previous = std::filesystem::current_path(); + std::filesystem::path path; + Directory() { +#ifdef _WIN32 + const auto process = _getpid(); +#else + const auto process = getpid(); +#endif + path = std::filesystem::temp_directory_path() / ("anyps5-prepare-input-" + std::to_string(process) + "-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(path), "cannot create isolated capture directory"); + std::filesystem::current_path(path); + } + ~Directory() { + std::error_code error; + std::filesystem::current_path(previous, error); + auto current = std::filesystem::directory_iterator(path, error); + const std::filesystem::directory_iterator end; + while (!error && current != end) { + std::filesystem::remove(current->path(), error); + if (!error) current.increment(error); + } + std::filesystem::remove(path, error); + } +}; + +} + +namespace ShaderRecompiler { + +std::shared_ptr PrepareShader(const RecompileRequest& request, ShaderPreparationContext*) { + ++entered; + const auto inputs = Inputs(request.shader.codeAddress); + const bool expected = mode != "disabled" && (mode != "selected" || request.shader.codeAddress == 0x12340000u); + bool found = false; + for (const auto& path : inputs) { + try { + auto saved = Read(path); + if (Matches(request, saved.request)) found = true; + } catch (const std::exception&) {} + } + const bool blocked = mode == "capture-write-failure" && request.context.userData[0] == 2u; + Require(found == (expected && !blocked), "capture state at compiler entry is wrong"); + if (mode == "failure") throw std::runtime_error("compiler sentinel"); + return {}; +} + +} + +int main(int argc, char** argv) { + try { + Require(argc == 2, "expected one test mode"); + mode = argv[1]; + Directory directory; + SetEnvironment("APS5_DUMP_PREPARE_INPUTS", mode == "disabled" ? "" : "1"); + SetEnvironment("APS5_DUMP_SHADERS", mode == "selected" ? "12340000" : mode == "failure" ? "1" : ""); +#ifndef _WIN32 + if (mode == "disabled") SetEnvironment("APS5_DUMP_PREPARE_INPUTS", nullptr); + if (mode != "selected" && mode != "failure") SetEnvironment("APS5_DUMP_SHADERS", nullptr); +#endif + unsigned expected = 0; + if (mode == "write-failure") { + WriteFailure(); + } else if (mode == "capture-write-failure") { + Invoke(1); + const auto first = Inputs(0x12340000u); + Require(first.size() == 1, "first capture missing"); + const auto name = first.front().filename().string(); + const auto separator = name.rfind('-'); + const auto ordinal = std::stoull(name.substr(separator + 1)); + const auto blocked = name.substr(0, separator + 1) + std::to_string(ordinal + 1) + ".prepare.req"; + Require(std::filesystem::create_directory(blocked), "cannot block next capture"); + Invoke(2); + Require(std::filesystem::remove(blocked), "cannot unblock next capture"); + Invoke(3); + expected = 2; + } else if (mode == "failure") { + try { + Invoke(1); + throw std::runtime_error("compiler failure was swallowed"); + } catch (const std::exception& error) { + Require(std::string(error.what()).find("compiler sentinel") != std::string::npos, "compiler error was changed"); + Require(std::string(error.what()).find("shader_12340000.prepare.req") != std::string::npos, "existing failure dump context was lost"); + Require(Read("shader_12340000.prepare.req").request.context.userData[0] == 1u, "existing failure dump changed"); + } + expected = 1; + } else if (mode == "concurrent") { + std::vector> futures; + for (unsigned i = 1; i <= 8; ++i) futures.push_back(std::async(std::launch::async, [i] { Invoke(i); })); + for (auto& future : futures) future.get(); + expected = 8; + } else if (mode == "selected") { + Invoke(1); + Invoke(2, 0x12340001u); + expected = 1; + } else { + Require(mode == "enabled" || mode == "disabled", "unknown test mode"); + Invoke(1); + Invoke(2); + expected = mode == "enabled" ? 2 : 0; + } + const auto inputs = Inputs(0x12340000u); + Require(inputs.size() == expected, "capture count or naming changed"); + std::set ids; + for (const auto& path : inputs) { + auto saved = Read(path); + Require(saved.request.context.userData.size() == 2, "saved user data missing"); + Require(ids.insert(saved.request.context.userData[0]).second, "concurrent capture input was overwritten"); + } + Require(entered == (mode == "write-failure" ? 0u : mode == "capture-write-failure" ? 3u : mode == "concurrent" ? 8u : mode == "failure" ? 1u : 2u), "compiler invocation count changed"); + std::printf("preparation input %s: passed\n", mode.c_str()); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "preparation input: %s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/ProcessSamplerStack.cpp b/core/libs/prx/libSceAgcDriver/tests/ProcessSamplerStack.cpp new file mode 100644 index 0000000000..39cb30c634 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/ProcessSamplerStack.cpp @@ -0,0 +1,137 @@ +#include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::atomic ready{false}; +std::atomic done{false}; +std::atomic caller{0}; + +[[gnu::noinline]] void Busy() { + caller.store(reinterpret_cast(__builtin_return_address(0))); + ready.store(true); + while (!done.load(std::memory_order_relaxed)) std::atomic_signal_fence(std::memory_order_seq_cst); +} + +[[gnu::noinline]] void Parent() { + Busy(); + ready.store(false); +} + +std::atomic lastWord{0}; + +bool Accepted(const void* address) { + std::uint64_t low = 0; + std::uint64_t high = 0; + const auto at = reinterpret_cast(address); + if (!AgcDriver::ReadableStackRegion(at, low, high)) return false; + std::uint64_t last = 0; + std::memcpy(&last, reinterpret_cast(high - sizeof(last)), sizeof(last)); + lastWord.store(last); + return low <= at && at < high; +} + +bool Rejected(const void* address) { + std::uint64_t low = 0; + std::uint64_t high = 0; + return !AgcDriver::ReadableStackRegion(reinterpret_cast(address), low, high) && low == 0 && high == 0; +} + +bool HasProtection(const void* address, DWORD expected) { + MEMORY_BASIC_INFORMATION memory{}; + return VirtualQuery(address, &memory, sizeof(memory)) != 0 && memory.Protect == expected; +} + +bool Fail(const char* what, DWORD protection) { + std::fprintf(stderr, "%s (protection 0x%lx)\n", what, static_cast(protection)); + return false; +} + +bool CheckAllocated(DWORD allocation, DWORD protection, bool readable) { + void* page = VirtualAlloc(nullptr, 65536, allocation, protection); + if (page == nullptr) return Fail("cannot allocate the test region", protection); + const auto* inside = static_cast(page) + 4096; + const bool committed = (allocation & MEM_COMMIT) != 0; + const bool passed = (!committed || HasProtection(page, protection)) && (readable ? Accepted(inside) : Rejected(inside)); + VirtualFree(page, 0, MEM_RELEASE); + return passed || Fail(readable ? "a readable region was rejected" : "an unreadable region was accepted", protection); +} + +bool CheckMapped(HANDLE section, DWORD access, DWORD protection) { + void* view = MapViewOfFile(section, access, 0, 0, 65536); + if (view == nullptr) return Fail("cannot map the copy-on-write view", protection); + const bool passed = HasProtection(view, protection) && Accepted(view); + UnmapViewOfFile(view); + return passed || Fail("a readable mapped view was rejected", protection); +} + +bool CheckRegions() { + for (const DWORD protection : {PAGE_READONLY, PAGE_READWRITE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE}) { + if (!CheckAllocated(MEM_RESERVE | MEM_COMMIT, protection, true)) return false; + } + HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, 0, 65536, nullptr); + if (section == nullptr) return Fail("cannot create the copy-on-write section", PAGE_EXECUTE_READWRITE); + const bool mapped = CheckMapped(section, FILE_MAP_COPY, PAGE_WRITECOPY) && CheckMapped(section, FILE_MAP_COPY | FILE_MAP_EXECUTE, PAGE_EXECUTE_WRITECOPY); + CloseHandle(section); + if (!mapped) return false; + for (const DWORD protection : {static_cast(PAGE_EXECUTE), static_cast(PAGE_NOACCESS), static_cast(PAGE_READWRITE | PAGE_GUARD)}) { + if (!CheckAllocated(MEM_RESERVE | MEM_COMMIT, protection, false)) return false; + } + if (!CheckAllocated(MEM_RESERVE, PAGE_READWRITE, false)) return false; + return Rejected(nullptr) || Fail("the null page was accepted", 0); +} + +} + +int main() { + if (!CheckRegions()) return 1; + const auto path = std::filesystem::temp_directory_path() / ("anyps5-process-stack-" + std::to_string(GetCurrentProcessId()) + ".txt"); + _putenv_s("APS5_SAMPLE_THREADS", path.string().c_str()); + _putenv_s("APS5_SAMPLE_WORKER", ""); + _putenv_s("APS5_SAMPLE_WORKER_QUEUE", "0x50"); + std::thread target(Parent); + while (!ready.load()) std::this_thread::yield(); + AgcDriver::StartWorkerSampler(0); + std::this_thread::sleep_for(std::chrono::milliseconds(750)); + AgcDriver::StopWorkerSampler(); + done.store(true); + target.join(); + const auto pc = caller.load(); + DWORD64 imageBase = 0; + if (RtlLookupFunctionEntry(pc, &imageBase, nullptr) != nullptr) { + std::filesystem::remove(path); + std::fprintf(stderr, "fixture unexpectedly has native Windows unwind metadata\n"); + return 1; + } + HMODULE module = nullptr; + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(pc), &module)) return 1; + wchar_t imagePath[MAX_PATH]{}; + if (GetModuleFileNameW(module, imagePath, MAX_PATH) == 0) return 1; + const auto imageName = std::filesystem::path(imagePath).filename().string(); + const auto offset = pc - reinterpret_cast(module); + std::ifstream file(path); + std::string line; + bool found = false; + std::uint64_t inclusive = 0; + while (std::getline(file, line)) { + char name[1024]{}; + unsigned long long address = 0, self = 0, total = 0; + if (std::sscanf(line.c_str(), " %1023s %llx %llu %llu", name, &address, &self, &total) == 4 && name == imageName && address == offset && total != 0) { + found = true; + inclusive += total; + } + } + file.close(); + std::filesystem::remove(path); + std::printf("process sampler caller: %s (%llu inclusive samples)\n", found ? "passed" : "missing", static_cast(inclusive)); + return found ? 0 : 1; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/RasterOrderGroup.cpp b/core/libs/prx/libSceAgcDriver/tests/RasterOrderGroup.cpp new file mode 100644 index 0000000000..20c2c37e8d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/RasterOrderGroup.cpp @@ -0,0 +1,33 @@ +#include "prx/libSceAgcDriver/Execution/include/RasterOrderGroup.hpp" +#include + +namespace { + +struct Case { + VkDriverId driver; + bool usable; +}; + +constexpr Case Cases[]{ + {VK_DRIVER_ID_AMD_PROPRIETARY, true}, + {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, false}, +}; + +} + +int main() { + int failures = 0; + for (const auto& item : Cases) { + if (AgcDriver::RasterOrderGroupUsable(item.driver) != item.usable) { + std::fprintf(stderr, "driver %d: expected a raster order group 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/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 0fcd025d8e..bc58d487a6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -13,12 +13,14 @@ #include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/MapImportGate.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Shaders.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureCache.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureSwizzleEquations.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp" #include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp" @@ -50,6 +52,10 @@ #include "BdaAbi.hpp" #include "ShaderCacheSourceVersion.hpp" #include +#ifdef __APPLE__ +#include +#include +#endif #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX @@ -66,7 +72,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -90,6 +98,10 @@ namespace { +struct TestConfigurationError : std::runtime_error { + using std::runtime_error::runtime_error; +}; + using namespace AgcDriver::Graphics; using AgcDriver::GuestMemory::GpuMutex; @@ -518,6 +530,13 @@ public: function("vkGetPhysicalDeviceProperties2")(context.physical, &properties); context.hostImportAlignment = hostProperties.minImportedHostPointerAlignment; context.discreteGpu = properties.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; + if (const char* forced = std::getenv("ANYPS5_TEST_HOST_IMPORT_ALIGNMENT"); forced != nullptr) { + VkDeviceSize alignment = 0; + const auto [end, error] = std::from_chars(forced, forced + std::strlen(forced), alignment); + if (error != std::errc{} || *end != '\0') throw TestConfigurationError("ANYPS5_TEST_HOST_IMPORT_ALIGNMENT is not a number"); + if (alignment != 0 && alignment % hostProperties.minImportedHostPointerAlignment != 0) throw TestConfigurationError("ANYPS5_TEST_HOST_IMPORT_ALIGNMENT is not a multiple of the device's host import alignment"); + context.hostImportAlignment = alignment; + } } 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); @@ -853,6 +872,42 @@ void copiedDirectOrderingTests(const Device& device, Recorder& recorder) { update(direct, 2); recorder.Sync(); Require(*block == 45, "direct consumers lost GPU-ordered updates"); + for (const bool reuse : {false, true}) { + const auto* imported = HostImportFor(context, address, bytes); + Require(imported != nullptr, "the label-ordering input lost its import"); + const auto commands = recorder.Commands(); + context.Function("vkCmdFillBuffer")(commands, imported->buffer, HostImportOffset(*imported, address), 4, 43); + const std::uint32_t value = 43; + recorder.NoteLabel(address, std::as_bytes(std::span(&value, 1)), 1001, 0); + auto completed = std::make_shared(false); + Require(recorder.AfterRecordedWork([completed] { *completed = true; }), "the unrelated completion was not queued"); + Require(recorder.PendingLabelIn(address, 4) && !recorder.CompletionLabelIn(address, 4), "the GPU label was classified as a CPU completion store"); + Require(!CompletePendingCpuWrites(context, address, 4), "an unrelated completion forced a CPU wait for a GPU-ordered label"); + if (reuse) Require(direct->Revalidate(std::span(&compute, 1)), "a GPU-only label prevented direct consumer reuse"); + else direct = std::make_shared(context, compute); + Require(!*completed, "the GPU-only label consumer completed unrelated host work"); + update(direct, 2); + recorder.Sync(); + Require(*completed && *block == 45, "the GPU label consumer lost its value or completion"); + } + { + const std::uint32_t stored = 50; + const auto labelBytes = std::as_bytes(std::span(&stored, 1)); + const auto* import = HostImportFor(context, address, sizeof(stored)); + Require(import != nullptr, "import the GPU label memory"); + static_cast(recorder.Commands()); + recorder.RecordStore(import->buffer, HostImportOffset(*import, address), labelBytes, address); + recorder.NoteLabel(address, labelBytes, 2000, 0); + Require(recorder.AfterRecordedWork([] {}) && Recorder::PendingCompletionLabels() != 0, "no completion pending beside the GPU label"); + const auto beforeFresh = recorder.Submissions(); + auto fresh = std::make_shared(context, compute); + Require(recorder.Submissions() == beforeFresh, "a fresh direct consumer waited for a label the GPU stores in order"); + if (recorder.QueuedStoreOverlaps(address, sizeof(stored))) recorder.FlushStores(); + update(fresh, 2); + recorder.Sync(); + Require(*block == 52, "a direct consumer preceded a label the GPU stored before it"); + *block = 45; + } { std::array unregistered{}; struct Finish { @@ -979,6 +1034,166 @@ void copiedDirectOrderingTests(const Device& device, Recorder& recorder) { std::cout << "Copied producers precede fresh and reused direct consumers\n"; } +void copiedWriteBackTests(const Device& device, Recorder& recorder) { + auto context = device.GetContext(); + context.hostImportAlignment = 0; + context.dmaBufImport = false; + recorder.Sync(); + alignas(4096) static std::uint32_t block[1024]; + std::memset(block, 0, sizeof(block)); + const auto address = reinterpret_cast(block); + ShaderRecompiler::RecompileResult program; + ShaderRecompiler::DescriptorBinding binding; + binding.kind = ShaderRecompiler::DescriptorKind::StorageBuffer; + binding.role = ShaderRecompiler::DescriptorRole::GuestBuffers; + binding.descriptorSet = 0; + binding.binding = 0; + binding.count = 1; + binding.guestDescriptor = {static_cast(address), static_cast(address >> 32u) & 0xffffu, 1024, 0x31000000u}; + binding.bufferWritten = {true}; + program.bindings.push_back(binding); + const CompiledShader compute{ShaderRecompiler::ShaderStage::Compute, &program, 0}; + struct Release { + const Context& context; + Recorder& recorder; + VkPipeline pipeline = VK_NULL_HANDLE; + VkPipelineLayout layout = VK_NULL_HANDLE; + VkShaderModule module = VK_NULL_HANDLE; + std::shared_ptr layoutOwner; + ~Release() { + recorder.Sync(); + if (pipeline) context.Function("vkDestroyPipeline")(context.device, pipeline, nullptr); + if (layout) context.Function("vkDestroyPipelineLayout")(context.device, layout, nullptr); + if (module) context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + } + } release{context, recorder}; + release.layoutOwner = std::make_shared(context, compute); + Require(release.layoutOwner->HasCopiedWrites(), "the written buffer did not take the copied path"); + const auto setLayout = release.layoutOwner->Layout(); + const VkPushConstantRange push{VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(std::uint32_t)}; + VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + layoutInfo.setLayoutCount = 1; + layoutInfo.pSetLayouts = &setLayout; + layoutInfo.pushConstantRangeCount = 1; + layoutInfo.pPushConstantRanges = &push; + Check(context.Function("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &release.layout), "copied write-back pipeline layout"); + VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + moduleInfo.codeSize = sizeof(BUFFER_UPDATE_SPV); + moduleInfo.pCode = BUFFER_UPDATE_SPV; + Check(context.Function("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &release.module), "copied write-back shader module"); + VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, nullptr, 0, VK_SHADER_STAGE_COMPUTE_BIT, release.module, "main", nullptr}; + pipelineInfo.layout = release.layout; + Check(context.Function("vkCreateComputePipelines")(context.device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &release.pipeline), "copied write-back pipeline"); + const auto run = [&](std::uint32_t delta) { + auto copied = std::make_shared(context, compute); + Require(copied->HasCopiedWrites(), "a use of the written buffer did not take the copied path"); + const auto commands = recorder.Commands(); + recorder.Keep(copied); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_COMPUTE, release.pipeline); + copied->Bind(commands, VK_PIPELINE_BIND_POINT_COMPUTE, release.layout); + context.Function("vkCmdPushConstants")(commands, release.layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(delta), &delta); + context.Function("vkCmdDispatch")(commands, 1, 1, 1); + 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, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_HOST_READ_BIT); + copied->MarkGpuWrites(recorder); + recorder.Sync(); + return copied; + }; + block[0] = 0x10; + block[1] = 0x20; + const auto first = run(1); + block[0] = 0x70; + block[1] = 0x25; + first->WriteBackBuffers(); + Require(block[0] == 0x70, "a copied write-back stored the shader's dword over the one the CPU wrote after the upload"); + Require(block[1] == 0x25, "a copied write-back rolled back a dword the shader never touched"); + const auto second = run(2); + second->WriteBackBuffers(); + Require(block[0] == 0x72, "the next use of the copied buffer did not read the dword the CPU wrote"); + block[0] = 0x10; + const auto third = run(1); + block[0] = 0x00aa0010u; + third->WriteBackBuffers(); + Require(block[0] == 0x00aa0011u, "a copied write-back did not merge the shader's byte with the CPU's byte of the same dword"); + std::cout << "Copied write-backs keep the bytes the CPU wrote after the upload\n"; +} + +void changedCopyTests(const Device& device, Recorder& recorder) { + Require(recorder.Idle(), "the changed copy test needs an idle recorder"); + constexpr std::uint32_t words = 70000; + constexpr auto usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + auto result = std::make_shared(device.GetContext(), words * 4u, usage); + auto snapshot = std::make_shared(device.GetContext(), words * 4u, usage); + auto target = std::make_shared(device.GetContext(), words * 4u, usage); + if (result->DeviceAddress() == 0 || snapshot->DeviceAddress() == 0 || target->DeviceAddress() == 0) { + std::cout << "changed copy tests skipped: no buffer device addresses\n"; + return; + } + constexpr std::uint32_t byteMasks[] = {0xffffffffu, 0x000000ffu, 0x0000ff00u, 0x00ff0000u, 0xff000000u, 0x00ffff00u}; + std::vector uploaded(words), shader(words), held(words), expected(words); + std::uint32_t contested = 0; + std::uint32_t mixed = 0; + for (std::uint32_t i = 0; i < words; ++i) { + uploaded[i] = i * 2654435761u; + shader[i] = i % 7 == 0 ? uploaded[i] ^ (0x5a5a5a5au & byteMasks[(i / 7) % 6]) : uploaded[i]; + held[i] = i % 5 == 0 ? uploaded[i] ^ (0xc3c3c3c3u & byteMasks[(i / 5 + 1) % 6]) : uploaded[i]; + bool overlap = false; + for (std::uint32_t shift = 0; shift < 32; shift += 8) { + const auto mask = 0xffu << shift; + const bool byShader = (shader[i] & mask) != (uploaded[i] & mask); + const bool byCpu = (held[i] & mask) != (uploaded[i] & mask); + expected[i] |= (byShader && !byCpu ? shader[i] : held[i]) & mask; + overlap = overlap || (byShader && byCpu); + } + contested += overlap ? 1u : 0u; + mixed += expected[i] != shader[i] && expected[i] != held[i] ? 1u : 0u; + } + Require(contested != 0 && mixed != 0, "the changed copy data lacks words both sides changed or words that mix their bytes"); + auto counter = std::make_shared(device.GetContext(), 16, usage); + std::memset(counter->Bytes().data(), 0, 16); + std::memcpy(snapshot->Bytes().data(), uploaded.data(), words * 4u); + std::memcpy(result->Bytes().data(), shader.data(), words * 4u); + std::memcpy(target->Bytes().data(), held.data(), words * 4u); + Require(recorder.RecordChangedCopy(recorder.Commands(), result->DeviceAddress(), snapshot->DeviceAddress(), target->DeviceAddress(), words, counter->DeviceAddress()), "the changed copy kernel is unavailable"); + recorder.Keep(result); + recorder.Keep(snapshot); + recorder.Keep(target); + recorder.Keep(counter); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + std::vector stored(words); + std::memcpy(stored.data(), target->Bytes().data(), words * 4u); + for (std::uint32_t i = 0; i < words; ++i) { + Require(stored[i] == expected[i], "changed copy word " + std::to_string(i) + ": " + std::to_string(stored[i]) + ", expected " + std::to_string(expected[i]) + " (the shader's changed bytes over the target's own)"); + } + std::uint32_t counted = 0; + std::memcpy(&counted, counter->Bytes().data(), sizeof(counted)); + Require(counted == contested, "the changed copy counted " + std::to_string(counted) + " words with bytes both sides changed, expected " + std::to_string(contested)); + constexpr std::uint64_t lead = 0x1000; + auto imported = std::make_shared(device.GetContext(), lead + words * 4u, usage); + std::memset(imported->Bytes().data(), 0xa5, lead); + std::memcpy(imported->Bytes().data() + lead, held.data(), words * 4u); + HostImport import{}; + import.base = 0x7f0000011000ull; + import.bytes = words * 4u; + import.address = imported->DeviceAddress() + lead; + import.lead = lead; + const auto start = HostImportBufferStart(import); + Require(start.guest == import.base - lead && start.device == imported->DeviceAddress() && HostImportOffset(import, import.base) == lead, "the copy source of an import with a lead does not start at the import's buffer"); + Require(recorder.RecordChangedCopy(recorder.Commands(), result->DeviceAddress(), snapshot->DeviceAddress(), start.device + (import.base - start.guest), words), "the changed copy kernel is unavailable"); + recorder.Keep(imported); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + for (std::uint64_t i = 0; i < lead; ++i) Require(imported->Bytes()[i] == std::byte{0xa5}, "the changed copy into an import with a lead wrote byte " + std::to_string(i) + " of the lead"); + std::memcpy(stored.data(), imported->Bytes().data() + lead, words * 4u); + for (std::uint32_t i = 0; i < words; ++i) { + Require(stored[i] == expected[i], "changed copy word " + std::to_string(i) + " after a lead: " + std::to_string(stored[i]) + ", expected " + std::to_string(expected[i])); + } +} + void readTrackingTests(const Device& device, Recorder& recorder) { Require(Recorder::ReadTracking(), "read tracking is off (APS5_COPY_READ_TRACKING=0 set?)"); Require(recorder.Idle() && !recorder.PendingReadOverlaps(0x10000, 16), "a fresh recorder reports a read"); @@ -1545,6 +1760,26 @@ void afterRecordedWorkTests(const Device& device, Recorder& recorder) { Require(seen == 1, "an action ran before the completion label recorded ahead of it"); } +void completionWaitTests(const Device& device, Recorder& recorder) { + alignas(64) static std::uint32_t memory[16]; + const auto base = reinterpret_cast(memory); + recorder.Sync(); + const std::array gpuValue{std::byte{5}, std::byte{0}, std::byte{0}, std::byte{0}}; + auto ran = std::make_shared(false); + recorder.NotePendingWrite(base, sizeof(memory)); + recorder.AfterCompletions(base, gpuValue, 7, 0, true); + Require(recorder.AfterRecordedWork([ran] { *ran = true; }) && Recorder::PendingCompletionLabels() != 0, "completion wait: the action behind the open batch is not pending"); + CompletePendingCpuWrites(device.GetContext(), base, sizeof(memory)); + Require(!*ran && !recorder.Idle(), "completion wait: a reader of a GPU-stored label waited for a batch whose only completion action writes no memory"); + recorder.Sync(); + Require(*ran && recorder.Idle(), "completion wait: the action did not run at the sync"); + const std::array cpuValue{std::byte{9}, std::byte{0}, std::byte{0}, std::byte{0}}; + recorder.NotePendingWrite(base, sizeof(memory)); + recorder.AfterCompletions(base + 4u, cpuValue, 8, 0, false); + CompletePendingCpuWrites(device.GetContext(), base, sizeof(memory)); + Require(memory[1] == 9u && Recorder::PendingCompletionLabels() == 0, "completion wait: a reader of a label stored behind a completion did not wait for that store"); +} + int gpuTimingCompletionTests(const Device& device, bool incomplete) { const auto& context = device.GetContext(); if (!device.Timestamps()) { @@ -3985,10 +4220,11 @@ bool bdaStagingTests(const Device& device, Recorder& recorder) { return false; } constexpr std::size_t bytes = 4u << 20u; + constexpr std::size_t allocationBytes = bytes + 65536; #ifdef _WIN32 - void* block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + void* block = VirtualAlloc(nullptr, allocationBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); #else - void* block = std::aligned_alloc(65536, bytes); + void* block = std::aligned_alloc(65536, allocationBytes); #endif Require(block != nullptr, "cannot allocate the test block"); std::memset(block, 0x11, bytes); @@ -4048,6 +4284,11 @@ bool bdaStagingTests(const Device& device, Recorder& recorder) { 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(); + const bool changedCopy = std::getenv("APS5_NO_CHANGED_COPY_BACK") == nullptr; + auto* contested = static_cast(words + first + 70); + auto* untouched = static_cast(words + first + 100); + *contested = 0x11111133u; + *untouched = 0x44444444u; 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); @@ -4057,7 +4298,9 @@ bool bdaStagingTests(const Device& device, Recorder& recorder) { 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; + auto expected = at >= 64 && at < 80 ? 0x22222222u : 0x11111111u; + if (at == 70 && changedCopy) expected = 0x22222233u; + if (at == 100 && changedCopy) expected = 0x44444444u; 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"); @@ -4078,6 +4321,51 @@ bool bdaStagingTests(const Device& device, Recorder& recorder) { recorder.Sync(); memory.WriteBack(); } + std::memset(block, 0xff, allocationBytes); + { + std::cout << "checking a crossing write after an address-based staging candidate\n"; + GuestBufferMemory memory(context); + memory.AllowDeviceStaging(); + memory.AcquireRegistered(); + memory.AddWritable(element, elementBytes, false, false); + const auto crossing = address + bytes - 16; + const auto dissolved = AddressSpaceCounters().dissolvedOverlap; + memory.AddWritable(crossing, 32, false, false); + Require(AddressSpaceCounters().dissolvedOverlap == dissolved + 1, "the crossing write did not dissolve its cached address space"); + const auto ordinary = address + bytes + 4096; + memory.AddReadable(ordinary, 64); + memory.Upload(true); + std::uint32_t elementAdjustment = 0; + std::uint32_t crossingAdjustment = 0; + std::uint32_t ordinaryAdjustment = 0; + const auto elementView = memory.Descriptor(element, elementBytes, elementAdjustment); + const auto crossingView = memory.Descriptor(crossing, 32, crossingAdjustment); + const auto ordinaryView = memory.Descriptor(ordinary, 64, ordinaryAdjustment); + auto uploaded = std::make_shared(context, 96, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + auto downloaded = std::make_shared(context, 64, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const auto source = uploaded->Bytes(); + std::fill(source.begin(), source.end(), std::byte{0}); + TextureDetiler repack(context); + const auto commands = recorder.Commands(); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + repack.DispatchRepack(commands, uploaded->Handle(), 0, elementView.buffer, elementView.offset + elementAdjustment, 64); + repack.DispatchRepack(commands, uploaded->Handle(), 64, crossingView.buffer, crossingView.offset + crossingAdjustment, 32); + repack.DispatchRepack(commands, ordinaryView.buffer, ordinaryView.offset + ordinaryAdjustment, downloaded->Handle(), 0, 64); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT); + memory.RecordCopyBacks(recorder); + recorder.Sync(); + memory.MarkDirectWrites(); + memory.WriteBack(); + const auto contents = downloaded->Bytes().first(64); + Require(std::all_of(contents.begin(), contents.end(), [](auto value) { return value == std::byte{0xff}; }), "a plain read lost its GPU owner when a crossing write dissolved the staged address space"); + for (std::size_t index = 0; index < allocationBytes / 4; ++index) { + const auto byte = index * 4; + const bool written = (byte >= element - address && byte < element - address + 64) || (byte >= bytes - 16 && byte < bytes + 16); + const auto expected = written ? 0u : 0xffffffffu; + Require(words[index] == expected, "dissolving a staged address space lost a write or changed bytes outside its views"); + } + std::cout << "a crossing write dissolves the staged space while keeping the heap, written views and ordinary read GPU-backed\n"; + } { GuestAllocations::Mutation mutation; mutation.Remove(block); @@ -6136,6 +6424,18 @@ bool addressShadowTests(const Device& device, Recorder& recorder) { return true; } +#ifdef __APPLE__ +vm_prot_t HostPageProtection(const void* page) { + mach_vm_address_t address = reinterpret_cast(page); + mach_vm_size_t size = 0; + vm_region_basic_info_data_64_t info{}; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object = MACH_PORT_NULL; + Require(mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast(&info), &count, &object) == KERN_SUCCESS && address <= reinterpret_cast(page), "cannot read the protection of a page"); + return info.protection & (VM_PROT_READ | VM_PROT_WRITE); +} +#endif + bool smallTiledShadowLabelTests(const Device& device, Recorder& recorder) { using namespace AgcDriver::GuestMemory; constexpr std::size_t unit = 65536; @@ -6259,6 +6559,146 @@ bool smallTiledShadowLabelTests(const Device& device, Recorder& recorder) { settle(); const auto held = static_cast(std::count(memory + offset, memory + offset + layout.guestBytes, std::uint8_t{0x3c})); Require(held == 32u * 32u * 4u, "the label readback did not publish the small tiled image"); +#ifdef __APPLE__ + if (std::getenv("APS5_NO_LABEL_READ_TRAP") == nullptr) { + const auto rewriteImage = [&](const std::shared_ptr& target, std::uint32_t extent, std::uint8_t value) { + auto source = std::make_shared(context, extent * extent * 4u, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + std::fill(source->Bytes().begin(), source->Bytes().end(), std::byte{value}); + const auto commands = recorder.Commands(); + recorder.Keep(target); + recorder.Keep(source); + 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_WRITE_BIT); + VkBufferImageCopy copy{}; + copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + copy.imageExtent = {extent, extent, 1}; + context.Function("vkCmdCopyBufferToImage")(commands, source->Handle(), target->Image(), VK_IMAGE_LAYOUT_GENERAL, 1, ©); + 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); + target->MarkDirty(); + }; + const auto rewrite = [&](std::uint8_t value) { rewriteImage(image, 32u, value); }; + const auto readUnlocked = [&](const std::function& read) { + bool seen = false; + Require(GpuMutex().DepthOnThisThread() == 1, "the test thread holds the GPU mutex more than once"); + GpuMutex().unlock(); + std::thread reader([&] { seen = read(); }); + reader.join(); + GpuMutex().lock(); + return seen; + }; + const auto holds = [&](std::uint8_t value) { return static_cast(std::count(memory + offset, memory + offset + layout.guestBytes, value)) == 32u * 32u * 4u; }; + rewrite(0x4b); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Require(HostPageProtection(memory + offset) == VM_PROT_NONE, "a label stored a pending small tiled image instead of trapping its reads"); + Require(readUnlocked([&] { return holds(0x4b); }), "a read of a trapped image did not see its pending results"); + Require(HostPageProtection(memory + offset) != VM_PROT_NONE, "a resolved trap did not give back the image's pages"); + rewrite(0x2d); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Require(HostPageProtection(memory + offset) == VM_PROT_NONE, "a second label did not trap the small tiled image's reads"); + Require(StorageTexture::FlushPending(resource.baseAddress, static_cast(layout.guestBytes), nullptr, "memory access", PublishScope::None), "storing a trapped image stored nothing"); + settle(); + Require(HostPageProtection(memory + offset) != VM_PROT_NONE, "storing a trapped image did not release its trap"); + Require(holds(0x2d), "storing a trapped image did not publish it"); + + rewrite(0x5e); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + std::thread flusher([&] { static_cast(StorageTexture::FlushPending(resource.baseAddress, static_cast(layout.guestBytes), nullptr, "memory access", PublishScope::None)); }); + while (StorageTexture::AnyPending()) std::this_thread::yield(); + Require(HostPageProtection(memory + offset) == VM_PROT_NONE, "a trapped image taken for a store gave back its pages before the store"); + bool fresh = false; + std::thread waiting([&] { fresh = holds(0x5e); }); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + GpuMutex().unlock(); + flusher.join(); + waiting.join(); + GpuMutex().lock(); + Require(fresh, "a read while a trapped image waited for its store did not see the stored results"); + + const auto page = static_cast(::getpagesize()); + auto linearA = resource; + linearA.baseAddress = address + 0xd0000; + linearA.width = 8; + linearA.height = 8; + linearA.tileMode = TextureTileMode::kLinear; + const auto layoutA = DescribeSurface(linearA); + auto linearB = linearA; + linearB.baseAddress = linearA.baseAddress + ((layoutA.guestBytes + 255u) & ~std::uint64_t{255}); + const auto layoutB = DescribeSurface(linearB); + Require(linearA.baseAddress / page == (linearB.baseAddress + layoutB.guestBytes - 1) / page && linearB.baseAddress + layoutB.guestBytes <= address + 0xe0000, "the two small linear surfaces do not share a page"); + auto imageA = StorageTexture::Create(context, detiler, linearA, 0); + auto imageB = StorageTexture::Create(context, detiler, linearB, 0); + imageA->Refresh(); + imageB->Refresh(); + settle(); + auto* pageBytes = memory + (linearA.baseAddress - address); + const auto holdsIn = [&](const GuestTextureResource& surface, std::size_t bytes, std::uint8_t value) { return static_cast(std::count(memory + (surface.baseAddress - address), memory + (surface.baseAddress - address) + bytes, value)) >= 8u * 8u * 4u; }; + rewriteImage(imageA, 8u, 0x61); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Require(HostPageProtection(pageBytes) == VM_PROT_NONE, "a label did not trap a lone pending linear image"); + rewriteImage(imageB, 8u, 0x62); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + settle(); + Require(HostPageProtection(pageBytes) != VM_PROT_NONE, "a label left a trap on a page it stored another image on"); + Require(holdsIn(linearA, static_cast(layoutA.guestBytes), 0x61) && holdsIn(linearB, static_cast(layoutB.guestBytes), 0x62), "a label did not store both images on a shared page"); + rewriteImage(imageA, 8u, 0x63); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Require(HostPageProtection(pageBytes) == VM_PROT_NONE, "a label did not trap a lone pending linear image again"); + rewriteImage(imageB, 8u, 0x64); + Require(StorageTexture::FlushPending(linearB.baseAddress, static_cast(layoutB.guestBytes), nullptr, "idle", PublishScope::None), "a CPU store next to a trapped image stored nothing"); + settle(); + Require(HostPageProtection(pageBytes) != VM_PROT_NONE && holdsIn(linearB, static_cast(layoutB.guestBytes), 0x64), "a CPU store next to a trapped image did not store through its page"); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Require(HostPageProtection(pageBytes) == VM_PROT_NONE, "a label did not trap the image a neighbouring CPU store left pending"); + Require(readUnlocked([&] { return holdsIn(linearA, static_cast(layoutA.guestBytes), 0x63); }), "a read of the re-trapped image did not see its pending results"); + + auto outer = resource; + outer.baseAddress = address; + outer.width = 256; + outer.height = 256; + outer.tileMode = TextureTileMode::kR64KBX; + const auto outerLayout = DescribeSurface(outer); + Require(outerLayout.guestBytes > StorageTexture::SmallStoreBytes && outer.baseAddress + outerLayout.guestBytes <= resource.baseAddress, "the large tiled surface does not fit below the small one"); + auto inner = linearA; + inner.baseAddress = address + 0x20000; + const auto innerLayout = DescribeSurface(inner); + auto outerImage = StorageTexture::Create(context, detiler, outer, 0); + auto innerImage = StorageTexture::Create(context, detiler, inner, 0); + outerImage->Refresh(); + innerImage->Refresh(); + settle(); + auto* innerBytes = memory + (inner.baseAddress - address); + const auto innerHolds = [&](std::uint8_t value) { return holdsIn(inner, static_cast(innerLayout.guestBytes), value); }; + const auto storeOuterBlock = [&] { + Require(StorageTexture::FlushPending(address + 0x28000, 256, nullptr, "memory access", PublishScope::None), "a store of the large image's block stored nothing"); + settle(); + }; + rewriteImage(outerImage, 256u, 0x71); + rewriteImage(innerImage, 8u, 0x72); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Require(HostPageProtection(innerBytes) == VM_PROT_NONE, "a label stored a small image inside a pending large tiled one instead of trapping its reads"); + storeOuterBlock(); + Require(HostPageProtection(innerBytes) != VM_PROT_NONE, "a store of a large image's block left a trapped image inside it trapped"); + Require(innerHolds(0x72), "a store of a large image's block over a trapped image did not leave the bytes a label store leaves"); + rewriteImage(outerImage, 256u, 0x73); + rewriteImage(innerImage, 8u, 0x75); + rewriteImage(outerImage, 256u, 0x74); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Require(HostPageProtection(innerBytes) == VM_PROT_NONE, "a label did not trap the small image again"); + storeOuterBlock(); + Require(HostPageProtection(innerBytes) != VM_PROT_NONE && innerHolds(0x75), "a large image written again after a trapped one was stored before it"); + rewriteImage(outerImage, 256u, 0x76); + rewriteImage(innerImage, 8u, 0x77); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Require(HostPageProtection(innerBytes) == VM_PROT_NONE, "a label did not trap the small image a third time"); + outerImage->Flush(); + settle(); + Require(HostPageProtection(innerBytes) != VM_PROT_NONE && innerHolds(0x77), "evicting a large image did not store the trapped image inside it first"); + rewriteImage(innerImage, 8u, 0x78); + StorageTexture::StoreReadbackBeforeLabel("readback before label"); + Require(HostPageProtection(innerBytes) == VM_PROT_NONE, "a label did not trap the small image after the large one was evicted"); + Require(readUnlocked([&] { return innerHolds(0x78); }), "a read of a trapped image inside an evicted large one did not see its results"); + } +#endif auto large = resource; large.baseAddress = address + 0x80000; large.width = 256; @@ -6661,6 +7101,70 @@ void parkedImportUnmapTests(const Device& device) { std::cout << "parked import unmap: ok\n"; } +void windowImportUnmapTests(const Device& device) { + const auto& context = device.GetContext(); + if (context.hostImportAlignment == 0) { + std::cout << "host imports unavailable: window import unmap not tested\n"; + return; + } + const auto alignment = static_cast(std::max(65536u, context.hostImportAlignment)); + const auto window = alignment * 2u; + const auto bytes = window * 4u; +#ifdef _WIN32 + void* block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); +#else + void* block = std::aligned_alloc(alignment, bytes); +#endif + Require(block != nullptr, "cannot allocate the window import unmap block"); + const auto release = [&] { +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + }; + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true, true); + } + SetHostImportRangeWindow(window); + const HostImport* imported = nullptr; + { + std::lock_guard gpu(GpuMutex()); + imported = HostImportFor(context, address, alignment); + } + const bool windowed = imported != nullptr && imported->base == address && imported->bytes == window; + if (imported == nullptr) { + SetHostImportRangeWindow(64ull << 20u); + GuestAllocations::Mutation mutation; + mutation.Remove(block); + release(); + std::cout << "host import refused: window import unmap not tested\n"; + return; + } + auto* tail = static_cast(block) + 2u * window; + bool unmapped = false; + std::string failure; + try { + GuestAllocations::Mutation mutation; + mutation.Unmap(tail, window, [&](const void*, std::size_t, const void*, bool) { unmapped = true; }); + } catch (const std::exception& error) { + failure = error.what(); + } + SetHostImportRangeWindow(64ull << 20u); + ClearHostImports(context.device); + { + GuestAllocations::Mutation mutation; + mutation.Unmap(block, bytes, [](const void*, std::size_t, const void*, bool) {}); + } + release(); + Require(windowed, "the access to the first window of a large range did not import that window alone"); + Require(failure.empty(), "guest unmap of a window another window's import pins: " + failure); + Require(unmapped, "guest unmap of a window another window's import pins did not run"); + std::cout << "window import unmap: ok\n"; +} + void importWatchTests(const Device& device) { using namespace AgcDriver::GuestMemory; const auto& context = device.GetContext(); @@ -6896,6 +7400,807 @@ void staleGenerationTests(const Device& device, Recorder& recorder) { #endif } +#ifdef _WIN32 +bool FailPendingCollect(std::uintptr_t, std::size_t, void (*)(void*, std::uintptr_t, std::uintptr_t), void*) { + return false; +} +#endif + +bool pendingAfterLeavingWatchTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (base.hostImportAlignment == 0) { + std::cout << "host imports unavailable: results pending after a block left the watch not tested\n"; + return false; + } + constexpr std::size_t unit = 65536; + const auto pattern = [](std::size_t i) { return static_cast(i * 131u + 7u); }; + const auto decided = PrepareImportWatch(base); + SetImportWatch(base, ImportWatch::Watch); + struct Restore { + const Context& context; + ImportWatch decided; + ~Restore() { SetImportWatch(context, decided); } + } restore{base, decided}; + enum class Write { None, AfterPending, BeforePending, UnwatchedFullClear, UnwatchedPartialWrite, BeforeUnwatch, UnwatchedWithoutHook, PendingWithoutHook, UnprovenUnwatch, LateUnwatchedWrite, LateBeforeUnwatch, LateWithoutHook, LateUnproven }; + for (const std::uint32_t side : {256u, 1024u}) + for (const auto write : {Write::None, Write::AfterPending, Write::BeforePending, Write::UnwatchedFullClear, Write::UnwatchedPartialWrite, Write::BeforeUnwatch, Write::UnwatchedWithoutHook, Write::PendingWithoutHook, Write::UnprovenUnwatch, Write::LateUnwatchedWrite, Write::LateBeforeUnwatch, Write::LateWithoutHook, Write::LateUnproven}) { +#ifndef _WIN32 + if (write == Write::UnprovenUnwatch || write == Write::LateUnproven) continue; +#endif + const std::size_t surfaceBytes = static_cast(side) * side * 4; + const std::size_t units = surfaceBytes / unit; + const std::size_t lastUnit = units - 1; + const bool large = side > 256; + const bool unwatchedWrite = write == Write::UnwatchedFullClear || write == Write::UnwatchedPartialWrite; + const bool late = write == Write::LateUnwatchedWrite || write == Write::LateBeforeUnwatch || write == Write::LateWithoutHook || write == Write::LateUnproven; + const bool lateUnwatch = write == Write::LateBeforeUnwatch || write == Write::LateUnproven; + const bool partial = write == Write::UnwatchedPartialWrite || late; + const bool unitWritten = unwatchedWrite || write == Write::BeforeUnwatch || late; + const bool unhashed = write == Write::UnwatchedWithoutHook || write == Write::PendingWithoutHook || write == Write::UnprovenUnwatch; + const bool pageWritten = write == Write::AfterPending || write == Write::BeforePending; + const std::string what = std::string(large ? "1024x1024, " : "") + (write == Write::None ? "no CPU write" : write == Write::AfterPending ? "a CPU write after the results were pending again" : write == Write::BeforePending ? "a CPU write before the results were pending again" : write == Write::UnwatchedFullClear ? "a CPU write into the unwatched block before results that cover it" : write == Write::UnwatchedPartialWrite ? "a CPU write into the unwatched block before results that cover part of it" : write == Write::BeforeUnwatch ? "a CPU write into the last block while it was still watched, before it left the watch" : write == Write::UnwatchedWithoutHook ? "the last block left the watch without the unwatch hook" : write == Write::PendingWithoutHook ? "the last block left the watch without the unwatch hook while the results were pending" : write == Write::LateUnwatchedWrite ? "a CPU write into the unwatched block after results that cover part of it were written" : write == Write::LateBeforeUnwatch ? "a CPU write into the last block after results that cover part of it were written, before it left the watch" : write == Write::LateWithoutHook ? "a CPU write into the last block after it left the watch without the unwatch hook and results that cover part of it were written" : write == Write::LateUnproven ? "a CPU write into the last block after results that cover part of it were written, before it left the watch through a failed collect" : "the last block left the watch through a failed collect"); + const std::string cause = !large && late ? "its bytes differ from the copy taken at its upload" : write == Write::LateUnwatchedWrite || (large && unwatchedWrite) ? "its bytes no longer match the hash taken when its range left the watch" : unwatchedWrite ? "its bytes differ from the copy taken at its upload" : write == Write::BeforeUnwatch || write == Write::LateBeforeUnwatch ? (large ? "the write watch saw CPU writes to it after its generation, before its range left the watch" : "its bytes differ from the copy taken at its upload") : write == Write::UnwatchedWithoutHook || write == Write::LateWithoutHook ? "no hash of its bytes was taken for its current generation" : write == Write::PendingWithoutHook ? "its results became pending while its range was watched, and its bytes were not saved when the range left the watch" : "its range left the watch through the unwatch hook without its CPU writes proven"; + void* block = AllocateWatched(surfaceBytes, unit); + if (block == nullptr) { + std::cout << "no write watching: results pending after a block left the watch not tested\n"; + return false; + } + auto* memory = static_cast(block); + std::memset(memory, 0x5a, surfaceBytes); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, surfaceBytes, true, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + std::size_t surfaceBytes; + ~Unregister() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, surfaceBytes); + ReleaseWatched(block, surfaceBytes); + } + } unregister{base, block, address, surfaceBytes}; + AgcDriver::GuestMemory::CollectWritesUncached(address, surfaceBytes); + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = side; + resource.height = side; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kR64KBX; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + Require(DescribeSurface(resource).guestBytes == surfaceBytes, "(p) unexpected surface size"); + { + auto image = StorageTexture::Create(context, detiler, resource, 0); + recorder.Keep(image); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + const auto clear = [&](const VkClearColorValue& value) { + const auto commands = recorder.Commands(); + 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(); + }; + const auto store = [&] { + image->WriteBack(); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + }; + const auto writePage = [&] { + for (std::size_t i = 0; i < 4096; ++i) memory[(units - 2) * unit + i] = pattern(i); + }; + const bool onPath = AgcDriver::GuestMemory::Watched(address, surfaceBytes) && HostImportFor(context, address, surfaceBytes) != nullptr; +#ifdef _WIN32 + if (!onPath) { + std::cout << "the image's host import left its memory unwatched: results pending after a block left the watch not tested\n"; + return false; + } +#else + Require(onPath, "(p) " + what + ": the image is not on the path under test"); +#endif + Require(!image->GuestSnapshotValid(), "(p) " + what + ": the upload left saved guest bytes while its memory was watched"); + const auto cpuUnit = [](std::size_t i) { return i % 4 == 0 ? static_cast(0x11) : static_cast(i * 131u + 7u); }; + if (write == Write::BeforeUnwatch) { + for (std::size_t i = 0; i < unit; ++i) memory[lastUnit * unit + i] = cpuUnit(i); + } + const auto unwatchLast = [&] { + if (write == Write::UnprovenUnwatch || write == Write::LateUnproven) { +#ifdef _WIN32 + struct Unhook { + ~Unhook() { AgcDriver::GuestMemory::SetFreshCollectHook(nullptr); } + } unhook; + AgcDriver::GuestMemory::SetFreshCollectHook(&FailPendingCollect); + Require(AgcDriver::GuestMemory::MarkWritten(address + lastUnit * unit, 4) == 0, "(p) " + what + ": the failed collect produced trusted stamps"); +#endif + } else if (write == Write::UnwatchedWithoutHook || write == Write::PendingWithoutHook || write == Write::LateWithoutHook) { + struct Rehook { + ~Rehook() { AgcDriver::GuestMemory::SetUnwatchHook(&StorageTexture::SaveBeforeUnwatch); } + } rehook; + AgcDriver::GuestMemory::SetUnwatchHook(nullptr); + AgcDriver::GuestMemory::Unwatch(address + lastUnit * unit, unit); + } else { + AgcDriver::GuestMemory::Unwatch(address + lastUnit * unit, unit); + } + Require(!AgcDriver::GuestMemory::Watched(address, surfaceBytes) && AgcDriver::GuestMemory::Watched(address, lastUnit * unit), "(p) " + what + ": the last block did not leave the watch alone"); + }; + if (write != Write::PendingWithoutHook && !lateUnwatch) unwatchLast(); + if (write == Write::BeforePending) writePage(); + if (unwatchedWrite) { + for (std::size_t i = 0; i < unit; ++i) memory[lastUnit * unit + i] = cpuUnit(i); + } + if (partial) { + constexpr std::uint32_t patch = 16; + Buffer staging(context, patch * patch * 4, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + const auto bytes = staging.Bytes(); + for (std::size_t at = 0; at < patch * patch * 4; at += 4) { + bytes[at] = std::byte{0}; + bytes[at + 1] = std::byte{255}; + bytes[at + 2] = std::byte{0}; + bytes[at + 3] = std::byte{255}; + } + const auto commands = recorder.Commands(); + 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); + VkBufferImageCopy copy{}; + copy.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + copy.imageOffset = {static_cast(side - 64), static_cast(side - 64), 0}; + copy.imageExtent = {patch, patch, 1}; + context.Function("vkCmdCopyBufferToImage")(commands, staging.Handle(), image->Image(), VK_IMAGE_LAYOUT_GENERAL, 1, ©); + 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); + if (late) { + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + for (std::size_t i = 0; i < unit; ++i) memory[lastUnit * unit + i] = cpuUnit(i); + if (lateUnwatch) unwatchLast(); + } + image->MarkDirty(); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + } else { + clear(VkClearColorValue{{0.0f, 1.0f, 0.0f, 1.0f}}); + } + if (write == Write::PendingWithoutHook) unwatchLast(); + if (write == Write::AfterPending) writePage(); + bool refused = false; + std::string refusal; + try { + store(); + } catch (const std::exception& error) { + try { + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + } catch (...) { + } + if (!unitWritten && !unhashed) throw; + refused = true; + refusal = error.what(); + static_cast(StorageTexture::DiscardPendingInside(address, surfaceBytes)); + } catch (...) { + try { + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + } catch (...) { + } + throw; + } + if (refused) { + const std::string unitCause = "(unit " + std::to_string(write == Write::PendingWithoutHook ? 0 : lastUnit) + " of " + std::to_string(units) + ": " + cause; + Require(refusal.find(unitCause) != std::string::npos, "(p) " + what + ": the refusal does not name the cause '" + unitCause + "': " + refusal); + } + if (unhashed && refused) { + std::cout << "(p) " + what + ": refused rather than stored\n"; + for (std::size_t i = 0; i < unit; ++i) Require(memory[lastUnit * unit + i] == 0x5a, "(p) " + what + ": the refused write-back changed the guest's bytes"); + recorder.Sync(); + continue; + } + if (unitWritten) { + if (refused) { + std::cout << "(p) " + what + ": refused rather than stored\n"; + for (std::size_t i = 0; i < unit; ++i) Require(memory[lastUnit * unit + i] == cpuUnit(i), "(p) " + what + ": the refused write-back changed the CPU's bytes"); + recorder.Sync(); + continue; + } + std::vector storedUnit(unit); + AgcDriver::GuestMemory::Read(address + lastUnit * unit, storedUnit); + std::size_t greens = 0; + for (std::size_t at = 0; at < unit; at += 4) { + const bool green = std::to_integer(storedUnit[at]) == 0 && std::to_integer(storedUnit[at + 1]) == 255 && std::to_integer(storedUnit[at + 2]) == 0 && std::to_integer(storedUnit[at + 3]) == 255; + bool cpu = true; + for (std::size_t c = 0; c < 4; ++c) cpu = cpu && std::to_integer(storedUnit[at + c]) == cpuUnit(at + c); + Require(green || cpu, "(p) " + what + ": a texel the GPU did not write holds neither the CPU's bytes nor a result, at +0x" + std::to_string(at)); + greens += green; + } + if (!partial) Require(greens == unit / 4, "(p) " + what + ": " + std::to_string(greens) + " of " + std::to_string(unit / 4) + " words took the GPU result"); + else Require(greens == 16 * 16, "(p) " + what + ": " + std::to_string(greens) + " words took the GPU result, want 256"); + std::vector rest(lastUnit * unit); + AgcDriver::GuestMemory::Read(address, rest); + for (std::size_t at = 0; at < lastUnit * unit; at += 4) { + const bool green = std::to_integer(rest[at]) == 0 && std::to_integer(rest[at + 1]) == 255 && std::to_integer(rest[at + 2]) == 0 && std::to_integer(rest[at + 3]) == 255; + if (!partial) Require(green, "(p) " + what + ": the watched units did not take the GPU result at +0x" + std::to_string(at)); + else Require(std::to_integer(rest[at]) == 0x5a, "(p) " + what + ": the watched units changed where the GPU wrote nothing, at +0x" + std::to_string(at)); + } + recorder.Sync(); + continue; + } + std::vector read(surfaceBytes); + AgcDriver::GuestMemory::Read(address, read); + constexpr std::array green{0, 255, 0, 255}; + const auto isGreen = [&](std::size_t at) { + for (std::size_t c = 0; c < 4; ++c) { + if (std::to_integer(read[at + c]) != green[c]) return false; + } + return true; + }; + std::string unitSummary; + for (std::size_t u = 0; u < units; ++u) { + std::size_t greens = 0, untouched = 0, other = 0, rawGreens = 0; + for (std::size_t at = u * unit; at < (u + 1) * unit; at += 4) { + if (isGreen(at)) ++greens; + else if (std::to_integer(read[at]) == 0x5a) ++untouched; + else ++other; + if (memory[at] == green[0] && memory[at + 1] == green[1] && memory[at + 2] == green[2] && memory[at + 3] == green[3]) ++rawGreens; + } + unitSummary += " unit " + std::to_string(u) + ": " + std::to_string(greens) + " green words, " + std::to_string(untouched) + " untouched, " + std::to_string(other) + " other, " + std::to_string(rawGreens) + " green in the raw memory;"; + } + for (std::size_t at = 0; at < surfaceBytes; at += 4) { + if (pageWritten && at >= (units - 2) * unit && at < lastUnit * unit) continue; + Require(isGreen(at), "(p) " + what + ": GPU results were not stored at +0x" + std::to_string(at) + ";" + unitSummary); + } + if (pageWritten) { + for (std::size_t i = 0; i < 4096; ++i) Require(std::to_integer(read[(units - 2) * unit + i]) == pattern(i), "(p) " + what + ": the GPU results overwrote the CPU's bytes;" + unitSummary); + } + } + recorder.Sync(); + } + return true; +} + +struct GateBlock { + void* block = nullptr; + std::size_t bytes = 0; + std::vector registered; + + std::uint64_t Address() const { return reinterpret_cast(block); } + + GuestAllocations::Mapped Register(std::uint64_t offset, std::size_t length) { + auto* at = static_cast(block) + offset; + { + GuestAllocations::Mutation mutation; + mutation.Add(at, length, true, true, true); + } + registered.push_back(at); + GuestAllocations::Mapped mapped; + for (const auto& range : GuestAllocations::GuestAllocationsAcquire_nid_postfix()) { + if (range->address == reinterpret_cast(at)) mapped.push_back(range); + } + Require(mapped.size() == 1, "(gate) a mapped range is not registered"); + return mapped; + } + + void Unregister(const Context& context) { + for (auto* at : registered) { + GuestAllocations::Mutation mutation; + mutation.Remove(at); + } + registered.clear(); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + HostImportFor(context, Address(), bytes); + } +}; + +struct GateFixture { + const Context& base; + ImportWatch decided; + TextureDetiler detiler; + Context context; + std::deque blocks; + + explicit GateFixture(const Context& base) : base(base), decided(PrepareImportWatch(base)), detiler(base), context(base) { + context.detiler = &detiler; + SetImportWatch(base, ImportWatch::Unwatch); + } + + ~GateFixture() { + AgcDriver::GuestMemory::SetUnwatchHook(&StorageTexture::SaveBeforeUnwatch); + StorageTexture::SetPublishHookForTest(nullptr); + MapImportGate::SetUnlockedHookForTest(nullptr); + for (auto& block : blocks) { + block.Unregister(context); + ReleaseWatched(block.block, block.bytes); + } + SetImportWatch(base, decided); + } + + GateBlock* Allocate(std::size_t bytes, std::size_t alignment) { + void* block = AllocateWatched(bytes, alignment); + if (block == nullptr) return nullptr; + std::memset(block, 0x5a, bytes); + AgcDriver::GuestMemory::CollectWritesUncached(reinterpret_cast(block), bytes); + blocks.push_back({block, bytes, {}}); + return &blocks.back(); + } + + bool Adopt() { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + return ImportMappedRanges(context, {}, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true); + } + + std::shared_ptr Image(std::uint64_t address, std::uint32_t side) { + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = side; + resource.height = side; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kR64KBX; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + Require(DescribeSurface(resource).guestBytes == static_cast(side) * side * 4, "(gate) unexpected surface size"); + return StorageTexture::Create(context, detiler, resource, 0); + } +}; + +struct GateWatchdog { + std::atomic finished{false}; + std::thread thread; + + GateWatchdog(const char* what, int seconds) { + thread = std::thread([this, what, seconds] { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(seconds); + while (!finished.load(std::memory_order_acquire)) { + if (std::chrono::steady_clock::now() > deadline) { + std::cerr << what << ": deadlock, a thread did not finish within " << seconds << " s\n"; + std::cerr.flush(); + std::_Exit(1); + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + }); + } + + ~GateWatchdog() { + finished.store(true, std::memory_order_release); + thread.join(); + } +}; + +bool GateUnavailable(const Context& context, const char* what) { + if (context.hostImportAlignment != 0 && AgcDriver::GuestMemory::WriteWatched()) return false; + std::cout << "host imports or write watching unavailable: " << what << " not tested\n"; + return true; +} + +std::atomic gateStage{0}; + +bool AwaitGate(const std::function& done, int seconds = 5) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(seconds); + while (!done()) { + if (std::chrono::steady_clock::now() > deadline) return false; + std::this_thread::yield(); + } + return true; +} + +void GateUnwatchHook(std::uint64_t address, std::size_t bytes); + +void GatePublishHook() { + AgcDriver::GuestMemory::SetUnwatchHook(&GateUnwatchHook); + gateStage.store(1, std::memory_order_release); + Require(AwaitGate([] { return MapImportGate::DrainingImportsForTest() != 0 || gateStage.load(std::memory_order_acquire) >= 2; }), "(g) the map-time import neither waited for the publication nor reached its unwatch hook"); +} + +std::function gateHookRelease; +std::atomic gateHookTimedOut{false}; + +void GateUnwatchHook(std::uint64_t address, std::size_t bytes) { + int expected = 1; + if (gateStage.compare_exchange_strong(expected, 2, std::memory_order_acq_rel) && gateHookRelease && !AwaitGate(gateHookRelease)) gateHookTimedOut.store(true, std::memory_order_release); + StorageTexture::SaveBeforeUnwatch(address, bytes); +} + +void GateUnlockedHook() { + int expected = 1; + if (gateStage.compare_exchange_strong(expected, 2, std::memory_order_acq_rel) && gateHookRelease && !AwaitGate(gateHookRelease)) gateHookTimedOut.store(true, std::memory_order_release); +} + +bool mapGatePublicationTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (GateUnavailable(base, "a publication between the map-time check and the unwatch hook")) return false; + GateFixture fixture(base); + auto* block = fixture.Allocate(4 << 20, 65536); + if (block == nullptr) return false; + const auto mapped = block->Register(0, 512 * 1024); + Require(fixture.Adopt(), "(g) the device was not adopted"); + const auto before = MapImportGate::CountersForTest(); + gateStage.store(0); + gateHookTimedOut.store(false); + gateHookRelease = [] { return gateStage.load(std::memory_order_acquire) >= 3; }; + StorageTexture::SetPublishHookForTest(&GatePublishHook); + std::atomic deferred{-1}; + std::shared_ptr image; + { + GateWatchdog watchdog("(g) publication between the check and the hook", 20); + std::thread mapping([&] { + if (!AwaitGate([] { return gateStage.load(std::memory_order_acquire) >= 1; })) return; + deferred.store(ImportMappedRanges(fixture.context, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), false) ? 0 : 1, std::memory_order_release); + }); + image = fixture.Image(block->Address(), 1024); + StorageTexture::SetPublishHookForTest(nullptr); + gateStage.store(3, std::memory_order_release); + HostImportFor(fixture.context, block->Address(), 512 * 1024); + mapping.join(); + } + recorder.Keep(image); + const auto after = MapImportGate::CountersForTest(); + Require(!gateHookTimedOut.load(), "(g) the unwatch hook was not released"); + Require(deferred.load() == 1, "(g) the map-time import ran its unwatch hook over a storage image published after its check"); + Require(after.drainWaits > before.drainWaits && after.fastPublications > before.fastPublications, "(g) the map-time import did not wait for the publication on the fast path"); + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Require(ImportMappedRanges(fixture.context, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true), "(g) the deferred import failed under the GPU lock"); + Require(HostImportFor(fixture.context, block->Address(), 512 * 1024) != nullptr, "(g) the mapped range was not imported"); + } + std::cout << "map-time import gate: publication between the check and the hook passed\n"; + return true; +} + +bool mapGatePendingTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (GateUnavailable(base, "units becoming pending between the map-time check and the unwatch hook")) return false; + GateFixture fixture(base); + auto* block = fixture.Allocate(2 << 20, 65536); + if (block == nullptr) return false; + const auto mapped = block->Register(0, 512 * 1024); + Require(fixture.Adopt(), "(p) the device was not adopted"); + auto image = fixture.Image(block->Address(), 512); + recorder.Keep(image); + const std::array, 1> span{{{block->Address(), block->Address() + (1 << 20)}}}; + Require(!StorageTexture::UnwatchHookLocksGpu(span), "(p) the clean 1 MiB image already makes the unwatch hook take the GPU lock"); + const auto before = MapImportGate::CountersForTest(); + gateStage.store(1); + gateHookTimedOut.store(false); + gateHookRelease = [&] { return MapImportGate::WaitingPublishersForTest() != 0 || StorageTexture::AnyPendingOverlaps(span); }; + AgcDriver::GuestMemory::SetUnwatchHook(&GateUnwatchHook); + std::atomic deferred{-1}; + { + GateWatchdog watchdog("(p) units pending between the check and the hook", 20); + std::thread mapping([&] { deferred.store(ImportMappedRanges(fixture.context, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), false) ? 0 : 1, std::memory_order_release); }); + Require(AwaitGate([] { return gateStage.load(std::memory_order_acquire) >= 2; }), "(p) the map-time import did not reach its unwatch hook"); + image->MarkDirty(); + HostImportFor(fixture.context, block->Address(), 512 * 1024); + mapping.join(); + } + const auto after = MapImportGate::CountersForTest(); + Require(!gateHookTimedOut.load(), "(p) the unwatch hook was not released"); + Require(deferred.load() == 0, "(p) the map-time import was deferred"); + Require(after.waitedPublications > before.waitedPublications, "(p) the pending transition did not wait for the map-time import"); + Require(!AgcDriver::GuestMemory::Watched(block->Address(), 512 * 1024), "(p) the mapped range is still watched"); + Require(StorageTexture::AnyPendingOverlaps(span), "(p) the image's results are not pending"); + std::cout << "map-time import gate: units pending between the check and the hook passed\n"; + return true; +} + +bool mapGateFastPathTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (GateUnavailable(base, "the map-time import gate's fast path")) return false; + GateFixture fixture(base); + auto* block = fixture.Allocate(4 << 20, 65536); + if (block == nullptr) return false; + const auto before = MapImportGate::CountersForTest(); + auto image = fixture.Image(block->Address(), 1024); + recorder.Keep(image); + image->MarkDirty(); + image->MarkDirty(); + const auto after = MapImportGate::CountersForTest(); + Require(after.fastPublications == before.fastPublications + 2, "(f) a creation and a first pending transition did not take the fast path"); + Require(after.stripedPublications == before.stripedPublications && after.waitedPublications == before.waitedPublications && after.importHolds == before.importHolds, "(f) a publication took a stripe without a map-time import in flight"); + std::cout << "map-time import gate: fast path passed\n"; + return true; +} + +bool mapGateDisjointTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (GateUnavailable(base, "map-time import gate stripes of disjoint ranges")) return false; + GateFixture fixture(base); + auto* mappedBlock = fixture.Allocate(2 << 20, 65536); + if (mappedBlock == nullptr) return false; + GateBlock* other = nullptr; + for (int attempt = 0; attempt < 8 && other == nullptr; ++attempt) { + auto* candidate = fixture.Allocate(4 << 20, 65536); + Require(candidate != nullptr, "(d) no second watched block"); + if ((MapImportGate::StripesOf(candidate->Address(), candidate->Address() + (4 << 20)) & MapImportGate::StripesOf(mappedBlock->Address(), mappedBlock->Address() + (2 << 20))).none()) other = candidate; + } + Require(other != nullptr, "(d) no watched block on other stripes"); + const auto mapped = mappedBlock->Register(0, 512 * 1024); + Require(fixture.Adopt(), "(d) the device was not adopted"); + auto warm = fixture.Image(other->Address() + (3 << 20), 256); + recorder.Keep(warm); + std::atomic created{false}; + gateStage.store(1); + gateHookTimedOut.store(false); + gateHookRelease = [&] { return created.load(std::memory_order_acquire); }; + MapImportGate::SetUnlockedHookForTest(&GateUnlockedHook); + const auto before = MapImportGate::CountersForTest(); + std::shared_ptr image; + { + GateWatchdog watchdog("(d) disjoint stripes", 20); + std::thread mapping([&] { ImportMappedRanges(fixture.context, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), false); }); + Require(AwaitGate([] { return gateStage.load(std::memory_order_acquire) >= 2; }), "(d) the map-time import did not take its stripes"); + image = fixture.Image(other->Address(), 1024); + created.store(true, std::memory_order_release); + mapping.join(); + } + MapImportGate::SetUnlockedHookForTest(nullptr); + recorder.Keep(image); + const auto after = MapImportGate::CountersForTest(); + Require(!gateHookTimedOut.load(), "(d) a creation over other stripes did not complete while the map-time import held its own"); + Require(after.stripedPublications == before.stripedPublications + 1 && after.waitedPublications == before.waitedPublications, "(d) a creation over other stripes waited for the map-time import or skipped its stripes"); + std::cout << "map-time import gate: disjoint stripes passed\n"; + return true; +} + +GateBlock* gateRetryBlock = nullptr; +std::atomic gateRetryHooks{0}; + +void GateRetryHook() { + if (gateRetryHooks.fetch_add(1) == 0) static_cast(gateRetryBlock->Register(2 << 20, 1 << 20)); +} + +bool mapGateRetryTests(const Device& device, Recorder& recorder) { + static_cast(recorder); + const auto& base = device.GetContext(); + if (GateUnavailable(base, "a map-time import run growing past its gate stripes")) return false; + GateFixture fixture(base); + GateBlock* block = nullptr; + for (int attempt = 0; attempt < 8 && block == nullptr; ++attempt) { + auto* candidate = fixture.Allocate(4 << 20, 1 << 21); + if (candidate == nullptr) return false; + if (candidate->Address() % (1 << 21) == 0) block = candidate; + } + Require(block != nullptr, "(r) no watched block aligned to a stripe"); + auto mapped = block->Register((3 << 20) / 2, 512 * 1024); + Require(fixture.Adopt(), "(r) the device was not adopted"); + gateRetryBlock = block; + gateRetryHooks.store(0); + MapImportGate::SetUnlockedHookForTest(&GateRetryHook); + const auto before = MapImportGate::CountersForTest(); + bool imported = false; + { + GateWatchdog watchdog("(r) run growing past its stripes", 20); + std::thread mapping([&] { imported = ImportMappedRanges(fixture.context, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), false); }); + mapping.join(); + } + MapImportGate::SetUnlockedHookForTest(nullptr); + const auto after = MapImportGate::CountersForTest(); + Require(imported, "(r) the map-time import was deferred"); + Require(gateRetryHooks.load() == 2 && after.retries == before.retries + 1, "(r) the grown run did not take the stripes again"); + Require(HostImportCovers(fixture.context, block->Address() + (3 << 20) / 2, (3 << 20) / 2), "(r) the grown run was not imported whole"); + std::cout << "map-time import gate: run growing past its stripes passed\n"; + return true; +} + +bool mapGateStressTests(const Device& device) { + const auto& base = device.GetContext(); + if (GateUnavailable(base, "the map-time import gate under load")) return false; + GateFixture fixture(base); + constexpr int slots = 4; + std::array blocks{}; + for (auto& block : blocks) { + block = fixture.Allocate(4 << 20, 65536); + if (block == nullptr) return false; + } + Require(fixture.Adopt(), "(s) the device was not adopted"); + std::array>, slots> images; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3); + std::atomic creations{0}; + std::atomic imports{0}; + std::atomic deferrals{0}; + std::exception_ptr failure; + std::mutex failureMutex; + const auto guard = [&](auto&& body) { + try { + body(); + } catch (...) { + std::lock_guard lock(failureMutex); + if (!failure) failure = std::current_exception(); + } + }; + const auto before = MapImportGate::CountersForTest(); + { + GateWatchdog watchdog("(s) map-time import gate under load", 40); + std::vector threads; + for (int worker = 0; worker < 2; ++worker) { + threads.emplace_back([&, worker] { + guard([&] { + std::minstd_rand random(static_cast(17 + worker)); + while (std::chrono::steady_clock::now() < deadline) { + const auto slot = static_cast(random() % slots); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + auto& kept = images[static_cast(slot)]; + switch (random() % 4) { + case 0: + kept.push_back(fixture.Image(blocks[static_cast(slot)]->Address(), random() % 2 == 0 ? 1024 : 512)); + creations.fetch_add(1, std::memory_order_relaxed); + if (kept.size() > 2) kept.erase(kept.begin()); + break; + case 1: + if (!kept.empty()) kept.back()->MarkDirty(); + break; + case 2: + if (!kept.empty()) kept.back()->Flush(); + break; + default: + HostImportFor(fixture.context, blocks[static_cast(slot)]->Address(), 512 * 1024); + break; + } + } + }); + }); + } + for (int mapper = 0; mapper < 2; ++mapper) { + threads.emplace_back([&, mapper] { + guard([&] { + std::minstd_rand random(static_cast(101 + mapper)); + while (std::chrono::steady_clock::now() < deadline) { + auto* block = blocks[static_cast(mapper + 2 * static_cast(random() % 2))]; + const auto mapped = block->Register(0, 512 * 1024); + if (ImportMappedRanges(fixture.context, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), false)) { + imports.fetch_add(1, std::memory_order_relaxed); + } else { + deferrals.fetch_add(1, std::memory_order_relaxed); + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + ImportMappedRanges(fixture.context, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true); + } + block->Unregister(fixture.context); + } + }); + }); + } + for (auto& thread : threads) thread.join(); + } + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + for (auto& kept : images) kept.clear(); + } + if (failure) std::rethrow_exception(failure); + const auto after = MapImportGate::CountersForTest(); + std::cout << "map-time import gate under load: " << creations.load() << " creations, " << imports.load() << " map-time imports, " << deferrals.load() << " deferred; publications " << after.fastPublications - before.fastPublications << " fast, " << after.stripedPublications - before.stripedPublications << " striped (" << after.waitedPublications - before.waitedPublications << " waited); " << after.importHolds - before.importHolds << " import holds (" << after.drainWaits - before.drainWaits << " drained, " << after.retries - before.retries << " retried)\n"; + Require(creations.load() != 0 && imports.load() + deferrals.load() != 0, "(s) the load did not run"); + std::cout << "map-time import gate under load passed\n"; + return true; +} + +std::atomic unwatchHookEntered{false}; + +void SignalUnwatchHook(std::uint64_t address, std::size_t bytes) { + unwatchHookEntered.store(true, std::memory_order_release); + StorageTexture::SaveBeforeUnwatch(address, bytes); +} + +bool unwatchHookLockOrderTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (base.hostImportAlignment == 0) { + std::cout << "host imports unavailable: the unwatch hook's lock order at a map-time import not tested\n"; + return false; + } + constexpr std::uint32_t side = 1024; + constexpr std::size_t surfaceBytes = static_cast(side) * side * 4; + constexpr std::size_t mappedBytes = 512 * 1024; + const auto decided = PrepareImportWatch(base); + SetImportWatch(base, ImportWatch::Unwatch); + struct Restore { + const Context& context; + ImportWatch decided; + ~Restore() { + SetImportWatch(context, decided); + AgcDriver::GuestMemory::SetUnwatchHook(&StorageTexture::SaveBeforeUnwatch); + } + } restore{base, decided}; + void* block = AllocateWatched(surfaceBytes, 65536); + if (block == nullptr) { + std::cout << "no write watching: the unwatch hook's lock order at a map-time import not tested\n"; + return false; + } + std::memset(block, 0x5a, surfaceBytes); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, mappedBytes, true, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + ~Unregister() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + HostImportFor(context, address, mappedBytes); + ReleaseWatched(block, surfaceBytes); + } + } unregister{base, block, address}; + GuestAllocations::Mapped mapped; + for (const auto& range : GuestAllocations::GuestAllocationsAcquire_nid_postfix()) { + if (range->address == address) mapped.push_back(range); + } + Require(mapped.size() == 1, "(l) the surface's range is not registered"); + AgcDriver::GuestMemory::CollectWritesUncached(address, surfaceBytes); + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = side; + resource.height = side; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kR64KBX; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + Require(DescribeSurface(resource).guestBytes == surfaceBytes, "(l) unexpected surface size"); + auto image = StorageTexture::Create(context, detiler, resource, 0); + recorder.Keep(image); + std::atomic finished{false}; + std::thread watchdog([&] { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (!finished.load(std::memory_order_acquire)) { + if (std::chrono::steady_clock::now() > deadline) { + std::cerr << "(l) deadlock: a map-time import ran the unwatch hook into the GPU lock under the import lock while the GPU lock's owner waited for the import lock\n"; + std::cerr.flush(); + std::_Exit(1); + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + }); + std::atomic deferred{-1}; + { + std::unique_lock gpu(AgcDriver::GuestMemory::GpuMutex()); + Require(ImportMappedRanges(context, {}, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true), "(l) the device was not adopted"); + unwatchHookEntered.store(false, std::memory_order_release); + AgcDriver::GuestMemory::SetUnwatchHook(&SignalUnwatchHook); + std::thread mapping([&] { deferred.store(ImportMappedRanges(context, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), false) ? 0 : 1, std::memory_order_release); }); + while (!unwatchHookEntered.load(std::memory_order_acquire) && deferred.load(std::memory_order_acquire) < 0) std::this_thread::yield(); + HostImportFor(context, address, mappedBytes); + gpu.unlock(); + mapping.join(); + } + finished.store(true, std::memory_order_release); + watchdog.join(); + AgcDriver::GuestMemory::SetUnwatchHook(&StorageTexture::SaveBeforeUnwatch); + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + if (deferred.load() == 1) Require(ImportMappedRanges(context, mapped, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), true), "(l) the deferred import failed under the GPU lock"); + Require(HostImportFor(context, address, mappedBytes) != nullptr, "(l) the surface was not imported"); + } + std::cout << "unwatch hook lock order at a map-time import passed\n"; + return true; +} + // A write-back stores the texels of an image and leaves every other byte of the surface's memory // as it was: the padding of the tile blocks the surface covers only partly (its last block column // and row), the tail block of a mip chain, the bytes between the mips and the pitch padding of a @@ -7315,6 +8620,91 @@ void writeBackBlockTests(const Device& device, Recorder& recorder) { std::cout << "write-back blocks: ok\n"; } +bool flushWaitMipChainTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + if (base.hostImportAlignment == 0) { + std::cout << "skipped, host imports are unavailable: the flush wait over a DCC mip chain not tested\n"; + return false; + } + if (65536 % base.hostImportAlignment != 0) { + std::cout << "skipped, the host import alignment " << base.hostImportAlignment << " does not divide 64 KiB: the flush wait over a DCC mip chain not tested\n"; + return false; + } + const std::string what = "flush wait over a DCC mip chain: "; + GuestTextureResource resource{}; + resource.width = 512; + resource.height = 512; + resource.mipCount = 3; + resource.tileMode = TextureTileMode::kR64KBX; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + const auto surfaceBytes = static_cast(DescribeSurface(resource).guestBytes); + Require(surfaceBytes > 2 * 65536, what + "the surface does not span several units"); + const auto keysOffset = (surfaceBytes + 65535) / 65536 * 65536; + const auto keyBytes = DccKeyBytes(surfaceBytes); + const auto bytes = (keysOffset + keyBytes + 65535) / 65536 * 65536; +#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, what + "cannot allocate the block"); + auto* memory = static_cast(block); + std::memset(memory, 0, keysOffset); + std::memset(memory + keysOffset, 0xff, keyBytes); + const auto address = reinterpret_cast(block); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true, true); + } + TextureDetiler detiler(base); + auto context = base; + context.detiler = &detiler; + resource.baseAddress = address; + resource.dccAddress = address + keysOffset; + const auto writer = address + (surfaceBytes - 1) / 65536 * 65536; + bool waited = false; + { + auto image = StorageTexture::Create(context, detiler, resource, 0); + Require(image->UploadedKeys() == DccKeys::Uncompressed, what + "the image did not take its keys as uncompressed"); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, resource.mipCount, 0, 1}; + const auto commands = recorder.Commands(); + recorder.Keep(image); + const VkClearColorValue value{{1.0f, 0.0f, 0.0f, 1.0f}}; + 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(); + recorder.NotePendingWrite(writer, 16); + Require(recorder.OpenWriteOverlaps(writer, 16), what + "the writer is not a write of the open batch"); + const auto serial = recorder.OpenSerial(); + Require(StorageTexture::FlushPending(address, 65536, nullptr, "DCC mip chain flush wait test"), what + "the pending image was not flushed"); + waited = recorder.CompletedThrough() >= serial; + StorageTexture::FlushPending(address, surfaceBytes, nullptr, "DCC mip chain flush wait test"); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + } + recorder.Sync(); + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + ClearHostImports(base.device); +#ifdef _WIN32 + VirtualFree(block, 0, MEM_RELEASE); +#else + std::free(block); +#endif + Require(waited, what + "a flush of the first unit did not complete the batch of a writer over another pending unit, which a write-back of a DCC image with more than one mip may store"); + std::cout << "flush wait over a DCC mip chain: ok\n"; + return true; +} + void singlePassTests(const Device& device, Recorder& recorder, bool watched) { const auto& base = device.GetContext(); if (base.hostImportAlignment == 0 || !base.singlePassStorage) { @@ -7827,6 +9217,120 @@ void refreshProofTests(const Device& device, Recorder& recorder) { recorder.Sync(); } +bool repeatedNeighbourStoreTests(const Device& device, Recorder& recorder) { + const auto& base = device.GetContext(); + constexpr std::uint32_t width = 1024; + constexpr std::uint32_t height = 512; + constexpr std::size_t surfaceBytes = std::size_t{width} * height * 4; + constexpr std::uint64_t offset = 0x2000; + constexpr std::size_t neighbourBytes = 0x6000; + constexpr std::size_t bytes = 35 * 65536; + constexpr std::array red{255, 0, 0, 255}; + const auto pattern = [](std::size_t i) { return static_cast(i * 131u + 7u); }; + bool ran = false; + for (const bool imported : {false, true}) { +#ifdef _WIN32 + if (imported) { + std::cout << "repeated neighbour stores over a watched host import: Linux write watch only\n"; + continue; + } +#endif + if (imported && base.hostImportAlignment == 0) { + std::cout << "host imports unavailable: repeated neighbour stores over a watched host import not tested\n"; + continue; + } + const auto decided = PrepareImportWatch(base); + if (imported) SetImportWatch(base, ImportWatch::Watch); + struct Restore { + const Context& context; + ImportWatch decided; + ~Restore() { SetImportWatch(context, decided); } + } restore{base, decided}; + const std::string what = imported ? "over a watched host import" : "over watched memory"; + void* block = AllocateWatched(bytes, 65536); + if (block == nullptr) { + std::cout << "no write watching: repeated neighbour stores " << what << " not tested\n"; + continue; + } + auto* memory = static_cast(block); + for (std::size_t i = 0; i < bytes; ++i) memory[i] = pattern(i); + const auto blockAddress = reinterpret_cast(block); + const auto address = blockAddress + offset; + { + GuestAllocations::Mutation mutation; + mutation.Add(block, bytes, true, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + std::size_t bytes; + ~Unregister() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + ReleaseWatched(block, bytes); + } + } unregister{base, block, blockAddress, bytes}; + AgcDriver::GuestMemory::CollectWritesUncached(blockAddress, bytes); + TextureDetiler detiler(base); + auto context = base; + if (!imported) context.hostImportAlignment = 0; + context.detiler = &detiler; + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = width; + resource.height = height; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kLinear; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + Require(DescribeSurface(resource).guestBytes == surfaceBytes, "(n) unexpected surface size"); + { + auto image = StorageTexture::Create(context, detiler, resource, 0); + image->Refresh(); + Require(image->Refresh(), "(n) " + what + ": the image does not settle after its upload"); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + const auto commands = recorder.Commands(); + recorder.Keep(image); + const VkClearColorValue value{{1.0f, 0.0f, 0.0f, 1.0f}}; + 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(); + for (int store = 0; store < 5; ++store) AgcDriver::GuestMemory::MarkWritten(address + surfaceBytes, neighbourBytes); + Require(AgcDriver::GuestMemory::Watched(address, surfaceBytes) && (HostImportFor(context, address, surfaceBytes) != nullptr) == imported, "(n) " + what + ": the image is not on the path under test"); + image->WriteBack(); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + std::vector read(bytes); + AgcDriver::GuestMemory::Read(blockAddress, read); + std::size_t dropped = 0; + for (std::size_t at = offset; at < offset + surfaceBytes; at += 4) { + for (std::size_t c = 0; c < 4; ++c) { + if (std::to_integer(read[at + c]) != red[c]) { + ++dropped; + break; + } + } + } + if (dropped != 0) throw std::runtime_error("repeated neighbour stores " + what + ": " + std::to_string(dropped) + " of " + std::to_string(surfaceBytes / 4) + " texels the GPU wrote were not stored"); + for (std::size_t at = offset + surfaceBytes; at < offset + surfaceBytes + neighbourBytes; ++at) Require(std::to_integer(read[at]) == pattern(at), "(n) " + what + ": the write-back stored over the neighbour's bytes"); + } + recorder.Sync(); + std::cout << "repeated neighbour stores " << what << ": ok\n"; + ran = true; + } + return ran; +} + void unchangedCpuStampTests(const Device& device, Recorder& recorder) { const auto& base = device.GetContext(); constexpr std::uint32_t side = 256; @@ -7948,6 +9452,230 @@ void unchangedCpuStampTests(const Device& device, Recorder& recorder) { std::cout << "unchanged CPU stamps: ok\n"; } +bool rewrittenBlockTests(const Device& device, Recorder& recorder) { + if (!AgcDriver::GuestMemory::WriteWatched()) { + std::cout << "no write watching: rewritten blocks not tested\n"; + return false; + } + const auto& base = device.GetContext(); + constexpr std::uint32_t side = 256; + constexpr std::size_t surfaceBytes = side * side * 4; + constexpr std::size_t block = 65536; + constexpr std::size_t bytes = surfaceBytes + block; + constexpr std::array red{255, 0, 0, 255}; + const auto pattern = [](std::size_t i) { return static_cast(i * 131u + 7u); }; + const auto decided = PrepareImportWatch(base); + struct Restore { + const Context& context; + ImportWatch decided; + ~Restore() { SetImportWatch(context, decided); } + } restore{base, decided}; + for (const bool after : {true, false}) { + const std::string what = after ? "a CPU store of the baseline bytes after the GPU results" : "a CPU store of the baseline bytes before the GPU results"; + void* memoryBlock = AllocateWatched(bytes, block); + Require(memoryBlock != nullptr, "no watched block although write watching is on: rewritten blocks"); + auto* memory = static_cast(memoryBlock); + for (std::size_t i = 0; i < bytes; ++i) memory[i] = pattern(i); + const auto address = reinterpret_cast(memoryBlock); + { + GuestAllocations::Mutation mutation; + mutation.Add(memoryBlock, bytes, true, true, true); + } + struct Unregister { + const Context& context; + void* block; + std::uint64_t address; + std::size_t bytes; + ~Unregister() { + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + HostImportFor(context, address, bytes); + ReleaseWatched(block, bytes); + } + } unregister{base, memoryBlock, address, bytes}; + AgcDriver::GuestMemory::CollectWritesUncached(address, bytes); + TextureDetiler detiler(base); + auto context = base; + context.hostImportAlignment = 0; + context.detiler = &detiler; + GuestTextureResource resource{}; + resource.baseAddress = address; + resource.width = side; + resource.height = side; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kLinear; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + Require(DescribeSurface(resource).guestBytes == surfaceBytes, "(c) unexpected surface size"); + const auto rewrite = [&] { + for (std::size_t i = block; i < 2 * block; ++i) memory[i] = pattern(i); + AgcDriver::GuestMemory::CollectWritesUncached(address, bytes); + }; + { + auto image = StorageTexture::Create(context, detiler, resource, 0); + if (!after) rewrite(); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + const auto commands = recorder.Commands(); + recorder.Keep(image); + const VkClearColorValue value{{1.0f, 0.0f, 0.0f, 1.0f}}; + 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(); + if (after) rewrite(); + Require(AgcDriver::GuestMemory::Watched(address, surfaceBytes) && HostImportFor(context, address, surfaceBytes) == nullptr, "(c) " + what + ": the image is not on the path under test"); + image->WriteBack(); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + std::vector read(bytes); + AgcDriver::GuestMemory::Read(address, read); + const auto texel = [&](std::size_t at) { + for (std::size_t c = 0; c < 4; ++c) { + if (std::to_integer(read[at + c]) != red[c]) return false; + } + return true; + }; + for (std::size_t at = 0; at < surfaceBytes; at += 4) { + const bool rewritten = at >= block && at < 2 * block; + if (after && rewritten) { + for (std::size_t c = 0; c < 4; ++c) Require(std::to_integer(read[at + c]) == pattern(at + c), "(c) " + what + ": the GPU results were stored over the CPU's later store at +" + std::to_string(at)); + } else { + Require(texel(at), "(c) " + what + ": GPU results were dropped at +" + std::to_string(at)); + } + } + } + recorder.Sync(); + } + std::cout << "rewritten blocks: ok\n"; + return true; +} + +bool rewrittenStaleUnitTests(const Device& device, Recorder& recorder) { + if (!AgcDriver::GuestMemory::WriteWatched()) { + std::cout << "no write watching: stale unit tests not run\n"; + return false; + } + const auto& base = device.GetContext(); + constexpr std::uint32_t width = 512; + constexpr std::uint32_t height = 256; + constexpr std::size_t surfaceBytes = std::size_t{width} * height * 4; + constexpr std::size_t unitBytes = 65536; + constexpr std::size_t units = surfaceBytes / unitBytes; + constexpr std::array red{255, 0, 0, 255}; + constexpr std::array blue{0, 0, 255, 255}; + const auto pattern = [](std::size_t i) { return static_cast(i * 131u + 7u); }; + for (const bool unwatch : {true, false}) { + const std::string what = unwatch ? "memory that left the write watch" : "watched memory"; + struct Backing { + Recorder& recorder; + void* block = nullptr; + ~Backing() { + try { + recorder.Sync(); + } catch (const std::exception& error) { + std::cerr << "(r) the recorded work did not finish, the test memory stays mapped: " << error.what() << '\n'; + return; + } + if (block == nullptr) return; + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + ReleaseWatched(block, surfaceBytes); + } + } backing{recorder}; + backing.block = AllocateWatched(surfaceBytes, unitBytes); + Require(backing.block != nullptr, "(r) no watched block although write watching is on"); + { + GuestAllocations::Mutation mutation; + mutation.Add(backing.block, surfaceBytes, true, true, true); + } + auto* memory = static_cast(backing.block); + for (std::size_t i = 0; i < surfaceBytes; ++i) memory[i] = pattern(i); + const auto address = reinterpret_cast(backing.block); + AgcDriver::GuestMemory::CollectWritesUncached(address, surfaceBytes); + TextureDetiler detiler(base); + auto context = base; + context.hostImportAlignment = 0; + 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 == surfaceBytes, "(r) unexpected surface size"); + Require(AgcDriver::GuestMemory::Watched(address, surfaceBytes) && HostImportFor(context, address, surfaceBytes) == nullptr, "(r) " + what + ": the image is not on the path under test"); + { + auto image = StorageTexture::Create(context, detiler, resource, 0); + const VkImageSubresourceRange range{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + const auto clear = [&](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(); + }; + const auto store = [&] { + image->WriteBack(); + recorder.Submit(); + device.WaitQueue(); + recorder.Sync(); + }; + const auto unitHolds = [&](std::size_t unit, const std::array& texel) { + std::vector read(unitBytes); + AgcDriver::GuestMemory::Read(address + unit * unitBytes, read); + for (std::size_t i = 0; i < unitBytes; ++i) { + if (std::to_integer(read[i]) != texel[i % 4]) return false; + } + return true; + }; + const auto unitRewritten = [&] { + std::vector read(unitBytes); + AgcDriver::GuestMemory::Read(address + unitBytes, read); + for (std::size_t i = 0; i < unitBytes; ++i) { + if (std::to_integer(read[i]) != pattern(unitBytes + i)) return false; + } + return true; + }; + clear({{1.0f, 0.0f, 0.0f, 1.0f}}); + for (std::size_t i = unitBytes; i < 2 * unitBytes; ++i) memory[i] = pattern(i); + AgcDriver::GuestMemory::CollectWritesUncached(address, surfaceBytes); + store(); + Require(unitRewritten(), "(r) " + what + ": the first write-back stored over unit 1, which the CPU rewrote with its baseline bytes after the results"); + for (std::size_t unit = 0; unit < units; ++unit) { + if (unit != 1) Require(unitHolds(unit, red), "(r) " + what + ": the first write-back did not store unit " + std::to_string(unit)); + } + if (unwatch) { + AgcDriver::GuestMemory::Unwatch(address, surfaceBytes); + Require(!AgcDriver::GuestMemory::Watched(address, surfaceBytes), "(r) the memory is still write-watched"); + } + clear({{0.0f, 0.0f, 1.0f, 1.0f}}); + store(); + Require(unitRewritten(), "(r) " + what + ": the second write-back stored the image's stale unit 1 over the CPU's store, which nothing re-uploaded"); + for (std::size_t unit = 0; unit < units; ++unit) { + if (unit != 1) Require(unitHolds(unit, blue), "(r) " + what + ": the second write-back did not store unit " + std::to_string(unit)); + } + } + std::cout << "a unit kept for the CPU and written again before a re-upload, in " << what << ": ok\n"; + } + return true; +} + std::uint64_t publishRaceAddress = 0; void writeDuringPublish(std::uint64_t begin, std::uint64_t end) { @@ -10470,6 +12198,48 @@ void failedCheckReportTests(const Device& device, Recorder& recorder) { Require(false, "a failed check stopped the test with a texture upload in its open batch"); } +void uploadScratchReuseTests(const Device& device, Recorder& recorder) { + const auto& context = device.GetContext(); + Require(Recorder::Active() == &recorder, "the upload scratch test needs the active recorder"); + TextureDetiler detiler(context); + SampleProgram program(context, recorder); + const VkComponentMapping identity{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}; + struct Upload { + std::uint32_t size; + std::uint8_t value; + bool grows; + }; + const std::array uploads{{{64, 0x20, false}, {32, 0x90, false}, {64, 0xe0, false}, {128, 0x50, true}, {128, 0xb0, false}}}; + std::vector> snapshots; + std::vector> textures; + recorder.Sync(); + std::size_t kept = 0; + for (std::size_t index = 0; index < uploads.size(); ++index) { + GuestTextureResource resource{}; + resource.baseAddress = 0x100000 + index * 0x100000; + resource.width = uploads[index].size; + resource.height = uploads[index].size; + resource.mipCount = 1; + resource.tileMode = TextureTileMode::kLinear; + resource.dimension = TextureDimension::k2D; + resource.format = 56; + resource.dstSelX = 4; + resource.dstSelY = 5; + resource.dstSelZ = 6; + resource.dstSelW = 7; + const auto geometry = DescribeSurface(resource); + snapshots.emplace_back(static_cast(geometry.guestBytes), std::byte{uploads[index].value}); + textures.push_back(std::make_unique(context, detiler, resource, identity, snapshots.back())); + Require(recorder.Recording(), "a texture upload was not recorded into the open batch"); + if (uploads[index].grows) Require(recorder.OpenKeptBytes() > kept + geometry.guestBytes, "a texture upload larger than its batch's scratch did not keep a grown scratch"); + else if (index != 0) Require(recorder.OpenKeptBytes() == kept + geometry.guestBytes, "a texture upload no larger than an earlier one of its batch allocated scratch of its own"); + kept = recorder.OpenKeptBytes(); + } + for (std::size_t index = 0; index < uploads.size(); ++index) { + expectRed(program.Red(textures[index]->View(), textures[index]->Layout(), 0.0f), uploads[index].value / 255.0f, "a texture uploaded through its batch's shared scratch sampled another upload's bytes"); + } +} + void singleCubeTests(const Device& device, Recorder& recorder) { auto context = device.GetContext(); TextureDetiler detiler(context); @@ -12319,6 +14089,234 @@ void Run(Context context) { } #endif +namespace SmallElementDetileTest { + +std::uint32_t equationOffset(const TextureSwizzleEquation& equation, std::uint32_t x, std::uint32_t y) { + std::uint32_t offset = 0; + for (std::size_t bit = 0; bit < equation.bits.size(); ++bit) { + const auto mask = equation.bits[bit]; + const auto selected = (x & (mask & 0xfffu)) ^ ((y << 12) & (mask & 0xfff000u)); + offset |= static_cast(std::popcount(selected) & 1u) << bit; + } + return offset; +} + +std::uint32_t standardOffset(std::uint32_t elementBytes, std::uint32_t blockBytes, std::uint32_t x, std::uint32_t y) { + std::uint32_t offset = elementBytes == 1u ? ((y << 4) & 0x1f0u) ^ ((y << 5) & 0x400u) ^ (x & 0x00fu) ^ ((x << 5) & 0x200u) ^ ((x << 6) & 0x800u) + : ((y << 4) & 0x070u) ^ ((y << 5) & 0x100u) ^ ((y << 6) & 0x400u) ^ ((x << 1) & 0x00eu) ^ ((x << 4) & 0x080u) ^ ((x << 5) & 0x200u) ^ ((x << 6) & 0x800u); + if (blockBytes > 4096u) offset ^= elementBytes == 1u ? ((x << 7) & 0x2000u) ^ ((x << 8) & 0x8000u) ^ ((y << 6) & 0x1000u) ^ ((y << 7) & 0x4000u) + : ((x << 7) & 0x2000u) ^ ((x << 8) & 0x8000u) ^ ((y << 7) & 0x1000u) ^ ((y << 8) & 0x4000u); + return offset & (blockBytes - 1u); +} + +std::uint32_t tiledOffset(TextureTileMode mode, std::uint32_t elementBytes, const TileMipLayout& mip, std::uint32_t x, std::uint32_t y) { + if (mode == TextureTileMode::kLinear) return y * mip.pitchBytes + x * elementBytes; + const auto block = ThinBlockLayout(mode, elementBytes); + const auto sx = mip.tail ? x + mip.tailX : x; + const auto sy = mip.tail ? y + mip.tailY : y; + const auto base = mip.tail ? 0u : (y / block[2] * mip.blocksPerRow + x / block[1]) * block[0]; + if (mode == TextureTileMode::kStandard4KB || mode == TextureTileMode::kStandard64KB) return base + standardOffset(elementBytes, block[0], sx, sy); + const auto* equation = FindTextureSwizzleEquation(EquationSwizzleMode(mode), elementBytes); + Require(equation != nullptr, "small element detile test: no swizzle equation"); + return base + equationOffset(*equation, sx, sy); +} + +class StorageImage { +public: + StorageImage(const Context& context, VkFormat format, std::uint32_t width, std::uint32_t height) : context(context) { + try { + VkImageCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + info.imageType = VK_IMAGE_TYPE_2D; + info.format = format; + info.extent = {width, height, 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_STORAGE_BIT; + info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + Check(context.Function("vkCreateImage")(context.device, &info, nullptr, &image), "vkCreateImage small element detile"); + 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); + Check(context.Function("vkAllocateMemory")(context.device, &allocation, nullptr, &memory), "vkAllocateMemory small element detile"); + Check(context.Function("vkBindImageMemory")(context.device, image, memory, 0), "vkBindImageMemory small element detile"); + 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 small element detile"); + } catch (...) { + release(); + throw; + } + } + ~StorageImage() { release(); } + StorageImage(const StorageImage&) = delete; + StorageImage& operator=(const StorageImage&) = delete; + VkImage Image() const { return image; } + VkImageView View() const { return view; } + +private: + void release() noexcept { + if (view != VK_NULL_HANDLE) context.Function("vkDestroyImageView")(context.device, view, nullptr); + if (image != VK_NULL_HANDLE) context.Function("vkDestroyImage")(context.device, image, nullptr); + if (memory != VK_NULL_HANDLE) context.Function("vkFreeMemory")(context.device, memory, nullptr); + } + Context context; + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkImageView view = VK_NULL_HANDLE; +}; + +void fillPattern(std::span bytes, std::uint32_t seed) { + for (std::size_t i = 0; i < bytes.size(); ++i) bytes[i] = static_cast((i * 37u + seed) & 0xffu); +} + +void fillSentinel(std::span bytes) { + std::fill(bytes.begin(), bytes.end(), std::byte{0xa5}); +} + +void requireBytes(std::span actual, std::span expected, const std::string& what) { + Require(actual.size() >= expected.size(), what + ": readback is shorter than expected"); + for (std::size_t i = 0; i < expected.size(); ++i) { + if (actual[i] != expected[i]) Require(false, what + ": byte " + std::to_string(i) + " is " + std::to_string(static_cast(actual[i])) + ", expected " + std::to_string(static_cast(expected[i]))); + } +} + +void hostBarrier(const Context& context, VkCommandBuffer commands) { + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); +} + +} + +void smallElementDetileTests(Device& device) { + using namespace SmallElementDetileTest; + const auto& context = device.GetContext(); + TextureDetiler detiler(context); + struct Case { + TextureTileMode mode; + const char* name; + std::uint32_t width; + std::uint32_t height; + std::uint32_t mips; + bool tail; + }; + const std::array cases{{ + {TextureTileMode::kStandard64KB, "SW_64KB_S", 37, 21, 1, false}, + {TextureTileMode::kStandard4KB, "SW_4KB_S", 70, 9, 1, false}, + {TextureTileMode::kR64KBX, "SW_64KB_R_X", 37, 21, 1, false}, + {TextureTileMode::kLinear, "linear", 37, 5, 1, false}, + {TextureTileMode::kStandard64KB, "SW_64KB_S tail", 256, 256, 9, true}, + {TextureTileMode::kR64KBX, "SW_64KB_R_X tail", 256, 256, 9, true}, + }}; + for (const auto& testCase : cases) { + for (const std::uint32_t elementBytes : {1u, 2u}) { + const auto layouts = ComputeElementMipLayout(testCase.mode, elementBytes, testCase.width, testCase.height, testCase.mips); + std::size_t level = 0; + if (testCase.tail) { + while (level < layouts.size() && !layouts[level].tail) ++level; + Require(level < layouts.size(), std::string("small element detile test: ") + testCase.name + " has no tail mip"); + } + const auto& mip = layouts[level]; + const auto tiledBytes = static_cast(mip.tiledSize); + const auto linearBytes = static_cast(mip.linearSize); + struct WindowCase { + const char* name; + DetileWindow window; + }; + std::vector windows{{"whole mip", DetileWindow{}}}; + DetileWindow ranged{}; + ranged.rangeBegin = 1u; + ranged.rangeEnd = static_cast(tiledBytes / 2u + 3u); + windows.push_back({"unaligned byte range", ranged}); + if (mip.width > 9u) { + DetileWindow columns{}; + columns.columnBegin = 3u; + columns.columnEnd = mip.width - 2u; + windows.push_back({"unaligned columns", columns}); + } + for (const auto& windowCase : windows) { + const auto& window = windowCase.window; + const auto what = std::string(testCase.name) + " " + std::to_string(elementBytes) + "B " + std::to_string(mip.width) + "x" + std::to_string(mip.height) + ", " + windowCase.name; + const auto columnEnd = window.columnEnd != 0 ? std::min(window.columnEnd, mip.width) : mip.width; + const auto gridEnd = std::min(mip.width, window.columnBegin + (columnEnd - window.columnBegin + 7u) / 8u * 8u); + const auto moved = [&](std::uint32_t x, std::uint32_t offset) { return x >= window.columnBegin && x < gridEnd && offset >= window.rangeBegin && offset < window.rangeEnd; }; + Buffer linear(context, linearBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + Buffer tiled(context, tiledBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + Buffer retiled(context, tiledBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + Buffer detiled(context, linearBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + fillPattern(linear.Bytes(), 11u); + fillPattern(tiled.Bytes(), 29u); + fillSentinel(retiled.Bytes()); + fillSentinel(detiled.Bytes()); + std::vector expectedRetile(tiledBytes, std::byte{0xa5}); + std::vector expectedDetile(linearBytes, std::byte{0xa5}); + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + const auto offset = tiledOffset(testCase.mode, elementBytes, mip, x, y); + if (!moved(x, offset)) continue; + const auto linearOffset = y * mip.pitchBytes + x * elementBytes; + for (std::uint32_t byte = 0; byte < elementBytes; ++byte) { + expectedRetile[offset + byte] = linear.Bytes()[linearOffset + byte]; + expectedDetile[linearOffset + byte] = tiled.Bytes()[offset + byte]; + } + } + } + detiler.BeginBatch(); + CommandBatch batch(context); + const auto commands = batch.Handle(); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + detiler.Dispatch(commands, testCase.mode, elementBytes, linear.Handle(), 0, retiled.Handle(), 0, mip, true, 0, false, window); + detiler.Dispatch(commands, testCase.mode, elementBytes, tiled.Handle(), 0, detiled.Handle(), 0, mip, false, 0, false, window); + hostBarrier(context, commands); + batch.SubmitAndWait(); + requireBytes(retiled.Bytes(), expectedRetile, what + ", buffer retile"); + requireBytes(detiled.Bytes(), expectedDetile, what + ", buffer detile"); + const auto format = TextureDetiler::ImageElementFormat(elementBytes); + VkFormatProperties properties{}; + context.formatProperties(context.physical, format, &properties); + if (!context.singlePassStorage || (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) == 0) continue; + StorageImage image(context, format, mip.width, mip.height); + Buffer imageRetiled(context, tiledBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + fillSentinel(imageRetiled.Bytes()); + std::vector expectedImage(tiledBytes, std::byte{0xa5}); + for (std::uint32_t y = 0; y < mip.height; ++y) { + for (std::uint32_t x = 0; x < mip.width; ++x) { + const auto offset = tiledOffset(testCase.mode, elementBytes, mip, x, y); + if (!moved(x, offset)) continue; + for (std::uint32_t byte = 0; byte < elementBytes; ++byte) expectedImage[offset + byte] = tiled.Bytes()[offset + byte]; + } + } + detiler.BeginBatch(); + CommandBatch imageBatch(context); + const auto imageCommands = imageBatch.Handle(); + VkImageMemoryBarrier toGeneral{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; + toGeneral.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + toGeneral.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + toGeneral.newLayout = VK_IMAGE_LAYOUT_GENERAL; + toGeneral.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toGeneral.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + toGeneral.image = image.Image(); + toGeneral.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + context.Function("vkCmdPipelineBarrier")(imageCommands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &toGeneral); + RecordMemoryBarrier(context, imageCommands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT); + detiler.DispatchImage(imageCommands, testCase.mode, elementBytes, tiled.Handle(), 0, image.View(), mip, false); + RecordMemoryBarrier(context, imageCommands, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + detiler.DispatchImage(imageCommands, testCase.mode, elementBytes, imageRetiled.Handle(), 0, image.View(), mip, true, 0, window); + hostBarrier(context, imageCommands); + imageBatch.SubmitAndWait(); + requireBytes(imageRetiled.Bytes(), expectedImage, what + ", image retile"); + } + } + } + std::cout << "small element detile tests passed\n"; +} + int main(int argc, char** argv) { _ZSt13set_terminatePFvvE_nid_postfix([] { std::fflush(nullptr); @@ -12387,6 +14385,8 @@ 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" || std::string_view(argv[1]) == "--host-import-lifetime-other-family-only" || std::string_view(argv[1]) == "--settle-write-order-other-family-only")); + } catch (const TestConfigurationError&) { + throw; } catch (const std::exception& error) { if (std::getenv("ANYPS5_REQUIRE_VULKAN") != nullptr) throw; std::cout << "skipped, no usable Vulkan device: " << error.what() << '\n'; @@ -12499,7 +14499,12 @@ int main(int argc, char** argv) { descriptorPoolTests(device); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--map-gate-stress-only") return mapGateStressTests(device) ? 0 : 77; std::unique_lock gpu(GpuMutex()); + if (argc == 2 && std::string_view(argv[1]) == "--small-element-detile-only") { + smallElementDetileTests(device); + return 0; + } #ifdef _WIN32 if (argc == 2 && std::string_view(argv[1]) == "--storage-cpu-snapshot-only") { if (device.GetContext().hostImportAlignment == 0) { @@ -12525,6 +14530,15 @@ int main(int argc, char** argv) { copiedDirectOrderingTests(device, recorder); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--copied-write-back-only") { + copiedWriteBackTests(device, recorder); + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--completion-wait-only") { + completionWaitTests(device, recorder); + std::cout << "Completion wait tests passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--pending-write-snapshot-only") { pendingWriteSnapshotTests(device, recorder); return 0; @@ -12556,6 +14570,11 @@ int main(int argc, char** argv) { std::cout << "Single cube snapshot and storage sampling tests passed\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--upload-scratch-only") { + uploadScratchReuseTests(device, recorder); + std::cout << "Texture upload scratch reuse tests passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--sampled-storage-base-level-usage-only") { sampledStorageBaseLevelUsageTests(device, recorder); return 0; @@ -12564,10 +14583,13 @@ int main(int argc, char** argv) { unchangedCpuStampTests(device, recorder); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--rewritten-blocks-only") return rewrittenBlockTests(device, recorder) ? 0 : 77; + if (argc == 2 && std::string_view(argv[1]) == "--rewritten-stale-units-only") return rewrittenStaleUnitTests(device, recorder) ? 0 : 77; if (argc == 2 && std::string_view(argv[1]) == "--idle-writeback-race-only") { idleWriteBackRaceTests(device, recorder); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--repeated-neighbour-stores-only") return repeatedNeighbourStoreTests(device, recorder) ? 0 : 77; if (argc == 2 && std::string_view(argv[1]) == "--kept-unit-settle-only") { keptUnitSettleTests(device, recorder); return 0; @@ -12591,6 +14613,13 @@ int main(int argc, char** argv) { settledBaselineTests(device, recorder); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--pending-after-unwatch-only") return pendingAfterLeavingWatchTests(device, recorder) ? 0 : 77; + if (argc == 2 && std::string_view(argv[1]) == "--unwatch-lock-order-only") return unwatchHookLockOrderTests(device, recorder) ? 0 : 77; + if (argc == 2 && std::string_view(argv[1]) == "--map-gate-publication-only") return mapGatePublicationTests(device, recorder) ? 0 : 77; + if (argc == 2 && std::string_view(argv[1]) == "--map-gate-pending-only") return mapGatePendingTests(device, recorder) ? 0 : 77; + if (argc == 2 && std::string_view(argv[1]) == "--map-gate-fast-path-only") return mapGateFastPathTests(device, recorder) ? 0 : 77; + if (argc == 2 && std::string_view(argv[1]) == "--map-gate-disjoint-only") return mapGateDisjointTests(device, recorder) ? 0 : 77; + if (argc == 2 && std::string_view(argv[1]) == "--map-gate-retry-only") return mapGateRetryTests(device, recorder) ? 0 : 77; if (argc == 2 && std::string_view(argv[1]) == "--command-buffer-release-only") { commandBufferReleaseTests(device, recorder); return 0; @@ -12650,11 +14679,27 @@ int main(int argc, char** argv) { parkedImportUnmapTests(device); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--changed-copy-only") { + changedCopyTests(device, recorder); + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--window-import-unmap-only") { + gpu.unlock(); + windowImportUnmapTests(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; } +#ifndef _WIN32 + if (const auto probe = AgcDriver::Graphics::ProbeImportWriteProtection(device.GetContext()); probe.failure != nullptr || probe.writtenAfterSubmit != 0) { + if (probe.failure != nullptr) std::cout << "skipped, the host import probe failed at " << probe.failure << " (" << static_cast(probe.result) << ")\n"; + else std::cout << "skipped, the device reports " << probe.writtenAfterSubmit << " of " << probe.pages << " host-imported pages written after a GPU read, so a forced watch of the import reads its submissions as CPU stores\n"; + return 77; + } +#endif idlePublishWaitTests(device, recorder); return 0; } @@ -12712,6 +14757,7 @@ int main(int argc, char** argv) { writeBackBlockTests(device, recorder); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--flush-wait-mip-chain-only") return flushWaitMipChainTests(device, recorder) ? 0 : 77; if (argc == 2 && std::string_view(argv[1]) == "--metadata-pass-only") { metadataPassTests(device, recorder); std::cout << "Color metadata pass tests passed\n"; @@ -12819,6 +14865,7 @@ int main(int argc, char** argv) { readListTests(device, recorder); overlappedRangeTests(recorder); spaceWriteSyncTests(device, recorder); + changedCopyTests(device, recorder); writeSettledTests(device, recorder); completionCountTests(device, recorder); afterRecordedWorkTests(device, recorder); @@ -12879,18 +14926,24 @@ int main(int argc, char** argv) { writeBackPaddingTests(device, recorder, true); splitWriteBackTests(device, recorder); writeBackBlockTests(device, recorder); + flushWaitMipChainTests(device, recorder); singlePassTests(device, recorder, false); singlePassTests(device, recorder, true); unchangedCpuStampTests(device, recorder); + rewrittenBlockTests(device, recorder); + rewrittenStaleUnitTests(device, recorder); + repeatedNeighbourStoreTests(device, recorder); keptUnitSettleTests(device, recorder); settledBaselineTests(device, recorder); importWatchTests(device); staleGenerationTests(device, recorder); + pendingAfterLeavingWatchTests(device, recorder); importWindowTests(device, recorder); dataWordPositionsTests(); dataRefreshTests(device, recorder); imageMemoTests(device, recorder); minLodTests(device, recorder); + uploadScratchReuseTests(device, recorder); firstLayerViewTests(device, recorder); sampledStorageBaseLevelUsageTests(device, recorder); singleCubeTests(device, recorder); @@ -12902,6 +14955,7 @@ int main(int argc, char** argv) { shadowedKeyStoreTests(device, recorder); sampleDumpTests(device, recorder); copiedDirectOrderingTests(device, recorder); + copiedWriteBackTests(device, recorder); gpu.unlock(); hostImportUnmapTests(device, recorder); drawSnapshotPatchTests(device); diff --git a/core/libs/prx/libSceAgcDriver/tests/ReferenceStorage.cpp b/core/libs/prx/libSceAgcDriver/tests/ReferenceStorage.cpp new file mode 100644 index 0000000000..d17393cc60 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/ReferenceStorage.cpp @@ -0,0 +1,190 @@ +#include "prx/libSceAgcDriver/Graphics/include/ReferenceStorage.hpp" +#include "AllocationFailure.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using AgcDriver::Graphics::ReferenceStorage; +using AgcDriver::Tests::SetAllocationFailureCountdown; + +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; +} + +constexpr std::size_t KiB = 1024; + +std::vector Storage(std::size_t bytes) { + std::vector storage; + storage.reserve(bytes); + return storage; +} + +bool NextAllocationFails() { + try { + void* probe = ::operator new(16); + ::operator delete(probe); + return false; + } catch (const std::bad_alloc&) { + return true; + } +} + +void TakeFromEmpty() { + ReferenceStorage storage(64 * KiB); + Expect(storage.Take(1).capacity() == 0, "an empty store hands out no storage"); + Expect(storage.IdleBytes() == 0 && storage.IdleCount() == 0, "an empty store holds nothing"); +} + +void ReuseSameStorage() { + ReferenceStorage storage(64 * KiB); + auto first = Storage(16 * KiB); + const auto* data = first.data(); + storage.Return(std::move(first)); + Expect(storage.IdleBytes() == 16 * KiB && storage.IdleCount() == 1, "a returned buffer is held"); + Expect(storage.Take(32 * KiB).capacity() == 0, "a buffer smaller than the request is not handed out"); + Expect(storage.IdleCount() == 1, "a refused take keeps the held buffer"); + auto again = storage.Take(8 * KiB); + Expect(again.data() == data && again.capacity() >= 16 * KiB, "a take reuses the held allocation"); + Expect(storage.IdleBytes() == 0 && storage.IdleCount() == 0, "a taken buffer leaves the store"); +} + +void LargerStorageTakesTheReferenceSize() { + ReferenceStorage storage(256 * KiB); + std::vector larger(64 * KiB, std::byte{0x5a}); + const auto* data = larger.data(); + storage.Return(std::move(larger)); + std::vector reference(16 * KiB); + for (std::size_t index = 0; index < reference.size(); ++index) reference[index] = static_cast(index); + const std::span bytes(reference); + auto taken = storage.Take(bytes.size()); + taken.assign(bytes.begin(), bytes.end()); + Expect(taken.data() == data, "a smaller reference reuses the larger held allocation"); + Expect(taken.size() == bytes.size(), "a reference copied into larger storage keeps the reference's size"); + Expect(std::equal(taken.begin(), taken.end(), bytes.begin()), "a reference copied into larger storage holds the reference's bytes"); +} + +void SmallestFit() { + ReferenceStorage storage(256 * KiB); + auto large = Storage(64 * KiB); + auto small = Storage(16 * KiB); + const auto* smallData = small.data(); + storage.Return(std::move(large)); + storage.Return(std::move(small)); + auto taken = storage.Take(8 * KiB); + Expect(taken.data() == smallData, "the smallest buffer that fits is handed out"); + Expect(storage.IdleBytes() == 64 * KiB, "the larger buffer stays held"); +} + +void CapacityEvictsOldest() { + ReferenceStorage storage(48 * KiB); + auto older = Storage(32 * KiB); + auto newer = Storage(32 * KiB); + const auto* newerData = newer.data(); + storage.Return(std::move(older)); + storage.Return(std::move(newer)); + Expect(storage.IdleBytes() == 32 * KiB && storage.IdleCount() == 1, "the byte limit evicts the oldest buffer"); + Expect(storage.Take(1).data() == newerData, "the newest buffer survives eviction"); +} + +void OversizeDropped() { + ReferenceStorage storage(16 * KiB); + storage.Return(Storage(32 * KiB)); + Expect(storage.IdleBytes() == 0 && storage.IdleCount() == 0, "a buffer above the limit is not held"); + storage.Return(std::vector()); + Expect(storage.IdleCount() == 0, "a buffer without capacity is not held"); +} + +void SlotsEvictOldest() { + ReferenceStorage storage(1024 * KiB); + std::vector> buffers; + for (std::size_t index = 0; index < ReferenceStorage::Slots + 2; ++index) buffers.push_back(Storage(KiB + index)); + std::vector> retained; + for (std::size_t index = 2; index < buffers.size(); ++index) retained.emplace_back(buffers[index].data(), buffers[index].capacity()); + std::vector> taken; + taken.reserve(ReferenceStorage::Slots + 1); + for (auto& buffer : buffers) storage.Return(std::move(buffer)); + Expect(storage.IdleCount() == ReferenceStorage::Slots, "the store holds at most its slot count"); + std::size_t expected = 0; + for (const auto& [data, capacity] : retained) expected += capacity; + Expect(storage.IdleBytes() == expected, "evicted buffers leave the byte count"); + for (std::size_t index = 0; index <= ReferenceStorage::Slots; ++index) taken.push_back(storage.Take(1)); + Expect(taken.back().capacity() == 0, "nothing beyond the slot count was held"); + taken.pop_back(); + std::size_t matched = 0; + for (const auto& buffer : taken) { + for (auto& identity : retained) { + if (identity.first == buffer.data() && identity.second == buffer.capacity()) { + identity.first = nullptr; + ++matched; + break; + } + } + } + Expect(matched == ReferenceStorage::Slots, "the held buffers are exactly the newest ones returned"); + Expect(storage.IdleBytes() == 0 && storage.IdleCount() == 0, "every held buffer was handed out"); +} + +void NoAllocationOnReturnOrTake() { + static_assert(noexcept(std::declval().Return(std::vector()))); + static_assert(noexcept(std::declval().Take(0))); + ReferenceStorage storage(48 * KiB); + auto first = Storage(32 * KiB); + auto second = Storage(32 * KiB); + auto third = Storage(8 * KiB); + SetAllocationFailureCountdown(0); + storage.Return(std::move(first)); + storage.Return(std::move(second)); + auto taken = storage.Take(16 * KiB); + storage.Return(std::move(third)); + const bool armed = NextAllocationFails(); + SetAllocationFailureCountdown(-1); + Expect(armed, "return, eviction and take allocate nothing"); + Expect(taken.capacity() >= 32 * KiB, "a take under allocation failure still hands out held storage"); + Expect(storage.IdleBytes() == 8 * KiB && storage.IdleCount() == 1, "the byte count stays consistent under allocation failure"); +} + +} + +void IdleBuffersReleased() { + using namespace std::chrono_literals; + const auto start = ReferenceStorage::Clock::time_point{} + 1h; + ReferenceStorage storage(64 * KiB); + storage.Return(Storage(16 * KiB), start); + storage.Return(Storage(8 * KiB), start + 1s); + Expect(storage.ReleaseIdle(start + 1999ms) == 0 && storage.IdleCount() == 2, "a buffer idle for less than the limit was released"); + Expect(storage.ReleaseIdle(start + 2s) == 16 * KiB, "a buffer idle for the limit was not released"); + Expect(storage.IdleCount() == 1 && storage.IdleBytes() == 8 * KiB, "releasing one idle buffer did not keep the newer one"); + Expect(storage.Take(4 * KiB, start + 3s).capacity() == 0, "a take handed out a buffer idle for the limit"); + Expect(storage.IdleBytes() == 0 && storage.IdleCount() == 0, "a take did not release buffers idle for the limit"); + storage.Return(Storage(16 * KiB), start + 4s); + storage.Return(Storage(8 * KiB), start + 6s); + Expect(storage.IdleCount() == 1 && storage.IdleBytes() == 8 * KiB, "a return did not release buffers idle for the limit"); + Expect(storage.Take(4 * KiB, start + 7s).capacity() >= 8 * KiB, "a buffer idle for less than the limit was not reused"); +} + +int main() { + TakeFromEmpty(); + ReuseSameStorage(); + LargerStorageTakesTheReferenceSize(); + SmallestFit(); + CapacityEvictsOldest(); + OversizeDropped(); + SlotsEvictOldest(); + NoAllocationOnReturnOrTake(); + IdleBuffersReleased(); + if (failures != 0) return 1; + std::printf("reference storage tests passed\n"); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDeviceProfile.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDeviceProfile.cpp index 20112c62f3..8129d74649 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDeviceProfile.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDeviceProfile.cpp @@ -1,5 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/ShaderDeviceProfile.hpp" #include "BdaAbi.hpp" +#include "CacheKey.hpp" #include "Optimization/BindingAllocator.hpp" #include #include @@ -134,6 +135,60 @@ void CheckProfile() { Require(std::ranges::find(geometryAccepted.Target().supportedCapabilities, spv::CapabilityGeometry) != geometryAccepted.Target().supportedCapabilities.end(), "enabled geometry shaders were rejected"); } +std::vector CacheKeyFor(const ShaderRecompiler::SpirvTarget& target) { + static constexpr std::array code{0xbf810000u}; + ShaderRecompiler::RecompileRequest request{}; + request.shader = {ShaderRecompiler::ShaderStage::Compute, 0x10000u, code, 0, {}}; + request.context.waveSize = 64; + request.target = target; + std::vector key; + ShaderRecompiler::RecompileCacheKey::Build(request, key); + return key; +} + +std::vector PerKeyDigestKeyFor(ShaderRecompiler::SpirvTarget target) { + target.featureDigestSealed = false; + return CacheKeyFor(target); +} + +void CheckFeatureDigest() { + ProfileInput input; + const AgcDriver::ShaderDeviceProfile profile(input.Target(), input.device, input.limits); + const auto sealed = profile.Target(); + Require(sealed.featureDigestSealed, "the shader device profile did not seal its feature digest"); + const auto key = CacheKeyFor(sealed); + Require(key == PerKeyDigestKeyFor(sealed), "the sealed feature digest differs from the per-key digest"); + const std::vector capabilityCopy(sealed.supportedCapabilities.begin(), sealed.supportedCapabilities.end()); + const std::vector extensionStorage(sealed.supportedExtensions.begin(), sealed.supportedExtensions.end()); + const std::vector extensionCopy(extensionStorage.begin(), extensionStorage.end()); + auto copied = sealed; + copied.supportedCapabilities = capabilityCopy; + copied.supportedExtensions = extensionCopy; + Require(CacheKeyFor(copied) == key, "equal feature lists in other storage gave another key than the sealed digest"); + auto fewerCapabilities = sealed; + fewerCapabilities.supportedCapabilities = sealed.supportedCapabilities.first(sealed.supportedCapabilities.size() - 1u); + Require(CacheKeyFor(fewerCapabilities) != key && CacheKeyFor(fewerCapabilities) == PerKeyDigestKeyFor(fewerCapabilities), "a sealed digest outlived the capability list it was computed from"); + auto fewerExtensions = sealed; + fewerExtensions.supportedExtensions = sealed.supportedExtensions.first(1u); + Require(CacheKeyFor(fewerExtensions) != key && CacheKeyFor(fewerExtensions) == PerKeyDigestKeyFor(fewerExtensions), "a sealed digest outlived the extension list it was computed from"); + ProfileInput reordered; + std::ranges::reverse(reordered.capabilities); + std::ranges::reverse(reordered.extensions); + const AgcDriver::ShaderDeviceProfile reorderedProfile(reordered.Target(), reordered.device, reordered.limits); + Require(CacheKeyFor(reorderedProfile.Target()) == key, "two profiles with the same features gave different keys"); + ProfileInput geometry; + geometry.capabilities.push_back(spv::CapabilityGeometry); + geometry.core.geometryShader = VK_TRUE; + const AgcDriver::ShaderDeviceProfile geometryProfile(geometry.Target(), geometry.device, geometry.limits); + const auto geometryKey = CacheKeyFor(geometryProfile.Target()); + Require(geometryKey != key && geometryKey == PerKeyDigestKeyFor(geometryProfile.Target()), "a profile capability did not change the feature digest"); + ProfileInput indexing; + indexing.extensions.push_back("SPV_EXT_descriptor_indexing"); + const AgcDriver::ShaderDeviceProfile indexingProfile(indexing.Target(), indexing.device, indexing.limits); + const auto indexingKey = CacheKeyFor(indexingProfile.Target()); + Require(indexingKey != key && indexingKey != geometryKey && indexingKey == PerKeyDigestKeyFor(indexingProfile.Target()), "a profile extension did not change the feature digest"); +} + void CheckAbi() { using ShaderRecompiler::RuntimeAbi::Binding; using ShaderRecompiler::RuntimeAbi::BindingNumber; @@ -219,6 +274,7 @@ int main() { CheckAbi(); CheckHeaps(); CheckProfile(); + CheckFeatureDigest(); std::cout << "shader runtime ABI and device profile tests passed\n"; return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp index 814b5a78f0..68fe9d637c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderDiskCache.cpp @@ -159,8 +159,10 @@ bool sameBindings(const std::vector& left, const std::vector< void requireSameArtifact(const CompiledShaderArtifact& left, const CompiledShaderArtifact& right, const char* what) { const std::string prefix = std::string(what) + ": "; require(left.spirv == right.spirv, prefix + "SPIR-V differs"); + require(left.meshWindowBuffer == right.meshWindowBuffer, prefix + "mesh window buffer flags differ"); require(left.bdaAbiVersion == right.bdaAbiVersion, prefix + "BDA ABI version differs"); require(left.descriptorFaults == right.descriptorFaults, prefix + "descriptor fault count differs"); + require(left.windowLaneGuards == right.windowLaneGuards, prefix + "window lane guards differ"); require(left.runtimeAbiVersion == right.runtimeAbiVersion, prefix + "runtime ABI version differs"); require(left.memoryOffsetDword == right.memoryOffsetDword, prefix + "memory offset differs"); require(left.shaderDataDwords == right.shaderDataDwords && left.imageMetadataDword == right.imageMetadataDword && left.runtimeImageCount == right.runtimeImageCount && left.runtimeImageResources == right.runtimeImageResources, prefix + "compact data layout differs"); @@ -174,6 +176,7 @@ void requireSameArtifact(const CompiledShaderArtifact& left, const CompiledShade 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.layerReplaySafe == right.layerReplaySafe, prefix + "layer replay proof 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) { @@ -245,7 +248,7 @@ DescriptorBinding sampleBinding(std::uint32_t seed) { binding.samplerCoordinatesIgnored = {false, seed % 2 == 0, true}; binding.imageUnnormalized = {true, false, seed % 2 == 0}; binding.imageUnnormalizedBaseLevel = {false, true, seed % 3 == 0}; - binding.imageSamplers = {0x5u, 0u, 0x80000000u}; + binding.imageSamplers = {0x5u, 0u, 0x8000000080000000u}; binding.bufferRead = {true, false, false, true, true, false}; binding.bufferExtent = {32, 0, 16, 4096, 8, 0}; return binding; @@ -253,6 +256,7 @@ DescriptorBinding sampleBinding(std::uint32_t seed) { RecompileResult sampleResult() { RecompileResult result; + result.meshWindowBuffer = true; std::vector words(1000); for (std::size_t i = 0; i < words.size(); ++i) words[i] = static_cast(i * 2654435761u); words[0] = 0x07230203u; @@ -265,6 +269,7 @@ RecompileResult sampleResult() { result.readsDepthRemapPush = true; result.bdaAbiVersion = 3; result.descriptorFaults = 2; + result.windowLaneGuards = 7u; result.memoryOffsetDword = 7; result.hostSubgroupSize = 32; result.fullSubgroups = true; @@ -287,6 +292,7 @@ RecompileResult sampleResult() { result.stencilReferences = {0u, 17u, 255u}; result.stencilReplaySafe = true; result.bdaWrites = true; + result.layerReplaySafe = true; result.stencilReferenceDword = 23u; result.poisonedSrtReads = 3; result.shaderDataUserWords = {2u, 0xffffffffu, 9u}; @@ -354,12 +360,16 @@ CompiledVariant sampleVariant() { image.sampledOnly = false; image.foldTexelOffsetsOnly = true; image.remappedDepth1D = true; + image.layerRowsCompatible = false; + image.layerRows = true; + image.maxArrayLayers = 0x01020304u; image.optionalDescriptor = true; image.byElements = 4; image.byComponents = 1; image.emulatedCompare = 0x1a07u; image.table = 0; image.tableOperation = TableOperation::Unsupported; + image.targetMerge = true; info.info.images = {image}; ResourceMaterializer::PrepareImageModes(info.info); info.info.samplers = {{7, 0x10, 3, true, false, SamplerUseExplicitLod | SamplerUseGather, true, 1}}; @@ -442,6 +452,13 @@ void verifyResultRoundTrip() { require(ShaderDiskCache::DecodeResult(bytes, decoded), "an encoded result does not decode"); requireSameResult(result, decoded, "result round trip"); require(decoded.variantId == 0 && !decoded.cacheHit, "the per-process fields were stored"); + auto memoized = result; + memoized.drawKeyMemo.words.store(std::make_shared>(std::vector{1u, 2u, 3u})); + std::vector memoizedBytes; + ShaderDiskCache::EncodeResult(memoized, memoizedBytes); + require(memoizedBytes == bytes, "the draw key memo was stored"); + RecompileResult memoizedDecoded; + require(ShaderDiskCache::DecodeResult(memoizedBytes, memoizedDecoded) && !memoizedDecoded.drawKeyMemo.words.load(), "a decoded result has a draw key memo"); for (std::size_t size = 0; size < bytes.size(); size += size < 256 ? 1 : 97) { RecompileResult partial; require(!ShaderDiskCache::DecodeResult(std::span(bytes).first(size), partial), "a result truncated to " + std::to_string(size) + " bytes decodes"); @@ -838,6 +855,7 @@ void verifyKeySensitivity() { changes("wave LDS memory barriers", [](SampleRequest& sample) { sample.request.target.waveLdsMemoryBarriers = true; }); changes("device-scope LDS memory barriers", [](SampleRequest& sample) { sample.request.target.deviceScopeLdsMemoryBarriers = true; }); changes("LDS lock fences", [](SampleRequest& sample) { sample.request.target.ldsLockFences = true; }); + changes("mesh window buffers", [](SampleRequest& sample) { sample.request.target.meshWindowBuffer = 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; }); @@ -2052,7 +2070,7 @@ bool samePlan(const IrResourcePlan& left, const IrResourcePlan& right) { for (std::size_t index = 0; index < left.descriptorSources.size(); ++index) { const auto& a = left.descriptorSources[index]; const auto& b = right.descriptorSources[index]; - if (a.dwordCount != b.dwordCount || a.tableColumn != b.tableColumn || (a.tableColumn.has_value() && (a.tableColumn->keyDomains != b.tableColumn->keyDomains || a.tableColumn->unkeyed != b.tableColumn->unkeyed || a.tableColumn->maxKey != b.tableColumn->maxKey))) return false; + if (a.dwordCount != b.dwordCount || a.tableColumn != b.tableColumn || a.requiredBlocks != b.requiredBlocks || (a.tableColumn.has_value() && (a.tableColumn->keyDomains != b.tableColumn->keyDomains || a.tableColumn->unkeyed != b.tableColumn->unkeyed || a.tableColumn->maxKey != b.tableColumn->maxKey))) return false; for (std::size_t dword = 0; dword < a.dwords.size(); ++dword) { if (!sameValue(a.dwords[dword], b.dwords[dword])) return false; } diff --git a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp index f23ea22458..98df094230 100644 --- a/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/ShaderMemory.cpp @@ -21,7 +21,9 @@ #include "IntermediateRepresentation/IrBuilder.hpp" #include "IntermediateRepresentation/IrProgram.hpp" #include "SpirvBackend/SpirvAnalysis.hpp" +#include "SpirvBackend/SpirvEmitter.hpp" #include "SavedExecMaskLoop.hpp" +#include "ShaderDiskCache.hpp" #if ANYPS5_ENABLE_SPIRV_TOOLS #include "SpirvBackend/SpirvOptimizer.hpp" #endif @@ -54,6 +56,7 @@ #include #include #include +#include #include #include #ifdef _WIN32 @@ -439,6 +442,54 @@ void verifySpecializedModuleRelease() { require(shared.spirv.data() == again.spirv.data(), "specialized module release: a second result of a live module copied its words"); } +void verifyLayerSpecializationRelease() { + using namespace ShaderRecompiler; + const std::array code{ + 0x7e000280u, 0x7e020280u, 0x7e040280u, 0x7e0602f2u, 0x7e080280u, + 0xf80000cfu, 0x03020100u, 0xf80008d4u, 0x00040000u, 0xbf810000u + }; + const std::array hidden{}; + RecompileRequest request{}; + request.shader = {ShaderStage::Mesh, 0x10000u, code, 0, {}}; + request.context.waveSize = 64u; + request.context.userData = hidden; + request.context.vertex = ShaderVertexStageInfo{}; + request.context.vertex->paClVsOutCntl = (1u << 18u) | (1u << 21u); + request.graphics = GraphicsCompileContext{0u, {}, MeshConfiguration{4u, 4u, 12u, 12u, 4u, 64u, 1024u, 0u, 4u}, std::nullopt, {}}; + static constexpr std::array capabilities{spv::CapabilityShader, spv::CapabilityImageGatherExtended, spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, spv::CapabilityClipDistance, spv::CapabilityMeshShadingEXT}; + static constexpr std::array extensions{"SPV_KHR_physical_storage_buffer", "SPV_KHR_8bit_storage", "SPV_EXT_mesh_shader"}; + request.target = BufferTarget(); + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010400u; + request.target.supportedCapabilities = capabilities; + request.target.supportedExtensions = extensions; + request.target.maxWorkgroupSize = {1024u, 1024u, 64u}; + request.target.maxWorkgroupInvocations = 1024u; + request.target.maxWorkgroupSharedMemoryBytes = 32768u; + request.target.mesh = MeshTargetLimits{{128u, 1u, 1u}, 128u, 32768u, 256u, 256u, 128u, 32768u, 1u, 1u}; + request.useCache = true; + const std::vector expected{ + {PipelineSpecialization::MeshLayerBase, 0u}, + {PipelineSpecialization::MeshLayerCount, 0xffffffffu} + }; + SharedSpirv::Weak released; + std::uint64_t specializationId = 0; + std::vector words; + { + const auto first = Recompile(request); + require(first.specialization == expected, "layer module release: the prepared layer constants were lost"); + released = first.spirv.Weaken(); + specializationId = first.specializationId; + words = first.spirv.Copy(); + } + require(released.Expired(), "layer module release: the metadata retained unused SPIR-V words"); + const auto again = Recompile(request); + require(again.specialization == expected, "layer module release: rebuilding the words lost the layer constants"); + require(again.specializationId == specializationId && std::ranges::equal(again.spirv.Words(), words), "layer module release: rebuilding changed the module identity or words"); + const auto shared = Recompile(request); + require(shared.specialization == expected && shared.spirv.data() == again.spirv.data(), "layer module release: a live cache hit lost constants or copied the words"); +} + const std::vector& resultMemoPixelCode() { static const std::vector code{0xbf800000u, 0x7e000200u, 0xf800180fu, 0u, 0xbf810000u}; return code; @@ -865,6 +916,98 @@ void verifyPlanRetention() { require(copiedWalk.active == wholeWalk.active && copiedWalk.results == wholeWalk.results && copiedWalk.flat == wholeWalk.flat, "plan retention: the plan's walk differs from the program's"); } +std::uint32_t branchReadsRefused = 0; + +bool readFiveUnlessBranch(void*, std::uint64_t, std::uint32_t* value) { + if (ShaderRecompiler::SrtEvaluatingBranch()) { + ++branchReadsRefused; + return false; + } + *value = 5u; + return true; +} + +void verifyPendingBranchLeftUnpruned() { + 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); + 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 = readFiveUnlessBranch; + runtime.readSpecializationMemory = readFiveUnlessBranch; + branchReadsRefused = 0; + const auto copiedWalk = walkPlan(plan, plan.materializationSources, plan.cleanFlatSlots, runtime); + require(branchReadsRefused != 0u && !SrtEvaluatingBranch(), "pending branch: the plan's walk read its branch condition outside a branch scope or left the scope open"); + branchReadsRefused = 0; + const std::array sources{0u, 1u}; + const auto wholeWalk = walkPlan(program.Resources(), sources, {}, runtime); + require(branchReadsRefused != 0u && !SrtEvaluatingBranch(), "pending branch: the program's walk read its branch condition outside a branch scope or left the scope open"); + require(wholeWalk.active == std::vector{1u, 1u}, "pending branch: a condition whose word could not be read pruned a successor"); + require(copiedWalk.active == wholeWalk.active && copiedWalk.results == wholeWalk.results && copiedWalk.flat == wholeWalk.flat, "pending branch: the plan's walk differs from the program's"); +} + void verifyPlanRetentionChains() { using namespace ShaderRecompiler; std::vector kept; @@ -1207,6 +1350,39 @@ void verifyMayEvaluate() { } } +void verifyMayDecideSharedConditions() { + using namespace ShaderRecompiler; + IrResourcePlan plan; + std::vector> values; + const auto make = [&](IrOpcode opcode, IrType type, std::initializer_list arguments = {}) -> IrValue& { + values.push_back(std::make_unique(opcode, type, static_cast(values.size()))); + for (auto* argument : arguments) values.back()->AddArgument(argument); + return *values.back(); + }; + auto& scalar = make(IrOpcode::Void, IrType::ScalarReg); + scalar.SetRegister({RegisterBank::Scalar, 0}); + auto& vector = make(IrOpcode::Void, IrType::VectorReg); + vector.SetRegister({RegisterBank::Vector, 0}); + auto& input = make(IrOpcode::GetUserData, IrType::U32, {&scalar}); + auto& lane = make(IrOpcode::GetRegister, IrType::U32, {&vector}); + const auto varying = [&] { return &make(IrOpcode::IEqual32, IrType::Bool, {&input, &lane}); }; + const auto chain = [&](IrValue* first) { + auto* condition = first; + for (int level = 0; level < 64; ++level) { + auto& left = make(IrOpcode::LogicalAnd, IrType::Bool, {condition, varying()}); + auto& right = make(IrOpcode::LogicalAnd, IrType::Bool, {condition, varying()}); + condition = &make(IrOpcode::LogicalOr, IrType::Bool, {&left, &right}); + } + return condition; + }; + auto* undecided = chain(varying()); + auto* decided = chain(&make(IrOpcode::LogicalAnd, IrType::Bool, {varying(), &make(IrOpcode::IEqual32, IrType::Bool, {&input, &input})})); + require(!Detail::Evaluator::MayDecide(plan, undecided), "MayDecide decided 64 levels of lane conditions that share their operands"); + require(Detail::Evaluator::MayDecide(plan, decided), "MayDecide did not decide 64 levels of shared conditions over a uniform conjunct"); + std::unordered_map memo; + require(!Detail::Evaluator::MayDecide(plan, undecided, memo) && Detail::Evaluator::MayDecide(plan, decided, memo), "MayDecide answered differently through a shared memo"); +} + void verifyEvaluatedValues() { using namespace ShaderRecompiler; std::vector> values; @@ -1293,7 +1469,7 @@ void verifySignedSrtComparison() { void verifyTypeZeroImageMaterialization() { using namespace ShaderRecompiler; - for (const std::uint32_t type : {0u, 1u}) { + for (const std::uint32_t type : {0u, 1u, 2u}) { IrResourcePlan plan; plan.srtPlanComplete = true; plan.resourceTrackingComplete = true; @@ -1321,19 +1497,29 @@ void verifyTypeZeroImageMaterialization() { plan.info.runtimeImageModes.push_back(ResourceMaterializer::RuntimeImageModes(image)); ResourceSnapshot snapshot; - ResourceMaterializer{}.Materialize(plan, SrtRuntime{}, snapshot); - require(snapshot.images.size() == 1u && snapshot.images[0].dwordCount == 8u && snapshot.images[0].dwords == words, - type == 0u ? "TYPE=0 image descriptor with a nonzero base was rejected or altered during materialization" : "TYPE=1 image descriptor with a nonzero base was rejected or altered during materialization"); + const auto materialize = [&] { ResourceMaterializer{}.Materialize(plan, SrtRuntime{}, snapshot); }; + if (type == 2u) { + expectFailure(materialize, "runtime image descriptor is invalid", "a reserved TYPE=2 read-only image descriptor was accepted"); + } else { + materialize(); + require(snapshot.images.size() == 1u && snapshot.images[0].dwordCount == 8u && snapshot.images[0].dwords == words, + type == 0u ? "TYPE=0 image descriptor with a nonzero base was rejected or altered during materialization" : "TYPE=1 image descriptor with a nonzero base was rejected or altered during materialization"); + } - for (const bool atomic : {false, true}) { + for (const bool read : {false, true}) { image.resourceClass = ImageResourceClass::Storage; - image.read = false; - image.written = !atomic; - image.atomic = atomic; + image.read = read; + image.written = true; plan.info.images[0] = image; plan.info.runtimeImageModes[0] = ResourceMaterializer::RuntimeImageModes(image); - expectFailure([&] { ResourceMaterializer{}.Materialize(plan, SrtRuntime{}, snapshot); }, "runtime image descriptor is invalid", - atomic ? "TYPE=0 or 1 atomic image descriptor was accepted without hardware evidence" : "TYPE=0 or 1 writable image descriptor was accepted without hardware evidence"); + if (type == 2u) { + expectFailure(materialize, "runtime image descriptor is invalid", "a reserved TYPE=2 storage image descriptor was accepted"); + } else { + snapshot = {}; + materialize(); + require(snapshot.images.size() == 1u && snapshot.images[0].dwordCount == 8u && snapshot.images[0].dwords == words, + read ? "TYPE=0 or 1 read-write storage image descriptor was not materialized as a null storage image" : "TYPE=0 or 1 written storage image descriptor was not materialized as a null storage image"); + } } } @@ -1918,7 +2104,7 @@ void verifyTypeZeroImageMaterialization() { { auto tornT = oldT; - tornT[1] &= ~(0x1ffu << 20u); + tornT[3] = (tornT[3] & ~(0xfu << 28u)) | (2u << 28u); srt.fill(0u); std::copy(tornT.begin(), tornT.end(), srt.begin()); std::copy(outputV.begin(), outputV.end(), srt.begin() + 16u); @@ -1959,7 +2145,7 @@ void verifyTypeZeroImageMaterialization() { { auto tornT = oldT; - tornT[1] &= ~(0x1ffu << 20u); + tornT[3] = (tornT[3] & ~(0xfu << 28u)) | (2u << 28u); srt.fill(0u); std::copy(tornT.begin(), tornT.end(), srt.begin()); std::copy(outputV.begin(), outputV.end(), srt.begin() + 16u); @@ -2180,6 +2366,145 @@ void verifyTypeZeroImageMaterialization() { } } +void verifyUnsetFormatImageMaterialization() { + using namespace ShaderRecompiler; + constexpr std::array measuredImage{0xffffffffu, 0xffffffffu, 0x00000019u, 0xc0017901u, 0x00000342u, 0xc8000000u, 0xc0064900u, 0x0000062fu}; + const auto materialize = [](const std::array& words, const ImageResource& image) { + IrResourcePlan plan; + plan.srtPlanComplete = true; + plan.resourceTrackingComplete = true; + plan.userDataCount = 0u; + plan.materializationSources = {0u}; + std::vector> values; + DescriptorSource source; + source.dwordCount = static_cast(words.size()); + for (std::uint32_t i = 0u; i < words.size(); ++i) { + auto value = std::make_unique(IrOpcode::Void, IrType::U32, i); + value->SetImmediateU32(words[i]); + source.dwords[i] = value.get(); + values.push_back(std::move(value)); + } + plan.descriptorSources.push_back(source); + plan.info.images.push_back(image); + plan.info.runtimeImageModes.push_back(ResourceMaterializer::RuntimeImageModes(image)); + ResourceSnapshot snapshot; + ResourceMaterializer{}.Materialize(plan, SrtRuntime{}, snapshot); + return snapshot; + }; + ImageResource sampled; + sampled.source = 0u; + sampled.resourceClass = ImageResourceClass::Sampled; + sampled.dimension = RdnaImageDimension::Dim1DArray; + sampled.read = true; + for (const std::uint32_t format : {0u, 0x1ffu}) { + auto words = measuredImage; + words[1] = (words[1] & ~(0x1ffu << 20u)) | (format << 20u); + const auto snapshot = materialize(words, sampled); + require(snapshot.images.size() == 1u && snapshot.images[0].dwordCount == 8u && snapshot.images[0].dwords == words, + "an unset-format measured image-type T# was rejected or altered during materialization"); + for (const bool atomic : {false, true}) { + auto storage = sampled; + storage.resourceClass = ImageResourceClass::Storage; + storage.read = false; + storage.written = !atomic; + storage.atomic = atomic; + bool rejected = false; + try { + static_cast(materialize(words, storage)); + } catch (const std::runtime_error& error) { + rejected = std::string_view(error.what()).find("image descriptor") != std::string_view::npos; + } + require(rejected, atomic ? "an unset-format atomic image descriptor was accepted" : "an unset-format writable image descriptor was accepted"); + } + } + const auto expectInvalidType = [&](std::uint32_t type, const char* message) { + auto words = measuredImage; + words[1] &= ~(0x1ffu << 20u); + words[3] = (words[3] & ~(0xfu << 28u)) | (type << 28u); + expectFailure([&] { static_cast(materialize(words, sampled)); }, "runtime image descriptor is invalid", message); + }; + expectInvalidType(2u, "a reserved TYPE 2 T# with an unset format was accepted as a null image"); + expectInvalidType(7u, "a reserved TYPE 7 T# with an unset format was accepted as a null image"); + expectInvalidType(15u, "an unimplemented TYPE 15 T# with an unset format was accepted as a null image"); +} + +void CompileDescriptorWeb(const std::vector& code, std::uint32_t address, const char* message, std::ptrdiff_t optionalImages = 2) { + using namespace ShaderRecompiler; + constexpr std::uint32_t Format8888UNorm = 56; + constexpr std::uint32_t Type2D = 9; + struct alignas(256) Texture { std::array bytes{}; }; + static Texture textures[2]; + const auto imageDescriptor = [&](const Texture& texture) { + const auto base = static_cast(reinterpret_cast(texture.bytes.data())); + return std::array{static_cast(base >> 8u), static_cast((base >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (3u << 30u), 3u << 14u, 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u}; + }; + std::array output{}; + const auto outputAddress = static_cast(reinterpret_cast(output.data())); + std::array srt{}; + const auto first = imageDescriptor(textures[0]); + const auto second = imageDescriptor(textures[1]); + std::copy(first.begin(), first.end(), srt.begin()); + const std::array outputV{static_cast(outputAddress), static_cast((outputAddress >> 32u) & 0xffffu), 16u, 0x30027facu}; + std::copy(outputV.begin(), outputV.end(), srt.begin() + 16); + srt[20] = 1u; + std::copy(second.begin(), second.end(), srt.begin() + 24); + 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{}; + request.shader = {ShaderStage::Compute, address, code, 0, {}}; + request.context.waveSize = 32u; + request.context.userDataBaseRegister = 0; + request.context.userData = userData; + request.context.compute = ShaderComputeStageInfo{{32u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + request.target.vulkanVersion = 0x00401000u; + request.target.spirvVersion = 0x00010300u; + request.target.subgroupSize = 32; + request.target.supportedCapabilities = capabilities; + request.target.supportedExtensions = extensions; + request.target.bdaAbiVersion = BdaAbi::Version; + request.target.fragmentShaderBarycentricEnabled = false; + request.layout.pushConstantSizeBytes = 128; + const auto plan = GetResourcePlan(request); + require(plan->info.usesFaultBuffer && plan->info.images.size() >= 2u && std::ranges::count_if(plan->info.images, [](const ImageResource& image) { return image.optionalDescriptor; }) >= optionalImages, message); + AgcDriver::ShaderMemory memory({}); + const auto capture = memory.Capture(request); + const auto memoryRegions = memory.Regions(); + request.context.memory = memoryRegions; + static_cast(Recompile(request, *capture)); +} + + +void verifyDescriptorWebLaneIndexFold() { + const std::vector code{ + 0xf4000400u, 0xfa000050u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf40c0200u, 0xfa000000u, 0xbf8cc07fu, 0xbf068010u, 0xbf850005u, 0xbf068110u, + 0xbf85000cu, 0xf40c0200u, 0xfa000060u, 0xbf8cc07fu, 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0x7e0c020cu, 0x7e0e020du, 0x7e10020eu, 0x7e12020fu, + 0xbf820009u, 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0x7e0c020cu, 0x7e0e020du, 0x7e10020eu, 0xd7650009u, 0x000100c1u, 0x7e100502u, 0x7e120503u, + 0x7e140504u, 0x7e160505u, 0x7e180506u, 0x7e1a0507u, 0x7e1c0508u, 0x7e1e0509u, 0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xbf810000u}; + CompileDescriptorWeb(code, 0x24000u, "descriptor web lane index: the path whose T# holds the lane index was not folded beside the two T#s into faulting arms"); +} + +void verifyDescriptorWebTexelFold() { + const std::vector code{ + 0xf4000400u, 0xfa000050u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf40c0200u, 0xfa000000u, 0xbf8cc07fu, 0xf0000108u, 0x00020900u, 0xbf8c3f70u, + 0xbf068010u, 0xbf850005u, 0xbf068110u, 0xbf85000cu, 0xf40c0200u, 0xfa000060u, 0xbf8cc07fu, 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0x7e0c020cu, + 0x7e0e020du, 0x7e10020eu, 0x7e12020fu, 0xbf820007u, 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0x7e0c020cu, 0x7e0e020du, 0x7e10020eu, 0x7e100502u, + 0x7e120503u, 0x7e140504u, 0x7e160505u, 0x7e180506u, 0x7e1a0507u, 0x7e1c0508u, 0x7e1e0509u, 0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, + 0xbf810000u}; + CompileDescriptorWeb(code, 0x26000u, "descriptor web texel: the path whose T# holds a texel from an image read was not folded beside the read's T# into a faulting arm", 1); +} + +void verifyDescriptorWebSelectOrder() { + const std::vector code{ + 0xf4000400u, 0xfa000050u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf40c0200u, 0xfa000000u, 0xf40c0700u, 0xfa000060u, 0xbf8cc07fu, 0x7d880090u, + 0x7e14021cu, 0x02041408u, 0x7e16021du, 0x02061609u, 0x7e18021eu, 0x0208180au, 0x7e1a021fu, 0x020a1a0bu, 0xbf068010u, 0xbf85000au, 0x7d880088u, 0x7e1c0220u, + 0x020c1c0cu, 0x7e1e0221u, 0x020e1e0du, 0x7e200222u, 0x0210200eu, 0x7e220223u, 0x0212220fu, 0xbf820004u, 0x7e0c020cu, 0x7e0e020du, 0x7e10020eu, 0x7e12020fu, + 0x7e100502u, 0x7e120503u, 0x7e140504u, 0x7e160505u, 0x7e180506u, 0x7e1a0507u, 0x7e1c0508u, 0x7e1e0509u, 0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, + 0x80060000u, 0xbf810000u}; + CompileDescriptorWeb(code, 0x25000u, "descriptor web select order: per-lane T# selects in two blocks were not split into arms"); +} + void verifyOutOfRangeConstantReads() { std::vector code{0xf40c0200u, 0xfa000000u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf4000400u, 0xfa000050u, 0xf4080a00u, 0xfa000080u, 0xbf8cc07fu, 0xf42c0814u, 0xfa000000u, 0xbf8cc07fu, 0xbf068010u, 0xbf850008u}; for (std::uint32_t dword = 0; dword < 8u; dword++) { @@ -2450,6 +2775,89 @@ void verifyExecGuardedDescriptorSources() { } } +void verifyPendingBranchCapture() { + 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{0x12345678u, 0xffffffffu, 0xffffffffu, 2u << 28u, 0u, 0u, 0u, 0u}; + const std::array sampler{0u, 0u, 0u, 0u}; + static 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}; + alignas(4096) static std::array srt{}; + alignas(4096) static std::array flags{}; + const auto flagsAddress = static_cast(reinterpret_cast(flags.data())); + const std::array flagsV{static_cast(flagsAddress), static_cast((flagsAddress >> 32u) & 0xffffu), 16u, 0xfacu}; + static std::uint64_t conditionAddress = 0; + static std::uint64_t waits = 0; + const auto fill = [&](const std::array& image, std::uint32_t mode) { + srt.fill(0u); + 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); + std::copy(flagsV.begin(), flagsV.end(), srt.begin() + 24); + flags[0] = mode; + }; + const auto srtAddress = static_cast(reinterpret_cast(srt.data())); + conditionAddress = flagsAddress; + 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{0xf4080700u, 0xfa000060u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xbf8cc07fu, 0xf420040eu, 0xfa000000u, 0xbf8cc07fu, 0xbf068010u, 0xbf850006u, 0xf40c0200u, 0xfa000000u, 0xbf8cc07fu, 0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xbf810000u}; + request.shader = {ShaderStage::Compute, 0x2a000u, 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; + + struct FlushReset { + ~FlushReset() { AgcDriver::GuestMemory::SetFlushHook(nullptr); } + } flushReset; + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t address, std::size_t bytes) { + if (address < conditionAddress + sizeof(std::uint32_t) && conditionAddress < address + bytes) ++waits; + }); + const auto pending = +[](std::uint64_t address, std::size_t bytes, std::span) -> AgcDriver::ShaderMemory::PendingWrite { + using Pending = AgcDriver::ShaderMemory::PendingWrite; + return address < conditionAddress + sizeof(std::uint32_t) && conditionAddress < address + bytes ? Pending::Sync : Pending::None; + }; + const auto captured = [&](const std::array& image, std::uint32_t mode) { + fill(image, mode); + waits = 0; + AgcDriver::ShaderMemory memory({}, pending); + return memory.Capture(request); + }; + const auto holds = [](const std::shared_ptr& capture, const std::array& image) { + return capture->snapshot.images.size() == 1u && std::equal(image.begin(), image.end(), capture->snapshot.images[0].dwords.begin()); + }; + const std::array null{}; + + const auto unpruned = captured(valid, 0u); + require(waits == 0u, ("pending branch capture: the branch condition over a pending GPU write waited " + std::to_string(waits) + " times").c_str()); + require(holds(unpruned, valid), "pending branch capture: the branch over a pending GPU write was pruned instead of keeping both successors"); + const auto redone = captured(invalid, 0u); + require(waits != 0u, "pending branch capture: a capture that failed behind an unpruned branch was not redone waiting for the condition"); + require(holds(redone, null), "pending branch capture: the capture redone waiting did not prune the branch its condition skips"); + fill(invalid, 1u); + { + AgcDriver::ShaderMemory memory({}, pending); + expectFailure([&] { static_cast(memory.Capture(request)); }, "runtime image descriptor is invalid", "pending branch capture: an invalid T# behind a taken branch was accepted after the capture was redone waiting"); + } + std::cout << "pending branch capture passed\n"; +} + void verifyGuardedDescriptorSources() { using namespace ShaderRecompiler; constexpr std::uint32_t Format8888UNorm = 56; @@ -2504,6 +2912,22 @@ void verifyGuardedDescriptorSources() { AgcDriver::ShaderMemory memory({}); expectFailure([&] { static_cast(memory.Capture(request)); }, "runtime image descriptor is invalid", "guarded sources: an invalid T# behind a taken branch was accepted"); } + auto unsetFormat = valid; + unsetFormat[1] = valid[1] & ~(0x1ffu << 20u); + fill(unsetFormat, 1u); + { + AgcDriver::ShaderMemory memory({}); + const auto capture = memory.Capture(request); + require(capture->snapshot.images.size() == 1u && capture->snapshot.images[0].dwords == unsetFormat, "guarded sources: a T# with an unset format behind a taken branch was rejected or altered"); + } + auto filledFormat = valid; + filledFormat[1] = valid[1] | (0x1ffu << 20u); + fill(filledFormat, 1u); + { + AgcDriver::ShaderMemory memory({}); + const auto capture = memory.Capture(request); + require(capture->snapshot.images.size() == 1u && capture->snapshot.images[0].dwords == filledFormat, "guarded sources: a T# with an all-ones format behind a taken branch was rejected or altered"); + } auto typeOne = valid; typeOne[3] = 0xfacu | (1u << 28u); fill(typeOne, 1u); @@ -2829,6 +3253,11 @@ TableCompiled CompileTable(ShaderRecompiler::RecompileRequest request) { return out; } +std::shared_ptr CaptureTable(const ShaderRecompiler::RecompileRequest& request) { + AgcDriver::ShaderMemory memory({}); + return memory.Capture(request); +} + std::array TableSelected(const ShaderRecompiler::ImageTableSnapshot& tables, std::uint32_t table, const char* message) { namespace Abi = ShaderRecompiler::ImageTableAbi; const auto& column = tables.tables.at(table); @@ -4301,24 +4730,26 @@ void verifyImageTableUnkeyedAccess() { struct TableManyGuest { std::array, 128> heap{}; std::array output{}; - std::array srt{}; + std::array srt{}; }; constexpr std::uint32_t TableManyDirectWord = 96u; constexpr std::uint32_t TableManyExtraSamplerWord = 112u; +constexpr std::uint32_t TableManyMoreSamplerWord = 116u; +constexpr std::array TableManySamplerSgprs{8u, 12u, 64u, 68u, 72u, 76u, 80u, 84u}; -std::vector TableManyKeyCode(std::uint32_t first, std::uint32_t second, bool twoSamplers, std::uint32_t direct = 0u, bool extraSampler = false) { +std::vector TableManyKeyCode(std::uint32_t first, std::uint32_t second, std::uint32_t samplers, std::uint32_t direct = 0u, bool extraSampler = false) { std::vector code{0xf4080100u, 0xfa000000u, 0xf4080200u, 0xfa000010u, 0xf4080300u, 0xfa000020u, 0xf4080700u, 0xfa000030u, 0xf4080a00u, 0xfa000040u, 0x7e100280u}; for (std::uint32_t image = 0; image < direct; image++) code.insert(code.end(), {0xf40c0000u | ((44u + image * 8u) << 6u), 0xfa000000u | ((TableManyDirectWord + image * 8u) * 4u)}); if (extraSampler) code.insert(code.end(), {0xf4080f00u, 0xfa000000u | (TableManyExtraSamplerWord * 4u)}); + for (std::uint32_t sampler = 2u; sampler < samplers; sampler++) code.insert(code.end(), {0xf4080000u | (TableManySamplerSgprs[sampler] << 6u), 0xfa000000u | ((TableManyMoreSamplerWord + (sampler - 2u) * 4u) * 4u)}); for (std::uint32_t read = 0; read < first + second; read++) { - code.insert(code.end(), {0xf4000400u, 0xfa000050u + read * 4u, 0x8f108510u, read < first ? 0xf42c0502u : 0xf42c0514u, 0x20000000u, 0xf09c0f08u, 0x00450400u, 0x06100908u}); - if (twoSamplers) code.insert(code.end(), {0xf09c0f08u, 0x00650400u, 0x06100908u}); + code.insert(code.end(), {0xf4000400u, 0xfa000050u + read * 4u, 0x8f108510u, read < first ? 0xf42c0502u : 0xf42c0514u, 0x20000000u}); + for (std::uint32_t sampler = 0; sampler < samplers; sampler++) code.insert(code.end(), {0xf09c0f08u, ((TableManySamplerSgprs[sampler] / 4u) << 21u) | 0x00050400u, 0x06100908u}); } for (std::uint32_t image = 0; image < direct; image++) { const auto resource = ((44u + image * 8u) / 4u) << 16u; - code.insert(code.end(), {0xf09c0f08u, 0x00400400u | resource, 0x06100908u}); - if (twoSamplers) code.insert(code.end(), {0xf09c0f08u, 0x00600400u | resource, 0x06100908u}); + for (std::uint32_t sampler = 0; sampler < samplers; sampler++) code.insert(code.end(), {0xf09c0f08u, ((TableManySamplerSgprs[sampler] / 4u) << 21u) | 0x00000400u | resource, 0x06100908u}); if (extraSampler && image + 1u == direct) code.insert(code.end(), {0xf09c0f08u, 0x01e00400u | resource, 0x06100908u}); } code.insert(code.end(), {0xe0700000u, 0x80070800u, 0xbf810000u}); @@ -4345,6 +4776,8 @@ void verifySelectedRecordsWithinLimits(bool pairs, bool direct) { std::copy(direct.begin(), direct.end(), guest.srt.begin() + TableManyDirectWord + image * 8u); } guest.srt[TableManyExtraSamplerWord] = 0x91u; + std::fill(guest.srt.begin() + TableManyMoreSamplerWord, guest.srt.end(), 0u); + for (std::uint32_t sampler = 2u; sampler < TableManySamplerSgprs.size(); sampler++) guest.srt[TableManyMoreSamplerWord + (sampler - 2u) * 4u] = 0x92u; const auto address = static_cast(reinterpret_cast(guest.srt.data())); const std::array userData{static_cast(address), static_cast(address >> 32u)}; const auto columnOf = [](const IrResourcePlan& plan, const ImageResource& image) { return *plan.descriptorSources.at(image.source).tableColumn; }; @@ -4352,49 +4785,50 @@ void verifySelectedRecordsWithinLimits(bool pairs, bool direct) { return static_cast(std::count_if(plan.info.images.begin(), plan.info.images.end(), [&](const ImageResource& image) { return image.table != NoTable && columnOf(plan, image).selected; })); }; const std::string name = pairs ? "selected records within the pair limit" : "selected records within the image limit"; - const std::uint32_t limit = pairs ? ShaderInfo::MaxSampledPairs / 2u : ShaderInfo::MaxImages; + const std::uint32_t samplers = pairs ? static_cast(TableManySamplerSgprs.size()) : 1u; + const std::uint32_t limit = pairs ? ShaderInfo::MaxSampledPairs / samplers : ShaderInfo::MaxImages; const auto outside = Abi::PoisonCode(static_cast(Abi::PoisonReason::OutsideDomain)); if (direct) { const auto directImages = [&](const IrResourcePlan& plan) { return static_cast(std::count_if(plan.info.images.begin(), plan.info.images.end(), [](const ImageResource& image) { return image.table == NoTable; })); }; const auto reads = limit - 1u; - const auto fittingDirectCode = TableManyKeyCode(reads, 0u, pairs, 1u); + const auto fittingDirectCode = TableManyKeyCode(reads, 0u, samplers, 1u); const auto fittingDirect = GetResourcePlan(TableRequest(fittingDirectCode, userData)); const auto describe = [&](const IrResourcePlan& plan) { return std::to_string(plan.info.images.size()) + " images (" + std::to_string(selectedTables(plan)) + " selected tables, " + std::to_string(directImages(plan)) + " direct), " + std::to_string(plan.info.sampledPairs.size()) + " pairs"; }; - require(fittingDirect->info.images.size() == limit && selectedTables(*fittingDirect) == reads && directImages(*fittingDirect) == 1u && fittingDirect->info.sampledPairs.size() == (pairs ? 2u : 1u) * limit, (name + ": selected reads and a direct image that fit lost the per-key split").c_str()); - std::cout << name << ": " << reads << " selected reads and 1 direct image" << (pairs ? ", each with 2 samplers" : "") << ": " << describe(*fittingDirect) << '\n'; - const auto pastDirectCode = TableManyKeyCode(reads, 0u, pairs, pairs ? 1u : 2u, pairs); + require(fittingDirect->info.images.size() == limit && selectedTables(*fittingDirect) == reads && directImages(*fittingDirect) == 1u && fittingDirect->info.sampledPairs.size() == samplers * limit, (name + ": selected reads and a direct image that fit lost the per-key split").c_str()); + std::cout << name << ": " << reads << " selected reads and 1 direct image" << (pairs ? ", each with " + std::to_string(samplers) + " samplers" : std::string()) << ": " << describe(*fittingDirect) << '\n'; + const auto pastDirectCode = TableManyKeyCode(reads, 0u, samplers, pairs ? 1u : 2u, pairs); const auto pastDirectRequest = TableRequest(pastDirectCode, userData); const auto pastDirect = GetResourcePlan(pastDirectRequest); const auto mergedDirect = std::find_if(pastDirect->info.images.begin(), pastDirect->info.images.end(), [](const ImageResource& image) { return image.table != NoTable; }); - require(pastDirect->info.images.size() == (pairs ? 2u : 3u) && selectedTables(*pastDirect) == 0u && directImages(*pastDirect) == (pairs ? 1u : 2u) && pastDirect->info.sampledPairs.size() == (pairs ? 5u : 3u) && mergedDirect != pastDirect->info.images.end(), (name + ": selected reads that the direct " + (pairs ? "sampler" : "images") + " push past the limit were not merged into one table").c_str()); + require(pastDirect->info.images.size() == (pairs ? 2u : 3u) && selectedTables(*pastDirect) == 0u && directImages(*pastDirect) == (pairs ? 1u : 2u) && pastDirect->info.sampledPairs.size() == (pairs ? 2u * samplers + 1u : 3u) && mergedDirect != pastDirect->info.images.end(), (name + ": selected reads that the direct " + (pairs ? "sampler" : "images") + " push past the limit were not merged into one table").c_str()); const auto mergedColumn = columnOf(*pastDirect, *mergedDirect); require(!mergedColumn.selected && mergedColumn.selection == 0u && !mergedColumn.unkeyed && mergedColumn.keyDomains.size() == reads, (name + ": the table merged beside the direct " + (pairs ? "sampler" : "images") + " is not the union of the keys").c_str()); - const auto compiledDirect = CompileTable(pastDirectRequest); - const auto& directTables = compiledDirect.capture->snapshot.tables; + const auto capturedDirect = pairs ? CaptureTable(pastDirectRequest) : CompileTable(pastDirectRequest).capture; + const auto& directTables = capturedDirect->snapshot.tables; require(directTables.tables.size() == 1u, (name + ": the snapshot beside the direct " + (pairs ? "sampler" : "images") + " does not hold one table").c_str()); const auto& directSnapshot = directTables.tables[mergedDirect->table]; for (const auto key : {2u, 2u + reads / 2u, 1u + reads}) { require(key < directSnapshot.codes.size() && (directSnapshot.codes[key] & Abi::PoisonFlag) == 0u && directSnapshot.words.at(directSnapshot.codes[key]).dwords == heap[key], (name + ": a key beside the direct " + (pairs ? "sampler" : "images") + " does not bind its record").c_str()); } require(directSnapshot.codes.at(1) == outside && directSnapshot.codes.at(reads + 2u) == outside, (name + ": a key no read selects was bound beside the direct " + (pairs ? "sampler" : "images")).c_str()); - std::cout << name << ": " << reads << " selected reads and " << (pairs ? "1 direct image with a third sampler" : "2 direct images") << ": " << describe(*pastDirect) << ", the reads merged into one table with " << mergedColumn.keyDomains.size() << " key domains\n"; + std::cout << name << ": " << reads << " selected reads and " << (pairs ? "1 direct image with one more sampler" : "2 direct images") << ": " << describe(*pastDirect) << ", the reads merged into one table with " << mergedColumn.keyDomains.size() << " key domains\n"; return; } - const auto fitting = TableManyKeyCode(limit, 0u, pairs); + const auto fitting = TableManyKeyCode(limit, 0u, samplers); const auto atLimit = GetResourcePlan(TableRequest(fitting, userData)); - require(atLimit->info.images.size() == limit && selectedTables(*atLimit) == limit && atLimit->info.sampledPairs.size() == (pairs ? 2u : 1u) * limit, (name + ": reads that fit lost the per-key split").c_str()); - const auto past = TableManyKeyCode(limit + 1u, 0u, pairs); + require(atLimit->info.images.size() == limit && selectedTables(*atLimit) == limit && atLimit->info.sampledPairs.size() == samplers * limit, (name + ": reads that fit lost the per-key split").c_str()); + const auto past = TableManyKeyCode(limit + 1u, 0u, samplers); const auto request = TableRequest(past, userData); const auto plan = GetResourcePlan(request); - require(plan->info.images.size() == 1u && plan->info.images[0].table == 0u && plan->info.sampledPairs.size() == (pairs ? 2u : 1u), (name + ": reads past the limit were not merged into one table").c_str()); + require(plan->info.images.size() == 1u && plan->info.images[0].table == 0u && plan->info.sampledPairs.size() == samplers, (name + ": reads past the limit were not merged into one table").c_str()); const auto merged = columnOf(*plan, plan->info.images[0]); require(!merged.selected && merged.selection == 0u && !merged.unkeyed && merged.keyDomains.size() == limit + 1u, (name + ": the merged table is not the union of the keys").c_str()); - const auto compiled = CompileTable(request); - const auto& tables = compiled.capture->snapshot.tables; + const auto captured = pairs ? CaptureTable(request) : CompileTable(request).capture; + const auto& tables = captured->snapshot.tables; require(tables.tables.size() == 1u, (name + ": the snapshot does not hold one table").c_str()); const auto& snapshot = tables.tables[0]; for (const auto key : {2u, 2u + limit / 2u, 2u + limit}) { @@ -4402,12 +4836,151 @@ void verifySelectedRecordsWithinLimits(bool pairs, bool direct) { } require(snapshot.codes.at(1) == outside && snapshot.codes.at(limit + 3u) == outside, (name + ": a key no read selects was bound").c_str()); if (pairs) return; - const auto split = TableManyKeyCode(40u, 30u, false); + const auto split = TableManyKeyCode(40u, 30u, 1u); const auto twoHeaps = GetResourcePlan(TableRequest(split, userData)); const auto unselected = std::find_if(twoHeaps->info.images.begin(), twoHeaps->info.images.end(), [&](const ImageResource& image) { return !columnOf(*twoHeaps, image).selected; }); require(twoHeaps->info.images.size() == 31u && selectedTables(*twoHeaps) == 30u && unselected != twoHeaps->info.images.end() && columnOf(*twoHeaps, *unselected).keyDomains.size() == 40u, (name + ": the larger heap was not merged first, or the smaller one lost its split").c_str()); } +struct SamplerLimitGuest { + std::array record{}; + std::array output{}; + std::array srt{}; +}; + +constexpr std::uint32_t SamplerLimitTableSamplerByte = 2048u; +constexpr std::uint32_t SamplerLimitDirectSamplerWord = 8u; +constexpr std::uint32_t SamplerLimitDirectImageWord = 160u; + +std::vector SamplerLimitCode(std::uint32_t tableSamplers, bool directImages, const std::vector>& pairs) { + std::vector code{0xf4080100u, 0xfa000000u, 0xf4080700u, 0xfa000010u, 0x7e200500u, 0x8f108c10u, 0x7e100280u}; + for (const auto& [image, sampler] : pairs) { + if (directImages) code.insert(code.end(), {0xf40c0500u, 0xfa000000u | ((SamplerLimitDirectImageWord + image * 8u) * 4u)}); + else code.insert(code.end(), {0xf42c0502u, 0x20000000u | (image * 32u)}); + if (sampler < tableSamplers) code.insert(code.end(), {0xf4280202u, 0x20000000u | (SamplerLimitTableSamplerByte + sampler * 16u)}); + else code.insert(code.end(), {0xf4080200u, 0xfa000000u | ((SamplerLimitDirectSamplerWord + (sampler - tableSamplers) * 4u) * 4u)}); + code.insert(code.end(), {0xf09c0f08u, 0x00450400u, 0x06100908u}); + } + code.insert(code.end(), {0xe0700000u, 0x80070800u, 0xbf810000u}); + return code; +} + +std::uint32_t SamplerLimitImageOf(const std::uint32_t* words) { + return (words[2] >> 14u) - 1u; +} + +void verifySamplerLimits() { + using namespace ShaderRecompiler; + static TableTexture textures[4]; + alignas(4096) static SamplerLimitGuest guest; + constexpr std::uint32_t Images = 51u; + constexpr std::uint32_t TableSamplers = 60u; + guest.record = {}; + for (std::uint32_t image = 0; image < Images; image++) { + const auto words = TableT(textures[image % 4u].bytes.data(), TableFormat8888UNorm, TableType2D, image + 1u); + std::copy(words.begin(), words.end(), guest.record.begin() + image * 8u); + } + for (std::uint32_t sampler = 0; sampler < 64u; sampler++) { + const std::array words{0x92u, (sampler + 1u) << 12u, 0u, 0u}; + std::copy(words.begin(), words.end(), guest.record.begin() + SamplerLimitTableSamplerByte / 4u + sampler * 4u); + } + guest.srt = {}; + const auto heapV = TableV(guest.record.data(), 4096u, 1u); + const auto outputV = TableV(guest.output.data(), 0u, 16u); + std::copy(heapV.begin(), heapV.end(), guest.srt.begin()); + std::copy(outputV.begin(), outputV.end(), guest.srt.begin() + 4); + for (std::uint32_t sampler = 0; sampler < 36u; sampler++) { + const std::array words{0x92u, (sampler + 101u) << 12u, 0u, 0u}; + std::copy(words.begin(), words.end(), guest.srt.begin() + SamplerLimitDirectSamplerWord + sampler * 4u); + } + for (std::uint32_t image = 0; image < 3u; image++) { + const auto words = TableT(textures[image].bytes.data(), image == 1u ? TableFormat32UInt : TableFormat8888UNorm, TableType2D, image + 1u); + std::copy(words.begin(), words.end(), guest.srt.begin() + SamplerLimitDirectImageWord + image * 8u); + } + const auto address = static_cast(reinterpret_cast(guest.srt.data())); + const std::array userData{static_cast(address), static_cast(address >> 32u)}; + + const auto directOf = [](std::uint32_t image) { return image < 19u ? 0u : image < 36u ? 1u : 2u; }; + const auto riftPairs = [&](std::uint32_t tableSamplers) { + std::vector> pairs; + for (std::uint32_t image = 0; image < Images; image++) pairs.emplace_back(image, image); + for (std::uint32_t sampler = Images; sampler < tableSamplers; sampler++) pairs.emplace_back(sampler - Images, sampler); + for (std::uint32_t image = 0; image < Images; image++) pairs.emplace_back(image, tableSamplers + directOf(image)); + pairs.emplace_back(0u, tableSamplers + 2u); + return pairs; + }; + const auto riftCode = SamplerLimitCode(TableSamplers, false, riftPairs(TableSamplers)); + const auto riftRequest = TableRequest(riftCode, userData); + const auto rift = GetResourcePlan(riftRequest); + const auto& samplers = rift->info.samplers; + const auto tableCount = static_cast(std::count_if(samplers.begin(), samplers.end(), [](const SamplerResource& sampler) { return sampler.table != NoTable; })); + require(samplers.size() == TableSamplers + 3u && tableCount == TableSamplers && rift->info.images.size() == Images && rift->info.sampledPairs.size() == 112u, ("sampler limits: the program planned " + std::to_string(samplers.size()) + " samplers (" + std::to_string(tableCount) + " through the table), " + std::to_string(rift->info.images.size()) + " images and " + std::to_string(rift->info.sampledPairs.size()) + " pairs, expected 63 (60), 51 and 112").c_str()); + std::vector directResources; + for (std::uint32_t resource = 0; resource < samplers.size(); resource++) { + if (samplers[resource].table == NoTable) directResources.push_back(resource); + } + require(directResources.size() == 3u && directResources.front() >= 32u, "sampler limits: the direct samplers were not planned after the table samplers, at resource indices past 31"); + const auto compiled = CompileTable(riftRequest); + const auto* guestSamplers = TableBinding(*compiled.result, DescriptorRole::GuestSamplers); + require(guestSamplers != nullptr && guestSamplers->guestDescriptor.size() == 6u * 4u, "sampler limits: the three direct samplers are not bound as six sampler elements"); + for (std::uint32_t element = 0; element < 6u; element++) { + require(guestSamplers->guestDescriptor[element * 4u + 1u] == (element / 2u + 101u) << 12u, "sampler limits: a direct sampler element does not hold its S#"); + } + const auto* table = TableBinding(*compiled.result, DescriptorRole::ImageTable); + require(table != nullptr && table->count == Images && table->imageSamplers.size() == Images, "sampler limits: the image table does not bind the 51 images with their sampler masks"); + for (std::uint32_t element = 0; element < Images; element++) { + const auto image = SamplerLimitImageOf(table->guestDescriptor.data() + element * 8u); + require(image < Images, "sampler limits: an image table element does not hold one of the images"); + auto expected = std::uint64_t{3} << (directOf(image) * 2u); + if (image == 0u) expected |= std::uint64_t{3} << 4u; + require(table->imageSamplers[element] == expected, ("sampler limits: image " + std::to_string(image) + " is paired with sampler elements " + std::to_string(table->imageSamplers[element]) + ", expected " + std::to_string(expected)).c_str()); + } + std::cout << "sampler limits: 60 table samplers and 3 direct samplers (resources " << directResources[0] << " to " << directResources[2] << "), 51 images and 112 pairs planned; each image table element pairs with its direct sampler elements\n"; + + constexpr std::uint32_t Direct = 20u; + std::vector> directPairs; + for (std::uint32_t sampler = 0; sampler < Direct; sampler++) { + directPairs.emplace_back(0u, sampler); + directPairs.emplace_back(1u, sampler); + if (sampler >= 16u) directPairs.emplace_back(2u, sampler); + } + const auto directCode = SamplerLimitCode(0u, true, directPairs); + const auto direct = CompileTable(TableRequest(directCode, userData)); + const auto* directSamplers = TableBinding(*direct.result, DescriptorRole::GuestSamplers); + require(directSamplers != nullptr && directSamplers->guestDescriptor.size() == Direct * 2u * 4u, "sampler limits: 20 direct samplers, each paired with a filtered and an integer image, are not bound as 40 sampler elements"); + const auto all = (std::uint64_t{1} << (Direct * 2u)) - 1u; + std::uint32_t pairedImages = 0u; + for (const auto& binding : direct.result->bindings) { + if (binding.kind != DescriptorKind::SampledImage || binding.role != DescriptorRole::GuestImages) continue; + for (std::uint32_t element = 0; element < binding.count; element++) { + const auto image = SamplerLimitImageOf(binding.guestDescriptor.data() + element * 8u); + const auto expected = image == 2u ? all & ~((std::uint64_t{1} << 32u) - 1u) : all; + require(image < 3u && binding.imageSamplers.at(element) == expected, ("sampler limits: direct image " + std::to_string(image) + " is paired with sampler elements " + std::to_string(binding.imageSamplers.at(element)) + ", expected " + std::to_string(expected)).c_str()); + pairedImages++; + } + } + require(pairedImages == 3u, "sampler limits: the three direct images are not bound"); + std::cout << "sampler limits: 20 direct samplers bound as 40 sampler elements; the image sampled only through samplers 16 to 19 pairs with elements 32 to 39\n"; + + constexpr std::uint32_t TableDirect = 18u; + std::vector> tablePairs; + for (std::uint32_t sampler = 0; sampler < TableDirect; sampler++) tablePairs.emplace_back(sampler % 2u, sampler); + const auto tableDirectCode = SamplerLimitCode(0u, false, tablePairs); + const auto tableDirect = CompileTable(TableRequest(tableDirectCode, userData)); + const auto* tableDirectSamplers = TableBinding(*tableDirect.result, DescriptorRole::GuestSamplers); + require(tableDirectSamplers != nullptr && tableDirectSamplers->guestDescriptor.size() == TableDirect * 2u * 4u, "sampler limits: 18 direct samplers paired with image table images are not bound as 36 sampler elements"); + const auto* tableImages = TableBinding(*tableDirect.result, DescriptorRole::ImageTable); + require(tableImages != nullptr && tableImages->count == 2u && tableImages->imageSamplers.size() == 2u, "sampler limits: the image table does not bind the two images beside 18 direct samplers"); + for (std::uint32_t element = 0; element < 2u; element++) { + const auto image = SamplerLimitImageOf(tableImages->guestDescriptor.data() + element * 8u); + std::uint64_t expected = 0u; + for (std::uint32_t sampler = image; sampler < TableDirect; sampler += 2u) expected |= std::uint64_t{3} << (sampler * 2u); + require(image < 2u && tableImages->imageSamplers[element] == expected, ("sampler limits: image table image " + std::to_string(image) + " is paired with sampler elements " + std::to_string(tableImages->imageSamplers[element]) + ", expected " + std::to_string(expected)).c_str()); + } + std::cout << "sampler limits: 18 direct samplers beside an image table bound as 36 sampler elements; each table image pairs with its samplers' elements up to 35\n"; + +} + void verifyImageTableSrtKey() { using namespace ShaderRecompiler; namespace Abi = ImageTableAbi; @@ -5532,6 +6105,100 @@ void verifyDescriptorPhis() { require(planImages(splitReloads(8u)) == 4u, "descriptor Phi: separate reloads of T# dword 7 and dwords 0-6 were not split into their 4 T#s"); expectFailure([&] { static_cast(planImages(splitReloads(260u))); }, "GetImageResource dword 0 is not a valid runtime value", "descriptor Phi: a web over more than 512 descriptor tuples was accepted"); expectFailure([&] { static_cast(planImages(splitReloads(260u))); }, "; descriptor web: more than 512 descriptor states", "descriptor Phi: the failure did not name the state limit"); + + static constexpr std::array rawCode{ + 0xbe8803ffu, 0x00021000u, 0xbe8903ffu, 0x03c00000u, 0xbe8a03ffu, 0x0000003fu, 0xbe8b03ffu, 0x9000003fu, + 0xbe8c03ffu, 0x00000000u, 0xbe8d03ffu, 0x00000000u, 0xbe8e03ffu, 0x00000000u, 0xbe8f03ffu, 0x00000000u, + 0x7e040207u, 0x7d820480u, 0xbe840300u, 0xbe850301u, 0xbf068007u, 0xbf850003u, 0xf40c0202u, 0x06000000u, + 0xbf8cc07fu, 0x7e000280u, 0x7e020280u, 0xf0000f08u, 0x00020400u, 0xbf8c3f70u, 0xf800180fu, 0x07060504u, + 0xbf810000u, + }; + constexpr std::array directImage{0x00021000u, 0x03c00000u, 0x0000003fu, 0x9000003fu, 0u, 0u, 0u, 0u}; + constexpr std::array loadedImage{0x00021080u, 0x03c00000u, 0x0000003fu, 0x9000003fu, 0u, 0u, 0u, 0u}; + std::array rawMemory{}; + std::copy(loadedImage.begin(), loadedImage.end(), rawMemory.begin() + 16); + std::array rawUserData{}; + rawUserData[0] = 0x500000u; + rawUserData[3] = 0x40u; + rawUserData[7] = 1u; + const std::array rawRegions{{{0x500000u, std::as_bytes(std::span{rawMemory})}}}; + ShaderPixelStageInfo rawPixel{}; + rawPixel.targetOutputMode[0] = 9u; + rawPixel.targetExportMapping.fill(0xe4u); + RecompileRequest rawRequest{}; + rawRequest.shader = {ShaderStage::Fragment, 0x240000u, rawCode, 0, {}}; + rawRequest.context.waveSize = 32u; + rawRequest.context.userDataBaseRegister = 0u; + rawRequest.context.userData = rawUserData; + rawRequest.context.pixel = rawPixel; + rawRequest.context.memory = rawRegions; + rawRequest.target = BufferTarget(); + rawRequest.target.vulkanVersion = 0x00401000u; + rawRequest.target.spirvVersion = 0x00010300u; + rawRequest.target.subgroupSize = 32u; + rawRequest.layout.pushConstantSizeBytes = 128u; + rawRequest.useCache = false; + require(GetResourcePlan(rawRequest)->info.images.size() == 2u, "descriptor Phi: a literal T# and a scalar-address-loaded T# were not split into separate images"); + RequestMemoryView rawView(rawRequest.context.memory); + const auto rawRuntime = rawView.MakeRuntime(rawRequest.context.userData, rawRequest.shader.codeAddress); + const auto rawCapture = CaptureResources(rawRequest, rawRuntime); + const auto holdsRaw = [&](const std::array& words) { + return std::ranges::any_of(rawCapture->snapshot.images, [&](const DescriptorValue& value) { return value.dwords == words; }); + }; + require(rawCapture->snapshot.images.size() == 2u && holdsRaw(directImage) && holdsRaw(loadedImage), "descriptor Phi: the capture did not keep both the literal and loaded T#s"); + for (const auto& source : rawCapture->plan->descriptorSources) { + if (source.requiredBlocks.empty()) continue; + for (std::uint32_t dword = 0; dword < source.dwordCount; ++dword) { + const auto* read = source.dwords[dword]->Resolve(); + if (read->Opcode() == IrOpcode::LoadAddressU32) require(rawCapture->plan->memoryInfo.at(read->Flags().index).planningOnly, "descriptor Phi: a copied descriptor read remains a GPU memory access"); + } + } + require(!Recompile(rawRequest).spirv.empty(), "descriptor Phi: a scalar-address-loaded T# merged with a literal did not recompile"); + std::vector rawPlanBytes; + require(ShaderDiskCache::EncodePlan(*rawCapture->plan, rawPlanBytes), "descriptor Phi: the guarded raw plan did not encode"); + IrResourcePlan cachedRawPlan; + require(ShaderDiskCache::DecodePlan(rawPlanBytes, cachedRawPlan), "descriptor Phi: the guarded raw plan did not decode"); + const auto guardedSource = std::ranges::find_if(cachedRawPlan.descriptorSources, [](const DescriptorSource& source) { return !source.requiredBlocks.empty(); }); + require(guardedSource != cachedRawPlan.descriptorSources.end(), "descriptor Phi: the cached plan lost its raw read guard"); + const auto requiredBlock = guardedSource->requiredBlocks.front(); + guardedSource->requiredBlocks.front() = static_cast(cachedRawPlan.controlFlow.size()); + std::vector invalidGuardBytes; + require(ShaderDiskCache::EncodePlan(cachedRawPlan, invalidGuardBytes), "descriptor Phi: the invalid guard fixture did not encode"); + IrResourcePlan invalidGuardPlan; + require(!ShaderDiskCache::DecodePlan(invalidGuardBytes, invalidGuardPlan), "descriptor Phi: a cached guard outside control flow was accepted"); + guardedSource->requiredBlocks.front() = requiredBlock; + rawUserData[7] = 0u; + for (const auto pointer : {0x500000u, 0x900000u, 0u}) { + rawUserData[0] = pointer; + const auto skipped = CaptureResources(rawRequest, rawView.MakeRuntime(rawUserData, rawRequest.shader.codeAddress)); + require(skipped->snapshot.images.size() == 2u, "descriptor Phi: a skipped scalar-address arm lost its planned image"); + require(skipped->snapshot.nullRootReads == 0u && skipped->snapshot.srtPoison.empty(), "descriptor Phi: an inactive raw read reported a null root or fault"); + require(std::ranges::any_of(skipped->snapshot.images, [&](const DescriptorValue& value) { return value.dwords == directImage; }), "descriptor Phi: skipping the scalar-address arm lost the literal T#"); + require(std::ranges::any_of(skipped->snapshot.images, [](const DescriptorValue& value) { return std::ranges::all_of(value.dwords, [](std::uint32_t word) { return word == 0u; }); }), "descriptor Phi: a skipped scalar-address T# was read"); + ResourceSnapshot cachedSnapshot; + ResourceMaterializer{}.Materialize(cachedRawPlan, rawView.MakeRuntime(rawUserData, rawRequest.shader.codeAddress), cachedSnapshot); + require(cachedSnapshot.nullRootReads == 0u && cachedSnapshot.srtPoison.empty(), "descriptor Phi: a cached inactive raw read reported a null root or fault"); + require(std::ranges::equal(cachedSnapshot.images, skipped->snapshot.images, [](const DescriptorValue& first, const DescriptorValue& second) { return first.dwords == second.dwords; }), "descriptor Phi: the decoded plan changed skipped raw reads"); + std::vector imageSources; + for (const auto& image : cachedRawPlan.info.images) imageSources.push_back(image.source); + std::vector interpretedImages; + std::vector interpretedFlat; + std::vector interpretedActive; + SrtWalker{}.EvaluateRuntimeSources(cachedRawPlan, imageSources, rawView.MakeRuntime(rawUserData, rawRequest.shader.codeAddress), interpretedImages, interpretedFlat, cachedRawPlan.cleanFlatSlots, interpretedActive); + require(std::ranges::equal(interpretedImages, cachedSnapshot.images, [](const DescriptorValue& first, const DescriptorValue& second) { return first.dwords == second.dwords; }), "descriptor Phi: interpreted source queries changed skipped raw reads"); + require(imageSources == cachedRawPlan.materializationSources, "descriptor Phi: the raw fixture has non-image materialization sources"); + const auto* tape = Detail::SrtTape::For(cachedRawPlan); + require(tape != nullptr, "descriptor Phi: the guarded plan has no SRT tape"); + std::vector tapeImages; + require(tape->Run(cachedRawPlan, rawView.MakeRuntime(rawUserData, rawRequest.shader.codeAddress), tapeImages, interpretedFlat, interpretedActive), "descriptor Phi: the SRT tape refused skipped raw reads"); + require(std::ranges::equal(tapeImages, interpretedImages, [](const DescriptorValue& first, const DescriptorValue& second) { return first.dwords == second.dwords; }), "descriptor Phi: SRT tape and interpreted raw reads disagree"); + require(!Recompile(rawRequest).spirv.empty(), "descriptor Phi: an untaken scalar-address arm prevented recompilation"); + } + rawUserData[7] = 1u; + rawUserData[0] = 0x900000u; + expectFailure([&] { static_cast(CaptureResources(rawRequest, rawView.MakeRuntime(rawUserData, rawRequest.shader.codeAddress))); }, "failed to evaluate runtime sources", "descriptor Phi: a taken unmapped scalar-address T# was accepted"); + expectFailure([&] { static_cast(Recompile(rawRequest)); }, "failed to evaluate runtime sources", "descriptor Phi: a taken unmapped scalar-address T# recompiled"); + std::cout << "descriptor Phi: skipped mapped, unmapped and null reads stay null; taken unmapped reads fail\n"; } void verifyUniformTableImage() { @@ -7013,7 +7680,7 @@ void verifyPixelRequestSerialization() { minimal.context.pixel->layerExported = true; minimal.context.pixel->ditheredAlphaToCoverage = true; const auto encoded = serializer.Serialize(minimal); - require(requestPrefix(encoded, 8u) == "NVNQQTMAAAA=", "new requests did not use serialization version 51"); + require(requestPrefix(encoded, 8u) == "NVNQQTUAAAA=", "new requests did not use serialization version 53"); auto withBarycentric = minimal; withBarycentric.target.fragmentBarycentricBuiltins = true; require(serializer.Deserialize(serializer.Serialize(withBarycentric)).request.target.fragmentBarycentricBuiltins && !serializer.Deserialize(encoded).request.target.fragmentBarycentricBuiltins, "barycentric built-ins were lost in serialization"); @@ -7134,7 +7801,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=", "NVNQQTIAAAA=", "NVNQQTQAAAA="}) { + for (const auto unsupported : {"NVNQQQAAAAA=", "NVNQQTIAAAA=", "NVNQQTYAAAA="}) { expectFailure([&] { static_cast(serializer.Deserialize(unsupported)); }, "serialization version", "an unsupported request version was accepted"); } static constexpr std::string_view version18Request{ @@ -7253,15 +7920,17 @@ void verifyDepthExportClamp() { const auto both = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(tail)); require(both.request.target.largePixelFunctionLdsBuffer && both.request.target.pixelFunctionStorageDwords == 2048u && both.request.context.pixel->depthExportMin == 0.25f && both.request.context.pixel->depthExportMax == 0.75f, - "depth export: a version 49 request lost the bounds or the version 45 target tail"); + "depth export: a version 53 request lost the bounds or the version 45 target tail"); auto bytes = decodeBase64(RequestSerializer{}.Serialize(tail)); - require(bytes.size() > 8u && bytes[4] == 51u, "depth export: a depth exporting request did not use serialization version 51"); + require(bytes.size() > 8u && bytes[4] == 53u, "depth export: a depth exporting request did not use serialization version 53"); static constexpr std::array boundBytes{0x00u, 0x00u, 0x80u, 0x3eu, 0x00u, 0x00u, 0x40u, 0x3fu}; const auto found = std::search(bytes.begin(), bytes.end(), boundBytes.begin(), boundBytes.end()); require(found != bytes.end() && std::search(found + 1, bytes.end(), boundBytes.begin(), boundBytes.end()) == bytes.end(), "depth export: the clamp bounds were not written exactly once"); bytes.erase(found, found + boundBytes.size()); bytes.pop_back(); + bytes.pop_back(); + bytes.pop_back(); for (const std::uint8_t legacyVersion : {48u, 47u, 46u}) { if (legacyVersion == 47u) bytes.pop_back(); bytes[4] = legacyVersion; @@ -7281,13 +7950,15 @@ void verifyDepthExportClamp() { ditheredPixel.ditheredAlphaSamples = 2u; dithered.context.pixel = ditheredPixel; auto ditheredBytes = decodeBase64(RequestSerializer{}.Serialize(dithered)); - require(ditheredBytes.size() > 8u && ditheredBytes[4] == 51u, "a two-sample dithered request did not use serialization version 51"); + require(ditheredBytes.size() > 8u && ditheredBytes[4] == 53u, "a two-sample dithered request did not use serialization version 53"); if (depthExport) { const auto bounds = std::search(ditheredBytes.begin(), ditheredBytes.end(), boundBytes.begin(), boundBytes.end()); require(bounds != ditheredBytes.end(), "a two-sample dithered depth exporting request lacks the clamp bounds"); ditheredBytes.erase(bounds, bounds + boundBytes.size()); } ditheredBytes.pop_back(); + ditheredBytes.pop_back(); + ditheredBytes.pop_back(); ditheredBytes[4] = 48u; const auto main48 = RequestSerializer{}.Deserialize(encodeBase64(ditheredBytes)); require(main48.request.context.pixel->ditheredAlphaToCoverage && main48.request.context.pixel->ditheredAlphaSamples == 2u && main48.request.context.pixel->depthExportEnable == depthExport && @@ -7353,6 +8024,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.pop_back(); + version20Vertex.pop_back(); version20Vertex.resize(version20Vertex.size() - sizeof(std::uint32_t)); version20Vertex.pop_back(); version20Vertex.pop_back(); @@ -7367,6 +8040,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.pop_back(); + legacy.pop_back(); legacy.resize(legacy.size() - sizeof(std::uint32_t)); legacy.pop_back(); legacy.pop_back(); @@ -7440,11 +8115,11 @@ void verifyVertexInfoVersion19() { auto disabledBlend = merging; disabledBlend.context.pixel->targetMerges.at(0).blend = {false, true, 6u, 7u, 1u, 1u, 0u, 4u, 0xbu, {1u, 2u, 3u, 4u}}; require(serializer.Serialize(disabledBlend) == serializer.Serialize(merging), "a disabled color target blend changed the serialized request"); - // A version 43 request: no color target merge, and no fused arithmetic flag, pixel function LDS flag, storage size or barycentric built-ins flag at the end. + // A version 43 request: no color target merge, and no fused arithmetic flag, pixel function LDS flag, storage size, barycentric built-ins flag, host interpolation first flag or shader image minimum LOD flag at the end. const auto version43Bytes = [&](std::vector bytes) { require(bytes.end() - bytes.begin() > mergeOffset + static_cast(mergeBytes) && std::equal(plainPixel.begin() + mergeOffset, plainPixel.begin() + mergeOffset + static_cast(mergeBytes), bytes.begin() + mergeOffset), "a replay fixture does not hold the default color target merge after the sample order"); bytes.erase(bytes.begin() + mergeOffset, bytes.begin() + mergeOffset + static_cast(mergeBytes)); - bytes.resize(bytes.size() - sizeof(std::uint32_t) - 4u); + bytes.resize(bytes.size() - sizeof(std::uint32_t) - 6u); return bytes; }; auto version42 = version43Bytes(plainPixel); @@ -7606,9 +8281,11 @@ void verifyDepthRemapRequests() { const auto plainBytes = decodeBase64(plain); const auto remappedBytes = decodeBase64(remapped); constexpr std::size_t pointTailBytes = 13u; - require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 15u + pointTailBytes && std::equal(plainBytes.begin(), plainBytes.end() - 15 - pointTailBytes, remappedBytes.begin()) && std::equal(plainBytes.end() - 8 - pointTailBytes, plainBytes.end(), remappedBytes.end() - 8 - pointTailBytes), "the depth remap flag and mode are not the 6 bytes before the f32 flush and denormal preserve flags, the point tail, the fused arithmetic flag, the pixel function LDS flag, the pixel function storage size, the barycentric built-in flag and the tessellation evaluation subgroup flag of a vertex request"); + require(plainBytes.size() == remappedBytes.size() && plainBytes.size() > 17u + pointTailBytes && std::equal(plainBytes.begin(), plainBytes.end() - 17 - pointTailBytes, remappedBytes.begin()) && std::equal(plainBytes.end() - 10 - pointTailBytes, plainBytes.end(), remappedBytes.end() - 10 - pointTailBytes), "the depth remap flag and mode are not the 6 bytes before the f32 flush and denormal preserve flags, the point tail, the fused arithmetic flag, the pixel function LDS flag, the pixel function storage size, the barycentric built-in flag, the tessellation evaluation subgroup flag, the host interpolation first flag and the shader image minimum LOD flag of a vertex request"); for (const std::uint8_t legacyVersion : {32u, 34u}) { auto legacyBytes = remappedBytes; + legacyBytes.pop_back(); + legacyBytes.pop_back(); legacyBytes.resize(legacyBytes.size() - sizeof(std::uint32_t)); legacyBytes.pop_back(); legacyBytes.pop_back(); @@ -7622,6 +8299,8 @@ void verifyDepthRemapRequests() { 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.pop_back(); + version35.pop_back(); version35.resize(version35.size() - sizeof(std::uint32_t)); version35.pop_back(); version35.pop_back(); @@ -7675,8 +8354,8 @@ void verifyMergedHullConfiguration() { require(!serializer.Deserialize(separate).request.graphics->tessellation->mergedHull, "a separate hull configuration was read as merged"); constexpr std::size_t pointTailBytes = 13u; auto legacy = decodeBase64(merged); - require(legacy.size() > 56u + pointTailBytes && legacy[4] == 51u, "a merged hull request did not use serialization version 51"); - legacy.resize(legacy.size() - sizeof(std::uint32_t) - 3u - pointTailBytes); + require(legacy.size() > 58u + pointTailBytes && legacy[4] == 53u, "a merged hull request did not use serialization version 53"); + legacy.resize(legacy.size() - sizeof(std::uint32_t) - 5u - pointTailBytes); legacy.pop_back(); legacy.pop_back(); require(legacy[legacy.size() - 52u] == 1u, "the merged hull flag is not the last byte of the tessellation configuration"); @@ -7898,7 +8577,7 @@ void verifyPixelInputs() { } } -ShaderRecompiler::RecompileResult recompileSlots(std::initializer_list controls, std::span code) { +ShaderRecompiler::RecompileRequest slotRequest(std::initializer_list controls, std::span code, bool hostInterpolationFirst) { using namespace ShaderRecompiler; ShaderPixelStageInfo pixel{}; pixel.interpolatorCount = static_cast(controls.size()); @@ -7916,15 +8595,21 @@ ShaderRecompiler::RecompileResult recompileSlots(std::initializer_list capabilities{spv::CapabilityFloat64}; request.target.supportedCapabilities = capabilities; request.layout.pushConstantSizeBytes = 128; request.useCache = false; - return Recompile(request); + return request; } -std::vector> slotInputs(std::initializer_list controls, std::span code) { - const auto result = recompileSlots(controls, code); +ShaderRecompiler::RecompileResult recompileSlots(std::initializer_list controls, std::span code, bool hostInterpolationFirst = false) { + return ShaderRecompiler::Recompile(slotRequest(controls, code, hostInterpolationFirst)); +} + +std::vector> slotInputs(std::initializer_list controls, std::span code, bool hostInterpolationFirst = false) { + const auto result = recompileSlots(controls, code, hostInterpolationFirst); const auto& words = result.spirv.Words(); std::map locations; std::map perVertex; @@ -7970,6 +8655,43 @@ void verifyPixelParameterSlots() { expectFailure([] { static_cast(recompileSlots({0x423u, 0x3u}, shared)); }, "passes its vertices through unchanged", "an interpolated pass-through input was accepted"); } +void verifyHostInterpolationFirst() { + static constexpr std::array shared{0xc8100000u, 0xc8110001u, 0xc8140500u, 0xc8150501u, 0xf800180fu, 0x05040504u, 0xbf810000u}; + auto inputs = slotInputs({0x3u, 0x3u}, shared, true); + require(inputs.size() == 1u && inputs[0].first == 3u && !inputs[0].second, "host interpolation first still read a plain v_interp input per vertex"); + static constexpr std::array mixed{0xc8100000u, 0xc8110001u, 0xc8160402u, 0xc81a0802u, 0xc81e0f02u, 0xf800180fu, 0x07060504u, 0xbf810000u}; + inputs = slotInputs({0x0u, 0x400u, 0x22u, 0x320u}, mixed, true); + require(inputs.size() == 1u && inputs[0].first == 0u && inputs[0].second, "host interpolation first did not keep KHR barycentrics for a slot read flat and interpolated"); + require(!recompileSlots({0x0u, 0x400u, 0x22u, 0x320u}, mixed, true).barycentricEmulation.active, "host interpolation first emulated barycentrics the device provides"); + using namespace ShaderRecompiler; + const RequestSerializer serializer; + const auto replayFixture = [&](std::initializer_list controls, std::span code) { + std::vector keys[2]; + for (const bool hostFirst : {false, true}) { + auto request = slotRequest(controls, code, hostFirst); + const auto encoded = serializer.Serialize(request); + const auto replay = serializer.Deserialize(encoded); + require(replay.request.target.hostInterpolationFirst == hostFirst && serializer.Serialize(replay.request) == encoded, "replay lost host interpolation first"); + std::vector replayKey; + RecompileCacheKey::Build(request, keys[hostFirst]); + RecompileCacheKey::Build(replay.request, replayKey); + require(replayKey == keys[hostFirst], "replay changed the cache key of a host interpolation first request"); + const auto original = Recompile(request); + const auto replayed = Recompile(replay.request); + require(replayed.barycentricEmulation.active == original.barycentricEmulation.active, "replay changed the barycentric path of a host interpolation first request"); + verifyResult(original, replayed); + request.useCache = true; + const auto cached = Recompile(request); + const auto hit = Recompile(serializer.Deserialize(serializer.Serialize(request)).request); + require(hit.cacheHit && hit.variantId == cached.variantId, "host interpolation first replay did not reuse the original shader variant"); + verifyResult(cached, hit); + } + require(keys[0] != keys[1], "the cache key ignores host interpolation first"); + }; + replayFixture({0x3u, 0x3u}, shared); + replayFixture({0x0u, 0x400u, 0x22u, 0x320u}, mixed); +} + void verifyF16PixelParameterSlots() { static constexpr std::array high{0xd7420002u, 0x00020100u, 0xd75a0003u, 0x040a0300u, 0xf800180fu, 0x03030303u, 0xbf810000u}; static constexpr std::array low{0xd7420002u, 0x00020000u, 0xd75a0003u, 0x040a0200u, 0xf800180fu, 0x03030303u, 0xbf810000u}; @@ -8108,11 +8830,16 @@ void verifyInt64AtomicCapabilities() { const auto texelAddress = static_cast(reinterpret_cast(texels.data())); const std::array userData{ 0u, 0u, 0u, 0u, - static_cast(bufferAddress), static_cast((bufferAddress >> 32u) & 0xffffu), 256u, 0x01016facu, + static_cast(bufferAddress), static_cast((bufferAddress >> 32u) & 0xffffu), 256u, 0x31016facu, static_cast(texelAddress >> 8u), static_cast((texelAddress >> 40u) & 0xffu) | (Format32_32UInt << 20u) | (3u << 30u), 7u | (7u << 14u), 0xfacu | (Type2D << 28u), 0u, 0u, 0u, 0u}; const std::vector bufferAtomic{0xe1705000u, 0x80012803u, 0xbf810000u}; const std::vector imageAtomic{0xf03c0308u, 0x00020a08u, 0xbf810000u}; + const std::vector bufferOr{0xe1685000u, 0x80010003u, 0xbf810000u}; + const std::vector bufferAnd{0xe1645000u, 0x80010003u, 0xbf810000u}; + const std::vector bufferXor{0xe16c5000u, 0x80010003u, 0xbf810000u}; + const std::vector bufferOrTwice{0xe1685000u, 0x80010003u, 0xe1685000u, 0x80010203u, 0xbf810000u}; + const std::vector bufferOrAnd{0xe1685000u, 0x80010003u, 0xe1645000u, 0x80010203u, 0xbf810000u}; const auto compile = [&](std::span code, spv::Capability added) { const std::array capabilities{spv::CapabilityInt64, spv::CapabilityPhysicalStorageBufferAddresses, spv::CapabilityStorageBuffer8BitAccess, static_cast(added)}; RecompileRequest request{}; @@ -8141,8 +8868,54 @@ void verifyInt64AtomicCapabilities() { } return false; }; + const auto verifySplit = [](std::span words, spv::Op atomic) { + std::map definitions; + std::map constants; + std::uint32_t uintType = 0; + for (std::size_t cursor = 5; cursor < words.size();) { + const auto count = words[cursor] >> 16u; + require(count != 0 && count <= words.size() - cursor, "64-bit atomics: truncated SPIR-V instruction"); + const auto op = words[cursor] & 0xffffu; + if (op == spv::OpTypeInt && words[cursor + 2] == 32u && words[cursor + 3] == 0u) uintType = words[cursor + 1]; + if (op == spv::OpConstant && count == 4u) constants[words[cursor + 2]] = words[cursor + 3]; + if (op == spv::OpAccessChain || op == spv::OpIAdd || op == spv::OpUConvert || op == spv::OpVariable) definitions[words[cursor + 2]] = cursor; + cursor += count; + } + const auto opcodeOf = [&](std::uint32_t id) { return definitions.contains(id) ? words[definitions.at(id)] & 0xffffu : 0xffffffffu; }; + std::vector halves; + for (std::size_t cursor = 5; cursor < words.size();) { + const auto count = words[cursor] >> 16u; + if ((words[cursor] & 0xffffu) == static_cast(atomic) && opcodeOf(words[cursor + 3]) == spv::OpAccessChain) { + const auto chain = definitions.at(words[cursor + 3]); + if ((words[chain] >> 16u) == 6u && opcodeOf(words[chain + 3]) == spv::OpAccessChain) halves.push_back(cursor); + } + cursor += count; + } + require(halves.size() == 2u, "64-bit atomics: an unused-result bitwise buffer atomic was not emitted as two 32-bit atomics"); + const auto low = halves[0]; + const auto high = halves[1]; + require(words[low + 1] == uintType && words[high + 1] == uintType, "64-bit atomics: a half of a split buffer atomic is not 32 bits wide"); + const auto lowChain = definitions.at(words[low + 3]); + const auto highChain = definitions.at(words[high + 3]); + require(words[lowChain + 3] == words[highChain + 3], "64-bit atomics: the halves of a split buffer atomic use different buffers"); + const auto highIndex = words[highChain + 5]; + require(opcodeOf(highIndex) == spv::OpIAdd, "64-bit atomics: the high half of a split buffer atomic does not address the next dword"); + const auto add = definitions.at(highIndex); + require(words[add + 3] == words[lowChain + 5] && constants.contains(words[add + 4]) && constants.at(words[add + 4]) == 1u, "64-bit atomics: the high half of a split buffer atomic is not one dword after the low half"); + require(words[low + 6] != words[high + 6] && opcodeOf(words[low + 6]) == spv::OpUConvert && opcodeOf(words[high + 6]) == spv::OpUConvert, "64-bit atomics: the halves of a split buffer atomic do not take the low and high dwords of the operand"); + }; expectFailure([&] { static_cast(compile(bufferAtomic, spv::CapabilityShader)); }, "64-bit buffer atomics need shaderBufferInt64Atomics", "64-bit atomics: a buffer_atomic_inc_x2 compiled without shaderBufferInt64Atomics"); require(declares(compile(bufferAtomic, spv::CapabilityInt64Atomics), spv::CapabilityInt64Atomics), "64-bit atomics: a buffer_atomic_inc_x2 does not declare Int64Atomics"); + const std::array*, spv::Op>, 3> bitwise{{{&bufferOr, spv::OpAtomicOr}, {&bufferAnd, spv::OpAtomicAnd}, {&bufferXor, spv::OpAtomicXor}}}; + for (const auto& [code, atomic] : bitwise) { + const auto split = compile(*code, spv::CapabilityShader); + require(!declares(split, spv::CapabilityInt64Atomics), "64-bit atomics: an unused-result bitwise buffer atomic declares Int64Atomics on a device without it"); + verifySplit(split, atomic); + require(declares(compile(*code, spv::CapabilityInt64Atomics), spv::CapabilityInt64Atomics), "64-bit atomics: an unused-result bitwise buffer atomic does not use the 64-bit atomic where the device has it"); + } + require(!declares(compile(bufferOrTwice, spv::CapabilityShader), spv::CapabilityInt64Atomics), "64-bit atomics: two buffer_atomic_or_x2 of one program declare Int64Atomics on a device without it"); + expectFailure([&] { static_cast(compile(bufferOrAnd, spv::CapabilityShader)); }, "64-bit buffer atomics need shaderBufferInt64Atomics", "64-bit atomics: a buffer_atomic_or_x2 and a buffer_atomic_and_x2 of one program compiled without shaderBufferInt64Atomics"); + require(declares(compile(bufferOrAnd, spv::CapabilityInt64Atomics), spv::CapabilityInt64Atomics), "64-bit atomics: a buffer_atomic_or_x2 and a buffer_atomic_and_x2 of one program do not use the 64-bit atomics where the device has them"); expectFailure([&] { static_cast(compile(imageAtomic, spv::CapabilityShader)); }, "64-bit image atomics need VK_EXT_shader_image_atomic_int64", "64-bit atomics: an image_atomic_swap on a 32_32 image compiled without shaderImageInt64Atomics"); require(declares(compile(imageAtomic, spv::CapabilityInt64ImageEXT), spv::CapabilityInt64ImageEXT), "64-bit atomics: an image_atomic_swap on a 32_32 image does not declare Int64ImageEXT"); } @@ -10151,6 +10924,149 @@ void verifySrgbDepthCompareModes() { } } +void verifyRuntimeImageSwitchDeduplication() { + using namespace ShaderRecompiler; + const auto emitImageSwitch = [](IrOpcode opcode, bool packed) { + const bool read = opcode == IrOpcode::ImageRead; + IrProgram program; + program.SetWaveSize(32u); + program.Resources().stage = IrShaderStage::Compute; + program.Resources().srtPlanComplete = true; + program.Resources().resourceTrackingComplete = true; + program.Metadata().shaderInfoComplete = true; + program.Metadata().bindingLayoutComplete = true; + auto& block = program.CreateBlock(); + program.SetEntryBlock(block); + program.BlockOrder().push_back(&block); + program.Metadata().blockInfo.emplace_back(); + auto& zero = program.CreateValue(IrOpcode::Void, IrType::U32); + zero.SetImmediateU32(0u); + auto& enabled = program.CreateValue(IrOpcode::Void, IrType::Bool); + enabled.SetImmediateBool(true); + auto& handle = program.CreateValue(IrOpcode::GetImageResource, IrType::ImageResource, 0u); + for (std::uint32_t i = 0u; i < 8u; ++i) handle.AddArgument(&zero); + block.AppendInstruction(&handle); + auto& address = program.CreateValue(IrOpcode::MakeImageAddress, IrType::ImageAddress); + for (std::uint32_t i = 0u; i < 13u; ++i) address.AddArgument(&zero); + block.AppendInstruction(&address); + MemoryInfo memory{}; + memory.kind = ResourceKind::Image; + memory.resource = 0u; + memory.imageDimension = packed ? RdnaImageDimension::Dim2DArray : RdnaImageDimension::Dim2D; + memory.imageAddressComponents = packed ? 3u : 2u; + memory.imagePacked = packed; + memory.dmask = 0xfu; + memory.dataBits = 32u; + memory.dataDwords = 4u; + memory.componentCount = 4u; + program.Resources().memoryInfo.push_back(memory); + ImageResource image{}; + image.resourceClass = read ? ImageResourceClass::Sampled : ImageResourceClass::Storage; + image.numericClass = IrTextureNumericClass::Uint; + image.dimension = memory.imageDimension; + image.packed = packed; + image.read = read; + image.written = !read; + std::vector modes; + if (packed) { + modes = ResourceMaterializer::RuntimeImageModes(image); + for (const auto& mode : modes) { + const auto kind = DescriptorBindingForImage(mode); + auto& descriptors = program.Metadata().bindings.descriptors; + if (std::ranges::none_of(descriptors, [&](const IrDescriptorBinding& entry) { return entry.kind == kind; })) { + descriptors.push_back({kind, {0u}}); + } + } + } else { + image.table = 0u; + DescriptorSource source{}; + source.dwordCount = 8u; + TableColumn column{}; + column.stride = 32u; + column.heapSource = 1u; + source.tableColumn = column; + program.Resources().descriptorSources.push_back(source); + DescriptorSource heap{}; + heap.dwordCount = 4u; + program.Resources().descriptorSources.push_back(heap); + modes = {image, image}; + modes[1].mipMode = static_cast(2); + program.Metadata().bindings.descriptors = { + {DescriptorBindingKind::FaultBuffer, {}}, + {DescriptorBindingKind::ImageTableMap, {}}, + {DescriptorBindingKind::ImageStorageTable, {}}, + }; + } + program.Info().images.push_back(image); + program.Info().runtimeImageModes.push_back(modes); + if (read) { + auto& op = program.CreateValue(IrOpcode::ImageRead, IrType::U32x4); + op.AddArgument(&handle); + op.AddArgument(&address); + op.AddArgument(&enabled); + block.AppendInstruction(&op); + } else { + auto& texel = program.CreateValue(IrOpcode::CompositeConstructU32x4, IrType::U32x4); + for (std::uint32_t i = 0u; i < 4u; ++i) texel.AddArgument(&zero); + block.AppendInstruction(&texel); + auto& op = program.CreateValue(IrOpcode::ImageWrite, IrType::Void); + op.AddArgument(&handle); + op.AddArgument(&address); + op.AddArgument(&texel); + op.AddArgument(&enabled); + block.AppendInstruction(&op); + } + ShaderComputeInputInfo compute{}; + compute.threadsNum[0] = 32u; + compute.threadsNum[1] = 1u; + compute.threadsNum[2] = 1u; + compute.hostSubgroupSize = 32u; + compute.waveSize = 32u; + ShaderStageInputInfo inputs{}; + inputs.compute = &compute; + CompiledBindingLayout bindings{}; + bindings.layout = program.Metadata().bindings; + SpirvTargetOptions target{}; + target.vulkanVersion = 0x00401000u; + target.spirvVersion = 0x00010300u; + target.subgroupSize = 32u; + const auto words = SpirvEmitter().Emit(program, inputs, bindings, target); + std::map targets; + for (std::size_t cursor = 5u; cursor < words.size(); cursor += words[cursor] >> 16u) { + const auto count = words[cursor] >> 16u; + require(count != 0u && count <= words.size() - cursor, "runtime image switch deduplication: truncated SPIR-V instruction"); + if ((words[cursor] & 0xffffu) == spv::OpSwitch) { + for (std::size_t at = cursor + 3u; at + 1u < cursor + count; at += 2u) { + targets.emplace(words[at], words[at + 1u]); + } + } + } + require(targets.size() == modes.size(), "runtime image switch deduplication: OpSwitch did not cover every runtime image mode"); + return std::pair{modes, targets}; + }; + for (const auto opcode : {IrOpcode::ImageRead, IrOpcode::ImageWrite}) { + const auto [modes, targets] = emitImageSwitch(opcode, true); + bool deduplicatedCube = false; + bool separatedFormats = false; + for (std::uint32_t i = 0u; i < modes.size(); ++i) { + for (std::uint32_t j = i + 1u; j < modes.size(); ++j) { + if (modes[i].dimension == modes[j].dimension && modes[i].numericClass == modes[j].numericClass) { + if (modes[i].packedFormat == modes[j].packedFormat) { + require(targets.at(i) == targets.at(j), "runtime image switch deduplication: packed cube and array modes with identical format were not merged"); + deduplicatedCube = true; + } else { + require(targets.at(i) != targets.at(j), "runtime image switch deduplication: distinct packed formats shared an OpSwitch target"); + separatedFormats = true; + } + } + } + } + require(deduplicatedCube && separatedFormats, "runtime image switch deduplication: packed modes did not exercise both merged and distinct OpSwitch targets"); + } + const auto [mipModes, mipTargets] = emitImageSwitch(IrOpcode::ImageWrite, false); + require(mipTargets.at(0u) != mipTargets.at(1u), "runtime image switch deduplication: storage image modes with different mip modes shared an OpSwitch target"); +} + int main(int argc, char** argv) { try { #ifdef _WIN32 @@ -10159,6 +11075,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]) == "--layer-specialization-release") { + verifyLayerSpecializationRelease(); + std::cout << "Layer specialization metadata survives SPIR-V release and rebuild\n"; + return 0; + } 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"; @@ -10260,6 +11181,11 @@ int main(int argc, char** argv) { std::cout << "Direct samplers count toward the sampled pair limit beside selected image-table records\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--sampler-limits") { + verifySamplerLimits(); + std::cout << "Sampler limits passed\n"; + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--srt-read-patching") { verifyUniformVectorComponents(); verifyWaterfallRuntimeValues(); @@ -10272,6 +11198,10 @@ int main(int argc, char** argv) { std::cout << "TYPE=0 image descriptor materialization passed\n"; return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--descriptor-phis") { + verifyDescriptorPhis(); + return 0; + } require(argc == 1, "unknown shader memory test arguments"); verifyRegisterSources(); verifyEvaluatedValues(); @@ -10279,15 +11209,22 @@ int main(int argc, char** argv) { verifyLayoutPair(); verifySignedSrtComparison(); verifyTypeZeroImageMaterialization(); + verifyUnsetFormatImageMaterialization(); verifyOutOfRangeConstantReads(); + verifyDescriptorWebLaneIndexFold(); + verifyDescriptorWebTexelFold(); + verifyDescriptorWebSelectOrder(); verifySignedMinimum(); verifyMayEvaluate(); + verifyMayDecideSharedConditions(); verifyPureFlatSlots(); verifyPlanConditionPruning(); verifyPlanRetention(); + verifyPendingBranchLeftUnpruned(); verifyPlanRetentionChains(); verifyPlanCopyAnswers(); verifyGuardedDescriptorSources(); + verifyPendingBranchCapture(); verifyLaneMaskGuardedDescriptorSources(); verifyExecGuardedDescriptorSources(); verifySrtTape(); @@ -10295,6 +11232,7 @@ int main(int argc, char** argv) { verifyImageTableKeys(); verifyImageTables(); verifySpecializedModuleRelease(); + verifyLayerSpecializationRelease(); verifyImageTableRegistration(); verifyScalarAddressRule(); verifyPointerImageTable(); @@ -10327,6 +11265,7 @@ int main(int argc, char** argv) { verifyLegacyPixelRequests(); verifyPixelExportReplay(); verifyPixelParameterSlots(); + verifyHostInterpolationFirst(); verifyF16PixelParameterSlots(); verifyComputedTexelOffsets(); verifyShaderClockScopes(); @@ -10355,6 +11294,7 @@ int main(int argc, char** argv) { verifySamplerTableDeduplication(); verifyDynamicStorageMipChain(); verifySrgbDepthCompareModes(); + verifyRuntimeImageSwitchDeduplication(); verifyQuadMaskNeighbourReads(); verifyCoherentGds(); #if ANYPS5_ENABLE_SPIRV_TOOLS diff --git a/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp b/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp index b3f20c7fa2..a6e3650096 100644 --- a/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/SubgroupBarrier.cpp @@ -16,18 +16,19 @@ namespace { struct Case { VkDriverId driver; - bool usable; + bool controlBarrier; + bool workgroupFence; }; 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}, + {VK_DRIVER_ID_AMD_PROPRIETARY, false, false}, + {VK_DRIVER_ID_AMD_OPEN_SOURCE, true, true}, + {VK_DRIVER_ID_MESA_RADV, true, true}, + {VK_DRIVER_ID_NVIDIA_PROPRIETARY, true, true}, + {VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS, true, true}, + {VK_DRIVER_ID_INTEL_OPEN_SOURCE_MESA, true, true}, + {VK_DRIVER_ID_MESA_LLVMPIPE, true, true}, + {VK_DRIVER_ID_MOLTENVK, false, true}, }; constexpr std::array BranchedLdsStep{ @@ -120,8 +121,8 @@ 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")); + require(AgcDriver::SubgroupControlBarrierUsable(item.driver) == item.controlBarrier, "driver " + std::to_string(static_cast(item.driver)) + ": expected subgroup control barriers to be " + (item.controlBarrier ? "usable" : "unusable")); + require(AgcDriver::WorkgroupScopeFenceUsable(item.driver) == item.workgroupFence, "driver " + std::to_string(static_cast(item.driver)) + ": expected workgroup-scope fences to be " + (item.workgroupFence ? "usable" : "unusable")); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp index 084422c535..5c0440e4f8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Submit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Submit.cpp @@ -4,6 +4,7 @@ #include "execution/VulkanTestDevice.hpp" #include "prx/libSceAgcDriver/Execution/include/QueueState.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" @@ -219,6 +220,9 @@ void testEndOfPipeInterrupts() { check(sceAgcDriverAddEqEvent(eq, 0, &graphicsTag) == 0, "graphics event registration failed"); check(sceAgcDriverAddEqEvent(eq, 0x20, &computeTag) == 0, "compute event registration failed"); expectFailure([] { sceAgcDriverAddEqEvent(0, 0, nullptr); }); + expectFailure([&] { sceAgcDriverAddEqEvent(eq, -1, &graphicsTag); }); + expectFailure([] { sceAgcDriverDeleteEqEvent(0, 0); }); + expectFailure([&] { sceAgcDriverDeleteEqEvent(eq, -1); }); struct Output final : AgcDriver::IVideoOutput, AgcDriver::IFlipRequest, std::enable_shared_from_this { std::atomic ready{0}; std::shared_ptr Reserve(const AgcDriver::FlipInfo&) override { return shared_from_this(); } @@ -311,6 +315,7 @@ void testEndOfPipeInterrupts() { check(sceAgcDriverDeleteEqEvent(eq, 0x20) == 0, "compute event deletion failed"); owner.reset(); check(sceKernelDeleteEqueue(eq) == 0, "event queue deletion failed"); + expectFailure([&] { sceAgcDriverDeleteEqEvent(eq, 0); }); } std::array writeData(volatile std::uint32_t* address, std::uint32_t value) { @@ -858,6 +863,23 @@ void testHeaderWithoutProgramAddress() { AgcDriverRegisterShader_nid_postfix(&header.shader); } +void testRegisterList() { + using AgcDriver::DriverDetail::RegisterList; + const AgcDriver::Registers userConfig{{0x24a, 1}, {0x24b, 2}, {0x25b, 3}}; + const RegisterList list(userConfig); + check(list.size() == 3 && list.Expand() == userConfig, "a register list does not hold its registers' offsets and values"); + check(list.find(0x24b) != list.end() && list.find(0x24b)->second == 2 && list.find(0x24c) == list.end() && list.find(0x200) == list.end(), "a register list found the wrong register"); + RegisterList added; + added.emplace(0x25b, 3); + added.emplace(0x24a, 1); + added.emplace(0x24b, 2); + added.emplace(0x24a, 9); + check(added == list, "a register list is not kept in offset order, or a second emplace replaced a value"); + AgcDriver::DriverDetail::DecodedShaderState decoded{{{0x08a, 7}}, AgcDriver::InitialContextRegisters(), userConfig}; + const AgcDriver::DriverDetail::RegisteredShaderState state(decoded); + check(state.shader.Expand() == decoded.shader && state.context.Expand() == decoded.context && state.userConfig.Expand() == decoded.userConfig, "a registered state did not keep the decoded registers"); +} + void testRegisteredFloatMode() { using AgcDriver::DriverDetail::RegisteredFloatMode; using AgcDriver::DriverDetail::ShaderSnapshot; @@ -1068,8 +1090,13 @@ void testRegisteredFastLaunchWorkgroupVertex() { const std::array context{{ {0x2d5u, 0x00482030u}, {0x1ffu, 32u}, {0x2ceu, 32u}, {0x2d3u, 64u}, {0x29bu, 2u}, {0x2abu, 4u}, }}; + auto& driver = AgcDriver::DriverDetail::Driver::Get(); + const std::array strip{{{0x242u, 6u}, {0x25bu, 0x201u}}}; + AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, context, strip); + const auto prepared = driver.PreparedEntryHashes(&header.shader); const std::array primitive{{{0x242u, 1u}, {0x25bu, 0x201u}}}; AgcDriverResolveGraphicsStagesAbi_nid_postfix(stages, context, primitive); + check(driver.PreparedEntryHashes(&header.shader) == prepared, "a registered fast-launch program resolved with a triangle strip was not prepared as for a point list"); } void testWorkerFailure() { @@ -1221,6 +1248,7 @@ int main(int argc, char** argv) { testLabelResetByItsQueueBeforeItLanded(); testWideLabelPartlyResetByItsQueueBeforeItLanded(); testSmallCopyBehindPendingSource(); + testRegisterList(); testRegisteredFloatMode(); testDispatchFloatMode(); testReadOnlyResummarize(); diff --git a/core/libs/prx/libSceAgcDriver/tests/TextureDetiler.cpp b/core/libs/prx/libSceAgcDriver/tests/TextureDetiler.cpp index c773ddb446..5076c91a8d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TextureDetiler.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TextureDetiler.cpp @@ -209,6 +209,26 @@ void RunTextureDetilerTests(const Context& context, const TextureDetilerTestAcce Require(access.descriptorPoolCount() == pools, "a full descriptor pool whose batches completed must be reset and reused, not joined by a new pool"); } + { + DeviceFunctions table{}; + table.updateDescriptorSets = context.Function("vkUpdateDescriptorSets"); + table.allocateDescriptorSets = context.Function("vkAllocateDescriptorSets"); + table.cmdBindPipeline = context.Function("vkCmdBindPipeline"); + table.cmdBindDescriptorSets = context.Function("vkCmdBindDescriptorSets"); + table.cmdPushConstants = context.Function("vkCmdPushConstants"); + table.cmdDispatch = context.Function("vkCmdDispatch"); + auto tabled = context; + tabled.functions = &table; + TextureDetiler cached(tabled); + cached.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 20, destination, 40, layout, 0); + const auto before = DeviceProcLookups(); + cached.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 20, destination, 40, layout, 0); + const auto lookups = DeviceProcLookups() - before; + Require(lookups == 0, "a detile dispatch looked up " + std::to_string(lookups) + " Vulkan functions by name while the device's function table holds them"); + const auto tabledCapture = access.lastDispatch(); + Require(tabledCapture.groupsX == 3 && tabledCapture.groupsY == 2 && tabledCapture.groupsZ == 1, "a detile dispatch through the device's function table recorded the wrong group counts"); + } + reject([&] { detiler.Dispatch(VK_NULL_HANDLE, TextureTileMode::kStandard4KB, 4, source, 0, destination, 0, layout, 0); }, "active command buffer"); reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, VK_NULL_HANDLE, 0, destination, 0, layout, 0); }, "source and destination buffers"); reject([&] { detiler.Dispatch(commands, TextureTileMode::kStandard4KB, 4, source, 0, VK_NULL_HANDLE, 0, layout, 0); }, "source and destination buffers"); diff --git a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp index f91d045d4f..edf7d68e41 100644 --- a/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/TextureTiling.cpp @@ -306,6 +306,11 @@ void gcnTilingTests() { Require(mips[0].tiledSize == 224u * 40u * 2u && mips[0].blocksPerRow == 28u && mips[0].pitchBytes == 448u && !mips[0].gcnMicroTiled, "PS4 1D thin texture layout changed"); reject([] { ComputeGcnMipLayout(TextureTileMode::kGcnThin1D, 14u, 212u, 35u, 216u, false, 1u); }, "is not padded the way its tile mode pads a row"); } + { + const auto mips = ComputeGcnMipLayout(TextureTileMode::kGcnThin1D, 20u, 68u, 68u, 128u, true, 7u); + Require(mips[0].tiledSize == 128u * 128u * 4u && mips[1].tiledOffset == 0x10000u && mips[2].tiledOffset == 0x14000u, "a POW2_PAD PS4 mip chain pads the rows of level 0"); + Require(ComputeGcnMipLayout(TextureTileMode::kGcnThin1D, 20u, 68u, 68u, 128u, true, 1u)[0].tiledSize == 128u * 72u * 4u, "a single POW2_PAD PS4 level keeps its micro-tile rows"); + } { const auto mips = ComputeGcnMipLayout(TextureTileMode::kGcnDisplay2D, 56u, 256u, 256u, 256u, false, 4u); Require(!mips[0].gcnMicroTiled && !mips[1].gcnMicroTiled && mips[2].gcnMicroTiled && mips[3].gcnMicroTiled, "PS4 2D tiled mips switch to 1D tiling below one macro tile"); diff --git a/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp b/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp index f1546a3e4b..d570e6c831 100644 --- a/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/UnsupportedDiagnostics.cpp @@ -1,7 +1,8 @@ #include "prx/libSceAgcDriver/include/AgcDriver.hpp" #include "SceTypes.hpp" +#include "tests/ExportLookup.hpp" -int main() { +int main(int argc, char** argv) { if (sceAgcDriverGetCoredumpAddress() != nullptr) return 1; if (sceAgcDriverSetTargetRingForDiag() != static_cast(0x8A6D0001)) return 2; auto* volatile linked = &sceAgcDriverSetFlip; @@ -13,5 +14,35 @@ int main() { if (sceAgcDriverSubmitToRazorACQ() != static_cast(0x8A6D0001)) return 8; if (sceAgcDriverSuspendPointSubmitDirect() != static_cast(0x8A6D0001)) return 9; if (sceAgcDriverModuleRegistration() != static_cast(0x8A6D0003)) return 10; + struct Export { + int (APS5_VABI* function)(void); + const char* name; + }; + const Export exports[] = { + {sceAgcDriverCreateQueue, "sceAgcDriverCreateQueue"}, + {sceAgcDriverCwsrResumeAcq, "sceAgcDriverCwsrResumeAcq"}, + {sceAgcDriverCwsrSuspendAcq, "sceAgcDriverCwsrSuspendAcq"}, + {sceAgcDriverDebugHardwareStatus, "sceAgcDriverDebugHardwareStatus"}, + {sceAgcDriverDestroyQueue, "sceAgcDriverDestroyQueue"}, + {sceAgcDriverGetRegShadowInfo, "sceAgcDriverGetRegShadowInfo"}, + {sceAgcDriverGetRegShadowInfoAgr, "sceAgcDriverGetRegShadowInfoAgr"}, + {sceAgcDriverGetReservedDmemForAgc, "sceAgcDriverGetReservedDmemForAgc"}, + {sceAgcDriverGetWorkloadStreamInfo, "sceAgcDriverGetWorkloadStreamInfo"}, + {sceAgcDriverIDHSSubmit, "sceAgcDriverIDHSSubmit"}, + {sceAgcDriverNotifyDefaultStates, "sceAgcDriverNotifyDefaultStates"}, + {sceAgcDriverPassInfoDownward, "sceAgcDriverPassInfoDownward"}, + {sceAgcDriverPatchClearState, "sceAgcDriverPatchClearState"}, + {sceAgcDriverSetWorkloadComplete, "sceAgcDriverSetWorkloadComplete"}, + {sceAgcDriverSetWorkloadsActive, "sceAgcDriverSetWorkloadsActive"}, + {sceAgcDriverSetupAsyncGraphics, "sceAgcDriverSetupAsyncGraphics"}, + {sceAgcDriverSetupRegisterShadow, "sceAgcDriverSetupRegisterShadow"}, + {sceAgcDriverSubmitToHDRScopesACQ, "sceAgcDriverSubmitToHDRScopesACQ"}, + {sceAgcDriverSuspendPointSubmit, "sceAgcDriverSuspendPointSubmit"}, + {sceAgcDriverSysEnableSubmitDone45Exception, "sceAgcDriverSysEnableSubmitDone45Exception"}, + {sceAgcDriverSysGetClientNumber, "sceAgcDriverSysGetClientNumber"}, + {sceAgcDriverSysIsGameClosed, "sceAgcDriverSysIsGameClosed"}, + {sceAgcDriverTmpInitIdhs, "sceAgcDriverTmpInitIdhs"}, + }; + if (!ExportsResolveByName(argc > 1 ? argv[1] : nullptr, exports, "sceAgcDriverNotExported")) return 11; return 0; } diff --git a/core/libs/prx/libSceAgcDriver/tests/VertexInput.cpp b/core/libs/prx/libSceAgcDriver/tests/VertexInput.cpp index 48d26f71df..4da90d4dc9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/VertexInput.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/VertexInput.cpp @@ -58,6 +58,131 @@ template void fails(TAction&& fn) { Require(failed, "invalid vertex input was accepted"); } +std::vector randomInput(std::mt19937& random) { + std::vector input; + const auto size = random() % 9; + for (unsigned n = 0; n < size; ++n) { + auto a = attribute(n); + a.components = 1 + random() % 4; + a.fetchIndex = random() % 2; + a.resource.fields[0] += (random() % 4096) * 16; + a.resource.fields[1] = (random() % 33) * 16u << 16u; + a.resource.fields[2] = random(); + const std::array formats{13u, 22u, 36u, 50u, 56u, 64u, 71u, 74u, 77u}; + a.resource.fields[3] = formats[random() % formats.size()] << 12u; + input.push_back(a); + } + return input; +} + +void rememberedFormats(const Context& ctx) { + VertexBufferFormats formats; + auto remembered = ctx; + remembered.vertexFormats = &formats; + for (unsigned pass = 0; pass < 2; ++pass) { + std::mt19937 random(4099); + for (unsigned i = 0; i < 2000; ++i) { + const auto input = randomInput(random); + const auto expected = BuildVertexInputLayout(ctx, input); + const auto count = queries.load(); + same(BuildVertexInputLayout(remembered, input), expected); + Require(pass == 0 || queries == count, "a remembered vertex format was queried again"); + } + } + auto unsupported = attribute(); + unsupported.components = 1; + unsupported.resource.fields[3] = 5u << 12u; + fails([&] { BuildVertexInputLayout(ctx, std::span(&unsupported, 1)); }); + fails([&] { BuildVertexInputLayout(remembered, std::span(&unsupported, 1)); }); + const auto count = queries.load(); + fails([&] { BuildVertexInputLayout(remembered, std::span(&unsupported, 1)); }); + Require(queries == count, "a remembered unsupported vertex format was queried again"); + auto duplicate = std::array{attribute(3), attribute(3)}; + fails([&] { BuildVertexInputLayout(remembered, duplicate); }); +} + +std::array lacking{}; + +std::string outcome(const Context& ctx, const VertexAttribute& input) { + try { + const auto layout = BuildVertexInputLayout(ctx, std::span(&input, 1)); + const auto host = layout.attributes[0].format; + return std::string(host == DecodeVertexFormat(input).format ? "narrow " : "widened ") + std::to_string(host) + " " + std::to_string(layout.bindings[0].stride); + } catch (const std::runtime_error& error) { + return error.what(); + } +} + +std::vector everyFormat() { + std::vector inputs; + for (std::uint32_t code = 0; code < 128u; ++code) { + for (std::uint32_t components = 1; components <= 4u; ++components) { + for (std::uint32_t formatComponents = 0; formatComponents <= 4u; ++formatComponents) { + auto input = attribute(); + input.components = components; + input.formatComponents = formatComponents; + input.resource.fields[3] = code << 12u; + inputs.push_back(input); + } + } + } + for (std::uint32_t components = 1; components <= 4u; ++components) { + auto input = attribute(); + input.components = components; + input.resource.fields = {}; + inputs.push_back(input); + } + return inputs; +} + +void rememberedFormatSets() { + auto ctx = context(); + ctx.formatProperties = [](VkPhysicalDevice, VkFormat format, VkFormatProperties* properties) { + ++queries; + *properties = {}; + const auto index = static_cast(format); + if (index >= lacking.size() || !lacking[index]) properties->bufferFeatures = VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT; + }; + const auto inputs = everyFormat(); + std::mt19937 random(8191); + std::vector expected; + unsigned widened = 0; + unsigned refused = 0; + for (unsigned device = 0; device < 9; ++device) { + for (auto& format : lacking) format = random() % 8 < (device < 8 ? device : 4u); + expected.clear(); + for (const auto& input : inputs) { + expected.push_back(outcome(ctx, input)); + widened += expected.back().starts_with("widened"); + refused += expected.back().find("device does not support vertex format") != std::string::npos; + } + VertexBufferFormats formats; + auto remembered = ctx; + remembered.vertexFormats = &formats; + if (device < 8) { + for (unsigned pass = 0; pass < 2; ++pass) { + const auto count = queries.load(); + for (std::size_t i = 0; i < inputs.size(); ++i) Require(outcome(remembered, inputs[i]) == expected[i], "a remembered vertex format answered unlike the device"); + Require(pass == 0 || queries == count, "a remembered vertex format was queried again"); + } + continue; + } + std::atomic mismatches{0}; + std::barrier ready(8); + std::vector threads; + for (unsigned t = 0; t < 8; ++t) threads.emplace_back([&, t] { + ready.arrive_and_wait(); + for (std::size_t n = 0; n < inputs.size(); ++n) { + const auto i = (n + t * 331u) % inputs.size(); + if (outcome(remembered, inputs[i]) != expected[i]) ++mismatches; + } + }); + for (auto& thread : threads) thread.join(); + Require(mismatches == 0, "concurrent layout builds disagreed with the device's vertex formats"); + } + Require(widened != 0 && refused != 0, "the device format sets left vertex format widening or refusal untested"); +} + void descriptorDiagnostics(const Context& ctx) { auto input = attribute(7); input.resource.fields = {0x89abcde0u, 0x0010abcdu, 0x12345678u, 0x0104dfacu}; @@ -88,6 +213,8 @@ void descriptorDiagnostics(const Context& ctx) { void run() { auto ctx = context(); descriptorDiagnostics(ctx); + rememberedFormats(ctx); + rememberedFormatSets(); VertexInputCache cache(ctx, 2); std::array attributes{attribute(0), attribute(1)}; auto first = get(cache, ctx, 1, attributes); @@ -137,19 +264,7 @@ void run() { VertexInputCache generated(ctx, 16); std::mt19937 random(719); for (unsigned i = 0; i < 10000; ++i) { - std::vector input; - const auto size = random() % 9; - for (unsigned n = 0; n < size; ++n) { - auto a = attribute(n); - a.components = 1 + random() % 4; - a.fetchIndex = random() % 2; - a.resource.fields[0] += (random() % 4096) * 16; - a.resource.fields[1] = (random() % 33) * 16u << 16u; - a.resource.fields[2] = random(); - const std::array formats{13u, 22u, 36u, 50u, 56u, 64u, 71u, 74u, 77u}; - a.resource.fields[3] = formats[random() % formats.size()] << 12u; - input.push_back(a); - } + auto input = randomInput(random); const auto expected = BuildVertexInputLayout(ctx, input); same(*get(generated, ctx, 1 + i, input), expected); for (auto& a : input) a.resource.fields[0] += 256; diff --git a/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp b/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp index ee2a3fb221..dc04843754 100644 --- a/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/WaitShutdown.cpp @@ -74,6 +74,11 @@ void CheckCommandBufferSubmission() { Require(sceAgcDriverSubmitMultiCommandBuffers(&graphicsCtx, cmdBufs, cmdSizes, 1) == 0, "graphics context multi submit failed"); Require(sceAgcDriverSubmitMultiCommandBuffers(&computeCtx, cmdBufs, cmdSizes, 1) == 0, "compute context multi submit failed"); + alignas(alignof(std::uint32_t)) char unalignedBuffer[sizeof(MockQueueContext) + 4]{}; + auto* unalignedCtx = reinterpret_cast(unalignedBuffer + 1); + Require(sceAgcDriverSubmitCommandBuffer(unalignedCtx, &emptyPacket) == 0, "unaligned context submit must succeed"); + Require(sceAgcDriverSubmitMultiCommandBuffers(unalignedCtx, cmdBufs, cmdSizes, 1) == 0, "unaligned context multi submit must succeed"); + bool submitRejected = false; try { sceAgcDriverSubmitCommandBuffer(&invalidCtx, &emptyPacket); diff --git a/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp b/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp index 755dc8c40d..274bd8ff07 100644 --- a/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp @@ -201,6 +201,50 @@ void CheckStoredOverUsesPageEvidence() { Require(!StoredOver(base + Page, Page, before), "a page-local CPU write made an untouched page in the same block count as stored over"); } +void CheckRepeatedNeighbourStores() { + void* memory = AllocateWatched(3 * Block); + const auto base = reinterpret_cast(memory); + const auto boundary = base + 2 * Block - 0x9000; + const auto sharedFirst = boundary - (base + Block); + const auto neighbourBytes = static_cast(base + 3 * Block - boundary); + std::memset(memory, 0x11, 3 * Block); + + const auto synced = CollectWrites(base, 3 * Block); + Require(synced != 0, "the test block is not collected"); + for (int store = 0; store < 6; ++store) MarkWritten(boundary, neighbourBytes); + Require(!StoredOver(base + Block, static_cast(sharedFirst), synced), "repeated stores of the neighbour's bytes count for the first surface's part of the shared block"); + Require(StoredAllSince_nid_no_patch(boundary, neighbourBytes, synced), "repeated stores of the neighbour's bytes forget that they covered them"); + + const auto pieces = TrackerGeneration(); + for (int store = 0; store < 3; ++store) MarkWritten(boundary + static_cast(store) * 0x100, 4); + MarkWritten(boundary, 0x400); + Require(StoredOver(boundary + 0x200, 4, pieces) && !StoredOver(boundary + 0x400, 4, pieces), "a store over earlier pieces of the block answers for other bytes than its own"); + MarkWritten(boundary + 0x800, 4); + MarkWritten(boundary + 0x900, 4); + Require(!StoredOver(base + Block, static_cast(sharedFirst), pieces), "a store over earlier pieces counts for the first surface's part of the shared block"); + + 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; + MarkWritten(neighbour, 0x100); + const auto secondStored = MarkWritten(neighbour + 0x1000, 0x100); + const auto thirdStored = MarkWritten(neighbour + 0x2000, 0x100); + MarkWritten(neighbour + 0x3000, 0x100); + const auto settled = TrackerGeneration(); + MarkWritten(neighbour, 0x100); + MarkWritten(neighbour, 0x100); + MarkWritten(neighbour + 0x5000, 0x100); + Require(!StoredOver(freshBase, static_cast(neighbour - freshBase), secondStored), "a full exact ring evicted a newer piece instead of its oldest"); + Require(StoredOver(freshBase, static_cast(neighbour - freshBase), freshSynced), "a forgotten store is not taken as a store over the rest of the block"); + + const auto partial = TrackerGeneration(); + MarkWritten(neighbour + 0x5080, 0x100); + Require(StoredOver(neighbour + 0x5000, 0x10, settled) && StoredOver(neighbour + 0x5100, 0x10, partial), "a store that overlaps an earlier piece without containing it replaces it"); + Require(!StoredOver(freshBase, static_cast(neighbour - freshBase), thirdStored), "a store that overlaps without containing evicted a newer piece instead of the oldest"); +} + void CheckStoredAllSince() { constexpr std::size_t bytes = 4 * Block; auto* memory = static_cast(AllocateWatched(bytes)); @@ -1353,10 +1397,12 @@ std::uint64_t failedCollectBegin = 0; std::uint64_t failedCollectEnd = 0; std::uint64_t unwatchHookBegin = 0; std::uint64_t unwatchHookEnd = 0; +bool unwatchHookProven = true; void RecordUnwatch(std::uint64_t address, std::size_t bytes) { unwatchHookBegin = address; unwatchHookEnd = address + bytes; + unwatchHookProven = UnwatchWritesProven(); } bool FailFreshCollect(std::uintptr_t address, std::size_t bytes, void (*)(void*, std::uintptr_t, std::uintptr_t), void*) { @@ -1380,6 +1426,7 @@ void CheckFailedCollectUnwatch() { Require(Watched(base, Block) && Watched(base + 2 * Block, Block), "a failed collect unwatched unrelated blocks"); Require(failedCollectBegin == base + Block && failedCollectEnd == base + 2 * Block, "the failed fresh collect did not cover its block"); Require(unwatchHookBegin <= base + Block && unwatchHookEnd >= base + 2 * Block, "a failed collect did not run the unwatch hook over its block"); + Require(!unwatchHookProven, "a failed collect ran the unwatch hook as if the CPU writes to its block were proven"); } struct CpuWritePinProbe; @@ -1895,6 +1942,106 @@ void CheckUnwatchedImport() { #endif } +struct ImportHookCall { + std::uint64_t begin; + std::uint64_t end; + bool watched; + bool proven; +}; + +std::vector importHookCalls; +std::uint64_t importHookProbe = 0; + +void RecordImportHook(std::uint64_t address, std::size_t bytes) { + importHookCalls.push_back({address, address + bytes, Watched(importHookProbe, Block), UnwatchWritesProven()}); +} + +void CheckImportWalkUnwatchHooks() { + void* memory = AllocateWatched(4 * Block); + const auto base = reinterpret_cast(memory); + std::memset(memory, 0x11, 4 * Block); + Require(CollectWritesUncached(base, 4 * Block) != 0, "the import walk block is not collected"); + struct Reset { + ~Reset() { + SetImportWalkFailureForTest(0); + SetUnwatchHook(nullptr); + } + } reset; + SetUnwatchHook(&RecordImportHook); + for (const int walk : {1, 2}) { + const std::string what = walk == 1 ? "an import that stays watched whose page walk failed before the import" : "an import that stays watched whose page walk failed after the import"; + const auto imported = base + static_cast(walk - 1) * 2 * Block; + Require(Watched(imported, Block), what + ": its block was not watched before the import"); + importHookCalls.clear(); + importHookProbe = imported; + bool hookedBeforeImport = false; + SetImportWalkFailureForTest(walk); + Require(ImportWatched(imported, Block, [&] { + hookedBeforeImport = !importHookCalls.empty(); + return true; + }), what + ": the import failed"); + SetImportWalkFailureForTest(0); + Require(importHookCalls.size() == 1, what + " did not run the unwatch hook once"); + const auto& call = importHookCalls.front(); + Require(call.begin <= imported && imported + Block <= call.end, what + " gave the unwatch hook a range that misses its block"); + Require(call.watched, what + " ran the unwatch hook after its block left the write watch"); + Require(!call.proven, what + " ran the unwatch hook as if the CPU writes to its block were proven"); + Require(hookedBeforeImport == (walk == 1), what + (walk == 1 ? " ran the unwatch hook after the import" : " ran the unwatch hook before the import")); + Require(!Watched(imported, Block), what + " left its block watched"); + } + importHookCalls.clear(); + importHookProbe = base + 3 * Block; + Unwatch(base + 3 * Block, Block); + Require(importHookCalls.size() == 1 && importHookCalls.front().watched && importHookCalls.front().proven, "Unwatch did not run the unwatch hook once, while watched, with its CPU writes proven"); +} + +std::uint64_t collectDuringHookAddress = 0; +std::size_t collectDuringHookBytes = 0; +std::size_t collectDuringHookCalls = 0; + +void CollectDuringHook(std::uint64_t, std::size_t) { + if (collectDuringHookCalls++ == 0) static_cast(CollectWritesUncached(collectDuringHookAddress, collectDuringHookBytes)); +} + +void CheckImportWalkHookKeepsLaterWindows() { + void* memory = AllocateWatched(4 * Block); + const auto base = reinterpret_cast(memory); + auto* bytes = static_cast(memory); + std::memset(memory, 0x11, 4 * Block); + Unwatch(base + Block, Block); + const auto synced = CollectWritesUncached(base + 2 * Block, 2 * Block); + Require(synced != 0 && CollectWritesUncached(base, Block) != 0, "the blocks around the unwatched one are not collected"); + struct Reset { + ~Reset() { + SetImportWalkFailureForTest(0); + SetUnwatchHook(nullptr); + } + } reset; + collectDuringHookAddress = base + 2 * Block; + collectDuringHookBytes = 2 * Block; + collectDuringHookCalls = 0; + SetUnwatchHook(&CollectDuringHook); + SetImportWalkFailureForTest(2, base); + Require(ImportWatched(base, 4 * Block, [&] { + bytes[0x100] = 0x11; + bytes[2 * Block + 0x100] = 0x11; + bytes[3 * Block + 0x2000] = 0x22; + return true; + }), "the import whose first span failed its page walk failed"); + SetImportWalkFailureForTest(0); + SetUnwatchHook(nullptr); + Require(collectDuringHookCalls == 1, "the span whose page walk failed after the import did not run the unwatch hook once"); + Require(!Watched(base, Block), "the span whose page walk failed after the import stayed watched"); + Require(Watched(base + 2 * Block, 2 * Block), "the span whose page walk succeeded left the write watch"); + const std::array generations{synced, synced}; + std::array changed{}; + std::array cpu{}; + Require(ChangedBlocks(base + 2 * Block, 2 * Block, generations, changed, cpu), "the span after the failed one is not tracked"); + Require(changed[0] == BlockUnchanged && UnchangedSince(base + 2 * Block, Block, synced), "a collect run by the unwatch hook of an earlier span counted a page the import touched without changing it as a CPU write"); + Require(cpu[0] == 1, "a page the import touched lost its import window walk to the unwatch hook of an earlier span"); + Require(changed[1] == BlockWritten && WrittenSince(base + 3 * Block, Block, synced), "a store of new bytes within the import was missed after the unwatch hook of an earlier span"); +} + struct HookCall { std::uint64_t address; std::size_t bytes; @@ -2129,7 +2276,45 @@ void CheckAdoptLatestEpoch() { Require(keptOwn, "adopting the newest collect epoch moved a thread off its own"); } -int main() { +void CheckGenerationWrap() { + constexpr std::uint64_t Wrap = std::uint64_t{1} << 32u; + constexpr std::size_t bytes = 192 * Block; + void* memory = AllocateWatched(bytes); + void* scratch = AllocateWatched(Block); + const auto base = reinterpret_cast(memory); + const auto other = reinterpret_cast(scratch); + std::memset(memory, 0x11, bytes); + std::memset(scratch, 0x11, Block); + BumpCollectEpoch(); + Require(CollectWrites(base, bytes) != 0 && CollectWrites(other, Block) != 0, "the wrap fixture was not collected"); + AdvanceTrackerGenerationForTest(Wrap - 4); + BumpCollectEpoch(); + const auto before = CollectWritesUncached(base, bytes); + Require(before >= Wrap - 4 && before < Wrap, "the stamp before the crossing was not taken just below 2^32"); + Require(UnchangedSince(base, bytes, before), "a range nothing wrote after its collect reports a change"); + for (int store = 0; store < 8; ++store) MarkWritten(other, 4); + Require(TrackerGeneration() > Wrap, "the tracker generation did not cross 2^32"); + const auto middle = base + bytes / 2; + const auto stored = MarkWritten(middle, 4); + Require(stored > Wrap && stored > before, "a driver store after 2^32 returned a stamp not newer than one taken before"); + Require(!UnchangedSince(base, bytes, before), "a driver store after 2^32 was reported unchanged over whole coarse runs"); + Require(!UnchangedSince(middle, Block, before), "a driver store after 2^32 was reported unchanged in its block"); + Require(WrittenSince(base, bytes, before) && StoredOver(middle, 4, before), "a driver store after 2^32 was not seen as written since the earlier stamp"); + Require(NewestStamp(middle, Block) > before, "the newest stamp after 2^32 is not newer than the earlier stamp"); + Require(!StoredAllSince_nid_no_patch(middle, 4, stored) && StoredAllSince_nid_no_patch(middle, 4, before), "the driver piece of a store after 2^32 lost its generation"); + Require(UnchangedSince(base, bytes / 2, before) && UnchangedSince(base, bytes, stored), "a store after 2^32 changed other blocks or a later stamp"); + static_cast(memory)[bytes - 1] = 0x22; + const auto last = base + bytes - Block; + Require(CollectWritesUncached(last, Block) > Wrap, "a collect after 2^32 returned a wrapped generation"); + Require(!UnchangedSince(last, Block, before) && !UnchangedSinceCollected(last, Block, before), "a CPU store after 2^32 was reported unchanged"); + std::array generations{before, before, before}; + std::array changed{}; + std::array cpu{}; + Require(ChangedBlocks(base + bytes - 3 * Block, 3 * Block, generations, changed, cpu), "the changed-block query over the wrap fixture failed"); + Require(changed[0] == BlockUnchanged && changed[2] == BlockWritten && cpu[0] == 0 && cpu[2] == 1, "per-block changes after 2^32 were misreported"); +} + +int main(int argc, char** argv) { try { #ifdef _WIN32 if (std::wstring_view(GetCommandLineW()).ends_with(MetadataCommitMode)) { @@ -2140,6 +2325,11 @@ int main() { std::cout << "no write watching: skipped\n"; return 77; } + if (argc == 2 && std::string_view(argv[1]) == "--generation-wrap") { + CheckGenerationWrap(); + std::cout << "generation wrap test passed\n"; + return 0; + } #ifdef _WIN32 if (std::wstring_view(GetCommandLineW()).ends_with(HostHoleMode)) { CheckHostHole(); @@ -2173,6 +2363,7 @@ int main() { #endif CheckSharedBlock(); CheckStoredOverUsesPageEvidence(); + CheckRepeatedNeighbourStores(); CheckStoredAllSince(); CheckSharedBlockProofs(); CheckRepeatedPossibleStores(); @@ -2205,6 +2396,8 @@ int main() { CheckStoreAcrossImports(); CheckImportWindowFootprint(); CheckUnwatchedImport(); + CheckImportWalkUnwatchHooks(); + CheckImportWalkHookKeepsLaterWindows(); CheckUnwatchHooks(); CheckCommittedRanges(); CheckAdoptLatestEpoch(); diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_bda_atomics_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_bda_atomics_tests.cmake new file mode 100644 index 0000000000..b9eab4d574 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_bda_atomics_tests.cmake @@ -0,0 +1,8 @@ +add_test_executable(agc_bda_atomics_tests EXCLUDE_FROM_ALL tests/BdaAtomics.cpp) +target_include_directories(agc_bda_atomics_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) +set_target_properties(agc_bda_atomics_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_bda_atomics_tests) +if(BUILD_TESTING) + set_target_properties(agc_bda_atomics_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_bda_atomics COMMAND agc_bda_atomics_tests) +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_buffer_pool_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_buffer_pool_tests.cmake index bce7660165..f3e18e8c9c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_buffer_pool_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_buffer_pool_tests.cmake @@ -1,5 +1,5 @@ add_test_executable(agc_buffer_pool_tests EXCLUDE_FROM_ALL tests/BufferPool.cpp) -target_sources(agc_buffer_pool_tests PRIVATE Graphics/src/BufferPool.cpp tests/AllocationFailure.cpp) +target_sources(agc_buffer_pool_tests PRIVATE Graphics/src/BufferPool.cpp Graphics/src/Resources.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) set_target_properties(agc_buffer_pool_tests PROPERTIES CXX_EXTENSIONS OFF) diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_capture_trace_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_capture_trace_tests.cmake index 5edb1c481e..1ef979a1eb 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_capture_trace_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_capture_trace_tests.cmake @@ -8,10 +8,18 @@ if(BUILD_TESTING) file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/explicit") file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/failure") file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/best-effort") + file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/format") + file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/queue") + file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/queue-shutdown") + file(MAKE_DIRECTORY "${agcCaptureTraceDirectory}/queue-cumulative") 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") + set(agcCaptureTraceFormatPath "${agcCaptureTraceDirectory}/format/trace.log") + set(agcCaptureTraceQueuePath "${agcCaptureTraceDirectory}/queue/trace.log") + set(agcCaptureTraceQueueShutdownPath "${agcCaptureTraceDirectory}/queue-shutdown/trace.log") + set(agcCaptureTraceQueueCumulativePath "${agcCaptureTraceDirectory}/queue-cumulative/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=") @@ -25,13 +33,47 @@ if(BUILD_TESTING) 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_format_overflow COMMAND agc_capture_trace_tests format-overflow) + set_tests_properties(agc_capture_trace_format_overflow PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceFormatPath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/format" TIMEOUT 15) + add_test(NAME agc_capture_trace_queue_drop COMMAND agc_capture_trace_tests queue-drop) + set_tests_properties(agc_capture_trace_queue_drop PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceQueuePath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/queue" TIMEOUT 15) + add_test(NAME agc_capture_trace_queue_drop_shutdown COMMAND agc_capture_trace_tests queue-drop-shutdown) + set_tests_properties(agc_capture_trace_queue_drop_shutdown PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceQueueShutdownPath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/queue-shutdown" TIMEOUT 15) + add_test(NAME agc_capture_trace_queue_drop_cumulative COMMAND agc_capture_trace_tests queue-drop-cumulative) + set_tests_properties(agc_capture_trace_queue_drop_cumulative PROPERTIES ENVIRONMENT "APS5_CAPTURE_TRACE=1;APS5_CAPTURE_TRACE_PATH=${agcCaptureTraceQueueCumulativePath}" WORKING_DIRECTORY "${agcCaptureTraceDirectory}/queue-cumulative" TIMEOUT 15) 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_default_writes agc_capture_trace_open_failure agc_capture_trace_best_effort_overflow agc_capture_trace_format_overflow agc_capture_trace_queue_drop agc_capture_trace_queue_drop_shutdown agc_capture_trace_queue_drop_cumulative agc_capture_trace_disabled PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(agc_capture_trace_failure_tests tests/CaptureTraceFailure.cpp) + target_include_directories(agc_capture_trace_failure_tests PRIVATE ${LIBS_INCLUDE_DIR}) + if(MINGW) + target_compile_options(agc_capture_trace_failure_tests PRIVATE -fasynchronous-unwind-tables) + endif() + add_test_executable(agc_capture_trace_runtime_failure_tests tests/CaptureTraceFailure.cpp) + target_include_directories(agc_capture_trace_runtime_failure_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(agc_capture_trace_runtime_failure_tests PRIVATE libc) + configure_windows_unwind(agc_capture_trace_runtime_failure_tests) + foreach(mode IN ITEMS write flush) + add_test(NAME agc_capture_trace_${mode}_failure + COMMAND "${CMAKE_COMMAND}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/tests/CaptureTraceFailure.cmake" + "$" "${mode}" "${agcCaptureTraceDirectory}") + set_tests_properties(agc_capture_trace_${mode}_failure PROPERTIES TIMEOUT 20) + add_test(NAME agc_capture_trace_${mode}_failure_runtime + COMMAND "${CMAKE_COMMAND}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/tests/CaptureTraceFailure.cmake" + "$" "${mode}" "${agcCaptureTraceDirectory}") + set_tests_properties(agc_capture_trace_${mode}_failure_runtime PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(agc_capture_trace_${mode}_failure_runtime PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_alpha_to_coverage_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_alpha_to_coverage_tests.cmake index ac0bbbe423..d8914f68f9 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_alpha_to_coverage_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_alpha_to_coverage_tests.cmake @@ -15,6 +15,8 @@ if(BUILD_TESTING) add_test(NAME ${testName} COMMAND agc_driver_alpha_to_coverage_tests --dcc-compared-import ${samples}) list(APPEND multisampledDccClearTests ${testName}) endforeach() - set_tests_properties(agc_driver_dithered_alpha_to_coverage agc_driver_two_sample_dithered_alpha_to_coverage PROPERTIES + add_test(NAME agc_driver_alpha_to_coverage_sample_mask_export COMMAND agc_driver_alpha_to_coverage_tests --sample-mask8) + add_test(NAME agc_driver_two_sample_alpha_to_coverage_sample_mask_export COMMAND agc_driver_alpha_to_coverage_tests --sample-mask2) + set_tests_properties(agc_driver_dithered_alpha_to_coverage agc_driver_two_sample_dithered_alpha_to_coverage agc_driver_alpha_to_coverage_sample_mask_export agc_driver_two_sample_alpha_to_coverage_sample_mask_export PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_dispatch_overlap_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_dispatch_overlap_tests.cmake new file mode 100644 index 0000000000..669a810034 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_dispatch_overlap_tests.cmake @@ -0,0 +1,9 @@ +if(BUILD_TESTING) + add_test(NAME agc_driver_dispatch_overlap_off COMMAND agc_driver_dispatch_overlap_tests --off) + set_tests_properties(agc_driver_dispatch_overlap_off PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_DISPATCH_OVERLAP=1" SKIP_RETURN_CODE 77 TIMEOUT 60 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_dispatch_overlap_off PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_draw_offset_indices_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_draw_offset_indices_tests.cmake new file mode 100644 index 0000000000..da26ba76db --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_draw_offset_indices_tests.cmake @@ -0,0 +1,19 @@ +if(BUILD_TESTING) + foreach(runtimeFetchIndex IN ITEMS 65535 65536 65537) + foreach(runtimeFetchPath IN ITEMS indexed compact-restart) + set(runtimeFetchArguments runtime-fetch ${runtimeFetchIndex}) + if(runtimeFetchPath STREQUAL "compact-restart") + list(APPEND runtimeFetchArguments compact-restart) + endif() + set(runtimeFetchTest agc_driver_runtime_fetch_${runtimeFetchPath}_${runtimeFetchIndex}) + add_test(NAME ${runtimeFetchTest} COMMAND agc_driver_draw_offset_indices_tests ${runtimeFetchArguments}) + set_tests_properties(${runtimeFetchTest} PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 45 SKIP_RETURN_CODE 77 + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(${runtimeFetchTest} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_draw_recapture_on_change_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_draw_recapture_on_change_tests.cmake new file mode 100644 index 0000000000..2008fb08fb --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_draw_recapture_on_change_tests.cmake @@ -0,0 +1,10 @@ +if(BUILD_TESTING) + add_test(NAME agc_driver_draw_recapture_on_change_inline COMMAND agc_driver_draw_recapture_on_change_tests) + set_tests_properties(agc_driver_draw_recapture_on_change_inline PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=0" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_driver_dispatch_recapture_on_change_items COMMAND agc_driver_dispatch_recapture_on_change_tests) + set_tests_properties(agc_driver_dispatch_recapture_on_change_items PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_PIPELINED_DRAWS=1;APS5_PIPELINE_DISPATCH=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_draw_recapture_on_change_inline agc_driver_dispatch_recapture_on_change_items PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_state_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_state_tests.cmake index 11e868084c..fe8f427bcc 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_state_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_frame_replay_state_tests.cmake @@ -6,7 +6,7 @@ if(BUILD_TESTING) configure_windows_unwind(agc_driver_frame_replay_state_tests) set(agcFrameReplayModes order tokens blocking) if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") - list(APPEND agcFrameReplayModes zero shader shader_host) + list(APPEND agcFrameReplayModes zero shader shader_host shader_prologue_code shader_prologue_header shader_prologue_readonly) endif() if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") list(APPEND agcFrameReplayModes unwatched) diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_gds_dma_fill_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_gds_dma_fill_tests.cmake new file mode 100644 index 0000000000..7a6d61800b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_gds_dma_fill_tests.cmake @@ -0,0 +1,8 @@ +if(BUILD_TESTING) + add_test(NAME agc_driver_gds_dma_fill_private COMMAND agc_driver_gds_dma_fill_tests) + set_tests_properties(agc_driver_gds_dma_fill_private PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_SHARED_GDS=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_gds_dma_fill_private PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_graphics_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_graphics_tests.cmake index 57daa509e3..b45b89f159 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_graphics_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_graphics_tests.cmake @@ -128,6 +128,12 @@ if(BUILD_TESTING) 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_reference_size COMMAND agc_driver_graphics_tests --heap-mirror-reference-size) + set_tests_properties(agc_heap_mirror_reference_size PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_heap_mirror_reference_size PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() 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") diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_image_sample_lod_clamp_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_image_sample_lod_clamp_tests.cmake new file mode 100644 index 0000000000..4cf0a20695 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_image_sample_lod_clamp_tests.cmake @@ -0,0 +1,8 @@ +if(BUILD_TESTING) + add_test(NAME agc_driver_image_sample_lod_clamp_shader_lane COMMAND agc_driver_image_sample_lod_clamp_tests --shader-lane) + set_tests_properties(agc_driver_image_sample_lod_clamp_shader_lane PROPERTIES ENVIRONMENT "APS5_NO_IMAGE_VIEW_MIN_LOD=1;APS5_NO_GPL=1;ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 60) + if(WIN32) + set_tests_properties(agc_driver_image_sample_lod_clamp_shader_lane PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_layered_volume_draw_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_layered_volume_draw_tests.cmake index e4602a51ce..981a720ef3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_layered_volume_draw_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_layered_volume_draw_tests.cmake @@ -1,12 +1,13 @@ if(BUILD_TESTING) add_test(NAME agc_driver_volume_mip_targets COMMAND agc_driver_layered_volume_draw_tests --mips) + add_test(NAME agc_driver_layered_volume_mesh COMMAND agc_driver_layered_volume_draw_tests --mesh-execution) add_test(NAME agc_driver_layered_volume_vertex COMMAND agc_driver_layered_volume_draw_tests --vertex-only) add_test(NAME agc_driver_layered_volume_vertex_no_gpl COMMAND agc_driver_layered_volume_draw_tests --vertex-only) - set_tests_properties(agc_driver_volume_mip_targets agc_driver_layered_volume_vertex agc_driver_layered_volume_vertex_no_gpl PROPERTIES + set_tests_properties(agc_driver_volume_mip_targets agc_driver_layered_volume_mesh agc_driver_layered_volume_vertex agc_driver_layered_volume_vertex_no_gpl PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") set_property(TEST agc_driver_layered_volume_vertex_no_gpl APPEND PROPERTY ENVIRONMENT "APS5_NO_GPL=1") if(WIN32) - set_tests_properties(agc_driver_volume_mip_targets agc_driver_layered_volume_vertex agc_driver_layered_volume_vertex_no_gpl PROPERTIES + set_tests_properties(agc_driver_volume_mip_targets agc_driver_layered_volume_mesh agc_driver_layered_volume_vertex agc_driver_layered_volume_vertex_no_gpl PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test(NAME agc_driver_volume_direct_readback COMMAND agc_driver_layered_volume_draw_tests) diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_linear_target_draw_bindings_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_linear_target_draw_bindings_tests.cmake new file mode 100644 index 0000000000..a9fd063a5b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_linear_target_draw_bindings_tests.cmake @@ -0,0 +1,8 @@ +if(BUILD_TESTING) + add_test(NAME agc_driver_linear_target_draw_bindings_counted COMMAND agc_driver_linear_target_draw_bindings_tests) + set_tests_properties(agc_driver_linear_target_draw_bindings_counted PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_READ_ONLY_LEASE_SKIP=1" SKIP_RETURN_CODE 77 TIMEOUT 60 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_linear_target_draw_bindings_counted PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake index 0992f09dc7..2e8dd64629 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_native_multisample_depth_tests.cmake @@ -6,10 +6,22 @@ if(BUILD_TESTING) set_tests_properties(agc_driver_native_depth_retirement PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_driver_native_depth_clears COMMAND agc_driver_native_multisample_depth_tests --clears) add_test(NAME agc_driver_native_depth_clear_refusals COMMAND agc_driver_native_multisample_depth_tests --clear-refusals) + add_test(NAME agc_driver_native_depth_stencil COMMAND agc_driver_native_multisample_depth_tests --stencil) + add_test(NAME agc_driver_native_depth_separate_stencil COMMAND agc_driver_native_multisample_depth_tests --separate-stencil) + add_test(NAME agc_driver_native_depth_aliased_stencil COMMAND agc_driver_native_multisample_depth_tests --aliased-stencil) + add_test(NAME agc_driver_native_depth_expand_gate COMMAND agc_driver_native_multisample_depth_tests --expand-gate) + add_test(NAME agc_driver_native_depth_stencil_view COMMAND agc_driver_native_multisample_depth_tests --stencil-view) add_test(NAME agc_driver_native_depth_buffer_clear COMMAND agc_driver_native_multisample_depth_tests --buffer-clear) - set_tests_properties(agc_driver_native_depth_clears agc_driver_native_depth_clear_refusals agc_driver_native_depth_buffer_clear PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_driver_native_depth_uniform_planes COMMAND agc_driver_native_multisample_depth_tests --uniform-planes) + set_tests_properties(agc_driver_native_depth_clears agc_driver_native_depth_clear_refusals agc_driver_native_depth_stencil agc_driver_native_depth_separate_stencil agc_driver_native_depth_aliased_stencil agc_driver_native_depth_expand_gate agc_driver_native_depth_stencil_view agc_driver_native_depth_buffer_clear agc_driver_native_depth_uniform_planes 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_depth_retirement PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") - set_tests_properties(agc_driver_native_depth_clears agc_driver_native_depth_clear_refusals agc_driver_native_depth_buffer_clear PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_driver_native_depth_clears agc_driver_native_depth_clear_refusals agc_driver_native_depth_stencil agc_driver_native_depth_separate_stencil agc_driver_native_depth_aliased_stencil agc_driver_native_depth_expand_gate agc_driver_native_depth_stencil_view agc_driver_native_depth_buffer_clear agc_driver_native_depth_uniform_planes PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_driver_native_depth_driver_path COMMAND agc_driver_native_multisample_depth_tests --driver-path) + add_test(NAME agc_driver_native_depth_driver_queued_fill COMMAND agc_driver_native_multisample_depth_tests --driver-queued-fill) + set_tests_properties(agc_driver_native_depth_driver_path agc_driver_native_depth_driver_queued_fill 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_depth_driver_path agc_driver_native_depth_driver_queued_fill PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_oracle_runner_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_oracle_runner_tests.cmake new file mode 100644 index 0000000000..e583cf5791 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_oracle_runner_tests.cmake @@ -0,0 +1 @@ +target_link_libraries(agc_driver_oracle_runner_tests PRIVATE agc_oracle_support) diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_read_only_lease_writer_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_read_only_lease_writer_tests.cmake new file mode 100644 index 0000000000..53e7001579 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_read_only_lease_writer_tests.cmake @@ -0,0 +1,8 @@ +if(BUILD_TESTING) + add_test(NAME agc_driver_read_only_lease_writer_counted COMMAND agc_driver_read_only_lease_writer_tests) + set_tests_properties(agc_driver_read_only_lease_writer_counted PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_READ_ONLY_LEASE_SKIP=1" SKIP_RETURN_CODE 77 TIMEOUT 60 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_read_only_lease_writer_counted PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake index a238e74936..77d639aab7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake @@ -24,6 +24,13 @@ target_link_libraries(agc_driver_recorder_tests PRIVATE libc ) +if(BUILD_TESTING) + add_test(NAME agc_completion_label_wait COMMAND agc_driver_recorder_tests --completion-wait-only) + set_tests_properties(agc_completion_label_wait PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_completion_label_wait PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() +endif() set_target_properties(agc_driver_recorder_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_recorder_tests) @@ -72,6 +79,12 @@ if(BUILD_TESTING) set_tests_properties(agc_pipeline_cache agc_pipeline_cache_file PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_detile_small_elements COMMAND agc_driver_recorder_tests --small-element-detile-only) + set_tests_properties(agc_detile_small_elements PROPERTIES SKIP_RETURN_CODE 77) + if(WIN32) + set_tests_properties(agc_detile_small_elements PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + 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) @@ -109,6 +122,18 @@ if(BUILD_TESTING) set_tests_properties(agc_unchanged_cpu_stamps PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_storage_rewritten_blocks COMMAND agc_driver_recorder_tests --rewritten-blocks-only) + set_tests_properties(agc_storage_rewritten_blocks PROPERTIES SKIP_RETURN_CODE 77) + if(WIN32) + set_tests_properties(agc_storage_rewritten_blocks PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_storage_rewritten_stale_units COMMAND agc_driver_recorder_tests --rewritten-stale-units-only) + set_tests_properties(agc_storage_rewritten_stale_units PROPERTIES SKIP_RETURN_CODE 77) + if(WIN32) + set_tests_properties(agc_storage_rewritten_stale_units 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) @@ -150,6 +175,12 @@ if(BUILD_TESTING) set_tests_properties(agc_partial_watch_restoration PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_repeated_neighbour_stores COMMAND agc_driver_recorder_tests --repeated-neighbour-stores-only) + set_tests_properties(agc_repeated_neighbour_stores PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_repeated_neighbour_stores PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_storage_kept_unit_settle COMMAND agc_driver_recorder_tests --kept-unit-settle-only) add_test(NAME agc_storage_idle_writeback_race COMMAND agc_driver_recorder_tests --idle-writeback-race-only) set_tests_properties(agc_storage_idle_writeback_race PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 60) @@ -158,6 +189,54 @@ if(BUILD_TESTING) set_tests_properties(agc_storage_idle_writeback_race agc_storage_kept_unit_settle PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_storage_pending_after_unwatch COMMAND agc_driver_recorder_tests --pending-after-unwatch-only) + set_tests_properties(agc_storage_pending_after_unwatch PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_storage_pending_after_unwatch PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_unwatch_hook_lock_order COMMAND agc_driver_recorder_tests --unwatch-lock-order-only) + set_tests_properties(agc_unwatch_hook_lock_order PROPERTIES ENVIRONMENT "APS5_ASSERT_GPU_LOCK=1" SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_unwatch_hook_lock_order PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_map_gate_publication COMMAND agc_driver_recorder_tests --map-gate-publication-only) + set_tests_properties(agc_map_gate_publication PROPERTIES ENVIRONMENT "APS5_ASSERT_GPU_LOCK=1" SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_map_gate_publication PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_map_gate_pending COMMAND agc_driver_recorder_tests --map-gate-pending-only) + set_tests_properties(agc_map_gate_pending PROPERTIES ENVIRONMENT "APS5_ASSERT_GPU_LOCK=1" SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_map_gate_pending PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_map_gate_fast_path COMMAND agc_driver_recorder_tests --map-gate-fast-path-only) + set_tests_properties(agc_map_gate_fast_path PROPERTIES ENVIRONMENT "APS5_ASSERT_GPU_LOCK=1" SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_map_gate_fast_path PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_map_gate_disjoint COMMAND agc_driver_recorder_tests --map-gate-disjoint-only) + set_tests_properties(agc_map_gate_disjoint PROPERTIES ENVIRONMENT "APS5_ASSERT_GPU_LOCK=1" SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_map_gate_disjoint PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_map_gate_retry COMMAND agc_driver_recorder_tests --map-gate-retry-only) + set_tests_properties(agc_map_gate_retry PROPERTIES ENVIRONMENT "APS5_ASSERT_GPU_LOCK=1" SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_map_gate_retry PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test(NAME agc_map_gate_stress COMMAND agc_driver_recorder_tests --map-gate-stress-only) + set_tests_properties(agc_map_gate_stress PROPERTIES ENVIRONMENT "APS5_ASSERT_GPU_LOCK=1" SKIP_RETURN_CODE 77 TIMEOUT 60) + if(WIN32) + set_tests_properties(agc_map_gate_stress PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_storage_baseline_settled COMMAND agc_driver_recorder_tests --settled-baseline-only) add_test(NAME agc_storage_clear_hand_over COMMAND agc_driver_recorder_tests --clear-hand-over-only) set_tests_properties(agc_storage_clear_hand_over PROPERTIES SKIP_RETURN_CODE 77) @@ -227,12 +306,24 @@ if(BUILD_TESTING) set_tests_properties(agc_copied_direct_ordering PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_copied_write_back COMMAND agc_driver_recorder_tests --copied-write-back-only) + set_tests_properties(agc_copied_write_back PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_copied_write_back PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_storage_writeback_blocks COMMAND agc_driver_recorder_tests --writeback-blocks-only) set_tests_properties(agc_storage_writeback_blocks PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000") if(WIN32) set_tests_properties(agc_storage_writeback_blocks PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME agc_storage_flush_wait_mip_chain COMMAND agc_driver_recorder_tests --flush-wait-mip-chain-only) + set_tests_properties(agc_storage_flush_wait_mip_chain PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000") + if(WIN32) + set_tests_properties(agc_storage_flush_wait_mip_chain PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test(NAME agc_recorder_read_list COMMAND agc_driver_recorder_tests --read-list-only) set_tests_properties(agc_recorder_read_list PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) add_test(NAME agc_recorder_deferred_failure_report COMMAND agc_driver_recorder_tests --deferred-failure-report-only) @@ -251,6 +342,8 @@ if(BUILD_TESTING) set_tests_properties(agc_host_import_unmap PROPERTIES SKIP_RETURN_CODE 77) add_test(NAME agc_parked_import_unmap COMMAND agc_driver_recorder_tests --parked-import-unmap-only) set_tests_properties(agc_parked_import_unmap PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000") + add_test(NAME agc_window_import_unmap COMMAND agc_driver_recorder_tests --window-import-unmap-only) + set_tests_properties(agc_window_import_unmap PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 ENVIRONMENT "APS5_PIN_WAIT_MS=3000") add_test(NAME agc_host_import_retirement COMMAND agc_driver_recorder_tests --host-import-retirement-only) set_tests_properties(agc_host_import_retirement PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) add_test(NAME agc_sparse_run_import COMMAND agc_driver_recorder_tests --sparse-run-import-only) @@ -263,6 +356,8 @@ if(BUILD_TESTING) endif() add_test(NAME agc_sparse_run_import_copied COMMAND agc_driver_recorder_tests --sparse-run-copied-only) set_tests_properties(agc_sparse_run_import_copied PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) + add_test(NAME agc_changed_copy COMMAND agc_driver_recorder_tests --changed-copy-only) + set_tests_properties(agc_changed_copy 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) @@ -320,7 +415,7 @@ if(BUILD_TESTING) add_test(NAME agc_metadata_pass COMMAND agc_driver_recorder_tests --metadata-pass-only) set_tests_properties(agc_metadata_pass PROPERTIES SKIP_RETURN_CODE 77) if(WIN32) - set_tests_properties(agc_recorder_read_list agc_host_import_unmap agc_parked_import_unmap agc_host_import_retirement agc_sparse_run_import agc_sparse_run_import_copied agc_unit_shadow agc_driver_recorder agc_idle_publish_wait agc_recorder_command_buffer_release agc_descriptor_pool_recycling agc_draw_snapshot_cap agc_draw_snapshot_epoch agc_multisample_guest_access agc_repeat_storage_guard agc_draw_inputs_in_place agc_draw_inputs_copied agc_resident_present agc_slice_dirty agc_queued_write_groups agc_metadata_pass agc_tiled_resolve PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_recorder_read_list agc_host_import_unmap agc_parked_import_unmap agc_window_import_unmap agc_host_import_retirement agc_sparse_run_import agc_sparse_run_import_copied agc_unit_shadow agc_driver_recorder agc_idle_publish_wait agc_recorder_command_buffer_release agc_descriptor_pool_recycling agc_draw_snapshot_cap agc_draw_snapshot_epoch agc_multisample_guest_access agc_repeat_storage_guard agc_draw_inputs_in_place agc_draw_inputs_copied agc_resident_present agc_slice_dirty agc_queued_write_groups agc_metadata_pass agc_tiled_resolve agc_changed_copy 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 @@ -362,10 +457,12 @@ if(BUILD_TESTING) set_tests_properties(agc_recorder_failed_check PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30 PASS_REGULAR_EXPRESSION "a failed check stopped the test with a texture upload in its open batch" FAIL_REGULAR_EXPRESSION "recorder teardown") + add_test(NAME agc_recorder_upload_scratch COMMAND agc_driver_recorder_tests --upload-scratch-only) + set_tests_properties(agc_recorder_upload_scratch 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 agc_small_tiled_shadow_label PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") - set_tests_properties(agc_recorder_completion_labels agc_recorder_cmask agc_recorder_cube agc_recorder_failed_check PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(agc_recorder_completion_labels agc_recorder_cmask agc_recorder_cube agc_recorder_failed_check agc_recorder_upload_scratch PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() set_property(TEST agc_driver_host_import_lifetime APPEND PROPERTY ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sampled_cache_budget_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sampled_cache_budget_tests.cmake index 454037a68c..ed0f578b29 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sampled_cache_budget_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_sampled_cache_budget_tests.cmake @@ -19,6 +19,14 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + add_test(NAME agc_driver_sampled_cache_budget_report_age COMMAND agc_driver_sampled_cache_budget_tests budget-report-age) + set_tests_properties(agc_driver_sampled_cache_budget_report_age PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_TEXTURE_CACHE_MIB=" SKIP_RETURN_CODE 77 TIMEOUT 30 + PASS_REGULAR_EXPRESSION "active storage image cache budget 4096 MiB.*storage_limit_age_ms=[1-9][0-9]*" + FAIL_REGULAR_EXPRESSION "FAIL:|VUID-") + if(WIN32) + set_tests_properties(agc_driver_sampled_cache_budget_report_age PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() 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) diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_store_idle_results_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_store_idle_results_tests.cmake index 360ac39859..e5d727b340 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_store_idle_results_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_store_idle_results_tests.cmake @@ -2,8 +2,13 @@ if(BUILD_TESTING) 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}") + add_test(NAME agc_driver_store_idle_results_disabled COMMAND agc_driver_store_idle_results_tests --disabled-idle-store) + set_tests_properties(agc_driver_store_idle_results_disabled PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_NO_IDLE_STORE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") if(WIN32) set_tests_properties(agc_driver_store_idle_results_wake PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(agc_driver_store_idle_results_disabled PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_two_device_pending_store_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_two_device_pending_store_tests.cmake new file mode 100644 index 0000000000..82b2c3de8f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_two_device_pending_store_tests.cmake @@ -0,0 +1,9 @@ +if(BUILD_TESTING) + add_test(NAME agc_driver_two_device_pending_store_label COMMAND agc_driver_two_device_pending_store_tests --label) + set_tests_properties(agc_driver_two_device_pending_store_label PROPERTIES + ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_STORE_AT_LABEL=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + if(WIN32) + set_tests_properties(agc_driver_two_device_pending_store_label PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_unsupported_diagnostics_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_unsupported_diagnostics_tests.cmake index a9c3c84ac0..b2b8a800d8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_unsupported_diagnostics_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_unsupported_diagnostics_tests.cmake @@ -3,7 +3,7 @@ if(BUILD_TESTING) 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) + 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:$") diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_user_data_link_reuse_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_user_data_link_reuse_tests.cmake new file mode 100644 index 0000000000..21f6b4e227 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_user_data_link_reuse_tests.cmake @@ -0,0 +1 @@ +target_include_directories(agc_driver_user_data_link_reuse_tests PRIVATE $) diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_host_import_sparse_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_host_import_sparse_tests.cmake index df60c3477f..730806e495 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_host_import_sparse_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_host_import_sparse_tests.cmake @@ -1,4 +1,4 @@ -add_test_executable(agc_host_import_sparse_tests EXCLUDE_FROM_ALL tests/HostImportSparseDmaBuf.cpp) +add_test_executable(agc_host_import_sparse_tests EXCLUDE_FROM_ALL tests/HostImportSparseDmaBuf.cpp tests/AllocationFailure.cpp) target_include_directories(agc_host_import_sparse_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_host_import_sparse_tests PRIVATE libSceAgcDriver libc) set_target_properties(agc_host_import_sparse_tests PROPERTIES CXX_EXTENSIONS OFF) diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_oracle_code_object_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_oracle_code_object_tests.cmake new file mode 100644 index 0000000000..f17785d12a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_oracle_code_object_tests.cmake @@ -0,0 +1,18 @@ +if(BUILD_TESTING) + add_test_executable(agc_oracle_code_object_tests tests/OracleCodeObject.cpp tools/OracleCodeObject.cpp) + target_include_directories(agc_oracle_code_object_tests PRIVATE + $ + ${CMAKE_SOURCE_DIR}/core/shader/recompiler + ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR} + ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR} + ) + target_link_libraries(agc_oracle_code_object_tests PRIVATE libc) + set_target_properties(agc_oracle_code_object_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(agc_oracle_code_object_tests) + add_test(NAME agc_oracle_code_object COMMAND agc_oracle_code_object_tests) + set_tests_properties(agc_oracle_code_object PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_oracle_code_object PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_page_state_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_page_state_tests.cmake index f4beee493e..e73b40c8de 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_page_state_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_page_state_tests.cmake @@ -5,6 +5,7 @@ if(BUILD_TESTING AND WIN32) 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 + add_test(NAME agc_page_state_runs COMMAND agc_page_state_tests --runs) + set_tests_properties(agc_page_states agc_page_state_metadata agc_page_state_runs PROPERTIES TIMEOUT 30 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_pipeline_cache_allocation_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_pipeline_cache_allocation_tests.cmake index ef7a969e69..c8d961b5e8 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_pipeline_cache_allocation_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_pipeline_cache_allocation_tests.cmake @@ -1,7 +1,7 @@ add_test_executable(agc_pipeline_cache_allocation_tests EXCLUDE_FROM_ALL tests/PipelineCacheFailures.cpp Graphics/src/PipelineCache.cpp tests/AllocationFailure.cpp) target_include_directories(agc_pipeline_cache_allocation_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) -target_link_libraries(agc_pipeline_cache_allocation_tests PRIVATE shader_recompiler_agc_driver libc) +target_link_libraries(agc_pipeline_cache_allocation_tests PRIVATE shader_recompiler_agc_driver libSceAgcDriver libc) set_target_properties(agc_pipeline_cache_allocation_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_pipeline_cache_allocation_tests) diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_raster_order_group_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_raster_order_group_tests.cmake new file mode 100644 index 0000000000..1c3aca8e13 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_raster_order_group_tests.cmake @@ -0,0 +1,8 @@ +add_test_executable(agc_raster_order_group_tests EXCLUDE_FROM_ALL tests/RasterOrderGroup.cpp) +target_include_directories(agc_raster_order_group_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) +set_target_properties(agc_raster_order_group_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_raster_order_group_tests) +if(BUILD_TESTING) + set_target_properties(agc_raster_order_group_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_raster_order_group COMMAND agc_raster_order_group_tests) +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_reference_storage_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_reference_storage_tests.cmake new file mode 100644 index 0000000000..3cf5dd5bd3 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_reference_storage_tests.cmake @@ -0,0 +1,14 @@ +add_test_executable(agc_reference_storage_tests EXCLUDE_FROM_ALL tests/ReferenceStorage.cpp) +target_sources(agc_reference_storage_tests PRIVATE Graphics/src/ReferenceStorage.cpp tests/AllocationFailure.cpp) +target_include_directories(agc_reference_storage_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(agc_reference_storage_tests PRIVATE libc) +set_target_properties(agc_reference_storage_tests PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(agc_reference_storage_tests) +if(BUILD_TESTING) + set_target_properties(agc_reference_storage_tests PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME agc_reference_storage COMMAND agc_reference_storage_tests) + if(WIN32) + set_tests_properties(agc_reference_storage PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake index 902a0d9bbc..e86ed31bbe 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_shader_memory_tests.cmake @@ -3,8 +3,11 @@ add_test_executable(agc_shader_memory_tests EXCLUDE_FROM_ALL Execution/src/ShaderMemory.cpp Execution/src/GuestMemory.cpp Execution/src/PerformanceTraceThread.cpp + Graphics/src/GuestTextureResource.cpp + Graphics/src/TextureTiling.cpp + Graphics/src/TextureFormat.cpp ) -target_include_directories(agc_shader_memory_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_include_directories(agc_shader_memory_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) target_link_libraries(agc_shader_memory_tests PRIVATE shader_recompiler_agc_driver libc) set_target_properties(agc_shader_memory_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_shader_memory_tests) @@ -18,9 +21,15 @@ if(BUILD_TESTING) add_test(NAME agc_shader_selected_records_pair_limit COMMAND agc_shader_memory_tests --selected-records-pair-limit) add_test(NAME agc_shader_selected_records_direct_image_limit COMMAND agc_shader_memory_tests --selected-records-direct-image-limit) add_test(NAME agc_shader_selected_records_direct_pair_limit COMMAND agc_shader_memory_tests --selected-records-direct-pair-limit) - set_tests_properties(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 PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") + add_test(NAME agc_shader_sampler_limits COMMAND agc_shader_memory_tests --sampler-limits) + set_tests_properties(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_sampler_limits PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_test(NAME agc_shader_srt_read_patching COMMAND agc_shader_memory_tests --srt-read-patching) 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_descriptor_phis COMMAND agc_shader_memory_tests --descriptor-phis) + set_tests_properties(agc_shader_descriptor_phis PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_VERIFY_SRT_TAPE=1;APS5_VERIFY_SRT_COMPACT=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30) + if(WIN32) + set_tests_properties(agc_shader_descriptor_phis PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() add_test(NAME agc_shader_table_snapshot_reuse COMMAND agc_shader_memory_tests --table-snapshot-reuse) add_test(NAME agc_shader_region_merge COMMAND agc_shader_memory_tests --region-merge) set_tests_properties(agc_shader_region_merge PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") @@ -31,7 +40,7 @@ if(BUILD_TESTING) 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) + set_tests_properties(agc_driver_image_table_vouched_lookups PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1;APS5_WRITE_WATCH_IMPORTS=watch;APS5_NO_SPANNED_WAVE=1" 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) @@ -61,7 +70,7 @@ if(BUILD_TESTING) add_test(NAME agc_shader_entry_lane_proof_budget COMMAND agc_shader_memory_tests --entry-lane-proof-budget) set_tests_properties(agc_shader_entry_lane_proof_budget PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") 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_result_memo_budget agc_shader_result_memo_budget_threads agc_shader_result_memo_budget_masked agc_shader_entry_lane_proof_budget PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + 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_result_memo_budget agc_shader_result_memo_budget_threads agc_shader_result_memo_budget_masked agc_shader_entry_lane_proof_budget agc_shader_sampler_limits PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() agc_add_default_test(agc_shader_memory_tests) endif() @@ -78,3 +87,11 @@ if(BUILD_TESTING) endif() endforeach() endif() + +if(BUILD_TESTING) + add_test(NAME agc_shader_layer_specialization_release COMMAND agc_shader_memory_tests --layer-specialization-release) + set_tests_properties(agc_shader_layer_specialization_release PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 60) + if(WIN32) + set_tests_properties(agc_shader_layer_specialization_release PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_write_tracking_tests.cmake b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_write_tracking_tests.cmake index c2a9e93d4e..6a37c12100 100644 --- a/core/libs/prx/libSceAgcDriver/tests/cmake/agc_write_tracking_tests.cmake +++ b/core/libs/prx/libSceAgcDriver/tests/cmake/agc_write_tracking_tests.cmake @@ -12,6 +12,8 @@ if(BUILD_TESTING) set_target_properties(agc_write_tracking_tests PROPERTIES EXCLUDE_FROM_ALL OFF) add_test(NAME agc_write_tracking COMMAND agc_write_tracking_tests) set_tests_properties(agc_write_tracking PROPERTIES SKIP_RETURN_CODE 77) + add_test(NAME agc_write_tracking_generation_wrap COMMAND agc_write_tracking_tests --generation-wrap) + set_tests_properties(agc_write_tracking_generation_wrap PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 20) if(WIN32) add_test(NAME agc_write_tracking_metadata_commit COMMAND agc_write_tracking_tests --metadata-commit) set_tests_properties(agc_write_tracking_metadata_commit PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30) @@ -27,7 +29,7 @@ if(BUILD_TESTING) 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_write_tracking_cpu_write_pin 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 + set_tests_properties(agc_write_tracking agc_write_tracking_generation_wrap agc_write_tracking_metadata_commit agc_write_tracking_cpu_write_pin 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() diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp index ab76da6171..cd982b67d3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/AlphaToCoverage.cpp @@ -522,6 +522,62 @@ constexpr std::array TwoSampleDitherCases{{ {0x00007e00u, 4u, {0x00u, 0x00u, 0x00u, 0x00u}}, }}; +void SampleMaskExportCoverage(AgcDriver::VulkanDevice& device, std::uint32_t samples, std::span cases) { + const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 8); + const auto all = (1u << samples) - 1u; + std::vector undithered; + for (const std::uint32_t bits : {0x3e800000u, 0x3f000000u, 0x3f400000u}) { + const auto mask = std::bit_cast(bits) < 0.5f ? 0u : all; + undithered.push_back({bits, Float32Export, {mask, mask, mask, mask}}); + } + std::vector> blocks; + for (const bool dithered : {true, false}) { + for (const auto& test : dithered ? cases : std::span(undithered)) { + for (const std::uint32_t exported : {0x00000001u, 0xaaaaaaaau, 0xffffff0fu}) { + blocks.push_back(std::make_unique(layout.Bytes() * samples)); + auto& source = *blocks.back(); + std::memset(source.data, 0, source.bytes); + const auto multisampled = DecodeTarget(source, samples); + for (std::uint32_t sample = 0; sample < samples; ++sample) { + const Color reference{std::ldexp(1.0f, static_cast(sample) - static_cast(samples - 1u)), 0, 0, 1}; + Draw(device, multisampled, false, Float32Export, Program(reference, Float32Export, 1u << sample), false, true); + } + const auto alpha = test.format == Float32Export ? std::bit_cast(test.bits) : 0.0f; + const Color color{0, 0, 0, alpha}; + const auto half = test.format == Float16Export ? std::optional(static_cast(test.bits)) : std::nullopt; + const auto code = Program(color, test.format, exported, half); + if (dithered) { + Draw(device, multisampled, true, test.format, code, true, true, static_cast(samples)); + } else { + Draw(device, multisampled, true, test.format, code, false, true); + } + blocks.push_back(std::make_unique(layout.Bytes())); + auto& resolved = *blocks.back(); + std::memset(resolved.data, 0, resolved.bytes); + AgcDriver::Graphics::ColorMetadataPass pass{}; + pass.mode = AgcDriver::Graphics::ColorMetadataPass::Mode::Resolve; + pass.source = multisampled; + pass.targets = {DecodeTarget(resolved)}; + device.ColorMetadataPass(pass); + const auto stored = ReadBack(device, resolved); + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto mask = test.masks[2u * (x & 1u) + (y & 1u)] & exported & all; + const auto expected = x < ScissorWidth ? static_cast(all - mask) / static_cast((1u << (samples - 1u)) * samples) : 0.0f; + const auto actual = FromHalf(stored[layout.Offset(x, y) / 2u]); + if (actual == expected) continue; + char detail[224]; + std::snprintf(detail, sizeof(detail), "%s coverage export %u alpha 0x%08x with exported mask 0x%08x pixel (%u,%u): resolved %.9g, expected %.9g for mask 0x%02x", dithered ? "dithered" : "undithered", test.format, test.bits, exported, x, y, actual, expected, mask); + throw std::runtime_error(detail); + } + } + programCount = 0; + } + } + } + AgcDriver::Graphics::ClearMultisampleTargets(device.Device()); +} + void DccSrgbClearTests(AgcDriver::VulkanDevice& device, std::uint32_t samples) { const ColorTargetLayout layout(Width, Height, ColorTileMode::RenderTarget, 4); const ColorTargetLayout allocation(Width, Height, ColorTileMode::RenderTarget, 8); @@ -600,7 +656,7 @@ void DccFastClearTests(AgcDriver::VulkanDevice& device, std::uint32_t selectedSa } clearTargets{device}; for (const auto samples : {2u, 4u, 8u}) { if (selectedSamples != 0 && samples != selectedSamples) continue; - for (const auto code : {0x00u, 0x40u, 0x80u, 0xc0u}) { + for (const auto code : {0x00u, 0x20u, 0x40u, 0x80u, 0xc0u}) { for (const auto alphaOnMsb : {false, true}) { for (const auto cmaskMode : {0u, 0xccu, 0xffu}) { blocks.push_back(std::make_unique(layout.Bytes() * samples)); @@ -613,6 +669,7 @@ void DccFastClearTests(AgcDriver::VulkanDevice& device, std::uint32_t selectedSa target.dccAddress = metadata.Address(); target.dccPipeAligned = true; target.dccAlphaOnMsb = alphaOnMsb; + if (code == 0x20u) target.clearWords = {0x38003000u, 0x3c003a00u}; if (cmaskMode != 0) { blocks.push_back(std::make_unique(BlockBytes)); auto& cmask = *blocks.back(); @@ -626,6 +683,7 @@ void DccFastClearTests(AgcDriver::VulkanDevice& device, std::uint32_t selectedSa const bool alphaOne = code == 0x40u || code == 0xc0u; Color initial{}; for (std::uint32_t channel = 0; channel < 4; ++channel) initial[channel] = channel == (alphaOnMsb ? 3u : 0u) ? static_cast(alphaOne) : static_cast(colorOne); + if (code == 0x20u) initial = {0.125f, 0.5f, 0.75f, 1.0f}; const Color written{0.25f, 0.25f, 0.25f, 0.25f}; if (cmaskMode != 0) device.ColorMetadataPass({AgcDriver::Graphics::ColorMetadataPass::Mode::FmaskDecompress, {target}}); Draw(device, target, false, Float32Export, Program(written, Float32Export, 1u), false, true); @@ -886,102 +944,108 @@ void DccRefillTests(AgcDriver::VulkanDevice& device, std::uint32_t selectedSampl if (selectedSamples != 0 && samples != selectedSamples) continue; for (const auto mode : {0u, 1u, 2u, 3u, 4u, 5u, 6u}) { for (const auto cmasked : {false, true}) { - if (mode == 4 && !cmasked) continue; + for (const auto code : {0xc0u, 0x20u}) { + if (mode == 4 && !cmasked) continue; + if (code == 0x20u && (mode == 2 || mode == 3 || mode == 4)) continue; + const auto refill = code * 0x01010101u; #ifdef _WIN32 - if (mode == 6) continue; + if (mode == 6) continue; #else - AgcDriver::Graphics::SetImportWatch(context, mode == 5 ? AgcDriver::Graphics::ImportWatch::Watch : mode == 6 ? AgcDriver::Graphics::ImportWatch::Unwatch : decided); + AgcDriver::Graphics::SetImportWatch(context, mode == 5 ? AgcDriver::Graphics::ImportWatch::Watch : mode == 6 ? AgcDriver::Graphics::ImportWatch::Unwatch : decided); #endif - blocks.push_back(std::make_unique(layout.Bytes() * samples)); - auto& source = *blocks.back(); - std::memset(source.data, 0, source.bytes); - blocks.push_back(std::make_unique(BlockBytes)); - auto& metadata = *blocks.back(); - std::memset(metadata.data, 0, BlockBytes); - auto target = DecodeTarget(source, samples); - target.dccAddress = metadata.Address(); - target.dccPipeAligned = true; - target.dccAlphaOnMsb = true; - Block* cmask = nullptr; - if (cmasked) { + blocks.push_back(std::make_unique(layout.Bytes() * samples)); + auto& source = *blocks.back(); + std::memset(source.data, 0, source.bytes); blocks.push_back(std::make_unique(BlockBytes)); - cmask = blocks.back().get(); - std::memset(cmask->data, 0xcc, BlockBytes); - target.cmaskAddress = cmask->Address(); - target.cmaskBytes = AgcDriver::Graphics::CmaskBytes(Width, Height); - target.cmaskFastClear = true; - device.ColorMetadataPass({AgcDriver::Graphics::ColorMetadataPass::Mode::FmaskDecompress, {target}}); - } - Draw(device, target, false, Float32Export, Program(written, Float32Export, 1u), false, true); - const auto dccBytes = AgcDriver::Graphics::MultisampledDccBytes(target); - std::string_view expected; - bool rewatchable = false; - if (mode == 0) { - fill(metadata, dccBytes, 0xc0c0c0c0u); - if (cmask != nullptr) fill(*cmask, target.cmaskBytes, 0xccccccccu); - } else if (mode == 1) { - fill(metadata, dccBytes, 0xc0c0c0c0u); - } else if (mode >= 5) { - std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); - const auto record = [&](const Block& block, std::size_t bytes, std::uint32_t value) { - const std::array pattern{value, value, value, value}; - Require(device.FillBuffer(block.Address(), bytes, pattern), "the metadata fill was not recorded on the GPU"); - AgcDriver::Graphics::NoteColorMetadataFill(block.Address(), bytes, value); - }; - record(metadata, dccBytes, 0xc0c0c0c0u); - if (cmask != nullptr) record(*cmask, target.cmaskBytes, 0xccccccccu); - const bool unwatched = !AgcDriver::GuestMemory::Watched(metadata.Address(), dccBytes) || (cmask != nullptr && !AgcDriver::GuestMemory::Watched(cmask->Address(), target.cmaskBytes)); + auto& metadata = *blocks.back(); + std::memset(metadata.data, 0, BlockBytes); + auto target = DecodeTarget(source, samples); + target.dccAddress = metadata.Address(); + target.dccPipeAligned = true; + target.dccAlphaOnMsb = true; + if (code == 0x20u) target.clearWords = {0x38003000u, 0x3c003a00u}; + const Color cleared = code == 0x20u ? Color{0.125f, 0.5f, 0.75f, 1.0f} : Color{1.0f, 1.0f, 1.0f, 1.0f}; + Block* cmask = nullptr; + if (cmasked) { + blocks.push_back(std::make_unique(BlockBytes)); + cmask = blocks.back().get(); + std::memset(cmask->data, 0xcc, BlockBytes); + target.cmaskAddress = cmask->Address(); + target.cmaskBytes = AgcDriver::Graphics::CmaskBytes(Width, Height); + target.cmaskFastClear = true; + device.ColorMetadataPass({AgcDriver::Graphics::ColorMetadataPass::Mode::FmaskDecompress, {target}}); + } + Draw(device, target, false, Float32Export, Program(written, Float32Export, 1u), false, true); + const auto dccBytes = AgcDriver::Graphics::MultisampledDccBytes(target); + std::string_view expected; + bool rewatchable = false; + if (mode == 0) { + fill(metadata, dccBytes, refill); + if (cmask != nullptr) fill(*cmask, target.cmaskBytes, 0xccccccccu); + } else if (mode == 1) { + fill(metadata, dccBytes, refill); + } else if (mode >= 5) { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + const auto record = [&](const Block& block, std::size_t bytes, std::uint32_t value) { + const std::array pattern{value, value, value, value}; + Require(device.FillBuffer(block.Address(), bytes, pattern), "the metadata fill was not recorded on the GPU"); + AgcDriver::Graphics::NoteColorMetadataFill(block.Address(), bytes, value); + }; + record(metadata, dccBytes, refill); + if (cmask != nullptr) record(*cmask, target.cmaskBytes, 0xccccccccu); + const bool unwatched = !AgcDriver::GuestMemory::Watched(metadata.Address(), dccBytes) || (cmask != nullptr && !AgcDriver::GuestMemory::Watched(cmask->Address(), target.cmaskBytes)); #ifdef _WIN32 - if (unwatched) expected = "requires watched guest memory"; - rewatchable = unwatched; + if (unwatched) expected = "requires watched guest memory"; + rewatchable = unwatched; #else - Require(unwatched == (mode == 6), "a GPU fill of multisampled DCC metadata did not take the write-watch mode of its host import"); + Require(unwatched == (mode == 6), "a GPU fill of multisampled DCC metadata did not take the write-watch mode of its host import"); #endif - } else if (mode == 2) { - fill(metadata, dccBytes / 2u, 0xc0c0c0c0u); - expected = "writes to a resident multisampled DCC"; - } else if (mode == 3) { - fill(metadata, dccBytes, 0xffffffffu); - expected = "not a supported uniform fast clear"; - } else { - fill(*cmask, target.cmaskBytes, 0xccccccccu); - expected = "writes to a resident multisampled DCC"; - } - bool drawn = false; - if (!expected.empty()) { - bool refused = false; - try { - Draw(device, target, false, Float32Export, Program(written, Float32Export, 1u), false, true); - drawn = true; - } catch (const std::runtime_error& error) { - refused = std::string_view(error.what()).find(expected) != std::string_view::npos; + } else if (mode == 2) { + fill(metadata, dccBytes / 2u, 0xc0c0c0c0u); + expected = "writes to a resident multisampled DCC"; + } else if (mode == 3) { + fill(metadata, dccBytes, 0xffffffffu); + expected = "not a supported uniform fast clear"; + } else { + fill(*cmask, target.cmaskBytes, 0xccccccccu); + expected = "writes to a resident multisampled DCC"; } - Require(refused || (drawn && rewatchable), "a write to resident multisampled DCC metadata that is not a full fast-clear fill was not refused"); - if (refused) { - programCount = 0; - continue; - } - } - if (!drawn) Draw(device, target, false, Float32Export, Program(written, Float32Export, 1u), false, true); - blocks.push_back(std::make_unique(layout.Bytes())); - auto& resolved = *blocks.back(); - std::memset(resolved.data, 0, resolved.bytes); - AgcDriver::Graphics::ColorMetadataPass pass{}; - pass.mode = AgcDriver::Graphics::ColorMetadataPass::Mode::Resolve; - pass.source = target; - pass.targets = {DecodeTarget(resolved)}; - device.ColorMetadataPass(pass); - const auto stored = ReadBack(device, resolved); - 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 expectedValue = x < ScissorWidth ? 1.0f + (written[channel] - 1.0f) / static_cast(samples) : 1.0f; - const auto actual = FromHalf(stored[layout.Offset(x, y) / 2u + channel]); - Require(actual == expectedValue, "a full DCC fast-clear fill of a resident multisampled target did not clear it to the new color before the next draw"); + bool drawn = false; + if (!expected.empty()) { + bool refused = false; + try { + Draw(device, target, false, Float32Export, Program(written, Float32Export, 1u), false, true); + drawn = true; + } catch (const std::runtime_error& error) { + refused = std::string_view(error.what()).find(expected) != std::string_view::npos; + } + Require(refused || (drawn && rewatchable), "a write to resident multisampled DCC metadata that is not a full fast-clear fill was not refused"); + if (refused) { + programCount = 0; + continue; } } + if (!drawn) Draw(device, target, false, Float32Export, Program(written, Float32Export, 1u), false, true); + blocks.push_back(std::make_unique(layout.Bytes())); + auto& resolved = *blocks.back(); + std::memset(resolved.data, 0, resolved.bytes); + AgcDriver::Graphics::ColorMetadataPass pass{}; + pass.mode = AgcDriver::Graphics::ColorMetadataPass::Mode::Resolve; + pass.source = target; + pass.targets = {DecodeTarget(resolved)}; + device.ColorMetadataPass(pass); + const auto stored = ReadBack(device, resolved); + 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 expectedValue = x < ScissorWidth ? cleared[channel] + (written[channel] - cleared[channel]) / static_cast(samples) : cleared[channel]; + const auto actual = FromHalf(stored[layout.Offset(x, y) / 2u + channel]); + Require(actual == expectedValue, "a full DCC fast-clear fill of a resident multisampled target did not clear it to the new color before the next draw"); + } + } + } + programCount = 0; } - programCount = 0; } } } @@ -1267,6 +1331,18 @@ int main(int argc, char** argv) { std::puts("dithered eight-sample alpha-to-coverage tests passed"); return 0; } + if (argc == 2 && (std::string_view(argv[1]) == "--sample-mask8" || std::string_view(argv[1]) == "--sample-mask2")) { + const bool eight = std::string_view(argv[1]) == "--sample-mask8"; + try { + SampleMaskExportCoverage(*device, eight ? 8u : 2u, eight ? std::span(EightSampleDitherCases) : std::span(TwoSampleDitherCases)); + } catch (const std::runtime_error& error) { + if (std::string_view(error.what()) != "AGC graphics: the device cannot render color with the target's sample count") throw; + std::printf("skipped, the device lacks %s-sample color attachments: %s\n", eight ? "eight" : "two", error.what()); + return VulkanTestSkipped; + } + std::printf("%s-sample alpha-to-coverage with sample-mask export tests passed\n", eight ? "eight" : "two"); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--dithered2") { try { DitheredCoverage(*device, 2, TwoSampleDitherCases); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/CalibratedClock.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/CalibratedClock.cpp new file mode 100644 index 0000000000..77caebd08c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/CalibratedClock.cpp @@ -0,0 +1,57 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Context.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +void EnableTraceTimestamps() { +#ifdef _WIN32 + _putenv_s("APS5_TRACE_SECONDS", "100000:1"); +#else + setenv("APS5_TRACE_SECONDS", "100000:1", 1); +#endif +} + +double Milliseconds(std::chrono::steady_clock::duration duration) { + return std::chrono::duration(duration).count(); +} + +} + +int main() { + try { + EnableTraceTimestamps(); + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + double firstGpuNs = 0.0; + std::chrono::steady_clock::time_point firstCpu; + if (!device->CalibrateClock(firstGpuNs, firstCpu)) { + std::cout << "skipped, the device calibrates its timestamps against no host clock the driver reads\n"; + return VulkanTestSkipped; + } + const double offsetMs = std::abs(Milliseconds(std::chrono::steady_clock::now() - firstCpu)); + Require(offsetMs < 50.0, "the calibrated GPU timestamp maps to a CPU time " + std::to_string(offsetMs) + " ms away from now: the host clock read is not the one the driver calibrated against"); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + double secondGpuNs = 0.0; + std::chrono::steady_clock::time_point secondCpu; + Require(device->CalibrateClock(secondGpuNs, secondCpu), "a second calibration failed after the first succeeded"); + const double cpuMs = Milliseconds(secondCpu - firstCpu); + const double gpuMs = (secondGpuNs - firstGpuNs) / 1e6; + Require(cpuMs >= 150.0 && std::abs(gpuMs - cpuMs) < 2.0, "200 ms apart, the two calibrations differ by " + std::to_string(gpuMs) + " ms on the GPU clock and " + std::to_string(cpuMs) + " ms on the CPU clock"); + std::cout << "calibrated clock tests passed (offset " << offsetMs << " ms, GPU " << gpuMs << " ms against CPU " << cpuMs << " ms)\n"; + 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 d2013c8b53..002bc1f57a 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ComputeFsTableCalls.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ComputeFsTableCalls.cpp @@ -98,6 +98,10 @@ int main() { #ifdef ANYPS5_ENABLE_ASAN std::puts("skipped: the shader calls through the FS table at the PS5's fixed 0xfe0040000, which an ANYPS5_ENABLE_ASAN build cannot map"); return VulkanTestSkipped; +#endif +#ifdef __APPLE__ + std::puts("skipped: the shader calls through the FS table at the PS5's fixed 0xfe0040000, which macOS reserves; the AGC driver block is moved on macOS (DriverMemory.hpp)"); + return VulkanTestSkipped; #endif try { { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DccKeyPresync.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeyPresync.cpp index 6fcb25ce6f..c39c8fa542 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DccKeyPresync.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DccKeyPresync.cpp @@ -15,6 +15,7 @@ #endif #include #include +#include #include #include #include @@ -234,6 +235,54 @@ void RunKnownKeys(AgcDriver::VulkanDevice& device, const Scenario& scenario) { RequireSampledClear(scenario, "a dispatch behind a pending fill of the keys"); } +void RunMixedWordKeys(AgcDriver::VulkanDevice& device, const Scenario& scenario) { + Reset(scenario); + const std::array pattern{0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu, 0x00ff00ffu}; + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Require(device.FillBuffer(AddressOf(scenario.keys), KeyBytes, pattern), "the word fill of the keys was not recorded"); + Require(AgcDriver::Graphics::DccKeyWritersPending(AddressOf(scenario.keys), KeyBytes), "a recorded word fill of the keys is not seen as a pending key writer"); + Require(!AgcDriver::Graphics::DccKeyWriterNeedsSync(AddressOf(scenario.keys), KeyBytes), "the keys of a recorded fill of a word with different bytes need a synchronization"); + } + const auto sample = Compile(device, SampleSecondTexelCode, scenario.sampleData); + const auto waits = AgcDriver::Graphics::KeyWaitCounts(); + const auto before = AgcDriver::VulkanDevice::PresyncCounts(); + auto prepared = device.PrepareDispatch(sample, {}, Threads, reinterpret_cast(SampleSecondTexelCode.data())); + const auto after = AgcDriver::VulkanDevice::PresyncCounts(); + Require(after.keyChecks > before.keyChecks, "stage A did not list the DCC keys of the sampled texture for the pre-sync"); + Require(after.presyncs == before.presyncs, "stage A waited for a word fill whose keys the locked lookup derives from the pending stores"); + { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + device.Dispatch(sample, 1, 1, 1, {}, reinterpret_cast(SampleSecondTexelCode.data()), std::move(prepared)); + device.WaitIdle(); + } + Require(AgcDriver::Graphics::KeyWaitCounts().synced == waits.synced, "the dispatch synchronized with the GPU although the pending word fill gives the keys"); + for (std::uint32_t index = 0; index < Threads * 4u; ++index) { + float value = 0.0f; + std::memcpy(&value, &scenario.output[index], sizeof(value)); + char message[256]; + std::snprintf(message, sizeof(message), "a dispatch behind a pending fill of mixed keys: component %u of thread %u is %g, expected the stored texel 0x55 (%g)", index % 4u, index / 4u, value, 85.0 / 255.0); + Require(std::fabs(value - 85.0f / 255.0f) < 1.0f / 512.0f, message); + } +} + +void RunWordFillKeyBytes(AgcDriver::VulkanDevice& device, const Scenario& scenario) { + Reset(scenario); + constexpr std::uint32_t word = 0x33221100u; + const std::array pattern{word, word, word, word}; + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + Require(device.FillBuffer(AddressOf(scenario.keys), KeyBytes, pattern), "the word fill of the keys was not recorded"); + for (std::uint64_t offset = 0; offset < 8u; ++offset) { + const auto key = AgcDriver::Graphics::CurrentUniformMetadataByte(AddressOf(scenario.keys) + offset, 1); + const auto expected = static_cast(word >> (8u * (offset % 4u))); + char message[160]; + std::snprintf(message, sizeof(message), "key byte %llu behind a pending fill of the word 0x%08x is %d, expected 0x%02x", static_cast(offset), word, key.has_value() ? static_cast(*key) : -1, expected); + Require(key == expected, message); + } + Require(!AgcDriver::Graphics::CurrentUniformMetadataByte(AddressOf(scenario.keys) + 1, 2).has_value(), "two key bytes of different values behind a pending word fill read as one key"); + device.WaitIdle(); +} + void RunOneKeyReadPerLookup(AgcDriver::VulkanDevice& device, const Scenario& scenario, const char* what) { Reset(scenario); const auto sample = Compile(device, SampleSecondTexelCode, scenario.sampleData); @@ -295,6 +344,8 @@ int main() { RunOneKeyReadPerLookup(*device, reads, "a dispatch's first lookup"); RunOneKeyReadPerLookup(*device, reads, "a dispatch's repeated lookup"); RunKnownKeys(*device, fills); + RunMixedWordKeys(*device, fills); + RunWordFillKeyBytes(*device, fills); std::puts("DCC key pre-sync tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DepthSurfaceFromStorage.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DepthSurfaceFromStorage.cpp index 8c7267c2ba..5893c70a25 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DepthSurfaceFromStorage.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DepthSurfaceFromStorage.cpp @@ -51,7 +51,7 @@ constexpr std::uint32_t TileDepth64KB = 0x18; constexpr std::uint32_t Type2D = 9; constexpr std::uint32_t Type2DArray = 13; constexpr std::size_t PlaneBytes = 65536; -constexpr std::size_t BlockBytes = 11 * PlaneBytes; +constexpr std::size_t BlockBytes = 15 * PlaneBytes; constexpr std::uint32_t RowPasses = 15; alignas(256) constexpr std::array StoreRowCode{ @@ -241,6 +241,20 @@ void RetireStoresDepthCase(AgcDriver::VulkanDevice& device, const DepthFastClear Require(half == stored.level0.size() / sizeof(float), "the retire left " + std::to_string(stored.level0.size() / sizeof(float) - half) + " of the drawn depth plane's texels without the depth drawn on the GPU"); } +void RetiredSurfaceCase(AgcDriver::VulkanDevice& device, const DepthFastClearHarness::Shaders& shaders, std::uint8_t* plane, std::uint8_t* metadata, bool clearFirst, const std::string& what) { + Fill(plane, VK_FORMAT_D32_SFLOAT); + std::memset(metadata, 0xff, DepthFastClearHarness::HtileBytes); + const auto htile = Address(metadata); + static_cast(DepthFastClearHarness::Draw(device, shaders, Plane(plane, VK_FORMAT_D32_SFLOAT, htile), {})); + AgcDriver::Graphics::RetireDepthSurfaces(AgcDriver::VulkanDeviceTestAccess::ContextOf(device).device, Address(plane), PlaneBytes); + Require(!AgcDriver::Graphics::DepthSurfaceAt(Address(plane)), what + ": the depth surface was not retired"); + if (clearFirst) AgcDriver::Graphics::NoteHtileDepthClear(htile, DepthFastClearHarness::HtileBytes); + StoreRow(device, plane, VK_FORMAT_D32_SFLOAT, 1); + Require(AgcDriver::Graphics::StorageTexture::FindPending(Address(plane), PlaneBytes) != nullptr, what + ": the stored row is not pending in a storage image"); + if (!clearFirst) AgcDriver::Graphics::NoteHtileDepthClear(htile, DepthFastClearHarness::HtileBytes); + RequireCount(DepthFastClearHarness::Draw(device, shaders, Plane(plane, VK_FORMAT_D32_SFLOAT, htile), {}), clearFirst ? RowPasses : Covered, what); +} + } int main() { @@ -270,6 +284,8 @@ int main() { RequireCount(DepthFastClearHarness::Draw(*device, shaders, depthStencil, {.depthTest = false, .stencilTest = true, .stencil = equalZero}), Covered, "the stencil plane of a new D32S8 depth surface that took a storage write, tested EQUAL to its clear value 0"); PendingChainCase(*device, shaders, block.Plane(8)); RetireStoresDepthCase(*device, shaders, block.Plane(9), block.Plane(10)); + RetiredSurfaceCase(*device, shaders, block.Plane(11), block.Plane(12), true, "a retired D32 surface with HTILE, a depth clear noted on it, then a storage write over its whole plane, bound again"); + RetiredSurfaceCase(*device, shaders, block.Plane(13), block.Plane(14), false, "a retired D32 surface with HTILE, a storage write over its whole plane, then a depth clear noted on it, bound again"); if (!failures.empty()) { std::string text; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DescriptorWebFaults.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DescriptorWebFaults.cpp index dc0524d6a8..abb28b0b68 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DescriptorWebFaults.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DescriptorWebFaults.cpp @@ -82,6 +82,25 @@ alignas(256) constexpr std::array TableNoLaneWebCode{ 0xbf810000u, }; +alignas(256) constexpr std::array LaneIndexWebCode{ + 0xf4000400u, 0xfa000050u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf40c0200u, 0xfa000000u, + 0xbf8cc07fu, 0xbf068010u, 0xbf850005u, 0xbf068110u, 0xbf85000cu, 0xf40c0200u, 0xfa000060u, 0xbf8cc07fu, + 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0x7e0c020cu, 0x7e0e020du, 0x7e10020eu, 0x7e12020fu, + 0xbf820009u, 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0x7e0c020cu, 0x7e0e020du, 0x7e10020eu, + 0xd7650009u, 0x000100c1u, 0x7e100502u, 0x7e120503u, 0x7e140504u, 0x7e160505u, 0x7e180506u, 0x7e1a0507u, + 0x7e1c0508u, 0x7e1e0509u, 0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, 0xbf810000u, +}; + +alignas(256) constexpr std::array NoLaneLaneIndexWebCode{ + 0xf4000400u, 0xfa000050u, 0xf4080500u, 0xfa000020u, 0xf4080600u, 0xfa000040u, 0xf40c0200u, 0xfa000000u, + 0xbf8cc07fu, 0xbf068010u, 0xbf850005u, 0xbf068110u, 0xbf85000cu, 0xf40c0200u, 0xfa000060u, 0xbf8cc07fu, + 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0x7e0c020cu, 0x7e0e020du, 0x7e10020eu, 0x7e12020fu, + 0xbf820009u, 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0x7e0c020cu, 0x7e0e020du, 0x7e10020eu, + 0xd7650009u, 0x000100c1u, 0x7e100502u, 0x7e120503u, 0x7e140504u, 0x7e160505u, 0x7e180506u, 0x7e1a0507u, + 0x7e1c0508u, 0x7e1e0509u, 0xbefe0380u, 0xf09c0f08u, 0x00a20000u, 0xbf8c3f70u, 0xe0700000u, 0x80060000u, + 0xbf810000u, +}; + class StderrCapture { public: StderrCapture() { @@ -246,6 +265,16 @@ void RunTests(AgcDriver::VulkanDevice& device) { Require(Faulted(tableSelected.log, TableSamplePc), "descriptor web faults: with an image table and no poisoned record, a lane selecting an undefined arm did not fault at the read:\n" + tableSelected.log); const auto tableNoLanes = Run(device, TableNoLaneWebCode, UndefinedFormat, SecondSelector, true); Require(Clean(tableNoLanes.log) && tableNoLanes.word == Untouched, "descriptor web faults: with an image table, an undefined arm faulted with no lane enabled:\n" + tableNoLanes.log); + + constexpr std::uint32_t LaneIndexSamplePc = 0xa8u; + const auto laneValid = Run(device, LaneIndexWebCode, Format8888UNorm, ValidSelector); + Require(Clean(laneValid.log) && Reads(laneValid.word, FirstTexel), "descriptor web faults: with a lane-index path, a lane selecting the first arm faulted or did not read its texture:\n" + laneValid.log); + const auto laneOther = Run(device, LaneIndexWebCode, Format8888UNorm, SecondSelector); + Require(Clean(laneOther.log) && Reads(laneOther.word, SecondTexel), "descriptor web faults: with a lane-index path, a lane selecting the second arm faulted or did not read its texture:\n" + laneOther.log); + const auto laneFolded = Run(device, LaneIndexWebCode, Format8888UNorm, FoldedSelector); + Require(Faulted(laneFolded.log, LaneIndexSamplePc), "descriptor web faults: a lane on the lane-index path did not fault at the read:\n" + laneFolded.log); + const auto laneFoldedNoLanes = Run(device, NoLaneLaneIndexWebCode, Format8888UNorm, FoldedSelector); + Require(Clean(laneFoldedNoLanes.log) && laneFoldedNoLanes.word == Untouched, "descriptor web faults: the lane-index path faulted or wrote with no lane enabled:\n" + laneFoldedNoLanes.log); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DispatchOverlap.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DispatchOverlap.cpp new file mode 100644 index 0000000000..a024ed7ffa --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DispatchOverlap.cpp @@ -0,0 +1,240 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.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/Shutdown.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint32_t Lanes = 64; +constexpr std::uint32_t BufferBytes = Lanes * 4u; +constexpr std::uint32_t ComputeQueue = 0x20; +constexpr std::size_t BlockBytes = 65536; +constexpr std::uint32_t Builds = 4; + +alignas(256) constexpr std::array StoreOne{0x34020082u, 0x4a040081u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array StoreTwo{0x34020082u, 0x4a040082u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array StoreThree{0x34020082u, 0x4a040083u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array StoreFour{0x34020082u, 0x4a040084u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array CopyCode{0x34020082u, 0xe0301000u, 0x80000201u, 0xbf8c3f70u, 0xe0701000u, 0x80010201u, 0xbf810000u}; + +const std::array StoreCodes{StoreOne.data(), StoreTwo.data(), StoreThree.data(), StoreFour.data()}; + +std::array Targets{}; +std::uint32_t* Spare = nullptr; +std::uint32_t* Copied = nullptr; + +std::uint32_t* NewBlock() { +#ifdef _WIN32 + auto* block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + auto* block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + if (block == nullptr) throw std::runtime_error("dispatch overlap: cannot allocate a guest block"); + std::memset(block, 0, BlockBytes); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + return block; +} + +void check(bool condition, const std::string& what) { + if (!condition) throw std::runtime_error("dispatch overlap: " + what); +} + +void Register(const std::uint32_t* code, std::size_t bytes) { + Shader shader{}; + shader.file_header = 0x34333231; + shader.version = 0x18; + shader.header_size = sizeof(Shader); + shader.shader_size = static_cast(bytes); + shader.code = code; + AgcDriverRegisterShader_nid_postfix(&shader); +} + +void AddBuffer(std::vector& words, std::uint32_t reg, const std::uint32_t* memory) { + const auto address = reinterpret_cast(memory); + words.insert(words.end(), {0xc0047600u, reg, static_cast(address), static_cast((address >> 32u) & 0xffffu), BufferBytes, 0x31016facu}); +} + +void AddDispatch(std::vector& words, const std::uint32_t* code) { + const auto codeAddress = reinterpret_cast(code); + words.insert(words.end(), {0xc0027600u, 0x20cu, static_cast(codeAddress >> 8u), static_cast(codeAddress >> 40u), 0xc0031500u, 1u, 1u, 1u, 0x8041u}); +} + +void AddPartialFlush(std::vector& words) { + words.insert(words.end(), {0xc0004600u, 0x407u}); +} + +std::vector Header() { + return {0xc0037600u, 0x207u, Lanes, 1u, 1u}; +} + +void AddUserSgprs(std::vector& words, std::uint32_t count) { + words.insert(words.end(), {0xc0017600u, 0x213u, count << 1u}); +} + +void Submit(std::vector& words) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check(sceAgcDriverSubmitAcb(ComputeQueue, &packet) == 0, "submission failed"); + AgcDriverWaitIdle_nid_postfix(); +} + +void Clear() { + for (auto* target : Targets) std::memset(target, 0xff, BufferBytes); + std::memset(Spare, 0xff, BufferBytes); + std::memset(Copied, 0xff, BufferBytes); +} + +void Expect(const std::uint32_t* memory, std::uint32_t add, const std::string& what) { + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(memory), BufferBytes); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) { + const auto value = std::atomic_ref(const_cast(memory[lane])).load(std::memory_order_acquire); + check(value == lane + add, what + ": lane " + std::to_string(lane) + " is " + std::to_string(value) + ", expected " + std::to_string(lane + add)); + } +} + +std::uint64_t Overlapped() { + return AgcDriver::Graphics::Recorder::OverlapCounts().overlapped; +} + +std::uint64_t RunBuilds(bool ordered) { + Clear(); + auto words = Header(); + AddUserSgprs(words, 4); + for (std::uint32_t build = 0; build < Builds; ++build) { + if (ordered && build != 0) AddPartialFlush(words); + AddBuffer(words, 0x240u, Targets[build]); + AddDispatch(words, StoreCodes[build]); + } + AddPartialFlush(words); + AddUserSgprs(words, 8); + AddBuffer(words, 0x240u, Targets[Builds - 1]); + AddBuffer(words, 0x244u, Copied); + AddDispatch(words, CopyCode.data()); + const auto before = Overlapped(); + Submit(words); + const auto joined = Overlapped() - before; + const std::string kind = ordered ? "dispatches ordered by partial flushes" : "unordered dispatches"; + for (std::uint32_t build = 0; build < Builds; ++build) Expect(Targets[build], build + 1, kind + ", build " + std::to_string(build + 1)); + Expect(Copied, Builds, kind + ", the ordered copy of the last result"); + return joined; +} + +std::uint64_t ReadAfterWrite(bool dependent) { + Clear(); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) Targets[1][lane] = lane + 5u; + auto words = Header(); + AddUserSgprs(words, 4); + AddBuffer(words, 0x240u, Targets[0]); + AddDispatch(words, StoreCodes[0]); + AddUserSgprs(words, 8); + AddBuffer(words, 0x240u, dependent ? Targets[0] : Targets[1]); + AddBuffer(words, 0x244u, Copied); + AddDispatch(words, CopyCode.data()); + const auto before = Overlapped(); + Submit(words); + const auto joined = Overlapped() - before; + Expect(Targets[0], 1, dependent ? "a dispatch reading the previous one's output, the writer" : "a dispatch reading other memory, the writer"); + Expect(Copied, dependent ? 1u : 5u, dependent ? "a dispatch reading the previous unordered dispatch's output" : "a dispatch reading memory the previous one does not write"); + return joined; +} + +std::uint64_t WriteAfterAccess(bool afterRead) { + Clear(); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) Targets[0][lane] = lane + 5u; + auto words = Header(); + if (afterRead) { + AddUserSgprs(words, 8); + AddBuffer(words, 0x240u, Targets[0]); + AddBuffer(words, 0x244u, Copied); + AddDispatch(words, CopyCode.data()); + } else { + AddUserSgprs(words, 4); + AddBuffer(words, 0x240u, Targets[0]); + AddDispatch(words, StoreCodes[0]); + } + AddUserSgprs(words, 4); + AddBuffer(words, 0x240u, Targets[0]); + AddDispatch(words, StoreCodes[1]); + const auto before = Overlapped(); + Submit(words); + const auto joined = Overlapped() - before; + if (afterRead) Expect(Copied, 5, "a dispatch reading memory the next unordered one overwrites"); + Expect(Targets[0], 2, afterRead ? "a dispatch overwriting what the previous unordered one read" : "a dispatch overwriting what the previous unordered one wrote"); + return joined; +} + +void SameBuildTwice() { + Clear(); + auto words = Header(); + AddUserSgprs(words, 4); + AddBuffer(words, 0x240u, Targets[0]); + AddDispatch(words, StoreCodes[0]); + AddBuffer(words, 0x240u, Spare); + AddDispatch(words, StoreCodes[0]); + Submit(words); + Expect(Targets[0], 1, "one build twice, first target"); + Expect(Spare, 1, "one build twice, second target"); +} + +} + +int main(int argc, char** argv) { + try { + { + const auto probe = OpenVulkanTestDevice(); + if (!probe) return VulkanTestSkipped; + } + const bool off = argc > 1 && std::string(argv[1]) == "--off"; + for (const auto* code : StoreCodes) Register(code, sizeof(StoreOne)); + Register(CopyCode.data(), sizeof(CopyCode)); + for (auto& target : Targets) target = NewBlock(); + Spare = NewBlock(); + Copied = NewBlock(); + + for (int round = 0; round < 3; ++round) { + const auto joined = RunBuilds(false); + const std::uint64_t expected = off ? 0 : Builds - 1; + check(joined == expected, "unordered dispatches of " + std::to_string(Builds) + " builds, round " + std::to_string(round + 1) + ": " + std::to_string(joined) + " joined a run, expected " + std::to_string(expected)); + const auto ordered = RunBuilds(true); + check(ordered == 0, "dispatches ordered by partial flushes, round " + std::to_string(round + 1) + ": " + std::to_string(ordered) + " joined a run, expected 0"); + SameBuildTwice(); + const auto dependent = ReadAfterWrite(true); + check(dependent == 0, "a dispatch reading what the previous unordered one wrote, round " + std::to_string(round + 1) + ": " + std::to_string(dependent) + " joined a run, expected 0"); + const auto afterRead = WriteAfterAccess(true); + check(afterRead == 0, "a dispatch overwriting what the previous unordered one read, round " + std::to_string(round + 1) + ": " + std::to_string(afterRead) + " joined a run, expected 0"); + const auto afterWrite = WriteAfterAccess(false); + check(afterWrite == 0, "a dispatch overwriting what the previous unordered one wrote, round " + std::to_string(round + 1) + ": " + std::to_string(afterWrite) + " joined a run, expected 0"); + const auto disjoint = ReadAfterWrite(false); + const std::uint64_t disjointExpected = off ? 0 : 1; + check(disjoint == disjointExpected, "a dispatch reading memory the previous unordered one does not write, round " + std::to_string(round + 1) + ": " + std::to_string(disjoint) + " joined a run, expected " + std::to_string(disjointExpected)); + } + std::printf("dispatch overlap: %s\n", off ? "no unordered dispatch joined a run with APS5_NO_DISPATCH_OVERLAP" : "unordered dispatches of distinct builds ran in one run, results kept"); + + LibcRunShutdown_nid_postfix(); + std::puts("dispatch overlap 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/DispatchRecaptureOnChange.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DispatchRecaptureOnChange.cpp new file mode 100644 index 0000000000..74c3c0972c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DispatchRecaptureOnChange.cpp @@ -0,0 +1,173 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.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 + +namespace { + +constexpr std::uint32_t Lanes = 32; +constexpr std::uint32_t BufferBytes = Lanes * 4u; +constexpr std::size_t BlockBytes = 65536; + +alignas(256) constexpr std::array TableCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x34020082u, 0xe0301000u, 0x80020201u, 0xbf8c3f70u, 0x4a060481u, 0xe0701000u, 0x80010301u, 0xbf810000u}; + +void Expect(bool condition, const std::string& what) { + if (!condition) throw std::runtime_error("dispatch recapture: " + what); +} + +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 + Expect(block != nullptr, "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::uint32_t* Words() const { return block; } + +private: + std::uint32_t* block = nullptr; +}; + +std::array BufferDescriptor(const void* data) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), BufferBytes, 0x31016facu}; +} + +const GuestBlock* Table = nullptr; +const GuestBlock* Moved = nullptr; +std::atomic TableChangeArmed{false}; +std::atomic TableChanges{0}; + +void ChangeTableBeforeCheck() { + if (!TableChangeArmed.exchange(false)) return; + const auto descriptor = BufferDescriptor(Moved->Words()); + std::memcpy(Table->Words(), descriptor.data(), sizeof(descriptor)); + TableChanges.fetch_add(1); +} + +void Dispatch(const GuestBlock& table, const GuestBlock& output) { + const auto codeAddress = reinterpret_cast(TableCode.data()); + const auto tableAddress = reinterpret_cast(table.Words()); + const auto stored = BufferDescriptor(output.Words()); + std::vector commands{ + 0xc0027600u, 0x20cu, static_cast(codeAddress >> 8u), static_cast(codeAddress >> 40u), + 0xc0037600u, 0x207u, Lanes, 1u, 1u, + 0xc0017600u, 0x213u, 8u << 1u, + 0xc0087600u, 0x240u, static_cast(tableAddress), static_cast(tableAddress >> 32u), 0u, 0u, stored[0], stored[1], stored[2], stored[3], + 0xc0031500u, 1u, 1u, 1u, 0x8041u, + }; + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Expect(sceAgcDriverSubmitDcb(&packet) == 0, "the dispatch submission failed"); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(output.Words()), BufferBytes); +} + +std::uint32_t Lane(const GuestBlock& output, std::uint32_t lane) { + return output.Words()[lane]; +} + +void ExpectOutput(const GuestBlock& output, std::uint32_t base, const std::string& what) { + for (std::uint32_t lane = 0; lane < Lanes; ++lane) Expect(Lane(output, lane) == base + lane + 1u, what + ": lane " + std::to_string(lane) + " stored " + std::to_string(Lane(output, lane)) + ", expected " + std::to_string(base + lane + 1u)); +} + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + Shader shader{}; + shader.file_header = 0x34333231; + shader.version = 0x18; + shader.header_size = sizeof(Shader); + shader.shader_size = sizeof(TableCode); + shader.code = TableCode.data(); + AgcDriverRegisterShader_nid_postfix(&shader); + GuestBlock table; + GuestBlock first; + GuestBlock moved; + GuestBlock output; + for (std::uint32_t lane = 0; lane < Lanes; ++lane) { + first.Words()[lane] = 0x1000u + lane; + moved.Words()[lane] = 0x2000u + lane; + } + const auto initial = BufferDescriptor(first.Words()); + std::memcpy(table.Words(), initial.data(), sizeof(initial)); + Table = &table; + Moved = &moved; + AgcDriver::DriverDetail::Driver::SetCaptureCheckHook(&ChangeTableBeforeCheck); + + Dispatch(table, output); + ExpectOutput(output, 0x1000u, "the first dispatch through the table"); + std::printf("the first dispatch stored 0x%x in lane 0 through the table's first V#\n", Lane(output, 0)); + + const auto retries = AgcDriver::DriverDetail::captureChangeRetries.load(); + TableChangeArmed.store(true); + Dispatch(table, output); + const auto redone = AgcDriver::DriverDetail::captureChangeRetries.load() - retries; + std::printf("the same dispatch with its table's V# moved to another buffer after its capture: the table changed %d time(s), lane 0 stored 0x%x (%s), %llu packet(s) prepared again\n", TableChanges.load(), Lane(output, 0), Lane(output, 0) == 0x2001u ? "the moved V# was bound" : "the V# captured before the change was bound", static_cast(redone)); + Expect(TableChanges.load() == 1, "the check seam did not run before the dispatch was recorded"); + ExpectOutput(output, 0x2000u, "the dispatch whose table changed before it was recorded"); + Expect(redone == 1, "the dispatch was prepared again " + std::to_string(redone) + " times, not once"); + + const auto steady = AgcDriver::DriverDetail::captureChangeRetries.load(); + Dispatch(table, output); + const auto again = AgcDriver::DriverDetail::captureChangeRetries.load() - steady; + std::printf("the same dispatch with its table unchanged: lane 0 stored 0x%x, %llu packet(s) prepared again\n", Lane(output, 0), static_cast(again)); + ExpectOutput(output, 0x2000u, "the dispatch with an unchanged table"); + Expect(again == 0, "a dispatch whose captured memory did not change was prepared again"); + + AgcDriver::DriverDetail::Driver::SetCaptureCheckHook(nullptr); + AgcDriverShutdown_nid_postfix(); + std::puts("dispatch recapture: a dispatch whose captured descriptor table changed before it was recorded binds the table in memory"); + 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/Division.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Division.cpp index 1287be8ac8..274f9ddd99 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/Division.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Division.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,11 @@ alignas(256) constexpr std::array ScaleCode{ 0xe0702004u, 0x80010b01u, 0xbf810000u, }; +alignas(256) constexpr std::array FmasCode{ + 0x34020082u, 0xe0302000u, 0x80000401u, 0xe0302004u, 0x80000501u, 0xe0302008u, 0x80000601u, 0xe030200cu, + 0x80000701u, 0xbf8c3f70u, 0x7d8a0e80u, 0xd56f000au, 0x041a0b04u, 0xe0702000u, 0x80010a01u, 0xbf810000u, +}; + struct ScaleVector { std::uint32_t s0, s1, s2, flushed, kept, vcc; }; @@ -621,6 +627,39 @@ constexpr DivisionVector DivisionVectors[] = { {0x2210592fu, 0x0ac8fdd3u, 0x56b7dabeu}, }; +struct FmasDenormalVector { + std::uint32_t s0, s1, s2, vcc, flushed, kept; +}; + +constexpr FmasDenormalVector FmasDenormalVectors[] = { + {0x00000001u, 0x80800000u, 0x1fea6146u, 1u, 0x00000000u, 0x003a9851u}, + {0x8da51d6fu, 0x96a2a07au, 0x2004bac1u, 1u, 0x00000000u, 0x00425d61u}, + {0xbec6b7ecu, 0x20000000u, 0x00800000u, 1u, 0x80000000u, 0x8018d6fdu}, + {0x00000001u, 0x6a2932fau, 0x00400000u, 1u, 0x00000000u, 0x002a4cbfu}, + {0x0becd7b0u, 0x2535a659u, 0x80000001u, 0u, 0x80000000u, 0x80000001u}, + {0x20000000u, 0x08fe0996u, 0x807fffffu, 0u, 0x80000000u, 0x807fffffu}, + {0x9d1996e9u, 0x03feab54u, 0x00400000u, 0u, 0x00000000u, 0x00400000u}, + {0x80000000u, 0x9d47e6bfu, 0x00000001u, 0u, 0x00000000u, 0x00000001u}, + {0x32d351d2u, 0x1f800000u, 0x9f800000u, 1u, 0x80000000u, 0x80200000u}, + {0x1452d51eu, 0xb9246da0u, 0x9682e357u, 1u, 0x80000000u, 0x80000008u}, + {0x84b5a818u, 0x2de2cedcu, 0x1f800000u, 1u, 0x00000000u, 0x00200000u}, + {0x149e259bu, 0x80800000u, 0x1a3dc795u, 1u, 0x00000000u, 0x000005eeu}, + {0xff800000u, 0x00000001u, 0x1e8b9c7bu, 0u, 0xff800000u, 0xff800000u}, + {0x096fea1fu, 0x40000000u, 0x00400000u, 0u, 0x09efea2fu, 0x09efea2fu}, + {0xb8c9817au, 0x0adcc633u, 0x007fffffu, 0u, 0x842cc747u, 0x842cc747u}, + {0x807fffffu, 0xff7fffffu, 0x3f000000u, 0u, 0x408fffffu, 0x408fffffu}, + {0xff800000u, 0x00000001u, 0x1e8b9c7bu, 1u, 0xff800000u, 0xff800000u}, + {0x807fffffu, 0xff7fffffu, 0x3f000000u, 1u, 0x208fffffu, 0x208fffffu}, + {0x802927c2u, 0x7f7fffffu, 0x80000000u, 1u, 0x9fa49f07u, 0x9fa49f07u}, + {0x007fffffu, 0xde01282au, 0x25a1ba53u, 1u, 0x05a1b64au, 0x05a1b64au}, + {0x00400000u, 0x14afa671u, 0x807fffffu, 0u, 0x80000000u, 0x807fffffu}, + {0x95928216u, 0x80000001u, 0x807fffffu, 0u, 0x80000000u, 0x807fffffu}, + {0x807fffffu, 0x3f800000u, 0x00000000u, 0u, 0x80000000u, 0x807fffffu}, + {0x9e2d4ac9u, 0x80000000u, 0x80000001u, 0u, 0x80000000u, 0x80000001u}, + {0x007fffffu, 0xff800000u, 0x15e81bf3u, 0u, 0xff800000u, 0xff800000u}, + {0x007fffffu, 0xff800000u, 0x15e81bf3u, 1u, 0xff800000u, 0xff800000u}, +}; + struct FmasNanVector { std::uint32_t s0, s1, s2, noIeee, ieee; }; @@ -773,6 +812,30 @@ void CheckFmasNan(AgcDriver::VulkanDevice& device, const std::optional(spv::CapabilityFloat64)) != capabilities.end(); +} + +void CheckFmasDenormals(AgcDriver::VulkanDevice& device) { + static_assert(std::size(FmasDenormalVectors) <= Threads); + for (const std::uint32_t mode : {0xc0u, 0xd0u, 0xe0u, 0xf0u}) { + Shader shader(device, FmasCode, ShaderRecompiler::ShaderFloatMode{mode, true, false, false}); + Input.fill(0u); + for (std::uint32_t lane = 0; lane < std::size(FmasDenormalVectors); ++lane) { + const auto& vector = FmasDenormalVectors[lane]; + std::copy_n(std::array{vector.s0, vector.s1, vector.s2, vector.vcc}.begin(), 4, Input.begin() + lane * Stride); + } + shader.Run(); + for (std::uint32_t lane = 0; lane < std::size(FmasDenormalVectors); ++lane) { + const auto& vector = FmasDenormalVectors[lane]; + const auto actual = Output[lane * Stride]; + const auto expected = ((mode >> 4u) & 2u) != 0u ? vector.kept : vector.flushed; + Require(actual == expected, "division fmas denormals: FLOAT_MODE " + Hex(mode) + " (" + Hex(vector.s0) + ", " + Hex(vector.s1) + ", " + Hex(vector.s2) + ") vcc " + std::to_string(vector.vcc) + ": v_div_fmas_f32 is " + Hex(actual) + ", expected " + Hex(expected)); + } + } +} + void CheckDivision(AgcDriver::VulkanDevice& device) { Shader shader(device, DivisionCode); constexpr std::uint32_t count = sizeof(DivisionVectors) / sizeof(DivisionVectors[0]); @@ -805,6 +868,11 @@ int main() { CheckFmasNan(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}); CheckFmasNan(*device, ShaderRecompiler::ShaderFloatMode{0xf0u, false, false, false}); CheckFmasNan(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, false, true, false}); + if (HasFloat64(*device)) { + CheckFmasDenormals(*device); + } else { + std::puts("the device has no Float64, so v_div_fmas_f32 denormal and scaling rows are not checked"); + } if (fused) { CheckDivision(*device); } else { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp index 9413952247..34895d5545 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawOffsetIndices.cpp @@ -50,6 +50,18 @@ alignas(256) constexpr std::array VertexCode{ 0xbf810000, }; +alignas(256) constexpr auto RuntimeFetchCode = [] { + std::array code{}; + std::copy_n(VertexCode.begin(), 23, code.begin()); + const std::array stores{ + 0xe0c01000, 0x80020c04, 0xe0c01004, 0x80020d04, + 0xe0c01008, 0x80020e04, 0xe0c0100c, 0x80020f04 + }; + std::copy(stores.begin(), stores.end(), code.begin() + 23); + std::copy(VertexCode.begin() + 25, VertexCode.end(), code.begin() + 31); + return code; +}(); + alignas(256) constexpr std::array PostAddCode{ 0x7e020280, 0x7e040280, 0xd70f7d05, 0x00020a10, @@ -281,7 +293,7 @@ constexpr std::uint32_t Width = 64; constexpr std::uint32_t Height = 32; alignas(256) std::array Pixels{}; alignas(256) std::array Output{}; -alignas(256) std::array VertexSlots{}; +alignas(256) std::array VertexSlots{}; alignas(256) std::array InstanceSlots{}; alignas(256) std::array BufferTable{}; alignas(256) constexpr std::array Indices{2, 0, 1, 0}; @@ -327,7 +339,7 @@ public: return userData; } - ShaderRecompiler::RecompileResult Vertex(std::span code, const std::vector& userData) const { + ShaderRecompiler::RecompileResult Vertex(std::span code, const std::vector& userData, bool runtimeFetch = false) const { ShaderRecompiler::ShaderVertexStageInfo info{}; info.resourcesNum = 2; info.resources[0].fields = {BufferTable[0], BufferTable[1], BufferTable[2], BufferTable[3]}; @@ -336,7 +348,7 @@ public: info.resourcesDst[1] = {11, 1, 1, 1}; info.fetchAttribReg = 6; info.fetchBufferReg = 4; - info.fetchEmbedded = true; + info.fetchEmbedded = !runtimeFetch; const std::array memory{{ {reinterpret_cast(code.data()), std::as_bytes(code)}, {reinterpret_cast(BufferTable.data()), std::as_bytes(std::span(BufferTable))} @@ -349,7 +361,7 @@ public: }; request.useCache = false; auto result = ShaderRecompiler::Recompile(request); - Require(result.vertexOffsetSgpr == static_cast(BaseVertexSgpr) && result.instanceOffsetSgpr == static_cast(StartInstanceSgpr), "the fetch adds of s16 and s17 were not folded into the draw (vertex " + std::to_string(result.vertexOffsetSgpr) + ", instance " + std::to_string(result.instanceOffsetSgpr) + ")"); + if (!runtimeFetch) Require(result.vertexOffsetSgpr == static_cast(BaseVertexSgpr) && result.instanceOffsetSgpr == static_cast(StartInstanceSgpr), "the fetch adds of s16 and s17 were not folded into the draw (vertex " + std::to_string(result.vertexOffsetSgpr) + ", instance " + std::to_string(result.instanceOffsetSgpr) + ")"); return result; } @@ -371,7 +383,7 @@ public: return state; } - void Draw(const ShaderRecompiler::RecompileResult& vertex, const AgcDriver::Pm4::DrawParameters& draw) { + void Draw(const ShaderRecompiler::RecompileResult& vertex, const AgcDriver::Pm4::DrawParameters& draw, bool compactRestart = false) { const auto vertexPush = static_cast(vertex.pushConstants.size()); ShaderRecompiler::ShaderPixelStageInfo pixelInfo{}; pixelInfo.wave32 = true; @@ -382,7 +394,15 @@ public: const auto pixel = ShaderRecompiler::Recompile(pixelRequest); const std::array shaders{{{ShaderStage::Vertex, &vertex, 0u}, {ShaderStage::Fragment, &pixel, PixelPushOffset(vertexPush, device->Target())}}}; Output.fill(Unwritten); - device->Draw(State(), draw, shaders); + if (compactRestart) { + auto context = device->graphicsContext(); + context.primitiveListRestart = false; + auto state = State(); + state.primitiveRestart = true; + AgcDriver::Graphics::Draw(context, state, draw, shaders); + } else { + device->Draw(State(), draw, shaders); + } device->WaitIdle(); } @@ -480,6 +500,25 @@ std::string Analysis(Fixture& fixture) { return failures; } +std::string RuntimeFetch(Fixture& fixture, std::uint32_t index, bool compactRestart) { + const auto userData = fixture.UserData(0, 0); + const auto vertex = fixture.Vertex(RuntimeFetchCode, userData, true); + Require(vertex.vertexAttributes.empty(), "runtime fetch must not become fixed vertex attributes"); + VertexSlots.fill(0); + InstanceSlots.fill(0); + alignas(256) static std::array indices{}; + indices = {0xffffffffu, index, index, index, 0xffffffffu}; + const auto first = compactRestart ? 0u : 1u; + const auto count = compactRestart ? 5u : 3u; + const AgcDriver::Pm4::DrawParameters draw{reinterpret_cast(indices.data() + first), count, 4, 1, 0, true, 0, 0}; + fixture.Draw(vertex, draw, compactRestart); + std::cout << "runtime fetch, index " << index << ", compact restart " << compactRestart + << ", attributes " << vertex.vertexAttributes.size() << ": atomic readback " + << Output[0] << ' ' << Output[1] << ' ' << Output[2] << ' ' << Output[3] << '\n'; + const std::array expected{index, 0, index, 0}; + return std::equal(expected.begin(), expected.end(), Output.begin()) ? "" : "runtime-fetched VertexIndex was changed\n"; +} + std::string Run(Fixture& fixture) { std::string failures = Analysis(fixture); const std::array autoIndices{0, 1, 2}; @@ -587,12 +626,22 @@ std::string Run(Fixture& fixture) { } -int main() { +int main(int argc, char** argv) { try { std::shared_ptr device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; Fixture fixture(device); - const auto failures = Run(fixture); + std::string failures; + if (argc == 1) failures = Run(fixture); + else { + Require(argc == 3 || argc == 4, "expected runtime-fetch INDEX [compact-restart]"); + Require(std::string(argv[1]) == "runtime-fetch", "unknown case"); + const auto index = std::stoul(argv[2]); + Require(index < VertexSlots.size(), "index exceeds the runtime input buffer"); + const bool compactRestart = argc == 4; + if (compactRestart) Require(std::string(argv[3]) == "compact-restart", "unknown index path"); + failures = RuntimeFetch(fixture, static_cast(index), compactRestart); + } if (!failures.empty()) { std::cerr << "vertex and instance indices differ from the guest's:\n" << failures; return 1; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DrawRecaptureOnChange.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawRecaptureOnChange.cpp new file mode 100644 index 0000000000..7f23eaed72 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawRecaptureOnChange.cpp @@ -0,0 +1,253 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.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 + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t TableOffset = 0x300; +constexpr std::size_t MovedOffset = 0x800; +constexpr std::size_t ShiftOffset = 0x900; +constexpr std::size_t StillOffset = 0xa00; +constexpr std::size_t ColorBytes = 65536; +constexpr std::uint32_t RenderTargetTileMode = 0x1b; + +alignas(256) constexpr std::array VertexCode{ + 0x7e000d05u, 0x080200f4u, 0x10020300u, 0x100202f4u, 0x060202f3u, 0x060400f3u, 0x10040500u, 0x060404f3u, + 0x7e060280u, 0x7e0802f2u, + 0xf4080104u, 0xfa000000u, 0xf4080204u, 0xfa000010u, 0xf4080304u, 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, +}; + +alignas(256) constexpr std::array PixelRead{0x7e1a0280u, 0xdc308000u, 0x0c02000du, 0xbf8c3f70u, 0x7e10030cu, 0x7e120280u, 0x7e140280u, 0x7e160280u, 0xf800180fu, 0x0b0a0908u, 0xbf810000u}; + +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, "draw recapture: 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 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; +}; + +std::array VertexDescriptor(std::uint64_t address) { + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (16u << 16u), 3u, 0x0104dfacu}; +} + +void WriteVertices(const GuestBlock& block, std::size_t offset, float x) { + for (std::size_t vertex = 0; vertex < 3; ++vertex) { + const std::array value{x, 0.0f, 0.0f, 0.0f}; + std::memcpy(block.Bytes(offset + vertex * 16u), value.data(), sizeof(value)); + } +} + +void WriteDescriptor(const GuestBlock& block, std::size_t slot, std::array descriptor) { + std::memcpy(block.Bytes(TableOffset + slot * 16u), descriptor.data(), sizeof(descriptor)); +} + +const GuestBlock* TableBlock = nullptr; +std::atomic TableChangeArmed{false}; +std::atomic TableChanges{0}; + +void ChangeTableBeforeCheck() { + if (!TableChangeArmed.exchange(false)) return; + WriteDescriptor(*TableBlock, 0, VertexDescriptor(TableBlock->At(MovedOffset))); + TableChanges.fetch_add(1); +} + +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 SubmitDraw(const GuestBlock& vertices, const GuestBlock& data, const GuestBlock& target) { + const auto color = target.At(0); + const auto vertex = reinterpret_cast(VertexCode.data()); + const auto pixel = reinterpret_cast(PixelRead.data()); + const auto table = vertices.At(TableOffset); + const auto pointer = data.At(0); + const float scale = 32.0f; + 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, 0x9000000u | (RenderTargetTileMode << 14u)}, {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(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; + Append(commands, 0x79, {0x242, 4}); + for (const auto& [offset, value] : context) Append(commands, 0x69, {offset, value}); + for (const auto& [offset, value] : std::initializer_list>{{0xc8, static_cast(vertex >> 8u)}, {0xc9, static_cast(vertex >> 40u)}, {0x8b, 2u << 1u}, {0x8c, static_cast(table)}, {0x8d, static_cast(table >> 32u)}, {0x00b, 8u << 1u}, {0x00c, 0}, {0x00d, 0}}) Append(commands, 0x76, {offset, value}); + Append(commands, 0x76, {0x008, static_cast(pixel >> 8u), static_cast(pixel >> 40u)}); + Append(commands, 0x76, {0x00e, static_cast(pointer), static_cast(pointer >> 32u)}); + Append(commands, 0x2d, {3, 2}); + Packet packet{commands.data(), static_cast(commands.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&packet) == 0, "draw recapture: the draw submission failed"); + AgcDriverWaitIdle_nid_postfix(); +} + +unsigned Brightest(const GuestBlock& target) { + AgcDriver::GuestMemory::FlushGpuWrites(static_cast(target.At(0)), ColorBytes); + unsigned brightest = 0; + for (std::size_t i = 0; i < ColorBytes; ++i) brightest = std::max(brightest, static_cast(target.Bytes(0)[i])); + return brightest; +} + +void Expect(bool condition, const std::string& what) { + if (!condition) throw std::runtime_error("draw recapture: " + what); +} + +void ChangedTableIsBound(const GuestBlock& vertices, const GuestBlock& data, const GuestBlock& target) { + std::memset(target.Bytes(0), 0, ColorBytes); + WriteDescriptor(vertices, 0, {}); + const auto retries = AgcDriver::DriverDetail::captureChangeRetries.load(); + TableChangeArmed.store(true); + SubmitDraw(vertices, data, target); + const auto brightest = Brightest(target); + const auto redone = AgcDriver::DriverDetail::captureChangeRetries.load() - retries; + std::printf("a draw whose descriptor table changed from a zero V# to a valid one after its capture: the table changed %d time(s), brightest color byte %u (%s), %llu packet(s) prepared again\n", TableChanges.load(), brightest, brightest == 255u ? "the valid V# was bound" : "the zero V# captured before the change was bound", static_cast(redone)); + Expect(TableChanges.load() == 1, "the check seam did not run before the draw was recorded"); + Expect(brightest == 255u, "the draw bound the zero V# captured before the table changed, not the valid V# in memory when it was recorded"); + Expect(redone == 1, "the draw was prepared again " + std::to_string(redone) + " times, not once"); +} + +void UnchangedTableIsNotRedone(const GuestBlock& vertices, const GuestBlock& data, const GuestBlock& target) { + std::memset(target.Bytes(0), 0, ColorBytes); + const auto retries = AgcDriver::DriverDetail::captureChangeRetries.load(); + SubmitDraw(vertices, data, target); + const auto brightest = Brightest(target); + const auto redone = AgcDriver::DriverDetail::captureChangeRetries.load() - retries; + std::printf("the same draw with its table unchanged: brightest color byte %u, %llu packet(s) prepared again\n", brightest, static_cast(redone)); + Expect(brightest == 255u, "the draw with an unchanged valid table did not draw"); + Expect(redone == 0, "a draw whose captured memory did not change was prepared again"); +} + +} + +int main() { + try { + { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + } + Register(VertexHeader, VertexCode.data(), sizeof(VertexCode), 2); + Register(PixelHeader, PixelRead.data(), sizeof(PixelRead), 1); + GuestBlock vertices; + GuestBlock data; + GuestBlock target; + WriteVertices(vertices, MovedOffset, -8.0f); + WriteVertices(vertices, ShiftOffset, 8.0f); + WriteVertices(vertices, StillOffset, 0.0f); + WriteDescriptor(vertices, 1, VertexDescriptor(vertices.At(ShiftOffset))); + WriteDescriptor(vertices, 2, VertexDescriptor(vertices.At(StillOffset))); + const float one = 1.0f; + std::memcpy(data.Bytes(0), &one, sizeof(one)); + TableBlock = &vertices; + AgcDriver::DriverDetail::Driver::SetCaptureCheckHook(&ChangeTableBeforeCheck); + ChangedTableIsBound(vertices, data, target); + UnchangedTableIsNotRedone(vertices, data, target); + AgcDriver::DriverDetail::Driver::SetCaptureCheckHook(nullptr); + AgcDriverShutdown_nid_postfix(); + std::puts("draw recapture: a draw whose captured descriptor table changed before it was recorded binds the table in memory"); + 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/DrawWriteRanges.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DrawWriteRanges.cpp index d45a4cc045..a9be10492e 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/DrawWriteRanges.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DrawWriteRanges.cpp @@ -87,6 +87,23 @@ int main() { Require(bounded, "a dispatch whose written images are a real one and a null one is not bounded"); Require(Contains(dispatchRanges, written, written + surfaceBytes), "a written image next to a null one is not a dispatch write range"); Require(!Touches(dispatchRanges, 0, surfaceBytes), "a null storage image is a dispatch write range"); + auto typeZero = ImageWords(0x500000000ull, 256, 128); + typeZero[3] &= 0x0fffffffu; + auto typeOne = typeZero; + typeOne[3] |= 1u << 28u; + const std::array reported{0x04efd000u, 0x00040011u, 0x00017a00u, 0x00005204u, 0x0af00000u, 0u, 0x00000612u, 0x00fff000u}; + for (const auto& [words, base] : {std::pair{typeZero, 0x500000000ull}, std::pair{typeOne, 0x500000000ull}, std::pair{reported, 0x1104efd00000ull}}) { + std::copy(words.begin(), words.end(), program.bindings[0].guestDescriptor.begin() + 8); + for (const bool exact : {false, true}) { + const auto ranges = DrawWriteRanges(state, stages, exact); + Require(Contains(ranges, written, written + surfaceBytes), "a written image next to a TYPE 0 or 1 one is not a write range"); + Require(!Touches(ranges, base, base + surfaceBytes), "a TYPE 0 or 1 storage image is a write range"); + } + bool typedBounded = false; + const auto typedRanges = AgcDriver::DriverDetail::DispatchWriteRanges(program, typedBounded); + Require(typedBounded && Contains(typedRanges, written, written + surfaceBytes), "a dispatch whose written images are a real one and a TYPE 0 or 1 one lost the real one"); + Require(!Touches(typedRanges, base, base + surfaceBytes), "a TYPE 0 or 1 storage image is a dispatch write range"); + } AgcDriver::Graphics::State targets{}; targets.hasColorTarget = true; auto& color = targets.color; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp index 939ecca409..2ff72ba842 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp @@ -185,7 +185,7 @@ bool IsNan16(std::uint32_t value) { return (value & 0x7fffu) > 0x7c00u; } -void Run(AgcDriver::VulkanDevice& device, const std::optional& floatMode) { +void Run(AgcDriver::VulkanDevice& device, const std::optional& floatMode, std::span code = Code) { for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); Output.fill(0xdeadbeefu); std::vector userData(8, 0u); @@ -193,7 +193,6 @@ void Run(AgcDriver::VulkanDevice& device, const std::optional(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{ @@ -235,6 +234,27 @@ int main() { Check(ExpectedNoIeee, "IEEE=0"); Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}); Check(ExpectedIeee, "IEEE=1"); + for (const std::uint32_t factor : {0x40000000u, 0x3f000000u, 0u, 0x7f800000u}) { + std::vector code(Code.begin(), Code.end()); + code[24] = (code[24] & ~0x1ffu) | 0xffu; + code.insert(code.begin() + 25, factor); + for (const bool ieee : {false, true}) { + Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, ieee, false}, code); + for (std::uint32_t tid = 0u; tid < Threads; ++tid) { + const std::uint32_t source = Rows[tid][1]; + std::uint32_t expected = source; + if (IsNan32(source)) { + if (ieee) expected |= 0x00400000u; + } else { + const std::uint32_t input = (source & 0x7fffffffu) < 0x00800000u ? source & 0x80000000u : source; + const std::uint32_t product = std::bit_cast(std::bit_cast(factor) * std::bit_cast(input)); + expected = IsNan32(product) ? 0xffc00000u : product; + if ((expected & 0x7fffffffu) < 0x00800000u) expected &= 0x80000000u; + } + Expect(tid, Output[tid * Results + 3u], expected, "literal multiplication"); + } + } + } } std::puts("f32 arithmetic nan tests passed"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/FillCompletionLabels.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/FillCompletionLabels.cpp new file mode 100644 index 0000000000..b6326f7414 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/FillCompletionLabels.cpp @@ -0,0 +1,186 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.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 + +namespace { + +using AgcDriver::Graphics::Recorder; +using AgcDriver::Graphics::Require; + +constexpr std::size_t BlockBytes = 65536; +constexpr std::size_t FillOffset = 4096; + +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, "cannot allocate the fill test 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::uint64_t Address() const { return reinterpret_cast(block); } + const std::uint32_t* Words() const { return block; } + +private: + std::uint32_t* block = nullptr; +}; + +void CheckFill(const GuestBlock& block, std::size_t bytes, const std::array& pattern) { + for (std::size_t i = 0; i < bytes / 4; ++i) { + Require(block.Words()[FillOffset / 4 + i] == pattern[i % 4], "the completed fill contains stale label bytes"); + } +} + +void Prepare(AgcDriver::VulkanDevice& device, const GuestBlock& block) { + const std::array zero{}; + Require(device.FillBuffer(block.Address(), BlockBytes, zero), "the device must import the fill test block"); + device.WaitIdle(); + Require(device.FillBuffer(block.Address() + BlockBytes - 16, 16, zero), "the preceding GPU work was not recorded"); +} + +void GpuLabel(AgcDriver::VulkanDevice& device, const GuestBlock& block, std::size_t fillBytes, const std::array& pattern, std::size_t labelBytes, std::int64_t offset, bool submitted) { + Prepare(device, block); + auto* recorder = Recorder::Active(); + Require(recorder != nullptr, "the Vulkan recorder is not active"); + const auto fill = block.Address() + FillOffset; + const auto address = static_cast(static_cast(fill) + offset); + const std::vector label(labelBytes / 4, 0x5a5a5a5au); + Require(device.WriteLabelOnGpu(address, std::as_bytes(std::span(label)), 10, 0, false) == 0, "the label was not stored on the GPU"); + Recorder::CloseLabelGroup(AgcDriver::GuestMemory::TrackerGeneration()); + if (submitted) device.SubmitRecorded(false); + const auto ran = std::make_shared(false); + Require(device.AfterRecordedWork([ran] { *ran = true; }, false), "the unrelated completion was not queued"); + Require(Recorder::PendingCompletionLabels() != 0 && recorder->PendingLabelIn(address, labelBytes), "the label and unrelated completion are not pending"); + Require(!recorder->CompletionLabelIn(address, labelBytes), "a GPU label requires a CPU completion"); + if (labelBytes <= Recorder::LabelTableBytes) { + Recorder::LabelRefusal refusal{}; + const auto before = Recorder::LookupLabel(address, 4, 10, &refusal); + Require(Recorder::LateTrust() ? before && before->late && before->value == label[0] : !before && refusal == Recorder::LabelRefusal::TrustOff, "the GPU label's initial late lookup is wrong"); + } + const auto syncs = AgcDriver::VulkanDevice::FillCompletionSyncCount(); + Require(device.FillBuffer(fill, fillBytes, pattern), "the fill over the GPU label was not recorded"); + Require(AgcDriver::VulkanDevice::FillCompletionSyncCount() == syncs, "a GPU-ordered label incremented the fill completion-wait counter"); + Require(!*ran, "the fill drained an unrelated CPU completion because of a GPU label"); + const bool overlaps = address < fill + fillBytes && address + labelBytes > fill; + if (labelBytes <= Recorder::LabelTableBytes) { + const auto probe = std::max(address, fill); + Recorder::LabelRefusal refusal{}; + const auto hit = Recorder::LookupLabel(overlaps ? probe : address, 4, 10, &refusal); + if (!Recorder::LateTrust()) { + Require(!hit && refusal == Recorder::LabelRefusal::TrustOff, "the fill enabled a disabled late label shortcut"); + } else { + Require(overlaps ? !hit && refusal == Recorder::LabelRefusal::Overwritten : hit && hit->value == label[0], "a later submission trusted an overwritten label or lost a disjoint one"); + } + const auto early = Recorder::LookupLabel(address, 4, 9); + Require(early && !early->late && early->value == label[0], "the fill lost the earlier submission's ordered label"); + if (labelBytes >= 8) { + const auto pair = Recorder::LookupLabel(address, 8, 10, &refusal); + if (!Recorder::LateTrust()) { + Require(!pair && refusal == Recorder::LabelRefusal::TrustOff, "a composed label enabled a disabled shortcut"); + } else { + Require(overlaps ? !pair && refusal == Recorder::LabelRefusal::Overwritten : pair && pair->value == 0x5a5a5a5a5a5a5a5aull, "a composed label trusted bytes overwritten by the fill"); + } + } + } + device.WaitIdle(); + Require(*ran && Recorder::PendingCompletionLabels() == 0, "the unrelated completion did not retire"); + CheckFill(block, fillBytes, pattern); + for (std::size_t i = 0; i < label.size(); ++i) { + const auto word = address + i * 4; + if (word < fill || word >= fill + fillBytes) { + Require(block.Words()[(word - block.Address()) / 4] == label[i], "the fill changed a label outside its range"); + } + } + Require(!Recorder::LookupLabel(address, 4, 9), "a label outlived its completed batch"); +} + +void CpuLabel(AgcDriver::VulkanDevice& device, const GuestBlock& block, const std::array& pattern, bool overlaps) { + Prepare(device, block); + auto* recorder = Recorder::Active(); + Require(recorder != nullptr, "the Vulkan recorder is not active"); + const auto fill = block.Address() + FillOffset; + const std::uint32_t value = 0x5a5a5a5au; + const auto bytes = std::as_bytes(std::span(&value, 1)); + Require(device.WriteLabelOnGpu(fill, bytes, 10, 0, false) == 0, "the preceding GPU label was not recorded"); + const auto address = overlaps ? fill + 12 : fill - 4; + recorder->AfterCompletions(address, bytes, 11, 0, false); + const auto ran = std::make_shared(false); + Require(device.AfterRecordedWork([ran] { *ran = true; }, false), "the completion observer was not queued"); + Require(recorder->CompletionLabelIn(address, 4), "the CPU label is not pending"); + const auto syncs = AgcDriver::VulkanDevice::FillCompletionSyncCount(); + Require(device.FillBuffer(fill, 4096, pattern), "the fill following the CPU label was not recorded"); + Require(AgcDriver::VulkanDevice::FillCompletionSyncCount() == syncs + (overlaps ? 1 : 0), "the fill completion-wait counter did not match CPU-label overlap"); + Require(*ran == overlaps, "the fill did not wait exactly for an overlapping CPU completion"); + device.WaitIdle(); + CheckFill(block, 4096, pattern); + if (!overlaps) Require(block.Words()[FillOffset / 4 - 1] == value, "the disjoint CPU label did not land"); +} + +} + +int main(int argc, char** argv) { + try { + GuestBlock block; + auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + const std::array uniform{0x12345678u, 0x12345678u, 0x12345678u, 0x12345678u}; + const std::array pattern{0x11223344u, 0x55667788u, 0x99aabbccu, 0xddeeff00u}; + const auto mode = argc == 2 ? std::string_view(argv[1]) : std::string_view{}; + Require(mode.empty() || mode == "--submitted" || mode == "--cpu-overlap" || mode == "--cpu-disjoint", "unknown fill test mode"); + for (const auto& fill : {uniform, pattern}) { + if (mode == "--cpu-overlap" || mode == "--cpu-disjoint") { + CpuLabel(*device, block, fill, mode == "--cpu-overlap"); + } else { + for (const auto bytes : {16u, 4096u}) { + for (const auto labelBytes : {4u, 8u, 64u, 256u}) { + GpuLabel(*device, block, bytes, fill, labelBytes, 0, mode == "--submitted"); + GpuLabel(*device, block, bytes, fill, labelBytes, -4, mode == "--submitted"); + GpuLabel(*device, block, bytes, fill, labelBytes, bytes - 4, mode == "--submitted"); + GpuLabel(*device, block, bytes, fill, labelBytes, bytes, mode == "--submitted"); + } + } + } + } + std::cout << "fill completion label 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/execution/Float16UnusedNanPayloads.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Float16UnusedNanPayloads.cpp new file mode 100644 index 0000000000..190a33208a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Float16UnusedNanPayloads.cpp @@ -0,0 +1,248 @@ +#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 + +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 = 8; +constexpr std::uint32_t Halves = 0x10000u; +constexpr std::uint32_t PackedGroups = Halves / (Threads * 2u); +constexpr std::uint32_t UnaryGroups = Halves / Threads; +constexpr std::uint32_t Rows = UnaryGroups * Threads; +std::vector Input(Rows * Inputs); +std::vector Output(Rows * Results); + +alignas(256) constexpr std::array PackedCode{ + 0x34020084u, 0x34060085u, 0x8f098a08u, 0x8f0a8b08u, 0x4a020209u, 0x4a06060au, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, + 0xcc0f400au, 0x18020b04u, + 0xcc10400bu, 0x18020b04u, + 0xcc0e400cu, 0x1c1a0b04u, + 0xcc0f4a0du, 0x30020b04u, + 0xcc10c00eu, 0x18020d04u, + 0xcc0e330fu, 0x8c1a0b04u, + 0x7e200306u, 0x78200b04u, + 0xcc0ec011u, 0x3c1a0f04u, + 0xe0781000u, 0x80010a03u, 0xe0781010u, 0x80010e03u, 0xbf810000u, +}; + +constexpr const char* PackedNames[Results] = { + "v_pk_add_f16 v10, v4, v5", + "v_pk_mul_f16 v11, v4, v5", + "v_pk_fma_f16 v12, v4, v5, v6", + "v_pk_add_f16 v13, v4, v5 op_sel:[1,0] op_sel_hi:[0,1] neg_lo:[1,0] neg_hi:[0,1]", + "v_pk_mul_f16 v14, v4, v6 clamp", + "v_pk_fma_f16 v15, v4, v5, v6 op_sel:[0,1,1] op_sel_hi:[1,0,0] neg_lo:[0,0,1] neg_hi:[1,1,0]", + "v_pk_fmac_f16 v16, v4, v5", + "v_pk_fma_f16 v17, v4, v7, v6 neg_lo:[1,0,0] clamp", +}; + +constexpr std::array UnaryOpcodes{0x54u, 0x55u, 0x56u, 0x57u, 0x58u, 0x5fu, 0x60u, 0x61u}; + +constexpr const char* UnaryNames[Results] = { + "v_rcp_f16", "v_sqrt_f16", "v_rsq_f16", "v_log_f16", "v_exp_f16", "v_fract_f16", "v_sin_f16", "v_cos_f16", +}; + +constexpr std::array MakeUnaryCode() { + std::array code{0x34020084u, 0x34060085u, 0x8f098a08u, 0x8f0a8b08u, 0x4a020209u, 0x4a06060au, 0xe0381000u, 0x80000401u, 0xbf8c3f70u}; + for (std::uint32_t index = 0; index < Results; ++index) { + code[9u + index] = 0x7e000280u | ((10u + index) << 17u) | (1u << 9u); + code[17u + index] = 0x7e000104u | ((10u + index) << 17u) | (UnaryOpcodes[index] << 9u); + } + code[25u] = 0xe0781000u; + code[26u] = 0x80010a03u; + code[27u] = 0xe0781010u; + code[28u] = 0x80010e03u; + code[29u] = 0xbf810000u; + return code; +} + +alignas(256) constexpr std::array UnaryCode = MakeUnaryCode(); + +struct Mode { + const char* name; + std::optional floatMode; +}; + +const std::array PackedModes{{ + {"no FLOAT_MODE", std::nullopt}, + {"dx10 ieee ovfl", ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, true}}, + {"dx10 ieee", ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}}, + {"dx10 ovfl", ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, true}}, + {"dx10", ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}}, + {"ieee ovfl", ShaderRecompiler::ShaderFloatMode{0xc0u, false, true, true}}, + {"ieee", ShaderRecompiler::ShaderFloatMode{0xc0u, false, true, false}}, + {"ovfl", ShaderRecompiler::ShaderFloatMode{0xc0u, false, false, true}}, + {"f16 denormals flushed", ShaderRecompiler::ShaderFloatMode{0x00u, true, true, false}}, +}}; + +const std::array UnaryModes{{ + {"no FLOAT_MODE", std::nullopt}, + {"dx10 ieee", ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}}, + {"dx10", ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}}, + {"f16 denormals flushed", ShaderRecompiler::ShaderFloatMode{0x00u, true, true, false}}, +}}; + +constexpr std::array Specials{ + 0x0000u, 0x8000u, 0x0001u, 0x8001u, 0x03ffu, 0x0400u, 0x3c00u, 0xbc00u, 0x3555u, 0x4000u, 0xc400u, 0x7bffu, 0xfbffu, + 0x7c00u, 0xfc00u, 0x7c01u, 0xfc01u, 0x7d55u, 0xfdffu, 0x7e00u, 0xfe00u, 0x7e55u, 0xfeaau, 0x7fffu, 0xffffu, 0x5bffu, +}; + +std::uint64_t Seed = 0x9e3779b97f4a7c15ull; + +std::uint32_t Next() { + Seed += 0x9e3779b97f4a7c15ull; + std::uint64_t z = Seed; + z = (z ^ (z >> 30u)) * 0xbf58476d1ce4e5b9ull; + z = (z ^ (z >> 27u)) * 0x94d049bb133111ebull; + return static_cast(z ^ (z >> 31u)); +} + +std::uint32_t Other() { + const std::uint32_t bits = Next(); + return (bits & 1u) != 0u ? Specials[(bits >> 1u) % Specials.size()] : (bits >> 16u); +} + +bool IsNan(std::uint32_t half) { + return (half & 0x7fffu) > 0x7c00u; +} + +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 CountOpcode(std::span spirv, std::uint32_t wanted) { + std::uint32_t count = 0u; + for (std::size_t offset = 5u; offset < spirv.size();) { + const std::uint32_t words = spirv[offset] >> 16u; + Require(words != 0u && offset + words <= spirv.size(), "f16 unused NaN payloads: malformed SPIR-V"); + if ((spirv[offset] & 0xffffu) == wanted) ++count; + offset += words; + } + return count; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto target = device->Target(); + const auto supports = [&](spv::Capability capability) { + return std::ranges::find(target.supportedCapabilities, static_cast(capability)) != target.supportedCapabilities.end(); + }; + if (!supports(spv::CapabilityFloat16) || !supports(spv::CapabilityRoundingModeRTE) || !supports(spv::CapabilityDenormPreserve) || !target.float16DenormPreserve) { + std::puts("f16 unused NaN payloads: skipped, the device has no native f16 conversions with RTE rounding and kept f16 denormals"); + return VulkanTestSkipped; + } + std::vector withoutFloat16; + std::ranges::copy_if(target.supportedCapabilities, std::back_inserter(withoutFloat16), [](std::uint32_t capability) { return capability != spv::CapabilityFloat16; }); + auto softwareTarget = target; + softwareTarget.supportedCapabilities = withoutFloat16; + softwareTarget.float16DenormPreserve = false; + + 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 ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {true, false, false}, false, 1}; + const auto compile = [&](std::span code, const ShaderRecompiler::SpirvTarget& shaderTarget, const Mode& mode) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + shaderTarget, + {0, 0, 0, 128} + }; + request.useCache = false; + request.context.floatMode = mode.floatMode; + return ShaderRecompiler::Recompile(request); + }; + const auto run = [&](const auto& result, std::span code, std::uint32_t groups, std::uint32_t unwritten) { + std::ranges::fill(Output, unwritten); + device->Dispatch(result, groups, 1, 1, {}, reinterpret_cast(code.data())); + device->WaitIdle(); + return Output; + }; + const auto compare = [&](const char* group, const Mode& mode, const char* const* names, const auto& fast, const auto& software, std::uint32_t rows) { + std::uint64_t nanResults = 0u; + for (std::uint32_t tid = 0; tid < rows; ++tid) { + const std::uint32_t* row = &Input[tid * Inputs]; + for (std::uint32_t index = 0; index < Results; ++index) { + const std::uint32_t actual = fast[tid * Results + index]; + const std::uint32_t expected = software[tid * Results + index]; + if (actual != expected) Require(false, std::string("f16 unused NaN payloads: ") + group + ", " + mode.name + ", " + names[index] + " with v4=" + Hex(row[0]) + " v5=" + Hex(row[1]) + " v6=" + Hex(row[2]) + " v7=" + Hex(row[3]) + " is " + Hex(actual) + ", the software conversions give " + Hex(expected)); + nanResults += (IsNan(expected & 0xffffu) ? 1u : 0u) + (IsNan(expected >> 16u) ? 1u : 0u); + } + } + return nanResults; + }; + + std::uint64_t compared = 0u; + std::uint64_t nanResults = 0u; + const std::span packedCode(PackedCode); + for (const auto& mode : PackedModes) { + const auto fast = compile(packedCode, target, mode); + const auto software = compile(packedCode, softwareTarget, mode); + if (mode.floatMode.has_value() && mode.floatMode->dx10Clamp) { + Require(CountOpcode(fast.spirv.Words(), spv::OpIsNan) == 0u, std::string("f16 unused NaN payloads: packed f16, ") + mode.name + " still rebuilds the NaN of an f16 input whose payload the result never uses"); + } + constexpr std::uint32_t rows = PackedGroups * Threads; + for (std::uint32_t exhaustive = 0; exhaustive < Inputs; ++exhaustive) { + for (std::uint32_t tid = 0; tid < rows; ++tid) { + for (std::uint32_t index = 0; index < Inputs; ++index) { + const std::uint32_t low = index == exhaustive ? tid * 2u : Other(); + const std::uint32_t high = index == exhaustive ? tid * 2u + 1u : Other(); + Input[tid * Inputs + index] = low | (high << 16u); + } + } + const auto fastOutput = run(fast, packedCode, PackedGroups, 0xdeadbeefu); + const auto softwareOutput = run(software, packedCode, PackedGroups, 0xfeedfaceu); + nanResults += compare("packed f16", mode, PackedNames, fastOutput, softwareOutput, rows); + compared += rows * Results * 2u; + } + } + const std::span unaryCode(UnaryCode); + for (const auto& mode : UnaryModes) { + const auto fast = compile(unaryCode, target, mode); + const auto software = compile(unaryCode, softwareTarget, mode); + for (std::uint32_t tid = 0; tid < Rows; ++tid) { + Input[tid * Inputs] = tid | (Next() & 0xffff0000u); + for (std::uint32_t index = 1; index < Inputs; ++index) Input[tid * Inputs + index] = Next(); + } + const auto fastOutput = run(fast, unaryCode, UnaryGroups, 0xdeadbeefu); + const auto softwareOutput = run(software, unaryCode, UnaryGroups, 0xfeedfaceu); + nanResults += compare("unary f16", mode, UnaryNames, fastOutput, softwareOutput, Rows); + compared += Rows * Results; + } + std::printf("f16 unused NaN payloads tests passed (%llu results, %llu NaN halves)\n", static_cast(compared), static_cast(nanResults)); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/GcnCompareExecWrites.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GcnCompareExecWrites.cpp new file mode 100644 index 0000000000..0b5484e984 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GcnCompareExecWrites.cpp @@ -0,0 +1,224 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "RdnaDecoder/RdnaInstructionDecoder.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 + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Lanes = 64; +constexpr std::uint32_t CaseDwords = 8; +constexpr std::uint32_t CaseBytes = CaseDwords * 4; + +constexpr std::uint32_t Sgpr8 = 8; +constexpr std::uint32_t VccLo = 106; +constexpr std::uint32_t VccHi = 107; +constexpr std::uint32_t ExecLo = 126; +constexpr std::uint32_t Literal = 255; +constexpr std::uint32_t Vgpr = 256; +constexpr std::uint32_t Four = 246; +constexpr std::uint32_t MinusOne = 193; +constexpr std::uint32_t Inline(std::uint32_t value) { return 128u + value; } + +constexpr std::uint32_t SMovB32 = 0x03; +constexpr std::uint32_t SMovB64 = 0x04; +constexpr std::uint32_t SNop = 0x00; +constexpr std::uint32_t SEndpgm = 0x01; +constexpr std::uint32_t SWaitcnt = 0x0c; +constexpr std::uint32_t VMovB32 = 0x01; +constexpr std::uint32_t VCvtF32U32 = 0x06; +constexpr std::uint32_t VCmpxEqF32 = 0x12; +constexpr std::uint32_t VCmpLtU32 = 0xc1; +constexpr std::uint32_t VCmpxLtU32 = 0xd1; +constexpr std::uint32_t BufferStoreDword = 0x1c; + +constexpr std::uint32_t Sop1(std::uint32_t opcode, std::uint32_t sdst, std::uint32_t ssrc0) { + return 0xbe800000u | (sdst << 16u) | (opcode << 8u) | ssrc0; +} + +constexpr std::uint32_t Sopp(std::uint32_t opcode, std::uint32_t immediate) { + return 0xbf800000u | (opcode << 16u) | immediate; +} + +constexpr std::uint32_t Vop1(std::uint32_t opcode, std::uint32_t vdst, std::uint32_t src0) { + return 0x7e000000u | (vdst << 17u) | (opcode << 9u) | src0; +} + +constexpr std::uint32_t Vopc(std::uint32_t opcode, std::uint32_t src0, std::uint32_t vsrc1) { + return 0x7c000000u | (opcode << 17u) | (vsrc1 << 9u) | src0; +} + +std::vector Vop3Compare(std::uint32_t opcode, std::uint32_t sdst, std::uint32_t src0, std::uint32_t src1) { + return {0xd0000000u | (opcode << 17u) | sdst, src0 | (src1 << 9u)}; +} + +constexpr std::array Mubuf(std::uint32_t opcode, std::uint32_t vdata, std::uint32_t vaddr, std::uint32_t offset) { + return {0xe0000000u | (opcode << 18u) | (1u << 12u) | offset, vaddr | (vdata << 8u) | (Inline(0) << 24u)}; +} + +constexpr std::uint64_t AllLanes = ~0ull; +constexpr std::uint64_t LowHalf = 0x00000000ffffffffull; +constexpr std::uint64_t Quarters = 0x0000ffff0000ffffull; +constexpr std::uint64_t Lane4 = 0x0000000000000010ull; +constexpr std::uint64_t GcnBufferBaseLimit = 1ull << 44u; +constexpr std::uintptr_t LowAddressHint = std::uintptr_t{1} << 40u; +constexpr std::uint64_t VccPreset = 0x2222222211111111ull; +constexpr std::uint64_t SdstPreset = 0x4444444433333333ull; + +struct Case { + const char* name; + std::uint64_t exec; + std::vector compare; + std::uint64_t expectedExec; + std::uint64_t expectedVcc; + std::uint64_t expectedSdst; +}; + +const std::array Cases{{ + {"v_cmpx_lt_u32_e32 vcc, v0, 32", AllLanes, {Vopc(VCmpxLtU32, Vgpr + 0, 1)}, LowHalf, LowHalf, SdstPreset}, + {"v_cmpx_lt_u32_e64 s[8:9], v0, 32", AllLanes, Vop3Compare(VCmpxLtU32, Sgpr8, Vgpr + 0, Vgpr + 1), LowHalf, VccPreset, LowHalf}, + {"v_cmpx_eq_f32_e32 vcc, 4.0, f32(v0)", AllLanes, {Vopc(VCmpxEqF32, Four, 2)}, Lane4, Lane4, SdstPreset}, + {"v_cmpx_lt_u32_e32 vcc, v0, 64 under exec 0000ffff0000ffff", Quarters, {Vopc(VCmpxLtU32, Vgpr + 0, 4)}, Quarters, Quarters, SdstPreset}, + {"v_cmpx_lt_u32_e64 s[8:9], v0, 64 under exec 0000ffff0000ffff", Quarters, Vop3Compare(VCmpxLtU32, Sgpr8, Vgpr + 0, Vgpr + 4), Quarters, VccPreset, Quarters}, + {"v_cmpx_lt_u32_e64 vcc, v0, 32", AllLanes, Vop3Compare(VCmpxLtU32, VccLo, Vgpr + 0, Vgpr + 1), LowHalf, LowHalf, SdstPreset}, + {"v_cmp_lt_u32_e64 s[8:9], v0, 32", AllLanes, Vop3Compare(VCmpLtU32, Sgpr8, Vgpr + 0, Vgpr + 1), AllLanes, VccPreset, LowHalf}, +}}; + +constexpr std::size_t OutputDwords = Cases.size() * CaseDwords; + +std::span LowOutput() { + constexpr std::size_t bytes = 65536; +#ifdef _WIN32 + void* block = VirtualAlloc(reinterpret_cast(LowAddressHint), bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (block == nullptr) block = VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); +#else + void* block = mmap(reinterpret_cast(LowAddressHint), bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (block == MAP_FAILED) block = nullptr; +#endif + Require(block != nullptr && reinterpret_cast(block) + bytes <= GcnBufferBaseLimit, "GCN compare EXEC writes: cannot allocate the output below the 44-bit GCN buffer base limit"); + return {static_cast(block), OutputDwords}; +} + +std::vector Program() { + std::vector code; + const auto emit = [&](auto... words) { (code.push_back(words), ...); }; + const auto movConstant = [&](std::uint32_t sdst, std::uint32_t value) { emit(Sop1(SMovB32, sdst, Literal), value); }; + emit(Vop1(VMovB32, 1, Inline(32))); + emit(Vop1(VMovB32, 4, Inline(64))); + emit(Vop1(VCvtF32U32, 2, Vgpr + 0)); + movConstant(16, static_cast(Quarters)); + movConstant(17, static_cast(Quarters >> 32u)); + for (std::uint32_t index = 0; index < Cases.size(); ++index) { + const auto& item = Cases[index]; + movConstant(VccLo, static_cast(VccPreset)); + movConstant(VccHi, static_cast(VccPreset >> 32u)); + movConstant(Sgpr8, static_cast(SdstPreset)); + movConstant(Sgpr8 + 1, static_cast(SdstPreset >> 32u)); + if (item.exec != AllLanes) emit(Sop1(SMovB64, ExecLo, 16)); + for (const auto word : item.compare) emit(word); + emit(Sopp(SNop, 4)); + emit(Sop1(SMovB64, 10, ExecLo)); + emit(Sop1(SMovB64, 12, VccLo)); + emit(Sop1(SMovB64, 14, Sgpr8)); + emit(Sop1(SMovB64, ExecLo, MinusOne)); + const auto base = index * CaseBytes; + if (base <= 64u) emit(Vop1(VMovB32, 11, Inline(base))); + else emit(Vop1(VMovB32, 11, Literal), base); + for (std::uint32_t dword = 0; dword < 6; ++dword) emit(Vop1(VMovB32, 12 + dword, 10 + dword)); + for (std::uint32_t dword = 0; dword < 6; ++dword) { + const auto store = Mubuf(BufferStoreDword, 12 + dword, 11, dword * 4u); + emit(store[0], store[1]); + } + } + emit(Sopp(SWaitcnt, 0)); + emit(Sopp(SEndpgm, 0)); + return code; +} + +std::array GcnBufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x27facu}; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, std::span output, const ShaderRecompiler::SpirvTarget& target) { + std::fill(output.begin(), output.end(), 0xdeadbeefu); + const auto descriptor = GcnBufferDescriptor(output.data(), static_cast(output.size_bytes())); + const std::vector userData(descriptor.begin(), descriptor.end()); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Lanes, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {Lanes, 0, userData, compute, std::nullopt, std::nullopt, memory}, + 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(); +} + +std::string Hex(std::uint64_t value) { + char text[17]; + std::snprintf(text, sizeof(text), "%016llx", static_cast(value)); + return text; +} + +void Check(std::span output, const char* run) { + for (std::uint32_t index = 0; index < Cases.size(); ++index) { + const auto& item = Cases[index]; + const auto pair = [&](std::uint32_t dword) { + return static_cast(output[index * CaseDwords + dword]) | (static_cast(output[index * CaseDwords + dword + 1]) << 32u); + }; + const auto expect = [&](const char* what, std::uint64_t actual, std::uint64_t expected) { + Require(actual == expected, std::string("GCN compare EXEC writes ") + run + ": " + item.name + " left " + what + " " + Hex(actual) + ", expected " + Hex(expected)); + }; + expect("exec", pair(0), item.expectedExec); + expect("vcc", pair(2), item.expectedVcc); + expect("s[8:9]", pair(4), item.expectedSdst); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + if (device->Target().subgroupSize < 32u) { + std::printf("skipped, the device's subgroups are narrower than the 32 lanes a wave64 run needs (%u lanes)\n", device->Target().subgroupSize); + return VulkanTestSkipped; + } + ShaderRecompiler::SetGcnGuestShaders(true); + const auto code = Program(); + const auto output = LowOutput(); + Run(*device, code, output, device->Target()); + Check(output, "wave64"); + Run(*device, code, output, device->ComputeTarget(32)); + Check(output, "wave64 split"); + std::puts("GCN compare EXEC writes 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/GdsDmaFill.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/GdsDmaFill.cpp new file mode 100644 index 0000000000..9d55a5dedc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/GdsDmaFill.cpp @@ -0,0 +1,356 @@ +#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" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.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 { + +using AgcDriver::DriverDetail::DrawPipeline; + +constexpr std::uint32_t GdsOffset = 0x40; +constexpr std::uint32_t FillBytes = 64; +constexpr std::size_t BlockBytes = 256 * 1024; +constexpr std::uint32_t BufferFillBytes = 128 * 1024; +constexpr std::uint32_t CounterOffset = 0x10; +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Results = 4; + +constexpr std::array AppendProgram{ + 0x34060082, + 0x7e0c02ff, 0x5eed0001u, 0x7e0e02ff, 0x5eed0002u, + 0xbefc03ff, 0x0000ffff, + 0xd8fa0010, 0x04000000, + 0xd8f60014, 0x05000000, + 0xbf800000, 0xbf800000, + 0xd8fa0018, 0x06000000, + 0xd8f6001c, 0x07000000, + 0xbf8cc07f, + 0xe0702000, 0x80000403, 0xe0702004, 0x80000503, 0xe0702008, 0x80000603, + 0xbf810000, +}; + +void check(bool condition, const std::string& what) { + if (!condition) throw std::runtime_error("GDS DMA fill: " + what); +} + +std::uint8_t* GuestBlock() { +#ifdef _WIN32 + auto* block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + auto* block = static_cast(std::aligned_alloc(65536, BlockBytes)); +#endif + check(block != nullptr, "cannot allocate a guest block"); + std::memset(block, 0, BlockBytes); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + return block; +} + +std::uint64_t Address(const void* pointer) { + return static_cast(reinterpret_cast(pointer)); +} + +constexpr std::array StoreProgram{ + 0x34060082, + 0x7e0c02ff, 0x5eed0001u, + 0xe0702000, 0x80000603, + 0xbf810000, +}; + +std::array FillGds(std::uint32_t value, std::uint32_t offset = GdsOffset, std::uint32_t bytes = FillBytes) { + return {0xc0055000u, (2u << 29u) | (1u << 20u), value, 0u, offset, 0u, bytes}; +} + +std::array PatternFill(std::uint64_t destination, std::uint32_t bytes) { + return {0xc0055000u, 2u << 29u, 0x1234abcdu, 0u, static_cast(destination), static_cast(destination >> 32u), bytes}; +} + +std::array CopyGdsTo(std::uint64_t destination, std::uint32_t offset = GdsOffset, std::uint32_t bytes = FillBytes) { + return {0xc0055000u, 1u << 29u, offset, 0u, static_cast(destination), static_cast(destination >> 32u), bytes}; +} + +std::array CopyToGds(std::uint64_t source, std::uint32_t offset, std::uint32_t bytes) { + return {0xc0055000u, 1u << 20u, static_cast(source), static_cast(source >> 32u), offset, 0u, bytes}; +} + +template +void Append(std::vector& words, const std::array& packet) { + words.insert(words.end(), packet.begin(), packet.end()); +} + +std::array SetShaderRegister(std::uint32_t offset, std::uint32_t value) { + return {0xc0017600u, offset, value}; +} + +void Submit(std::vector words) { + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check(sceAgcDriverSubmitDcb(&packet) == 0, "submission failed"); +} + +void AppendDispatch(std::vector& words, const void* code, const void* output) { + const auto program = Address(code); + const auto out = Address(output); + Append(words, SetShaderRegister(0x20c, static_cast(program >> 8u))); + Append(words, SetShaderRegister(0x20d, static_cast(program >> 40u) & 0xffu)); + Append(words, SetShaderRegister(0x207, Threads)); + Append(words, SetShaderRegister(0x208, 1)); + Append(words, SetShaderRegister(0x209, 1)); + Append(words, SetShaderRegister(0x213, 4u << 1u)); + const std::array descriptor{static_cast(out), static_cast((out >> 32u) & 0xffffu) | (4u << 16u), Threads * Results, 0x11016facu}; + for (std::uint32_t i = 0; i < 4; ++i) Append(words, SetShaderRegister(0x240 + i, descriptor[i])); + Append(words, std::array{0xc0031500u, 1, 1, 1, 0x8041u}); +} + +void ExpectGds(std::uint8_t* readBack, std::uint32_t value, const char* what) { + std::memset(readBack, 0, FillBytes); + std::vector words; + Append(words, CopyGdsTo(Address(readBack))); + Submit(std::move(words)); + AgcDriverWaitIdle_nid_postfix(); + std::array read{}; + AgcDriver::GuestMemory::Read(Address(readBack), std::as_writable_bytes(std::span(read)), 4); + for (const auto word : read) check(word == value, what); +} + +} + +int main() { +#ifdef _WIN32 + _putenv_s("APS5_PIPELINED_DRAWS", "1"); +#else + setenv("APS5_PIPELINED_DRAWS", "1", 1); +#endif + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + using namespace AgcDriver::DriverDetail; + const bool sharedMapping = AgcDriver::Pm4::GdsShared(); + check(sharedMapping == (AgcDriver::Pm4::ImportableGdsRequested() && std::getenv("APS5_NO_SHARED_GDS") == nullptr), sharedMapping ? "the GDS is in shared memory although that was not requested or is switched off" : "the GDS kept its private mapping although shared memory was requested"); + const bool shared = AgcDriver::Pm4::GdsImportable(); + check(!shared || sharedMapping, "the GDS counts as importable without a shared mapping"); + if (sharedMapping && !shared) std::printf("the device refused the host import of the shared GDS: the copy path is checked\n"); + auto* readBack = GuestBlock(); + auto* filled = GuestBlock(); + auto* held = GuestBlock(); + auto* code = GuestBlock(); + auto* output = GuestBlock(); + auto* source = GuestBlock(); + auto* secondOutput = GuestBlock(); + std::memcpy(code, AppendProgram.data(), sizeof(AppendProgram)); + + std::vector words; + AppendDispatch(words, code, output); + Submit(std::move(words)); + AgcDriverWaitIdle_nid_postfix(); + + const auto drainedBefore = storesDrained.load(); + const auto fillsBefore = dmaFills.load(); + const auto gpuBefore = storesOnGpu.load() + storesBehindCompletions.load(); + words.clear(); + Append(words, PatternFill(Address(filled), BufferFillBytes)); + Append(words, FillGds(0x5eed1234u)); + Submit(std::move(words)); + AgcDriverWaitIdle_nid_postfix(); + check(storesDrained.load() == drainedBefore, "an immediate DMA_DATA fill of GDS drained the device"); + if (dmaFills.load() == fillsBefore) std::printf("buffer fill not recorded: the device does not store fills into imported guest memory\n"); + check(storesOnGpu.load() + storesBehindCompletions.load() == gpuBefore, "the fill of GDS after recorded work was recorded as a GPU store over the GDS"); + ExpectGds(readBack, 0x5eed1234u, "a GDS to memory copy did not see the immediate fill before it"); + + for (std::uint32_t round = 0; round < 3; ++round) { + const auto value = 1000u + round; + const auto dispatchDrainsBefore = storesDrained.load(); + const auto dispatchGpuBefore = storesOnGpu.load() + storesBehindCompletions.load(); + words.clear(); + Append(words, FillGds(value, CounterOffset, 16)); + AppendDispatch(words, code, output); + Submit(std::move(words)); + AgcDriverWaitIdle_nid_postfix(); + std::array returned{}; + AgcDriver::GuestMemory::Read(Address(output), std::as_writable_bytes(std::span(returned)), 4); + check(returned[0] == value && returned[1] == value, "a dispatch using the GDS after a fill read " + std::to_string(returned[0]) + " and " + std::to_string(returned[1]) + ", not the fill's " + std::to_string(value)); + check(storesDrained.load() == dispatchDrainsBefore, "a fill of GDS before a dispatch using it drained the device"); + check(storesOnGpu.load() + storesBehindCompletions.load() == dispatchGpuBefore, "a fill of GDS before a dispatch using it was recorded as a GPU store over the GDS"); + } + + { + const std::array counters{2000u, 2000u, 2000u, 2000u}; + std::memcpy(filled, counters.data(), sizeof(counters)); + const auto copyDrainsBefore = storesDrained.load(); + const auto copyGpuBefore = storesOnGpu.load() + storesBehindCompletions.load(); + const auto source = Address(filled); + words.clear(); + Append(words, PatternFill(Address(readBack), BufferFillBytes)); + Append(words, std::array{0xc0055000u, 1u << 20u, static_cast(source), static_cast(source >> 32u), CounterOffset, 0u, 16u}); + AppendDispatch(words, code, output); + Submit(std::move(words)); + AgcDriverWaitIdle_nid_postfix(); + std::array returned{}; + AgcDriver::GuestMemory::Read(Address(output), std::as_writable_bytes(std::span(returned)), 4); + check(returned[0] == 2000u && returned[1] == 2000u, "a dispatch using the GDS after a copy into it read " + std::to_string(returned[0]) + " and " + std::to_string(returned[1]) + ", not the copied 2000"); + check(storesDrained.load() == copyDrainsBefore, "a DMA_DATA copy into the GDS drained the device"); + check(storesOnGpu.load() + storesBehindCompletions.load() == copyGpuBefore, "a DMA_DATA copy into the GDS was recorded as a GPU store over the GDS"); + } + + { + std::atomic release{false}; + const auto address = Address(held); + DrawPipeline::Queue0().Enqueue([&release] { + while (!release.load(std::memory_order_acquire)) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + }, {{address, address + 16}}); + const auto drainsBefore = DrawPipeline::DrainRequestsByOpcode(0x50); + words.clear(); + Append(words, FillGds(0x0badf00du)); + Submit(std::move(words)); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + const auto gds = AgcDriver::Pm4::GdsAddress() + GdsOffset; + while (!DrawPipeline::Queue0().Overlaps(gds, FillBytes) && DrawPipeline::DrainRequestsByOpcode(0x50) == drainsBefore && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const bool queued = DrawPipeline::Queue0().Overlaps(gds, FillBytes); + const auto drains = DrawPipeline::DrainRequestsByOpcode(0x50); + release.store(true, std::memory_order_release); + check(drains == drainsBefore, "an immediate DMA_DATA fill of GDS drained the draw pipeline"); + check(queued, "an immediate DMA_DATA fill of GDS was not queued behind the held draw pipeline item"); + } + ExpectGds(readBack, 0x0badf00du, "a GDS to memory copy did not see the fill queued in the draw pipeline"); + + { + std::atomic release{false}; + const auto address = Address(held); + DrawPipeline::Queue0().Enqueue([&release] { + while (!release.load(std::memory_order_acquire)) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + }, {{address, address + 16}}); + const auto drainsBefore = DrawPipeline::DrainRequestsByOpcode(0x50); + std::memset(readBack, 0, FillBytes); + words.clear(); + Append(words, FillGds(0x600dcafeu)); + Append(words, CopyGdsTo(Address(readBack))); + Submit(std::move(words)); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (DrawPipeline::DrainRequestsByOpcode(0x50) == drainsBefore && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const auto drains = DrawPipeline::DrainRequestsByOpcode(0x50); + release.store(true, std::memory_order_release); + AgcDriverWaitIdle_nid_postfix(); + std::array read{}; + AgcDriver::GuestMemory::Read(Address(readBack), std::as_writable_bytes(std::span(read)), 4); + for (const auto word : read) check(word == 0x600dcafeu, "a GDS to memory copy behind a fill of GDS queued in the draw pipeline read the GDS before the fill"); + check(drains != drainsBefore, "a GDS to memory copy behind a fill of GDS queued in the draw pipeline did not drain it"); + } + + { + std::array copied{}; + copied.fill(0x0c0ffee0u); + std::memcpy(source, copied.data(), sizeof(copied)); + std::atomic release{false}; + const auto address = Address(held); + DrawPipeline::Queue0().Enqueue([&release] { + while (!release.load(std::memory_order_acquire)) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + }, {{address, address + 16}}); + const auto drainsBefore = DrawPipeline::DrainRequestsByOpcode(0x50); + const auto from = Address(source); + words.clear(); + Append(words, FillGds(0xfeedf00du)); + Append(words, std::array{0xc0055000u, 1u << 20u, static_cast(from), static_cast(from >> 32u), GdsOffset, 0u, FillBytes}); + Submit(std::move(words)); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (DrawPipeline::DrainRequestsByOpcode(0x50) == drainsBefore && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + const auto drains = DrawPipeline::DrainRequestsByOpcode(0x50); + release.store(true, std::memory_order_release); + AgcDriverWaitIdle_nid_postfix(); + ExpectGds(readBack, 0x0c0ffee0u, "a memory to GDS copy behind a fill of GDS queued in the draw pipeline was overwritten by the fill"); + check(drains != drainsBefore, "a memory to GDS copy behind a fill of GDS queued in the draw pipeline did not drain it"); + } + + { + words.clear(); + Append(words, FillGds(500u, CounterOffset, 16)); + Submit(std::move(words)); + AgcDriverWaitIdle_nid_postfix(); + const auto drainsBefore = storesDrained.load(); + const auto gpuStoresBefore = storesOnGpu.load() + storesBehindCompletions.load(); + const auto gpuCopiesBefore = copiesBehindPendingSources.load(); + words.clear(); + AppendDispatch(words, code, output); + Append(words, FillGds(3000u, CounterOffset, 16)); + AppendDispatch(words, code, secondOutput); + Append(words, CopyGdsTo(Address(readBack), CounterOffset, 16)); + Submit(std::move(words)); + AgcDriverWaitIdle_nid_postfix(); + std::array first{}; + std::array second{}; + std::array counters{}; + AgcDriver::GuestMemory::Read(Address(output), std::as_writable_bytes(std::span(first)), 4); + AgcDriver::GuestMemory::Read(Address(secondOutput), std::as_writable_bytes(std::span(second)), 4); + AgcDriver::GuestMemory::Read(Address(readBack), std::as_writable_bytes(std::span(counters)), 4); + check(first[0] == 500u, "the dispatch before the fill read " + std::to_string(first[0]) + ", not the earlier fill's 500"); + check(second[0] == 3000u, "the dispatch after the fill, in the same submission, read " + std::to_string(second[0]) + ", not the fill's 3000"); + check(counters[0] == 3032u && counters[1] == 2968u && counters[2] == 3032u, "the GDS counters after a dispatch, a fill and a dispatch are " + std::to_string(counters[0]) + ", " + std::to_string(counters[1]) + " and " + std::to_string(counters[2]) + ", not 3032, 2968 and 3032: the first dispatch's atomics landed after the fill"); + if (shared) { + check(storesDrained.load() == drainsBefore, "a fill of the shared GDS behind a dispatch still using it drained the device"); + check(storesOnGpu.load() + storesBehindCompletions.load() == gpuStoresBefore + 1, "a fill of the shared GDS behind a dispatch still using it was not stored on the GPU"); + check(copiesBehindPendingSources.load() == gpuCopiesBefore + 1, "the copy out of the shared GDS behind the dispatches using it was not recorded on the GPU"); + } else { + check(storesDrained.load() == drainsBefore + 1, "a fill behind a dispatch still using the GDS did not drain the device as main does"); + } + } + + { + constexpr std::uint32_t OrderOffset = 0x400; + constexpr std::uint32_t OrderValue = 0x5151a1a1u; + auto* storeCode = GuestBlock(); + auto* orderOutput = GuestBlock(); + auto* orderReadBack = GuestBlock(); + std::memcpy(storeCode, StoreProgram.data(), sizeof(StoreProgram)); + const auto drainsBefore = storesDrained.load(); + const auto gpuStoresBefore = storesOnGpu.load() + storesBehindCompletions.load(); + const auto gpuCopiesBefore = copiesBehindPendingSources.load(); + words.clear(); + AppendDispatch(words, storeCode, orderOutput); + Append(words, CopyToGds(Address(orderOutput), OrderOffset, 4)); + Append(words, FillGds(OrderValue, OrderOffset, 4)); + Append(words, CopyGdsTo(Address(orderReadBack), OrderOffset, 4)); + Submit(std::move(words)); + AgcDriverWaitIdle_nid_postfix(); + std::array value{}; + AgcDriver::GuestMemory::Read(Address(orderReadBack), std::as_writable_bytes(std::span(value)), 4); + std::array stored{}; + AgcDriver::GuestMemory::Read(Address(orderOutput), std::as_writable_bytes(std::span(stored)), 4); + check(stored[0] == 0x5eed0001u, "the dispatch that writes the copy's source stored " + std::to_string(stored[0]) + ", not 0x5eed0001"); + check(value[0] == OrderValue, "a GDS fill after a copy into the GDS from the output of a dispatch that does not use the GDS read back as " + std::to_string(value[0]) + ", not the fill's value: the copy landed after the fill"); + if (shared) { + check(copiesBehindPendingSources.load() > gpuCopiesBefore, "the copy into the shared GDS behind the dispatch writing its source was not recorded on the GPU"); + check(storesOnGpu.load() + storesBehindCompletions.load() > gpuStoresBefore, "the fill of the shared GDS behind the GPU copy into it was not stored on the GPU"); + check(storesDrained.load() == drainsBefore, "a DMA_DATA into or out of the shared GDS behind a dispatch drained the device"); + } + } + + LibcRunShutdown_nid_postfix(); + std::puts("GDS DMA fill 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/IdleStoreYields.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/IdleStoreYields.cpp index e2d59fd5ca..e8c21ebf7c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/IdleStoreYields.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/IdleStoreYields.cpp @@ -6,6 +6,7 @@ #include "VulkanTestDevice.hpp" #include #include +#include #include #include #include @@ -25,7 +26,8 @@ constexpr std::uint32_t Untouched = 0xdeadbeefu; constexpr std::uint32_t Stored = 0x12345678u; constexpr std::uint32_t FarRow = 1000; constexpr std::uint64_t Piece = 4u << 20u; -constexpr std::uint32_t Attempts = 10; +alignas(4) std::atomic betweenImagesHold{0}; +alignas(4) std::atomic withinAnImageHold{0}; alignas(256) constexpr std::array StoreRowZeroCode{ 0x7e040300, 0x7e060280, 0x7e0202ff, Stored, 0xf0200108, 0x00020102, 0xbf810000, 0xbf810000, @@ -108,13 +110,38 @@ bool Contains(std::uint64_t begin, std::uint64_t end) { return false; } -void SubmitAndWaitForPending(std::vector& words) { +class QueueHold { +public: + explicit QueueHold(std::atomic& label) : label(label) {} + ~QueueHold() { Release(); } + QueueHold(const QueueHold&) = delete; + QueueHold& operator=(const QueueHold&) = delete; + + void Append(std::vector& words) const { + const auto address = AddressOf(&label); + words.insert(words.end(), {0xc0053c00u, 0x13u, static_cast(address), static_cast(address >> 32u), 1u, 0xffffffffu, 0x19u}); + } + + bool Reached() const { + std::array waits{}; + const auto count = AgcDriverAwaitedWaits_nid_postfix(waits.data(), waits.size()); + return std::ranges::any_of(std::span(waits.data(), count), [&](const AgcDriver::AwaitedWait& wait) { return wait.address == AddressOf(&label); }); + } + + void Release() { label.store(1, std::memory_order_release); } + +private: + std::atomic& label; +}; + +void SubmitAndWaitHeld(std::vector& words, const QueueHold& hold) { + hold.Append(words); Packet packet{words.data(), static_cast(words.size()), 0, {}}; AgcDriver::Submit(&packet, 0); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); - while (!AgcDriver::Graphics::StorageTexture::AnyPending() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + while (!hold.Reached() && std::chrono::steady_clock::now() < deadline) std::this_thread::sleep_for(std::chrono::microseconds(200)); + Require(hold.Reached(), "idle yield fixture: the queue did not reach the wait after the dispatches"); Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "idle yield fixture did not observe pending storage results"); - std::this_thread::sleep_for(std::chrono::milliseconds(5)); } struct Registered { @@ -133,17 +160,6 @@ struct Registered { } }; -void Repeat(void (*scenario)()) { - for (std::uint32_t attempt = 1;; ++attempt) { - try { - scenario(); - return; - } catch (const std::exception&) { - if (attempt == Attempts) throw; - } - } -} - void StopsBetweenImages() { Surface first(256, 256); Surface second(256, 256); @@ -151,10 +167,12 @@ void StopsBetweenImages() { std::vector words; AppendStore(words, first, StoreRowZeroCode); AppendStore(words, second, StoreRowZeroCode); - SubmitAndWaitForPending(words); + QueueHold hold(betweenImagesHold); + SubmitAndWaitHeld(words, hold); const auto pending = [](const Surface& surface) { return Pending(surface.Address(), surface.Address() + surface.bytes); }; { std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); + hold.Release(); Require(pending(first) && pending(second), "the queue-idle store ran before the fixture could take the GPU mutex"); std::size_t checks = 0; const bool finished = AgcDriver::Graphics::StorageTexture::FlushAllPendingUntil("idle", [&] { return checks++ != 0; }); @@ -173,12 +191,14 @@ void StopsWithinAnImage() { std::vector words; AppendStore(words, surface, StoreRowZeroCode); AppendStore(words, surface, StoreFarRowCode); - SubmitAndWaitForPending(words); + QueueHold hold(withinAnImageHold); + SubmitAndWaitHeld(words, hold); const auto begin = surface.Address(); const auto split = begin + Piece; const auto end = begin + surface.bytes; { std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); + hold.Release(); Require(Pending(begin, end), "the large image has no pending results"); std::size_t checks = 0; const bool finished = AgcDriver::Graphics::StorageTexture::FlushAllPendingUntil("idle", [&] { return checks++ != 0; }); @@ -201,8 +221,8 @@ int main() { struct ShutdownGuard { ~ShutdownGuard() { try { AgcDriverShutdown_nid_postfix(); } catch (...) {} } } shutdown; - Repeat(StopsBetweenImages); - Repeat(StopsWithinAnImage); + StopsBetweenImages(); + StopsWithinAnImage(); AgcDriverShutdown_nid_postfix(); std::puts("idle store yields tests passed"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayRowsPastLayerLimit.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayRowsPastLayerLimit.cpp new file mode 100644 index 0000000000..e76dbca6a0 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayRowsPastLayerLimit.cpp @@ -0,0 +1,614 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "Recompiler.hpp" +#include "prx/libSceAgcDriver/Execution/include/ShaderMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" +#include "Optimization/ResourceProgram.hpp" +#include "Optimization/ResourceMaterializer.hpp" +#include "IntermediateRepresentation/IrMetadata/ResourcePlan.hpp" +#include "prx/libc/include/GuestAllocations.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 Threads = 32; +constexpr std::uint32_t Width = 8; +constexpr std::uint32_t MaxGuestLayers = 8192; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Type1DArray = 12; +constexpr std::uint32_t Wrap = 0; +constexpr std::uint32_t ClampEdge = 2; +constexpr std::uint32_t Bilinear = (1u << 20u) | (1u << 22u); + +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; +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 SampleLevelZero{ + 0x34020084, 0xe0381000, 0x80000201, 0xbf8c3f70, 0xf09c0f20, 0x00820602, 0x34020085, 0xbf8c3f70, + 0xe0781000, 0x80010601, 0xbf810000, +}; + +alignas(256) constexpr std::array SampleLevel{ + 0x34020084, 0xe0381000, 0x80000201, 0xbf8c3f70, 0xf0900f20, 0x00820602, 0x34020085, 0xbf8c3f70, + 0xe0781000, 0x80010601, 0xbf810000, +}; + +std::uint32_t Layers = 0; + +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(std::uint32_t baseArray) { + const auto address = static_cast(reinterpret_cast(Texels.data())); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Width - 1u) & 3u) << 30u), + (Width - 1u) >> 2u, + 0xfacu | (Type1DArray << 28u), + (Layers - 1u) | (baseArray << 16u), 0u, 0u, 0u, + }; +} + +std::array Texel(std::uint32_t x, std::uint32_t layer) { + return {static_cast(x * 30u + layer % 5u), static_cast((layer & 1u) != 0u ? 255u : 0u), static_cast(layer), 0xff}; +} + +void FillTexels() { + std::ranges::fill(Texels, std::uint8_t{0}); + const auto surface = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor(0))); + const auto& mip = surface.mips.at(0); + Require(surface.layers == Layers && surface.guestBytes <= Texels.size(), "the test array does not fit its storage"); + for (std::uint32_t layer = 0; layer < Layers; ++layer) { + for (std::uint32_t x = 0; x < Width; ++x) { + const auto texel = Texel(x, layer); + std::copy(texel.begin(), texel.end(), Texels.begin() + static_cast(surface.GuestLayerOffset(layer) + mip.tiledOffset + x * 4u)); + } + } +} + +void FillInput(std::uint32_t limit) { + const auto last = static_cast(Layers - 1u); + const std::array layers{0.0f, 1.0f, 2.4f, 1.6f, -1.0f, -0.4f, 0.4f, last, last + 0.4f, last + 1.0f, last + 7.0f, 1.0e6f, -1.0e6f, static_cast(limit - 1u), static_cast(limit), static_cast(limit) + 0.6f}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + Input[tid * 4u + 0u] = std::bit_cast(-0.2f + 0.045f * static_cast(tid)); + Input[tid * 4u + 1u] = std::bit_cast(layers[tid % layers.size()]); + Input[tid * 4u + 2u] = std::bit_cast(static_cast(tid % 3u) * 1.7f); + Input[tid * 4u + 3u] = 0u; + } +} + +float Unorm(std::uint8_t value) { + return static_cast(value); +} + +class RegisteredTextureRange { +public: + RegisteredTextureRange(void* address, std::size_t bytes) : address(address) { + GuestAllocations::Mutation mutation; + mutation.Add(address, bytes, true, false, true); + } + + ~RegisteredTextureRange() { + Unregister(); + } + + void Resize(std::size_t bytes) { + GuestAllocations::Mutation mutation; + if (registered) mutation.Remove(address); + mutation.Add(address, bytes, true, false, true); + registered = true; + } + + void Unregister() { + if (!registered) return; + GuestAllocations::Mutation mutation; + mutation.Remove(address); + registered = false; + } + + RegisteredTextureRange(const RegisteredTextureRange&) = delete; + RegisteredTextureRange& operator=(const RegisteredTextureRange&) = delete; + +private: + void* address; + bool registered = true; +}; + +int CaptureStillRowsRegisteredWindow(AgcDriver::VulkanDevice& device, std::uint32_t limit) { + constexpr std::uint32_t baseArray = 1u; + if (limit > MaxGuestLayers - 3u) { + std::puts("still-rows: skipped because the device layer limit leaves no guest descriptor headroom"); + return VulkanTestSkipped; + } + if (2u * (limit + 1u) > device.graphicsContext().limits.maxImageDimension2D) { + std::puts("still-rows: skipped because the registered row geometry exceeds maxImageDimension2D"); + return VulkanTestSkipped; + } + Layers = limit + 3u; + const auto texture = TextureDescriptor(baseArray); + const auto rawResource = AgcDriver::Graphics::DecodeTextureResource(texture); + const auto rawGeometry = AgcDriver::Graphics::DescribeSurface(rawResource); + const auto rawViewLayers = rawGeometry.imageLayers - rawResource.baseArray; + Require(rawResource.baseArray == baseArray && rawViewLayers == limit + 2u, "still-rows: the raw descriptor does not cover L+2 slices from baseArray 1"); + Require(rawGeometry.layerBytes != 0u, "still-rows: the descriptor has no complete layer size"); + if (rawGeometry.guestBytes > Texels.size()) { + std::puts("still-rows: skipped because the full descriptor geometry exceeds the 4 MiB test backing"); + return VulkanTestSkipped; + } + const auto registeredBytes = static_cast(rawGeometry.layerBytes * (limit + 2u)); + if (registeredBytes > Texels.size()) { + std::puts("still-rows: skipped because the exact registered complete-slice range exceeds the 4 MiB test backing"); + return VulkanTestSkipped; + } + Require(reinterpret_cast(Texels.data()) % TexelAlignment == 0u, "still-rows: the descriptor base is not 64 KiB aligned"); + FillTexels(); + FillInput(limit + 1u); + RegisteredTextureRange registered(Texels.data(), registeredBytes); + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast(Texels.data()), false); + Require(range != nullptr && range->address == reinterpret_cast(Texels.data()) && range->bytes == registeredBytes && range->readable && !range->writable && range->gpu, "still-rows: lookup did not return the exact readable registered extent"); + const auto completeRegisteredSlices = registeredBytes / rawGeometry.layerBytes; + Require(completeRegisteredSlices == limit + 2u, "still-rows: the registered extent does not contain the expected complete slices"); + const auto expectedLastArray = static_cast(completeRegisteredSlices - 1u); + const auto boundedRows = expectedLastArray - baseArray + 1u; + Require(expectedLastArray == limit + 1u && boundedRows == limit + 1u, "still-rows: the independent complete-range oracle did not produce the expected L+1 view"); + std::vector userData(20, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(sizeof(Input))); + const auto output = BufferDescriptor(Output.data(), static_cast(sizeof(Output))); + const std::array sampler{ClampEdge | (ClampEdge << 3u) | (ClampEdge << 6u), 0u, 0u, 0u}; + 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); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 16); + const std::array memory{{{reinterpret_cast(SampleLevelZero.data()), std::as_bytes(std::span(SampleLevelZero))}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(SampleLevelZero.data()), SampleLevelZero, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + AgcDriver::DriverDetail::ShaderSnapshot snapshot{reinterpret_cast(SampleLevelZero.data()), 0, 0, {SampleLevelZero.begin(), SampleLevelZero.end()}, {}}; + request.shader.code = snapshot.code; + auto invocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, request); + AgcDriver::ShaderMemory shaderMemory(request.context.memory); + const auto capture = AgcDriver::DriverDetail::CaptureInvocation(shaderMemory, snapshot, 0, request, invocation); + Require(capture->snapshot.images.size() == 1u && capture->plan->info.images.size() == 1u && capture->plan->info.runtimeImageModes.size() == 1u, "still-rows: capture did not resolve exactly one sampled image"); + const auto& capturedDescriptor = capture->snapshot.images.front(); + const auto capturedResource = AgcDriver::Graphics::DecodeTextureResource(std::span(capturedDescriptor.dwords).first(capturedDescriptor.dwordCount)); + const auto capturedGeometry = AgcDriver::Graphics::DescribeSurface(capturedResource); + const auto& image = capture->plan->info.images.front(); + const auto& modes = capture->plan->info.runtimeImageModes.front(); + const auto modeIndex = ShaderRecompiler::ResourceMaterializer::RuntimeImageMode(image, capturedDescriptor, modes); + const auto& selectedMode = modes.at(modeIndex); + Require(selectedMode.layerRows, "still-rows: the over-limit effective view did not select layerRows mode"); + Require(capture->snapshot.registeredImages.size() == 1u && capture->snapshot.registeredImages.front().provenance.dwords == texture && capture->snapshot.registeredImages.front().effective == capturedDescriptor && capture->snapshot.registeredImages.front().rangeBytes == registeredBytes, "still-rows: capture identity lost original provenance, effective geometry, or the registered extent"); + Require(capturedResource.baseArray == baseArray && capturedResource.depthOrLastArray == expectedLastArray && capturedGeometry.imageLayers - capturedResource.baseArray == boundedRows, "still-rows: captured descriptor geometry differs from the independent complete registered window before dispatch"); + const auto expectedRowHeight = boundedRows * 2u; + Require(expectedRowHeight <= device.graphicsContext().limits.maxImageDimension2D, "still-rows: the independent bound row height exceeds maxImageDimension2D"); + const auto result = invocation.Materialize(*capture); + auto prepared = device.PrepareDispatch(*result, {}, Threads, reinterpret_cast(SampleLevelZero.data()), std::nullopt, &capture->snapshot.registeredImages); + device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast(SampleLevelZero.data()), std::move(prepared), nullptr, std::nullopt, &capture->snapshot.registeredImages); + device.WaitIdle(); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto logicalLayer = static_cast(std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(limit))); + const auto layer = baseArray + logicalLayer; + const auto x = static_cast(std::clamp(static_cast(std::floor(std::bit_cast(Input[tid * 4u + 0u]) * static_cast(Width))), 0, static_cast(Width) - 1)); + const auto expected = Texel(x, layer); + for (std::uint32_t component = 0; component < expected.size(); ++component) Require(std::fabs(Output[tid * 8u + component] * 255.0f - Unorm(expected[component])) <= 1.0f, "still-rows: first/last/beyond-limit ClampEdge samples disagree with the existing Texel oracle"); + } + return 0; +} + +void CaptureRegisteredWindow(AgcDriver::VulkanDevice& device, std::uint32_t limit) { + constexpr std::uint32_t baseArray = 1u; + Layers = limit + 2u; + const auto texture = TextureDescriptor(baseArray); + const auto rawResource = AgcDriver::Graphics::DecodeTextureResource(texture); + const auto rawGeometry = AgcDriver::Graphics::DescribeSurface(rawResource); + const auto rawViewLayers = rawGeometry.imageLayers - rawResource.baseArray; + Require(rawResource.baseAddress == reinterpret_cast(Texels.data()) && rawResource.baseArray == baseArray && rawResource.format == Format8888UNorm, "registered window: the decoded descriptor lost its aligned sampled-image base, baseArray, or format"); + Require(rawViewLayers == limit + 1u, "registered window: the raw descriptor is not maxImageArrayLayers + 1 from baseArray 1"); + Require(rawGeometry.layerBytes != 0u && rawGeometry.guestBytes <= Texels.size() && rawGeometry.layerBytes * (baseArray + limit) <= Texels.size(), "registered window: the complete raw geometry or bounded complete-slice range does not fit the real backing"); + Require(reinterpret_cast(Texels.data()) % TexelAlignment == 0u, "registered window: the descriptor base is not 64 KiB aligned"); + + const auto registeredBytes = static_cast(rawGeometry.layerBytes * (baseArray + limit)); + const auto generationBefore = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + RegisteredTextureRange registered(Texels.data(), registeredBytes); + const auto generationAfter = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + Require(generationAfter > generationBefore, "registered window: adding the slice range did not advance the allocation generation"); + { + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast(Texels.data()), false); + Require(range != nullptr && range->address == reinterpret_cast(Texels.data()) && range->bytes == registeredBytes && range->readable && !range->writable && range->gpu, "registered window: lookup did not return the one readable sampled-image extent"); + Require(range->allocationAddress == range->address && range->allocationBytes == registeredBytes, "registered window: lookup allocation extent differs from the registered slice range"); + const auto registeredViewLayers = range->bytes / rawGeometry.layerBytes - rawResource.baseArray; + Require(registeredViewLayers == limit, "registered window: the actual registered extent does not bound the descriptor view to the device limit"); + } + + std::vector userData(20, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(sizeof(Input))); + const auto output = BufferDescriptor(Output.data(), static_cast(sizeof(Output))); + const std::array sampler{ClampEdge | (ClampEdge << 3u) | (ClampEdge << 6u), 0u, 0u, 0u}; + 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); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 16); + const std::array memory{{{reinterpret_cast(SampleLevelZero.data()), std::as_bytes(std::span(SampleLevelZero))}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(SampleLevelZero.data()), SampleLevelZero, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + AgcDriver::DriverDetail::ShaderSnapshot snapshot{reinterpret_cast(SampleLevelZero.data()), 0, 0, {SampleLevelZero.begin(), SampleLevelZero.end()}, {}}; + request.shader.code = snapshot.code; + auto invocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, request); + Require(!invocation.Request().context.layerRowSamples, "registered window: the fresh invocation already has layer-row sampling enabled"); + const auto beforeCapture = snapshot.prepared->entries.size(); + Require(beforeCapture == 1u, "registered window: the fresh snapshot did not prepare exactly one initial native invocation"); + AgcDriver::ShaderMemory shaderMemory(request.context.memory); + const auto capture = AgcDriver::DriverDetail::CaptureInvocation(shaderMemory, snapshot, 0, request, invocation); + Require(!invocation.Request().context.layerRowSamples, "registered window: capture promoted the sampled array to layer rows before dispatch"); + Require(snapshot.prepared->entries.size() == beforeCapture, "registered window: capture added a prepared mode while selecting the effective native view"); + Require(capture->snapshot.images.size() == 1u && capture->plan->info.images.size() == 1u && capture->plan->info.runtimeImageModes.size() == 1u, "registered window: capture did not resolve exactly one sampled image"); + + const auto& capturedDescriptor = capture->snapshot.images.front(); + Require(capturedDescriptor.dwordCount == 8u, "registered window: capture did not retain a complete image descriptor"); + const auto capturedResource = AgcDriver::Graphics::DecodeTextureResource(std::span(capturedDescriptor.dwords).first(capturedDescriptor.dwordCount)); + const auto capturedGeometry = AgcDriver::Graphics::DescribeSurface(capturedResource); + Require(capturedResource.baseArray == baseArray && capturedGeometry.imageLayers - capturedResource.baseArray == limit, "registered window: captured descriptor window was not normalized to the complete registered slice extent"); + const auto& image = capture->plan->info.images.front(); + const auto& modes = capture->plan->info.runtimeImageModes.front(); + const auto modeIndex = ShaderRecompiler::ResourceMaterializer::RuntimeImageMode(image, capturedDescriptor, modes); + const auto& selectedMode = modes.at(modeIndex); + Require(selectedMode.dimension == ShaderRecompiler::RdnaImageDimension::Dim1DArray && !selectedMode.layerRows, "registered window: the normalized capture did not select the native 1D-array mode"); + Require(capture->snapshot.registeredImages.size() == 1u && capture->snapshot.registeredImages.front().provenance.dwords == texture, "registered window: capture did not retain the original descriptor provenance"); + Require(capture->snapshot.registeredImages.front().effective == capturedDescriptor && capture->snapshot.registeredImages.front().generation == generationAfter && capture->snapshot.registeredImages.front().rangeBytes == registeredBytes, "registered window: capture identity does not match the effective view's registered range"); + Require(capture->snapshot.imageAllocationGeneration == generationAfter, "registered window: capture did not bind the stable allocation generation"); + FillTexels(); + FillInput(limit); + auto cachedRequest = request; + cachedRequest.useCache = true; + auto cachedInvocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, cachedRequest); + AgcDriver::ShaderMemory cachedMemory(cachedRequest.context.memory); + const auto cachedCapture = AgcDriver::DriverDetail::CaptureInvocation(cachedMemory, snapshot, 0, cachedRequest, cachedInvocation); + const auto cachedResult = cachedInvocation.Materialize(*cachedCapture); + Require(cachedResult == cachedInvocation.Materialize(*cachedCapture), "registered window: identical registered captures did not reuse the cached materialization"); + const auto dispatchCaptured = [&](const auto& result, const auto& currentCapture) { + auto prepared = device.PrepareDispatch(*result, {}, Threads, reinterpret_cast(SampleLevelZero.data()), std::nullopt, ¤tCapture->snapshot.registeredImages); + device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast(SampleLevelZero.data()), std::move(prepared), nullptr, std::nullopt, ¤tCapture->snapshot.registeredImages); + device.WaitIdle(); + }; + dispatchCaptured(cachedResult, cachedCapture); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto layer = baseArray + static_cast(std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(limit - 1u))); + const auto x = static_cast(std::clamp(static_cast(std::floor(std::bit_cast(Input[tid * 4u + 0u]) * static_cast(Width))), 0, static_cast(Width) - 1)); + const auto expected = Texel(x, layer); + for (std::uint32_t component = 0; component < expected.size(); ++component) Require(std::fabs(Output[tid * 8u + component] * 255.0f - Unorm(expected[component])) <= 1.0f, "registered window: bound native image geometry sampled outside its registered extent"); + } + const auto stableView = cachedCapture->snapshot.registeredImages.front(); + std::array unrelatedStorage{}; + const auto stableGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + std::shared_ptr unchangedResult; + std::shared_ptr unchangedCapture; + { + RegisteredTextureRange unrelated(unrelatedStorage.data(), sizeof(unrelatedStorage)); + const auto unrelatedGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + Require(unrelatedGeneration > stableGeneration, "registered window: the unrelated mapping did not advance the allocation generation"); + auto unchangedInvocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, cachedRequest); + AgcDriver::ShaderMemory unchangedMemory(cachedRequest.context.memory); + unchangedCapture = AgcDriver::DriverDetail::CaptureInvocation(unchangedMemory, snapshot, 0, cachedRequest, unchangedInvocation); + const auto& unchangedView = unchangedCapture->snapshot.registeredImages.front(); + Require(unchangedView.provenance == stableView.provenance && unchangedView.effective == stableView.effective && unchangedView.address == stableView.address && unchangedView.rangeAddress == stableView.rangeAddress && unchangedView.rangeBytes == stableView.rangeBytes && unchangedView.allocationAddress == stableView.allocationAddress && unchangedView.allocationBytes == stableView.allocationBytes && unchangedView.backingSerial == stableView.backingSerial && unchangedView.hasRange == stableView.hasRange && unchangedView.readable == stableView.readable && unchangedView.writable == stableView.writable && unchangedView.gpu == stableView.gpu, "registered window: an unrelated mapping changed the unchanged sampled-image range facts"); + Require(!unchangedInvocation.Request().context.layerRowSamples && snapshot.prepared->entries.size() == beforeCapture, "registered window: an unrelated generation bump promoted or prepared a row-mode artifact for the unchanged range"); + const auto unchangedMode = ShaderRecompiler::ResourceMaterializer::RuntimeImageMode(unchangedCapture->plan->info.images.front(), unchangedCapture->snapshot.images.front(), unchangedCapture->plan->info.runtimeImageModes.front()); + Require(unchangedCapture->plan->info.runtimeImageModes.front().at(unchangedMode).dimension == ShaderRecompiler::RdnaImageDimension::Dim1DArray && !unchangedCapture->plan->info.runtimeImageModes.front().at(unchangedMode).layerRows, "registered window: an unrelated generation bump changed the unchanged native image mode"); + unchangedResult = unchangedInvocation.Materialize(*unchangedCapture); + Require(unchangedResult == cachedResult, "registered window: an unrelated generation bump missed the warm cached materialization for unchanged range identity"); + } + const auto removedUnrelatedGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + Require(removedUnrelatedGeneration > stableGeneration, "registered window: removing the unrelated mapping did not advance the allocation generation"); + auto removedUnrelatedInvocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, cachedRequest); + AgcDriver::ShaderMemory removedUnrelatedMemory(cachedRequest.context.memory); + const auto removedUnrelatedCapture = AgcDriver::DriverDetail::CaptureInvocation(removedUnrelatedMemory, snapshot, 0, cachedRequest, removedUnrelatedInvocation); + const auto& removedUnrelatedView = removedUnrelatedCapture->snapshot.registeredImages.front(); + Require(removedUnrelatedView.effective == stableView.effective && removedUnrelatedView.rangeAddress == stableView.rangeAddress && removedUnrelatedView.rangeBytes == stableView.rangeBytes && removedUnrelatedView.backingSerial == stableView.backingSerial, "registered window: removing an unrelated mapping changed the sampled-image range identity"); + Require(!removedUnrelatedInvocation.Request().context.layerRowSamples && snapshot.prepared->entries.size() == beforeCapture, "registered window: removing an unrelated mapping promoted or prepared a row-mode artifact for the unchanged range"); + const auto removedUnrelatedResult = removedUnrelatedInvocation.Materialize(*removedUnrelatedCapture); + Require(removedUnrelatedResult == cachedResult, "registered window: removing an unrelated mapping missed the warm cached materialization for unchanged range identity"); + dispatchCaptured(unchangedResult, unchangedCapture); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto layer = baseArray + static_cast(std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(limit - 1u))); + const auto x = static_cast(std::clamp(static_cast(std::floor(std::bit_cast(Input[tid * 4u + 0u]) * static_cast(Width))), 0, static_cast(Width) - 1)); + const auto expected = Texel(x, layer); + for (std::uint32_t component = 0; component < expected.size(); ++component) Require(std::fabs(Output[tid * 8u + component] * 255.0f - Unorm(expected[component])) <= 1.0f, "registered window: an unrelated registration altered the bound native image result"); + } + dispatchCaptured(removedUnrelatedResult, removedUnrelatedCapture); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto layer = baseArray + static_cast(std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(limit - 1u))); + const auto x = static_cast(std::clamp(static_cast(std::floor(std::bit_cast(Input[tid * 4u + 0u]) * static_cast(Width))), 0, static_cast(Width) - 1)); + const auto expected = Texel(x, layer); + for (std::uint32_t component = 0; component < expected.size(); ++component) Require(std::fabs(Output[tid * 8u + component] * 255.0f - Unorm(expected[component])) <= 1.0f, "registered window: an unrelated generation change altered the bound native image result"); + } + if (limit > 1u) { + std::uint64_t previousSerial = 0; + { + const auto range = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast(Texels.data()), false); + previousSerial = range->backingSerial; + } + registered.Resize(registeredBytes); + const auto sameExtentGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + { + const auto sameExtentRange = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast(Texels.data()), false); + Require(sameExtentGeneration != generationAfter && sameExtentRange != nullptr && sameExtentRange->backingSerial != previousSerial, "registered window: same-extent registration replacement did not change allocation identity"); + } + auto sameExtentInvocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, cachedRequest); + AgcDriver::ShaderMemory sameExtentMemory(cachedRequest.context.memory); + const auto sameExtentCapture = AgcDriver::DriverDetail::CaptureInvocation(sameExtentMemory, snapshot, 0, cachedRequest, sameExtentInvocation); + const auto sameExtentResult = sameExtentInvocation.Materialize(*sameExtentCapture); + Require(sameExtentCapture->snapshot.registeredImages.front().effective == cachedCapture->snapshot.registeredImages.front().effective && sameExtentCapture->snapshot.registeredImages.front().backingSerial != cachedCapture->snapshot.registeredImages.front().backingSerial, "registered window: same-extent replacement lost descriptor equality or allocation identity"); + Require(sameExtentResult != cachedResult, "registered window: allocation-only change reused a stale cached materialization"); + dispatchCaptured(sameExtentResult, sameExtentCapture); + registered.Resize(static_cast(rawGeometry.layerBytes * (baseArray + limit - 1u))); + const auto resizedGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + std::size_t resizedBytes = 0; + { + const auto resizedRange = GuestAllocations::GuestAllocationsLookup_nid_postfix(reinterpret_cast(Texels.data()), false); + Require(resizedGeneration != generationAfter && resizedRange != nullptr && resizedRange->bytes < registeredBytes, "registered window: resizing the registration did not change its identity"); + resizedBytes = resizedRange->bytes; + } + auto resizedInvocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, cachedRequest); + AgcDriver::ShaderMemory resizedMemory(cachedRequest.context.memory); + const auto resizedCapture = AgcDriver::DriverDetail::CaptureInvocation(resizedMemory, snapshot, 0, cachedRequest, resizedInvocation); + Require(!resizedInvocation.Request().context.layerRowSamples && snapshot.prepared->entries.size() == beforeCapture, "registered window: allocation-only change promoted or added a row-mode artifact"); + Require(resizedCapture->snapshot.imageAllocationGeneration == resizedGeneration, "registered window: recapture reused a stale allocation generation"); + Require(resizedCapture->snapshot.registeredImages.size() == 1u && resizedCapture->snapshot.registeredImages.front().generation == resizedGeneration && resizedCapture->snapshot.registeredImages.front().rangeBytes == resizedBytes, "registered window: recapture reused stale registered-view identity"); + Require(resizedCapture->snapshot.registeredImages.front().provenance == capture->snapshot.registeredImages.front().provenance, "registered window: allocation-only change altered original descriptor provenance"); + Require(resizedCapture->snapshot.registeredImages.front().effective != capture->snapshot.registeredImages.front().effective, "registered window: allocation extent change did not change effective descriptor identity"); + const auto resizedResource = AgcDriver::Graphics::DecodeTextureResource(resizedCapture->snapshot.images.front().dwords); + const auto resizedGeometry = AgcDriver::Graphics::DescribeSurface(resizedResource); + Require(resizedResource.baseArray == baseArray && resizedGeometry.imageLayers - resizedResource.baseArray == limit - 1u, "registered window: recaptured physical geometry disagrees with the effective registered extent"); + const auto resizedMode = ShaderRecompiler::ResourceMaterializer::RuntimeImageMode(resizedCapture->plan->info.images.front(), resizedCapture->snapshot.images.front(), resizedCapture->plan->info.runtimeImageModes.front()); + Require(resizedCapture->plan->info.runtimeImageModes.front().at(resizedMode).dimension == ShaderRecompiler::RdnaImageDimension::Dim1DArray && !resizedCapture->plan->info.runtimeImageModes.front().at(resizedMode).layerRows, "registered window: resized native view changed to a layer-row mode"); + const auto resizedResult = resizedInvocation.Materialize(*resizedCapture); + Require(resizedResult != sameExtentResult, "registered window: changed complete-slice geometry reused a stale cached materialization"); + dispatchCaptured(resizedResult, resizedCapture); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto layer = baseArray + static_cast(std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(limit - 2u))); + const auto x = static_cast(std::clamp(static_cast(std::floor(std::bit_cast(Input[tid * 4u + 0u]) * static_cast(Width))), 0, static_cast(Width) - 1)); + const auto expected = Texel(x, layer); + for (std::uint32_t component = 0; component < expected.size(); ++component) Require(std::fabs(Output[tid * 8u + component] * 255.0f - Unorm(expected[component])) <= 1.0f, "registered window: resized bound geometry disagrees with the current registered slice extent"); + } + registered.Resize(rawGeometry.guestBytes); + const auto fullRangeGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + auto fullRowInvocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, cachedRequest); + AgcDriver::ShaderMemory fullRowMemory(cachedRequest.context.memory); + const auto fullRowCapture = AgcDriver::DriverDetail::CaptureInvocation(fullRowMemory, snapshot, 0, cachedRequest, fullRowInvocation); + Require(fullRowInvocation.Request().context.layerRowSamples, "registered window: complete over-limit registration did not produce the full-row mode"); + Require(fullRowCapture->snapshot.imageAllocationGeneration == fullRangeGeneration && fullRowCapture->snapshot.registeredImages.size() == 1u && fullRowCapture->snapshot.registeredImages.front().provenance.dwords == texture, "registered window: full-row capture lost the unchanged descriptor or current allocation generation"); + const auto fullRowResource = AgcDriver::Graphics::DecodeTextureResource(fullRowCapture->snapshot.images.front().dwords); + const auto fullRowGeometry = AgcDriver::Graphics::DescribeSurface(fullRowResource); + Require(fullRowResource.baseArray == baseArray && fullRowGeometry.imageLayers - fullRowResource.baseArray == limit + 1u, "registered window: full-row capture did not retain the complete over-limit guest view"); + const auto fullRowMode = ShaderRecompiler::ResourceMaterializer::RuntimeImageMode(fullRowCapture->plan->info.images.front(), fullRowCapture->snapshot.images.front(), fullRowCapture->plan->info.runtimeImageModes.front()); + Require(fullRowCapture->plan->info.runtimeImageModes.front().at(fullRowMode).layerRows, "registered window: complete over-limit capture did not select layer-row mode"); + const auto fullRowResult = fullRowInvocation.Materialize(*fullRowCapture); + Require(fullRowResult == fullRowInvocation.Materialize(*fullRowCapture), "registered window: identical full-row captures did not reuse the cached materialization"); + Require(fullRowResult != resizedResult, "registered window: full-row transition reused the bounded-native cached materialization"); + const auto fullRowPreparedEntries = snapshot.prepared->entries.size(); + registered.Resize(registeredBytes); + const auto reboundGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + auto reboundInvocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, cachedRequest); + AgcDriver::ShaderMemory reboundMemory(cachedRequest.context.memory); + const auto reboundCapture = AgcDriver::DriverDetail::CaptureInvocation(reboundMemory, snapshot, 0, cachedRequest, reboundInvocation); + Require(reboundCapture->snapshot.imageAllocationGeneration == reboundGeneration && reboundCapture->snapshot.registeredImages.size() == 1u, "registered window: native recapture lost the current bounded allocation generation"); + const auto& reboundView = reboundCapture->snapshot.registeredImages.front(); + Require(reboundView.provenance.dwords == fullRowCapture->snapshot.registeredImages.front().provenance.dwords && reboundView.provenance.dwords == texture && reboundView.effective != fullRowCapture->snapshot.registeredImages.front().effective && reboundView.backingSerial != fullRowCapture->snapshot.registeredImages.front().backingSerial && reboundView.rangeBytes == registeredBytes, "registered window: bounded native recapture reused full-row range identity or changed raw descriptor provenance"); + const auto reboundResource = AgcDriver::Graphics::DecodeTextureResource(reboundCapture->snapshot.images.front().dwords); + const auto reboundGeometry = AgcDriver::Graphics::DescribeSurface(reboundResource); + Require(reboundResource.baseArray == baseArray && reboundGeometry.imageLayers - reboundResource.baseArray == limit, "registered window: bounded native recapture did not restore the device-limit view geometry"); + const auto reboundMode = ShaderRecompiler::ResourceMaterializer::RuntimeImageMode(reboundCapture->plan->info.images.front(), reboundCapture->snapshot.images.front(), reboundCapture->plan->info.runtimeImageModes.front()); + Require(reboundCapture->plan->info.runtimeImageModes.front().at(reboundMode).dimension == ShaderRecompiler::RdnaImageDimension::Dim1DArray && !reboundCapture->plan->info.runtimeImageModes.front().at(reboundMode).layerRows, "registered window: bounded native recapture did not select native 1D-array mode"); + Require(snapshot.prepared->entries.size() == fullRowPreparedEntries, "registered window: bounded native recovery added an unnecessary prepared mode"); + const auto reboundResult = reboundInvocation.Materialize(*reboundCapture); + Require(reboundResult != fullRowResult, "registered window: bounded native recapture reused the full-row materialization"); + dispatchCaptured(reboundResult, reboundCapture); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto layer = baseArray + static_cast(std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(limit - 1u))); + const auto x = static_cast(std::clamp(static_cast(std::floor(std::bit_cast(Input[tid * 4u + 0u]) * static_cast(Width))), 0, static_cast(Width) - 1)); + const auto expected = Texel(x, layer); + for (std::uint32_t component = 0; component < expected.size(); ++component) Require(std::fabs(Output[tid * 8u + component] * 255.0f - Unorm(expected[component])) <= 1.0f, "registered window: bounded native recovery after full-row capture produced incorrect sampled output"); + } + registered.Unregister(); + const auto missingGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + auto missingInvocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, cachedRequest); + AgcDriver::ShaderMemory missingMemory(cachedRequest.context.memory); + const auto missingCapture = AgcDriver::DriverDetail::CaptureInvocation(missingMemory, snapshot, 0, cachedRequest, missingInvocation); + Require(missingInvocation.Request().context.layerRowSamples && missingCapture->snapshot.imageAllocationGeneration == missingGeneration && missingCapture->snapshot.registeredImages.size() == 1u && !missingCapture->snapshot.registeredImages.front().hasRange && missingCapture->snapshot.registeredImages.front().provenance.dwords == texture, "registered window: missing-range capture did not retain the raw descriptor and current absent-range identity"); + const auto missingResult = missingInvocation.Materialize(*missingCapture); + Require(missingResult != reboundResult, "registered window: missing-range materialization reused the bounded-native cached result"); + registered.Resize(registeredBytes); + const auto restoredGeneration = GuestAllocations::GuestAllocationsGeneration_nid_postfix(); + auto restoredInvocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, cachedRequest); + AgcDriver::ShaderMemory restoredMemory(cachedRequest.context.memory); + const auto restoredCapture = AgcDriver::DriverDetail::CaptureInvocation(restoredMemory, snapshot, 0, cachedRequest, restoredInvocation); + Require(restoredCapture->snapshot.imageAllocationGeneration == restoredGeneration && restoredCapture->snapshot.registeredImages.size() == 1u, "registered window: complete registration did not restore native mode after missing-range capture"); + const auto& restoredView = restoredCapture->snapshot.registeredImages.front(); + Require(restoredView.provenance.dwords == texture && restoredView.effective != missingCapture->snapshot.registeredImages.front().effective && restoredView.backingSerial != missingCapture->snapshot.registeredImages.front().backingSerial && restoredView.rangeBytes == registeredBytes && restoredView.hasRange, "registered window: restored native capture reused missing-range identity"); + const auto restoredResource = AgcDriver::Graphics::DecodeTextureResource(restoredCapture->snapshot.images.front().dwords); + const auto restoredGeometry = AgcDriver::Graphics::DescribeSurface(restoredResource); + Require(restoredResource.baseArray == baseArray && restoredGeometry.imageLayers - restoredResource.baseArray == limit, "registered window: restored native capture did not recover bounded view geometry"); + const auto restoredMode = ShaderRecompiler::ResourceMaterializer::RuntimeImageMode(restoredCapture->plan->info.images.front(), restoredCapture->snapshot.images.front(), restoredCapture->plan->info.runtimeImageModes.front()); + Require(restoredCapture->plan->info.runtimeImageModes.front().at(restoredMode).dimension == ShaderRecompiler::RdnaImageDimension::Dim1DArray && !restoredCapture->plan->info.runtimeImageModes.front().at(restoredMode).layerRows, "registered window: restored native capture did not select native 1D-array mode"); + Require(snapshot.prepared->entries.size() == fullRowPreparedEntries, "registered window: missing-range recovery added an unnecessary prepared mode"); + const auto restoredResult = restoredInvocation.Materialize(*restoredCapture); + Require(restoredResult != missingResult && restoredResult != reboundResult, "registered window: restored registration reused stale missing-range or prior-backing materialization"); + dispatchCaptured(restoredResult, restoredCapture); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto layer = baseArray + static_cast(std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(limit - 1u))); + const auto x = static_cast(std::clamp(static_cast(std::floor(std::bit_cast(Input[tid * 4u + 0u]) * static_cast(Width))), 0, static_cast(Width) - 1)); + const auto expected = Texel(x, layer); + for (std::uint32_t component = 0; component < expected.size(); ++component) Require(std::fabs(Output[tid * 8u + component] * 255.0f - Unorm(expected[component])) <= 1.0f, "registered window: native recovery after missing-range capture produced incorrect sampled output"); + } + } +} + +void Expect(std::uint32_t tid, const std::string& name, std::uint32_t component, float scaled, float expected, float tolerance) { + Require(std::fabs(scaled - expected) <= tolerance, name + ": thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(scaled) + "/255, expected " + std::to_string(expected) + "/255"); +} + +struct Compiled { + std::size_t bindings = 0; + std::uint32_t elements = 0; + ShaderRecompiler::SpirvContentHash spirv; + std::uint64_t registeredVariant = 0; + std::uint32_t registeredElements = 0; + std::uint32_t preparedModes = 0; + std::size_t preparedEntries = 0; + bool promotedAtCapture = false; +}; + +Compiled Run(AgcDriver::VulkanDevice& device, std::uint32_t baseArray, std::uint32_t addressX, std::uint32_t filter, const char* name, const std::array& code = SampleLevelZero) { + Output.fill(-1.0f); + std::vector userData(20, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(sizeof(Input))); + const auto output = BufferDescriptor(Output.data(), static_cast(sizeof(Output))); + const auto texture = TextureDescriptor(baseArray); + const std::array sampler{addressX | (ClampEdge << 3u) | (ClampEdge << 6u), 0u, filter, 0u}; + 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); + std::copy(sampler.begin(), sampler.end(), userData.begin() + 16); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(std::span(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); + static AgcDriver::DriverDetail::ShaderSnapshot zeroSnapshot{reinterpret_cast(SampleLevelZero.data()), 0, 0, {SampleLevelZero.begin(), SampleLevelZero.end()}, {}}; + static AgcDriver::DriverDetail::ShaderSnapshot levelSnapshot{reinterpret_cast(SampleLevel.data()), 0, 0, {SampleLevel.begin(), SampleLevel.end()}, {}}; + auto& snapshot = &code == &SampleLevel ? levelSnapshot : zeroSnapshot; + request.shader.code = snapshot.code; + auto invocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, request); + const auto beforeCapture = snapshot.prepared->entries.size(); + AgcDriver::ShaderMemory shaderMemory(request.context.memory); + const auto capture = AgcDriver::DriverDetail::CaptureInvocation(shaderMemory, snapshot, 0, request, invocation); + const auto registered = invocation.Materialize(*capture); + auto prepared = device.PrepareDispatch(*registered, {}, Threads, reinterpret_cast(code.data()), std::nullopt, &capture->snapshot.registeredImages); + device.Dispatch(*registered, 1, 1, 1, {}, reinterpret_cast(code.data()), std::move(prepared), nullptr, std::nullopt, &capture->snapshot.registeredImages); + device.WaitIdle(); + const auto viewed = Layers - baseArray; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const float u = std::bit_cast(Input[tid * 4u + 0u]); + const auto layer = baseArray + static_cast(std::clamp(std::nearbyint(std::bit_cast(Input[tid * 4u + 1u])), 0.0f, static_cast(viewed - 1u))); + const float* actual = &Output[tid * 8u]; + std::array expected{}; + std::array tolerance{0.01f, 0.01f, 0.01f, 0.01f}; + if (filter == 0u) { + const auto scaledU = static_cast(std::floor(u * static_cast(Width))); + const auto x = addressX == Wrap ? static_cast(((scaledU % static_cast(Width)) + static_cast(Width)) % static_cast(Width)) : static_cast(std::clamp(scaledU, 0, static_cast(Width) - 1)); + const auto texel = Texel(x, layer); + for (std::uint32_t component = 0; component < 4u; ++component) expected[component] = Unorm(texel[component]); + } else { + const float position = u * static_cast(Width) - 0.5f; + const auto left = static_cast(std::floor(position)); + const float weight = position - std::floor(position); + const auto clampX = [](int x) { return static_cast(std::clamp(x, 0, static_cast(Width) - 1)); }; + const auto a = Texel(clampX(left), layer); + const auto b = Texel(clampX(left + 1), layer); + for (std::uint32_t component = 0; component < 4u; ++component) expected[component] = Unorm(a[component]) * (1.0f - weight) + Unorm(b[component]) * weight; + tolerance[0] = 1.0f; + } + for (std::uint32_t component = 0; component < 4u; ++component) Expect(tid, name, component, actual[component] * 255.0f, expected[component], tolerance[component]); + } + Compiled compiled{result.bindings.size(), 0u, result.spirv.ContentHash()}; + for (const auto& binding : result.bindings) compiled.elements += binding.count; + compiled.registeredVariant = registered->variantId; + for (const auto& binding : registered->bindings) compiled.registeredElements += binding.count; + for (const auto& image : capture->plan->info.images) compiled.preparedModes += static_cast(ShaderRecompiler::ResourceMaterializer::RuntimeImageModes(image).size()); + compiled.preparedEntries = snapshot.prepared->entries.size(); + compiled.promotedAtCapture = compiled.preparedEntries > beforeCapture; + std::printf("%s: %zu bindings, %u descriptor elements, SPIR-V %016llx%016llx\n", name, compiled.bindings, compiled.elements, static_cast(compiled.spirv.high), static_cast(compiled.spirv.low)); + std::printf("registered: %u descriptor elements, prepared: %u image modes, variant %llu, capture promotion %u\n", compiled.registeredElements, compiled.preparedModes, static_cast(compiled.registeredVariant), compiled.promotedAtCapture ? 1u : 0u); + return compiled; +} + +} + +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; + const auto limit = device->graphicsContext().limits.maxImageArrayLayers; + Require(limit != 0u, "the device reports no maxImageArrayLayers"); + if (limit >= MaxGuestLayers) { + std::puts("skipped: the device allows every array a T# can describe"); + return VulkanTestSkipped; + } + if (limit > MaxGuestLayers - 2u) { + std::puts("skipped: maxImageArrayLayers + 1 from baseArray 1 exceeds the guest descriptor's 13-bit last-array field"); + return VulkanTestSkipped; + } + const auto stillRowsResult = CaptureStillRowsRegisteredWindow(*device, limit); + if (stillRowsResult == VulkanTestSkipped) return stillRowsResult; + CaptureRegisteredWindow(*device, limit); + Layers = std::min(limit + 3u, MaxGuestLayers); + const auto baseWithin = Layers - limit; + const auto basePast = baseWithin - 1u; + FillTexels(); + FillInput(limit); + const auto atLimit = Run(*device, baseWithin, ClampEdge, 0, "point image_sample_lz on a view of exactly maxImageArrayLayers slices"); + const auto atLimitLevel = Run(*device, baseWithin, ClampEdge, Bilinear, "bilinear image_sample_l at levels 0 to 3.4 on a view of exactly maxImageArrayLayers slices", SampleLevel); + const auto pastLimit = Run(*device, basePast, ClampEdge, 0, "point image_sample_lz on a view of maxImageArrayLayers + 1 slices"); + const auto pastLimitLevel = Run(*device, basePast, ClampEdge, Bilinear, "bilinear image_sample_l at levels 0 to 3.4 on a view of maxImageArrayLayers + 1 slices", SampleLevel); + Require(atLimit.preparedModes < pastLimit.preparedModes && atLimitLevel.preparedModes < pastLimitLevel.preparedModes, "a shader that never saw a view past maxImageArrayLayers was given the layer-row mode"); + Require(!(atLimit.spirv == pastLimit.spirv) && !(atLimitLevel.spirv == pastLimitLevel.spirv), "a view past maxImageArrayLayers was sampled through the shader prepared for views within the limit"); + Require(pastLimit.promotedAtCapture && pastLimitLevel.promotedAtCapture, "capturing the first oversized view did not promote its registered shader"); + Require(atLimit.registeredVariant != pastLimit.registeredVariant && atLimitLevel.registeredVariant != pastLimitLevel.registeredVariant, "registered shaders used the same artifact before and after layer-row promotion"); + const auto repeated = Run(*device, basePast, ClampEdge, 0, "repeated oversized view through its promoted registered shader"); + const auto returned = Run(*device, baseWithin, ClampEdge, 0, "within-limit view through its promoted registered shader"); + const auto repeatedLevel = Run(*device, basePast, ClampEdge, Bilinear, "repeated explicit-LOD oversized view through its promoted registered shader", SampleLevel); + const auto returnedLevel = Run(*device, baseWithin, ClampEdge, Bilinear, "within-limit explicit-LOD view through its promoted registered shader", SampleLevel); + for (const auto* reused : {&repeated, &returned}) { + Require(reused->registeredVariant == pastLimit.registeredVariant && reused->preparedEntries == pastLimit.preparedEntries && !reused->promotedAtCapture, "registered layer-row shader was prepared again instead of reused"); + } + for (const auto* reused : {&repeatedLevel, &returnedLevel}) { + Require(reused->registeredVariant == pastLimitLevel.registeredVariant && reused->preparedEntries == pastLimitLevel.preparedEntries && !reused->promotedAtCapture, "registered explicit-LOD layer-row shader was prepared again instead of reused"); + } + Run(*device, 0, ClampEdge, 0, "point image_sample_lz on a 1D array past maxImageArrayLayers"); + Run(*device, 0, Wrap, 0, "wrapped point image_sample_lz on a 1D array past maxImageArrayLayers"); + Run(*device, 0, ClampEdge, Bilinear, "bilinear image_sample_lz on a 1D array past maxImageArrayLayers"); + Run(*device, 0, ClampEdge, Bilinear, "bilinear image_sample_l at levels 0 to 3.4 on a single-level 1D array past maxImageArrayLayers", SampleLevel); + std::puts("a 1D array past maxImageArrayLayers was sampled as rows of a 2D image"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp index d6a5e73941..3185cd2e62 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageArrayWindowPastLayerLimit.cpp @@ -1,4 +1,6 @@ #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" #include "Recompiler.hpp" @@ -9,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +19,7 @@ namespace { +using AgcDriver::Graphics::DccKeys; using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; @@ -24,6 +28,7 @@ constexpr std::uint32_t Width = 8; constexpr std::uint32_t Layers = 3; constexpr std::uint32_t BaseArray = 8100; constexpr std::uint32_t LastArray = BaseArray + Layers - 1u; +constexpr std::uint32_t MaxGuestLayers = 8192; constexpr std::uint32_t Format8888UNorm = 56; constexpr std::uint32_t Type1DArray = 12; constexpr std::uint32_t ClampEdge = 2; @@ -35,24 +40,35 @@ constexpr std::size_t TexelAlignment = 65536; std::array TexelStorage{}; const std::span Texels{reinterpret_cast((reinterpret_cast(TexelStorage.data()) + TexelAlignment - 1) & ~(TexelAlignment - 1)), TexelBytes}; +constexpr std::size_t ClearKeyBytes = TexelBytes / 256u; +alignas(256) std::array ClearKeys{}; + alignas(256) constexpr std::array Code{ 0x34020084, 0xe0381000, 0x80000201, 0xbf8c3f70, 0xf09c0f20, 0x00820602, 0xf0001f20, 0x00020a04, 0x34020085, 0xbf8c3f70, 0xe0781000, 0x80010601, 0xe0781010, 0x80010a01, 0xbf810000, }; +alignas(256) constexpr std::array SampleOnlyCode{ + 0x34020084, 0xe0381000, 0x80000201, 0xbf8c3f70, 0xf09c0f20, 0x00820602, 0x34020085, 0xbf8c3f70, + 0xe0781000, 0x80010601, 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::array TextureDescriptor(std::uint32_t baseArray = BaseArray) { +std::array TextureDescriptor(std::uint32_t baseArray = BaseArray, std::uint32_t lastArray = LastArray, bool dccClear = false) { const auto address = static_cast(reinterpret_cast(Texels.data())); + const auto meta = dccClear ? static_cast(reinterpret_cast(ClearKeys.data())) >> 8u : 0u; return { static_cast(address >> 8u), static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Width - 1u) & 3u) << 30u), (Width - 1u) >> 2u, 0xfacu | (Type1DArray << 28u), - LastArray | (baseArray << 16u), 0u, 0u, 0u, + lastArray | (baseArray << 16u), 0u, + dccClear ? (1u << 20u) | (1u << 21u) | (static_cast(meta & 0xffu) << 24u) : 0u, + static_cast(meta >> 8u), }; } @@ -94,18 +110,18 @@ void Expect(std::uint32_t tid, const char* name, const float* actual, const std: } } -void Run(AgcDriver::VulkanDevice& device, std::uint32_t baseArray = BaseArray) { +void Run(AgcDriver::VulkanDevice& device, std::uint32_t baseArray = BaseArray, std::uint32_t lastArray = LastArray, bool dccClear = false) { Output.fill(-1.0f); std::vector userData(20, 0u); const auto input = BufferDescriptor(Input.data(), static_cast(sizeof(Input))); const auto output = BufferDescriptor(Output.data(), static_cast(sizeof(Output))); - const auto texture = TextureDescriptor(baseArray); + const auto texture = TextureDescriptor(baseArray, lastArray, dccClear); const auto sampler = SamplerDescriptor(); 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); std::copy(sampler.begin(), sampler.end(), userData.begin() + 16); - const std::span code(Code); + const std::span code = dccClear ? std::span(SampleOnlyCode) : std::span(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{ @@ -116,14 +132,24 @@ void Run(AgcDriver::VulkanDevice& device, std::uint32_t baseArray = BaseArray) { }; request.useCache = false; const auto result = ShaderRecompiler::Recompile(request); + const auto clearsBefore = AgcDriver::Graphics::SampledTexturesCleared(); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); + if (dccClear) { + const auto resource = AgcDriver::Graphics::DecodeTextureResource(texture); + Require(AgcDriver::Graphics::TextureClearKeys(resource, AgcDriver::Graphics::DescribeSurface(resource).guestBytes) == DccKeys::Clear0000, "the 1D array fixture does not expose a DCC zero clear"); + if (std::getenv("APS5_NO_TEXTURE_CACHE") == nullptr) Require(AgcDriver::Graphics::SampledTexturesCleared() == clearsBefore + 1u, "the over-limit DCC row window was not materialized by a GPU clear"); + } 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(LastArray - baseArray)); + 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])); + if (dccClear) { + Expect(tid, "image_sample_lz of a DCC-cleared row window", &Output[tid * 8u], {0, 0, 0, 0}); + } else { + 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])); + } } } @@ -137,6 +163,21 @@ int main() { FillTexels(); FillInput(); Run(*device); + const auto& limits = device->graphicsContext().limits; + const auto limit = limits.maxImageArrayLayers; + Require(limit != 0u, "the device reports no maxImageArrayLayers"); + if (limit >= MaxGuestLayers - 1u) { + std::fprintf(stderr, "skipped: maxImageArrayLayers %u leaves no encodable over-limit array index below %u\n", limit, MaxGuestLayers); + return VulkanTestSkipped; + } + if (limit + 1u > limits.maxImageDimension2D / 2u) { + std::fprintf(stderr, "skipped: maxImageArrayLayers %u needs %u physical rows but maxImageDimension2D %u permits %u\n", limit, 2u * (limit + 1u), limits.maxImageDimension2D, limits.maxImageDimension2D / 2u); + return VulkanTestSkipped; + } + const auto clearResource = AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor(1u, limit + 1u, true)); + const auto clearBytes = AgcDriver::Graphics::DescribeSurface(clearResource).guestBytes; + Require(clearBytes <= Texels.size() && AgcDriver::Graphics::DccKeyBytes(clearBytes) <= ClearKeys.size(), "the over-limit clear fixture exceeds its guest texel or DCC-key storage"); + Run(*device, 1u, limit + 1u, true); try { Run(*device, 0); } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageConvertedUnorm.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageConvertedUnorm.cpp index bb50f103fd..8e302dd989 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageConvertedUnorm.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageConvertedUnorm.cpp @@ -51,11 +51,12 @@ constexpr std::uint32_t DivisionUlps = 3; constexpr std::size_t BlockBytes = 65536; constexpr std::size_t RoundTripOffset = 16384; constexpr std::size_t StoreOffset = 32768; +constexpr std::uint32_t D16Width = 1024; +constexpr std::uint32_t D16Groups = D16Width / Threads; constexpr std::array PointSampler{0x00000092u, 0x00fff000u, 0u, 0u}; constexpr std::array BilinearSampler{0x00000092u, 0x00fff000u, 0x00500000u, 0u}; constexpr std::array WrapBilinearSampler{0x00000090u, 0x00fff000u, 0x00500000u, 0u}; constexpr std::array WrapPointSampler{0x00000090u, 0x00fff000u, 0u, 0u}; -constexpr std::array BorderSampler{0x000000b6u, 0x00fff000u, 0u, 0u}; constexpr std::uint32_t SampleStep = 0x3a200000u; constexpr std::uint32_t OffGridStep = 0x3a206000u; constexpr std::uint32_t SampleStart = 0x39c00000u; @@ -72,6 +73,14 @@ constexpr std::array LoadCode(std::uint32_t dmask, std::uint3 }; } +constexpr std::array LoadStoreCode(std::uint32_t dmask, std::uint32_t d16) { + return { + 0x7e080218u, 0x343c0885u, 0x4a3c3d00u, 0x34063c84u, 0x7e3e0280u, 0x7e140280u, 0x7e160280u, 0x7e180280u, + 0x7e1a0280u, 0xf0001008u | (dmask << 8u), 0x00010a1eu | d16, 0xbf8c3f70u, 0xe0781000u, 0x80000a03u, 0xf0201008u | (dmask << 8u), 0x00010a1eu | d16, + 0xbf810000u, + }; +} + constexpr std::array StoreCode(std::uint32_t dmask, std::uint32_t d16) { return { 0x34060084u, 0xe0381000u, 0x80030a03u, 0x7e3c0300u, 0x7e3e0280u, 0xbf8c3f70u, 0xf0201008u | (dmask << 8u), 0x00010a1eu | d16, @@ -102,12 +111,12 @@ alignas(256) constexpr auto LoadXyzw = LoadCode(0xfu, 0u); alignas(256) constexpr auto LoadXyz = LoadCode(0x7u, 0u); alignas(256) constexpr auto LoadXy = LoadCode(0x3u, 0u); alignas(256) constexpr auto LoadD16 = LoadCode(0xfu, D16); +alignas(256) constexpr auto LoadStoreD16 = LoadStoreCode(0x7u, D16); alignas(256) constexpr auto StoreXyz = StoreCode(0x7u, 0u); alignas(256) constexpr auto StoreXz = StoreCode(0x5u, 0u); alignas(256) constexpr auto StoreD16 = StoreCode(0x7u, D16); +alignas(256) constexpr auto StoreXzD16 = StoreCode(0x5u, D16); alignas(256) constexpr auto SampleLz = SamplerCode(0xf09c0f08u); -alignas(256) constexpr auto Gather4Lz = SamplerCode(0xf11c0108u); -alignas(256) constexpr auto GetLod = SamplerCode(0xf1800308u); alignas(256) constexpr auto SampleAt = SampleAtCode(SampleStep, SampleStart); alignas(256) constexpr auto SampleWrapped = SampleAtCode(SampleStep, WrappedStart); alignas(256) constexpr auto SampleOffGrid = SampleAtCode(OffGridStep, SampleStart); @@ -118,6 +127,28 @@ constexpr std::array StoreValues{ 0x40000000u, 0x7f800000u, 0x7fc00000u, 0x000116c2u, 0x3f7fbe77u, }; +constexpr std::array HalfStoreValues{ + 0x0000u, 0x3c00u, 0x3800u, 0x3555u, 0x8000u, 0xbc00u, + 0x4000u, 0x7c00u, 0x7e00u, 0x0001u, 0x3bffu, +}; + +std::uint32_t HalfToFloatBits(std::uint32_t half) { + const auto sign = (half & 0x8000u) << 16u; + const auto exponent = (half >> 10u) & 0x1fu; + const auto mantissa = half & 0x3ffu; + if (exponent == 31u) return sign | 0x7f800000u | (mantissa << 13u); + if (exponent != 0u) return sign | ((exponent + 112u) << 23u) | (mantissa << 13u); + return sign | std::bit_cast(static_cast(mantissa) * 0x1p-24f); +} + +bool HalfNan(std::uint32_t half) { + return (half & 0x7c00u) == 0x7c00u && (half & 0x3ffu) != 0u; +} + +std::uint32_t ResultHalf(std::uint32_t index, std::uint32_t component) { + return (Output[index * 4u + component / 2u] >> ((component % 2u) * 16u)) & 0xffffu; +} + class GuestBlock { public: GuestBlock() { @@ -252,17 +283,23 @@ void CheckRoundTrip(AgcDriver::VulkanDevice& device, std::uint8_t* texels) { } } -void CheckStore(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::span code, std::uint32_t dmask, const std::string& what) { +void CheckStore(AgcDriver::VulkanDevice& device, std::uint8_t* texels, std::span code, std::uint32_t dmask, const std::string& what, bool d16 = false) { std::fill(texels, texels + Threads * 4u, static_cast(0xa5u)); Input.fill(0u); std::array expected{}; + const auto& source = d16 ? HalfStoreValues : StoreValues; for (std::uint32_t tid = 0; tid < Threads; ++tid) { - const std::array values{StoreValues[tid % 11u], StoreValues[(tid + 4u) % 11u], StoreValues[(tid + 8u) % 11u]}; + const std::array values{source[tid % 11u], source[(tid + 4u) % 11u], source[(tid + 8u) % 11u]}; std::uint32_t slot = 0; for (std::uint32_t component = 0; component < 3u; ++component) { if (((dmask >> component) & 1u) == 0u) continue; - Input[tid * 4u + slot++] = values[component]; - expected[tid] |= StoreField(values[component], component == 2u ? 1023u : 2047u) << (component * 11u); + if (d16) { + Input[tid * 4u + slot / 2u] |= values[component] << ((slot % 2u) * 16u); + } else { + Input[tid * 4u + slot] = values[component]; + } + ++slot; + expected[tid] |= StoreField(d16 ? HalfToFloatBits(values[component]) : values[component], component == 2u ? 1023u : 2047u) << (component * 11u); } } Run(device, code, TextureDescriptor(texels, UnormFormat, SwizzleXYZ1, Threads), BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)), 1); @@ -431,6 +468,148 @@ void CheckSample(AgcDriver::VulkanDevice& device, const std::uint8_t* texels, st } } +void CheckUnormLoadD16(AgcDriver::VulkanDevice& device, const std::uint8_t* texels, std::uint32_t swizzle, const std::string& what) { + Run(device, LoadD16, TextureDescriptor(texels, UnormFormat, swizzle, LoadWidth), {}, LoadGroups); + for (std::uint32_t index = 0; index < LoadWidth; ++index) { + for (std::uint32_t component = 0; component < 4u; ++component) { + const auto actual = ResultHalf(index, component); + const auto expected = ChannelValue(LoadTexel(index), swizzle, component); + const auto tolerance = expected == 0.0 || expected == 1.0 ? 0.0 : std::ldexp(1.0, std::ilogb(expected) - 10); + const auto value = static_cast(std::bit_cast(HalfToFloatBits(actual))); + Require((actual & 0x8000u) == 0u && std::fabs(value - expected) <= tolerance, what + ": texel " + std::to_string(index) + " component " + std::to_string(component) + " is " + Hex(actual) + ", expected " + std::to_string(expected) + " within one half ULP"); + } + Require(Output[index * 4u + 2u] == 0u && Output[index * 4u + 3u] == 0u, what + ": texel " + std::to_string(index) + " writes more than two result dwords"); + } +} + +struct FloatLayout { + std::uint32_t format; + std::array offsets; + std::array mantissas; + std::size_t loadOffset; + std::size_t storeOffset; + const char* name; +}; + +constexpr std::array FloatLayouts{{ + {FloatFormat, {0u, 10u, 21u}, {5u, 6u, 6u}, 49152, 57344, "R10_G11_B11_FLOAT"}, + {NativeFloatFormat, {0u, 11u, 22u}, {6u, 6u, 5u}, 53248, 58368, "R11_G11_B10_FLOAT"}, +}}; + +constexpr std::array, 8> HalfStoreEdges{{ + {0xbc00u, 0x8000u, 0x7e00u}, + {0x7c00u, 0x7bffu, 0xfc00u}, + {0x3c01u, 0x3c10u, 0x3c30u}, + {0x0001u, 0x03ffu, 0x0400u}, + {0xfe00u, 0x7c01u, 0x7dffu}, + {0x3bffu, 0x7800u, 0x7bf8u}, + {0x0010u, 0x0200u, 0x3c08u}, + {0x7bf0u, 0x1400u, 0x3c18u}, +}}; + +std::uint32_t SmallFloatField(std::uint32_t index, std::uint32_t component, std::uint32_t mantissa) { + constexpr std::array exponents{0u, 0u, 0u, 1u, 15u, 30u, 31u, 31u}; + const auto maximum = (1u << mantissa) - 1u; + const std::array mantissas{0u, 1u, maximum, 0u, 0u, maximum, 0u, 1u}; + const auto special = (index + component * 3u) % 8u; + if (index < 64u) return (exponents[special] << mantissa) | mantissas[special]; + return ((index * 2654435761u) >> (component * 7u + 3u)) & ((1u << (mantissa + 5u)) - 1u); +} + +std::uint32_t ExactHalf(float value) { + if (value == 0.0f) return 0u; + const auto bits = std::bit_cast(value); + return ((((bits >> 23u) & 0xffu) - 112u) << 10u) | ((bits & 0x7fffffu) >> 13u); +} + +void CheckFloatLoadD16(AgcDriver::VulkanDevice& device, std::uint8_t* block, const FloatLayout& layout, bool storage) { + auto* texels = block + layout.loadOffset; + const auto texelAt = [&](std::uint32_t index) { + std::uint32_t texel = 0; + for (std::uint32_t component = 0; component < 3u; ++component) texel |= SmallFloatField(index, component, layout.mantissas[component]) << layout.offsets[component]; + return texel; + }; + for (std::uint32_t index = 0; index < D16Width; ++index) { + const auto texel = texelAt(index); + std::memcpy(texels + index * 4u, &texel, 4u); + } + const auto what = std::string(storage ? "image_load d16 then image_store d16 of " : "image_load d16 of ") + layout.name; + Run(device, storage ? std::span(LoadStoreD16) : std::span(LoadD16), TextureDescriptor(texels, layout.format, SwizzleXYZ1, D16Width), {}, D16Groups); + for (std::uint32_t index = 0; index < D16Width; ++index) { + for (std::uint32_t component = 0; component < 4u; ++component) { + const auto actual = ResultHalf(index, component); + auto expected = storage ? 0u : 0x3c00u; + if (component < 3u) { + const auto mantissa = layout.mantissas[component]; + const auto field = SmallFloatField(index, component, mantissa); + expected = ((field >> mantissa) << 10u) | ((field & ((1u << mantissa) - 1u)) << (10u - mantissa)); + } + Require(HalfNan(expected) ? HalfNan(actual) : actual == expected, what + ": texel " + std::to_string(index) + " component " + std::to_string(component) + " is " + Hex(actual) + ", expected " + Hex(expected)); + } + Require(Output[index * 4u + 2u] == 0u && Output[index * 4u + 3u] == 0u, what + ": texel " + std::to_string(index) + " writes more than two result dwords"); + } + if (!storage) return; + AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast(texels), D16Width * 4u, nullptr, "test"); + device.WaitIdle(); + for (std::uint32_t index = 0; index < D16Width; ++index) { + std::uint32_t actual = 0; + std::memcpy(&actual, texels + index * 4u, 4u); + for (std::uint32_t component = 0; component < 3u; ++component) { + const auto mantissa = layout.mantissas[component]; + const auto mask = (1u << (mantissa + 5u)) - 1u; + const auto field = (actual >> layout.offsets[component]) & mask; + const auto expected = SmallFloatField(index, component, mantissa); + const auto nan = [&](std::uint32_t value) { return (value >> mantissa) == 31u && (value & ((1u << mantissa) - 1u)) != 0u; }; + Require(nan(expected) ? nan(field) : field == expected, what + ": texel " + std::to_string(index) + " is " + Hex(actual) + " after the round trip, expected " + Hex(texelAt(index))); + } + } +} + +void CheckFloatStoreD16(AgcDriver::VulkanDevice& device, std::uint8_t* block, const FloatLayout& layout, std::uint32_t dmask) { + auto* written = block + layout.storeOffset; + auto* reference = written + 256u; + std::array, Threads> halves{}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + halves[tid] = tid < HalfStoreEdges.size() ? HalfStoreEdges[tid] : std::array{ExactHalf(FloatValues[tid % 11u]), ExactHalf(FloatValues[(tid + 4u) % 11u]), ExactHalf(FloatValues[(tid + 8u) % 11u])}; + } + const auto store = [&](std::uint8_t* target, std::span code, bool d16) { + std::fill(target, target + Threads * 4u, static_cast(0xa5u)); + Input.fill(0u); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::uint32_t slot = 0; + for (std::uint32_t component = 0; component < 3u; ++component) { + if (((dmask >> component) & 1u) == 0u) continue; + if (d16) { + Input[tid * 4u + slot / 2u] |= halves[tid][component] << ((slot % 2u) * 16u); + } else { + Input[tid * 4u + slot] = HalfToFloatBits(halves[tid][component]); + } + ++slot; + } + } + Run(device, code, TextureDescriptor(target, layout.format, SwizzleXYZ1, Threads), BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)), 1); + AgcDriver::Graphics::StorageTexture::FlushPending(reinterpret_cast(target), Threads * 4u, nullptr, "test"); + device.WaitIdle(); + }; + store(reference, dmask == 0x7u ? std::span(StoreXyz) : std::span(StoreXz), false); + store(written, dmask == 0x7u ? std::span(StoreD16) : std::span(StoreXzD16), true); + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + std::uint32_t actual = 0; + std::uint32_t widened = 0; + std::memcpy(&actual, written + tid * 4u, 4u); + std::memcpy(&widened, reference + tid * 4u, 4u); + const auto what = std::string("image_store d16 dmask:") + Hex(dmask) + " of " + layout.name + ": texel " + std::to_string(tid); + if (tid >= HalfStoreEdges.size()) { + std::uint32_t expected = 0; + for (std::uint32_t component = 0; component < 3u; ++component) { + if (((dmask >> component) & 1u) != 0u) expected |= SmallFloat(FloatValues[(tid + component * 4u) % 11u], layout.mantissas[component]) << layout.offsets[component]; + } + Require(actual == expected, what + " is " + Hex(actual) + ", expected " + Hex(expected)); + } + Require(actual == widened, what + " is " + Hex(actual) + ", but the 32-bit store of the same values wrote " + Hex(widened)); + } +} + void CheckConvertedPreparation(AgcDriver::VulkanDevice& device, const std::uint8_t* texels) { std::vector userData(24, 0u); const auto texture = TextureDescriptor(texels, UnormFormat, SwizzleXYZ1, LoadWidth); @@ -595,11 +774,16 @@ int main(int argc, char** argv) { CheckSample(*device, texels, SampleOffGrid, OffGridStep, SampleStart, SwizzleZYX1, BilinearSampler, true, false, "off-grid bilinear image_sample_lz Z Y X 1"); CheckSample(*device, texels, SampleWrappedOffGrid, OffGridStep, WrappedStart, SwizzleXYZ1, WrapBilinearSampler, true, true, "off-grid wrapped bilinear image_sample_lz X Y Z 1"); CheckConvertedPreparation(*device, texels); - RequireRefused(*device, SampleLz, texels, SwizzleXYZ1, BorderSampler, "implemented only with wrap or clamp-to-edge addressing", "image_sample_lz with clamp-to-border addressing"); - RequireRefused(*device, Gather4Lz, texels, SwizzleXYZ1, PointSampler, "sampling, gathering or querying LOD of a converted unorm image", "image_gather4_lz"); - RequireRefused(*device, GetLod, texels, SwizzleXYZ1, PointSampler, "sampling, gathering or querying LOD of a converted unorm image", "image_get_lod"); - RequireRefused(*device, LoadD16, texels, SwizzleXYZ1, {}, "converted unorm image with 16-bit data", "image_load d16"); - RequireRefused(*device, StoreD16, texels + StoreOffset, SwizzleXYZ1, BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)), "converted unorm image with 16-bit data", "image_store d16"); + for (const auto& layout : FloatLayouts) { + CheckFloatStoreD16(*device, texels, layout, 0x7u); + CheckFloatStoreD16(*device, texels, layout, 0x5u); + CheckFloatLoadD16(*device, texels, layout, true); + CheckFloatLoadD16(*device, texels, layout, false); + } + CheckUnormLoadD16(*device, texels, SwizzleXYZ1, "image_load d16 X Y Z 1"); + CheckUnormLoadD16(*device, texels, SwizzleZYX1, "image_load d16 Z Y X 1"); + CheckStore(*device, texels + StoreOffset, StoreD16, 0x7u, "image_store d16 dmask:0x7", true); + CheckStore(*device, texels + StoreOffset, StoreXzD16, 0x5u, "image_store d16 dmask:0x5", true); RequireRefused(*device, LoadXyzw, texels, SwizzleXYZW, {}, "selects a channel the converted image format does not have", "image_load with DST_SEL X Y Z W"); RequireRefused(*device, StoreXyz, texels + StoreOffset, SwizzleXYZW, BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)), "selects a channel the converted image format does not have", "image_store with DST_SEL X Y Z W"); std::puts("image converted unorm tests passed"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp index 51e039534e..f40e29c4bd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleDerivatives.cpp @@ -80,7 +80,7 @@ alignas(256) constexpr std::array Input{ alignas(256) std::array Buffer{}; alignas(256) std::array Texels{}; -alignas(256) constexpr std::array Code2D{ +alignas(256) constexpr std::array Code2D{ 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, @@ -93,15 +93,25 @@ alignas(256) constexpr std::array Code2D{ 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1a00108, 0x00c40c40, 0xbefe031e, 0x7e80032c, 0x7e82032e, 0x7e840304, 0x7e860305, 0xf0880109, 0x00c40d40, 0x7e80032c, 0x7e82032e, 0x7e840304, 0x7e860305, 0xf1a00109, 0x00c40e40, 0x7e800328, 0x7e820329, 0x7e840332, 0x7e860333, 0x7e880330, 0xf0880108, - 0x40c40f40, 0x7e80032c, 0x7e82032e, 0x7e840330, 0xf0880109, 0x40c41040, 0x7e80033c, 0x7e820328, - 0x7e840329, 0x7e860332, 0x7e880333, 0x7e8a0304, 0x7e8c0305, 0xf1b00108, 0x00c41140, 0x7e80033e, - 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, 0xf0c80109, 0x00c41240, 0x7e80033c, 0x7e82032c, - 0x7e84032e, 0x7e860304, 0x7e880305, 0xf1b00109, 0x00c41340, 0x7e80033c, 0x7e82032c, 0x7e84032e, - 0x7e860330, 0xf0c80109, 0x40c41440, 0x7e80033c, 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, - 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1b00109, 0x00c41540, 0xbefe031e, 0xbf8c3f70, 0xe0701600, + 0x40c40f40, 0x7e80032c, 0x7e82032e, 0x7e840330, 0xf0880109, 0x40c41040, 0xbf8c3f70, 0xe0701600, 0x80000a03, 0xe0701604, 0x80000b03, 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, - 0x80000e03, 0xe0701614, 0x80000f03, 0xe0701618, 0x80001003, 0xe070161c, 0x80001103, 0xe0701620, - 0x80001203, 0xe0701624, 0x80001303, 0xe0701628, 0x80001403, 0xe070162c, 0x80001503, 0xbf810000, + 0x80000e03, 0xe0701614, 0x80000f03, 0xe0701618, 0x80001003, 0xbf810000, +}; + +alignas(256) constexpr std::array Code2DOffsets{ + 0x160200b0, 0x34060086, 0xe0381000, 0x80000401, 0xe0381010, 0x80002801, 0xe0381020, 0x80003201, + 0xbe900404, 0xbe920406, 0xbe940408, 0xbe96040a, 0xbe98040c, 0xbe9a040e, 0x7e140280, 0x7e160280, + 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, 0x7e240280, 0x7e260280, + 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, 0xbf8c3f70, 0x5e585328, + 0xd52f002d, 0x0201012a, 0x5e5c6732, 0xd52f002f, 0x02010134, 0x5e600b04, 0xd52f0031, 0x02010106, + 0xd52f0036, 0x02010128, 0xd52f0037, 0x02010132, 0xd52f003b, 0x02010104, 0x7e780281, 0x7e7a02f6, + 0x7e7c02ff, 0x00000101, 0x7e80033c, 0x7e820328, 0x7e840329, 0x7e860332, 0x7e880333, 0x7e8a0304, + 0x7e8c0305, 0xf1b00108, 0x00c41140, 0x7e80033e, 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, + 0xf0c80109, 0x00c41240, 0x7e80033c, 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, 0xf1b00109, + 0x00c41340, 0x7e80033c, 0x7e82032c, 0x7e84032e, 0x7e860330, 0xf0c80109, 0x40c41440, 0x7e80033c, + 0x7e82032c, 0x7e84032e, 0x7e860304, 0x7e880305, 0xbe9e037e, 0xbefe03ff, 0xaeeeffff, 0xf1b00109, + 0x00c41540, 0xbefe031e, 0xbf8c3f70, 0xe0701600, 0x80001103, 0xe0701604, 0x80001203, 0xe0701608, + 0x80001303, 0xe070160c, 0x80001403, 0xe0701610, 0x80001503, 0xbf810000, }; alignas(256) constexpr std::array Code3D{ @@ -137,57 +147,77 @@ alignas(256) constexpr std::array Code1D{ 0xe0701608, 0x80000c03, 0xe070160c, 0x80000d03, 0xe0701610, 0x80000e03, 0xbf810000, }; -constexpr std::array Names2D{"image_sample_d", "image_sample_cd", "image_sample_cd (exec)", "image_sample_d_g16", "image_sample_cd_g16", "image_sample_d a16", "image_sample_d_g16 a16", "image_sample_cd_o", "image_sample_d_o_g16", "image_sample_cd_o_g16", "image_sample_d_o_g16 a16", "image_sample_cd_o_g16 (exec)"}; -constexpr std::array Masked2D{false, false, true, false, false, false, false, false, false, false, false, true}; -constexpr std::array Expected2D{ - 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x40100000, - 0x42cc8000, 0x40100000, 0x40100000, 0x40100000, 0x42cc0000, 0x42cc8000, 0x42cc8000, 0x42cc0000, - 0x42cc8000, 0x42cc0000, 0x42cc0000, 0x42ce8000, 0x434b0000, 0x42ce8000, 0x42ce0000, 0x42ce8000, - 0x434b0000, 0x434b4000, 0x434b4000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b0000, 0x434c4000, - 0x43980000, 0x434c4000, 0x434c0000, 0x434c4000, 0x43980000, 0x43982000, 0x43982000, 0x43980000, - 0x43982000, 0x43980000, 0x43980000, 0x4398a000, 0x43988000, 0x4398a000, 0x43988000, 0x4398a000, - 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40c00000, - 0x42d40000, 0x40c00000, 0x40c00000, 0x40c00000, 0x42d48000, 0x42d40000, 0x42d40000, 0x42d48000, - 0x42d40000, 0x42d48000, 0x42d48000, 0x42d60000, 0x434f4000, 0x42d60000, 0x42d68000, 0x42d60000, - 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x43500000, - 0x439a0000, 0x43500000, 0x43500000, 0x43500000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, - 0x439a0000, 0x439a0000, 0x439a0000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, - 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41200000, - 0x42dc0000, 0x41200000, 0x41200000, 0x41200000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, - 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42de0000, 0x43530000, 0x42de0000, 0x42de0000, 0x42de0000, - 0x43534000, 0x43530000, 0x43530000, 0x43534000, 0x43530000, 0x43534000, 0x43534000, 0x43540000, - 0x439c2000, 0x43540000, 0x43544000, 0x43540000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, - 0x439c0000, 0x439c0000, 0x439c0000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, - 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41600000, - 0x42e40000, 0x41600000, 0x41600000, 0x41600000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, - 0x42e40000, 0x42e40000, 0x42e40000, 0x42e60000, 0x43570000, 0x42e60000, 0x42e60000, 0x42e60000, - 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43580000, - 0x439e0000, 0x43580000, 0x43580000, 0x43580000, 0x439e2000, 0x439e0000, 0x439e0000, 0x439e2000, - 0x439e0000, 0x439e2000, 0x439e2000, 0x439e8000, 0x439ea000, 0x439e8000, 0x439ea000, 0x439e8000, - 0x41880000, 0x41880000, 0x00000000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, 0x41900000, - 0x42ec0000, 0x41900000, 0x41900000, 0x00000000, 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, - 0x42ec0000, 0x42ec8000, 0x42ec8000, 0x42ee0000, 0x435b4000, 0x42ee0000, 0x42ee8000, 0x42ee8000, - 0x435b0000, 0x435b0000, 0x435b4000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435c0000, - 0x43a00000, 0x435c0000, 0x435c0000, 0x435c4000, 0x43a00000, 0x43a00000, 0x43a02000, 0x43a00000, - 0x43a00000, 0x43a00000, 0x43a00000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a0a000, - 0x41a80000, 0x41a80000, 0x00000000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41b00000, - 0x42f40000, 0x41b00000, 0x41b00000, 0x00000000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, - 0x42f40000, 0x42f40000, 0x42f40000, 0x42f60000, 0x435f0000, 0x42f60000, 0x42f60000, 0x42f60000, - 0x435f4000, 0x435f0000, 0x435f0000, 0x435f4000, 0x435f0000, 0x435f4000, 0x435f4000, 0x43600000, - 0x43a22000, 0x43600000, 0x43604000, 0x43600000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, - 0x43a20000, 0x43a20000, 0x43a20000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, - 0x41c80000, 0x41c80000, 0x00000000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41d00000, - 0x42fc0000, 0x41d00000, 0x41d00000, 0x00000000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, - 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fe0000, 0x43630000, 0x42fe0000, 0x42fe0000, 0x42fe0000, - 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43640000, - 0x43a40000, 0x43640000, 0x43640000, 0x43640000, 0x43a42000, 0x43a40000, 0x43a40000, 0x43a42000, - 0x43a40000, 0x43a42000, 0x43a42000, 0x43a48000, 0x43a4a000, 0x43a48000, 0x43a4a000, 0x43a48000, - 0x41e80000, 0x41e80000, 0x00000000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41f00000, - 0x43020000, 0x41f00000, 0x41f00000, 0x00000000, 0x43024000, 0x43020000, 0x43024000, 0x43024000, - 0x43020000, 0x43024000, 0x43024000, 0x43030000, 0x43674000, 0x43030000, 0x43034000, 0x43034000, - 0x43670000, 0x43670000, 0x00000000, 0x43670000, 0x43670000, 0x43670000, 0x43670000, 0x43680000, - 0x43a60000, 0x43680000, 0x43680000, 0x00000000, 0x43a60000, 0x43a60000, 0x43a62000, 0x43a60000, - 0x43a60000, 0x43a60000, 0x43a60000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a6a000, +constexpr std::array Names2D{"image_sample_d", "image_sample_cd", "image_sample_cd (exec)", "image_sample_d_g16", "image_sample_cd_g16", "image_sample_d a16", "image_sample_d_g16 a16"}; +constexpr std::array Masked2D{false, false, true, false, false, false, false}; +constexpr std::array Names2DOffsets{"image_sample_cd_o", "image_sample_d_o_g16", "image_sample_cd_o_g16", "image_sample_d_o_g16 a16", "image_sample_cd_o_g16 (exec)"}; +constexpr std::array Masked2DOffsets{false, false, false, false, true}; +constexpr std::array Expected2D{ + 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, 0x3fa00000, + 0x42cc0000, 0x42cc8000, 0x42cc8000, 0x42cc0000, 0x42cc8000, 0x42cc0000, 0x42cc0000, + 0x434b0000, 0x434b4000, 0x434b4000, 0x434b0000, 0x434b4000, 0x434b0000, 0x434b0000, + 0x43980000, 0x43982000, 0x43982000, 0x43980000, 0x43982000, 0x43980000, 0x43980000, + 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, 0x40a00000, + 0x42d48000, 0x42d40000, 0x42d40000, 0x42d48000, 0x42d40000, 0x42d48000, 0x42d48000, + 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, 0x434f0000, + 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, 0x439a0000, + 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, 0x41100000, + 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, 0x42dc0000, + 0x43534000, 0x43530000, 0x43530000, 0x43534000, 0x43530000, 0x43534000, 0x43534000, + 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, 0x439c0000, + 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, 0x41500000, + 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, 0x42e40000, + 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, 0x43570000, + 0x439e2000, 0x439e0000, 0x439e0000, 0x439e2000, 0x439e0000, 0x439e2000, 0x439e2000, + 0x41880000, 0x41880000, 0x00000000, 0x41880000, 0x41880000, 0x41880000, 0x41880000, + 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, 0x42ec0000, 0x42ec8000, 0x42ec8000, + 0x435b0000, 0x435b0000, 0x435b4000, 0x435b0000, 0x435b0000, 0x435b0000, 0x435b0000, + 0x43a00000, 0x43a00000, 0x43a02000, 0x43a00000, 0x43a00000, 0x43a00000, 0x43a00000, + 0x41a80000, 0x41a80000, 0x00000000, 0x41a80000, 0x41a80000, 0x41a80000, 0x41a80000, + 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, 0x42f40000, + 0x435f4000, 0x435f0000, 0x435f0000, 0x435f4000, 0x435f0000, 0x435f4000, 0x435f4000, + 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, 0x43a20000, + 0x41c80000, 0x41c80000, 0x00000000, 0x41c80000, 0x41c80000, 0x41c80000, 0x41c80000, + 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, 0x42fc0000, + 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, 0x43630000, + 0x43a42000, 0x43a40000, 0x43a40000, 0x43a42000, 0x43a40000, 0x43a42000, 0x43a42000, + 0x41e80000, 0x41e80000, 0x00000000, 0x41e80000, 0x41e80000, 0x41e80000, 0x41e80000, + 0x43024000, 0x43020000, 0x43024000, 0x43024000, 0x43020000, 0x43024000, 0x43024000, + 0x43670000, 0x43670000, 0x00000000, 0x43670000, 0x43670000, 0x43670000, 0x43670000, + 0x43a60000, 0x43a60000, 0x43a62000, 0x43a60000, 0x43a60000, 0x43a60000, 0x43a60000, +}; +constexpr std::array Expected2DOffsets{ + 0x40100000, 0x42cc8000, 0x40100000, 0x40100000, 0x40100000, + 0x42ce8000, 0x434b0000, 0x42ce8000, 0x42ce0000, 0x42ce8000, + 0x434c4000, 0x43980000, 0x434c4000, 0x434c0000, 0x434c4000, + 0x4398a000, 0x43988000, 0x4398a000, 0x43988000, 0x4398a000, + 0x40c00000, 0x42d40000, 0x40c00000, 0x40c00000, 0x40c00000, + 0x42d60000, 0x434f4000, 0x42d60000, 0x42d68000, 0x42d60000, + 0x43500000, 0x439a0000, 0x43500000, 0x43500000, 0x43500000, + 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, 0x439a8000, + 0x41200000, 0x42dc0000, 0x41200000, 0x41200000, 0x41200000, + 0x42de0000, 0x43530000, 0x42de0000, 0x42de0000, 0x42de0000, + 0x43540000, 0x439c2000, 0x43540000, 0x43544000, 0x43540000, + 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, 0x439c8000, + 0x41600000, 0x42e40000, 0x41600000, 0x41600000, 0x41600000, + 0x42e60000, 0x43570000, 0x42e60000, 0x42e60000, 0x42e60000, + 0x43580000, 0x439e0000, 0x43580000, 0x43580000, 0x43580000, + 0x439e8000, 0x439ea000, 0x439e8000, 0x439ea000, 0x439e8000, + 0x41900000, 0x42ec0000, 0x41900000, 0x41900000, 0x00000000, + 0x42ee0000, 0x435b4000, 0x42ee0000, 0x42ee8000, 0x42ee8000, + 0x435c0000, 0x43a00000, 0x435c0000, 0x435c0000, 0x435c4000, + 0x43a08000, 0x43a08000, 0x43a08000, 0x43a08000, 0x43a0a000, + 0x41b00000, 0x42f40000, 0x41b00000, 0x41b00000, 0x00000000, + 0x42f60000, 0x435f0000, 0x42f60000, 0x42f60000, 0x42f60000, + 0x43600000, 0x43a22000, 0x43600000, 0x43604000, 0x43600000, + 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, 0x43a28000, + 0x41d00000, 0x42fc0000, 0x41d00000, 0x41d00000, 0x00000000, + 0x42fe0000, 0x43630000, 0x42fe0000, 0x42fe0000, 0x42fe0000, + 0x43640000, 0x43a40000, 0x43640000, 0x43640000, 0x43640000, + 0x43a48000, 0x43a4a000, 0x43a48000, 0x43a4a000, 0x43a48000, + 0x41f00000, 0x43020000, 0x41f00000, 0x41f00000, 0x00000000, + 0x43030000, 0x43674000, 0x43030000, 0x43034000, 0x43034000, + 0x43680000, 0x43a60000, 0x43680000, 0x43680000, 0x00000000, + 0x43a68000, 0x43a68000, 0x43a68000, 0x43a68000, 0x43a6a000, }; constexpr std::array Names3D{"image_sample_d 3D", "image_sample_cd 3D", "image_sample_d_g16 3D", "image_sample_cd_g16 3D", "image_sample_d a16 3D", "image_sample_d_g16 a16 3D"}; constexpr std::array Masked3D{false, false, false, false, false, false}; @@ -329,12 +359,13 @@ int main() { if (device->Target().subgroupSize < Threads) { std::printf("EXEC-masked cases skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize); } - const bool computedOffsets = device->Target().nonConstantImageOffsets; - if (!computedOffsets) std::puts("2D cases skipped, their computed texel offsets need VK_KHR_maintenance8"); + const bool nonConstantOffsets = device->Target().nonConstantImageOffsets; + if (!nonConstantOffsets) std::puts("image_sample_*_o cases skipped without VK_KHR_maintenance8"); std::vector waves{Threads}; if (device->Target().subgroupSize >= Threads) waves.push_back(Wave64Threads); for (const auto threads : waves) { - if (computedOffsets) Run(*device, Code2D, threads, 9u, Height, 1u, Names2D, Masked2D, Expected2D); + Run(*device, Code2D, threads, 9u, Height, 1u, Names2D, Masked2D, Expected2D); + if (nonConstantOffsets) Run(*device, Code2DOffsets, threads, 9u, Height, 1u, Names2DOffsets, Masked2DOffsets, Expected2DOffsets); Run(*device, Code3D, threads, 10u, Height, Depth, Names3D, Masked3D, Expected3D); Run(*device, Code1D, threads, 8u, 1u, 1u, Names1D, Masked1D, Expected1D); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodClamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodClamp.cpp index 1a1fcffbc6..789fac1041 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodClamp.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleLodClamp.cpp @@ -1,6 +1,9 @@ #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" +#include "IntermediateRepresentation/IrMetadata/ShaderInfo.hpp" +#include "Optimization/ResourceProgram.hpp" +#include "PipelineSpecialization.hpp" #include "Recompiler.hpp" #include "VulkanTestDevice.hpp" #include @@ -9,10 +12,22 @@ #include #include #include +#include #include +#include +#include +#include #include #include +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static bool ImageViewMinLod(const VulkanDevice& device) { return device.graphicsContext().imageViewMinLod; } +}; + +} + namespace { using AgcDriver::Graphics::Require; @@ -30,15 +45,45 @@ constexpr std::uint32_t Type2D = 9; alignas(256) std::array Buffer{}; alignas(256) std::array Texels{}; -alignas(256) constexpr std::array Code{ +alignas(256) constexpr std::array Code{ 0x34020087, 0xe0301000, 0x80000501, 0xe0301004, 0x80000601, 0xe0301008, 0x80000701, 0xe030100c, - 0x80000801, 0xe0301010, 0x80000901, 0xe0301014, 0x80000a01, 0xe0301018, 0x80000b01, 0xbf8c3f70, + 0x80000801, 0xe0301010, 0x80000901, 0xe0301014, 0x80000a01, 0xe0301018, 0x80000b01, 0xe0301010, + 0x80001001, 0xe0301014, 0x80001101, 0xe030101c, 0x80001201, 0xbf8c3f70, 0xf08c0f08, 0x00610c05, 0xe0701020, 0x80000c01, 0xe0701024, 0x80000d01, 0xe0701028, 0x80000e01, 0xe070102c, 0x80000f01, 0xf0840f08, 0x00610c09, 0xe0701030, 0x80000c01, 0xe0701034, 0x80000d01, 0xe0701038, 0x80000e01, 0xe070103c, 0x80000f01, 0x7e0802ff, 0x00000201, 0xf0cc0f08, 0x00610c04, 0xe0701040, 0x80000c01, 0xe0701044, 0x80000d01, 0xe0701048, 0x80000e01, 0xe070104c, 0x80000f01, 0x7e0802ff, 0x00003e3f, 0xf0cc0f08, 0x00610c04, 0xe0701050, 0x80000c01, 0xe0701054, 0x80000d01, - 0xe0701058, 0x80000e01, 0xe070105c, 0x80000f01, 0xbf810000, + 0xe0701058, 0x80000e01, 0xe070105c, 0x80000f01, 0xf0900f08, 0x00610c10, 0xe0701060, 0x80000c01, + 0xe0701064, 0x80000d01, 0xe0701068, 0x80000e01, 0xe070106c, 0x80000f01, 0xf09c0f08, 0x00610c09, + 0xe0701070, 0x80000c01, 0xe0701074, 0x80000d01, 0xe0701078, 0x80000e01, 0xe070107c, 0x80000f01, + 0xbf810000, +}; + +alignas(256) constexpr std::array GradientCode{ + 0x34020087, 0xe0301000, 0x80000501, 0xe0301004, 0x80000601, 0xe0301008, 0x80000701, 0xe030100c, + 0x80000801, 0xe0301010, 0x80000901, 0xe0301014, 0x80000a01, 0xe0301018, 0x80000b01, 0xbf8c3f70, + 0xf08c0f08, 0x00610c05, 0xe0701020, 0x80000c01, 0xe0701024, 0x80000d01, 0xe0701028, 0x80000e01, + 0xe070102c, 0x80000f01, 0xbf810000, +}; + +alignas(256) constexpr std::array PairedCode{ + 0x34020087u, 0xe0301000u, 0x80000501u, 0xe0301004u, 0x80000601u, 0xe0301008u, 0x80000701u, 0xe030100cu, + 0x80000801u, 0xe0301010u, 0x80000901u, 0xe0301014u, 0x80000a01u, 0xbf8c3f70u, 0xf0880f08u, 0x00610c05u, + 0xe0701020u, 0x80000c01u, 0xe0701024u, 0x80000d01u, 0xe0701028u, 0x80000e01u, 0xe070102cu, 0x80000f01u, + 0xf09c0f08u, 0x00810c09u, 0xe0701030u, 0x80000c01u, 0xe0701034u, 0x80000d01u, 0xe0701038u, 0x80000e01u, + 0xe070103cu, 0x80000f01u, 0xf0880f08u, 0x00810c05u, 0xe0701040u, 0x80000c01u, 0xe0701044u, 0x80000d01u, + 0xe0701048u, 0x80000e01u, 0xe070104cu, 0x80000f01u, 0xf09c0f08u, 0x00850c09u, 0xe0701050u, 0x80000c01u, + 0xe0701054u, 0x80000d01u, 0xe0701058u, 0x80000e01u, 0xe070105cu, 0x80000f01u, 0xbf810000u, +}; + +struct Case { + std::uint32_t minLod; + std::uint32_t baseLevel; + std::uint32_t maxLod = 0xfffu; + std::uint32_t mipFilter = 1u; + std::int32_t bias = 0; + std::uint32_t filters = 0; }; struct Sample { @@ -47,10 +92,11 @@ struct Sample { std::uint32_t clamp; std::uint32_t x; std::uint32_t y; + std::int32_t lod; }; Sample SampleOf(std::uint32_t tid) { - return {tid % Levels, (tid / Levels) % Gradients, (tid / 3u) % Clamps, (tid * 7u + 3u) % Size, (tid * 5u + 1u) % Size}; + return {tid % Levels, (tid / Levels) % Gradients, (tid / 3u) % Clamps, (tid * 7u + 3u) % Size, (tid * 5u + 1u) % Size, static_cast((tid / 4u) % Levels) - 1}; } std::uint32_t Bits(float value) { @@ -68,6 +114,7 @@ void FillInput() { words[4] = Bits((static_cast(sample.x) + 0.5f) / static_cast(Size)); words[5] = Bits((static_cast(sample.y) + 0.5f) / static_cast(Size)); words[6] = Bits(static_cast(sample.clamp)); + words[7] = Bits(static_cast(sample.lod)); } } @@ -94,13 +141,13 @@ std::array BufferDescriptor(const void* data, std::uint32_t by return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x31016facu}; } -std::array TextureDescriptor(const void* data) { +std::array TextureDescriptor(const void* data, std::uint32_t minLod, std::uint32_t baseLevel, std::uint32_t lastLevel = Levels - 1u) { const auto address = static_cast(reinterpret_cast(data)); return { static_cast(address >> 8u), - static_cast((address >> 40u) & 0xffu) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), + static_cast((address >> 40u) & 0xffu) | (minLod << 8u) | (Format8888UNorm << 20u) | (((Size - 1u) & 3u) << 30u), ((Size - 1u) >> 2u) | ((Size - 1u) << 14u), - 0xfacu | ((Levels - 1u) << 16u) | (Type2D << 28u), + 0xfacu | (baseLevel << 12u) | (lastLevel << 16u) | (Type2D << 28u), 0u, (Levels - 1u) << 4u, 0u, @@ -108,29 +155,37 @@ std::array TextureDescriptor(const void* data) { }; } -std::array SamplerDescriptor() { - return {0u, 0xfffu << 12u, 1u << 26u, 0u}; +std::array SamplerDescriptor(const Case& item) { + return {0u, item.maxLod << 12u, (static_cast(item.bias) & 0x3fffu) | item.filters | (item.mipFilter << 26u), 0u}; } -void Run(AgcDriver::VulkanDevice& device) { - std::vector userData(16, 0u); - const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); - const auto texture = TextureDescriptor(Texels.data()); - const auto sampler = SamplerDescriptor(); - 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::span code(Code); - const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; +ShaderRecompiler::RecompileRequest Request(std::span code, const ShaderRecompiler::SpirvTarget& target, const std::vector& userData, std::span memory, bool cached) { 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(), + target, {0, 0, 0, 128} }; - request.useCache = false; - const auto result = ShaderRecompiler::Recompile(request); + request.useCache = cached; + return request; +} + +std::vector UserData(const Case& item) { + std::vector userData(16, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data(), item.minLod, item.baseLevel); + const auto sampler = SamplerDescriptor(item); + 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); + return userData; +} + +void Run(AgcDriver::VulkanDevice& device, std::span code, const Case& item, bool cached) { + const auto userData = UserData(item); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const auto result = ShaderRecompiler::Recompile(Request(code, device.Target(), userData, memory, cached)); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); } @@ -140,40 +195,226 @@ std::uint32_t Wrap(std::uint32_t coordinate, std::int32_t offset, std::uint32_t return static_cast(((static_cast(coordinate) + offset) % extent + extent) % extent); } -void Check() { - constexpr std::array names{"image_sample_d_cl", "image_sample_cl", "image_sample_d_cl_o (1, 2)", "image_sample_d_cl_o (-1, -2)"}; - constexpr std::array, 4> offsets{{{0, 0}, {0, 0}, {1, 2}, {-1, -2}}}; +std::uint32_t Level(float lambda, float floor, float maxLod, std::uint32_t last, std::uint32_t baseLevel) { + const auto lod = std::min(std::max({lambda, floor, 0.0f}), maxLod); + Require(std::abs(lod - std::floor(lod) - 0.5f) > 1.0f / 512.0f, "image sample LOD clamp: an expected LOD of " + std::to_string(lod) + " sits on a nearest-mip rounding tie, which Vulkan leaves to the implementation"); + return std::min(static_cast(std::ceil(lod + 0.5f)) - 1u, last) + baseLevel; +} + +std::string Describe(const Case& item) { + return "T# MIN_LOD " + std::to_string(item.minLod) + " over BASE_LEVEL " + std::to_string(item.baseLevel) + " with S# MAX_LOD " + std::to_string(item.maxLod) + ", mip filter " + std::to_string(item.mipFilter) + " and LOD bias " + std::to_string(item.bias); +} + +void Check(const Case& item, std::size_t results) { + constexpr std::array names{"image_sample_d_cl", "image_sample_cl", "image_sample_d_cl_o (1, 2)", "image_sample_d_cl_o (-1, -2)", "image_sample_l", "image_sample_lz"}; + constexpr std::array, 6> offsets{{{0, 0}, {0, 0}, {1, 2}, {-1, -2}, {0, 0}, {0, 0}}}; + const std::uint32_t last = Levels - 1u - item.baseLevel; + const auto maxLod = static_cast(item.maxLod) / 256.0f; + const auto view = item.minLod > item.baseLevel * 256u ? static_cast(std::min(item.minLod - item.baseLevel * 256u, last * 256u)) / 256.0f : 0.0f; for (std::uint32_t tid = 0; tid < Threads; ++tid) { const auto sample = SampleOf(tid); - const std::uint32_t clamped = std::max(sample.level, sample.clamp); - const std::array levels{clamped, sample.clamp, clamped, clamped}; - for (std::uint32_t index = 0; index < levels.size(); ++index) { - const std::uint32_t level = levels[index]; + const auto gradient = static_cast(sample.level) - 0.25f - static_cast(item.baseLevel) + static_cast(item.bias) / 256.0f; + const auto floor = std::max(static_cast(sample.clamp), view); + const auto sampled = Level(gradient, floor, maxLod, last, item.baseLevel); + const auto explicitLevel = [&](float lod, float clamp) { + const auto clamped = view > 0.0f ? std::max({lod, clamp, view}) : std::max(lod, clamp); + return Level(clamped + static_cast(item.bias) / 256.0f, 0.0f, maxLod, last, item.baseLevel); + }; + const auto none = -std::numeric_limits::infinity(); + const auto levelOf = [&](std::size_t index) { + switch (index) { + case 1: return explicitLevel(0.0f, static_cast(sample.clamp)); + case 4: return explicitLevel(static_cast(sample.lod), none); + case 5: return explicitLevel(0.0f, none); + default: return sampled; + } + }; + for (std::size_t index = 0; index < results; ++index) { + const std::uint32_t level = levelOf(index); const std::uint32_t size = Size >> level; const std::array expected{level, Wrap(sample.x >> level, offsets[index][0], size), Wrap(sample.y >> level, offsets[index][1], size), 255u}; for (std::uint32_t component = 0; component < 4u; ++component) { const float value = std::bit_cast(Buffer[tid * Words + ResultWord + index * 4u + component]) * 255.0f; - Require(std::lround(value) == static_cast(expected[component]), std::string(names[index]) + ": thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component])); + Require(std::lround(value) == static_cast(expected[component]), std::string(names[index]) + " with " + Describe(item) + ": thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(value) + ", expected " + std::to_string(expected[component])); } } } } +void RunAndCheck(AgcDriver::VulkanDevice& device, std::span code, std::size_t results, const Case& item, bool cached) { + FillInput(); + Run(device, code, item, cached); + Check(item, results); } -int main() { +struct Pairing { + std::uint32_t minLod; + std::uint32_t baseLevel; + std::array held; + std::uint32_t secondMinLod = 0; + std::uint32_t secondLastLevel = 0; +}; + +std::string Describe(const Pairing& item) { + return "T# MIN_LOD " + std::to_string(item.minLod) + " over BASE_LEVEL " + std::to_string(item.baseLevel) + " with a second S# of words 1 " + std::to_string(item.held[1]) + " and 2 " + std::to_string(item.held[2]) + ", and a second T# of MIN_LOD " + std::to_string(item.secondMinLod) + " and LAST_LEVEL " + std::to_string(item.secondLastLevel); +} + +void RunPairing(AgcDriver::VulkanDevice& device, const Pairing& item) { + FillInput(); + std::vector userData(28, 0u); + const auto buffer = BufferDescriptor(Buffer.data(), static_cast(Buffer.size() * 4u)); + const auto texture = TextureDescriptor(Texels.data(), item.minLod, item.baseLevel); + const auto sampler = SamplerDescriptor(Case{0u, 0u}); + const auto second = TextureDescriptor(Texels.data(), item.secondMinLod, 0u, item.secondLastLevel); + 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); + std::copy(item.held.begin(), item.held.end(), userData.begin() + 16); + std::copy(second.begin(), second.end(), userData.begin() + 20); + const std::span code(PairedCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const auto result = ShaderRecompiler::Recompile(Request(code, device.Target(), userData, memory, false)); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +float Bilinear(std::uint32_t coordinate, std::uint32_t size) { + const auto texel = (static_cast(coordinate) + 0.5f) / static_cast(Size) * static_cast(size) - 0.5f; + const auto low = std::floor(texel); + const auto weight = texel - low; + const auto first = Wrap(0u, static_cast(low), size); + const auto next = Wrap(0u, static_cast(low) + 1, size); + return (1.0f - weight) * static_cast(first) + weight * static_cast(next); +} + +void CheckPairing(const Pairing& item) { + const std::uint32_t last = Levels - 1u - item.baseLevel; + const auto view = item.minLod > item.baseLevel * 256u ? static_cast(std::min(item.minLod - item.baseLevel * 256u, last * 256u)) / 256.0f : 0.0f; + const auto secondView = item.secondLastLevel == 0u ? 0.0f : static_cast(std::min(item.secondMinLod, item.secondLastLevel * 256u)) / 256.0f; + const bool magnifyLinear = ((item.held[2] >> 20u) & 1u) != 0u; + const bool mipLinear = ((item.held[2] >> 26u) & 3u) == 2u; + constexpr std::array names{"image_sample_d through the mipmapped S#", "image_sample_lz through the S# that holds the LOD at 0", "image_sample_d through the S# that holds the LOD at 0", "image_sample_lz of the second T# through the S# that holds the LOD at 0"}; + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const auto sample = SampleOf(tid); + const auto gradient = static_cast(sample.level) - 0.25f - static_cast(item.baseLevel); + const std::array levels{Level(gradient, view, 4095.0f / 256.0f, last, item.baseLevel), Level(view, 0.0f, static_cast(last), last, item.baseLevel), Level(view, 0.0f, static_cast(last), last, item.baseLevel), Level(secondView, 0.0f, static_cast(item.secondLastLevel), item.secondLastLevel, 0u)}; + for (std::size_t index = 0; index < levels.size(); ++index) { + const auto level = levels[index]; + const auto size = Size >> level; + const auto read = [&](std::uint32_t component) { return std::bit_cast(Buffer[tid * Words + ResultWord + index * 4u + component]) * 255.0f; }; + const auto fail = [&](std::uint32_t component, float expected) { return std::string(names[index]) + " with " + Describe(item) + ": thread " + std::to_string(tid) + " component " + std::to_string(component) + " is " + std::to_string(read(component)) + ", expected " + std::to_string(expected); }; + if (index != 0u && index != 3u && mipLinear) { + const auto blended = static_cast(item.baseLevel) + view; + const auto lower = static_cast(std::floor(blended)); + const auto weight = blended - static_cast(lower); + const std::array expected{blended, (1.0f - weight) * static_cast(sample.x >> lower) + weight * static_cast(sample.x >> (lower + 1u)), (1.0f - weight) * static_cast(sample.y >> lower) + weight * static_cast(sample.y >> (lower + 1u))}; + for (std::uint32_t component = 0; component < expected.size(); ++component) Require(std::abs(read(component) - expected[component]) < 1.0f, fail(component, expected[component])); + continue; + } + const bool filtered = index != 0u && magnifyLinear; + const std::array expected{static_cast(level), filtered ? Bilinear(sample.x, size) : static_cast(sample.x >> level), filtered ? Bilinear(sample.y, size) : static_cast(sample.y >> level), 255.0f}; + for (std::uint32_t component = 0; component < 4u; ++component) Require(std::abs(read(component) - expected[component]) < (filtered ? 1.0f : 0.5f), fail(component, expected[component])); + } + } +} + +void RequireRejected(AgcDriver::VulkanDevice& device, const Pairing& item, const std::string& reason) { try { + RunPairing(device, item); + } catch (const std::exception& error) { + Require(std::string(error.what()).find(reason) != std::string::npos, Describe(item) + " was rejected with \"" + error.what() + "\", expected \"" + reason + "\""); + return; + } + Require(false, Describe(item) + " ran in the shader-clamp lane, which cannot apply its clamp as the view clamp does"); +} + +void RequireRejected(AgcDriver::VulkanDevice& device, const Case& item, const std::string& reason, std::span code = Code) { + FillInput(); + try { + Run(device, code, item, false); + } catch (const std::exception& error) { + Require(std::string(error.what()).find(reason) != std::string::npos, Describe(item) + " was rejected with \"" + error.what() + "\", expected \"" + reason + "\""); + return; + } + Require(false, Describe(item) + " ran in the shader-clamp lane, which cannot apply its clamp as the view clamp does"); +} + +bool MinLodSpecId(std::uint32_t id) { + return id >= ShaderRecompiler::PipelineSpecialization::MinLodBase && id < ShaderRecompiler::PipelineSpecialization::MinLodBase + ShaderRecompiler::ShaderInfo::MaxImages; +} + +std::set MinLodSpecIds(std::span spirv) { + std::set ids; + for (std::size_t word = 5; word < spirv.size();) { + const auto count = spirv[word] >> 16u; + if (count == 0u) break; + if ((spirv[word] & 0xffffu) == spv::OpDecorate && count >= 4u && spirv[word + 2u] == spv::DecorationSpecId) { + if (MinLodSpecId(spirv[word + 3u])) ids.insert(spirv[word + 3u]); + } + word += count; + } + return ids; +} + +void CheckSpirvLanes(const ShaderRecompiler::SpirvTarget& deviceTarget) { + const Case item{0x200u, 0u}; + const auto userData = UserData(item); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + auto target = deviceTarget; + target.shaderImageMinLod = false; + const auto view = ShaderRecompiler::PrepareShader(Request(code, target, userData, memory, true)); + Require(MinLodSpecIds(ShaderRecompiler::GetPreparedArtifact(*view).spirv.Words()).empty(), "a target without the shader clamp declares minimum LOD specialization constants"); + target.shaderImageMinLod = true; + const auto shader = ShaderRecompiler::PrepareShader(Request(code, target, userData, memory, true)); + Require(MinLodSpecIds(ShaderRecompiler::GetPreparedArtifact(*shader).spirv.Words()) == std::set{ShaderRecompiler::PipelineSpecialization::MinLodBase}, "a target with the shader clamp does not declare exactly the image's minimum LOD specialization constant"); +} + +} + +int main(int argc, char** argv) { + try { + const bool shaderLane = argc > 1 && std::strcmp(argv[1], "--shader-lane") == 0; const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; if (!TargetHasCapability(device->Target(), spv::CapabilityMinLod)) { std::puts("skipped, the device has no shaderResourceMinLod"); return VulkanTestSkipped; } - FillInput(); + const bool shaderClamp = device->Target().shaderImageMinLod; + if (shaderLane && !shaderClamp) { + std::puts("skipped, the device does not apply T# minimum LODs in the shader under APS5_NO_IMAGE_VIEW_MIN_LOD and APS5_NO_GPL"); + return VulkanTestSkipped; + } + if (!shaderClamp && !AgcDriver::VulkanDeviceTestAccess::ImageViewMinLod(*device)) { + std::puts("skipped, the device has no VK_EXT_image_view_min_lod, and graphics pipeline libraries keep T# minimum LODs out of the shader"); + return VulkanTestSkipped; + } + CheckSpirvLanes(device->Target()); FillTexture(); - Run(*device); - Check(); - std::puts("image sample LOD clamp tests passed"); + for (const auto& item : {Case{0u, 0u}, Case{0x200u, 0u}, Case{0x200u, 1u}, Case{0x200u, 0u, 0x300u}}) RunAndCheck(*device, Code, 6u, item, false); + if (!shaderClamp) RunAndCheck(*device, GradientCode, 1u, Case{0x100u, 1u, 0xfffu, 1u, -128}, false); + if (!shaderClamp) RunAndCheck(*device, GradientCode, 1u, Case{0x200u, 0u, 0xfffu, 1u, -256}, false); + if (!shaderClamp) RunAndCheck(*device, Code, 6u, Case{0u, 0u, 0xfffu, 1u, 256}, false); + RunAndCheck(*device, Code, 6u, Case{0x100u, 1u, 0xfffu, 1u, -96}, false); + for (const auto& item : {Case{0x200u, 0u}, Case{0u, 0u}, Case{0x300u, 0u}, Case{0x200u, 1u}, Case{0x100u, 0u}}) RunAndCheck(*device, Code, 6u, item, true); + for (const auto& item : {Pairing{0x300u, 0u, {0u, 0u, 0u, 0u}}, Pairing{0x300u, 1u, {0u, 0u, 1u << 26u, 0u}}, Pairing{0x300u, 0u, {0u, 0u, (1u << 20u) | (1u << 22u), 0u}}, Pairing{0x300u, 0u, {0u, 0u, 0x3f00u, 0u}}, Pairing{0x300u, 0u, {0u, 0u, 0u, 0u}, 0x300u, 4u}}) { + RunPairing(*device, item); + CheckPairing(item); + } + if (shaderClamp) { + for (const auto& item : {Case{0x1c0u, 0u}, Case{0x2c0u, 1u}}) RunAndCheck(*device, Code, 6u, item, false); + RunAndCheck(*device, GradientCode, 1u, Case{0x1c0u, 0u}, false); + for (const auto& item : {Case{0x1c0u, 0u}, Case{0u, 0u}, Case{0x2c0u, 1u}}) RunAndCheck(*device, Code, 6u, item, true); + RequireRejected(*device, Case{0x200u, 0u, 0x100u}, "above the maximum LOD of 256/256 of S# 0, which is not implemented"); + RunPairing(*device, Pairing{0x140u, 0u, {0u, 0u, 2u << 26u, 0u}}); + CheckPairing(Pairing{0x140u, 0u, {0u, 0u, 2u << 26u, 0u}}); + RequireRejected(*device, Pairing{0x300u, 0u, {0u, 0u, 0u, 0u}, 0x100u, 4u}, "which holds the LOD at 0, also samples image 1 with another minimum LOD, which is not implemented"); + RequireRejected(*device, Case{0x200u, 0u, 0xfffu, 1u, -256}, "to an explicit LOD that S# 0 biases by -256/256, which is not implemented"); + RequireRejected(*device, Case{0x200u, 0u, 0xfffu, 1u, 64}, "to an explicit LOD that S# 0 biases by 64/256, which is not implemented"); + RequireRejected(*device, Case{0x200u, 0u, 0xfffu, 1u, 0, 1u << 20u}, "and S# 0 has different magnification and minification filters"); + } + std::puts(shaderClamp ? "image sample LOD clamp tests passed in the shader-clamp lane" : "image sample LOD clamp tests passed in the image-view lane"); return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleReduction.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleReduction.cpp index e2f8d2dc66..c5fa541b6d 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleReduction.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleReduction.cpp @@ -236,7 +236,7 @@ void Gather(AgcDriver::VulkanDevice& device, const Sampler& sampler, const char* } } -std::uint32_t PairedSamplers(const ShaderRecompiler::RecompileResult& result, std::uint32_t format) { +std::uint64_t PairedSamplers(const ShaderRecompiler::RecompileResult& result, std::uint32_t format) { for (const auto& binding : result.bindings) { if (binding.kind != ShaderRecompiler::DescriptorKind::SampledImage) continue; for (std::uint32_t element = 0; element < binding.count; ++element) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleUnnormalized.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleUnnormalized.cpp index 26c5032332..db4065a954 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleUnnormalized.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageSampleUnnormalized.cpp @@ -98,14 +98,19 @@ struct SamplerCase { bool linear; bool border; bool atBaseLevel; + bool mirrorX = false; + bool mirrorY = false; }; -constexpr std::array Samplers{{ +constexpr std::array Samplers{{ {"bilinear clamp-to-last-texel, point mips, MAX_LOD 0xfff", {0x00008092u, 0x00fff000u, 0x05500000u, 0u}, true, false, true}, {"point clamp-to-last-texel, point mips", {0x00008092u, 0x00fff000u, 0x04000000u, 0u}, false, false, true}, {"bilinear clamp-to-last-texel, linear mips, MIN_LOD 1, LOD bias 1.5", {0x00008092u, 0x00fff100u, 0x09500180u, 0u}, true, false, false}, {"bilinear clamp-to-border, transparent black", {0x000080b6u, 0x00fff000u, 0x05500000u, 0u}, true, true, true}, {"point clamp-to-border, no mips, transparent black", {0x000080b6u, 0u, 0u, 0u}, false, true, true}, + {"bilinear mirror on X and Y, point mips", {0x00008089u, 0x00fff000u, 0x05500000u, 0u}, true, false, true, true, true}, + {"point mirror-once on X, clamp-to-last-texel on Y", {0x00008093u, 0x00fff000u, 0x04000000u, 0u}, false, false, true, true, false}, + {"bilinear clamp-to-last-texel on X, mirror-once on Y", {0x0000809au, 0x00fff000u, 0x05500000u, 0u}, true, false, true, false, true}, }}; struct Coordinate { @@ -130,7 +135,7 @@ std::vector Coordinates(std::uint32_t level) { for (std::int32_t k = 0; k < width; ++k) { for (const float fraction : {0.5f, 0.0f, 0.25f, 0.75f}) us.push_back(static_cast(k) + fraction); } - for (const float edge : {-1.0f, -0.5f, static_cast(width), static_cast(width + 1), static_cast(width) - 0.25f}) us.push_back(edge); + for (const float edge : {-1.0f, -0.5f, static_cast(width), static_cast(width + 1), static_cast(width) - 0.25f, -0.25f, -0.75f, -2.0f, -3.25f, -static_cast(width) + 0.5f, -static_cast(width) - 1.5f}) us.push_back(edge); constexpr std::array fractions{0.5f, 0.0f, 0.75f}; std::vector result; for (std::size_t index = 0; index < us.size(); ++index) { @@ -142,6 +147,8 @@ std::vector Coordinates(std::uint32_t level) { const float v = static_cast(x % height) + 0.5f; result.push_back({static_cast(x) - 1.0f, v}); result.push_back({static_cast(x) + 2.0f, v}); + result.push_back({static_cast(x) + 0.5f, -v}); + result.push_back({-static_cast(x) - 0.25f, -1.0f - v}); } return result; } @@ -247,6 +254,8 @@ Samples Run(AgcDriver::VulkanDevice& device, const Program& program, const std:: std::array Fetch(const SamplerCase& sampler, std::uint32_t level, std::int32_t x, std::int32_t y) { const auto width = LevelWidth(level); const auto height = LevelHeight(level); + if (sampler.mirrorX && x < 0) x = -x - 1; + if (sampler.mirrorY && y < 0) y = -y - 1; if (sampler.border && (x < 0 || x >= width || y < 0 || y >= height)) return {0.0, 0.0, 0.0, 0.0}; return TexelOf(level, std::clamp(x, 0, width - 1), std::clamp(y, 0, height - 1)); } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp index 104fb882b6..322b5b2fdd 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ImageTableCacheBounds.cpp @@ -20,6 +20,13 @@ #else #include #endif +#ifdef __linux__ +#include +#include +#include +#include +#include +#endif #include #include #include @@ -31,7 +38,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -124,6 +133,93 @@ std::vector Pm4(std::uint32_t opcode, std::initializer_list dispatchesSubmitted{0}; +std::atomic dispatchesIdle{0}; + +#ifdef __linux__ +void PrintThreadStack(int) { + void* frames[48]; + const int count = backtrace(frames, 48); + backtrace_symbols_fd(frames, count, STDERR_FILENO); +} + +std::string FirstLine(const std::string& path) { + std::ifstream file(path); + std::string line; + std::getline(file, line); + return line; +} +#endif + +void PrintStalledThreads() { +#ifdef __linux__ + void* preload[1]; + backtrace(preload, 1); + struct sigaction action {}; + action.sa_handler = PrintThreadStack; + sigemptyset(&action.sa_mask); + sigaction(SIGUSR2, &action, nullptr); + const auto self = static_cast(syscall(SYS_gettid)); + DIR* tasks = opendir("/proc/self/task"); + if (!tasks) return; + while (const dirent* entry = readdir(tasks)) { + const int thread = std::atoi(entry->d_name); + if (thread <= 0) continue; + const auto base = std::string("/proc/self/task/") + entry->d_name; + const auto stat = FirstLine(base + "/stat"); + const auto state = stat.rfind(") ") == std::string::npos ? std::string("?") : stat.substr(stat.rfind(") ") + 2, 1); + std::fprintf(stderr, "thread %d %s state=%s syscall=%s\n", thread, FirstLine(base + "/comm").c_str(), state.c_str(), FirstLine(base + "/syscall").c_str()); + if (thread == self) continue; + syscall(SYS_tgkill, getpid(), thread, SIGUSR2); + usleep(50000); + } + closedir(tasks); +#endif +} + +struct StallWatchdog { + std::atomic finished{false}; + std::atomic reporterExited{false}; + std::thread reporter; + std::thread hardStop; + + StallWatchdog(int seconds, int graceSeconds) { + const auto start = std::chrono::steady_clock::now(); + reporter = std::thread([this, seconds, start] { + const auto deadline = start + std::chrono::seconds(seconds); + while (!finished.load(std::memory_order_acquire)) { + if (std::chrono::steady_clock::now() > deadline) { + std::fprintf(stderr, "image table vouched lookups: no result after %d s, %u dispatches submitted and %u idle\n", seconds, dispatchesSubmitted.load(), dispatchesIdle.load()); + PrintStalledThreads(); + std::fflush(stderr); + std::_Exit(1); + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + reporterExited.store(true, std::memory_order_release); + }); + hardStop = std::thread([this, seconds, graceSeconds, start] { +#ifdef __linux__ + sigset_t blocked; + sigemptyset(&blocked); + sigaddset(&blocked, SIGUSR2); + pthread_sigmask(SIG_BLOCK, &blocked, nullptr); +#endif + const auto deadline = start + std::chrono::seconds(seconds + graceSeconds); + while (!reporterExited.load(std::memory_order_acquire)) { + if (std::chrono::steady_clock::now() > deadline) std::_Exit(1); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + }); + } + + ~StallWatchdog() { + finished.store(true, std::memory_order_release); + reporter.join(); + hardStop.join(); + } +}; + void Dispatch(const GuestBlock& block, std::size_t slot, std::size_t kernelOffset = 0, std::vector words = {}) { const auto kernel = block.At(kernelOffset); const auto srt = block.At(SrtOffset + slot * SrtBytes); @@ -131,8 +227,10 @@ void Dispatch(const GuestBlock& block, std::size_t slot, std::size_t kernelOffse words.insert(words.end(), packet.begin(), packet.end()); } Packet dcb{words.data(), static_cast(words.size()), 0, {}}; + ++dispatchesSubmitted; Require(sceAgcDriverSubmitDcb(&dcb) == 0, "image table cache bounds: the dispatch submission failed"); AgcDriverWaitIdle_nid_postfix(); + ++dispatchesIdle; } void Prepare(const GuestBlock& block) { @@ -473,6 +571,8 @@ bool ImportsStayWatchedThroughReads(const AgcDriver::Graphics::Context& context) } int main(int argc, char** argv) { + std::unique_ptr watchdog; + if (argc == 2 && std::string(argv[1]) == "--vouched-lookups") watchdog = std::make_unique(30, 15); SetBudgets(); try { { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LabelAfterImportCopy.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LabelAfterImportCopy.cpp new file mode 100644 index 0000000000..09e8d2f17d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LabelAfterImportCopy.cpp @@ -0,0 +1,97 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::size_t CopyBytes = 0x40000; +constexpr std::size_t LabelOffset = 2 * CopyBytes; +constexpr std::size_t BlockBytes = LabelOffset + 0x10000; +constexpr int Rounds = 32; + +std::uint64_t AddressOf(const volatile void* data) { + return static_cast(reinterpret_cast(data)); +} + +std::uint8_t Pattern(int round, std::size_t at) { + return static_cast(0x31u + (at + static_cast(round) * 7u) % 67u); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + std::vector storage(BlockBytes + 0x10000u); + auto* block = reinterpret_cast((reinterpret_cast(storage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}); + auto* source = block; + auto* destination = block + CopyBytes; + auto* label = reinterpret_cast(block + LabelOffset); + std::memset(block, 0, BlockBytes); + { + GuestAllocations::Mutation mutation; + mutation.Add(block, BlockBytes, true, true, true); + } + std::vector seen(CopyBytes); + for (int round = 0; round < Rounds; ++round) { + for (std::size_t at = 0; at < CopyBytes; ++at) source[at] = Pattern(round, at); + std::memset(destination, 0xa5, CopyBytes); + *label = 0; + const std::uint32_t value = 0x600d0001u + static_cast(round); + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + const auto outcome = device->CopyBuffer(AddressOf(destination), AddressOf(source), CopyBytes, 0, std::numeric_limits::max(), 0, 0, 0, [](std::span, std::uint64_t) {}); + if (outcome.path != 1) { + std::printf("skipped, the copy took path %d instead of the GPU transfer\n", outcome.path); + return VulkanTestSkipped; + } + const int reason = device->WriteLabelOnGpu(AddressOf(label), std::as_bytes(std::span(&value, 1)), static_cast(round) + 1u, 0); + if (reason != 0) { + std::printf("skipped, the label was not recorded on the GPU (reason %d)\n", reason); + return VulkanTestSkipped; + } + device->SubmitRecorded(true); + } + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (*label != value) { + Require(std::chrono::steady_clock::now() < deadline, "the label after the copy never landed"); + std::this_thread::yield(); + } + std::memcpy(seen.data(), destination, CopyBytes); + std::size_t stale = 0; + for (std::size_t at = 0; at < CopyBytes; ++at) stale += seen[at] != Pattern(round, at); + Require(stale == 0, "round " + std::to_string(round) + ": a CPU read after the label that followed the copy found " + std::to_string(stale) + " of " + std::to_string(CopyBytes) + " destination bytes not yet copied"); + device->WaitIdle(); + } + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + AgcDriver::Graphics::FlushCachedTextures(device->Device()); + } + device->WaitIdle(); + { + GuestAllocations::Mutation mutation; + mutation.Remove(block); + } + std::puts("label after import copy 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/LayeredVolumeDraw.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp index c9b7b68350..a449dc4cef 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LayeredVolumeDraw.cpp @@ -33,9 +33,9 @@ using AgcDriver::Graphics::ColorTileMode; using AgcDriver::Graphics::Require; using ShaderRecompiler::ShaderStage; -constexpr std::uint32_t Width = 48; +constexpr std::uint32_t Width = 99; constexpr std::uint32_t Height = 16; -constexpr std::uint32_t Slices = 4; +constexpr std::uint32_t Slices = 34; constexpr std::uint32_t FirstSlice = 1; constexpr std::uint32_t Layers = Slices - FirstSlice; constexpr std::uint32_t BandWidth = Width / Layers; @@ -76,8 +76,16 @@ alignas(256) constexpr std::array LayerFieldPixelCode{ 0x2c020090, 0x7e020d01, 0x100202f0, 0xf800180f, 0x01010101, 0xbf810000, }; -constexpr std::array WhiteBands{255, 255, 255}; -constexpr std::array LayerFieldBands{0, 128, 255}; +constexpr auto WhiteBands = [] { + std::array bands{}; + bands.fill(255); + return bands; +}(); +constexpr auto LayerFieldBands = [] { + std::array bands{}; + for (std::uint32_t i = 0; i < Layers; ++i) bands[i] = static_cast(std::min(i * 128u, 255u)); + return bands; +}(); struct Vertex { std::array position; @@ -85,7 +93,7 @@ struct Vertex { std::uint32_t layer; }; -alignas(256) std::array Vertices{}; +alignas(256) std::array Vertices{}; constexpr ShaderRecompiler::MeshConfiguration Subgroup{4u, 4u, 12u, 12u, 4u, 64u, 1024u, 0u, 4u}; @@ -156,7 +164,7 @@ bool DeclaresLayer(std::span words) { return false; } -void Run(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, bool mesh, std::span pixelCode, bool layerField, std::span vertices = Vertices) { +void Run(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, bool mesh, std::span pixelCode, bool layerField, std::span vertices = Vertices, bool executeMesh = true) { const auto target = device.Target(); const auto vertexBuffer = BufferDescriptor(vertices.data(), sizeof(Vertex), static_cast(vertices.size())); const AgcDriver::Pm4::DrawParameters draw{0, 6u * color.layers, 0, 1, 0, false}; @@ -175,7 +183,18 @@ void Run(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget }; if (mesh) geometry.graphics = ShaderRecompiler::GraphicsCompileContext{0, {}, Subgroup, std::nullopt, {draw.indexAddress, draw.indexCount, draw.indexSize, draw.instanceCount}}; geometry.useCache = false; - const auto geometryResult = ShaderRecompiler::Recompile(geometry); + auto geometryResult = ShaderRecompiler::Recompile(geometry); + if (mesh) { + for (const auto required : {ShaderRecompiler::PipelineSpecialization::MeshLayerBase, ShaderRecompiler::PipelineSpecialization::MeshLayerCount}) { + bool found = false; + for (std::size_t at = 5; at < geometryResult.spirv.size();) { + const auto length = geometryResult.spirv[at] >> 16u; + if ((geometryResult.spirv[at] & 0xffffu) == 71u && length == 4u && geometryResult.spirv[at + 2u] == 1u && geometryResult.spirv[at + 3u] == required) found = true; + at += length; + } + Require(found, "mesh layer specialization was frozen: " + std::to_string(required)); + } + } const auto geometryPush = static_cast(geometryResult.pushConstants.size()); ShaderRecompiler::ShaderPixelStageInfo pixel{}; @@ -221,13 +240,18 @@ void Run(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget state.blendConstants = {}; if (mesh) { Require(DeclaresLayer(geometryResult.spirv), "the mesh program declares no Layer output"); - VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES}; - subgroup.subgroupSize = target.subgroupSize; - subgroup.supportedStages = VK_SHADER_STAGE_ALL; - subgroup.supportedOperations = 0xffu; - subgroup.quadOperationsInAllStages = VK_TRUE; - AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false, false, false, false, false, false, false, false, true); - return; + if (!executeMesh) return; + } + if (mesh) { + const auto replaySafe = geometryResult.layerReplaySafe; + geometryResult.layerReplaySafe = false; + auto empty = draw; + empty.indexCount = 0u; + device.Draw(state, empty, shaders); + empty = draw; + empty.instanceCount = 0u; + device.Draw(state, empty, shaders); + geometryResult.layerReplaySafe = replaySafe; } device.Draw(state, draw, shaders); device.WaitIdle(); @@ -375,7 +399,8 @@ int main(int argc, char** argv) { } const bool vertexOnly = argc == 2 && std::strcmp(argv[1], "--vertex-only") == 0; const bool mips = argc == 2 && std::strcmp(argv[1], "--mips") == 0; - Require(argc == 1 || vertexOnly || mips, "usage: layered volume draw [--vertex-only|--mips]"); + const bool meshExecution = argc == 2 && std::strcmp(argv[1], "--mesh-execution") == 0; + Require(argc == 1 || vertexOnly || mips || meshExecution, "usage: layered volume draw [--vertex-only|--mips|--mesh-execution]"); const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; const auto target = device->Target(); @@ -387,6 +412,11 @@ int main(int argc, char** argv) { MipTargets(*device); return 0; } + if (meshExecution && (!target.mesh.has_value() || (target.subgroupSize != 32u && target.subgroupSize != 64u))) { + std::puts("skipped, mesh execution requires a supported 32- or 64-lane host subgroup layout"); + return VulkanTestSkipped; + } + std::printf("graphics pipeline libraries: %s\n", device->GraphicsPipelineLibraries() ? "enabled" : "disabled"); BuildBands(); const GuestVolume volume((VolumeTarget(nullptr).bytes + 65535u) & ~std::size_t{65535u}); const auto color = VolumeTarget(volume.Data()); @@ -433,8 +463,18 @@ int main(int argc, char** argv) { Run(*device, thickColor, false, PixelCode, false); Check(thickVolume.Data(), thickColor, "thick SW_4KB_S_X vertex draw and writeback", WhiteBands); if (vertexOnly) std::puts("layered volume vertex draw tests passed"); - else if (target.mesh.has_value()) Run(*device, color, true, PixelCode, false); - else std::puts("mesh case skipped, the device has no VK_EXT_mesh_shader"); + else if (meshExecution) { + const GuestVolume meshVolume((VolumeTarget(nullptr).bytes + 65535u) & ~std::size_t{65535u}); + const auto meshColor = VolumeTarget(meshVolume.Data()); + Run(*device, meshColor, true, PixelCode, false); + Check(meshVolume.Data(), meshColor, "mesh draw", WhiteBands); + const GuestVolume meshFieldVolume((VolumeTarget(nullptr).bytes + 65535u) & ~std::size_t{65535u}); + const auto meshFieldColor = VolumeTarget(meshFieldVolume.Data()); + Run(*device, meshFieldColor, true, LayerFieldPixelCode, true); + Check(meshFieldVolume.Data(), meshFieldColor, "mesh fragment layer input", LayerFieldBands); + } + else if (target.mesh.has_value()) Run(*device, color, true, PixelCode, false, Vertices, false); + else std::puts("mesh compilation skipped, the device has no VK_EXT_mesh_shader"); std::puts("layered volume draw tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LinearReadbackLabel.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearReadbackLabel.cpp index 36105a9e93..7091f5ddea 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/LinearReadbackLabel.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearReadbackLabel.cpp @@ -37,17 +37,22 @@ 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 BlockBytes = 0x200000; 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 OccasionalSide = 256; +constexpr std::size_t OccasionalOffset = 0x100000; +constexpr std::size_t OccasionalLabelOffset = 0x8200; 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; +constexpr std::uint32_t Rewritten = 0x0badcafeu; +constexpr std::uint32_t WrittenAgain = 0x600dd00du; alignas(256) constexpr std::array StoreCode{ 0x4a500081, 0xd5690014, 0x0201ff28, 0x9e3779b1, 0x4a2a28ff, 0x7f4a7c15, 0x4a2c28ff, 0xfe94f82a, @@ -59,6 +64,14 @@ alignas(256) constexpr std::array TiledStoreCode{ 0x7e040300, 0x7e060280, 0x7e0202ff, Written, 0xf0200108, 0x00020102, 0xbf810000, }; +alignas(256) constexpr std::array RewriteStoreCode{ + 0x7e040300, 0x7e060280, 0x7e0202ff, Rewritten, 0xf0200108, 0x00020102, 0xbf810000, +}; + +alignas(256) constexpr std::array AgainStoreCode{ + 0x7e040300, 0x7e060280, 0x7e0202ff, WrittenAgain, 0xf0200108, 0x00020102, 0xbf810000, +}; + class GuestBlock { public: GuestBlock() { @@ -125,12 +138,12 @@ std::array UserData(const void* texels) { return data; } -std::array TiledDescriptor(const void* texels) { +std::array TiledDescriptor(const void* texels, std::uint32_t side = TiledSide) { 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), + static_cast((address >> 40u) & 0xffu) | (Format32Uint << 20u) | (((side - 1u) & 3u) << 30u), + ((side - 1u) >> 2u) | ((side - 1u) << 14u), IdentitySwizzle | (TileR64KBX << 20u) | (Type2D << 28u), 0u, 0u, @@ -139,9 +152,9 @@ std::array TiledDescriptor(const void* texels) { }; } -std::array TiledUserData(const void* texels) { +std::array TiledUserData(const void* texels, std::uint32_t side = TiledSide) { std::array data{}; - const auto texture = TiledDescriptor(texels); + const auto texture = TiledDescriptor(texels, side); std::copy(texture.begin(), texture.end(), data.begin() + 8); return data; } @@ -215,6 +228,36 @@ void ReadTiledAfterLabel(std::uint8_t* block) { 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)); } +std::uint32_t CountHolding(const std::uint32_t* texels, std::size_t bytes, std::uint32_t value) { + const volatile std::uint32_t* cpu = texels; + std::uint32_t count = 0; + for (std::size_t index = 0; index < bytes / 4u; ++index) count += cpu[index] == value ? 1u : 0u; + return count; +} + +void ReadOccasionalAfterLabel(std::uint8_t* block) { + using AgcDriver::Graphics::StorageTexture; + auto* texels = reinterpret_cast(block + OccasionalOffset); + auto* label = reinterpret_cast(block + OccasionalLabelOffset); + const auto bytes = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TiledDescriptor(texels, OccasionalSide))).guestBytes; + Require(bytes > StorageTexture::SmallStoreBytes && bytes <= StorageTexture::OccasionalStoreBytes && bytes <= BlockBytes - OccasionalOffset, "occasional readback label: the test image is not an occasional store's size"); + std::fill(texels, texels + bytes / 4u, Untouched); + const auto userData = TiledUserData(texels, OccasionalSide); + const auto stored = StorageTexture::OccasionalLabelStores(); + SubmitAndWait(Commands(TiledStoreCode, TiledThreads, userData, label), label, "occasional readback label"); + Require(CountHolding(texels, bytes, Written) == TiledThreads, "occasional readback label: the first write of an image was not stored before its label"); + Require(StorageTexture::OccasionalLabelStores() == stored + 1u, "occasional readback label: the first write of an image was not chosen for its label"); + SubmitAndWait(Commands(RewriteStoreCode, TiledThreads, userData, label), label, "occasional readback label"); + Require(StorageTexture::OccasionalLabelStores() == stored + 1u, "occasional readback label: a second write with no flip between was chosen for its label"); + StorageTexture::NoteFlip(); + SubmitAndWait(Commands(RewriteStoreCode, TiledThreads, userData, label), label, "occasional readback label"); + Require(StorageTexture::OccasionalLabelStores() == stored + 1u, "occasional readback label: an image written on consecutive flips was chosen for its label"); + for (std::uint64_t flip = 0; flip <= StorageTexture::OccasionalWriteFlips; ++flip) StorageTexture::NoteFlip(); + SubmitAndWait(Commands(AgainStoreCode, TiledThreads, userData, label), label, "occasional readback label"); + Require(CountHolding(texels, bytes, WrittenAgain) == TiledThreads, "occasional readback label: a write after a long gap was not stored before its label"); + Require(StorageTexture::OccasionalLabelStores() == stored + 2u, "occasional readback label: a write after a long gap was not chosen for its label"); +} + } int main() { @@ -226,6 +269,7 @@ int main() { GuestBlock block; ReadAfterLabel(block.Data()); ReadTiledAfterLabel(block.Data()); + ReadOccasionalAfterLabel(block.Data()); AgcDriverWaitIdle_nid_postfix(); AgcDriverShutdown_nid_postfix(); std::puts("linear readback label tests passed"); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCpuWrites.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCpuWrites.cpp index cbd2e525e4..63ef8c4372 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCpuWrites.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCpuWrites.cpp @@ -228,8 +228,8 @@ std::array Texel(const std::uint8_t* target, std::uint32_t x, std::uin 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" : ""); +Outcome Run(AgcDriver::VulkanDevice& device, bool watched, bool seeded, bool hookRead, bool leaveWatch = false) { + const auto name = std::string(seeded ? "seeded" : "unseeded") + ", " + (watched ? "write-watched" : "unwatched") + " memory" + (hookRead ? ", driver read before the store" : "") + (leaveWatch ? ", left the watch while drawn" : ""); 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); @@ -253,6 +253,10 @@ Outcome Run(AgcDriver::VulkanDevice& device, bool watched, bool seeded, bool hoo 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); + if (leaveWatch) { + AgcDriver::GuestMemory::Unwatch(block.Address(), block.bytes); + Require(!AgcDriver::GuestMemory::Watched(block.Address(), TargetBytes), name + ": the drawn target's memory is still write-watched"); + } 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) { @@ -296,6 +300,11 @@ int main() { } } } + for (const bool seeded : {false, true}) { + const auto outcome = Run(*device, true, seeded, false, true); + 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; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDrawBindings.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDrawBindings.cpp index 4335e86e2c..32e8a49d47 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDrawBindings.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetDrawBindings.cpp @@ -67,6 +67,7 @@ alignas(256) constexpr std::array VertexCode{ 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 PixelReadStore{0x7e1a0280u, 0xdc308000u, 0x0c02000du, 0xbf8c3f70u, 0xdc708000u, 0x00020c0du, 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{{ @@ -250,7 +251,7 @@ void ExpectTexels(const std::uint8_t* target, const char* what, std::uint32_t fi } } -bool Expect(AgcDriver::VulkanDevice& device, bool unwatch) { +bool Expect(AgcDriver::VulkanDevice& device, bool unwatch, bool readOnly) { const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); if (!context.bufferDeviceAddress) { std::puts("skipped, the device lacks bufferDeviceAddress"); @@ -285,9 +286,11 @@ bool Expect(AgcDriver::VulkanDevice& device, bool unwatch) { 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 std::uint64_t synced = readOnly && std::getenv("APS5_NO_READ_ONLY_LEASE_SKIP") == nullptr ? 0u : 1u; + const std::string reader = readOnly ? "the read-only address-based draw" : "the storing address-based draw"; 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); + Draw(device, drawn, readOnly ? std::span(PixelRead) : std::span(PixelReadStore), 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; @@ -295,12 +298,12 @@ bool Expect(AgcDriver::VulkanDevice& device, bool unwatch) { 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)"); + Require(recorder->Recording() && recorder->Submissions() == submitted + synced, "storing the drawn linear target after " + reader + " made " + std::to_string(recorder->Submissions() - submitted) + " submissions, expected " + std::to_string(synced)); 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)"); + if (watched) Require(recorder->Recording() && recorder->Submissions() == submitted + synced, "the second copying draw submitted recorded work after " + reader); + else Require(recorder->Submissions() == submitted + synced + 1, "the second copying draw into a stored target outside the write watch did not submit once to capture its bytes after " + reader + " (" + std::to_string(recorder->Submissions() - submitted - synced) + " 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)); @@ -323,8 +326,10 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - if (!Expect(*device, false)) return VulkanTestSkipped; - if (!Expect(*device, true)) return VulkanTestSkipped; + for (const bool readOnly : {false, true}) { + if (!Expect(*device, false, readOnly)) return VulkanTestSkipped; + if (!Expect(*device, true, readOnly)) return VulkanTestSkipped; + } std::puts("linear target draw binding tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/LinearTexturePitch.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTexturePitch.cpp new file mode 100644 index 0000000000..28e4b2822e --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/LinearTexturePitch.cpp @@ -0,0 +1,109 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.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::uint32_t Width = 16; +constexpr std::uint32_t Height = 8; +constexpr std::uint32_t Groups = Width * Height / Threads; +constexpr std::uint32_t MaxPitch = 256; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t SwizzleXYZW = 0xfacu; +constexpr std::uint32_t Mark = 0xa0000000u; + +alignas(256) std::array Output{}; +alignas(256) std::array Texels{}; + +alignas(256) constexpr std::array LoadXy{ + 0x7e080218u, 0x343c0885u, 0x4a3c3d00u, 0x34063c84u, 0x2c3e3c84u, 0x363c3c8fu, 0x7e140280u, 0x7e160280u, + 0x7e180280u, 0x7e1a0280u, 0xf0001f08u, 0x00010a1eu, 0xbf8c3f70u, 0xe0781000u, 0x80000a03u, 0xbf810000u, +}; + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +std::array TextureDescriptor(std::uint32_t depth) { + const auto address = static_cast(reinterpret_cast(Texels.data())); + return { + static_cast(address >> 8u), + static_cast((address >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Width - 1u) & 3u) << 30u), + ((Width - 1u) >> 2u) | ((Height - 1u) << 14u), + SwizzleXYZW | (Type2D << 28u), + depth, + 0u, + 0u, + 0u, + }; +} + +void Run(AgcDriver::VulkanDevice& device, std::uint32_t depth) { + Output.fill(0xdeadbeefu); + std::vector userData(24, 0u); + const auto outputAddress = reinterpret_cast(Output.data()); + const std::array output{static_cast(outputAddress), static_cast((outputAddress >> 32u) & 0xffffu), static_cast(Output.size() * 4u), 0x31016facu}; + const auto texture = TextureDescriptor(depth); + std::copy(output.begin(), output.end(), userData.begin()); + std::copy(texture.begin(), texture.end(), userData.begin() + 4); + const std::span code = LoadXy; + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {true, 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, Groups, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(AgcDriver::VulkanDevice& device, std::uint32_t depth, std::uint32_t pitch) { + Run(device, depth); + for (std::uint32_t index = 0; index < Width * Height; ++index) { + const auto x = index % Width; + const auto y = index / Width; + const auto expected = Mark | (y * pitch + x); + const auto actual = Output[index * 4u]; + Require(actual == expected, "linear 2D texture with DEPTH " + std::to_string(depth) + ": texel (" + std::to_string(x) + ", " + std::to_string(y) + ") is " + Hex(actual) + ", expected " + Hex(expected) + " (row pitch " + std::to_string(pitch) + ")"); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (std::uint32_t index = 0; index < Texels.size(); ++index) Texels[index] = Mark | index; + for (const auto& [depth, pitch] : std::array, 6>{{{15u, 64u}, {63u, 64u}, {64u, 128u}, {127u, 128u}, {128u, 192u}, {255u, 256u}}}) { + Check(*device, depth, pitch); + } + std::puts("linear texture pitch 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/ManyDirectSamplers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ManyDirectSamplers.cpp new file mode 100644 index 0000000000..c2bdd9a4be --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ManyDirectSamplers.cpp @@ -0,0 +1,229 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.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 = 4; +constexpr std::uint32_t Lanes = 64; +constexpr std::uint32_t Samplers = 17; +constexpr std::uint32_t Last = Samplers - 1u; +constexpr std::uint32_t SamplerWord = 32; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Format32Float = 22; +constexpr std::uint32_t Format8888UNorm = 56; +constexpr std::uint32_t Format8888Srgb = 130; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t ForceDegamma = 1u << 20u; +constexpr std::uint32_t Elements = Last * 2u + 1u; +constexpr std::uint64_t FloatElements = (std::uint64_t{1} << Elements) - 1u; +constexpr std::uint64_t IntegerElements = (std::uint64_t{1} << (Last * 2u)) - 1u; +constexpr std::array PointClamp{0x92u, (4u * 256u) << 12u, 0u, 0u}; +constexpr std::array OpaqueBlackBorder{0x1b6u, (4u * 256u) << 12u, 0u, 1u << 30u}; +constexpr std::array OpaqueWhiteBorder{0x1b6u, (4u * 256u) << 12u, 0u, 2u << 30u}; + +alignas(256) std::array FloatTexels{}; +alignas(256) std::array ColorTexels{}; +alignas(256) std::array UintTexels{}; +alignas(256) std::array Output{}; +alignas(256) std::array Srt{}; + +std::vector SamplerCode(bool lastOutside) { + std::vector code{0xf4080700u, 0xfa000020u, 0xf4080900u, 0xfa000040u, 0xf4080a00u, 0xfa000050u}; + code.insert(code.end(), {0x360200bfu, 0x7e020d01u, 0x060202f0u, 0x100202ffu, 0x3c800000u, 0x7e0402ffu, 0x3e000000u, 0x061402f4u, 0x7e160302u, 0x7e220280u}); + for (std::uint32_t sampler = 0; sampler < Samplers; ++sampler) { + code.insert(code.end(), {0xf4080800u, 0xfa000000u | ((SamplerWord + sampler * 4u) * 4u)}); + if (sampler == Last) { + code.insert(code.end(), {0xf09c8f08u, lastOutside ? 0x01090c0au : 0x01090c01u}); + continue; + } + code.insert(code.end(), {0xf09c8f08u, 0x01090401u, sampler == 0u ? 0x7e100304u : 0x06100908u, 0xf09c8f08u, 0x010a0401u, 0x4a222304u}); + } + code.insert(code.end(), {0x7e240308u, 0x7e120202u, 0x34201286u, 0x4a202100u, 0x34202084u, 0xe0781000u, 0x80070c10u, 0xe0741800u, 0x80071110u, 0xbf810000u}); + return code; +} + +const std::vector InsideCode = SamplerCode(false); +const std::vector OutsideCode = SamplerCode(true); + +std::array Buffer(const void* base, std::uint32_t bytes) { + const auto address = static_cast(reinterpret_cast(base)); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x31016facu}; +} + +std::array Image(const void* texels, std::uint32_t format, std::uint32_t bcSwizzle = 0u) { + const auto address = static_cast(reinterpret_cast(texels)); + return {static_cast(address >> 8u), static_cast((address >> 40u) & 0xffu) | (format << 20u) | (((Width - 1u) & 3u) << 30u), ((Width - 1u) >> 2u) | ((Height - 1u) << 14u) | (1u << 31u), 0xfacu | (bcSwizzle << 25u) | (Type2D << 28u)}; +} + +struct Outcome { + std::vector lanes; + std::vector integerSums; + std::vector floatSums; + std::vector> samplers; + std::vector imageSamplers; +}; + +Outcome Run(AgcDriver::VulkanDevice& device, const std::vector& code, const std::array& image, const std::array& last) { + Srt = {}; + const auto output = Buffer(Output.data(), static_cast(sizeof(Output))); + std::copy(output.begin(), output.end(), Srt.begin() + 8); + const auto integerImage = Image(UintTexels.data(), Format32UInt); + std::copy(image.begin(), image.end(), Srt.begin() + 16); + std::copy(integerImage.begin(), integerImage.end(), Srt.begin() + 20); + for (std::uint32_t sampler = 0; sampler < Samplers; ++sampler) { + const auto& words = sampler == Last ? last : PointClamp; + std::copy(words.begin(), words.end(), Srt.begin() + SamplerWord + sampler * 4u); + } + const auto rootAddress = reinterpret_cast(Srt.data()); + const std::array userData{static_cast(rootAddress), static_cast(rootAddress >> 32u)}; + const std::span span(code); + const std::array memory{{ + {reinterpret_cast(span.data()), std::as_bytes(span)}, + {reinterpret_cast(Srt.data()), std::as_bytes(std::span(Srt))}, + }}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Lanes, 1, 1}, 0u, {true, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(span.data()), span, 0, {}}, + {64, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + Outcome outcome; + for (const auto& binding : result.bindings) { + if (binding.role == ShaderRecompiler::DescriptorRole::GuestSamplers) { + for (std::size_t word = 0; word + 4u <= binding.guestDescriptor.size(); word += 4u) outcome.samplers.push_back({binding.guestDescriptor[word], binding.guestDescriptor[word + 1u], binding.guestDescriptor[word + 2u], binding.guestDescriptor[word + 3u]}); + } + if (binding.kind == ShaderRecompiler::DescriptorKind::SampledImage) outcome.imageSamplers.insert(outcome.imageSamplers.end(), binding.imageSamplers.begin(), binding.imageSamplers.end()); + } + Output.fill(-1.0f); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(span.data())); + device.WaitIdle(); + outcome.lanes.assign(Output.begin(), Output.begin() + Lanes * 4u); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) { + outcome.integerSums.push_back(std::bit_cast(Output[512u + lane * 4u])); + outcome.floatSums.push_back(Output[513u + lane * 4u]); + } + return outcome; +} + +std::string Refusal(AgcDriver::VulkanDevice& device, const std::vector& code, const std::array& image, const std::array& last) { + try { + static_cast(Run(device, code, image, last)); + } catch (const std::exception& error) { + return error.what(); + } + return {}; +} + +float Component(const Outcome& outcome, std::uint32_t lane, std::uint32_t component) { + return outcome.lanes.at(lane * 4u + component); +} + +float FirstSamples(float texel) { + float sum = texel; + for (std::uint32_t sampler = 1; sampler < Last; ++sampler) sum += texel; + return sum; +} + +void Fill() { + for (std::uint32_t y = 0; y < Height; ++y) { + for (std::uint32_t x = 0; x < Width; ++x) { + FloatTexels[y * Width + x] = static_cast(x) + 100.0f * static_cast(y) + 0.25f; + ColorTexels[y * Width + x] = 0x1000u + x * 3u + 0x100u * y; + UintTexels[y * Width + x] = 0x2000u + x * 5u + 0x100u * y; + } + } +} + +void CheckPairing(AgcDriver::VulkanDevice& device) { + const auto outcome = Run(device, InsideCode, Image(FloatTexels.data(), Format32Float), OpaqueWhiteBorder); + Require(outcome.samplers.size() == Elements, "many direct samplers: the sampler binding holds " + std::to_string(outcome.samplers.size()) + " elements, expected " + std::to_string(Elements)); + const auto& words = outcome.samplers[Elements - 1u]; + Require(words == OpaqueWhiteBorder, "many direct samplers: sampler element " + std::to_string(Elements - 1u) + " does not hold the last S#"); + std::vector expected{FloatElements, IntegerElements}; + auto paired = outcome.imageSamplers; + std::sort(paired.begin(), paired.end()); + std::sort(expected.begin(), expected.end()); + Require(paired == expected, "many direct samplers: the float image is not paired with sampler elements 0 to " + std::to_string(Elements - 1u) + " and the integer image with 0 to " + std::to_string(Elements - 2u)); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) Require(outcome.integerSums[lane] == Last * UintTexels[lane % Width], "many direct samplers: lane " + std::to_string(lane) + " summed " + std::to_string(outcome.integerSums[lane]) + " from the integer image through the 16 point copies, expected " + std::to_string(Last * UintTexels[lane % Width])); + std::printf("many direct samplers: %u samplers bound as %zu sampler elements; the float image is paired with elements 0 to %u, the last sampler's at element %u, and the integer image with 0 to %u through the point copies\n", Samplers, outcome.samplers.size(), Elements - 1u, Elements - 1u, Elements - 2u); +} + +void CheckBorder(AgcDriver::VulkanDevice& device) { + const auto image = Image(FloatTexels.data(), Format32Float); + const auto border = Run(device, OutsideCode, image, OpaqueWhiteBorder); + const auto clamped = Run(device, OutsideCode, image, PointClamp); + const auto edge = FloatTexels[Width - 1u]; + for (std::uint32_t lane = 0; lane < Lanes; ++lane) { + const auto expected = FirstSamples(FloatTexels[lane % Width]); + Require(std::bit_cast(border.floatSums[lane]) == std::bit_cast(expected), "many direct samplers: lane " + std::to_string(lane) + " summed " + std::to_string(border.floatSums[lane]) + " through samplers 0 to 15, expected " + std::to_string(expected)); + Require(Component(border, lane, 0) == 1.0f && Component(border, lane, 3) == 1.0f, "many direct samplers: lane " + std::to_string(lane) + " read " + std::to_string(Component(border, lane, 0)) + ", alpha " + std::to_string(Component(border, lane, 3)) + " outside the image through sampler 16, expected its opaque white border"); + Require(Component(clamped, lane, 0) == edge, "many direct samplers: the clamped control of lane " + std::to_string(lane) + " read " + std::to_string(Component(clamped, lane, 0)) + ", expected the edge texel " + std::to_string(edge)); + } + std::puts("many direct samplers: sampler 16, at sampler element 32, reads its opaque white border outside the image; a clamping S# there reads the edge texel"); +} + +void CheckDegamma(AgcDriver::VulkanDevice& device) { + const auto image = Image(ColorTexels.data(), Format8888UNorm); + auto forced = PointClamp; + forced[0] |= ForceDegamma; + const auto last = Run(device, InsideCode, image, forced); + const auto srgb = Run(device, InsideCode, Image(ColorTexels.data(), Format8888Srgb), PointClamp); + const auto plain = Run(device, InsideCode, image, PointClamp); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) { + for (std::uint32_t component = 0; component < 4u; ++component) { + Require(std::bit_cast(Component(last, lane, component)) == std::bit_cast(Component(srgb, lane, component)), "many direct samplers: lane " + std::to_string(lane) + " component " + std::to_string(component) + " sampled " + std::to_string(Component(last, lane, component)) + " with FORCE_DEGAMMA on sampler 16, " + std::to_string(Component(srgb, lane, component)) + " from the same texels as 8_8_8_8_SRGB"); + } + Require(std::bit_cast(last.floatSums[lane]) == std::bit_cast(srgb.floatSums[lane]), "many direct samplers: lane " + std::to_string(lane) + " summed " + std::to_string(last.floatSums[lane]) + " with FORCE_DEGAMMA on sampler 16, " + std::to_string(srgb.floatSums[lane]) + " from the same texels as 8_8_8_8_SRGB"); + Require(Component(last, lane, 1) < Component(plain, lane, 1), "many direct samplers: lane " + std::to_string(lane) + " green " + std::to_string(Component(last, lane, 1)) + " with FORCE_DEGAMMA on sampler 16 is not decoded below " + std::to_string(Component(plain, lane, 1))); + } + std::puts("many direct samplers: FORCE_DEGAMMA on sampler 16, at sampler element 32, decodes the paired 8_8_8_8_UNORM image as 8_8_8_8_SRGB"); +} + +void CheckBorderSwizzle(AgcDriver::VulkanDevice& device) { + const auto alphaImage = Image(FloatTexels.data(), Format32Float, 1u); + const auto control = Refusal(device, InsideCode, alphaImage, PointClamp); + Require(control.empty(), "many direct samplers: an alpha-moving BC swizzle without an opaque black border was refused: " + control); + std::puts("many direct samplers: an alpha-moving BC swizzle paired with a point-clamp S# on sampler 16 is planned"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const auto limits = device->graphicsContext().limits; + if (limits.maxPerStageDescriptorSamplers < Elements || limits.maxDescriptorSetSamplers < Elements) { + std::printf("skipped, the device binds fewer than %u samplers per stage or set\n", Elements); + return VulkanTestSkipped; + } + Fill(); + CheckPairing(*device); + CheckBorder(*device); + CheckDegamma(*device); + CheckBorderSwizzle(*device); + std::puts("many direct samplers 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 1cb6bea34a..da21e4b249 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingVertex.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/MeshProvokingVertex.cpp @@ -25,6 +25,7 @@ constexpr std::uint32_t Height = 64; using Image = std::array; alignas(256) Image Pixels{}; alignas(256) constexpr std::array Indices{0, 1, 2, 3}; +alignas(256) constexpr std::array FanIndices{1, 0, 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} }}; @@ -65,7 +66,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, bool strip = false) { +Image Draw(const std::uint32_t* pixelCode, bool flat, bool last, std::uint32_t primitive = 4u) { Pixels.fill(std::byte{0}); const auto color = reinterpret_cast(Pixels.data()); const auto geometry = reinterpret_cast(GeometryCode.data()); @@ -99,13 +100,13 @@ Image Draw(const std::uint32_t* pixelCode, bool flat, bool last, bool strip = fa {0x8, static_cast(pixel >> 8u)}, {0x9, static_cast(pixel >> 40u)}, {0xb, 0} }; std::vector commands; - Write(commands, 0x79, 0x242, strip ? 6u : 4u); + Write(commands, 0x79, 0x242, primitive); 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()); - const std::uint32_t count = strip ? 4u : 3u; + const auto address = reinterpret_cast(primitive == 5u ? FanIndices.data() : Indices.data()); + const std::uint32_t count = primitive == 4u ? 3u : 4u; 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"); @@ -145,12 +146,24 @@ void CheckFlat(const Image& image, std::span #include #include +#include #include #include #include @@ -31,6 +35,9 @@ #include #include #include +#include +#include +#include #include #ifdef _WIN32 #ifndef NOMINMAX @@ -47,10 +54,32 @@ #endif #endif -namespace AgcDriver::DriverDetail { -struct DriverTestAccess { - static std::shared_ptr GetDevice() { return Driver::Get().device.Load(); } +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } }; + +} + +namespace AgcDriver::DriverDetail { + +struct DriverTestAccess { + static void UseDevice(std::shared_ptr device) { Driver::Get().device = std::move(device); } + static std::shared_ptr GetDevice() { return Driver::Get().device.Load(); } + static void Stop() { Driver::Get().stop(); } + static void WaitQueue() { + auto& driver = Driver::Get(); + std::unique_lock lock(driver.mutex); + const auto target = driver.accepted; + ++driver.idleWaiters; + driver.changed.wait(lock, [&] { return driver.failure != nullptr || driver.stopping || driver.completed >= target; }); + --driver.idleWaiters; + driver.rethrowFailure(); + driver.checkStopping(); + } +}; + } namespace { @@ -357,6 +386,23 @@ void QueueTargetByteWrite(const Context& context, Recorder& recorder, std::uint6 recorder.NotePendingWrite(address, 1u); } +void QueueTargetFill(const Context& context, Recorder& recorder, std::uint64_t address, std::size_t bytes, std::uint32_t pattern) { + auto buffer = std::make_shared(context, bytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT); + context.Function("vkCmdFillBuffer")(recorder.Commands(), buffer->Handle(), 0u, bytes, pattern); + recorder.Keep(buffer); + AgcDriver::GuestMemory::MarkWritten(address, bytes); + recorder.NotePendingWrite(address, bytes); +} + +void QueueTargetWordFill(const Context& context, Recorder& recorder, std::uint64_t address, std::size_t bytes, std::uint32_t word) { + AgcDriver::GuestMemory::StoreOwnBytes(address, bytes, [&] { for (std::size_t offset = 0u; offset < bytes; offset += 4u) std::memcpy(reinterpret_cast(address + offset), &word, 4u); }); + auto buffer = std::make_shared(context, bytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT); + context.Function("vkCmdFillBuffer")(recorder.Commands(), buffer->Handle(), 0u, bytes, word); + recorder.Keep(buffer); + recorder.NotePendingWordFill(address, bytes, word); + AgcDriver::GuestMemory::MarkWritten(address, bytes); +} + std::vector FetchShader(std::uint32_t width, std::uint32_t height, std::uint32_t fragments, bool arrayed, bool floating) { ShaderRecompiler::SpirvModule module; module.EmitCapability(spv::CapabilityShader); @@ -423,14 +469,17 @@ std::vector FetchShader(std::uint32_t width, std::uint32_t height class Fetch { public: - Fetch(const Context& context, const Texture& texture, Buffer& output, std::span code, bool firstLayer = false) : context(context) { + Fetch(const Context& context, const Texture& texture, Buffer& output, std::span code, bool firstLayer = false) : Fetch(context, std::array{VkDescriptorImageInfo{VK_NULL_HANDLE, firstLayer ? texture.FirstLayerView() : texture.View(), texture.Layout()}}, output, code) {} + Fetch(const Context& context, std::span images, 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}}}; + const auto imageBinding = [](std::size_t index) { return index == 0u ? 0u : static_cast(index) + 1u; }; + std::vector bindings{{1u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}}; + for (std::size_t index = 0u; index < images.size(); ++index) bindings.push_back({imageBinding(index), VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1u, VK_SHADER_STAGE_COMPUTE_BIT, nullptr}); VkDescriptorSetLayoutCreateInfo info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; - info.bindingCount = 2u; + info.bindingCount = static_cast(bindings.size()); info.pBindings = bindings.data(); Check(context.Function("vkCreateDescriptorSetLayout")(context.device, &info, nullptr, &setLayout), "fetch descriptor layout"); - const std::array sizes{{{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1u}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u}}}; + const std::array sizes{{{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, static_cast(images.size())}, {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1u}}}; VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; poolInfo.maxSets = 1u; poolInfo.poolSizeCount = 2u; @@ -438,10 +487,10 @@ public: Check(context.Function("vkCreateDescriptorPool")(context.device, &poolInfo, nullptr, &pool), "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), "fetch descriptor set"); - const VkDescriptorImageInfo image{VK_NULL_HANDLE, firstLayer ? texture.FirstLayerView() : 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); + std::vector writes{{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, 1u, 0u, 1u, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nullptr, &buffer, nullptr}}; + for (std::size_t index = 0u; index < images.size(); ++index) writes.push_back({VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr, set, imageBinding(index), 0u, 1u, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, &images[index], nullptr, nullptr}); + context.Function("vkUpdateDescriptorSets")(context.device, static_cast(writes.size()), writes.data(), 0u, nullptr); VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; layoutInfo.setLayoutCount = 1u; layoutInfo.pSetLayouts = &setLayout; @@ -479,6 +528,67 @@ private: VkPipeline pipeline = VK_NULL_HANDLE; }; +std::vector DepthStencilFetchShader(std::uint32_t width, std::uint32_t height, std::uint32_t fragments) { + 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 f32 = module.Type(spv::OpTypeFloat, 32u); + const auto u4 = module.Type(spv::OpTypeVector, u32, 4u); + const auto f4 = module.Type(spv::OpTypeVector, f32, 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, u4); + 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 depthType = module.Type(spv::OpTypeImage, f32, spv::Dim2D, 0u, 0u, 1u, 1u, spv::ImageFormatUnknown); + const auto stencilType = module.Type(spv::OpTypeImage, u32, spv::Dim2D, 0u, 0u, 1u, 1u, spv::ImageFormatUnknown); + const auto depth = module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, depthType), spv::StorageClassUniformConstant); + module.AddAnnotation(spv::OpDecorate, depth, spv::DecorationDescriptorSet, 0u); + module.AddAnnotation(spv::OpDecorate, depth, spv::DecorationBinding, 0u); + const auto stencil = module.DefineGlobalVariable(module.Type(spv::OpTypePointer, spv::StorageClassUniformConstant, stencilType), spv::StorageClassUniformConstant); + module.AddAnnotation(spv::OpDecorate, stencil, spv::DecorationDescriptorSet, 0u); + module.AddAnnotation(spv::OpDecorate, stencil, spv::DecorationBinding, 2u); + 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 operation = [&](spv::Op op, std::uint32_t type, auto... operands) { + const auto result = module.AllocateId(); + module.AddFunction(op, type, result, operands...); + return result; + }; + const auto pixel = operation(spv::OpUDiv, u32, index, constant(fragments)); + const auto sample = operation(spv::OpUMod, u32, index, constant(fragments)); + const auto x = operation(spv::OpBitcast, i32, operation(spv::OpUMod, u32, pixel, constant(width))); + const auto y = operation(spv::OpBitcast, i32, operation(spv::OpUMod, u32, operation(spv::OpUDiv, u32, pixel, constant(width)), constant(height))); + const auto coordinate = operation(spv::OpCompositeConstruct, v2, x, y); + const auto depthValue = operation(spv::OpImageFetch, f4, operation(spv::OpLoad, depthType, depth), coordinate, spv::ImageOperandsSampleMask, sample); + const auto stencilValue = operation(spv::OpImageFetch, u4, operation(spv::OpLoad, stencilType, stencil), coordinate, spv::ImageOperandsSampleMask, sample); + const auto depthBits = operation(spv::OpBitcast, u32, operation(spv::OpCompositeExtract, f32, depthValue, 0u)); + const auto value = operation(spv::OpCompositeConstruct, u4, depthBits, operation(spv::OpCompositeExtract, u32, stencilValue, 0u), operation(spv::OpCompositeExtract, u32, stencilValue, 1u), operation(spv::OpCompositeExtract, u32, stencilValue, 3u)); + const auto pointer = operation(spv::OpAccessChain, module.Type(spv::OpTypePointer, spv::StorageClassStorageBuffer, u4), buffer, constant(0u), operation(spv::OpIAdd, u32, index, constant(1u))); + 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(); +} + std::vector DepthVertexShader() { ShaderRecompiler::SpirvModule module; module.EmitCapability(spv::CapabilityShader); @@ -581,11 +691,19 @@ std::vector DepthFeedbackFragmentShader(std::uint32_t binding, bo return module.Finalize(); } +struct StencilUse { + VkCompareOp compare; + VkStencilOp pass; + std::uint8_t reference; +}; + class DepthRaster { public: - DepthRaster(const Context& context, const DepthTarget& target, VkImageView view) : context(context), extent(target.extent), samples(target.samples) { + DepthRaster(const Context& context, const DepthTarget& target, VkImageView view, std::optional stencil = std::nullopt, bool perPixel = false) : context(context), extent(target.extent), samples(target.samples) { try { - const VkAttachmentDescription attachment{0u, target.format, static_cast(samples), VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_ATTACHMENT_STORE_OP_DONT_CARE, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL}; + const auto stencilLoad = target.stencilAddress != 0u ? VK_ATTACHMENT_LOAD_OP_LOAD : VK_ATTACHMENT_LOAD_OP_DONT_CARE; + const auto stencilStore = target.stencilAddress != 0u ? VK_ATTACHMENT_STORE_OP_STORE : VK_ATTACHMENT_STORE_OP_DONT_CARE; + const VkAttachmentDescription attachment{0u, target.format, static_cast(samples), VK_ATTACHMENT_LOAD_OP_LOAD, VK_ATTACHMENT_STORE_OP_STORE, stencilLoad, stencilStore, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL}; const VkAttachmentReference reference{0u, VK_IMAGE_LAYOUT_GENERAL}; VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; @@ -637,8 +755,17 @@ public: VkPipelineDepthStencilStateCreateInfo depth{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO}; depth.depthTestEnable = depth.depthWriteEnable = VK_TRUE; depth.depthCompareOp = VK_COMPARE_OP_ALWAYS; + if (stencil) { + depth.stencilTestEnable = VK_TRUE; + depth.front = depth.back = {VK_STENCIL_OP_KEEP, stencil->pass, VK_STENCIL_OP_KEEP, stencil->compare, 0xffu, 0xffu, stencil->reference}; + } VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; + constexpr std::array dynamicStates{VK_DYNAMIC_STATE_SCISSOR, VK_DYNAMIC_STATE_STENCIL_REFERENCE}; + VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; + dynamic.dynamicStateCount = static_cast(dynamicStates.size()); + dynamic.pDynamicStates = dynamicStates.data(); VkGraphicsPipelineCreateInfo info{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO}; + if (perPixel) info.pDynamicState = &dynamic; info.stageCount = 2u; info.pStages = stages.data(); info.pVertexInputState = &input; @@ -675,6 +802,31 @@ public: context.Function("vkCmdEndRenderPass")(commands); RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); } + template + void RunPerPixel(VkCommandBuffer commands, float offset, Reference reference) const { + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT); + VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + begin.renderPass = pass; + begin.framebuffer = framebuffer; + begin.renderArea = {{0, 0}, extent}; + context.Function("vkCmdBeginRenderPass")(commands, &begin, VK_SUBPASS_CONTENTS_INLINE); + for (std::uint32_t sample = 0u; sample < samples; ++sample) { + const auto value = offset + static_cast(sample + 1u) / 16.0f; + context.Function("vkCmdBindPipeline")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines[sample]); + context.Function("vkCmdPushConstants")(commands, layout, VK_SHADER_STAGE_FRAGMENT_BIT, 0u, 4u, &value); + for (std::uint32_t row = extent.height; row-- > 0u;) { + for (std::uint32_t column = extent.width; column-- > 0u;) { + const auto x = column, y = row; + const VkRect2D scissor{{static_cast(x), static_cast(y)}, {1u, 1u}}; + context.Function("vkCmdSetScissor")(commands, 0u, 1u, &scissor); + context.Function("vkCmdSetStencilReference")(commands, VK_STENCIL_FACE_FRONT_AND_BACK, reference(x, y, sample)); + context.Function("vkCmdDraw")(commands, 3u, 1u, 0u, 0u); + } + } + } + context.Function("vkCmdEndRenderPass")(commands); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + } private: void release() noexcept { for (const auto pipeline : pipelines) if (pipeline) context.Function("vkDestroyPipeline")(context.device, pipeline, nullptr); @@ -693,13 +845,47 @@ private: std::vector pipelines; }; +std::vector SingleSampleFetchShader(std::uint32_t width, std::uint32_t height) { + auto code = FetchShader(width, height, 1u, false, true); + for (std::size_t word = 5u; word < code.size();) { + const auto count = code[word] >> 16u; + const auto opcode = code[word] & 0xffffu; + if (opcode == spv::OpTypeImage && count > 6u) code[word + 6u] = 0u; + if (opcode == spv::OpImageFetch && count > 5u && code[word + 5u] == spv::ImageOperandsSampleMask) code[word + 5u] = spv::ImageOperandsLodMask; + word += count != 0u ? count : 1u; + } + return code; +} + std::array DepthSampleWords(const DepthTarget& target) { const auto base = (target.address | target.pipeBankXor) >> 8u; const auto metadata = target.htileAddress >> 8u; const auto format = target.format == VK_FORMAT_D16_UNORM ? 7u : 22u; const auto count = static_cast(std::countr_zero(target.samples)); const auto width = target.extent.width - 1u, height = target.extent.height - 1u; - return {static_cast(base), static_cast((base >> 32u) & 255u) | (format << 20u) | ((width & 3u) << 30u), (width >> 2u) | (height << 14u), (14u << 28u) | (24u << 20u) | (count << 16u) | 0x204u, 0u, count << 4u, (target.iterate256 ? 1u << 10u : 0u) | (1u << 19u) | (1u << 21u) | (static_cast(metadata & 255u) << 24u), static_cast(metadata >> 8u)}; + return {static_cast(base), static_cast((base >> 32u) & 255u) | (format << 20u) | ((width & 3u) << 30u), (width >> 2u) | (height << 14u), (14u << 28u) | (24u << 20u) | (count << 16u) | 0x204u, 0u, count << 4u, (target.iterate256 ? 1u << 10u : 0u) | (target.htilePipeAligned ? 1u << 19u : 0u) | (1u << 21u) | (static_cast(metadata & 255u) << 24u), static_cast(metadata >> 8u)}; +} + +std::array StencilSampleWords(const DepthTarget& target) { + auto words = DepthSampleWords(target); + const auto base = (target.stencilAddress | target.pipeBankXor) >> 8u; + words[0] = static_cast(base); + words[1] = static_cast((base >> 32u) & 255u) | (5u << 20u) | (words[1] & (3u << 30u)); + words[6] = 0u; + words[7] = 0u; + return words; +} + +void NoteRenderControl(const DepthTarget& target, std::uint32_t control) { + AgcDriver::QueueState queue; + queue.context[0x000] = control; + queue.context[0x010] = (target.format == VK_FORMAT_D16_UNORM ? 1u : 3u) | (static_cast(std::countr_zero(target.samples)) << 2u); + queue.context[0x011] = target.stencilAddress != 0u ? 1u : 0u; + queue.context[0x012] = static_cast((target.address | target.pipeBankXor) >> 8u); + queue.context[0x01a] = static_cast((target.address >> 40u) & 0xffu); + const auto decoded = DecodeDepthRenderControl(queue); + Require(decoded.has_value() && decoded->address == target.address && decoded->samples == target.samples && decoded->control == control, "a DB_RENDER_CONTROL draw over the native depth target was not decoded"); + NoteDepthRenderControl(*decoded); } bool DepthSupported(const Context& context, VkFormat format, std::uint32_t fragments) { @@ -859,7 +1045,8 @@ void CheckStorageAliasRetirement() { 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, DriverBufferDispatch }; +enum class ClearFault { None, PartialStore, CpuWrite, DepthWrite, BadMetadata, UnprovenDispatch, PendingWrite, Overlap, PossibleStore, CompletedPossibleStore, DriverDispatch, ImportWindowWrite, FillDuringClear, DriverBufferDispatch }; +enum class ClearSource { HtileDispatch, Fill, QueuedFill }; void CheckClearDispatch() { const std::array code{ @@ -908,7 +1095,8 @@ void CheckClearDispatch() { } } -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) { +void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, bool one, bool clearing, std::optional depthOnlyOverride, ClearFault fault, ClearSource source, std::unique_lock& gpuLock, bool iterate256 = true) { + const bool filling = source != ClearSource::HtileDispatch; 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, fault == ClearFault::DriverDispatch || fault == ClearFault::DriverBufferDispatch); @@ -1018,6 +1206,25 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, RecordMemoryBarrier(context, arrayBatch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); arrayBatch.SubmitAndWait(); check(true, offset); + const auto firstSample = DepthSurfaceFirstSample(context, words, DecodeTextureResource(words), VkComponentMapping{}); + Require(firstSample != nullptr && DepthSurfaceFirstSample(context, words, DecodeTextureResource(words), VkComponentMapping{}) == firstSample, "sample 0 of the native depth target was not read into one reused 2D image"); + recorder.Sync(); + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + { + const Fetch firstFetch(context, *firstSample, output, SingleSampleFetchShader(extent.width, extent.height)); + CommandBatch firstBatch(context); + firstFetch.Run(firstBatch.Handle(), extent.width * extent.height); + RecordMemoryBarrier(context, firstBatch.Handle(), VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + firstBatch.SubmitAndWait(); + } + output.Invalidate(); + for (std::uint32_t index = 0u; index < extent.width * extent.height; ++index) { + std::array result{}; + std::memcpy(result.data(), output.Bytes().data() + (index + 1u) * 16u, 16u); + const auto expected = offset + 1.0f / 16.0f; + const auto tolerance = format == VK_FORMAT_D16_UNORM ? 1.0f / 65535.0f : 0.0f; + Require(std::abs(result[0] - expected) <= tolerance && result[1] == 0.0f && result[2] == 0.0f && result[3] == 1.0f, "a 2D read of the native depth target did not return its sample 0 at pixel " + std::to_string(index) + ": " + std::to_string(result[0]) + ", expected " + std::to_string(expected)); + } } if (clearing) { for (std::uint32_t iteration = 0u; iteration < 3u; ++iteration) { @@ -1030,71 +1237,95 @@ 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; - if ((fault == ClearFault::DriverDispatch || fault == ClearFault::DriverBufferDispatch) && clearOne) { - struct ShutdownDriver { - ~ShutdownDriver() { - try { AgcDriverShutdown_nid_postfix(); } catch (...) {} + if (source == ClearSource::HtileDispatch) { + if ((fault == ClearFault::DriverDispatch || fault == ClearFault::DriverBufferDispatch) && clearOne) { + struct ShutdownDriver { + ~ShutdownDriver() { + try { AgcDriverShutdown_nid_postfix(); } catch (...) {} + } + } shutdownDriver; + const auto generation = AgcDriver::GuestMemory::TrackerGeneration(); + const auto driverContext = SubmitDriverHtileClear(target.htileAddress, gpuLock, fault == ClearFault::DriverBufferDispatch); + 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"); + std::puts("Native HTILE tracked dispatch completed"); } - } shutdownDriver; - const auto generation = AgcDriver::GuestMemory::TrackerGeneration(); - const auto driverContext = SubmitDriverHtileClear(target.htileAddress, gpuLock, fault == ClearFault::DriverBufferDispatch); - 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"); - std::puts("Native HTILE tracked dispatch completed"); + 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::FillDuringClear) NoteDepthMetadataFill(target.htileAddress, static_cast(metadataBytes), clearPattern); + 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 && fault == ClearFault::None) { + TargetRefuses([&] { + auto uploadContext = context; + uploadContext.hostImportAlignment = 0; + GuestBufferMemory buffer(uploadContext); + buffer.AddWritable(target.htileAddress, metadataBytes); + buffer.Upload(false); + }, "sample-preserving guest publication"); + const auto accepted = std::pair{target.htileAddress, static_cast(metadataBytes)}; + RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes, accepted); + TargetRefuses([&] { RequireNoResidentDepthRead_nid_no_patch(target.address, bytes, accepted); }, "sample-preserving guest publication"); + TargetRefuses([&] { RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes + 1u, accepted); }, "sample-preserving guest publication"); + Require(DepthSurfaceAt(target.address), "an accepted HTILE buffer access retired its native depth owner"); + } + if (completionCandidate) ArmHtileDepthClear(target.htileAddress, static_cast(metadataBytes)); + if (completionCandidate && fault == ClearFault::None) { + TargetRefuses([&] { RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes, std::pair{target.htileAddress, static_cast(metadataBytes)}); }, "sample-preserving guest publication"); + } + 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}); } } 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 && fault == ClearFault::None) { - TargetRefuses([&] { - auto uploadContext = context; - uploadContext.hostImportAlignment = 0; - GuestBufferMemory buffer(uploadContext); - buffer.AddWritable(target.htileAddress, metadataBytes); - buffer.Upload(false); - }, "sample-preserving guest publication"); - const auto accepted = std::pair{target.htileAddress, static_cast(metadataBytes)}; - RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes, accepted); - TargetRefuses([&] { RequireNoResidentDepthRead_nid_no_patch(target.address, bytes, accepted); }, "sample-preserving guest publication"); - TargetRefuses([&] { RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes + 1u, accepted); }, "sample-preserving guest publication"); - Require(DepthSurfaceAt(target.address), "an accepted HTILE buffer access retired its native depth owner"); - } - if (completionCandidate) ArmHtileDepthClear(target.htileAddress, static_cast(metadataBytes)); - if (completionCandidate && fault == ClearFault::None) { - TargetRefuses([&] { RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes, std::pair{target.htileAddress, static_cast(metadataBytes)}); }, "sample-preserving guest publication"); - } - 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 (source == ClearSource::QueuedFill) { + const std::uint32_t queuedPattern = fault == ClearFault::BadMetadata ? 0x0fu : clearPattern; + AgcDriver::GuestMemory::StoreOwnBytes(target.htileAddress, static_cast(metadataBytes), [&] { + for (std::size_t i = 0u; i < metadataBytes / 4u; ++i) std::memcpy(metadata.Data() + i * 4u, &queuedPattern, 4u); + }); + QueueTargetFill(context, recorder, target.htileAddress, static_cast(metadataBytes), queuedPattern); + NoteDepthMetadataFill(target.htileAddress, static_cast(metadataBytes), queuedPattern); + } else { + NoteDepthMetadataFill(target.htileAddress, static_cast(metadataBytes), clearPattern); + if (fault == ClearFault::Overlap) NoteHtileDepthClear(target.htileAddress, static_cast(metadataBytes), nativeBytes); + 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) { @@ -1117,7 +1348,7 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments, batch.SubmitAndWait(); check(false, 0.0f); } - std::printf("Native depth completed clear format=%d samples=%u depthOnly=%u value=%u cycles=3\n", format, fragments, depthOnly, one); + std::printf("Native depth completed %s format=%d samples=%u depthOnly=%u value=%u cycles=3\n", source == ClearSource::QueuedFill ? "queued metadata fill" : filling ? "metadata fill" : "clear", format, fragments, depthOnly, one); } } Require(std::all_of(memory.Data(), memory.Data() + bytes, [](auto value) { return value == std::byte{0xa5}; }), "native depth sampling silently published guest memory"); @@ -1299,6 +1530,592 @@ void CheckHtileRouting(Context context, VkFormat format, std::uint32_t fragments std::printf("Native depth format=%u samples=%u: binds over the owner's HTILE left its words alone\n", static_cast(format), fragments); } +enum class StencilFault { None, DepthWord, StencilWord, PartialStencil, MixedDepth, UnwatchedStencil, StencilWrite, StencilQueued, FillPartial, FillDepthWord, FillStencilWord, FillPartialStore, FillCpuWrite, FillStencilWrite, FillOverlap, MaskClear }; + +void CheckStencilTarget(Context context, std::uint32_t fragments, bool one, bool filling, StencilFault fault = StencilFault::None) { + const VkExtent2D extent{5u, 3u}; + const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, fragments)); + const auto stencilBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, fragments)); + TargetMemory memory(bytes), stencil(stencilBytes), metadata(65536u); + const auto metadataBytes = static_cast(HtileMetadataBytes(extent)); + const auto clearWord = [](bool value) { return value ? 0xfffc00f0u : 0u; }; + const auto setWord = [&](std::size_t offset, std::uint32_t word) { std::memcpy(metadata.Data() + offset, &word, 4u); }; + const auto setWords = [&](std::uint32_t word, std::size_t count) { for (std::size_t offset = 0u; offset + 4u <= count; offset += 4u) setWord(offset, word); }; + setWords(clearWord(one), metadataBytes); + if (fault == StencilFault::DepthWord) setWord(0u, clearWord(one) | 0xfu); + if (fault == StencilFault::StencilWord) setWord(0u, clearWord(one) | 0x300u); + if (fault == StencilFault::PartialStencil) setWord(metadataBytes - 4u, clearWord(one) | 0x100u); + if (fault == StencilFault::MixedDepth) setWord(metadataBytes - 4u, clearWord(!one)); + if (fault == StencilFault::UnwatchedStencil) AgcDriver::GuestMemory::Unwatch(stencil.Address(), stencilBytes); + DepthTarget target{memory.Address(), stencil.Address(), extent, VK_FORMAT_D32_SFLOAT_S8_UINT, one ? 1.0f : 0.0f, 0x5au, metadata.Address(), true, fragments}; + target.depthSwizzle = 24u; + target.pipeBankXor = one ? 0x8c00u : 0u; + target.iterate256 = target.htilePipeAligned = 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 view = DepthSurfaceView(context, target); + NoteDepthSurfaceWrite(context, target); + const auto words = DepthSampleWords(target); + auto texture = CachedSampledTexture(context, words); + Require(texture != nullptr && CachedSampledTexture(context, words) == texture, "native depth-stencil sampled view was not reused"); + std::array probe{}; + for (const auto address : {target.stencilAddress, target.stencilAddress + stencilBytes - 1u}) TargetRefuses([&] { AgcDriver::GuestMemory::Read(address, probe); }, "sample-preserving guest publication"); + if (fault == StencilFault::StencilWrite) stencil.Data()[stencilBytes - 1u] ^= std::byte{0x01}; + if (fault == StencilFault::StencilQueued) QueueTargetByteWrite(context, recorder, target.stencilAddress + stencilBytes - 1u, std::byte{0x5a}); + if (fault == StencilFault::StencilWrite || fault == StencilFault::StencilQueued) CachedSampledTexture(context, words); + recorder.Sync(); + const auto count = extent.width * extent.height * fragments; + Buffer output(context, (count + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const Fetch fetch(context, *texture, output, FetchShader(extent.width, extent.height, fragments, false, true)); + const auto sample = [&](const DepthRaster* raster, float offset, std::optional drawn) { + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + if (raster != nullptr) { + CommandBatch rasterBatch(context); + raster->Run(rasterBatch.Handle(), offset); + rasterBatch.SubmitAndWait(); + Require(CachedSampledTexture(context, words) == texture, "native depth-stencil sampled view changed after a raster write"); + recorder.Sync(); + } + 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(); + for (std::uint32_t index = 0u; index < count; ++index) { + std::array result{}; + std::memcpy(result.data(), output.Bytes().data() + (index + 1u) * 16u, 16u); + const auto expected = drawn ? *drawn + static_cast(index % fragments + 1u) / 16.0f : target.clearDepth; + Require(result == std::array{expected, 0.0f, 0.0f, 1.0f}, "native depth-stencil sampled fetch lost a depth sample or a stencil test outcome"); + } + }; + const auto checkCleared = [&] { + sample(nullptr, 0.0f, std::nullopt); + const DepthRaster miss(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, static_cast(target.clearStencil + 1u)}); + sample(&miss, 0.375f, std::nullopt); + const DepthRaster hit(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, target.clearStencil}); + sample(&hit, 0.25f, 0.25f); + }; + checkCleared(); + if (filling) { + const DepthRaster replace(context, target, view, StencilUse{VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0xc3u}); + for (std::uint32_t iteration = 0u; iteration < 3u; ++iteration) { + const auto offset = static_cast(iteration) / 16.0f; + sample(&replace, offset, offset); + { + const DepthRaster stale(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, target.clearStencil}); + sample(&stale, 0.375f, offset); + } + const bool clearOne = iteration == 1u ? !one : one; + target.clearDepth = clearOne ? 1.0f : 0.0f; + target.clearStencil = iteration == 1u ? 0x33u : 0x5au; + Require(DepthSurfaceView(context, target) == view, "updating the native depth-stencil clear values replaced its attachment"); + const auto fillBytes = fault == StencilFault::FillPartial ? metadataBytes - 4u : metadataBytes; + if (fault == StencilFault::MaskClear) NoteHtileDepthClear(target.htileAddress, metadataBytes, metadataBytes); + else NoteDepthMetadataFill(target.htileAddress, fillBytes, clearWord(clearOne)); + if (fault == StencilFault::FillOverlap) NoteDepthMetadataFill(target.htileAddress, fillBytes, clearWord(clearOne)); + const auto storedBytes = fault == StencilFault::FillPartialStore ? 4u : fillBytes; + AgcDriver::GuestMemory::StoreOwnBytes(target.htileAddress, storedBytes, [&] { + setWords(clearWord(clearOne), storedBytes); + if (fault == StencilFault::FillDepthWord) setWord(0u, clearWord(clearOne) | 0xfu); + if (fault == StencilFault::FillStencilWord) setWord(0u, clearWord(clearOne) | 0x300u); + }); + if (fault == StencilFault::FillCpuWrite) metadata.Data()[metadataBytes - 1u] ^= std::byte{0x01}; + if (fault == StencilFault::FillStencilWrite) stencil.Data()[0] ^= std::byte{0x01}; + Require(CachedSampledTexture(context, words) == texture, "completed native depth-stencil fill replaced its sampled view"); + recorder.Sync(); + checkCleared(); + } + } + Require(std::all_of(stencil.Data(), stencil.Data() + stencilBytes, [](auto value) { return value == std::byte{0xa5}; }), "native depth-stencil sampling silently published the stencil plane"); + texture.reset(); + std::printf("Native depth-stencil samples=%u clear=%u fill=%u: imported, stencil-tested, sampled%s\n", fragments, one, filling, filling ? " and refilled three times" : ""); +} + +enum class PlaneFault { None, StencilByte, StencilWrite, UnwatchedStencil, HtileWord, PartialStencilFill, StencilFillPattern, StencilFillUnstored, StencilFillCpuWrite, StencilFillStoredFirst }; +enum class PlaneFill { None, Htile, Stencil, Both }; + +void CheckSeparateStencilTarget(Context context, std::uint32_t fragments, bool one, PlaneFill fill, PlaneFault fault = PlaneFault::None, bool pipeAligned = true) { + const VkExtent2D extent{5u, 3u}; + const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, fragments)); + const auto stencilBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, fragments)); + TargetMemory memory(bytes), stencil(stencilBytes), metadata(65536u); + const auto metadataBytes = static_cast(HtileMetadataBytes(extent)); + const std::uint32_t clearWord = one ? 0xfffffff0u : 0u; + const auto setWords = [&] { for (std::size_t offset = 0u; offset + 4u <= metadataBytes; offset += 4u) std::memcpy(metadata.Data() + offset, &clearWord, 4u); }; + setWords(); + if (fault == PlaneFault::HtileWord) metadata.Data()[metadataBytes - 4u] ^= std::byte{0x10}; + const std::uint8_t clearStencil = one ? 0x5au : 0u; + std::memset(stencil.Data(), clearStencil, stencilBytes); + if (fault == PlaneFault::StencilByte) stencil.Data()[stencilBytes / 2u] ^= std::byte{0x01}; + if (fault == PlaneFault::UnwatchedStencil) AgcDriver::GuestMemory::Unwatch(stencil.Address(), stencilBytes); + const std::vector imported(stencil.Data(), stencil.Data() + stencilBytes); + DepthTarget target{memory.Address(), stencil.Address(), extent, VK_FORMAT_D32_SFLOAT_S8_UINT, one ? 1.0f : 0.0f, clearStencil, metadata.Address(), false, fragments}; + target.depthSwizzle = 24u; + target.pipeBankXor = one ? 0x8c00u : 0u; + target.iterate256 = target.htilePipeAligned = pipeAligned; + 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 view = DepthSurfaceView(context, target); + const auto words = DepthSampleWords(target); + auto texture = CachedSampledTexture(context, words); + Require(texture != nullptr && CachedSampledTexture(context, words) == texture, "native depth with a separate stencil plane: sampled view was not reused"); + if (fault == PlaneFault::StencilWrite) { + stencil.Data()[stencilBytes - 1u] ^= std::byte{0x01}; + CachedSampledTexture(context, words); + } + recorder.Sync(); + const auto count = extent.width * extent.height * fragments; + Buffer output(context, (count + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const Fetch fetch(context, *texture, output, FetchShader(extent.width, extent.height, fragments, false, true)); + auto bareWords = words; + bareWords[6] &= ~((0xffu << 24u) | (1u << 21u) | (1u << 19u)); + bareWords[7] = 0u; + NoteRenderControl(target, 0x60u); + auto bare = CachedSampledTexture(context, bareWords); + Require(bare != nullptr && bare != texture && CachedSampledTexture(context, bareWords) == bare, "native depth sampled through a descriptor without HTILE was not given its own reused view"); + const Fetch bareFetch(context, *bare, output, FetchShader(extent.width, extent.height, fragments, false, true)); + auto xorWords = bareWords; + xorWords[0] ^= 0x04u; + TargetRefuses([&] { CachedSampledTexture(context, xorWords); }, "native multisample depth sampled metadata or XOR mismatch"); + auto otherWords = words; + otherWords[7] += 1u; + TargetRefuses([&] { CachedSampledTexture(context, otherWords); }, "native multisample depth sampled metadata or XOR mismatch"); + const auto sample = [&](const Fetch& reader, const DepthRaster* raster, float offset, std::optional drawn) { + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + if (raster != nullptr) { + CommandBatch rasterBatch(context); + raster->Run(rasterBatch.Handle(), offset); + rasterBatch.SubmitAndWait(); + Require(CachedSampledTexture(context, words) == texture, "native depth with a separate stencil plane: sampled view changed after a raster write"); + recorder.Sync(); + } + CommandBatch batch(context); + reader.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(); + for (std::uint32_t index = 0u; index < count; ++index) { + std::array result{}; + std::memcpy(result.data(), output.Bytes().data() + (index + 1u) * 16u, 16u); + const auto expected = drawn ? *drawn + static_cast(index % fragments + 1u) / 16.0f : target.clearDepth; + Require(result == std::array{expected, 0.0f, 0.0f, 1.0f}, "native depth with a separate stencil plane: a sampled fetch lost a depth sample or a stencil test outcome"); + } + }; + sample(fetch, nullptr, 0.0f, std::nullopt); + sample(bareFetch, nullptr, 0.0f, std::nullopt); + { + const DepthRaster miss(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, static_cast(clearStencil + 1u)}); + sample(fetch, &miss, 0.375f, std::nullopt); + const DepthRaster hit(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, clearStencil}); + sample(fetch, &hit, 0.25f, 0.25f); + sample(bareFetch, nullptr, 0.0f, 0.25f); + } + const bool htileFill = fill == PlaneFill::Htile || fill == PlaneFill::Both; + const bool stencilFill = fill == PlaneFill::Stencil || fill == PlaneFill::Both; + const std::uint8_t stencilValue = one ? 0x11u : 0u; + if (fill != PlaneFill::None) { + const DepthRaster replace(context, target, view, StencilUse{VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0xc3u}); + sample(fetch, &replace, 0.125f, 0.125f); + if (htileFill) { + NoteDepthMetadataFill(target.htileAddress, metadataBytes, clearWord); + AgcDriver::GuestMemory::StoreOwnBytes(target.htileAddress, metadataBytes, setWords); + } + if (stencilFill) { + const auto fillBytes = fault == PlaneFault::PartialStencilFill ? stencilBytes / 2u : stencilBytes; + const std::uint32_t pattern = fault == PlaneFault::StencilFillPattern ? 0x00010001u : stencilValue * 0x01010101u; + const auto storeStencil = [&] { AgcDriver::GuestMemory::StoreOwnBytes(stencil.Address(), fillBytes, [&] { std::memset(stencil.Data(), stencilValue, fillBytes); }); }; + if (fault == PlaneFault::StencilFillStoredFirst) storeStencil(); + NoteDepthMetadataFill(stencil.Address(), fillBytes, pattern); + if (fault != PlaneFault::StencilFillUnstored && fault != PlaneFault::StencilFillStoredFirst) storeStencil(); + if (fault == PlaneFault::StencilFillCpuWrite) stencil.Data()[stencilBytes - 1u] ^= std::byte{0x01}; + } + Require(CachedSampledTexture(context, words) == texture, "a whole-plane fill replaced the sampled view of a target with a separate stencil plane"); + recorder.Sync(); + if (htileFill) sample(fetch, nullptr, 0.0f, std::nullopt); + else sample(fetch, nullptr, 0.0f, 0.125f); + const std::uint8_t expected = stencilFill ? stencilValue : 0xc3u; + const DepthRaster kept(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, expected}); + sample(fetch, &kept, 0.25f, 0.25f); + const DepthRaster other(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, static_cast(expected == 0xc3u ? clearStencil : 0xc3u)}); + sample(fetch, &other, 0.375f, 0.25f); + sample(bareFetch, nullptr, 0.0f, 0.25f); + } + if (stencilFill) Require(std::all_of(stencil.Data(), stencil.Data() + stencilBytes, [&](auto value) { return value == std::byte{stencilValue}; }), "native depth sampling with a separate stencil plane changed the filled guest stencil plane"); + else Require(std::equal(imported.begin(), imported.end(), stencil.Data()), "native depth sampling with a separate stencil plane changed the guest stencil plane"); + bare.reset(); + texture.reset(); + std::printf("Native depth with a separate stencil plane samples=%u clear=%u fill=%u pipeAligned=%u: imported, stencil-tested and sampled with and without HTILE; other metadata and XOR refused\n", fragments, one, static_cast(fill), pipeAligned); +} + +enum class GhostFill { Stored, QueuedAfterNote, QueuedBeforeNote, QueuedNotedTwice }; +enum class GhostFault { None, LaterStencilWrite, OtherHtileWord, PartialHtileFill, CreationRefused, RepeatedStencilNote }; + +void CheckGhostSequence(Context context, std::uint32_t fragments, GhostFill path, GhostFault fault = GhostFault::None) { + const VkExtent2D extent{5u, 3u}; + const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, fragments)); + const auto stencilBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, fragments)); + TargetMemory memory(bytes), stencil(stencilBytes), metadata(65536u); + const auto metadataBytes = static_cast(HtileMetadataBytes(extent)); + std::memset(memory.Data(), 0x5c, bytes); + std::memset(metadata.Data(), 0, metadataBytes); + if (fault == GhostFault::CreationRefused) metadata.Data()[metadataBytes - 4u] = std::byte{0x10}; + std::memset(stencil.Data(), 0, stencilBytes); + DepthTarget target{memory.Address(), stencil.Address(), extent, VK_FORMAT_D32_SFLOAT_S8_UINT, 0.0f, 0u, metadata.Address(), false, fragments}; + target.depthSwizzle = 24u; + 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 view = DepthSurfaceView(context, target); + for (const auto& range : {std::pair{target.htileAddress, metadataBytes}, std::pair{target.stencilAddress, stencilBytes}}) { + const auto address = range.first; + const auto size = range.second; + const bool htile = address == target.htileAddress; + if (path == GhostFill::QueuedBeforeNote) QueueTargetWordFill(context, recorder, address, size, 0u); + NoteDepthMetadataFill(address, size, 0u); + if (!htile && fault == GhostFault::RepeatedStencilNote) NoteDepthMetadataFill(address, size, 0u); + if (path == GhostFill::QueuedNotedTwice) { + QueueTargetWordFill(context, recorder, address, size, 0u); + NoteDepthMetadataFill(address, size, 0u); + } + if (path == GhostFill::QueuedAfterNote) QueueTargetWordFill(context, recorder, address, htile && fault == GhostFault::PartialHtileFill ? size - 4u : size, htile && fault == GhostFault::OtherHtileWord ? 0xfffffff0u : 0u); + if (path == GhostFill::Stored) AgcDriver::GuestMemory::StoreOwnBytes(address, size, [&] { std::memset(reinterpret_cast(address), 0, size); }); + } + if (fault == GhostFault::LaterStencilWrite) QueueTargetByteWrite(context, recorder, target.stencilAddress + stencilBytes - 1u, std::byte{0}); + const auto words = DepthSampleWords(target); + auto texture = CachedSampledTexture(context, words); + Require(texture != nullptr, "native depth sampled after the HTILE and stencil plane fills was not imported"); + recorder.Sync(); + const auto count = extent.width * extent.height * fragments; + Buffer output(context, (count + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const Fetch fetch(context, *texture, output, FetchShader(extent.width, extent.height, fragments, false, true)); + const auto sample = [&](const DepthRaster* raster, float offset, std::optional drawn) { + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + if (raster != nullptr) { + CommandBatch rasterBatch(context); + raster->Run(rasterBatch.Handle(), offset); + rasterBatch.SubmitAndWait(); + Require(CachedSampledTexture(context, words) == texture, "native depth sampled after the HTILE and stencil plane fills changed its view after a raster write"); + recorder.Sync(); + } + 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(); + for (std::uint32_t index = 0u; index < count; ++index) { + std::array result{}; + std::memcpy(result.data(), output.Bytes().data() + (index + 1u) * 16u, 16u); + const auto expected = drawn ? *drawn + static_cast(index % fragments + 1u) / 16.0f : target.clearDepth; + Require(result == std::array{expected, 0.0f, 0.0f, 1.0f}, "native depth sampled after the HTILE and stencil plane fills lost a depth sample or a stencil test outcome"); + } + }; + sample(nullptr, 0.0f, std::nullopt); + const DepthRaster miss(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, 1u}); + sample(&miss, 0.375f, std::nullopt); + const DepthRaster hit(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, 0u}); + sample(&hit, 0.25f, 0.25f); + Require(std::all_of(stencil.Data(), stencil.Data() + stencilBytes, [](auto value) { return value == std::byte{0}; }), "native depth sampling after the stencil plane fill changed the guest stencil plane"); + texture.reset(); + std::printf("Native depth after HTILE and stencil plane fills samples=%u path=%u: imported, sampled the cleared depth and stencil-tested the filled stencil\n", fragments, static_cast(path)); +} + +enum class AliasCase { Cleared, DrawnAfterClear, OtherFormat, OtherSamples, Drawn }; + +constexpr std::string_view stencilAliasRefusal = "native multisample stencil plane aliases a multisampled colour target whose samples are only on the host"; + +void CheckAliasedStencil(Context context, std::uint32_t fragments, AliasCase which, std::uint32_t colorFragments = 0u, bool filled = true) { + const VkExtent2D extent{5u, 3u}; + const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, fragments)); + const auto stencilBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, fragments)); + TargetMemory memory(bytes), stencil(stencilBytes), metadata(65536u); + const auto metadataBytes = static_cast(HtileMetadataBytes(extent)); + std::memset(metadata.Data(), 0, metadataBytes); + DepthTarget target{memory.Address(), stencil.Address(), extent, VK_FORMAT_D32_SFLOAT_S8_UINT, 0.0f, 0u, metadata.Address(), false, fragments}; + target.depthSwizzle = 24u; + target.iterate256 = target.htilePipeAligned = true; + target.htileDepthOnly = true; + ColorTarget color{}; + color.address = color.surfaceAddress = stencil.Address(); + color.extent = color.surfaceExtent = extent; + color.format = which == AliasCase::OtherFormat ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8_UNORM; + color.elementBytes = which == AliasCase::OtherFormat ? 2u : 1u; + color.tileMode = ColorTileMode::RenderTarget; + color.samples = which == AliasCase::OtherSamples ? colorFragments : fragments; + color.bytes = static_cast(extent.width) * extent.height * color.elementBytes; + Recorder recorder(context); + recorder.Activate(); + struct Cleanup { + Recorder& recorder; + Context context; + ~Cleanup() { recorder.Sync(); ClearCachedTextures(context.device); ClearDepthSurfaces(context.device); ClearMultisampleTargets(context.device); } + } cleanup{recorder, context}; + const auto absorb = [&](std::uint8_t value) { + const std::uint32_t pattern = value * 0x01010101u; + Require(MultisampledColorFillAbsorbs(color.address, stencilBytes, pattern), "the colour target over the stencil plane did not absorb its fill"); + NoteDepthMetadataFill(color.address, stencilBytes, pattern); + NoteColorMetadataFill(color.address, stencilBytes, pattern); + }; + constexpr std::uint8_t cleared = 0x5au, refilled = 0x33u; + MultisampleTargetView(context, color); + if (which != AliasCase::Drawn) absorb(cleared); + if (which == AliasCase::DrawnAfterClear) MultisampleTargetView(context, color); + const auto view = DepthSurfaceView(context, target); + const auto words = DepthSampleWords(target); + auto texture = CachedSampledTexture(context, words); + Require(texture != nullptr, "native depth over a stencil plane aliased by a colour target was not imported"); + recorder.Sync(); + const auto count = extent.width * extent.height * fragments; + Buffer output(context, (count + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const Fetch fetch(context, *texture, output, FetchShader(extent.width, extent.height, fragments, false, true)); + const auto read = [&](std::optional drawn) { + 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(); + for (std::uint32_t index = 0u; index < count; ++index) { + std::array result{}; + std::memcpy(result.data(), output.Bytes().data() + (index + 1u) * 16u, 16u); + const auto expected = drawn ? *drawn + static_cast(index % fragments + 1u) / 16.0f : target.clearDepth; + Require(result == std::array{expected, 0.0f, 0.0f, 1.0f}, "native depth over an aliased stencil plane lost a depth sample or a stencil test outcome"); + } + }; + const auto sample = [&](const DepthRaster& raster, float offset, std::optional drawn) { + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + CommandBatch rasterBatch(context); + raster.Run(rasterBatch.Handle(), offset); + rasterBatch.SubmitAndWait(); + Require(CachedSampledTexture(context, words) == texture, "native depth over an aliased stencil plane changed its view after a raster write"); + recorder.Sync(); + read(drawn); + }; + const auto untouched = [&] { return std::all_of(stencil.Data(), stencil.Data() + stencilBytes, [](auto value) { return value == std::byte{0xa5}; }); }; + if (which != AliasCase::Cleared) { + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + read(std::nullopt); + const auto stencilWords = StencilSampleWords(target); + TargetRefuses([&] { DepthSurfaceView(context, target, true, true); }, stencilAliasRefusal); + TargetRefuses([&] { DepthSurfaceViewWithStencilWriteIntent(context, target, true, true, false); }, stencilAliasRefusal); + TargetRefuses([&] { CachedSampledTexture(context, stencilWords); }, stencilAliasRefusal); + Require(DepthSurfaceView(context, target) == view && CachedSampledTexture(context, words) == texture, "a depth-only use of a target whose stencil plane is unproven was refused or replaced its view"); + Require(DepthSurfaceViewWithStencilWriteIntent(context, target, true, false, false) == view, "a depth-only bind of an unproven stencil plane was refused or replaced its view"); + if (!filled) { + recorder.Sync(); + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + read(std::nullopt); + Require(untouched(), "native depth over an unproven aliased stencil plane read or wrote the guest stencil plane"); + texture.reset(); + std::printf("Native depth over a stencil plane aliased by a colour target samples=%u case=%u: imported with the plane unproven, depth sampled, a stencil-tested bind and a stencil plane sample refused\n", fragments, static_cast(which)); + return; + } + absorb(cleared); + Require(DepthSurfaceView(context, target, true, true) == view, "a stencil-tested bind after a whole stencil plane fill replaced its attachment"); + recorder.Sync(); + } + sample(DepthRaster(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, static_cast(cleared + 1u)}), 0.375f, std::nullopt); + sample(DepthRaster(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, cleared}), 0.25f, 0.25f); + absorb(refilled); + Require(CachedSampledTexture(context, words) == texture, "a fill of the colour target over an aliased stencil plane replaced the sampled view"); + recorder.Sync(); + sample(DepthRaster(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, cleared}), 0.375f, 0.25f); + sample(DepthRaster(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, refilled}), 0.125f, 0.125f); + MultisampleTargetView(context, color); + TargetRefuses([&] { CachedSampledTexture(context, words); }, "native multisample stencil plane aliases a multisampled colour target whose samples are only on the host"); + Require(untouched(), "native depth over an aliased stencil plane read or wrote the guest stencil plane"); + texture.reset(); + std::printf("Native depth over a stencil plane aliased by an R8 colour target samples=%u case=%u: %s, refilled from its fill, refused once the colour target drew\n", fragments, static_cast(which), which == AliasCase::Cleared ? "imported from the colour target's clear" : "imported unproven and proven by a whole-plane fill the colour target absorbed"); +} + +void CheckStencilView(Context context, std::uint32_t fragments) { + const VkExtent2D extent{5u, 3u}; + const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, fragments)); + const auto stencilBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, fragments)); + TargetMemory memory(bytes), stencil(stencilBytes), metadata(65536u); + const auto metadataBytes = static_cast(HtileMetadataBytes(extent)); + std::memset(metadata.Data(), 0, metadataBytes); + std::memset(stencil.Data(), 0, stencilBytes); + DepthTarget target{memory.Address(), stencil.Address(), extent, VK_FORMAT_D32_SFLOAT_S8_UINT, 0.0f, 0u, metadata.Address(), false, fragments}; + target.depthSwizzle = 24u; + target.pipeBankXor = 0x8c00u; + target.iterate256 = target.htilePipeAligned = true; + 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 view = DepthSurfaceView(context, target); + const auto depthWords = DepthSampleWords(target); + const auto stencilWords = StencilSampleWords(target); + auto depth = CachedSampledTexture(context, depthWords); + auto stencilTexture = CachedSampledTexture(context, stencilWords); + Require(depth != nullptr && stencilTexture != nullptr && stencilTexture != depth && CachedSampledTexture(context, stencilWords) == stencilTexture, "the stencil plane of a native D32S8 target was not given its own reused sampled view"); + Require(DepthSurfaceHolds(context, DecodeTextureResource(stencilWords), stencilTexture.get()), "the native stencil view is not held by its attachment"); + recorder.Sync(); + const auto count = extent.width * extent.height * fragments; + Buffer output(context, (count + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const std::array images{VkDescriptorImageInfo{VK_NULL_HANDLE, depth->View(), depth->Layout()}, VkDescriptorImageInfo{VK_NULL_HANDLE, stencilTexture->View(), stencilTexture->Layout()}}; + const Fetch fetch(context, images, output, DepthStencilFetchShader(extent.width, extent.height, fragments)); + const DepthRaster raster(context, target, view, StencilUse{VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0u}, true); + const auto check = [&](std::optional drawn, auto reference) { + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + 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(); + for (std::uint32_t index = 0u; index < count; ++index) { + std::array result{}; + std::memcpy(result.data(), output.Bytes().data() + (index + 1u) * 16u, 16u); + const auto sample = index % fragments, pixel = index / fragments; + const auto expected = drawn ? *drawn + static_cast(sample + 1u) / 16.0f : target.clearDepth; + const auto stencilExpected = drawn ? reference(pixel % extent.width, pixel / extent.width, sample) : target.clearStencil; + Require(result == std::array{std::bit_cast(expected), stencilExpected, 0u, 1u}, "a native D32S8 sample read through the depth and stencil views in one dispatch lost its depth, its stencil or the stencil view's component mapping"); + } + Require(std::all_of(output.Bytes().begin(), output.Bytes().begin() + 16u, [](auto byte) { return byte == std::byte{0xa5}; }), "the depth and stencil fetch changed its output guard"); + }; + check(std::nullopt, [](std::uint32_t, std::uint32_t, std::uint32_t) { return 0u; }); + for (const auto round : {0u, 1u}) { + const auto reference = [&](std::uint32_t x, std::uint32_t y, std::uint32_t sample) { return 0x10u + round * 0x78u + (y * extent.width + x) * fragments + sample; }; + const auto offset = static_cast(round) / 8.0f; + { + CommandBatch rasterBatch(context); + raster.RunPerPixel(rasterBatch.Handle(), offset, reference); + rasterBatch.SubmitAndWait(); + } + Require(CachedSampledTexture(context, stencilWords) == stencilTexture && CachedSampledTexture(context, depthWords) == depth, "a raster write replaced the native depth or stencil view"); + recorder.Sync(); + check(offset, reference); + } + auto resource = DecodeTextureResource(stencilWords); + for (const auto& [field, expected] : std::array{ + std::pair{0u, "native multisample depth sampled identity or format mismatch"}, + std::pair{1u, "native multisample depth sampled identity or format mismatch"}, + std::pair{2u, "native multisample depth sampled identity or format mismatch"}, + std::pair{3u, "native multisample depth sampled array, mip or layout is not implemented"}, + std::pair{4u, "native multisample depth sampled metadata or XOR mismatch"}, + std::pair{5u, "native multisample depth sampled metadata or XOR mismatch"}}) { + auto changed = resource; + switch (field) { + case 0u: changed.format = 1u; break; + case 1u: changed.samples = fragments == 2u ? 4u : 2u; break; + case 2u: ++changed.width; break; + case 3u: changed.tileMode = TextureTileMode::kR64KBX; break; + case 4u: changed.pipeBankXor ^= 0x100u; break; + case 5u: changed.dccAddress = target.htileAddress; break; + } + TargetRefuses([&] { DepthSurfaceTexture(context, stencilWords, changed, VkComponentMapping{}); }, expected); + } + Require(std::all_of(stencil.Data(), stencil.Data() + stencilBytes, [](auto value) { return value == std::byte{0}; }), "sampling the native stencil view read or wrote the guest stencil plane"); + stencilTexture.reset(); + depth.reset(); + std::printf("Native D32S8 stencil view samples=%u: per-pixel, per-sample stencil and depth read in one dispatch through an R8_UINT and a D32 2D MSAA T#, twice after raster writes; other stencil descriptors refused\n", fragments); +} + +void CheckExpandGate(Context context, std::uint32_t fragments, bool stencilPlane) { + const VkExtent2D extent{5u, 3u}; + const auto bytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, fragments)); + const auto stencilBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, fragments)); + TargetMemory memory(bytes), stencil(stencilBytes), metadata(65536u); + const auto metadataBytes = static_cast(HtileMetadataBytes(extent)); + std::memset(metadata.Data(), 0, metadataBytes); + std::memset(stencil.Data(), 0, stencilBytes); + DepthTarget target{memory.Address(), stencilPlane ? stencil.Address() : 0u, extent, stencilPlane ? VK_FORMAT_D32_SFLOAT_S8_UINT : VK_FORMAT_D32_SFLOAT, 0.0f, 0u, metadata.Address(), false, fragments}; + target.depthSwizzle = 24u; + target.iterate256 = target.htilePipeAligned = true; + 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 view = DepthSurfaceView(context, target); + const auto words = DepthSampleWords(target); + auto texture = CachedSampledTexture(context, words); + Require(texture != nullptr, "native depth for the in-place expand gate was not imported"); + auto bareWords = words; + bareWords[6] &= ~((0xffu << 24u) | (1u << 21u) | (1u << 19u)); + bareWords[7] = 0u; + const auto refused = [&] { TargetRefuses([&] { CachedSampledTexture(context, bareWords); }, "native multisample depth sampled metadata or XOR mismatch"); }; + const std::uint32_t expand = stencilPlane ? 0x60u : 0x40u; + refused(); + if (stencilPlane) { + NoteRenderControl(target, 0x40u); + refused(); + } + NoteRenderControl(target, expand); + auto bare = CachedSampledTexture(context, bareWords); + Require(bare != nullptr && bare != texture && CachedSampledTexture(context, bareWords) == bare, "native depth sampled without metadata after an in-place expand was not given its own reused view"); + recorder.Sync(); + const auto count = extent.width * extent.height * fragments; + Buffer output(context, (count + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + const Fetch fetch(context, *bare, output, FetchShader(extent.width, extent.height, fragments, false, true)); + const auto sample = [&](const DepthRaster* raster, float offset, std::optional drawn) { + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + if (raster != nullptr) { + CommandBatch rasterBatch(context); + raster->Run(rasterBatch.Handle(), offset); + rasterBatch.SubmitAndWait(); + Require(CachedSampledTexture(context, bareWords) == bare, "native depth sampled without metadata changed its view after a raster write"); + recorder.Sync(); + } + 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(); + for (std::uint32_t index = 0u; index < count; ++index) { + std::array result{}; + std::memcpy(result.data(), output.Bytes().data() + (index + 1u) * 16u, 16u); + const auto expected = drawn ? *drawn + static_cast(index % fragments + 1u) / 16.0f : target.clearDepth; + Require(result == std::array{expected, 0.0f, 0.0f, 1.0f}, "native depth sampled without metadata after an in-place expand lost a depth sample"); + } + }; + sample(nullptr, 0.0f, std::nullopt); + const DepthRaster raster(context, target, view); + sample(&raster, 0.25f, 0.25f); + NoteDepthMetadataFill(target.htileAddress, metadataBytes, 0u); + AgcDriver::GuestMemory::StoreOwnBytes(target.htileAddress, metadataBytes, [&] { std::memset(metadata.Data(), 0, metadataBytes); }); + refused(); + NoteRenderControl(target, expand); + Require(CachedSampledTexture(context, bareWords) == bare, "native depth sampled without metadata was not accepted again after another in-place expand"); + NoteRenderControl(target, 0x1u); + refused(); + NoteRenderControl(target, expand); + Require(CachedSampledTexture(context, bareWords) == bare, "native depth sampled without metadata was not accepted again after another in-place expand"); + if (stencilPlane) { + NoteDepthMetadataFill(stencil.Address(), stencilBytes, 0u); + AgcDriver::GuestMemory::StoreOwnBytes(stencil.Address(), stencilBytes, [&] { std::memset(stencil.Data(), 0, stencilBytes); }); + } else { + if (NoteHtileDepthClear(target.htileAddress, metadataBytes, metadataBytes)) ArmHtileDepthClear(target.htileAddress, metadataBytes); + AgcDriver::GuestMemory::StoreOwnBytes(target.htileAddress, metadataBytes, [&] { std::memset(metadata.Data(), 0, metadataBytes); }); + } + refused(); + bare.reset(); + texture.reset(); + std::printf("Native depth in-place expand gate samples=%u stencil=%u: sampled without metadata only after an expand draw, refused again after an HTILE fill, a clear draw and %s\n", fragments, stencilPlane, stencilPlane ? "a stencil plane fill" : "an HTILE mask clear"); +} + void CheckEligibility(Context context) { constexpr VkExtent2D extent{129u, 67u}; constexpr std::uint32_t samples = 4u; @@ -1338,7 +2155,7 @@ void CheckEligibility(Context context) { case 8u: invalid.clearDepth = 0.5f; reason = "clear value"; break; case 9u: invalid.htileAddress = invalid.address; reason = "overlap"; break; case 10u: metadata.Data()[HtileMetadataBytes(extent) - 1u] = std::byte{0x40}; reason = "clear value"; break; - case 11u: invalid.format = VK_FORMAT_D32_SFLOAT_S8_UINT; invalid.stencilAddress = stencil.Address(); reason = "stencil"; break; + case 11u: invalid.format = VK_FORMAT_D32_SFLOAT_S8_UINT; invalid.stencilAddress = stencil.Address(); reason = "requires HTILE stencil metadata"; break; case 12u: invalid.htileDepthOnly = true; metadata.Data()[0] = std::byte{0x30}; reason = "clear value"; break; case 13u: invalid.clearDepth = 1.0f; { const std::uint32_t word = 0xfffffff0u; for (std::size_t offset = 0u; offset < HtileMetadataBytes(extent); offset += 4u) std::memcpy(metadata.Data() + offset, &word, 4u); } reason = "clear value"; break; case 14u: metadata.Data()[0] = std::byte{0xf}; reason = "clear value"; break; @@ -1389,9 +2206,44 @@ void CheckEligibility(Context context) { TargetRefuses([&] { lookup(target); }, "external GPU writes"); clear(); DepthSurfaceView(context, target); - NoteDepthMetadataFill(target.htileAddress, HtileMetadataBytes(extent), 0u); + NoteDepthMetadataFill(target.htileAddress, HtileMetadataBytes(extent) - 4u, 0u); TargetRefuses([&] { lookup(target); }, "metadata fill transition"); clear(); + DepthSurfaceView(context, target); + NoteDepthMetadataFill(target.htileAddress, HtileMetadataBytes(extent), 0u); + TargetRefuses([&] { lookup(target); }, "no complete tracked HTILE store"); + clear(); + const auto storeHtile = [&] { AgcDriver::GuestMemory::StoreOwnBytes(target.htileAddress, HtileMetadataBytes(extent), [&] { std::memset(metadata.Data(), 0, HtileMetadataBytes(extent)); }); }; + storeHtile(); + DepthSurfaceView(context, target); + NoteDepthMetadataFill(target.htileAddress, HtileMetadataBytes(extent), 0u); + TargetRefuses([&] { lookup(target); }, "no complete tracked HTILE store"); + clear(); + DepthSurfaceView(context, target); + storeHtile(); + NoteDepthMetadataFill(target.htileAddress, HtileMetadataBytes(extent), 0u); + Require(lookup(target) != nullptr, "a native depth clear whose HTILE fill was stored before its note was refused"); + clear(); + DepthSurfaceView(context, target); + QueueTargetWordFill(context, recorder, target.htileAddress, HtileMetadataBytes(extent), 0u); + recorder.Submit(); + NoteDepthMetadataFill(target.htileAddress, HtileMetadataBytes(extent), 0u); + recorder.Sync(); + Require(lookup(target) != nullptr, "a native depth clear whose HTILE fill retired unseen by its note was refused"); + clear(); + DepthSurfaceView(context, target); + QueueTargetWordFill(context, recorder, target.htileAddress, HtileMetadataBytes(extent), 0u); + NoteDepthMetadataFill(target.htileAddress, HtileMetadataBytes(extent), 0u); + recorder.Sync(); + Require(lookup(target) != nullptr, "a native depth clear whose noted HTILE fill retired before the first check was refused"); + clear(); + DepthSurfaceView(context, target); + storeHtile(); + NoteDepthMetadataFill(target.htileAddress, HtileMetadataBytes(extent), 0u); + metadata.Data()[0] = std::byte{0x01}; + TargetRefuses([&] { lookup(target); }, "CPU writes raced"); + clear(); + std::memset(metadata.Data(), 0, 65536u); const auto initialView = DepthSurfaceView(context, target); auto changedClearRegister = target; changedClearRegister.clearDepth = 1.0f; @@ -1543,6 +2395,274 @@ void CheckRetirement(Context context, VkFormat format, std::uint32_t samples, st } } +constexpr std::size_t DriverMetadataSpan = 131072u; + +void SubmitDriverDmaFill(std::uint64_t address, std::uint32_t bytes, std::uint32_t pattern) { + std::array words{0xc0055000u, 0x40000000u, pattern, 0u, static_cast(address), static_cast(address >> 32u), bytes}; + Packet dcb{words.data(), static_cast(words.size()), 0, {}}; + Require(sceAgcDriverSubmitDcb(&dcb) == 0, "the driver refused a DMA_DATA fill over a native depth target's HTILE"); + AgcDriver::DriverDetail::DriverTestAccess::WaitQueue(); + const auto start = std::chrono::steady_clock::now(); + while (AgcDriver::DriverDetail::DrawPipeline::Queue0().Outstanding() != 0u) { + Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the draw pipeline kept an item after a DMA_DATA fill over a native depth target's HTILE"); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } +} + +enum class DriverFill { Whole, Partial, Queued }; + +class DriverDepthTarget { +public: + DriverDepthTarget(const Context& context, Recorder& recorder, std::uint32_t fragments, bool one) + : context(context), recorder(recorder), extent{5u, 3u}, fragments(fragments), + memory(static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, fragments))), + stencil(static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, fragments))), + metadata(DriverMetadataSpan), + htileBytes(static_cast(HtileMetadataBytes(extent))), + clearWord(one ? 0xfffffff0u : 0u), + clearStencil(one ? 0x5au : 0u), + target{memory.Address(), stencil.Address(), extent, VK_FORMAT_D32_SFLOAT_S8_UINT, one ? 1.0f : 0.0f, clearStencil, metadata.Address(), false, fragments} { + for (std::size_t offset = 0u; offset < htileBytes; offset += 4u) std::memcpy(metadata.Data() + offset, &clearWord, 4u); + std::memset(stencil.Data(), clearStencil, MultisampledDepthBytes_nid_no_patch(extent, 1u, fragments)); + target.depthSwizzle = 24u; + target.pipeBankXor = one ? 0x8c00u : 0u; + target.iterate256 = target.htilePipeAligned = true; + target.htileDepthOnly = true; + words = DepthSampleWords(target); + { + GuestAllocations::Mutation mutation; + mutation.Add(metadata.Data(), DriverMetadataSpan, true, true, true); + } + const std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + view = DepthSurfaceView(context, target); + texture = CachedSampledTexture(context, words); + Require(texture != nullptr, "the native depth target on the driver's device was not imported for sampling"); + recorder.Sync(); + output = std::make_unique(context, (Count() + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + fetch = std::make_unique(context, *texture, *output, FetchShader(extent.width, extent.height, fragments, false, true)); + } + ~DriverDepthTarget() { + { + const std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + fetch.reset(); + texture.reset(); + recorder.Sync(); + ClearCachedTextures(context.device); + ClearDepthSurfaces(context.device); + } + GuestAllocations::Mutation mutation; + mutation.Remove(metadata.Data()); + } + DriverDepthTarget(const DriverDepthTarget&) = delete; + DriverDepthTarget& operator=(const DriverDepthTarget&) = delete; + + void Fill(std::size_t bytes) const { SubmitDriverDmaFill(target.htileAddress, static_cast(bytes), clearWord); } + std::shared_ptr Sampled() const { + const std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + return CachedSampledTexture(context, words); + } + void Sample(std::optional drawn, std::optional offset, std::string_view what) const { + const std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + std::memset(output->Bytes().data(), 0xa5, output->Bytes().size()); + if (offset) { + const DepthRaster raster(context, target, view, StencilUse{VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, clearStencil}); + CommandBatch batch(context); + raster.Run(batch.Handle(), *offset); + batch.SubmitAndWait(); + } + Require(CachedSampledTexture(context, words) == texture, std::string(what) + ": the sampled view of the native depth target on the driver's device changed"); + recorder.Sync(); + 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(); + for (std::uint32_t index = 0u; index < Count(); ++index) { + std::array result{}; + std::memcpy(result.data(), output->Bytes().data() + (index + 1u) * 16u, 16u); + const auto expected = drawn ? *drawn + static_cast(index % fragments + 1u) / 16.0f : target.clearDepth; + Require(result == std::array{expected, 0.0f, 0.0f, 1.0f}, std::string(what) + ": a sampled depth on the driver's device is not " + std::to_string(expected)); + } + } + std::size_t HtileBytes() const { return htileBytes; } + +private: + std::uint32_t Count() const { return extent.width * extent.height * fragments; } + + Context context; + Recorder& recorder; + VkExtent2D extent; + std::uint32_t fragments; + TargetMemory memory; + TargetMemory stencil; + TargetMemory metadata; + std::size_t htileBytes; + std::uint32_t clearWord; + std::uint8_t clearStencil; + DepthTarget target; + std::array words{}; + VkImageView view = VK_NULL_HANDLE; + std::shared_ptr texture; + std::unique_ptr output; + std::unique_ptr fetch; +}; + +void CheckDriverPathTarget(const Context& context, Recorder& recorder, std::uint32_t fragments, bool one, DriverFill fill) { + const DriverDepthTarget target(context, recorder, fragments, one); + target.Sample(std::nullopt, std::nullopt, "native depth imported on the driver's device"); + target.Sample(0.25f, 0.25f, "native depth drawn on the driver's device"); + const auto fillsBefore = AgcDriver::DriverDetail::dmaFills.load(); + if (fill == DriverFill::Partial) { + target.Fill(target.HtileBytes() / 2u); + TargetRefuses([&] { target.Sampled(); }, "native multisample depth metadata fill transition is not implemented"); + std::printf("Native depth on the driver's device samples=%u clear=%u: a DMA_DATA fill over half its HTILE refused\n", fragments, one); + return; + } + if (fill == DriverFill::Queued) { + target.Fill(DriverMetadataSpan); + Require(AgcDriver::DriverDetail::dmaFills.load() == fillsBefore + 1u, "a DMA_DATA fill of 128 KiB from the HTILE was not queued as a buffer fill"); + target.Sample(std::nullopt, std::nullopt, "a queued DMA_DATA fill of 128 KiB from the HTILE through the driver"); + std::printf("Native depth on the driver's device samples=%u clear=%u: cleared by a queued DMA_DATA fill of 128 KiB from its HTILE through the driver and sampled\n", fragments, one); + return; + } + target.Fill(target.HtileBytes()); + Require(AgcDriver::DriverDetail::dmaFills.load() == fillsBefore, "a whole-HTILE DMA_DATA fill of 64 KiB or less was stored as a queued buffer fill"); + target.Sample(std::nullopt, std::nullopt, "a whole-HTILE DMA_DATA fill through the driver"); + target.Sample(0.125f, 0.125f, "native depth drawn after a DMA_DATA clear through the driver"); + std::printf("Native depth on the driver's device samples=%u clear=%u: drawn, cleared by a DMA_DATA fill of its HTILE through the driver and sampled\n", fragments, one); +} + +int CheckDriverPath(bool queued) { +#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 + std::shared_ptr device = OpenVulkanTestDevice(); + if (device == nullptr) return VulkanTestSkipped; + AgcDriver::DriverDetail::DriverTestAccess::UseDevice(device); + const int result = [&] { + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device); + auto* recorder = Recorder::Active(); + Require(recorder != nullptr && recorder->Device() == context.device, "the driver's device did not activate its recorder"); +#ifdef _WIN32 + if (queued && PrepareImportWatch(context) == ImportWatch::Unwatch) { + std::printf("skipped, host imports are compared here, not write-watched, and a queued fill's HTILE proof needs a write-watched import\n"); + return VulkanTestSkipped; + } +#else + SetImportWatch(context, ImportWatch::Watch); +#endif + const auto supported = [&](std::uint32_t samples) { return DepthSupported(context, VK_FORMAT_D32_SFLOAT_S8_UINT, samples) && (context.limits.framebufferStencilSampleCounts & samples) != 0u; }; + Require(supported(4u), "the native depth driver-path regression requires sampled D32S8 with four samples"); + std::uint32_t checked = 0u, unavailable = 0u; + for (const auto samples : {2u, 4u, 8u}) { + if (!supported(samples)) { unavailable += 2u; continue; } + for (const bool one : {false, true}) { CheckDriverPathTarget(context, *recorder, samples, one, queued ? DriverFill::Queued : DriverFill::Whole); ++checked; } + } + if (!queued) CheckDriverPathTarget(context, *recorder, 4u, false, DriverFill::Partial); + AgcDriverWaitIdle_nid_postfix(); + std::printf("Native depth on the driver's device cases checked=%u unavailable=%u\n", checked, unavailable); + return 0; + }(); + device.reset(); + AgcDriver::DriverDetail::DriverTestAccess::Stop(); + return result; +} + +} + +void CheckUniformPlanesWithoutHtile(Context context, std::uint32_t samples) { + constexpr VkExtent2D extent{129u, 67u}; + const auto depthBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples)); + const auto stencilBytes = static_cast(MultisampledDepthBytes_nid_no_patch(extent, 1u, samples)); + TargetMemory memory(depthBytes), stencil(stencilBytes); + const auto fill = [&](float depth, std::uint8_t value) { + const auto word = std::bit_cast(depth); + for (std::size_t offset = 0u; offset < depthBytes; offset += 4u) std::memcpy(memory.Data() + offset, &word, 4u); + std::memset(stencil.Data(), value, stencilBytes); + }; + DepthTarget target{memory.Address(), stencil.Address(), extent, VK_FORMAT_D32_SFLOAT_S8_UINT, 1.0f, 0u, 0u, false, samples}; + target.depthSwizzle = 24u; + Recorder recorder(context); + recorder.Activate(); + auto words = DepthSampleWords(target); + words[6] &= ~((1u << 19u) | (1u << 21u)); + auto stencilTarget = target; + stencilTarget.address = target.stencilAddress; + auto stencilWords = DepthSampleWords(stencilTarget); + stencilWords[1] = (stencilWords[1] & ~(0x1ffu << 20u)) | (5u << 20u); + stencilWords[6] &= ~((1u << 19u) | (1u << 21u)); + const auto lookup = [&] { return DepthSurfaceTexture(context, words, DecodeTextureResource(words), VkComponentMapping{}); }; + const auto clear = [&] { + recorder.Sync(); + ClearCachedTextures(context.device); + ClearDepthSurfaces(context.device); + }; + struct Cleanup { + decltype(clear)& action; + ~Cleanup() { action(); } + } cleanup{clear}; + for (std::uint32_t field = 0u; field < 5u; ++field) { + fill(0.25f, 0x5au); + auto invalid = target; + std::string_view reason; + switch (field) { + case 0u: memory.Data()[depthBytes - 1u] = std::byte{0x3f}; reason = "does not hold one depth value"; break; + case 1u: fill(2.0f, 0x5au); reason = "does not hold one depth value"; break; + case 2u: stencil.Data()[stencilBytes - 1u] = std::byte{0x5b}; reason = "does not hold one stencil value"; break; + case 3u: invalid.htileStencil = true; reason = "stencil import"; break; + case 4u: invalid.stencilAddress = memory.Address() + 65536u; reason = "overlap"; break; + } + Require(DepthSurfaceView(context, invalid) != VK_NULL_HANDLE, "a refused uniform-plane import must preserve attachment rendering"); + TargetRefuses([&] { DepthSurfaceTexture(context, words, DecodeTextureResource(words), VkComponentMapping{}); }, reason); + clear(); + } + for (const auto& [depth, value] : std::array{std::pair{0.0f, std::uint8_t{0xc3u}}, std::pair{0.25f, std::uint8_t{0x5au}}}) { + fill(depth, value); + Require(DepthSurfaceView(context, target) != VK_NULL_HANDLE, "a uniform-plane D32S8 target without HTILE has no attachment view"); + auto texture = lookup(); + Require(texture != nullptr && texture->View() != VK_NULL_HANDLE, "a uniform-plane D32S8 target without HTILE was not sampled natively"); + Buffer output(context, (extent.width * extent.height * samples + 2u) * 16u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + { + const Fetch fetch(context, *texture, output, FetchShader(extent.width, extent.height, samples, false, true)); + CommandBatch batch(context); + fetch.Run(batch.Handle(), extent.width * extent.height * samples); + 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(); + for (std::uint32_t i = 0u; i < extent.width * extent.height * samples; ++i) { + std::array sampled{}; + std::memcpy(sampled.data(), output.Bytes().data() + (i + 1u) * 16u, 16u); + Require(sampled == std::array{depth, 0.0f, 0.0f, 1.0f}, "a uniform depth plane without HTILE was not imported into every sample"); + } + } + auto stencilTexture = DepthSurfaceTexture(context, stencilWords, DecodeTextureResource(stencilWords), VkComponentMapping{}); + Require(stencilTexture != nullptr && stencilTexture->View() != VK_NULL_HANDLE, "the stencil plane of a uniform-plane D32S8 target without HTILE was not sampled natively"); + { + std::memset(output.Bytes().data(), 0xa5, output.Bytes().size()); + const Fetch fetch(context, *stencilTexture, output, FetchShader(extent.width, extent.height, samples, false, false)); + CommandBatch batch(context); + fetch.Run(batch.Handle(), extent.width * extent.height * samples); + 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(); + for (std::uint32_t i = 0u; i < extent.width * extent.height * samples; ++i) { + std::array sampled{}; + std::memcpy(sampled.data(), output.Bytes().data() + (i + 1u) * 16u, 16u); + Require(sampled[0] == value, "a uniform stencil plane without HTILE was not imported into every sample: sample " + std::to_string(i) + " holds " + std::to_string(sampled[0]) + ", the plane " + std::to_string(value)); + } + } + stencilTexture.reset(); + texture.reset(); + stencil.Data()[0] = std::byte{static_cast(value + 1u)}; + TargetRefuses([&] { lookup(); }, "guest writes"); + clear(); + } + std::printf("Native D32S8 without HTILE, samples=%u: uniform planes imported and sampled, other planes refused\n", samples); } int main(int argc, char** argv) { @@ -1550,6 +2670,8 @@ int main(int argc, char** argv) { #ifdef __linux__ const SharedBackingScope sharedBacking; #endif + if (argc == 2 && std::string_view(argv[1]) == "--driver-path") return CheckDriverPath(false); + if (argc == 2 && std::string_view(argv[1]) == "--driver-queued-fill") return CheckDriverPath(true); std::unique_ptr device; try { device = std::make_unique(); } catch (const std::exception& error) { @@ -1572,34 +2694,194 @@ int main(int argc, char** argv) { 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, lock); }, expected); + std::pair{ClearFault::Overlap, "overlapping native multisample depth"}, + std::pair{ClearFault::FillDuringClear, "metadata fill over a pending HTILE clear dispatch"}}) { + TargetRefuses([&] { CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, fault, ClearSource::HtileDispatch, lock); }, expected); std::printf("Native depth clear fault=%u refused: %s\n", static_cast(fault), expected); } + for (const auto& [fault, expected] : std::array{ + 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::PendingWrite, "external GPU writes"}}) { + TargetRefuses([&] { CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, fault, ClearSource::QueuedFill, lock); }, expected); + std::printf("Native depth queued fill fault=%u refused: %s\n", static_cast(fault), expected); + } return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--uniform-planes") { + std::uint32_t checked = 0u, unavailable = 0u; + for (const auto samples : {2u, 4u, 8u}) { + if (!DepthSupported(context, HostDepthFormat(context, VK_FORMAT_D32_SFLOAT_S8_UINT), samples)) { ++unavailable; continue; } + CheckUniformPlanesWithoutHtile(context, samples); + ++checked; + } + std::printf("Native depth uniform-plane cases checked=%u unavailable=%u\n", checked, unavailable); + return checked != 0u ? 0 : VulkanTestSkipped; + } if (argc == 2 && std::string_view(argv[1]) == "--clears") { CheckClearDispatch(); std::uint32_t checked = 0u, unavailable = 0u; for (const auto format : {VK_FORMAT_D16_UNORM, VK_FORMAT_D32_SFLOAT}) { for (const auto samples : {2u, 4u, 8u}) { - if (!DepthSupported(context, format, samples)) { unavailable += 4u; continue; } + if (!DepthSupported(context, format, samples)) { unavailable += 12u; continue; } for (const bool depthOnly : {false, true}) { - for (const bool one : {false, true}) { CheckDepthTarget(context, format, samples, one, true, depthOnly, ClearFault::None, lock); ++checked; } + for (const bool one : {false, true}) { + for (const auto source : {ClearSource::HtileDispatch, ClearSource::Fill, ClearSource::QueuedFill}) { CheckDepthTarget(context, format, samples, one, true, depthOnly, ClearFault::None, source, lock); ++checked; } + } } } } - CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::CompletedPossibleStore, lock); + CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::CompletedPossibleStore, ClearSource::HtileDispatch, lock); ++checked; - CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::ImportWindowWrite, lock); + CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::ImportWindowWrite, ClearSource::HtileDispatch, lock); ++checked; - CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::DriverDispatch, lock); + CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::DriverDispatch, ClearSource::HtileDispatch, lock); ++checked; std::printf("Native depth completed clear cases checked=%u unavailable=%u\n", checked, unavailable); return 0; } + if (argc == 2 && std::string_view(argv[1]) == "--stencil") { + const auto supported = [&](std::uint32_t samples) { return DepthSupported(context, VK_FORMAT_D32_SFLOAT_S8_UINT, samples) && (context.limits.framebufferStencilSampleCounts & samples) != 0u; }; + Require(supported(4u), "the native depth-stencil regression requires sampled D32S8 with four samples"); + std::uint32_t checked = 0u, unavailable = 0u; + for (const auto samples : {2u, 4u, 8u}) { + if (!supported(samples)) { unavailable += 4u; continue; } + for (const bool one : {false, true}) { + for (const bool filling : {false, true}) { CheckStencilTarget(context, samples, one, filling); ++checked; } + } + } + for (const auto& [fault, filling, expected] : std::array{ + std::tuple{StencilFault::DepthWord, false, "native multisample depth initial HTILE does not prove its clear value"}, + std::tuple{StencilFault::MixedDepth, false, "native multisample depth initial HTILE does not prove its clear value"}, + std::tuple{StencilFault::StencilWord, false, "native multisample stencil initial HTILE does not prove its clear value"}, + std::tuple{StencilFault::PartialStencil, false, "native multisample stencil initial HTILE does not prove its clear value"}, + std::tuple{StencilFault::UnwatchedStencil, false, "watched accessible guest memory"}, + std::tuple{StencilFault::StencilWrite, false, "guest writes over native multisample depth"}, + std::tuple{StencilFault::StencilQueued, false, "external GPU writes"}, + std::tuple{StencilFault::FillPartial, true, "metadata fill transition"}, + std::tuple{StencilFault::FillDepthWord, true, "native multisample depth completed HTILE does not prove its clear value"}, + std::tuple{StencilFault::FillStencilWord, true, "native multisample stencil completed HTILE does not prove its clear value"}, + std::tuple{StencilFault::FillPartialStore, true, "no complete tracked HTILE store"}, + std::tuple{StencilFault::FillCpuWrite, true, "CPU writes raced"}, + std::tuple{StencilFault::FillStencilWrite, true, "guest writes over native multisample depth"}, + std::tuple{StencilFault::FillOverlap, true, "overlapping native multisample depth"}, + std::tuple{StencilFault::MaskClear, true, "HTILE mask clear of a stencil surface"}}) { + TargetRefuses([&] { CheckStencilTarget(context, 4u, false, filling, fault); }, expected); + std::printf("Native depth-stencil fault=%u refused: %s\n", static_cast(fault), expected); + } + std::printf("Native depth-stencil cases checked=%u unavailable=%u\n", checked, unavailable); + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--separate-stencil") { + const auto supported = [&](std::uint32_t samples) { return DepthSupported(context, VK_FORMAT_D32_SFLOAT_S8_UINT, samples) && (context.limits.framebufferStencilSampleCounts & samples) != 0u; }; + Require(supported(4u), "the native separate stencil regression requires sampled D32S8 with four samples"); + std::uint32_t checked = 0u, unavailable = 0u; + for (const auto samples : {2u, 4u, 8u}) { + if (!supported(samples)) { unavailable += 16u; continue; } + for (const bool one : {false, true}) { + for (const auto fill : {PlaneFill::None, PlaneFill::Htile, PlaneFill::Stencil, PlaneFill::Both}) { + for (const bool pipeAligned : {true, false}) { CheckSeparateStencilTarget(context, samples, one, fill, PlaneFault::None, pipeAligned); ++checked; } + } + } + } + for (const auto& [fault, expected] : std::array{ + std::pair{PlaneFault::StencilByte, "native multisample stencil plane does not hold its clear value"}, + std::pair{PlaneFault::StencilWrite, "guest writes over native multisample depth"}, + std::pair{PlaneFault::UnwatchedStencil, "watched accessible guest memory"}}) { + TargetRefuses([&] { CheckSeparateStencilTarget(context, 4u, false, PlaneFill::None, fault); }, expected); + std::printf("Native depth with a separate stencil plane fault=%u refused: %s\n", static_cast(fault), expected); + } + for (const auto& [fault, expected] : std::array{ + std::pair{PlaneFault::HtileWord, "native multisample depth HTILE that is not pipe-aligned holds more than one word"}}) { + TargetRefuses([&] { CheckSeparateStencilTarget(context, 4u, false, PlaneFill::None, fault, false); }, expected); + std::printf("Native depth with a separate stencil plane fault=%u refused: %s\n", static_cast(fault), expected); + } + for (const auto& [fault, expected] : std::array{ + std::pair{PlaneFault::PartialStencilFill, "native multisample stencil plane partial fill transition is not implemented"}, + std::pair{PlaneFault::StencilFillPattern, "native multisample stencil plane fill of more than one byte value is not implemented"}, + std::pair{PlaneFault::StencilFillUnstored, "native multisample stencil plane fill has no complete tracked store"}, + std::pair{PlaneFault::StencilFillCpuWrite, "CPU writes raced native multisample stencil plane fill"}}) { + TargetRefuses([&] { CheckSeparateStencilTarget(context, 4u, true, PlaneFill::Stencil, fault, false); }, expected); + std::printf("Native depth with a separate stencil plane fill fault=%u refused: %s\n", static_cast(fault), expected); + } + CheckSeparateStencilTarget(context, 4u, true, PlaneFill::Stencil, PlaneFault::StencilFillStoredFirst, false); + std::puts("Native depth with a separate stencil plane accepted a fill stored before its note"); + for (const auto samples : {2u, 4u, 8u}) { + if (!supported(samples)) { unavailable += 4u; continue; } + for (const auto path : {GhostFill::Stored, GhostFill::QueuedAfterNote, GhostFill::QueuedBeforeNote, GhostFill::QueuedNotedTwice}) { CheckGhostSequence(context, samples, path); ++checked; } + } + for (const auto fault : {GhostFault::LaterStencilWrite, GhostFault::OtherHtileWord, GhostFault::PartialHtileFill}) { + TargetRefuses([&] { CheckGhostSequence(context, 4u, GhostFill::QueuedAfterNote, fault); }, "external GPU writes over native multisample depth are not implemented"); + std::printf("Native depth after HTILE and stencil plane fills fault=%u refused: external GPU writes over native multisample depth are not implemented\n", static_cast(fault)); + } + for (const auto path : {GhostFill::Stored, GhostFill::QueuedNotedTwice}) { + TargetRefuses([&] { CheckGhostSequence(context, 4u, path, GhostFault::RepeatedStencilNote); }, "overlapping native multisample stencil plane fills are not implemented"); + std::printf("Native depth after HTILE and stencil plane fills path=%u refused a stencil plane fill noted twice without a write between: overlapping native multisample stencil plane fills are not implemented\n", static_cast(path)); + } + for (const auto path : {GhostFill::Stored, GhostFill::QueuedAfterNote}) { + TargetRefuses([&] { CheckGhostSequence(context, 4u, path, GhostFault::CreationRefused); }, "native multisample depth HTILE that is not pipe-aligned holds more than one word"); + std::printf("Native depth after HTILE and stencil plane fills path=%u kept its creation refusal: native multisample depth HTILE that is not pipe-aligned holds more than one word\n", static_cast(path)); + } + std::printf("Native depth with a separate stencil plane cases checked=%u unavailable=%u\n", checked, unavailable); + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--aliased-stencil") { + const auto supported = [&](std::uint32_t samples) { return DepthSupported(context, VK_FORMAT_D32_SFLOAT_S8_UINT, samples) && (context.limits.framebufferStencilSampleCounts & samples) != 0u && (context.limits.framebufferColorSampleCounts & samples) != 0u; }; + Require(supported(4u), "the aliased stencil plane regression requires sampled D32S8 and multisampled colour with four samples"); + const auto refusal = stencilAliasRefusal; + std::uint32_t checked = 0u, unavailable = 0u; + for (const auto samples : {2u, 4u, 8u}) { + if (!supported(samples)) { unavailable += 11u; continue; } + CheckAliasedStencil(context, samples, AliasCase::Cleared); + ++checked; + for (const auto which : {AliasCase::Drawn, AliasCase::DrawnAfterClear}) { + for (const bool filled : {true, false}) { CheckAliasedStencil(context, samples, which, 0u, filled); ++checked; } + } + CheckAliasedStencil(context, samples, AliasCase::OtherFormat, 0u, false); + TargetRefuses([&] { CheckAliasedStencil(context, samples, AliasCase::OtherFormat); }, refusal); + std::printf("Native depth with an aliased stencil plane samples=%u case=%u refused the whole-plane fill its colour target cannot confirm: %s\n", samples, static_cast(AliasCase::OtherFormat), refusal.data()); + checked += 2u; + for (const auto colorSamples : {2u, 4u, 8u}) { + if (colorSamples == samples) continue; + if ((context.limits.framebufferColorSampleCounts & colorSamples) == 0u) { unavailable += 2u; continue; } + CheckAliasedStencil(context, samples, AliasCase::OtherSamples, colorSamples, false); + TargetRefuses([&] { CheckAliasedStencil(context, samples, AliasCase::OtherSamples, colorSamples); }, refusal); + std::printf("Native depth with an aliased stencil plane samples=%u colour samples=%u refused the whole-plane fill its colour target cannot confirm: %s\n", samples, colorSamples, refusal.data()); + checked += 2u; + } + } + std::printf("Native depth with an aliased stencil plane cases checked=%u unavailable=%u\n", checked, unavailable); + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--stencil-view") { + const auto supported = [&](std::uint32_t samples) { return DepthSupported(context, VK_FORMAT_D32_SFLOAT_S8_UINT, samples) && (context.limits.framebufferStencilSampleCounts & samples) != 0u && (context.limits.sampledImageStencilSampleCounts & samples) != 0u; }; + Require(supported(4u), "the native stencil view regression requires sampled D32S8 depth and stencil with four samples"); + std::uint32_t checked = 0u, unavailable = 0u; + for (const auto samples : {2u, 4u, 8u}) { + if (!supported(samples)) { ++unavailable; continue; } + CheckStencilView(context, samples); + ++checked; + } + std::printf("Native D32S8 stencil view cases checked=%u unavailable=%u\n", checked, unavailable); + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--expand-gate") { + const auto supported = [&](std::uint32_t samples, bool stencilPlane) { return DepthSupported(context, stencilPlane ? VK_FORMAT_D32_SFLOAT_S8_UINT : VK_FORMAT_D32_SFLOAT, samples) && (!stencilPlane || (context.limits.framebufferStencilSampleCounts & samples) != 0u); }; + Require(supported(4u, true), "the in-place expand gate regression requires sampled D32S8 with four samples"); + std::uint32_t checked = 0u, unavailable = 0u; + for (const auto samples : {2u, 4u, 8u}) { + for (const bool stencilPlane : {false, true}) { + if (!supported(samples, stencilPlane)) { ++unavailable; continue; } + CheckExpandGate(context, samples, stencilPlane); + ++checked; + } + } + std::printf("Native depth in-place expand gate cases checked=%u unavailable=%u\n", checked, unavailable); + return 0; + } if (argc == 2 && std::string_view(argv[1]) == "--buffer-clear") { - CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::DriverBufferDispatch, lock); + CheckDepthTarget(context, VK_FORMAT_D32_SFLOAT, 4u, false, true, false, ClearFault::DriverBufferDispatch, ClearSource::HtileDispatch, lock); std::puts("Native depth clear by the buffer HTILE mask kernel through PM4 checked"); return 0; } @@ -1620,7 +2902,7 @@ 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, lock, iterate256); ++checked; } + for (const bool iterate256 : {true, false}) { CheckDepthTarget(context, format, samples, one, false, std::nullopt, ClearFault::None, ClearSource::HtileDispatch, lock, iterate256); ++checked; } } CheckHtileRouting(context, format, samples); CheckUnwrittenGuestAccess(context, format, samples); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/OracleRunner.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/OracleRunner.cpp new file mode 100644 index 0000000000..67f5e059a5 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/OracleRunner.cpp @@ -0,0 +1,110 @@ +#include "../../tools/OracleDispatch.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t ExtraBase = 0x5a000000; +constexpr std::uint32_t ExtraStep = 0x00010203; + +constexpr std::array Text{ + 0xf4080100, 0xfa000000, 0x34020084, 0x34040086, 0xbf8cc07f, 0xdc388000, 0x04040001, 0xbf8c3f70, + 0x7e140280, 0x7e160280, 0x7e180280, 0x7e1a0280, 0x7e1c0280, 0x7e1e0280, 0x7e200280, 0x7e220280, + 0x7e240280, 0x7e260280, 0x7e280280, 0x7e2a0280, 0x7e2c0280, 0x7e2e0280, 0x7e300280, 0x7e320280, + 0x34500082, 0x4a5050ff, 0x00000400, 0xdc308000, 0x0b040028, 0xbf8c3f70, 0x64140b04, 0xdc788000, + 0x00060a02, 0xdc788010, 0x00060e02, 0xdc788020, 0x00061202, 0xdc788030, 0x00061602, 0xbf810000, +}; + +struct Row { + std::uint32_t a; + std::uint32_t b; + std::uint32_t expected; + bool denormal; +}; + +constexpr std::array Rows{{ + {0x3c00, 0x3c00, 0x4000, false}, + {0x0001, 0x0001, 0x0002, true}, + {0x4200, 0x4400, 0x4700, false}, + {0x3c00, 0xbc00, 0x0000, false}, + {0x7bff, 0x3c00, 0x7bff, false}, + {0x7bff, 0x7bff, 0x7c00, false}, +}}; + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +void Rejects(const std::string& label, AgcDriver::VulkanDevice& device, const OracleKernel& kernel, std::uint32_t threads, std::span input) { + try { + RunOracleKernel(device, kernel, threads, input); + } catch (const std::runtime_error&) { + return; + } + throw std::runtime_error(label + ": accepted"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + OracleKernel kernel{}; + kernel.text.assign(Text.begin(), Text.end()); + kernel.ldsBytes = 4096; + kernel.waveSize = 32; + kernel.floatMode = {0xc0u, true, false, false}; + kernel.groupIdEnable = {true, false, false}; + kernel.tgSizeEnable = false; + kernel.threadIdComponentCount = 1; + + constexpr std::uint32_t threads = Threads; + std::vector input(threads * 16 + threads * 4); + for (std::uint32_t lane = 0; lane < threads; ++lane) { + const auto& row = Rows[lane % Rows.size()]; + const std::array words{row.a, row.b, 0, 0}; + std::memcpy(input.data() + lane * 16, words.data(), sizeof(words)); + const std::uint32_t extra = ExtraBase + lane * ExtraStep; + std::memcpy(input.data() + threads * 16 + lane * 4, &extra, sizeof(extra)); + } + const auto output = RunOracleKernel(*device, kernel, threads, input); + Require(output.size() == threads * 64, "output size is not 64 bytes per lane"); + const bool keepsDenormals = device->Target().float16DenormPreserve; + std::uint32_t flushedDenormalRows = 0; + for (std::uint32_t lane = 0; lane < threads; ++lane) { + std::array dwords{}; + std::memcpy(dwords.data(), output.data() + lane * 64, sizeof(dwords)); + const auto& row = Rows[lane % Rows.size()]; + if (dwords[0] != row.expected && row.denormal && !keepsDenormals && dwords[0] == 0u) { + ++flushedDenormalRows; + } else { + Require(dwords[0] == row.expected, "lane " + std::to_string(lane) + " v10 is " + std::to_string(dwords[0]) + ", expected " + std::to_string(row.expected)); + } + Require(dwords[1] == ExtraBase + lane * ExtraStep, "lane " + std::to_string(lane) + " v11 is " + std::to_string(dwords[1]) + ", expected its extra dword " + std::to_string(ExtraBase + lane * ExtraStep)); + for (std::size_t index = 2; index < dwords.size(); ++index) { + Require(dwords[index] == 0, "lane " + std::to_string(lane) + " v" + std::to_string(10 + index) + " is not 0"); + } + } + + if (flushedDenormalRows != 0) std::printf("the device does not preserve f16 denormals and flushed the denormal row to 0 in %u lanes; that row is not checked there\n", flushedDenormalRows); + + Rejects("zero threads", *device, kernel, 0, input); + Rejects("too many threads", *device, kernel, 1025, input); + Rejects("threads not a wave multiple", *device, kernel, 48, input); + Rejects("short input", *device, kernel, threads, std::span(input).first(threads * 16 - 1)); + std::puts("oracle runner passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/OverlapStagedCopies.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/OverlapStagedCopies.cpp new file mode 100644 index 0000000000..a1a92d7b2a --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/OverlapStagedCopies.cpp @@ -0,0 +1,227 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.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/Shutdown.hpp" +#include "VulkanTestDevice.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint32_t Lanes = 64; +constexpr std::uint32_t BufferBytes = Lanes * 4u; +constexpr std::uint32_t ComputeQueue = 0x20; +constexpr std::size_t BlockBytes = 65536; + +std::uint32_t* Data = nullptr; +std::uint32_t* Counters = nullptr; + +alignas(256) constexpr std::array IncrementCode{0x34020082u, 0xe0301000u, 0x80000201u, 0xbf8c3f70u, 0x4a040481u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array AtomicAddCode{0x34020082u, 0x7e040281u, 0xe0c81000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array AtomicAddCopy{0x34020082u, 0x7e040281u, 0xbf800000u, 0xe0c81000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array StoreCode{0x34020082u, 0x4a040087u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array CopyCode{0x34020082u, 0xe0301000u, 0x80000201u, 0xbf8c3f70u, 0xe0701000u, 0x80010201u, 0xbf810000u}; + +alignas(256) constexpr std::array StoreOne{0x34020082u, 0x4a040081u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array StoreTwo{0x34020082u, 0x4a040082u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array StoreThree{0x34020082u, 0x4a040083u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +alignas(256) constexpr std::array StoreFour{0x34020082u, 0x4a040084u, 0xe0701000u, 0x80000201u, 0xbf810000u}; +const std::array StoreCodes{StoreOne.data(), StoreTwo.data(), StoreThree.data(), StoreFour.data()}; + +std::uint32_t* Source = nullptr; +std::uint32_t* Target = nullptr; +std::array Staged{}; + +std::uint32_t* NewBlock() { +#ifdef _WIN32 + auto* block = static_cast(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + auto* block = static_cast(std::aligned_alloc(BlockBytes, BlockBytes)); +#endif + if (block == nullptr) throw std::runtime_error("overlap staged copies: cannot allocate a guest block"); + std::memset(block, 0, BlockBytes); + GuestAllocations::Mutation().Add(block, BlockBytes, true, true, true); + return block; +} + +void check(bool condition, const std::string& what) { + if (!condition) throw std::runtime_error("overlap staged copies: " + what); +} + +std::vector Dispatches(std::uint32_t count) { + const auto codeAddress = reinterpret_cast(IncrementCode.data()); + const auto data = reinterpret_cast(Data); + std::vector words{ + 0xc0027600u, 0x20cu, static_cast(codeAddress >> 8u), static_cast(codeAddress >> 40u), + 0xc0037600u, 0x207u, Lanes, 1u, 1u, + 0xc0017600u, 0x213u, 4u << 1u, + 0xc0047600u, 0x240u, static_cast(data), static_cast((data >> 32u) & 0xffffu), BufferBytes, 0x31016facu, + }; + for (std::uint32_t index = 0; index < count; ++index) words.insert(words.end(), {0xc0031500u, 1u, 1u, 1u, 0x8041u}); + return words; +} + +void Register(const std::uint32_t* code, std::size_t bytes) { + Shader shader{}; + shader.file_header = 0x34333231; + shader.version = 0x18; + shader.header_size = sizeof(Shader); + shader.shader_size = static_cast(bytes); + shader.code = code; + AgcDriverRegisterShader_nid_postfix(&shader); +} + +std::vector AtomicPair() { + const auto data = reinterpret_cast(Counters); + std::vector words{ + 0xc0037600u, 0x207u, Lanes, 1u, 1u, + 0xc0017600u, 0x213u, 4u << 1u, + 0xc0047600u, 0x240u, static_cast(data), static_cast((data >> 32u) & 0xffffu), BufferBytes, 0x31016facu, + }; + for (const auto* code : {AtomicAddCode.data(), AtomicAddCopy.data()}) { + const auto codeAddress = reinterpret_cast(code); + words.insert(words.end(), {0xc0027600u, 0x20cu, static_cast(codeAddress >> 8u), static_cast(codeAddress >> 40u), 0xc0031500u, 1u, 1u, 1u, 0x8041u}); + } + return words; +} + +void AtomicRound(std::uint32_t& expected, const std::string& what) { + auto words = AtomicPair(); + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check(sceAgcDriverSubmitAcb(ComputeQueue, &packet) == 0, "submission failed"); + AgcDriverWaitIdle_nid_postfix(); + expected += 2; + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(Counters), BufferBytes); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) { + const auto value = std::atomic_ref(Counters[lane]).load(std::memory_order_acquire); + check(value == expected, what + ": counter " + std::to_string(lane) + " is " + std::to_string(value) + ", expected " + std::to_string(expected)); + } +} + +void ReadRound(std::uint32_t round) { + std::memset(Source, 0, BufferBytes); + std::memset(Target, 0xff, BufferBytes); + const auto source = reinterpret_cast(Source); + const auto target = reinterpret_cast(Target); + std::vector words{ + 0xc0037600u, 0x207u, Lanes, 1u, 1u, + 0xc0017600u, 0x213u, 8u << 1u, + 0xc0087600u, 0x240u, static_cast(source), static_cast((source >> 32u) & 0xffffu), BufferBytes, 0x31016facu, + static_cast(target), static_cast((target >> 32u) & 0xffffu), BufferBytes, 0x31016facu, + }; + for (const auto* code : {StoreCode.data(), CopyCode.data()}) { + const auto codeAddress = reinterpret_cast(code); + words.insert(words.end(), {0xc0027600u, 0x20cu, static_cast(codeAddress >> 8u), static_cast(codeAddress >> 40u), 0xc0031500u, 1u, 1u, 1u, 0x8041u}); + } + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check(sceAgcDriverSubmitAcb(ComputeQueue, &packet) == 0, "submission failed"); + AgcDriverWaitIdle_nid_postfix(); + AgcDriver::GuestMemory::FlushGpuWrites(target, BufferBytes); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) { + const auto value = std::atomic_ref(Target[lane]).load(std::memory_order_acquire); + check(value == lane + 7u, "a read of an earlier unordered dispatch's staged output, round " + std::to_string(round) + ": lane " + std::to_string(lane) + " read " + std::to_string(value) + ", expected " + std::to_string(lane + 7u)); + } +} + +void StagedRun(std::uint32_t round) { + std::vector words{0xc0037600u, 0x207u, Lanes, 1u, 1u, 0xc0017600u, 0x213u, 4u << 1u}; + for (std::size_t build = 0; build < StoreCodes.size(); ++build) { + std::memset(Staged[build], 0xff, BufferBytes); + const auto data = reinterpret_cast(Staged[build]); + const auto codeAddress = reinterpret_cast(StoreCodes[build]); + words.insert(words.end(), {0xc0047600u, 0x240u, static_cast(data), static_cast((data >> 32u) & 0xffffu), BufferBytes, 0x31016facu}); + words.insert(words.end(), {0xc0027600u, 0x20cu, static_cast(codeAddress >> 8u), static_cast(codeAddress >> 40u), 0xc0031500u, 1u, 1u, 1u, 0x8041u}); + } + const auto before = AgcDriver::Graphics::Recorder::OverlapCounts().overlapped; + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check(sceAgcDriverSubmitAcb(ComputeQueue, &packet) == 0, "submission failed"); + AgcDriverWaitIdle_nid_postfix(); + const auto joined = AgcDriver::Graphics::Recorder::OverlapCounts().overlapped - before; + for (std::size_t build = 0; build < StoreCodes.size(); ++build) { + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(Staged[build]), BufferBytes); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) { + const auto value = std::atomic_ref(Staged[build][lane]).load(std::memory_order_acquire); + check(value == lane + static_cast(build) + 1u, "staged unordered dispatches, round " + std::to_string(round) + ", build " + std::to_string(build + 1) + ": lane " + std::to_string(lane) + " is " + std::to_string(value)); + } + } + check(joined == StoreCodes.size() - 1, "staged unordered dispatches of four builds, round " + std::to_string(round) + ": " + std::to_string(joined) + " joined a run, expected 3"); +} + +void Round(std::uint32_t dispatches, std::uint32_t& expected, const std::string& what) { + auto words = Dispatches(dispatches); + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + check(sceAgcDriverSubmitAcb(ComputeQueue, &packet) == 0, "submission failed"); + AgcDriverWaitIdle_nid_postfix(); + expected += dispatches; + AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast(Data), BufferBytes); + for (std::uint32_t lane = 0; lane < Lanes; ++lane) { + const auto value = std::atomic_ref(Data[lane]).load(std::memory_order_acquire); + check(value == expected, what + ": lane " + std::to_string(lane) + " is " + std::to_string(value) + ", expected " + std::to_string(expected)); + } +} + +} + +int main() { + try { +#ifdef _WIN32 + _putenv_s("APS5_WRITTEN_SHADOW_MIN_KIB", "0"); +#else + setenv("APS5_WRITTEN_SHADOW_MIN_KIB", "0", 1); +#endif + { + const auto probe = OpenVulkanTestDevice(); + if (!probe) return VulkanTestSkipped; + } + + for (const auto* code : StoreCodes) Register(code, sizeof(StoreOne)); + for (auto& block : Staged) block = NewBlock(); + for (std::uint32_t round = 1; round <= 3; ++round) StagedRun(round); + std::puts("overlap staged copies: unordered staged dispatches of four builds ran in one run"); + Register(IncrementCode.data(), sizeof(IncrementCode)); + Register(AtomicAddCode.data(), sizeof(AtomicAddCode)); + Register(AtomicAddCopy.data(), sizeof(AtomicAddCopy)); + + Data = NewBlock(); + Counters = NewBlock(); + std::uint32_t expected = 0; + Round(1, expected, "one dispatch"); + Round(2, expected, "two unordered dispatches of one build"); + Round(4, expected, "four unordered dispatches of one build"); + std::printf("overlap staged reuse: %u increments kept\n", expected); + std::uint32_t counted = 0; + for (int round = 0; round < 3; ++round) AtomicRound(counted, "two unordered atomic dispatches, round " + std::to_string(round + 1)); + std::printf("overlap staged atomics: %u atomic adds kept\n", counted); + Register(StoreCode.data(), sizeof(StoreCode)); + Register(CopyCode.data(), sizeof(CopyCode)); + Source = NewBlock(); + Target = NewBlock(); + for (std::uint32_t round = 1; round <= 3; ++round) ReadRound(round); + std::puts("overlap staged reads: a later unordered dispatch read the earlier one's staged output"); + + LibcRunShutdown_nid_postfix(); + std::puts("overlap staged copies 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/PipelineJournal.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelineJournal.cpp index 6aa6bb1e0f..7d9abb917b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PipelineJournal.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelineJournal.cpp @@ -27,6 +27,7 @@ #include #endif #include +#include #include #include #include @@ -366,6 +367,23 @@ void JournalTests() { const auto final = Snapshot(); Require(final.journal.recordedGraphics == 1 && final.journal.recordedCompute == 1 && final.journal.recordedModules == 3, "the second run recorded pipelines the journal already held"); } + constexpr std::array, 3> limits{{{"1", ""}, {"1", "0"}, {"0", "1"}}}; + for (const auto& [graphicsLimit, computeLimit] : limits) { + SetEnvironment("APS5_PIPELINE_JOURNAL_ENTRIES", graphicsLimit); + SetEnvironment("APS5_PIPELINE_JOURNAL_COMPUTE_ENTRIES", computeLimit); + const auto before = Snapshot(); + const auto device = OpenVulkanTestDevice(); + Require(device != nullptr, "a bounded replay's device could not be opened"); + device->WaitForPipelineJournalReplay(); + const auto bounded = Snapshot(); + const auto graphics = bounded.journal.replayedGraphics - before.journal.replayedGraphics; + const auto compute = bounded.journal.replayedCompute - before.journal.replayedCompute; + const auto wantGraphics = std::string(graphicsLimit) == "0" ? 0u : 1u; + const auto wantCompute = std::string(computeLimit) == "0" ? 0u : 1u; + Require(graphics == wantGraphics && compute == wantCompute && device->ComputePipelinesCreated() == wantCompute, "a replay bounded to " + std::string(graphicsLimit) + " graphics and " + (*computeLimit == '\0' ? std::string("the default") : std::string(computeLimit)) + " compute entries replayed " + std::to_string(graphics) + " graphics and " + std::to_string(compute) + " compute pipelines"); + } + SetEnvironment("APS5_PIPELINE_JOURNAL_ENTRIES", ""); + SetEnvironment("APS5_PIPELINE_JOURNAL_COMPUTE_ENTRIES", ""); } void HeldModuleTests() { @@ -553,7 +571,8 @@ void NewestEntriesTests() { 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"); + SetEnvironment("APS5_PIPELINE_JOURNAL_ENTRIES", "0"); + SetEnvironment("APS5_PIPELINE_JOURNAL_COMPUTE_ENTRIES", "2"); const auto before = Snapshot(); { const auto device = OpenVulkanTestDevice(); @@ -575,6 +594,7 @@ void NewestEntriesTests() { Require(device->ComputePipelinesCreated() == created + 1, "the oldest pipeline was built by the replay"); } SetEnvironment("APS5_PIPELINE_JOURNAL_ENTRIES", "1024"); + SetEnvironment("APS5_PIPELINE_JOURNAL_COMPUTE_ENTRIES", ""); } void EvictionTests() { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PipelineStatisticsReport.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PipelineStatisticsReport.cpp new file mode 100644 index 0000000000..aca22b8262 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PipelineStatisticsReport.cpp @@ -0,0 +1,270 @@ +#include "VulkanTestDevice.hpp" +#include "../../tools/ComputePipelineFixture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using AgcDriver::Graphics::Require; + +class StderrCapture { +public: + StderrCapture() : path(std::filesystem::temp_directory_path() / ("aps5_pipeline_statistics_" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".txt")) { + std::fflush(stderr); +#ifdef _WIN32 + saved = _dup(_fileno(stderr)); + Require(saved >= 0, "cannot save stderr"); + Require(_wfreopen(path.wstring().c_str(), L"wb", stderr) != nullptr, "cannot redirect stderr"); +#else + saved = dup(fileno(stderr)); + Require(saved >= 0, "cannot save stderr"); + Require(std::freopen(path.c_str(), "wb", stderr) != nullptr, "cannot redirect stderr"); +#endif + } + + ~StderrCapture() { + restore(); + std::error_code error; + std::filesystem::remove(path, error); + } + + StderrCapture(const StderrCapture&) = delete; + StderrCapture& operator=(const StderrCapture&) = delete; + + std::string Finish() { + restore(); + std::ifstream file(path, std::ios::binary); + return {std::istreambuf_iterator(file), std::istreambuf_iterator()}; + } + +private: + void restore() { + if (saved < 0) return; + std::fflush(stderr); +#ifdef _WIN32 + _dup2(saved, _fileno(stderr)); + _close(saved); +#else + dup2(saved, fileno(stderr)); + close(saved); +#endif + saved = -1; + } + + std::filesystem::path path; + int saved = -1; +}; + +std::unique_ptr OpenDevice() { + try { + return OpenVulkanTestDevice(); + } catch (const std::exception& error) { + if (std::string_view(error.what()).find("APS5_PIPELINE_STATS requires") == std::string_view::npos) throw; + std::printf("skipped, the device reports no pipeline executable statistics: %s\n", error.what()); + return nullptr; + } +} + +struct StatisticsPipeline { + explicit StatisticsPipeline(const AgcDriver::Graphics::Context& context) : context(context) { + const auto spirv = AgcDriver::ComputePipelineFixture().spirv; + VkShaderModuleCreateInfo moduleInfo{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + moduleInfo.codeSize = spirv.size() * sizeof(std::uint32_t); + moduleInfo.pCode = spirv.data(); + Require(context.Function("vkCreateShaderModule")(context.device, &moduleInfo, nullptr, &module) == VK_SUCCESS, "vkCreateShaderModule"); + VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + Require(context.Function("vkCreatePipelineLayout")(context.device, &layoutInfo, nullptr, &layout) == VK_SUCCESS, "vkCreatePipelineLayout"); + VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineInfo.flags = VK_PIPELINE_CREATE_CAPTURE_STATISTICS_BIT_KHR; + pipelineInfo.stage = {VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + pipelineInfo.stage.module = module; + pipelineInfo.stage.pName = "main"; + pipelineInfo.layout = layout; + Require(context.Function("vkCreateComputePipelines")(context.device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &pipeline) == VK_SUCCESS, "vkCreateComputePipelines"); + } + + ~StatisticsPipeline() { + context.Function("vkDestroyPipeline")(context.device, pipeline, nullptr); + context.Function("vkDestroyPipelineLayout")(context.device, layout, nullptr); + context.Function("vkDestroyShaderModule")(context.device, module, nullptr); + } + + StatisticsPipeline(const StatisticsPipeline&) = delete; + StatisticsPipeline& operator=(const StatisticsPipeline&) = delete; + + AgcDriver::Graphics::Context context; + VkShaderModule module = VK_NULL_HANDLE; + VkPipelineLayout layout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; +}; + +std::vector ExecutableShape(const AgcDriver::Graphics::Context& context, VkPipeline pipeline) { + const auto getProperties = context.Function("vkGetPipelineExecutablePropertiesKHR"); + const auto getStatistics = context.Function("vkGetPipelineExecutableStatisticsKHR"); + VkPipelineInfoKHR pipelineInfo{VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR}; + pipelineInfo.pipeline = pipeline; + std::uint32_t executables = 0; + Require(getProperties(context.device, &pipelineInfo, &executables, nullptr) == VK_SUCCESS, "vkGetPipelineExecutablePropertiesKHR count"); + std::vector shape; + for (std::uint32_t index = 0; index < executables; ++index) { + VkPipelineExecutableInfoKHR executable{VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR}; + executable.pipeline = pipeline; + executable.executableIndex = index; + std::uint32_t statistics = 0; + Require(getStatistics(context.device, &executable, &statistics, nullptr) == VK_SUCCESS, "vkGetPipelineExecutableStatisticsKHR count"); + shape.push_back(statistics); + } + return shape; +} + +std::vector StatisticLines(const std::string& text) { + std::istringstream stream(text); + std::vector lines; + for (std::string line; std::getline(stream, line);) { + if (line.find("[pipeline-stats]") == std::string::npos) continue; + Require(line.starts_with("[pipeline-stats] executable=") && line.find("[pipeline-stats]", 1) == std::string::npos, "a [pipeline-stats] line was split by another report"); + lines.push_back(std::move(line)); + } + return lines; +} + +void CheckShape(const std::vector& report, const std::vector& shape) { + Require(!shape.empty(), "the pipeline reports no executables"); + std::size_t line = 0; + for (std::size_t executable = 0; executable < shape.size(); ++executable) { + const auto prefix = "[pipeline-stats] executable=" + std::to_string(executable) + " "; + Require(line < report.size() && report[line].starts_with(prefix + "stages=0x"), "an executable's header is missing from the reference report"); + ++line; + for (std::size_t statistic = 0; statistic < shape[executable]; ++statistic, ++line) { + Require(line < report.size() && report[line].starts_with(prefix) && !report[line].starts_with(prefix + "stages="), "an executable's statistics in the reference report differ from the queried ones"); + } + } + Require(line == report.size(), "the reference report has lines past its executables"); +} + +std::size_t CountReports(const std::vector& lines, const std::vector& reference) { + Require(!reference.empty(), "the reference report is empty"); + for (std::size_t line = 0; line < lines.size(); ++line) { + Require(lines[line] == reference[line % reference.size()], "a pipeline statistics report was interleaved with another or lost a line"); + } + Require(lines.size() % reference.size() == 0, "the last pipeline statistics report is incomplete"); + return lines.size() / reference.size(); +} + +template +bool Rejects(TCheck check) { + try { + check(); + } catch (const std::exception&) { + return true; + } + return false; +} + +void CheckParser() { + const std::vector reference = { + "[pipeline-stats] executable=0 stages=0x1 subgroup=32 name=VS", + "[pipeline-stats] executable=0 Register Count=24 (registers)", + "[pipeline-stats] executable=0 Binary Size=4096 (bytes)", + "[pipeline-stats] executable=0 Stack Size=0 (bytes)", + "[pipeline-stats] executable=1 stages=0x10 subgroup=32 name=FS", + "[pipeline-stats] executable=1 Output Colors=1 (colors)", + }; + std::string report; + for (const auto& line : reference) report += line + "\n"; + CheckShape(StatisticLines(report), {3, 1}); + Require(Rejects([&] { CheckShape(StatisticLines(report), {1, 3}); }), "the parser accepted a report against another executable shape"); + Require(CountReports(StatisticLines("[gpu] other output\n" + report + report + report), reference) == 3, "the parser did not count three intact two-executable reports"); + const auto split = report.substr(0, report.find(" (registers)")) + reference[0] + "\n (registers)\n" + report.substr(report.find(" (registers)") + 13); + Require(Rejects([&] { CountReports(StatisticLines(split + report), reference); }), "the parser accepted a split line"); + auto missing = reference; + missing.erase(missing.begin() + 2); + std::string lost; + for (const auto& line : missing) lost += line + "\n"; + Require(Rejects([&] { CountReports(StatisticLines(lost + report), reference); }), "the parser accepted a report that lost a line"); + std::string interleaved; + for (std::size_t line = 0; line < reference.size(); ++line) interleaved += reference[line] + "\n" + reference[line] + "\n"; + Require(Rejects([&] { CountReports(StatisticLines(interleaved), reference); }), "the parser accepted two interleaved reports"); + Require(Rejects([&] { CountReports(StatisticLines(report + reference[0] + "\n"), reference); }), "the parser accepted an incomplete last report"); +} + +} + +int main() { + try { + CheckParser(); + std::puts("pipeline statistics: the parser accepts intact two-executable reports and rejects split, lost, interleaved and incomplete ones"); +#ifdef _WIN32 + Require(_putenv_s("APS5_PIPELINE_STATS", "1") == 0, "cannot set APS5_PIPELINE_STATS"); +#else + Require(setenv("APS5_PIPELINE_STATS", "1", 1) == 0, "cannot set APS5_PIPELINE_STATS"); +#endif + const auto device = OpenDevice(); + if (!device) return VulkanTestSkipped; + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device); + Require(context.pipelineExecutableInfo, "APS5_PIPELINE_STATS=1 did not enable pipeline executable statistics"); + const StatisticsPipeline pipeline(context); + std::vector reference; + { + StderrCapture output; + AgcDriver::Graphics::LogPipelineStatistics_nid_no_patch(context, pipeline.pipeline); + reference = StatisticLines(output.Finish()); + } + const auto shape = ExecutableShape(context, pipeline.pipeline); + CheckShape(reference, shape); + constexpr unsigned threads = 8; + constexpr unsigned reportsPerThread = 64; + std::vector failures(threads); + StderrCapture output; + { + std::vector workers; + for (unsigned thread = 0; thread < threads; ++thread) { + workers.emplace_back([&, thread] { + try { + for (unsigned report = 0; report < reportsPerThread; ++report) AgcDriver::Graphics::LogPipelineStatistics_nid_no_patch(context, pipeline.pipeline); + } catch (...) { + failures[thread] = std::current_exception(); + } + }); + } + } + const auto text = output.Finish(); + for (const auto& failure : failures) { + if (failure) std::rethrow_exception(failure); + } + const auto reports = CountReports(StatisticLines(text), reference); + Require(reports == threads * reportsPerThread, "a pipeline statistics report is missing"); + std::printf("pipeline statistics: %zu reports of %zu executables and %zu lines each from %u threads, each in one piece\n", reports, shape.size(), reference.size(), threads); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "pipeline statistics report test: %s\n", error.what()); + return 1; + } +} diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp index 74adeac4c0..54050c3d7b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp @@ -219,6 +219,14 @@ int main() { std::puts("skipped, the device lacks fragmentShaderPixelInterlock"); return VulkanTestSkipped; } + if (!device->RasterOrderGroupUsable()) { + std::puts("skipped, the device does not support the raster order group the pixel interlock needs"); + return VulkanTestSkipped; + } + if (!device->BdaAtomicsUsable()) { + std::puts("skipped, the device does not support atomics on buffer device addresses"); + return VulkanTestSkipped; + } GuestCounters guest; for (const auto waveSize : {64u, 32u}) { for (std::uint32_t pass = 0; pass < 3; ++pass) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp index 56ea345d1f..199febbf1b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedGraphics.cpp @@ -84,7 +84,8 @@ void Check(AgcDriver::VulkanDevice& device, AgcDriver::Graphics::ShaderPath path fragment.Initialize(1u); const std::array pixelCode{0xc8020002u, 0xc8060102u, 0xc80a0202u, 0xc80e0302u, 0xf800180fu, 0x03020100u, 0xbf810000u}; fragment.snapshot->code.resize(64); - fragment.snapshot->code.insert(fragment.snapshot->code.end(), pixelCode.begin(), pixelCode.end()); + auto& fragmentCode = fragment.snapshot->code.Mutable(); + fragmentCode.insert(fragmentCode.end(), pixelCode.begin(), pixelCode.end()); ShaderRegistry registry; for (const auto* fixture : {&front, &back, &domain, &fragment}) registry.emplace(fixture->snapshot->codeAddress, std::vector>{fixture->snapshot}); AgcDriver::QueueState queue{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp index 1273ff05c7..bbcd01b7e0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedPublication.cpp @@ -338,7 +338,7 @@ void Registration(AgcDriver::VulkanDevice& device) { Require(invocation.Materialize(*capture)->spirv.data() == result->spirv.data(), "prepared handle did not retain its artifact"); auto changed = makeSnapshot(); changed->headerAddress = replacement->headerAddress; - changed->code[0] ^= 1; + changed->code.Mutable()[0] ^= 1; PublishRegisteredShader(registry, changed); Require(registry->at(0x10000).back() == changed && registry->at(0x10000).size() == 1 && old.expired(), "changed shader code retained obsolete headers"); auto metadata = makeSnapshot(); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp index 9c06e24148..f02dadb119 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp @@ -300,10 +300,10 @@ void FailureCapture(AgcDriver::VulkanDevice& device) { const std::filesystem::path retryDirectory = "shader_12345cafe_retry"; std::filesystem::remove_all(retryDirectory); const auto failedPath = AgcDriver::DriverDetail::Driver::dumpRequest(request.shader.codeAddress, request, "_retry/test"); - Require(!std::ifstream(failedPath, std::ios::binary).is_open(), "uncreatable dump path was unexpectedly created"); + Require(failedPath.empty(), "uncreatable dump path reported a saved file"); std::filesystem::create_directories(retryDirectory); const auto retriedPath = AgcDriver::DriverDetail::Driver::dumpRequest(request.shader.codeAddress, request, "_retry/test"); - Require(retriedPath == failedPath, "retried dump returned a different path"); + Require(retriedPath == (retryDirectory / "test.req").generic_string(), "retried dump returned a different path"); std::ifstream retriedFile(retriedPath, std::ios::binary); Require(retriedFile.is_open(), "retried dump file was not created"); std::ostringstream retriedText; @@ -596,6 +596,7 @@ void SharedCodeHeaders() { }; const auto [vertexSnapshot, meshSnapshot] = snapshots(); Require(vertexSnapshot->registeredState != meshSnapshot->registeredState, "a header with other registers shared another header's registered state"); + Require(vertexSnapshot->code.SharesWith(meshSnapshot->code), "two headers of one program keep separate copies of its code"); Shader copy = vertex.shader; copy.user_data = nullptr; AgcDriverResolveShaderAbi_nid_postfix(©, {}, primitive); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp index df68d87509..f8c2e94811 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PreparedUnregisteredGraphics.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace { @@ -28,7 +29,42 @@ void Bind(AgcDriver::QueueState& queue, const void* code, std::uint32_t program, queue.shader[resources] = 16u << 1u; } +void CheckRetainedDecode() { + using namespace AgcDriver::DriverDetail; + alignas(256) static std::array code{0xbf810000u}; + alignas(256) static std::array tail{0xbf810000u}; + alignas(256) static std::array, 4097> others{}; + const auto address = reinterpret_cast(code.data()); + DrawProgram program{}; + program.snapshot = ReadRawShader(address); + program.tailContinuation = ReadRawShader(reinterpret_cast(tail.data())); + DrawDecode decode{}; + decode.programs.push_back(std::move(program)); + const ShaderRegistry registry; + Require(DecodeProgramsCurrent(decode, registry), "a newly captured raw draw decode is not current"); + for (auto& other : others) { + other.fill(0xbf810000u); + static_cast(ReadRawShader(reinterpret_cast(other.data()))); + } + const auto reread = ReadRawShader(address); + Require(reread != decode.programs.front().snapshot, "raw snapshot churn did not evict the retained snapshot"); + Require(reread->code == decode.programs.front().snapshot->code, "raw snapshot churn changed the shader contents"); + std::cout << "retained raw snapshot evicted=" << (reread != decode.programs.front().snapshot) << '\n'; + Require(DecodeProgramsCurrent(decode, registry), "an unchanged retained raw draw decode was invalidated by snapshot cache eviction"); + code[0] = 0xbf800000u; + code[1] = 0xbf810000u; + Require(!DecodeProgramsCurrent(decode, registry), "a retained raw draw decode ignored changed main code"); + code[0] = 0xbf810000u; + Require(DecodeProgramsCurrent(decode, registry), "restoring raw main code did not restore its retained decode"); + tail[0] = 0xbf800000u; + tail[1] = 0xbf810000u; + Require(!DecodeProgramsCurrent(decode, registry), "a retained raw draw decode ignored changed tail code"); + tail[0] = 0xbf810000u; + Require(DecodeProgramsCurrent(decode, registry), "restoring raw tail code did not restore its retained decode"); +} + void Check(AgcDriver::VulkanDevice& device) { + CheckRetainedDecode(); using namespace AgcDriver::DriverDetail; using namespace ShaderRecompiler; AgcDriver::QueueState queue{}; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/QueuedIndexReadOrder.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/QueuedIndexReadOrder.cpp index 24d913ecde..6e7f1b0f51 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/QueuedIndexReadOrder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/QueuedIndexReadOrder.cpp @@ -343,8 +343,10 @@ void CopyPendingImage(std::size_t bytes, const char* phase) { int main() { #ifdef _WIN32 _putenv_s("APS5_PIPELINED_DRAWS", "1"); + _putenv_s("APS5_NO_IDLE_STORE", "1"); #else setenv("APS5_PIPELINED_DRAWS", "1", 1); + setenv("APS5_NO_IDLE_STORE", "1", 1); #endif try { { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/QueuedRawShader.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/QueuedRawShader.cpp index 703f7523d7..a6eae35bcf 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/QueuedRawShader.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/QueuedRawShader.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawCache.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Draw/DrawPipeline.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -11,6 +12,7 @@ #include #include #include +#include namespace { @@ -22,6 +24,12 @@ void QueuedCode(bool cached) { code[0] = 0x7e000200u; const auto address = reinterpret_cast(code.data()); const auto before = cached ? ReadRawShader(address) : nullptr; + DrawDecode decode{}; + if (before != nullptr) { + DrawProgram program{}; + program.snapshot = before; + decode.programs.push_back(std::move(program)); + } std::atomic finished{false}; std::atomic started{false}; struct Restore { @@ -42,10 +50,13 @@ void QueuedCode(bool cached) { }, {{address, address + sizeof(std::uint32_t)}}); while (!started.load(std::memory_order_acquire)) std::this_thread::yield(); DrawPipeline::Active() = true; + if (before != nullptr && DecodeProgramsCurrent(decode, ShaderRegistry{})) { + throw std::runtime_error("a retained raw draw decode ignored a queued write to its code"); + } const auto after = ReadRawShader(address); finished.store(true, std::memory_order_release); DrawPipeline::Queue0().Drain(DrawPipeline::DrainReason::Submission); - if (after->code.front() != 0x7e0002f2u || (before != nullptr && (before->code.front() != 0x7e000200u || before == after))) { + if (after->code[0] != 0x7e0002f2u || (before != nullptr && (before->code[0] != 0x7e000200u || before == after))) { throw std::runtime_error(std::string(cached ? "cached" : "fresh") + " raw shader read ignored a queued write to its code"); } } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ReadOnlyLeaseWriter.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ReadOnlyLeaseWriter.cpp new file mode 100644 index 0000000000..e9957c8c55 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ReadOnlyLeaseWriter.cpp @@ -0,0 +1,158 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "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::size_t BlockBytes = 1u << 20u; +constexpr std::uint32_t Width = 256; +constexpr std::uint32_t Height = 128; +constexpr std::size_t ImageBytes = Width * Height * 4u; +constexpr std::uint32_t Format32UInt = 20; +constexpr std::uint32_t Type2D = 9; +constexpr std::uint32_t Initial = 0x3f800000u; +constexpr std::uint32_t Loaded = 0x5a5a1234u; +constexpr std::uint32_t Stored = 0x22222222u; +constexpr std::size_t SourceOffset = BlockBytes / 2u; +constexpr std::size_t LoadedOffset = SourceOffset + 0x40u; +constexpr std::size_t QueryOffset = SourceOffset + 0x10000u; + +alignas(256) constexpr std::array LoadToImageCode{0x7e000200u, 0x7e020201u, 0xdc308040u, 0x027d0000u, 0xbf8c3f70u, 0x7e000280u, 0x7e020280u, 0xf0200108u, 0x00010200u, 0xbf810000u}; +alignas(256) constexpr std::array StoreGlobalCode{0x7e000208u, 0x7e020209u, 0x7e0402ffu, Stored, 0xdc708100u, 0x007d0200u, 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(65536, BlockBytes)); +#endif + Require(block != nullptr, "read-only lease writer: cannot allocate the guest block"); + std::fill(Words(), Words() + BlockBytes / 4u, Initial); + Words()[LoadedOffset / 4u] = Loaded; + 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::uint32_t* Words() const { return reinterpret_cast(block); } + std::uint64_t Address() const { return static_cast(reinterpret_cast(block)); } + +private: + std::uint8_t* block = nullptr; +}; + +ShaderRecompiler::RecompileResult Compile(std::span code, std::span userData, const ShaderRecompiler::SpirvTarget& target) { + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {64u, 0, userData, compute, std::nullopt, std::nullopt, memory}, + target, + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +std::vector LoadUserData(std::uint64_t image, std::uint64_t source) { + std::vector userData(12, 0u); + userData[0] = static_cast(source); + userData[1] = static_cast(source >> 32u); + userData[4] = static_cast(image >> 8u); + userData[5] = static_cast((image >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Width - 1u) & 3u) << 30u); + userData[6] = ((Width - 1u) >> 2u) | ((Height - 1u) << 14u); + userData[7] = 0xfacu | (Type2D << 28u); + return userData; +} + +std::vector StoreUserData(std::uint64_t base) { + std::vector userData(10, 0u); + userData[8] = static_cast(base); + userData[9] = static_cast(base >> 32u); + return userData; +} + +struct Pending { + bool read; + std::uint64_t writer; + std::uint64_t open; +}; + +Pending PendingAt(std::uint64_t address) { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto* recorder = AgcDriver::Graphics::Recorder::Active(); + Require(recorder != nullptr, "read-only lease writer: no active recorder"); + return {recorder->DescribePendingRead(address, 64u).has_value(), recorder->PendingWriterSerial(address, 64u), recorder->OpenSerial()}; +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool counted = std::getenv("APS5_NO_READ_ONLY_LEASE_SKIP") != nullptr; + GuestBlock guest; + const auto target = device->Target(); + const auto query = guest.Address() + QueryOffset; + device->WaitIdle(); + + const auto reader = Compile(LoadToImageCode, LoadUserData(guest.Address(), guest.Address() + SourceOffset), target); + device->Dispatch(reader, 1u, 1u, 1u, {}, reinterpret_cast(LoadToImageCode.data())); + const auto afterRead = PendingAt(query); + Require(afterRead.read, "read-only lease writer: the build that only loads through BDA noted no lease over the guest block"); + if (counted) Require(afterRead.writer == afterRead.open, "read-only lease writer: APS5_NO_READ_ONLY_LEASE_SKIP did not count the read-only lease as a writer"); + else Require(afterRead.writer == 0, "read-only lease writer: the lease of a build that never stores through BDA counts as a writer (serial " + std::to_string(afterRead.writer) + ")"); + Require(AgcDriver::Graphics::StorageTexture::FlushPending(guest.Address(), ImageBytes, nullptr, "test"), "read-only lease writer: the read-only build's image store left no results pending"); + device->WaitIdle(); + Require(guest.Words()[0] == Loaded, "read-only lease writer: the read-only build did not store the loaded word into its image"); + + const auto storer = Compile(StoreGlobalCode, StoreUserData(guest.Address() + SourceOffset), target); + device->Dispatch(storer, 1u, 1u, 1u, {}, reinterpret_cast(StoreGlobalCode.data())); + const auto afterStore = PendingAt(query); + Require(afterStore.writer == afterStore.open, "read-only lease writer: the lease of a build that stores through BDA does not count as a writer"); + device->WaitIdle(); + Require(guest.Words()[(SourceOffset + 0x100u) / 4u] == Stored, "read-only lease writer: the storing build's word did not reach guest memory"); + + std::puts("read-only lease writer 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/ResidentHeapMirror.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ResidentHeapMirror.cpp index e708bf7302..8a71eab5e2 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/ResidentHeapMirror.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ResidentHeapMirror.cpp @@ -287,6 +287,25 @@ void ResidentTests(AgcDriver::VulkanDevice& device) { CheckStores(written, "unchanged dispatch"); } +void StagingChunkTests(AgcDriver::VulkanDevice& device) { + constexpr std::uint64_t LargeBytes = 48u << 20u; + constexpr std::uint64_t ChunkBoundary = 32u << 20u; + constexpr std::uint64_t First = ChunkBoundary - Threads / 2 * ThreadStride; + GuestHeap read(false, 1, LargeBytes); + GuestHeap written(true); + const OutputBlock output; + for (std::uint64_t block = 0; block < LargeBytes; block += Block) read.Store(block + 8, 0x11u); + for (std::uint32_t thread = 0; thread < Threads; ++thread) read.Store(First + thread * ThreadStride, Pattern(thread)); + const ImportBudget budget(AgcDriver::Graphics::HostImportLiveBytes() + Block); + const auto before = AgcDriver::Graphics::MirrorCounters(); + Dispatch(device, read.Address() + First, written.Address(), output); + const auto made = AgcDriver::Graphics::MirrorCounters(); + Require(made.heapBytes - before.heapBytes > First + Threads * ThreadStride, "resident heap mirror: a " + std::to_string(LargeBytes >> 20u) + " MiB heap mirror does not reach past the read range (" + std::to_string((made.heapBytes - before.heapBytes) >> 10u) + " KiB held)"); + for (std::uint32_t thread = 0; thread < Threads; ++thread) { + Require(output.Data()[thread] == Pattern(thread), "resident heap mirror: an upload split into staging chunks: thread " + std::to_string(thread) + " read " + Hex(output.Data()[thread]) + " at heap offset " + Hex(First + thread * ThreadStride) + ", expected " + Hex(Pattern(thread))); + } +} + void StorageImageTests(AgcDriver::VulkanDevice& device) { GuestHeap heap(true); GuestHeap written(true); @@ -555,6 +574,7 @@ int main() { const bool sparse = context.sparseBinding && context.sparseResidencyBuffer && context.sparseResidencyAliased; if (sparse) { ResidentTests(*device); + StagingChunkTests(*device); ReusedAddressTests(*device); JoinedImportTests(*device); } else { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp index f4218f55cf..4d62f946b5 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SampleMultisampleTarget.cpp @@ -7,6 +7,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp" +#include "prx/libSceAgcDriver/Graphics/include/MultisampleTextureLayout.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include "prx/libSceAgcDriver/Graphics/include/State.hpp" #include "prx/libc/include/GuestAllocations.hpp" @@ -518,16 +519,6 @@ void CheckWritten(const std::vector>& before, const std::ve Require(written != 0 && written != after.size(), "the stores covered every sample or none " + what); } -void RequireStoreRefused(AgcDriver::VulkanDevice& device, const std::array& texture, const std::string& expected, const std::string& what) { - std::string refusal; - try { - StoreSamples(device, texture, {{0u, 0u, 0u}}); - } catch (const std::exception& error) { - refusal = error.what(); - } - Require(refusal.find(expected) != std::string::npos, what + " was not refused: " + refusal); -} - bool DeclaresCapability(std::span words, spv::Capability capability) { for (std::size_t cursor = 5; cursor < words.size();) { const auto count = words[cursor] >> 16u; @@ -872,6 +863,33 @@ void StoreWords(const Block& block, std::uint64_t bytes, std::uint32_t word) { for (std::uint64_t offset = 0; offset < bytes; offset += sizeof(word)) std::memcpy(block.data + offset, &word, sizeof(word)); } +std::vector> StoreVaryingUnorm(const Block& block, AgcDriver::Graphics::TextureTileMode tileMode) { + using namespace AgcDriver::Graphics; + const MultisampleTextureLayout layout(tileMode, 4u, Samples, Side, Side, 1u); + Require(layout.Bytes() <= block.bytes, "the varying fixture does not fit its color block"); + StoreWords(block, block.bytes, 0u); + std::vector linear(static_cast(layout.LinearBytes())); + std::vector> expected(Side * Side * Samples); + for (std::uint32_t index = 0; index < expected.size(); ++index) { + const auto pixel = index / Samples; + const bool odd = ((pixel % Side) * 3u + (pixel / Side) * 5u + (index % Samples) * 7u) % 2u != 0u; + const std::uint32_t word = odd ? 0x00ff00ffu : 0xff00ff00u; + std::memcpy(linear.data() + index * sizeof(word), &word, sizeof(word)); + expected[index] = odd ? std::array{1.0f, 0.0f, 1.0f, 0.0f} : std::array{0.0f, 1.0f, 0.0f, 1.0f}; + } + layout.Tile(linear, std::as_writable_bytes(std::span(block.data, block.bytes))); + return expected; +} + +void CheckLoaded(const std::vector>& loaded, const std::vector>& expected, const std::string& what) { + Require(loaded.size() == expected.size(), "the loaded sample count differs " + what); + for (std::size_t index = 0; index < loaded.size(); ++index) { + char where[96]; + std::snprintf(where, sizeof(where), "sample %zu of pixel (%zu, %zu)", index % Samples, index / Samples % Side, index / Samples / Side); + Require(loaded[index] == expected[index], std::string(where) + " does not hold its guest color " + what); + } +} + ShaderRecompiler::RecompileResult CompileBdaSpan(AgcDriver::VulkanDevice& device, std::uint64_t address) { const std::span code(BdaSpanCode); const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; @@ -1437,7 +1455,17 @@ int main(int argc, char** argv) { StoreWords(mixed, colorBytes, FillOnes); const std::uint32_t odd = 0x3c000000u; std::memcpy(mixed.data + colorBytes - sizeof(odd), &odd, sizeof(odd)); - RequireRefused(*device, TargetDescriptor(mixed, SampleShift), "does not hold one repeated 32-bit value", "loading a multisampled texture no draw rendered to whose color memory is not uniform"); + CheckUniform(LoadSamples(*device, mixed), Ones, "from color memory no draw rendered to whose padding alone differs"); + for (const auto& [mode, tileMode] : {std::pair{TileRenderTarget, AgcDriver::Graphics::TextureTileMode::kR64KBX}, std::pair{TileZ64KBX, AgcDriver::Graphics::TextureTileMode::kZ64KBX}}) { + Block varying(BlockBytes); + const auto expected = StoreVaryingUnorm(varying, tileMode); + CheckLoaded(LoadSamples(*device, varying, Format8888Unorm, mode), expected, "from varying color memory of a " + std::to_string(mode) + " swizzle surface no draw rendered to"); + } + Block varyingD(BlockBytes); + StoreWords(varyingD, colorBytes, FillOnes); + std::memcpy(varyingD.data, &odd, sizeof(odd)); + RequireRefused(*device, TargetDescriptor(varyingD, SampleShift, Format16161616Float, TileD64KBX), "non-uniform multisampled guest color tiling is not implemented", "loading varying D_X color memory no draw rendered to"); + std::puts("loads of R_X and Z_X color memory no draw rendered to return each sample's guest color, and varying D_X color memory is refused"); const auto oneSample = [](std::uint32_t, std::uint32_t, std::uint32_t sample) { return sample == StoredSample; }; const auto drawnBefore = LoadSamples(*device, block); StoreSamples(*device, TargetDescriptor(block, SampleShift), SampleWrites(oneSample)); @@ -1466,8 +1494,9 @@ int main(int argc, char** argv) { Block mixedStore(BlockBytes); StoreWords(mixedStore, colorBytes, FillOnes); std::memcpy(mixedStore.data + colorBytes - sizeof(odd), &odd, sizeof(odd)); - RequireStoreRefused(*device, TargetDescriptor(mixedStore, SampleShift), "writing the 16x16 multisampled texture", "storing to a multisampled texture no draw rendered to whose color memory is not uniform"); - std::puts("stores to one sample of each pixel of a drawn 4x target and to samples of zeroed color memory no draw rendered to reach exactly those samples, and a store to non-uniform color memory no draw rendered to is refused"); + StoreSamples(*device, TargetDescriptor(mixedStore, SampleShift), SampleWrites(oneSample)); + CheckWritten(std::vector>(Side * Side * Samples, Ones), LoadSamples(*device, mixedStore), oneSample, "into one sample of each pixel of color memory no draw rendered to whose padding alone differs"); + std::puts("stores to one sample of each pixel of a drawn 4x target and to samples of zeroed or non-uniform color memory no draw rendered to reach exactly those samples"); for (const auto mode : {TileZ64KBX, TileD64KBX}) { Block swizzled(BlockBytes); StoreWords(swizzled, colorBytes, FillOnes); diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp index 276bb59320..5f462aeb66 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp @@ -1050,6 +1050,42 @@ void VKAPI_PTR ReportBudget(VkPhysicalDevice, VkPhysicalDeviceMemoryProperties2* } } +struct BudgetReportAgeProbe { + std::uint64_t budget = 8ull << 30u; + unsigned queries = 0; +} budgetReportAge; + +void VKAPI_PTR ReportBudgetAge(VkPhysicalDevice, VkPhysicalDeviceMemoryProperties2* properties) { + ++budgetReportAge.queries; + auto* reported = static_cast(properties->pNext); + for (std::uint32_t heap = 0; heap < VK_MAX_MEMORY_HEAPS; ++heap) { + reported->heapBudget[heap] = budgetReportAge.budget; + reported->heapUsage[heap] = 0; + } +} + +void budgetReportAgeTests(Context context) { + constexpr std::uint64_t GiB = 1ull << 30u; + const auto heap = LargestDeviceLocalHeap(context.memory); + Require(heap.has_value(), "the budget report age fixture needs a device-local heap"); + context.memory.memoryHeaps[*heap].size = std::max(context.memory.memoryHeaps[*heap].size, 16 * GiB); + context.memoryProperties2 = ReportBudgetAge; + Require(StorageTextureCacheBudget(context) == 4 * GiB, "the initial storage limit is not 4 GiB"); + const auto queries = budgetReportAge.queries; + std::this_thread::sleep_for(std::chrono::milliseconds(25)); + budgetReportAge.budget = 2 * GiB; + Require(SampledTextureCacheBudget(context) == 2 * GiB, "sampled did not use the reduced heap budget"); + Require(budgetReportAge.queries == queries + 1, "reporting the storage limit issued an extra memory budget query"); + Require(StorageTextureCacheBudget(context) == 4 * GiB, "reporting refreshed the still-active cached storage limit"); + VkPhysicalDeviceMemoryBudgetPropertiesEXT reported{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_BUDGET_PROPERTIES_EXT}; + reported.heapBudget[*heap] = budgetReportAge.budget; + Require(StorageTextureBudget(context.memory, &reported, 0, 0) == GiB, "the fresh storage calculation does not differ from the active cached limit"); + std::this_thread::sleep_for(std::chrono::milliseconds(1100)); + Require(StorageTextureCacheBudget(context) == GiB, "storage did not refresh after its own cache interval"); + Require(budgetReportAge.queries == queries + 2, "storage did not issue exactly one query when its cache interval expired"); + std::puts("sampled budget report age tests passed"); +} + 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}; @@ -1319,10 +1355,10 @@ int main(int argc, char** argv) { 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", recover ? "16" : "1"); + if (!memory && mode != "budget-report-age") _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", recover ? "16" : "1", 1); + if (!memory && mode != "budget-report-age") setenv("APS5_TEXTURE_CACHE_MIB", recover ? "16" : "1", 1); setenv("APS5_TEMPLATE_AUDIT_MS", "250", 0); #endif std::unique_ptr device; @@ -1339,6 +1375,16 @@ int main(int argc, char** argv) { return 0; } auto context = device->GetContext(); + if (mode == "budget-report-age") { + try { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + budgetReportAgeTests(context); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "FAIL: %s\n", error.what()); + return 1; + } + } if (disableStorageImageMultisample) { PlaceholderImages placeholders(context); for (std::uint32_t heap = ShaderRecompiler::RuntimeAbi::FirstStorageImageBinding; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScatteredRuns.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScatteredRuns.cpp new file mode 100644 index 0000000000..297f2e7a10 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScatteredRuns.cpp @@ -0,0 +1,221 @@ +// Execution test: a scattered TWO-RUN guest range whose total is larger than udmabuf's +// size_limit_mb (64 MiB by default). Without the run-list sparse import the range goes to one +// UDMABUF_CREATE_LIST, which the kernel refuses with EINVAL, and falls back to copies; with it, +// each run is bound from the shared 64 MiB chunk imports and the range imports. +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "VulkanTestDevice.hpp" +#include "../AllocationFailure.hpp" +#ifdef __linux__ +#include "prx/libc/include/GuestArena.hpp" +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +int main() { + std::printf("skipped, udmabuf imports are Linux only\n"); + return 77; +} +#else +namespace AgcDriver { + +struct VulkanDeviceTestAccess { + static Graphics::Context ContextOf(const VulkanDevice& device) { return device.graphicsContext(); } +}; + +} + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::uint64_t ChunkBytes = std::uint64_t{64} << 20u; + +std::uint8_t* mapping = nullptr; +std::uint64_t mappingBytes = 0; +int backing = -1; +int lastBackingFile = -1; +bool failAfterRunsResolved = false; + +// Guest segment 0 -> file [0, ChunkBytes); guest segment 1 -> file [2*ChunkBytes, 3*ChunkBytes). +// Non-contiguous file offsets, so this is a genuine scattered run list, not one long range. +bool ResolveRuns(std::uintptr_t address, std::size_t bytes, int* file, GuestArena::SharedBackingRun* runs, std::size_t capacity, std::size_t* count) { + const auto begin = reinterpret_cast(mapping); + if (address < begin || address + bytes > begin + mappingBytes) return false; + const std::size_t offset = address - begin; + std::size_t done = 0; + std::size_t used = 0; + while (done < bytes) { + const std::size_t guestPos = offset + done; + const std::size_t segment = guestPos / ChunkBytes; + const std::size_t within = guestPos % ChunkBytes; + const std::size_t take = std::min(bytes - done, ChunkBytes - within); + if (used >= capacity) return false; + runs[used++] = {static_cast(segment) * 2 * ChunkBytes + within, take}; + done += take; + } + *file = dup(backing); + if (*file < 0) return false; + *count = used; + lastBackingFile = *file; + if (failAfterRunsResolved) { + failAfterRunsResolved = false; + AgcDriver::Tests::SetAllocationFailureCountdown(0); + } + return true; +} + +std::uint64_t UdmabufLimitBytes() { + std::ifstream limit("/sys/module/udmabuf/parameters/size_limit_mb"); + std::uint64_t megabytes = 64; + limit >> megabytes; + return megabytes << 20u; +} + +bool Imported(VkDevice device, std::uint64_t address, std::size_t bytes) { + AgcDriver::Graphics::Context probe{}; + probe.device = device; + probe.hostImportAlignment = 1; + return AgcDriver::Graphics::HostImportCovers(probe, address, bytes); +} + +bool DmaBufImport(const AgcDriver::VulkanDevice& device, std::uint64_t address, std::size_t bytes) { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + const auto* import = AgcDriver::Graphics::HostImportFor(AgcDriver::VulkanDeviceTestAccess::ContextOf(device), address, bytes); + return import != nullptr && import->dmaBuf; +} + +} + +int main() { + try { + const int udmabuf = open("/dev/udmabuf", O_RDWR | O_CLOEXEC); + if (udmabuf < 0) { + std::printf("skipped, host imports are unavailable: /dev/udmabuf is not accessible\n"); + return 77; + } + close(udmabuf); + const auto limit = UdmabufLimitBytes(); + auto device = OpenVulkanTestDevice(); + if (!device) return 77; + + // A device probe, so an unsupported device reads as a skip rather than a failure. + constexpr std::size_t ProbeBytes = 65536; + auto* probe = static_cast(std::aligned_alloc(ProbeBytes, ProbeBytes)); + Require(probe != nullptr, "scattered import: cannot allocate the probe"); + GuestAllocations::Mutation().Add(probe, ProbeBytes, true, true, true); + bool adopted = false; + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + const std::array pattern{1u, 2u, 3u, 4u}; + adopted = device->FillBuffer(reinterpret_cast(probe), ProbeBytes, pattern); + device->WaitIdle(); + } + if (!adopted) { + std::printf("skipped, the device does not import guest memory\n"); + return 77; + } + + // Two 64 MiB segments = 128 MiB total, above a default 64 MiB udmabuf limit. + Require(2 * ChunkBytes > limit, "scattered import: run this where the udmabuf limit is below two chunks"); + mappingBytes = 2 * ChunkBytes; + const std::uint64_t fileBytes = 3 * ChunkBytes; + backing = memfd_create("anyps5-scattered-runs", MFD_CLOEXEC | MFD_ALLOW_SEALING); + Require(backing >= 0 && ftruncate(backing, static_cast(fileBytes)) == 0, "scattered import: cannot make the backing file"); + Require(fcntl(backing, F_ADD_SEALS, F_SEAL_SHRINK) == 0, "scattered import: cannot seal the backing file"); + void* reservation = mmap(nullptr, mappingBytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(reservation != MAP_FAILED, "scattered import: cannot reserve the guest range"); + for (std::uint64_t segment = 0; segment < 2; ++segment) { + void* piece = mmap(static_cast(reservation) + segment * ChunkBytes, ChunkBytes, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, backing, + static_cast(segment * 2 * ChunkBytes)); + Require(piece != MAP_FAILED, "scattered import: cannot map a segment"); + } + mapping = static_cast(reservation); + std::memset(mapping, 0, static_cast(mappingBytes)); + GuestArena::GuestArenaSetSharedBackingRuns_nid_postfix(ResolveRuns); + + GuestAllocations::Mutation().Add(mapping, static_cast(mappingBytes), true, true, true); + GuestAllocations::Mapped ranges; + for (const auto& range : GuestAllocations::GuestAllocationsAcquire_nid_postfix()) { + if (range->address == reinterpret_cast(mapping)) ranges.push_back(range); + } + Require(ranges.size() == 1, "scattered import: the allocation is not registered"); + + const auto address = reinterpret_cast(mapping); + bool injected = false; + failAfterRunsResolved = true; + try { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + AgcDriver::Graphics::HostImportFor(AgcDriver::VulkanDeviceTestAccess::ContextOf(*device), address, static_cast(mappingBytes)); + } catch (const std::bad_alloc&) { + injected = true; + } + AgcDriver::Tests::SetAllocationFailureCountdown(-1); + failAfterRunsResolved = false; + if (injected) { + Require(lastBackingFile >= 0 && fcntl(lastBackingFile, F_GETFD) == -1 && errno == EBADF, + "scattered import: pre-callback allocation failure retained the backing descriptor"); + std::printf("scattered import: allocation failure closed the backing descriptor\n"); + } + bool imported = false; + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + imported = device->ImportGuestMemory(ranges, GuestAllocations::GuestAllocationsGeneration_nid_postfix(), false); + device->WaitIdle(); + } + Require(imported, "scattered import: ImportGuestMemory refused the range"); + Require(Imported(device->Device(), address, static_cast(mappingBytes)), + "a range of two scattered runs larger than udmabuf's size limit was not imported"); + // A device that imports a MAP_SHARED mapping through a host pointer (i915) never builds the run + // list this case drives, so, as in the sparse dma-buf case, that reads as a skip. + if (!DmaBufImport(*device, address, static_cast(mappingBytes))) { + std::printf("skipped, the device imports shared memory through host pointers, so no run list was bound\n"); + return 77; + } + + // Write through the GPU into the second run; it must land in the second file region. + constexpr std::uint32_t Pattern = 0x5CA77E12u; + { + std::lock_guard lock(AgcDriver::GuestMemory::GpuMutex()); + Require(device->FillBuffer(address + ChunkBytes, 65536, std::array{Pattern, Pattern, Pattern, Pattern}), + "scattered import: the fill in the second run was not served by the import"); + device->WaitIdle(); + } + std::uint32_t second = 0; + Require(pread(backing, &second, sizeof(second), static_cast(2 * ChunkBytes)) == static_cast(sizeof(second)), + "scattered import: cannot read the second run's file region"); + Require(second == Pattern, "scattered import: the second run's GPU write did not land at its scattered file offset"); + std::uint32_t first = 0; + Require(pread(backing, &first, sizeof(first), 0) == static_cast(sizeof(first)), + "scattered import: cannot read the first run's file region"); + Require(first != Pattern, "scattered import: the second run's write landed in the first run's file region"); + + std::printf("limit %llu MiB, imported %llu MiB as two scattered runs\n", + static_cast(limit >> 20u), static_cast(mappingBytes >> 20u)); + std::cout << "AGC scattered run-list import test passed\n"; + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} +#endif diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/ScratchPool.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/ScratchPool.cpp new file mode 100644 index 0000000000..0359abebcc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/ScratchPool.cpp @@ -0,0 +1,499 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#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(); } + static std::pair HeaderOf(const VulkanDevice& device, const ShaderRecompiler::RecompileResult& shader) { return device.scratchPoolHeader(shader); } +}; + +} + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; +using Words = std::vector; + +constexpr std::uint32_t PrivateHigh = 0x70000000u; +constexpr std::uint32_t WaitCnt0 = 0xbf8c0070u; +constexpr std::uint32_t EndProgram = 0xbf810000u; +constexpr std::uint32_t ShiftLaneBy5 = 0x34020085u; +constexpr std::uint32_t Barrier = 0xbf8a0000u; +constexpr std::uint32_t StoreDwordOp = 0x1cu; +constexpr std::uint32_t LoadDwordOp = 0x0cu; +constexpr std::uint32_t AtomicAddOp = 0x32u; +constexpr std::uint32_t ScratchSegment = 1u << 14u; +constexpr std::uint32_t AliasScratchDwords = 4u; +constexpr std::uint32_t ContentionScratchDwords = 1900u; +constexpr std::uint32_t ContentionGroups = 64u; +constexpr std::uint32_t DelayIterations = 100000u; + +enum class Form : std::uint8_t { Table, Literal, Computed }; +constexpr std::array FormNames{"table load", "literal", "computed"}; + +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 FlatWord0(std::uint32_t op, bool glc, std::uint32_t offset = 0u) { + return 0xdc000000u | (op << 18u) | (glc ? 0x10000u : 0u) | offset; +} + +std::uint32_t FlatWord1(std::uint32_t vdst, std::uint32_t data, std::uint32_t address) { + return (vdst << 24u) | (0x7du << 16u) | (data << 8u) | address; +} + +void EmitFlat(Words& code, std::uint32_t op, bool glc, std::uint32_t vdst, std::uint32_t data, std::uint32_t address, std::uint32_t offset = 0u) { + code.push_back(FlatWord0(op, glc, offset)); + code.push_back(FlatWord1(vdst, data, address)); +} + +void EmitScratch(Words& code, std::uint32_t op, std::uint32_t vdst, std::uint32_t data, std::uint32_t address, std::uint32_t offset = 0u) { + code.push_back(FlatWord0(op, false, offset) | ScratchSegment); + code.push_back(FlatWord1(vdst, data, address)); +} + +void EmitTableLoad(Words& code, std::uint32_t vdst, std::uint32_t offset) { + code.push_back(0xe0381000u | offset); + code.push_back(0x80010001u | (vdst << 8u)); +} + +void EmitOutputStore(Words& code, std::uint32_t vdata, std::uint32_t offset) { + code.push_back(0xe0701000u | offset); + code.push_back(0x80000001u | (vdata << 8u)); +} + +std::uint32_t VMov(std::uint32_t vdst, std::uint32_t source) { + return 0x7e000000u | (vdst << 17u) | (1u << 9u) | source; +} + +std::uint32_t VShiftLeft(std::uint32_t vdst, std::uint32_t amount, std::uint32_t vsrc) { + return 0x34000000u | (vdst << 17u) | (vsrc << 9u) | (128u + amount); +} + +std::uint32_t VAdd(std::uint32_t vdst, std::uint32_t source0, std::uint32_t vsrc1) { + return 0x4a000000u | (vdst << 17u) | (vsrc1 << 9u) | source0; +} + +constexpr std::uint32_t Vgpr(std::uint32_t index) { return 256u + index; } +constexpr std::uint32_t Inline(std::uint32_t value) { return 128u + value; } +constexpr std::uint32_t Literal = 255u; + +void EmitDelay(Words& code) { + code.push_back(0xbe9403ffu); + code.push_back(DelayIterations); + code.push_back(0x80948114u); + code.push_back(0xbf078014u); + code.push_back(0xbf85fffdu); +} + +std::uint32_t OperandA(std::uint32_t lane) { + return 0x9e3779b1u * (lane + 1u) ^ 0x5a5a0f0fu; +} + +std::uint32_t OperandB(std::uint32_t lane) { + return OperandA(lane) ^ 0xa5a5a5a5u; +} + +std::uint32_t InitValue(std::uint32_t lane) { + return 0x3c6ef372u * (lane + 3u) + 11u; +} + +Words AliasKernel(Form form, bool atomic) { + Words code{ShiftLaneBy5}; + EmitTableLoad(code, 2u, 0u); + EmitTableLoad(code, 6u, 16u); + code.push_back(WaitCnt0); + if (form == Form::Literal) { + code.push_back(VMov(3u, Literal)); + code.push_back(PrivateHigh); + } else if (form == Form::Computed) { + code.push_back(VMov(7u, Literal)); + code.push_back(0x30000000u); + code.push_back(VAdd(3u, Vgpr(7u), 3u)); + } + if (atomic) { + EmitScratch(code, StoreDwordOp, 0u, 6u, 2u); + code.push_back(WaitCnt0); + EmitFlat(code, AtomicAddOp, true, 10u, 4u, 2u); + code.push_back(WaitCnt0); + EmitScratch(code, LoadDwordOp, 11u, 0u, 2u); + code.push_back(WaitCnt0); + } else { + EmitFlat(code, StoreDwordOp, false, 0u, 4u, 2u); + code.push_back(WaitCnt0); + EmitScratch(code, LoadDwordOp, 10u, 0u, 2u); + EmitScratch(code, StoreDwordOp, 0u, 5u, 2u, 8u); + code.push_back(WaitCnt0); + EmitFlat(code, LoadDwordOp, false, 11u, 0u, 2u, 8u); + code.push_back(WaitCnt0); + } + EmitOutputStore(code, 10u, 0u); + EmitOutputStore(code, 11u, 4u); + code.push_back(EndProgram); + return code; +} + +void EmitGlobalLaneSetup(Words& code, std::uint32_t threadShift) { + code.push_back(VMov(7u, 4u)); + code.push_back(VShiftLeft(8u, threadShift, 7u)); + code.push_back(VAdd(9u, Vgpr(8u), 0u)); + code.push_back(VShiftLeft(13u, 2u, 9u)); + code.push_back(VShiftLeft(1u, 4u, 9u)); + code.push_back(VMov(14u, Inline(0u))); + for (std::uint32_t k = 0; k < 4u; ++k) code.push_back(VAdd(15u + k, Inline(k), 13u)); +} + +void EmitScratchFill(Words& code) { + for (std::uint32_t k = 0; k < 4u; ++k) EmitScratch(code, StoreDwordOp, 0u, 15u + k, 14u, 4u * k); + code.push_back(WaitCnt0); +} + +void EmitScratchReadback(Words& code) { + for (std::uint32_t k = 0; k < 4u; ++k) EmitScratch(code, LoadDwordOp, 20u + k, 0u, 14u, 4u * k); + code.push_back(WaitCnt0); + for (std::uint32_t k = 0; k < 4u; ++k) EmitOutputStore(code, 20u + k, 4u * k); + code.push_back(EndProgram); +} + +Words ContentionKernel(std::uint32_t waveSize) { + Words code; + EmitGlobalLaneSetup(code, waveSize == 64u ? 7u : 6u); + EmitScratchFill(code); + code.push_back(Barrier); + EmitDelay(code); + EmitScratchReadback(code); + return code; +} + +Words DivergentExitKernel(std::uint32_t waveSize) { + Words code; + EmitGlobalLaneSetup(code, waveSize == 64u ? 7u : 6u); + code.push_back(0x7c000000u | (0xc1u << 17u) | Inline(waveSize - 1u)); + code.push_back(0xbf870001u); + code.push_back(EndProgram); + EmitScratchFill(code); + EmitDelay(code); + EmitScratchReadback(code); + return code; +} + +Words CalleeKernel() { + Words code{ShiftLaneBy5}; + EmitTableLoad(code, 2u, 0u); + EmitTableLoad(code, 6u, 16u); + code.push_back(WaitCnt0); + const auto counterAfterGetpc = code.size() + 1u; + code.push_back(0xbe941f00u); + code.push_back(0x8014ff14u); + const auto literalAt = code.size(); + code.push_back(0u); + code.push_back(0x82158015u); + code.push_back(0xbe9e2114u); + const auto branchAt = code.size(); + code.push_back(0xbf820000u); + code.at(literalAt) = static_cast((code.size() - counterAfterGetpc) * 4u); + EmitFlat(code, StoreDwordOp, false, 0u, 4u, 2u); + code.push_back(WaitCnt0); + code.push_back(0xbe80201eu); + code.at(branchAt) |= static_cast(code.size() - branchAt - 1u); + EmitScratch(code, LoadDwordOp, 10u, 0u, 2u); + code.push_back(WaitCnt0); + EmitOutputStore(code, 10u, 0u); + code.push_back(EndProgram); + return code; +} + +Words PlainKernel() { + Words code{ShiftLaneBy5}; + EmitTableLoad(code, 2u, 0u); + code.push_back(WaitCnt0); + EmitOutputStore(code, 2u, 0u); + code.push_back(EndProgram); + return code; +} + +struct Facts { + bool poolBinding = false; + bool functionArrayAccess = false; +}; + +Facts Inspect(std::span words) { + Facts facts; + std::set arrays, pointers, variables; + for (std::size_t cursor = 5; cursor < words.size();) { + const auto count = words[cursor] >> 16u; + const auto op = words[cursor] & 0xffffu; + Require(count != 0u, "scratch pool: malformed SPIR-V"); + if (op == spv::OpDecorate && words[cursor + 2u] == spv::DecorationDescriptorSet && words[cursor + 3u] == ShaderRecompiler::ScratchPoolDescriptorSet) facts.poolBinding = true; + if (op == spv::OpTypeArray) arrays.insert(words[cursor + 1u]); + if (op == spv::OpTypePointer && words[cursor + 2u] == spv::StorageClassFunction && arrays.contains(words[cursor + 3u])) pointers.insert(words[cursor + 1u]); + if (op == spv::OpVariable && pointers.contains(words[cursor + 1u])) variables.insert(words[cursor + 2u]); + if ((op == spv::OpAccessChain || op == spv::OpInBoundsAccessChain) && variables.contains(words[cursor + 3u])) facts.functionArrayAccess = true; + cursor += count; + } + return facts; +} + +ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, const Words& code, std::vector& userData, std::uint32_t waveSize, std::uint32_t threads, bool groupId, std::uint32_t scratchDwords) { + const std::span codeSpan(code); + const std::array memory{{{reinterpret_cast(codeSpan.data()), std::as_bytes(codeSpan)}}}; + ShaderRecompiler::ShaderComputeStageInfo compute{{threads, 1, 1}, 0, {groupId, false, false}, false, 1}; + compute.scratchDwords = scratchDwords; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(codeSpan.data()), codeSpan, 0, {}}, + {waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + return ShaderRecompiler::Recompile(request); +} + +void RunAlias(AgcDriver::VulkanDevice& device, Form form, bool atomic, std::uint32_t waveSize) { + const std::string name = std::string("scratch pool alias ") + FormNames[static_cast(form)] + (atomic ? " atomic" : " store") + " wave" + std::to_string(waveSize); + std::vector table(waveSize * 8u, 0u); + for (std::uint32_t lane = 0; lane < waveSize; ++lane) { + table[lane * 8u] = 4u; + table[lane * 8u + 1u] = form == Form::Table ? PrivateHigh : (form == Form::Computed ? PrivateHigh - 0x30000000u : 0x1234u); + table[lane * 8u + 2u] = OperandA(lane); + table[lane * 8u + 3u] = OperandB(lane); + table[lane * 8u + 4u] = InitValue(lane); + } + std::vector output(waveSize * 8u, 0xdeadbeefu); + std::vector userData(8, 0u); + const auto outputDescriptor = BufferDescriptor(output.data(), static_cast(output.size() * 4u)); + const auto tableDescriptor = BufferDescriptor(table.data(), static_cast(table.size() * 4u)); + std::copy(outputDescriptor.begin(), outputDescriptor.end(), userData.begin()); + std::copy(tableDescriptor.begin(), tableDescriptor.end(), userData.begin() + 4); + const auto code = AliasKernel(form, atomic); + const auto result = Compile(device, code, userData, waveSize, waveSize, false, AliasScratchDwords); + const auto facts = Inspect(std::span(result.spirv.data(), result.spirv.size())); + Require(facts.poolBinding && result.scratchPoolSlotDwords != 0u, name + ": the scratch program has no scratch pool binding"); + Require(!facts.functionArrayAccess, name + ": the scratch program indexes a Function array"); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + for (std::uint32_t lane = 0; lane < waveSize; ++lane) { + const auto first = output[lane * 8u]; + const auto second = output[lane * 8u + 1u]; + const auto expectedFirst = atomic ? InitValue(lane) : OperandA(lane); + const auto expectedSecond = atomic ? InitValue(lane) + OperandA(lane) : OperandB(lane); + Require(first == expectedFirst, name + ": lane " + std::to_string(lane) + " first value " + Hex(first) + ", expected " + Hex(expectedFirst)); + Require(second == expectedSecond, name + ": lane " + std::to_string(lane) + " second value " + Hex(second) + ", expected " + Hex(expectedSecond)); + } +} + +void RunCallee(AgcDriver::VulkanDevice& device, std::uint32_t waveSize) { + const std::string name = "scratch pool callee wave" + std::to_string(waveSize); + std::vector table(waveSize * 8u, 0u); + for (std::uint32_t lane = 0; lane < waveSize; ++lane) { + table[lane * 8u] = 4u; + table[lane * 8u + 1u] = PrivateHigh; + table[lane * 8u + 2u] = OperandA(lane); + } + std::vector output(waveSize * 8u, 0xdeadbeefu); + std::vector userData(8, 0u); + const auto outputDescriptor = BufferDescriptor(output.data(), static_cast(output.size() * 4u)); + const auto tableDescriptor = BufferDescriptor(table.data(), static_cast(table.size() * 4u)); + std::copy(outputDescriptor.begin(), outputDescriptor.end(), userData.begin()); + std::copy(tableDescriptor.begin(), tableDescriptor.end(), userData.begin() + 4); + const auto code = CalleeKernel(); + const auto result = Compile(device, code, userData, waveSize, waveSize, false, AliasScratchDwords); + Require(result.scratchPoolSlotDwords != 0u, name + ": the scratch program has no scratch pool binding"); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + for (std::uint32_t lane = 0; lane < waveSize; ++lane) { + Require(output[lane * 8u] == OperandA(lane), name + ": lane " + std::to_string(lane) + " read " + Hex(output[lane * 8u]) + " back, expected " + Hex(OperandA(lane))); + } +} + +void RunPlain(AgcDriver::VulkanDevice& device) { + std::vector table(32u * 8u, 7u); + std::vector output(32u * 8u, 0u); + std::vector userData(8, 0u); + const auto outputDescriptor = BufferDescriptor(output.data(), static_cast(output.size() * 4u)); + const auto tableDescriptor = BufferDescriptor(table.data(), static_cast(table.size() * 4u)); + std::copy(outputDescriptor.begin(), outputDescriptor.end(), userData.begin()); + std::copy(tableDescriptor.begin(), tableDescriptor.end(), userData.begin() + 4); + const auto code = PlainKernel(); + const auto result = Compile(device, code, userData, 32u, 32u, false, 0u); + const auto facts = Inspect(std::span(result.spirv.data(), result.spirv.size())); + Require(!facts.poolBinding && result.scratchPoolSlotDwords == 0u, "scratch pool: a program without scratch got a scratch pool binding"); +} + +void RunContention(AgcDriver::VulkanDevice& device, const char* label, const Words& code, std::uint32_t waveSize, bool exitsEarly) { + const auto threads = waveSize * 2u; + const auto lanes = ContentionGroups * threads; + std::vector output(static_cast(lanes) * 4u, 0xdeadbeefu); + std::vector userData(4, 0u); + const auto outputDescriptor = BufferDescriptor(output.data(), static_cast(output.size() * 4u)); + std::copy(outputDescriptor.begin(), outputDescriptor.end(), userData.begin()); + const auto result = Compile(device, code, userData, waveSize, threads, true, ContentionScratchDwords); + const std::string name = std::string("scratch pool ") + label + " wave" + std::to_string(waveSize); + Require(result.scratchPoolSlotDwords == threads * ContentionScratchDwords, name + ": unexpected slot size " + std::to_string(result.scratchPoolSlotDwords)); + for (std::uint32_t round = 0; round < 2u; ++round) { + std::fill(output.begin(), output.end(), 0xdeadbeefu); + device.Dispatch(result, ContentionGroups, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + for (std::uint32_t lane = 0; lane < lanes; ++lane) { + const bool exited = exitsEarly && (lane % threads) < waveSize; + for (std::uint32_t k = 0; k < 4u; ++k) { + const auto value = output[static_cast(lane) * 4u + k]; + const auto expected = exited ? 0xdeadbeefu : lane * 4u + k; + Require(value == expected, name + ": round " + std::to_string(round) + " lane " + std::to_string(lane) + " dword " + std::to_string(k) + " read " + Hex(value) + ", expected " + Hex(expected)); + } + } + } +} + +void RunDiscardedInitialization(AgcDriver::VulkanDevice& device) { + const std::string name = "scratch pool initialization after a discarded batch"; + constexpr std::uint32_t waveSize = 32u; + constexpr std::uint32_t threads = waveSize * 2u; + const auto code = ContentionKernel(waveSize); + const auto lanes = ContentionGroups * threads; + std::vector output(static_cast(lanes) * 4u, 0xdeadbeefu); + std::vector userData(4, 0u); + const auto outputDescriptor = BufferDescriptor(output.data(), static_cast(output.size() * 4u)); + std::copy(outputDescriptor.begin(), outputDescriptor.end(), userData.begin()); + const auto result = Compile(device, code, userData, waveSize, threads, true, ContentionScratchDwords); + const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(device); + const std::uint64_t slotBytes = (static_cast(result.scratchPoolSlotDwords) + 2u) * 4u; + const std::uint64_t slots = std::clamp(std::max((1u << 20u) / slotBytes, 1u), 1u, ContentionGroups); + const std::size_t poolBytes = static_cast(slots * slotBytes); + { + AgcDriver::Graphics::DeviceBuffer dirty(context, poolBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT); + AgcDriver::Graphics::CommandBatch batch(context); + context.Function("vkCmdFillBuffer")(batch.Handle(), dirty.Handle(), 0, poolBytes, 0xffffffffu); + batch.SubmitAndWait(); + } + AgcDriver::Graphics::Recorder::FailNextSubmitsForTest(1u); + bool discarded = false; + try { + device.Dispatch(result, ContentionGroups, 1, 1, {}, reinterpret_cast(code.data())); + device.Dispatch(result, ContentionGroups, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + } catch (const std::exception& error) { + discarded = std::string(error.what()).find("injected failure") != std::string::npos; + } + AgcDriver::Graphics::Recorder::FailNextSubmitsForTest(0u); + Require(discarded, name + ": the first batch was not discarded"); + const auto [header, headerBytes] = AgcDriver::VulkanDeviceTestAccess::HeaderOf(device, result); + Require(header != VK_NULL_HANDLE && headerBytes != 0u, name + ": the dispatches created no scratch pool"); + AgcDriver::Graphics::Buffer readback(context, headerBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT); + { + AgcDriver::Graphics::CommandBatch batch(context); + AgcDriver::Graphics::CopyBuffer(context, batch.Handle(), header, 0, readback.Handle(), 0, headerBytes); + AgcDriver::Graphics::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(); + } + readback.Invalidate(); + const auto* words = reinterpret_cast(readback.Bytes().data()); + for (std::uint64_t word = 0; word < headerBytes / 4u; ++word) { + Require(words[word] == 0u, name + ": header word " + std::to_string(word) + " reads " + Hex(words[word]) + " after the discarded batch"); + } + device.Dispatch(result, ContentionGroups, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + for (std::uint32_t lane = 0; lane < lanes; ++lane) { + for (std::uint32_t k = 0; k < 4u; ++k) { + const auto value = output[static_cast(lane) * 4u + k]; + Require(value == lane * 4u + k, name + ": lane " + std::to_string(lane) + " dword " + std::to_string(k) + " read " + Hex(value) + " after the retry"); + } + } +} + +void RunEviction(AgcDriver::VulkanDevice& device) { + const std::string name = "scratch pool eviction over the total budget"; + constexpr std::uint32_t waveSize = 32u; + constexpr std::uint32_t threads = waveSize * 2u; + const auto lanes = ContentionGroups * threads; + const auto first = ContentionKernel(waveSize); + const auto second = DivergentExitKernel(waveSize); + std::vector output(static_cast(lanes) * 4u, 0xdeadbeefu); + std::vector userData(4, 0u); + const auto outputDescriptor = BufferDescriptor(output.data(), static_cast(output.size() * 4u)); + std::copy(outputDescriptor.begin(), outputDescriptor.end(), userData.begin()); + const auto firstResult = Compile(device, first, userData, waveSize, threads, true, ContentionScratchDwords); + const auto secondResult = Compile(device, second, userData, waveSize, threads, true, ContentionScratchDwords); + const auto run = [&](const ShaderRecompiler::RecompileResult& result, const Words& code, bool exitsEarly, const char* step) { + std::fill(output.begin(), output.end(), 0xdeadbeefu); + device.Dispatch(result, ContentionGroups, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + for (std::uint32_t lane = 0; lane < lanes; ++lane) { + const bool exited = exitsEarly && (lane % threads) < waveSize; + for (std::uint32_t k = 0; k < 4u; ++k) { + const auto value = output[static_cast(lane) * 4u + k]; + const auto expected = exited ? 0xdeadbeefu : lane * 4u + k; + Require(value == expected, name + ": " + step + " lane " + std::to_string(lane) + " dword " + std::to_string(k) + " read " + Hex(value) + ", expected " + Hex(expected)); + } + } + }; + const auto held = [&](const ShaderRecompiler::RecompileResult& result) { return AgcDriver::VulkanDeviceTestAccess::HeaderOf(device, result).first != VK_NULL_HANDLE; }; + run(firstResult, first, false, "first program"); + Require(held(firstResult), name + ": the first program got no pool"); + run(secondResult, second, true, "second program"); + Require(held(secondResult) && !held(firstResult), name + ": the second pool did not evict the first"); + run(firstResult, first, false, "first program again"); + Require(held(firstResult) && !held(secondResult), name + ": the first program's pool was not recreated in place of the second"); +} + +} + +int main() { + try { +#ifdef _WIN32 + _putenv_s("APS5_SCRATCH_POOL_MIB", "1"); + _putenv_s("APS5_SCRATCH_POOL_TOTAL_MIB", "1"); +#else + setenv("APS5_SCRATCH_POOL_MIB", "1", 1); + setenv("APS5_SCRATCH_POOL_TOTAL_MIB", "1", 1); +#endif + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + const bool wave64 = device->Target().subgroupSize >= 32u; + RunPlain(*device); + RunDiscardedInitialization(*device); + RunEviction(*device); + for (const std::uint32_t waveSize : {32u, 64u}) { + if (waveSize == 64u && !wave64) continue; + for (const Form form : {Form::Table, Form::Literal, Form::Computed}) { + RunAlias(*device, form, false, waveSize); + RunAlias(*device, form, true, waveSize); + } + RunCallee(*device, waveSize); + RunContention(*device, "isolation", ContentionKernel(waveSize), waveSize, false); + RunContention(*device, "divergent exit", DivergentExitKernel(waveSize), waveSize, true); + } + std::puts("scratch pool 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/SdwaFloatSelectors.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp index 6efd68b5d2..4ee9be5330 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaFloatSelectors.cpp @@ -121,6 +121,59 @@ constexpr const char* Names[16] = { "mul_f16 BYTE_2/DWORD -> BYTE_1", }; +alignas(256) constexpr std::array ClampCode{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x64140ef9, 0x04052006, 0x66160cf9, + 0x04022505, 0x68180cf9, 0x03042607, 0x7e1a0305, 0x6a1a0ef9, 0x05043306, 0x741c0ef9, 0x06012405, + 0x721e0af9, 0x00062606, 0x64200ef9, 0x05002a05, 0x6a220ef9, 0x06252106, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xbf810000, +}; + +constexpr std::uint32_t ClampExpected[32][8] = { + {0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00001db6u, 0x00000000u, 0x00000000u, 0x0000009du, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x0000003cu, 0x00004700u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00470000u, 0x00000000u, 0x00470047u, 0x00000000u, 0x00000047u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00003c00u, 0x001fba34u, 0x00000000u, 0x00000034u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000047u, 0x00000000u, 0x00008800u}, + {0x00000000u, 0x30980000u, 0x00003c00u, 0xb3808080u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000042u, 0xc0000000u, 0x00000000u, 0x000002c0u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x1464a58eu, 0x000000a5u, 0x00002714u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000007cu, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000068u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00001100u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x00000100u, 0x001c0000u, 0x0000ca00u}, + {0x00000000u, 0x00800000u, 0x0000249du, 0x00800080u, 0x00000000u, 0x00000080u, 0x00000000u, 0x00000000u}, + {0x00000046u, 0x3c000000u, 0x00000000u, 0x0000bc00u, 0x000000bcu, 0x00000000u, 0xffe60000u, 0x00005100u}, + {0x00000000u, 0x00000000u, 0x00003c00u, 0x00470047u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00800080u, 0x00000000u, 0x0000068bu, 0x00000000u, 0x0000f400u}, + {0x00000003u, 0x3c000000u, 0x00000000u, 0x00800080u, 0x00000000u, 0x00000080u, 0x00000000u, 0x0000e500u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00ff00ffu, 0x00000000u, 0x000000ffu, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00ff0000u, 0x00000000u, 0x00ff00ffu, 0x00000000u, 0x000000ffu, 0xfffe0000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00ff00ffu, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000052u, 0x00000100u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00003c00u, 0x002f8e4au, 0x00000000u, 0x0000004au, 0x004a0000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x000000e7u, 0x00d8e2bcu, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u}, + {0x00000080u, 0x00000000u, 0x00000000u, 0xf033ad9cu, 0x00000000u, 0x0000009cu, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x0ed30000u, 0x00000000u, 0x00f70eabu, 0x0000000eu, 0x000000abu, 0xfff10000u, 0x00000000u}, + {0x00000000u, 0x00300000u, 0x00000000u, 0x00778095u, 0x00000080u, 0x00000095u, 0x00000000u, 0x00000000u}, + {0x0000004fu, 0x3c000000u, 0x00000000u, 0x00119b05u, 0x0000009bu, 0x00000005u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u} +}; +constexpr std::uint32_t ClampChecked[32] = {0xffu, 0x93u, 0xfbu, 0xddu, 0xffu, 0xdfu, 0x97u, 0xdfu, 0xd3u, 0x6eu, 0x44u, 0x37u, 0xffu, 0xd5u, 0x57u, 0xd9u, 0xefu, 0xffu, 0xffu, 0xdfu, 0xdfu, 0x1cu, 0xffu, 0xd5u, 0xfbu, 0x9fu, 0xfbu, 0xd6u, 0x0cu, 0x45u, 0xcfu, 0x22u}; +constexpr const char* ClampNames[8] = { + "add_f16 WORD_1/WORD_0 -> BYTE_0 clamp", + "sub_f16 BYTE_2/WORD_0 -> WORD_1 clamp", + "subrev_f16 WORD_0/BYTE_3 -> DWORD clamp", + "mul_f16 WORD_0/WORD_1 -> BYTE_3 preserve clamp", + "min_f16 BYTE_1/DWORD -> WORD_0 clamp", + "max_f16 DWORD/BYTE_0 -> DWORD clamp", + "add_f16 BYTE_0/WORD_1 -> BYTE_2 sext clamp", + "mul_f16 |WORD_1|/DWORD -> BYTE_1 clamp", +}; + void Fill(std::uint32_t tid, std::uint32_t* words) { std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); } @@ -158,36 +211,14 @@ ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std:: return ShaderRecompiler::Recompile(request); } -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(); } -void CheckByteSelectorClampRefused(AgcDriver::VulkanDevice& device) { - constexpr std::array, 6> forms{{ - {0x640a0cf9u, 0x04052004u}, - {0x660a0cf9u, 0x04022504u}, - {0x680a0cf9u, 0x03042604u}, - {0x6a0a0cf9u, 0x05043304u}, - {0x740a0cf9u, 0x06012404u}, - {0x720a0cf9u, 0x00062604u}, - }}; - for (const auto& form : forms) { - alignas(256) const std::array code{form[0], form[1], 0xbf810000u}; - std::string refusal; - try { - static_cast(Compile(device, code)); - } catch (const std::exception& error) { - refusal = error.what(); - } - Require(refusal.find("VOP2 SDWA clamp with byte selectors is not implemented") != std::string::npos, "f16 SDWA form " + Hex(form[0]) + " " + Hex(form[1]) + " with clamp was not refused"); - } -} - void Check() { for (std::uint32_t tid = 0; tid < Threads; ++tid) { const std::uint32_t* in = &Input[tid * Inputs]; @@ -200,15 +231,27 @@ void Check() { } } +void CheckClamp() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 8; ++i) { + if ((ClampChecked[tid] >> i) & 1u) { + Expect(tid, out[i], ClampExpected[tid][i], ClampNames[i]); + } + } + } +} + } int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - Run(*device); + Run(*device, Code); Check(); - CheckByteSelectorClampRefused(*device); + Run(*device, ClampCode); + CheckClamp(); std::puts("sdwa float selectors tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaLdexpF16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaLdexpF16.cpp index ec46c5b46f..ac16cb2e80 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/SdwaLdexpF16.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/SdwaLdexpF16.cpp @@ -121,6 +121,59 @@ constexpr const char* Names[16] = { "BYTE_3/WORD_1 -> WORD_0", }; +alignas(256) constexpr std::array ClampCode{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x76140af9, 0x04042004, 0x76160af9, + 0x06022504, 0x76180af9, 0x01062604, 0x7e1a0304, 0x761a0ef9, 0x00053306, 0x761c0ef9, 0x0b052906, + 0x761e0ef9, 0x0d142206, 0x76200cf9, 0x02242407, 0x762208f9, 0x08052305, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xbf810000, +}; + +constexpr std::uint32_t ClampExpected[32][8] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00001b8au, 0x00000000u}, + {0x00000040u, 0x00080000u, 0x00003c00u, 0xf88003ffu, 0x00000000u, 0x00000000u, 0x00003c00u, 0x02000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00f0fff0u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x0022fe4du, 0x00000000u, 0x00000000u, 0x00001c00u, 0x00000000u}, + {0x00000000u, 0x09980000u, 0x00000000u, 0x00b3fddeu, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x007a7ca0u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00007d00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00007e00u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x10000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00097c3cu, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00edf7dbu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00fc7d70u, 0x00000400u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x0a7f7f7fu, 0x00000a00u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x005cfc30u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x000b0000u, 0x00000000u, 0x00b6fe11u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x0a000000u, 0x00000000u, 0x00c0ffc0u, 0x00000000u, 0x00820000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x0008fc92u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00860000u, 0x00000000u, 0x00437c27u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00207fd4u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00957e8au, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00050000u, 0x00000000u, 0x00057dadu, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00dffc34u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00317eabu, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00987d0au, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00a4fe29u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x0042ffedu, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x001cfca9u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x0092fd96u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00baff3cu, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u}, + {0x00000000u, 0x3c000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u} +}; +constexpr std::uint32_t ClampChecked[32] = {0xe7u, 0x30u, 0xfau, 0xb8u, 0xb8u, 0xf8u, 0xbau, 0x7au, 0xb8u, 0xbdu, 0xe8u, 0xf8u, 0x98u, 0xf8u, 0xb8u, 0xe0u, 0x98u, 0xe2u, 0xf8u, 0x62u, 0xfau, 0xfau, 0xd8u, 0xb8u, 0xb8u, 0xbau, 0xfau, 0xf8u, 0xb8u, 0xdau, 0xfau, 0xe0u}; +constexpr const char* ClampNames[8] = { + "WORD_0/WORD_0 -> BYTE_0 clamp", + "BYTE_2/DWORD -> WORD_1 clamp", + "DWORD/BYTE_1 -> DWORD clamp", + "WORD_1/BYTE_0 -> BYTE_3 preserve clamp", + "WORD_1/sext BYTE_3 -> BYTE_1 sext clamp", + "-WORD_0/sext WORD_1 -> BYTE_2 clamp", + "|WORD_0|/BYTE_2 -> WORD_0 clamp", + "WORD_1/sext BYTE_0 -> BYTE_3 clamp", +}; + void Fill(std::uint32_t tid, std::uint32_t* words) { std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); } @@ -159,10 +212,9 @@ ShaderRecompiler::RecompileResult Compile(AgcDriver::VulkanDevice& device, std:: return ShaderRecompiler::Recompile(request); } -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(); @@ -173,10 +225,7 @@ void CheckRefused(AgcDriver::VulkanDevice& device) { std::uint32_t modifier; const char* error; }; - constexpr std::array forms{{ - {0x04042004u, "VOP2 SDWA clamp with byte selectors is not implemented"}, - {0x06022504u, "VOP2 SDWA clamp with byte selectors is not implemented"}, - {0x01062604u, "VOP2 SDWA clamp with byte selectors is not implemented"}, + constexpr std::array forms{{ {0x10050404u, "VOP2 SDWA source modifiers are not supported"}, {0x26060604u, "VOP2 SDWA source modifiers are not supported"}, }}; @@ -204,14 +253,27 @@ void Check() { } } +void CheckClamp() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 8; ++i) { + if ((ClampChecked[tid] >> i) & 1u) { + Expect(tid, out[i], ClampExpected[tid][i], ClampNames[i]); + } + } + } +} + } int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - Run(*device); + Run(*device, Code); Check(); + Run(*device, ClampCode); + CheckClamp(); CheckRefused(*device); std::puts("sdwa ldexp f16 tests passed"); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Signed64BitLiteral.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Signed64BitLiteral.cpp new file mode 100644 index 0000000000..dac8631412 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Signed64BitLiteral.cpp @@ -0,0 +1,120 @@ +#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; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xd4a10008u, 0x000100ffu, 0x80000000u, + 0xd501000au, 0x00210280u, 0xd4a40008u, 0x0001fe80u, 0xff00ff00u, 0xd501000bu, 0x00210280u, 0xd4a20008u, + 0x000208ffu, 0xfffffffbu, 0xd501000cu, 0x00210280u, 0xbe8918ffu, 0x80000000u, 0x7e1a0209u, 0xd701000eu, + 0x0001fe84u, 0x80000000u, 0xd7000010u, 0x0001fe84u, 0x80000000u, 0xd5770a12u, 0x03fd0481u, 0xfffffffbu, + 0xd5760a14u, 0x03fd0481u, 0xfffffffbu, 0x918c84ffu, 0x80000000u, 0x7e2c020cu, 0x7e2e020du, 0xbe9003ffu, + 0x00280010u, 0x950e10ffu, 0x80000000u, 0x7e30020eu, 0x7e32020fu, 0xe0781000u, 0x80010a02u, 0xe0781010u, + 0x80010e02u, 0xe0781020u, 0x80011202u, 0xe0781030u, 0x80011602u, 0xbf810000u, +}; + +constexpr std::array Names{ + "v_cmp_lt_i64 0x80000000, 0", + "v_cmp_gt_i64 0, 0xff00ff00", + "v_cmp_eq_i64 0xfffffffb, -5", + "s_flbit_i32_i64 0x80000000", + "v_ashrrev_i64 4, 0x80000000 (low)", + "v_ashrrev_i64 4, 0x80000000 (high)", + "v_lshrrev_b64 4, 0x80000000 (low)", + "v_lshrrev_b64 4, 0x80000000 (high)", + "v_mad_i64_i32 1, 2, 0xfffffffb (low)", + "v_mad_i64_i32 1, 2, 0xfffffffb (high)", + "v_mad_u64_u32 1, 2, 0xfffffffb (low)", + "v_mad_u64_u32 1, 2, 0xfffffffb (high)", + "s_ashr_i64 0x80000000, 4 (low)", + "s_ashr_i64 0x80000000, 4 (high)", + "s_bfe_i64 0x80000000, offset 16 width 40 (low)", + "s_bfe_i64 0x80000000, offset 16 width 40 (high)", +}; + +constexpr std::array Expected{ + 0x00000001u, 0x00000001u, 0x00000001u, 0x00000021u, 0xf8000000u, 0xffffffffu, 0x08000000u, 0x00000000u, + 0xfffffffdu, 0xffffffffu, 0xfffffffdu, 0x00000000u, 0xf8000000u, 0xffffffffu, 0xffff8000u, 0xffffffffu, +}; + +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) { + Input.fill(0u); + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + Input[lane * Inputs] = 0xfffffffbu; + Input[lane * Inputs + 1u] = 0xffffffffu; + } + 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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + for (std::uint32_t column = 0; column < Results; ++column) { + const auto value = Output[lane * Results + column]; + Require(value == Expected[column], std::string(Names[column]) + " in thread " + std::to_string(lane) + " is " + Hex(value) + ", expected " + Hex(Expected[column])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("signed 64-bit literal 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/StencilPlaneMemory.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StencilPlaneMemory.cpp index b89b0f39c8..b83fc2f0a6 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StencilPlaneMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StencilPlaneMemory.cpp @@ -73,6 +73,51 @@ VkStencilOpState Stencil(VkCompareOp compare, VkStencilOp pass, std::uint32_t re return {VK_STENCIL_OP_KEEP, pass, VK_STENCIL_OP_KEEP, compare, 0xffu, 0xffu, reference}; } +void CheckConsumedStencilClear(AgcDriver::VulkanDevice& device, const DepthFastClearHarness::Shaders& shaders, std::uint32_t swizzle, bool sampled) { + auto* blocks = static_cast(AllocateBlocks(DepthBytes + StencilBytes + Block)); + auto* depth = reinterpret_cast(blocks); + auto* stencil = reinterpret_cast(blocks + DepthBytes); + auto* htile = reinterpret_cast(blocks + DepthBytes + StencilBytes); + const auto htileBytes = AgcDriver::Graphics::HtileMetadataBytes(PlaneExtent); + Require(htileBytes <= Block, "the clear metadata does not fit its allocation"); + std::fill(depth, depth + PlaneTexels, 1.0f); + Fill(stencil, 0u); + std::fill(htile, htile + htileBytes / sizeof(*htile), 0xffffffffu); + AgcDriver::GuestMemory::BumpCollectEpoch(); + const auto stencilBase = reinterpret_cast(stencil); + Surface surface{reinterpret_cast(depth), stencilBase, reinterpret_cast(htile), true, VK_FORMAT_D32_SFLOAT_S8_UINT, 0, 0, swizzle}; + Require(Draw(device, shaders, surface, {.depthTest = true, .depthWrite = true, .depthCompare = VK_COMPARE_OP_ALWAYS, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x18u)}, PlaneExtent, Colors) == PlaneTexels, "the clear control draw did not cover the plane"); + Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "the clear control did not store its initial stencil"); + device.WaitIdle(); + Require(Holding(stencil, 0x18u) == StencilBytes, "the clear control stencil was not stored"); + Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "the clear control stencil is not clean"); + std::fill(htile, htile + htileBytes / sizeof(*htile), 0u); + AgcDriver::GuestMemory::BumpCollectEpoch(); + AgcDriver::Graphics::NoteDepthMetadataFill(surface.htile, htileBytes, 0u); + if (sampled) { + AgcDriver::Graphics::GuestTextureResource resource{}; + resource.baseAddress = stencilBase; + resource.width = PlaneWidth; + resource.height = PlaneHeight; + resource.format = 5u; + resource.dimension = AgcDriver::Graphics::TextureDimension::k2D; + resource.tileMode = AgcDriver::Graphics::TextureTileMode::kZ64KBX; + resource.mipCount = resource.allocatedMipCount = 1u; + resource.dstSelX = 4u; resource.dstSelY = 5u; resource.dstSelZ = 6u; resource.dstSelW = 7u; + const std::array words{}; + Require(AgcDriver::Graphics::DepthSurfaceTexture(device.graphicsContext(), words, resource, {}) != nullptr, "the stencil clear could not be sampled"); + } else { + Require(!AgcDriver::Graphics::FlushDepthPlanes(surface.depth, DepthBytes).empty(), "the depth-only flush did not consume the pending clear"); + } + device.WaitIdle(); + surface.htile = 0u; + Require(Draw(device, shaders, surface, {.depthTest = false, .stencilTest = false}, PlaneExtent, Colors) == PlaneTexels, "the read-only rebind after a clear did not cover the plane"); + Require(!AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a consumed stencil clear was lost on a read-only rebind"); + device.WaitIdle(); + Require(Holding(stencil, 0u) == StencilBytes, "the consumed stencil clear did not reach guest memory"); + Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "the consumed stencil clear was stored twice"); +} + } int main() { @@ -98,6 +143,24 @@ int main() { Require(Holding(stencilPlane, 0x18u) == StencilBytes, "a read of the stencil plane after a draw wrote stencil 0x18 does not see it in guest memory"); Require(AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(), "a second read of the stored stencil plane stored it again"); + CheckConsumedStencilClear(*device, shaders, swizzle, true); + CheckConsumedStencilClear(*device, shaders, swizzle, false); + + std::size_t readOnlyFailures = 0; + const auto checkReadOnly = [&](const DrawOptions& options, const char* name) { + Require(draw(options) == PlaneTexels, "a read-only stencil draw did not cover the plane"); + const bool dirty = !AgcDriver::Graphics::FlushDepthPlanes(stencilBase, StencilBytes).empty(); + device->WaitIdle(); + Require(Holding(stencilPlane, 0x18u) == StencilBytes, "a read-only draw changed the stored stencil bytes"); + std::cout << name << ": " << (dirty ? "dirtied the stored stencil plane" : "kept the stored stencil plane clean") << '\n'; + if (dirty) ++readOnlyFailures; + }; + checkReadOnly({.depthTest = false, .stencilTest = false}, "stencil-off"); + checkReadOnly({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_KEEP, 0x18u)}, "all-KEEP"); + auto maskedStencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x99u); + maskedStencil.writeMask = 0u; + checkReadOnly({.depthTest = false, .stencilTest = true, .stencil = maskedStencil}, "zero-mask"); + Require(draw({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_ALWAYS, VK_STENCIL_OP_REPLACE, 0x2au)}) == PlaneTexels, "the draw writing stencil 0x2a did not cover the plane"); AgcDriver::Graphics::RetireDepthSurfaces(device->graphicsContext().device, reinterpret_cast(depthPlane), DepthBytes); device->WaitIdle(); @@ -111,6 +174,7 @@ int main() { Require(draw({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, 0x05u)}) == PlaneTexels, "a draw testing stencil EQUAL 0x05 after the guest wrote 0x05 into the stencil plane did not pass everywhere"); Require(draw({.depthTest = false, .stencilTest = true, .stencil = Stencil(VK_COMPARE_OP_EQUAL, VK_STENCIL_OP_KEEP, 0x18u)}) == 0u, "a draw testing stencil EQUAL 0x18 passed after the guest wrote 0x05 over the stored stencil plane"); } + Require(readOnlyFailures == 0, "read-only stencil draws dirtied the stored plane"); std::puts("stencil plane memory tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp index ec90020045..c0511eead7 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StorageFlushOrdering.cpp @@ -41,6 +41,11 @@ constexpr std::uint32_t Stored = 0x11111111u; constexpr std::uint32_t Overwritten = 0x22222222u; constexpr std::size_t OverwrittenDword = 0x100u / 4u; constexpr std::size_t FarDword = 100u * Width; +constexpr std::uint32_t Filled = 0x33333333u; +constexpr std::size_t FillBytes = 4096u; +constexpr std::size_t FillPast = 0x10000u; +constexpr std::size_t ShadowUnitBytes = 0x10000u; +constexpr std::uint32_t Standard256B = 1u; alignas(256) constexpr std::array StoreImageCode{0x7e000280u, 0x7e020280u, 0x7e0402ffu, Stored, 0xf0200108u, 0x00010200u, 0x7e0202ffu, 0x00000064u, 0xf0200108u, 0x00010200u, 0xbf810000u}; alignas(256) constexpr std::array StoreGlobalCode{0x7e000208u, 0x7e020209u, 0x7e0402ffu, Overwritten, 0xdc708100u, 0x007d0200u, 0xbf810000u}; @@ -105,12 +110,12 @@ ShaderRecompiler::RecompileResult Compile(std::span code, s return ShaderRecompiler::Recompile(request); } -std::vector ImageUserData(std::uint64_t base) { +std::vector ImageUserData(std::uint64_t base, std::uint32_t tileMode = 0u) { std::vector userData(12, 0u); userData[4] = static_cast(base >> 8u); userData[5] = static_cast((base >> 40u) & 0xffu) | (Format32UInt << 20u) | (((Width - 1u) & 3u) << 30u); userData[6] = ((Width - 1u) >> 2u) | ((Height - 1u) << 14u); - userData[7] = 0xfacu | (Type2D << 28u); + userData[7] = 0xfacu | (tileMode << 20u) | (Type2D << 28u); return userData; } @@ -128,6 +133,75 @@ bool AddressBasedStorePending(std::uint64_t image, bool open) { return read.has_value() && read->kind == AgcDriver::Graphics::Recorder::ReadKind::AddressBased && read->open == open; } +bool OpenBatchWrites(std::uint64_t address, std::size_t bytes) { + std::lock_guard gpu(AgcDriver::GuestMemory::GpuMutex()); + const auto* recorder = AgcDriver::Graphics::Recorder::Active(); + return recorder != nullptr && recorder->OpenWriteOverlaps(address, bytes); +} + +std::uint64_t StoreImage(AgcDriver::VulkanDevice& device, const GuestBlock& guest, std::size_t offset, std::uint32_t tileMode = 0u) { + const auto image = guest.Address() + offset; + const auto storeImage = Compile(StoreImageCode, ImageUserData(image, tileMode), device.Target()); + device.Dispatch(storeImage, 1u, 1u, 1u, {}, reinterpret_cast(StoreImageCode.data())); + return image; +} + +void Fill(AgcDriver::VulkanDevice& device, std::uint64_t address, std::size_t bytes, const std::string& what) { + const std::array pattern{Filled, Filled, Filled, Filled}; + Require(device.FillBuffer(address, bytes, pattern), what + "the fill was not recorded on the GPU"); + Require(OpenBatchWrites(address, bytes), what + "the fill is not a write of the open batch"); +} + +void RunWriterOutsideLayers(AgcDriver::VulkanDevice& device, const GuestBlock& guest, std::size_t offset) { + const std::string what = "storage flush ordering, writer outside the image's pending layers: "; + const auto* words = guest.Words() + offset / 4u; + const auto image = StoreImage(device, guest, offset); + const auto fill = image + ImageBytes + FillPast; + Fill(device, fill, FillBytes, what); + Require(AgcDriver::Graphics::StorageTexture::FlushPending(image, static_cast(fill + FillBytes - image), nullptr, "test"), what + "the image store left no results pending"); + Require(OpenBatchWrites(fill, FillBytes), what + "the flush submitted and waited for a writer its stores cannot overwrite"); + device.WaitIdle(); + Require(words[FarDword] == Stored, what + "the pending image's texel was not stored"); + Require(words[(ImageBytes + FillPast) / 4u] == Filled, what + "the fill's bytes did not land"); +} + +void RunWriterInsideLayer(AgcDriver::VulkanDevice& device, const GuestBlock& guest, std::size_t offset) { + const std::string what = "storage flush ordering, writer inside the image's pending layer: "; + const auto* words = guest.Words() + offset / 4u; + const auto image = StoreImage(device, guest, offset); + const auto fill = image + OverwrittenDword * 4u; + Fill(device, fill, 16u, what); + Require(AgcDriver::Graphics::StorageTexture::FlushPending(image, ImageBytes, nullptr, "test"), what + "the image store left no results pending"); + device.WaitIdle(); + Require(words[OverwrittenDword] == Filled, what + "the pending image's store put its older texels over a newer fill"); + Require(words[FarDword] == Stored, what + "the pending image's texel away from the newer fill was not stored"); +} + +void RunShadowedUnitUnderWriter(AgcDriver::VulkanDevice& device, const GuestBlock& guest) { + const std::string what = "storage flush ordering, shadowed unit under a writer outside the flushed image's layers: "; + if (!AgcDriver::Graphics::UnitShadowEnabled()) { + std::puts("unit shadows are off: the shadowed-unit case is skipped"); + return; + } + const auto* shadowedWords = guest.Words(); + const auto* flushedWords = guest.Words() + (BlockBytes / 2u) / 4u; + const auto shadowedImage = StoreImage(device, guest, 0, Standard256B); + Require(AgcDriver::Graphics::StorageTexture::FlushPending(shadowedImage, ImageBytes, nullptr, "test", AgcDriver::Graphics::PublishScope::None), what + "the first image's store left no results pending"); + if (!AgcDriver::Graphics::AnyShadowedOverlaps(shadowedImage, ShadowUnitBytes) || !AgcDriver::Graphics::AnyShadowedOverlaps(shadowedImage + ShadowUnitBytes, ShadowUnitBytes)) { + std::puts("the write-back did not retile both units into the unit shadow: the shadowed-unit case is skipped"); + return; + } + Fill(device, shadowedImage, ShadowUnitBytes, what); + const auto flushedImage = StoreImage(device, guest, BlockBytes / 2u); + Require(AgcDriver::Graphics::StorageTexture::FlushPending(shadowedImage, static_cast(flushedImage + ImageBytes - shadowedImage), nullptr, "test"), what + "the second image's store left no results pending"); + Require(!OpenBatchWrites(shadowedImage, ShadowUnitBytes), what + "the flush did not wait for the writer over the shadowed unit it publishes"); + device.WaitIdle(); + Require(shadowedWords[0] == Filled, what + "the publish put the shadowed unit's older texels over a newer fill"); + Require(shadowedWords[ShadowUnitBytes / 4u - 1u] == Filled, what + "the fill's last dword did not land"); + Require(std::count(shadowedWords + ShadowUnitBytes / 4u, shadowedWords + ImageBytes / 4u, Stored) == 1, what + "the shadowed unit the fill did not touch was not published"); + Require(flushedWords[FarDword] == Stored, what + "the flushed image's texel was not stored"); +} + void Run(AgcDriver::VulkanDevice& device, const GuestBlock& guest, std::size_t offset, bool submit, const char* name) { const auto* words = guest.Words() + offset / 4u; const auto image = guest.Address() + offset; @@ -164,6 +238,10 @@ int main() { else if (!device->CanWaitUnlocked()) std::puts("the recorder cannot wait for the GPU without running completions: the writer-in-flight case is skipped"); else Run(*device, guest, 0, true, "writer in flight"); Run(*device, guest, BlockBytes / 2u, false, "writer in the open batch"); + RunWriterOutsideLayers(*device, guest, BlockBytes / 4u); + RunWriterInsideLayer(*device, guest, 3u * BlockBytes / 4u); + GuestBlock shadowed(watched); + RunShadowedUnitUnderWriter(*device, shadowed); std::puts("storage flush ordering tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/StoreIdleResults.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/StoreIdleResults.cpp index c9c8e08c3a..491abfd4e1 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/StoreIdleResults.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/StoreIdleResults.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Synchronization/SynchronizationStatistics.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureTiling.hpp" @@ -13,6 +14,7 @@ #include #include #include +#include #include namespace { @@ -86,8 +88,9 @@ int main(int argc, char** argv) { } std::vector storage((BlockBytes + 0x10000u) / 4u); const bool wake = argc == 2 && std::string_view(argv[1]) == "--idle-epoch-wake"; - std::vector nextStorage(wake ? (BlockBytes + 0x10000u) / 4u : 0u); - auto* nextTexels = wake ? reinterpret_cast((reinterpret_cast(nextStorage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}) : nullptr; + const bool disabled = argc == 2 && std::string_view(argv[1]) == "--disabled-idle-store"; + std::vector nextStorage(wake || disabled ? (BlockBytes + 0x10000u) / 4u : 0u); + auto* nextTexels = wake || disabled ? reinterpret_cast((reinterpret_cast(nextStorage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}) : nullptr; struct ShutdownGuard { ~ShutdownGuard() { try { AgcDriverShutdown_nid_postfix(); } catch (...) {} } } shutdown; @@ -127,9 +130,30 @@ int main(int argc, char** argv) { cpu = nextTexels; Store(nextTexels); } - 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"); + if (disabled) { + 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(), "disabled idle store fixture did not observe pending storage results"); + const auto dormant = [] { return AgcDriver::DriverDetail::queue0Dormant.load(std::memory_order_relaxed); }; + const auto dormantDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (!dormant() && std::chrono::steady_clock::now() < dormantDeadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(dormant(), "with APS5_NO_IDLE_STORE queue 0 kept polling over pending storage results with every queue idle instead of waiting"); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + Require(AgcDriver::Graphics::StorageTexture::AnyPending() && !stored(), "with APS5_NO_IDLE_STORE the idle store still brought the result to guest memory"); + const std::array, 1> nextRange{{{AddressOf(nextTexels), AddressOf(nextTexels) + bytes}}}; + Require(!AgcDriver::Graphics::StorageTexture::AnyPendingOverlaps(nextRange), "disabled idle store fixture found a result pending over the second texture before its dispatch"); + Store(nextTexels); + const auto wakeDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (!AgcDriver::Graphics::StorageTexture::AnyPendingOverlaps(nextRange) && std::chrono::steady_clock::now() < wakeDeadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(AgcDriver::Graphics::StorageTexture::AnyPendingOverlaps(nextRange), "queue 0 did not run a submission made after it waited with APS5_NO_IDLE_STORE"); + const auto parkedAgainDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (!dormant() && std::chrono::steady_clock::now() < parkedAgainDeadline) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(dormant(), "with APS5_NO_IDLE_STORE queue 0 kept polling after running the second dispatch instead of waiting again"); + } else { + 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"); + } AgcDriverShutdown_nid_postfix(); { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/TwoDevicePendingStore.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/TwoDevicePendingStore.cpp new file mode 100644 index 0000000000..1a7495b761 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/TwoDevicePendingStore.cpp @@ -0,0 +1,156 @@ +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestTextureResource.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Recorder.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" +#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 Threads = 8; +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 BlockBytes = 0x60000; +constexpr std::uint32_t Untouched = 0xdeadbeefu; +constexpr std::uint32_t Stored = 0x12345678u; + +alignas(256) constexpr std::array StoreCode{ + 0x7e040300u, 0x7e060280u, 0x7e0202ffu, Stored, 0xf0200108u, 0x00020102u, 0xbf810000u, +}; + +std::array TextureDescriptor(const std::uint32_t* texels) { + const auto address = static_cast(reinterpret_cast(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 Store(AgcDriver::VulkanDevice& device, const std::uint32_t* texels) { + std::vector userData(16, 0u); + const auto texture = TextureDescriptor(texels); + std::copy(texture.begin(), texture.end(), userData.begin() + 8); + const std::span code(StoreCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {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 FlushAll(const char* reason) { + std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); + AgcDriver::Graphics::StorageTexture::FlushAllPending(reason); +} + +void Label(AgcDriver::VulkanDevice& device, const std::uint32_t* label) { + { + std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); + const std::uint32_t value = 1; + static_cast(device.WriteLabelOnGpu(static_cast(reinterpret_cast(label)), std::as_bytes(std::span(&value, 1)), 1, 0, false)); + device.SubmitRecorded(false); + } + device.WaitIdle(); +} + +} + +int main(int argc, char** argv) { + try { + const bool label = argc > 1 && std::strcmp(argv[1], "--label") == 0; + Require(!label || AgcDriver::Graphics::StorageTexture::StoreAtLabelRequested(std::getenv("APS5_STORE_AT_LABEL")), "two devices: --label needs APS5_STORE_AT_LABEL=1"); + const auto first = OpenVulkanTestDevice(); + if (!first) return VulkanTestSkipped; + std::vector storage((BlockBytes + 0x10000u) / 4u); + auto* texels = reinterpret_cast((reinterpret_cast(storage.data()) + 0xffffu) & ~std::uintptr_t{0xffffu}); + std::fill(texels, texels + BlockBytes / 4u, Untouched); + std::array labelWords{}; + { + GuestAllocations::Mutation mutation; + mutation.Add(texels, BlockBytes, true, true, true); + } + const auto bytes = AgcDriver::Graphics::DescribeSurface(AgcDriver::Graphics::DecodeTextureResource(TextureDescriptor(texels))).guestBytes; + const volatile std::uint32_t* cpu = texels; + const auto stored = [&] { + for (std::size_t index = 0; index < bytes / 4u; ++index) { + if (cpu[index] == Stored) return true; + } + return false; + }; + + Store(*first, texels); + Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: the first device's image store left no pending results"); + { + const auto second = OpenVulkanTestDevice(); + Require(second != nullptr, "two devices: the second device did not open"); + Require(AgcDriver::Graphics::Recorder::Active() == AgcDriver::VulkanDeviceTestAccess::ContextOf(*second).recorder, "two devices: the second device did not activate its recorder"); + second->StoreIdleResults(false); + second->WaitIdle(); + Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: the second device's idle store took the first device's pending results"); + Require(!stored(), "two devices: the first device's results reached guest memory through the second device's idle store"); + if (label) { + Label(*second, labelWords.data()); + Require(AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: the second device's label store took the first device's pending results"); + Require(!stored(), "two devices: the first device's results reached guest memory through the second device's label store"); + Label(*first, labelWords.data()); + Require(!AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: the first device's label store left its results pending"); + Require(stored(), "two devices: the first device's label store did not bring its results to guest memory"); + } else { + FlushAll("memory access"); + first->WaitIdle(); + Require(!AgcDriver::Graphics::StorageTexture::AnyPending(), "two devices: a flush with the second device active left the first device's results pending"); + Require(stored(), "two devices: a flush with the second device active did not bring the first device's results to guest memory"); + } + second->WaitIdle(); + } + { + GuestAllocations::Mutation mutation; + mutation.Remove(texels); + } + std::puts("two device pending 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/UintColorTarget.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/UintColorTarget.cpp index 9c9d7f8230..c995f30b1b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/UintColorTarget.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/UintColorTarget.cpp @@ -20,6 +20,7 @@ #else #include #endif +#include #include #include #include @@ -58,6 +59,9 @@ constexpr TargetFormat R32{"R32_UINT", 0x410u, VK_FORMAT_R32_UINT, 1, 4}; constexpr TargetFormat R32G32{"R32G32_UINT", 0x42cu, VK_FORMAT_R32G32_UINT, 2, 4}; constexpr TargetFormat R32G32B32A32{"R32G32B32A32_UINT", 0x438u, VK_FORMAT_R32G32B32A32_UINT, 4, 4}; constexpr TargetFormat R8{"R8_UINT", 0x404u, VK_FORMAT_R8_UINT, 1, 1}; +constexpr TargetFormat R16{"R16_UINT", 0x408u, VK_FORMAT_R16_UINT, 1, 2}; +constexpr TargetFormat R8G8B8A8{"R8G8B8A8_UINT", 0x428u, VK_FORMAT_R8G8B8A8_UINT, 4, 1}; +constexpr TargetFormat R16G16B16A16{"R16G16B16A16_UINT", 0x430u, VK_FORMAT_R16G16B16A16_UINT, 4, 2}; struct ExportCase { const char* name; @@ -69,6 +73,7 @@ constexpr ExportCase Export32R{"32_R", 1, VK_COLOR_COMPONENT_R_BIT}; constexpr ExportCase Export32GR{"32_GR", 2, VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT}; constexpr ExportCase Export32AR{"32_AR", 3, VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_A_BIT}; constexpr ExportCase Export32ABGR{"32_ABGR", 9, VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT}; +constexpr ExportCase ExportFp16ABGR{"FP16_ABGR", 4, VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT}; alignas(256) constexpr std::array VertexCode{ 0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000, @@ -99,6 +104,10 @@ std::span WordProgram(const std::array& w return Program(words, false); } +alignas(256) constexpr std::array UserDataHalvesProgram{ + 0x7e080200u, 0x7e0a0201u, 0xf8001c0fu, 0x00000504u, 0xbf810000u, +}; + struct Block { explicit Block(std::size_t bytes) : bytes(bytes), watched(AgcDriver::GuestMemory::WriteWatched()) { #ifdef _WIN32 @@ -157,7 +166,7 @@ struct Block { std::uint8_t* data = nullptr; }; -AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::size_t bytes, const TargetFormat& target, std::uint32_t attrib3) { +AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::size_t bytes, const TargetFormat& target, std::uint32_t attrib3, ShaderRecompiler::ColorExportPacking packing) { AgcDriver::Registers cx; cx[0x318] = static_cast(block.Address() >> 8u); cx[0x31b] = 0; @@ -168,10 +177,11 @@ AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::size_t by cx[0x390] = static_cast(block.Address() >> 40u); auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0); Require(color.format == target.format && color.elementBytes == target.channels * target.channelBytes && color.componentMapping == 0xe4u && color.packing == ShaderRecompiler::ColorExportPacking::Uint && color.bytes == bytes, std::string("the UINT target did not decode as an ") + target.name + " surface with the unsigned integer packing"); + color.packing = packing; return color; } -void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, const ExportCase& exported, std::span pixelCode) { +std::uint64_t Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, const ExportCase& exported, std::span pixelCode, std::span pixelUserData = {}) { const auto exportFormat = exported.format; const auto target = device.Target(); constexpr std::uint32_t waveSize = 64; @@ -213,7 +223,7 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge 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}, + {waveSize, 0, pixelUserData, std::nullopt, AgcDriver::Graphics::DecodePixelStageInfo(context, {}), std::nullopt, pixelMemory}, target, PixelPushLayout(vertexPush, target) }; @@ -223,7 +233,7 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge 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 ShaderRecompiler::SrtRuntime runtime{pixelUserData}; const auto materialize = [&](const ShaderRecompiler::RecompileRequest& request) { const auto invocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, request); return invocation.Materialize(*invocation.Capture(runtime)); @@ -234,12 +244,18 @@ void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarge 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 unsigned integer packing did not key its own specialization"); + if (color.packing != ShaderRecompiler::ColorExportPacking::Uint) { + auto unclamped = fragment; + unclamped.context.pixel->targetExportPacking[color.exportIndex] = ShaderRecompiler::ColorExportPacking::Uint; + Require(materialize(unclamped)->PipelineVariantId() != pixelResult->PipelineVariantId(), "the clamping unsigned integer 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); + return pixelResult->PipelineVariantId(); } std::vector ReadBack(AgcDriver::VulkanDevice& device, const Block& block) { @@ -286,24 +302,51 @@ std::array Expected(const ExportCase& exported, const std::arr return vgprs; } -void TargetTests(AgcDriver::VulkanDevice& device, const TargetFormat& target, const ExportCase& exported, std::span> values, ColorTileMode mode, const char* kind) { +std::uint32_t WidenedHalf(std::uint32_t half) { + const auto sign = (half & 0x8000u) << 16u; + const auto exponent = (half >> 10u) & 0x1fu; + auto mantissa = half & 0x3ffu; + if (exponent == 0x1fu) return sign | 0x7f800000u | (mantissa << 13u) | (mantissa != 0u ? 0x400000u : 0u); + if (exponent != 0u) return sign | ((exponent + 112u) << 23u) | (mantissa << 13u); + if (mantissa == 0u) return sign; + std::uint32_t shift = 0; + while ((mantissa & 0x400u) == 0u) { + mantissa <<= 1u; + ++shift; + } + return sign | ((113u - shift) << 23u) | ((mantissa & 0x3ffu) << 13u); +} + +std::array HalfExpected(ShaderRecompiler::ColorExportPacking packing, const std::array& vgprs) { + const auto limit = packing == ShaderRecompiler::ColorExportPacking::UintClamp8 ? 0xffu : packing == ShaderRecompiler::ColorExportPacking::UintClamp16 ? 0xffffu : 0xffffffffu; + std::array expected{}; + for (std::uint32_t channel = 0; channel < expected.size(); ++channel) expected[channel] = std::min(WidenedHalf((vgprs[channel / 2u] >> (16u * (channel % 2u))) & 0xffffu), limit); + return expected; +} + +void TargetTests(AgcDriver::VulkanDevice& device, const TargetFormat& target, const ExportCase& exported, std::span> values, ColorTileMode mode, const char* kind, ShaderRecompiler::ColorExportPacking packing) { const auto elementBytes = target.channels * target.channelBytes; const ColorTargetLayout layout(Width, Height, mode, elementBytes); Block block((layout.Bytes() + BlockBytes - 1) / BlockBytes * BlockBytes); - const auto color = DecodeTarget(block, layout.Bytes(), target, mode == ColorTileMode::Linear ? LinearAttrib3 : TiledAttrib3); + const auto color = DecodeTarget(block, layout.Bytes(), target, mode == ColorTileMode::Linear ? LinearAttrib3 : TiledAttrib3, packing); + const bool halves = exported.format == ExportFp16ABGR.format; auto before = Fill(block); + std::uint64_t variant = 0; for (const auto& vgprs : values) { - Draw(device, color, exported, WordProgram(vgprs)); - char what[160]; - std::snprintf(what, sizeof(what), "a %s export of 0x%08x 0x%08x 0x%08x 0x%08x into a %s %s target", exported.name, vgprs[0], vgprs[1], vgprs[2], vgprs[3], kind, target.name); - ExpectSurface(device, block, target, layout, before, exported, Expected(exported, vgprs), what); + const std::array userData{vgprs[0], vgprs[1]}; + const auto drawn = halves ? Draw(device, color, exported, UserDataHalvesProgram, userData) : Draw(device, color, exported, WordProgram(vgprs)); + Require(!halves || variant == 0 || drawn == variant, "the exported halves were folded into the pixel shader instead of read at run time"); + variant = drawn; + char what[192]; + std::snprintf(what, sizeof(what), "a %s export of 0x%08x 0x%08x 0x%08x 0x%08x into a %s %s target with packing %u", exported.name, vgprs[0], vgprs[1], vgprs[2], vgprs[3], kind, target.name, static_cast(packing)); + ExpectSurface(device, block, target, layout, before, exported, halves ? HalfExpected(packing, vgprs) : Expected(exported, vgprs), what); before = ReadBack(device, block); } } -void FormatTests(AgcDriver::VulkanDevice& device, const TargetFormat& target, const ExportCase& exported, std::span> values) { - TargetTests(device, target, exported, values, ColorTileMode::RenderTarget, "tiled"); - TargetTests(device, target, exported, values, ColorTileMode::Linear, "linear"); +void FormatTests(AgcDriver::VulkanDevice& device, const TargetFormat& target, const ExportCase& exported, std::span> values, ShaderRecompiler::ColorExportPacking packing = ShaderRecompiler::ColorExportPacking::Uint) { + TargetTests(device, target, exported, values, ColorTileMode::RenderTarget, "tiled", packing); + TargetTests(device, target, exported, values, ColorTileMode::Linear, "linear", packing); } } @@ -323,6 +366,21 @@ int main() { FormatTests(*device, R32G32B32A32, Export32ABGR, wide); const std::vector> narrow{{0u, 0x11111111u, 0x22222222u, 0x33333333u}, {1u, 0u, 0u, 0u}, {0x7fu, 0u, 0u, 0u}, {0xa5u, 0u, 0u, 0u}, {0xffu, 0u, 0u, 0u}}; FormatTests(*device, R8, Export32R, narrow); + constexpr std::array halves{{0x0000u, 0x8000u, 0x3c00u, 0xbc00u, 0x3800u, 0x0001u, 0x83ffu, 0x03ffu, 0x7bffu, 0x7c00u, 0xfc00u, 0x7c01u, 0xfe00u}}; + std::vector> packed; + for (std::size_t index = 0; index < halves.size(); ++index) { + const auto half = [&](std::size_t offset) { return halves[(index + offset) % halves.size()]; }; + packed.push_back({half(0) | (half(3) << 16u), half(5) | (half(7) << 16u), 0u, 0u}); + } + using ShaderRecompiler::ColorExportPacking; + FormatTests(*device, R32, ExportFp16ABGR, packed); + FormatTests(*device, R32, ExportFp16ABGR, packed, ColorExportPacking::UintClamp8); + FormatTests(*device, R32, ExportFp16ABGR, packed, ColorExportPacking::UintClamp16); + FormatTests(*device, R32G32B32A32, ExportFp16ABGR, packed); + FormatTests(*device, R16, ExportFp16ABGR, packed, ColorExportPacking::UintClamp16); + FormatTests(*device, R16G16B16A16, ExportFp16ABGR, packed, ColorExportPacking::UintClamp16); + FormatTests(*device, R8, ExportFp16ABGR, packed, ColorExportPacking::UintClamp8); + FormatTests(*device, R8G8B8A8, ExportFp16ABGR, packed, ColorExportPacking::UintClamp8); std::puts("UINT color target tests passed"); return 0; } catch (const std::exception& error) { diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/UnclampedNormalizedBlend.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/UnclampedNormalizedBlend.cpp index a8c47fc2c4..ddcb97536c 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/UnclampedNormalizedBlend.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/UnclampedNormalizedBlend.cpp @@ -5,6 +5,7 @@ #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/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/State.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libc/include/GuestAllocations.hpp" @@ -30,12 +31,22 @@ #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::ColorTargetLayout; @@ -124,6 +135,7 @@ struct DrawSetup { std::uint64_t depth = 0; bool depthWrite = false; bool withoutInterlock = false; + const AgcDriver::Graphics::Context* context = nullptr; }; struct Block { @@ -301,7 +313,8 @@ void Draw(AgcDriver::VulkanDevice& device, std::span targets, std: {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); + if (setup.context != nullptr) AgcDriver::Graphics::Draw(*setup.context, state, draw, shaders); + else device.Draw(state, draw, shaders); } void Draw(AgcDriver::VulkanDevice& device, const AgcDriver::Graphics::ColorTarget& color, const BlendCase& blendCase, std::uint32_t exportFormat, std::span pixelCode, const DrawSetup& setup = {}) { @@ -463,11 +476,44 @@ void RunCase(AgcDriver::VulkanDevice& device, Block& block, const ColorTargetLay Require(outcome.distinguished == testCase.distinguishesClamping, std::string("unclamped blend ") + testCase.name + (testCase.distinguishesClamping ? " does not differ from Vulkan's clamped blend, so it cannot catch a clamping approximation" : " differs from Vulkan's clamped blend, which the case declares equal")); } -void RunTwoTargets(AgcDriver::VulkanDevice& device, const std::string& name, std::array blocks, std::array layouts, std::array colors, std::array cases, std::uint32_t seed) { +struct RecoveryAtImageCreation { + static AgcDriver::Graphics::Context Install(const AgcDriver::Graphics::Context& base) { + resolver = base.deviceProc; + auto context = base; + context.deviceProc = resolve; + context.functions = nullptr; + return context; + } + + static inline bool armed = false; + static inline unsigned recoveries = 0; + static inline std::uint64_t storageBytes = 0; + +private: + static PFN_vkVoidFunction VKAPI_CALL resolve(VkDevice device, const char* name) { + if (std::string_view(name) == "vkCreateImage") return reinterpret_cast(createImage); + return resolver(device, name); + } + static VKAPI_ATTR VkResult VKAPI_CALL createImage(VkDevice device, const VkImageCreateInfo* info, const VkAllocationCallbacks* callbacks, VkImage* image) { + if (armed) { + armed = false; + ++recoveries; + const auto before = AgcDriver::Graphics::TextureCacheUsage().storageBytes; + static_cast(AgcDriver::Graphics::ReleaseTextureCaches(std::numeric_limits::max(), true)); + storageBytes = before - AgcDriver::Graphics::TextureCacheUsage().storageBytes; + } + return reinterpret_cast(resolver(device, "vkCreateImage"))(device, info, callbacks, image); + } + + static inline PFN_vkGetDeviceProcAddr resolver = nullptr; +}; + +void RunTwoTargets(AgcDriver::VulkanDevice& device, const std::string& name, std::array blocks, std::array layouts, std::array colors, std::array cases, std::uint32_t seed, const DrawSetup& setup = {}, const std::function& beforeDraw = {}) { std::array, 2> before; for (std::size_t index = 0; index < 2u; ++index) before[index] = Fill(*blocks[index], seed + static_cast(index)); + if (beforeDraw) beforeDraw(); const std::array targets{{{colors[0], cases[0].blend}, {colors[1], cases[1].blend}}}; - Draw(device, targets, Float32Export | (Float32Export << 4u), TwoTargetProgram(cases[0].rgba, cases[1].rgba)); + Draw(device, targets, Float32Export | (Float32Export << 4u), TwoTargetProgram(cases[0].rgba, cases[1].rgba), setup); for (std::size_t index = 0; index < 2u; ++index) { const auto& testCase = cases[index]; const auto what = name + ", MRT" + std::to_string(index) + " " + testCase.name; @@ -569,7 +615,18 @@ int main() { {{{"with an out-of-range source and alpha", over, {0.75f, -0.5f, 2.0f, 1.5f}, true}, {"with an out-of-range source", over, {1.5f, 0.25f, -1.0f, 0.5f}, true}}}, 501u); RunTwoTargets(*device, "an 8_8_8_8 UNORM MRT0 and an 8_8 UNORM MRT1 with different blend equations in one draw", {&block, &secondPair}, {&layout, &pairLayout}, {unorm, secondTarget}, {{unormCases[3], unormCases[4]}}, 503u); - std::printf("unclamped normalized color blend tests passed: %zu UNORM draws, %zu SNORM draws, 2 same-draw overdraws of 16 layers, 2 draws blending two targets each, 1 refusal without pixel interlock\n", unormCases.size(), snormCases.size()); + Block thirdPair(pairLayout.Bytes()); + Block fourthPair(pairLayout.Bytes()); + const auto thirdTarget = DecodeTarget(thirdPair, Unorm88Info, 0); + const auto fourthTarget = DecodeTarget(fourthPair, Unorm88Info, 1); + const auto recovering = RecoveryAtImageCreation::Install(AgcDriver::VulkanDeviceTestAccess::ContextOf(*device)); + RunTwoTargets(*device, "two 8_8 UNORM targets in one draw whose MRT1 image creation releases the texture caches as an out-of-memory allocation does, evicting MRT0's resident image", {&thirdPair, &fourthPair}, {&pairLayout, &pairLayout}, {thirdTarget, fourthTarget}, + {{{"with an out-of-range source and alpha", over, {0.75f, -0.5f, 2.0f, 1.5f}, true}, {"with an out-of-range source", over, {1.5f, 0.25f, -1.0f, 0.5f}, true}}}, 505u, {.context = &recovering}, [&] { + static_cast(AgcDriver::Graphics::CachedStorageSurface(recovering, AgcDriver::Graphics::SurfaceForTarget(thirdTarget))); + RecoveryAtImageCreation::armed = true; + }); + Require(RecoveryAtImageCreation::recoveries == 1u && RecoveryAtImageCreation::storageBytes != 0u, "the release at MRT1's image creation did not evict MRT0's resident image"); + std::printf("unclamped normalized color blend tests passed: %zu UNORM draws, %zu SNORM draws, 2 same-draw overdraws of 16 layers, 3 draws blending two targets each (1 with a texture cache release at MRT1's image creation), 1 refusal without pixel interlock\n", unormCases.size(), snormCases.size()); return 0; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/UserDataLinkReuse.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/UserDataLinkReuse.cpp new file mode 100644 index 0000000000..8147e551dc --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/UserDataLinkReuse.cpp @@ -0,0 +1,132 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "ControlFlow/GraphBuilder.hpp" +#include "Optimization/ResourceProgram.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; + +constexpr std::uint32_t Lanes = 32; +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Untouched = 0xcafebabeu; + +alignas(256) constexpr std::array Caller{ + 0x7e020280u, 0xbe940383u, 0xbe8e0300u, 0xbe8f0301u, 0xbe8e210eu, 0xbe8e0302u, 0xbe8f0303u, 0xbe8e210eu, + 0x80948114u, 0xbf078014u, 0xbf85fff7u, 0x34040082u, 0xe0701000u, 0x80010102u, 0xbf810000u, 0xbf9f0000u, + 0x30306c73u, 0x00000040u, 0x00000008u}; +alignas(256) std::array First{}; +alignas(256) std::array Second{}; +alignas(256) std::array Output{}; + +std::array AddInline(std::uint32_t value) { + return {0x4a020280u + value, 0xbe80200eu}; +} + +std::array UserData(const void* first, const void* second) { + const auto low = reinterpret_cast(first); + const auto high = reinterpret_cast(second); + const auto output = reinterpret_cast(Output.data()); + return {static_cast(low), static_cast(low >> 32u), static_cast(high), static_cast(high >> 32u), + static_cast(output), static_cast((output >> 32u) & 0xffffu), static_cast(sizeof(Output)), 0x31016facu}; +} + +struct Fixture { + AgcDriver::VulkanDevice& device; + AgcDriver::DriverDetail::ShaderSnapshot snapshot; + std::array users{}; + ShaderRecompiler::RecompileRequest request; + + explicit Fixture(AgcDriver::VulkanDevice& device) : device(device), snapshot{reinterpret_cast(Caller.data()), 0, 0, {Caller.begin(), Caller.end()}, {}} { + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + request = {{ShaderRecompiler::ShaderStage::Compute, snapshot.codeAddress, snapshot.code, 0, {}}, {Lanes, 0, users, compute, std::nullopt, std::nullopt, {}}, device.ComputeTarget(Lanes), {0, 0, 0, 128}}; + request.useCache = false; + } + + std::vector Links() const { + std::lock_guard lock(snapshot.prepared->mutex); + return snapshot.prepared->userDataLinks; + } + + void Check(const void* first, const void* second, std::uint32_t expected, const std::string& what) { + users = UserData(first, second); + request.context.userData = users; + const auto invocation = AgcDriver::DriverDetail::InvocationFor(snapshot, 0, request); + ShaderRecompiler::SrtRuntime runtime{}; + runtime.userData = users; + const auto capture = invocation.Capture(runtime); + const auto result = invocation.Materialize(*capture); + Output.fill(Untouched); + device.Dispatch(*result, 1, 1, 1); + device.WaitIdle(); + for (std::uint32_t lane = 0; lane < Threads; ++lane) { + Require(Output[lane] == expected, what + ": lane " + std::to_string(lane) + " is " + std::to_string(Output[lane]) + ", expected " + std::to_string(expected)); + } + } +}; + +void Run(AgcDriver::VulkanDevice& device) { + Fixture fixture(device); + First = AddInline(5u); + Second = AddInline(64u); + fixture.Check(First.data(), Second.data(), 3u * (5u + 64u), "first link"); + auto links = fixture.Links(); + Require(links.size() == 1u && links.front().linked != nullptr && links.front().callees.size() == 2u, "the first invocation did not keep exactly one user-data link with both callees"); + const auto firstLink = links.front().linked; + Require(!firstLink->code.empty(), "the kept user-data link has no linked code"); + + fixture.Check(First.data(), Second.data(), 3u * (5u + 64u), "same callees again"); + links = fixture.Links(); + Require(links.size() == 1u && links.front().linked == firstLink, "an invocation with unchanged callees linked the program again instead of reusing its link"); + + First = AddInline(1u); + fixture.Check(First.data(), Second.data(), 3u * (1u + 64u), "first callee rewritten in place"); + links = fixture.Links(); + Require(links.size() == 2u && links.back().linked != firstLink && links.back().callees == links.front().callees, "a callee rewritten at the same address did not get a new link"); + const auto rewrittenLink = links.back().linked; + Require(!std::ranges::equal(rewrittenLink->code, firstLink->code), "the link of a rewritten callee kept the old callee's code"); + + First = AddInline(5u); + fixture.Check(First.data(), Second.data(), 3u * (5u + 64u), "first callee restored"); + links = fixture.Links(); + Require(links.size() == 2u, "restoring a callee's earlier code linked the program again instead of reusing the earlier link"); + + fixture.Check(Second.data(), Second.data(), 3u * (64u + 64u), "both pointers at the second callee"); + links = fixture.Links(); + Require(links.size() == 3u && links.back().callees.front() == links.back().callees.back(), "callees moved to another address did not get a new link"); + + for (std::uint32_t value = 10; value < 20; ++value) { + First = AddInline(value); + fixture.Check(First.data(), Second.data(), 3u * (value + 64u), "first callee rewritten to add " + std::to_string(value)); + } + links = fixture.Links(); + Require(links.size() == 8u && links.back().code.front() == std::vector(First.begin(), First.end()), "the user-data links of one program are not limited to the eight most recent"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + std::puts("user-data link reuse 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/WriteDataWrittenAhead.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/WriteDataWrittenAhead.cpp index 529aad4163..67d2f784ae 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/WriteDataWrittenAhead.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/WriteDataWrittenAhead.cpp @@ -1,27 +1,129 @@ #include "VulkanTestDevice.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver/Memory/BufferCopy.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" #include "prx/libSceAgcDriver/Submit/include/Acb.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestWriteWatch.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#endif #include #include +#include +#include #include #include #include #include #include +namespace AgcDriver::DriverDetail { +struct DriverTestAccess { + static void PrepareSamplingForRejectedAhead() { + auto& driver = Driver::Get(); + driver.evidenceValidations.store(1u, std::memory_order_relaxed); + driver.evidenceReads.store(15u, std::memory_order_relaxed); + } +}; +} + namespace { void Require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } +constexpr std::size_t BlockBytes = 65536; + +struct GuestBlock { + explicit GuestBlock(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"); + GuestAllocations::Mutation mutation; + mutation.Add(data, bytes, true, true, true); + } + ~GuestBlock() { + { + 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 + } + GuestBlock(const GuestBlock&) = delete; + GuestBlock& operator=(const GuestBlock&) = delete; + std::uint32_t* Dwords(std::size_t byteOffset) const { return reinterpret_cast(data + byteOffset); } + std::uint8_t* data = nullptr; + std::size_t bytes = 0; + bool watched = false; +}; + alignas(256) std::array CounterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000281u, 0xe0c80000u, 0x80020000u, 0xbf810000u}; +alignas(256) std::array WriterCode{0xf4080200u, 0xfa000000u, 0xbf8cc07fu, 0x7e000202u, 0x7e020203u, 0x7e040204u, 0xe07c0000u, 0x80020000u, 0xbf810000u}; +alignas(256) std::array ImageWriterCode{0x7e000280u, 0x7e020280u, 0x7e040208u, 0x7e060209u, 0x7e08020au, 0x7e0a020bu, 0xf0201f08u, 0x00000200u, 0xbf810000u}; +alignas(256) std::array GdsWriterCode{0x7e000280u, 0xd8360000u, 0x00000000u, 0xbf810000u}; +alignas(256) std::array StreamCopyCode{0xd7460008u, 0x04010e08u, 0x8f6a8708u, 0xd76d0009u, 0x0401806au, 0xe00c2000u, 0x80000008u, 0xe00c2000u, 0x80000409u, 0xbf8c3f71u, 0xe01c2000u, 0x80010008u, 0xbf8c3f70u, 0xe01c2000u, 0x80010409u, 0xbf810000u}; +alignas(256) std::array FillKernelCode{0xd7460004u, 0x04010c08u, 0x7e000204u, 0x7e020205u, 0x7e040206u, 0x7e060207u, 0xe01c2000u, 0x80000004u, 0xbf810000u}; +alignas(256) std::array DwordPatternFillCode{ + 0xbfa00003u, 0xd7460002u, 0x04010c0au, 0x7da80408u, 0xbf88003fu, 0x7e000c09u, 0xbf070980u, 0x858a807eu, + 0x7e005700u, 0x100000ffu, 0x4f800000u, 0x7e060f00u, 0xd5766a00u, 0x02020609u, 0x7d8a0280u, 0x4c020080u, + 0x02000101u, 0xd56a0001u, 0x00020700u, 0x4c000303u, 0x4a020303u, 0x02000101u, 0xd56a0000u, 0x00020500u, + 0xd5690001u, 0x00020009u, 0x4c060302u, 0x7d860609u, 0x7d8c02f9u, 0x06068c02u, 0x87ea6a0cu, 0x50000080u, + 0xd5286a00u, 0x003200c1u, 0xd5010000u, 0x002a00c1u, 0xd5690000u, 0x00020009u, 0x4c000102u, 0x7d0a0080u, + 0xbe88246au, 0xbf880015u, 0x7d0a0081u, 0xbe8a246au, 0xbf88000cu, 0x7d0a0082u, 0xbeea246au, 0xbf880003u, + 0x7e000207u, 0xe0102000u, 0x80000002u, 0x8afe7e6au, 0xbf880003u, 0x7e000206u, 0xe0102000u, 0x80000002u, + 0xbefe046au, 0x8afe7e0au, 0xbf880003u, 0x7e000205u, 0xe0102000u, 0x80000002u, 0xbefe040au, 0x8afe7e08u, + 0xbf880003u, 0x7e000204u, 0xe0102000u, 0x80000002u, 0xbf810000u}; alignas(256) std::array First{}; alignas(256) std::array Stale{}; alignas(256) std::array Fresh{}; +alignas(256) std::array Arguments{1, 1, 1, 0}; alignas(256) std::array FirstDescriptor{}; alignas(256) std::array SecondDescriptor{}; +alignas(256) std::array CopySource{}; +alignas(256) std::array ArgumentsDescriptor{}; alignas(256) volatile std::uint32_t Gate = 1; alignas(256) volatile std::uint32_t Done = 0; @@ -41,22 +143,113 @@ void SetShaderRegister(std::vector& words, std::uint32_t offset, words.insert(words.end(), {0xc0017600u, offset, value}); } -void Program(std::vector& words, const void* code, std::initializer_list userData) { +void Program(std::vector& words, const void* code, std::initializer_list userData, std::uint32_t threadsX = 1, bool groupIdX = false) { 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, 0x207, threadsX); + SetShaderRegister(words, 0x208, 1); + SetShaderRegister(words, 0x209, 1); SetShaderRegister(words, 0x212, 0); - SetShaderRegister(words, 0x213, static_cast(userData.size()) << 1u); + SetShaderRegister(words, 0x213, (static_cast(userData.size()) << 1u) | (groupIdX ? (1u << 7u) : 0u)); std::uint32_t offset = 0x240; for (const auto value : userData) SetShaderRegister(words, offset++, value); } -void Count(std::vector& words, const void* descriptor) { - Program(words, CounterCode.data(), {Low(descriptor), High(descriptor)}); +void Count(std::vector& words, const void* descriptor, std::uint32_t threadsX = 1) { + Program(words, CounterCode.data(), {Low(descriptor), High(descriptor)}, threadsX); words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); } +void CountIndirect(std::vector& words, const void* descriptor, const void* arguments) { + Program(words, CounterCode.data(), {Low(descriptor), High(descriptor)}); + words.insert(words.end(), {0xc0021600u, Low(arguments), High(arguments), 0x8041u}); +} + +void WriteThreeDwords(std::vector& words, const void* targetDescriptor, std::uint32_t d0, std::uint32_t d1, std::uint32_t d2) { + Program(words, WriterCode.data(), {Low(targetDescriptor), High(targetDescriptor), d0, d1, d2}); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); +} + +void ImageWriteDescriptor(std::vector& words, const void* destination, const std::array& pattern) { + const auto address = static_cast(reinterpret_cast(destination)); + constexpr std::uint32_t format = 75u; + constexpr std::uint32_t width = 16u; + constexpr std::uint32_t height = 1u; + constexpr std::uint32_t type2d = 9u; + Program(words, ImageWriterCode.data(), { + static_cast(address >> 8u), + (static_cast(address >> 40u) & 0xffu) | (format << 20u) | (((width - 1u) & 3u) << 30u), + ((width - 1u) >> 2u) | ((height - 1u) << 14u), + 0xfacu | (type2d << 28u), + 0u, 0u, 0u, 0u, + pattern[0], pattern[1], pattern[2], pattern[3] + }); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); +} + +void GdsWrite(std::vector& words) { + Program(words, GdsWriterCode.data(), {}); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); +} + +void StreamCopyDescriptor(std::vector& words, const void* source, const void* destination) { + constexpr std::uint32_t format = 0x0004bfacu; + Program(words, StreamCopyCode.data(), { + Low(source), (High(source) & 0xffffu) | (16u << 16u), 1u, format, + Low(destination), (High(destination) & 0xffffu) | (16u << 16u), 1u, format + }, 64, true); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); +} + +void FillKernelDescriptor(std::vector& words, const void* destination, const std::array& pattern) { + constexpr std::uint32_t format = 0x0004bfacu; + Program(words, FillKernelCode.data(), { + Low(destination), (High(destination) & 0xffffu) | (16u << 16u), 1u, format, + pattern[0], pattern[1], pattern[2], pattern[3] + }, 64, true); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); +} + +void DwordPatternFillDescriptor(std::vector& words, const void* destination, const std::array& pattern) { + constexpr std::uint32_t format = 0x00014facu; + Program(words, DwordPatternFillCode.data(), { + Low(destination), (High(destination) & 0xffffu) | (4u << 16u), 4u, format, + pattern[0], pattern[1], pattern[2], pattern[3], 4u, 4u + }, 64, true); + words.insert(words.end(), {0xc0031500u, 1, 1, 1, 0x8041u}); +} + +template +void RunUpdateAheadCase(const std::string& label, std::uint32_t* targetDescriptor, const std::array& stale, EmitUpdate&& emitUpdate, std::uint64_t expectedAdopted = 4, bool exactAdopted = false) { + First[0] = 0; + Stale[0] = 0; + Fresh[0] = 0; + Done = 0; + std::memcpy(targetDescriptor, stale.data(), sizeof(stale)); + const auto adoptedBefore = AgcDriverDispatchesAdoptedAhead_nid_postfix(); + std::vector words; + words.insert(words.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u}); + Count(words, FirstDescriptor.data()); + WriteThreeDwords(words, ArgumentsDescriptor.data(), 1u, 1u, 1u); + emitUpdate(words); + Count(words, targetDescriptor); + CountIndirect(words, targetDescriptor, Arguments.data()); + words.insert(words.end(), {0xc0033700u, 0x00100200u, Low(&Done), High(&Done), 1u}); + Packet packet{words.data(), static_cast(words.size()), 0, {}}; + Require(sceAgcDriverSubmitAcb(0x20, &packet) == 0, label + ": compute submit failed"); + AgcDriverWaitIdle_nid_postfix(); + Require(Done == 1, label + ": submission did not finish"); + Require(First[0] == 1, label + ": first dispatch did not run"); + Require(Fresh[0] == 2 && Stale[0] == 0, label + ": dispatches after descriptor update used stale descriptor (stale " + std::to_string(Stale[0]) + ", fresh " + std::to_string(Fresh[0]) + ")"); + const auto adopted = AgcDriverDispatchesAdoptedAhead_nid_postfix() - adoptedBefore; + if (exactAdopted) { + Require(adopted == expectedAdopted, label + ": expected exactly " + std::to_string(expectedAdopted) + " dispatches adopted ahead, got " + std::to_string(adopted)); + } else { + Require(adopted >= expectedAdopted, label + ": expected at least " + std::to_string(expectedAdopted) + " dispatches adopted ahead across pre-update and reopened post-update gates, got " + std::to_string(adopted)); + } +} + } int main() { @@ -64,19 +257,72 @@ int main() { auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; device.reset(); + GuestBlock imported(BlockBytes); + std::uint32_t* importedSecondDescriptor = imported.Dwords(0); + std::uint32_t* importedCopySource = imported.Dwords(256); + std::uint32_t* importedCopySourceDescriptor = imported.Dwords(512); + std::uint32_t* importedPolluteDescriptor = imported.Dwords(768); + std::uint32_t* importedPolluteMetaDescriptor = imported.Dwords(1024); + const auto stale = Descriptor(Stale.data(), sizeof(Stale)); + const auto fresh = Descriptor(Fresh.data(), sizeof(Fresh)); FirstDescriptor = Descriptor(First.data(), sizeof(First)); - SecondDescriptor = Descriptor(Stale.data(), sizeof(Stale)); + SecondDescriptor = stale; + std::memcpy(importedSecondDescriptor, stale.data(), sizeof(stale)); + const auto copySrcDesc = Descriptor(importedCopySource, 16u); + std::memcpy(importedCopySourceDescriptor, copySrcDesc.data(), sizeof(copySrcDesc)); + std::memcpy(importedPolluteDescriptor, stale.data(), sizeof(stale)); + const auto polluteMetaDesc = Descriptor(importedPolluteDescriptor, 16u); + std::memcpy(importedPolluteMetaDescriptor, polluteMetaDesc.data(), sizeof(polluteMetaDesc)); + ArgumentsDescriptor = Descriptor(Arguments.data(), sizeof(Arguments)); std::vector warmUp; Count(warmUp, FirstDescriptor.data()); Count(warmUp, SecondDescriptor.data()); + CountIndirect(warmUp, SecondDescriptor.data(), Arguments.data()); + Count(warmUp, importedSecondDescriptor); + CountIndirect(warmUp, importedSecondDescriptor, Arguments.data()); + WriteThreeDwords(warmUp, ArgumentsDescriptor.data(), 1u, 1u, 1u); + WriteThreeDwords(warmUp, importedCopySourceDescriptor, fresh[0], fresh[1], fresh[2]); + Count(warmUp, importedPolluteDescriptor, 2); + for (int i = 0; i < 4; ++i) { + WriteThreeDwords(warmUp, importedPolluteMetaDescriptor, stale[0], stale[1], stale[2]); + Count(warmUp, importedPolluteDescriptor, 2); + } + WriteThreeDwords(warmUp, importedPolluteMetaDescriptor, fresh[0], fresh[1], fresh[2]); + ImageWriteDescriptor(warmUp, importedSecondDescriptor, stale); + GdsWrite(warmUp); warmUp.insert(warmUp.end(), {0xc0033700u, 0x00100200u, Low(&Done), High(&Done), 2u}); Packet first{warmUp.data(), static_cast(warmUp.size()), 0, {}}; Require(sceAgcDriverSubmitAcb(0x20, &first) == 0, "warm-up submit failed"); AgcDriverWaitIdle_nid_postfix(); - Require(Done == 2 && First[0] == 1 && Stale[0] == 1, "the warm-up dispatches did not run"); + Require(Done == 2 && First[0] == 1 && Stale[0] == 14, "the warm-up dispatches did not run"); First[0] = 0; Stale[0] = 0; - const auto fresh = Descriptor(Fresh.data(), sizeof(Fresh)); + Fresh[0] = 0; + { + Done = 0; + std::memcpy(importedPolluteDescriptor, stale.data(), sizeof(stale)); + AgcDriver::DriverDetail::DriverTestAccess::PrepareSamplingForRejectedAhead(); + const auto countersBefore = AgcDriver::DriverDetail::Driver::Get().DispatchCacheCounters(); + const auto adoptedBefore = AgcDriverDispatchesAdoptedAhead_nid_postfix(); + std::vector polluteWords; + polluteWords.insert(polluteWords.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u}); + Count(polluteWords, FirstDescriptor.data()); + WriteThreeDwords(polluteWords, importedPolluteMetaDescriptor, fresh[0], fresh[1], fresh[2]); + Count(polluteWords, importedPolluteDescriptor, 3); + polluteWords.insert(polluteWords.end(), {0xc0033700u, 0x00100200u, Low(&Done), High(&Done), 1u}); + Packet pollutePacket{polluteWords.data(), static_cast(polluteWords.size()), 0, {}}; + Require(sceAgcDriverSubmitAcb(0x20, &pollutePacket) == 0, "rejected ahead cache pollution submit failed"); + AgcDriverWaitIdle_nid_postfix(); + const auto adopted = AgcDriverDispatchesAdoptedAhead_nid_postfix() - adoptedBefore; + const auto countersAfter = AgcDriver::DriverDetail::Driver::Get().DispatchCacheCounters(); + Require(Done == 1 && First[0] == 1, "rejected ahead cache pollution submission did not finish"); + Require(adopted == 2, "rejected ahead capture was unexpectedly adopted (adopted " + std::to_string(adopted) + ")"); + Require(Fresh[0] == 3 && Stale[0] == 0, "normal dispatch after rejected ahead capture received the rejected stale variant from global dispatchCache (stale " + std::to_string(Stale[0]) + ", fresh " + std::to_string(Fresh[0]) + ")"); + Require(countersAfter.inserts - countersBefore.inserts == 1, "rejected ahead capture polluted global dispatchCache with an extra insert (inserts delta " + std::to_string(countersAfter.inserts - countersBefore.inserts) + ")"); + } + First[0] = 0; + Stale[0] = 0; + Fresh[0] = 0; std::vector words; words.insert(words.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u}); Count(words, FirstDescriptor.data()); @@ -90,6 +336,62 @@ int main() { Require(SecondDescriptor[0] == fresh[0] && SecondDescriptor[1] == fresh[1], "WRITE_DATA did not replace the descriptor"); Require(First[0] == 1, "the first dispatch did not run"); Require(Fresh[0] == 1 && Stale[0] == 0, "a dispatch whose descriptor a WRITE_DATA before it replaces used the old descriptor (stale " + std::to_string(Stale[0]) + ", fresh " + std::to_string(Fresh[0]) + ")"); + + CopySource = fresh; + RunUpdateAheadCase("HLE StreamCopy (CPU-backed)", SecondDescriptor.data(), stale, [&](std::vector& cmd) { + StreamCopyDescriptor(cmd, CopySource.data(), SecondDescriptor.data()); + }); + + const auto gpuCopiesBefore = AgcDriver::DriverDetail::copyPathCounts[AgcDriver::DriverDetail::CopyGpu].load(); + RunUpdateAheadCase("HLE StreamCopy (GPU-backed SourcePending)", importedSecondDescriptor, stale, [&](std::vector& cmd) { + const std::array initSrc{0u, 0u, fresh[2], fresh[3]}; + std::memcpy(importedCopySource, initSrc.data(), sizeof(initSrc)); + WriteThreeDwords(cmd, importedCopySourceDescriptor, fresh[0], fresh[1], fresh[2]); + StreamCopyDescriptor(cmd, importedCopySource, importedSecondDescriptor); + }, 4); + Require(AgcDriver::DriverDetail::copyPathCounts[AgcDriver::DriverDetail::CopyGpu].load() > gpuCopiesBefore, "GPU-backed StreamCopy did not take CopyGpu path"); + + RunUpdateAheadCase("HLE FillKernel (CPU-backed)", SecondDescriptor.data(), stale, [&](std::vector& cmd) { + FillKernelDescriptor(cmd, SecondDescriptor.data(), fresh); + }); + + RunUpdateAheadCase("HLE FillKernel (GPU-backed)", importedSecondDescriptor, stale, [&](std::vector& cmd) { + FillKernelDescriptor(cmd, importedSecondDescriptor, fresh); + }); + + RunUpdateAheadCase("HLE DwordPatternFill (CPU-backed)", SecondDescriptor.data(), stale, [&](std::vector& cmd) { + DwordPatternFillDescriptor(cmd, SecondDescriptor.data(), fresh); + }); + + RunUpdateAheadCase("HLE DwordPatternFill (GPU-backed)", importedSecondDescriptor, stale, [&](std::vector& cmd) { + DwordPatternFillDescriptor(cmd, importedSecondDescriptor, fresh); + }); + + RunUpdateAheadCase("StorageImage write overlap", importedSecondDescriptor, stale, [&](std::vector& cmd) { + ImageWriteDescriptor(cmd, importedSecondDescriptor, fresh); + }, 3, true); + + { + First[0] = 0; + Stale[0] = 0; + Fresh[0] = 0; + Done = 0; + SecondDescriptor = fresh; + const auto adoptedBefore = AgcDriverDispatchesAdoptedAhead_nid_postfix(); + std::vector gdsWords; + gdsWords.insert(gdsWords.end(), {0xc0053c00u, 0x13u, Low(&Gate), High(&Gate), 1u, 0xffffffffu, 0x19u}); + Count(gdsWords, FirstDescriptor.data()); + GdsWrite(gdsWords); + Count(gdsWords, SecondDescriptor.data()); + gdsWords.insert(gdsWords.end(), {0xc0033700u, 0x00100200u, Low(&Done), High(&Done), 1u}); + Packet gdsPacket{gdsWords.data(), static_cast(gdsWords.size()), 0, {}}; + Require(sceAgcDriverSubmitAcb(0x20, &gdsPacket) == 0, "GDS unbounded write submit failed"); + AgcDriverWaitIdle_nid_postfix(); + const auto adopted = AgcDriverDispatchesAdoptedAhead_nid_postfix() - adoptedBefore; + Require(Done == 1 && First[0] == 1 && Fresh[0] == 1, "GDS unbounded write submission did not run"); + Require(adopted == 2, "unbounded GDS write did not stop resolveGroupAhead (expected 2 adopted ahead, got " + std::to_string(adopted) + ")"); + } + std::cout << "write data written ahead test passed\n"; AgcDriverShutdown_nid_postfix(); return 0; diff --git a/core/libs/prx/libSceAgcDriver/tools/OracleCodeObject.cpp b/core/libs/prx/libSceAgcDriver/tools/OracleCodeObject.cpp new file mode 100644 index 0000000000..4eecb9c80f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tools/OracleCodeObject.cpp @@ -0,0 +1,199 @@ +#include "OracleCodeObject.hpp" +#include +#include +#include +#include + +namespace { + +constexpr std::size_t ElfHeaderBytes = 64; +constexpr std::size_t SectionHeaderBytes = 64; +constexpr std::size_t SymbolBytes = 24; +constexpr std::size_t DescriptorBytes = 64; +constexpr std::uint16_t MachineAmdGpu = 224; +constexpr std::uint16_t TypeDyn = 3; +constexpr std::uint8_t SymbolObject = 1; +constexpr std::uint8_t SymbolFunc = 2; +constexpr std::uint32_t MaxGroupBytes = 65536; + +struct Section { + std::string name; + std::uint64_t address; + std::uint64_t offset; + std::uint64_t size; + std::uint32_t link; +}; + +struct Symbol { + std::string name; + std::uint8_t type; + std::uint16_t section; + std::uint64_t value; + std::uint64_t size; +}; + +void Require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +class Reader { +public: + explicit Reader(std::span bytes) : bytes(bytes) {} + + template + T Read(std::uint64_t offset, const char* what) const { + Check(offset, sizeof(T), what); + T value; + std::memcpy(&value, bytes.data() + offset, sizeof(T)); + return value; + } + + std::span Range(std::uint64_t offset, std::uint64_t size, const char* what) const { + Check(offset, size, what); + return bytes.subspan(static_cast(offset), static_cast(size)); + } + +private: + void Check(std::uint64_t offset, std::uint64_t size, const char* what) const { + Require(offset <= bytes.size() && size <= bytes.size() - offset, std::string("code object is truncated: ") + what + " lies outside the file"); + } + + std::span bytes; +}; + +std::string NameAt(std::span table, std::uint64_t offset, const char* what) { + Require(offset < table.size(), std::string("code object ") + what + " name offset is outside its string table"); + const auto begin = reinterpret_cast(table.data()) + offset; + const auto limit = static_cast(table.size() - offset); + std::size_t length = 0; + while (length < limit && begin[length] != '\0') ++length; + Require(length < limit, std::string("code object ") + what + " name is not terminated"); + return std::string(begin, length); +} + +const Symbol& FindSymbol(const std::vector& symbols, std::string_view name) { + for (const auto& symbol : symbols) { + if (symbol.name == name) return symbol; + } + throw std::runtime_error("code object has no symbol " + std::string(name)); +} + +const Section& SectionOf(const std::vector
& sections, const Symbol& symbol, std::string_view expected) { + Require(symbol.section != 0 && symbol.section < sections.size(), "symbol " + symbol.name + " has an invalid section index"); + const auto& section = sections[symbol.section]; + Require(section.name == expected, "symbol " + symbol.name + " is in section " + section.name + ", not " + std::string(expected)); + Require(symbol.value >= section.address && symbol.value - section.address <= section.size && symbol.size <= section.size - (symbol.value - section.address), + "symbol " + symbol.name + " lies outside section " + section.name); + return section; +} + +} + +std::uint32_t ParseOracleCount(std::string_view text, const char* what) { + const auto reject = [&] { throw std::runtime_error(std::string(what) + " must be a decimal count from 0 to 4294967295, not \"" + std::string(text) + "\""); }; + if (text.empty()) reject(); + std::uint64_t value = 0; + for (const char character : text) { + if (character < '0' || character > '9') reject(); + value = value * 10u + static_cast(character - '0'); + if (value > 0xffffffffu) reject(); + } + return static_cast(value); +} + +OracleKernel ParseOracleCodeObject(std::span bytes) { + const Reader reader(bytes); + Require(bytes.size() >= ElfHeaderBytes, "code object is truncated: ELF header lies outside the file"); + Require(std::memcmp(bytes.data(), "\x7f" "ELF", 4) == 0, "code object ELF magic is wrong"); + Require(reader.Read(4, "EI_CLASS") == 2, "code object EI_CLASS is not ELF64"); + Require(reader.Read(5, "EI_DATA") == 1, "code object EI_DATA is not little-endian"); + Require(reader.Read(16, "e_type") == TypeDyn, "code object e_type is not ET_DYN"); + Require(reader.Read(18, "e_machine") == MachineAmdGpu, "code object e_machine is not EM_AMDGPU (224)"); + const auto sectionOffset = reader.Read(40, "e_shoff"); + const auto sectionEntrySize = reader.Read(58, "e_shentsize"); + const auto sectionCount = reader.Read(60, "e_shnum"); + const auto nameSection = reader.Read(62, "e_shstrndx"); + Require(sectionEntrySize == SectionHeaderBytes, "code object e_shentsize is not 64"); + Require(sectionCount != 0 && nameSection < sectionCount, "code object e_shnum or e_shstrndx is invalid"); + + struct RawSection { + std::uint32_t name; + std::uint64_t address; + std::uint64_t offset; + std::uint64_t size; + std::uint32_t link; + }; + std::vector raw; + for (std::uint16_t index = 0; index < sectionCount; ++index) { + const auto base = sectionOffset + static_cast(index) * SectionHeaderBytes; + raw.push_back({reader.Read(base, "section header"), reader.Read(base + 16, "section header"), reader.Read(base + 24, "section header"), + reader.Read(base + 32, "section header"), reader.Read(base + 40, "section header")}); + } + const auto sectionNames = reader.Range(raw[nameSection].offset, raw[nameSection].size, ".shstrtab"); + std::vector
sections; + for (const auto& item : raw) { + sections.push_back({NameAt(sectionNames, item.name, "section"), item.address, item.offset, item.size, item.link}); + } + + std::size_t symtab = sections.size(); + for (std::size_t index = 0; index < sections.size(); ++index) { + if (sections[index].name == ".symtab") symtab = index; + } + Require(symtab != sections.size(), "code object has no .symtab section"); + const auto& symbolSection = sections[symtab]; + Require(symbolSection.link < sections.size() && sections[symbolSection.link].name == ".strtab", "code object .symtab does not link to .strtab"); + Require(symbolSection.size % SymbolBytes == 0, "code object .symtab size is not a multiple of 24"); + const auto strings = reader.Range(sections[symbolSection.link].offset, sections[symbolSection.link].size, ".strtab"); + std::vector symbols; + for (std::uint64_t at = 0; at < symbolSection.size; at += SymbolBytes) { + const auto base = symbolSection.offset + at; + const auto nameOffset = reader.Read(base, "symbol"); + const auto info = reader.Read(base + 4, "symbol"); + symbols.push_back({NameAt(strings, nameOffset, "symbol"), static_cast(info & 0xfu), reader.Read(base + 6, "symbol"), + reader.Read(base + 8, "symbol"), reader.Read(base + 16, "symbol")}); + } + + const auto& probe = FindSymbol(symbols, "probe"); + const auto& descriptor = FindSymbol(symbols, "probe.kd"); + Require(probe.type == SymbolFunc, "symbol probe is not STT_FUNC"); + Require(descriptor.type == SymbolObject, "symbol probe.kd is not STT_OBJECT"); + Require(probe.size != 0, "symbol probe has size 0"); + Require(probe.size % 4 == 0, "symbol probe size is not a multiple of 4"); + Require(descriptor.size == DescriptorBytes, "symbol probe.kd size is not 64"); + const auto& textSection = SectionOf(sections, probe, ".text"); + const auto& rodataSection = SectionOf(sections, descriptor, ".rodata"); + const auto textBytes = reader.Range(textSection.offset + (probe.value - textSection.address), probe.size, "probe"); + const auto descriptorBytes = reader.Range(rodataSection.offset + (descriptor.value - rodataSection.address), descriptor.size, "probe.kd"); + + const auto field = [&](std::size_t offset, std::size_t size) { + std::uint64_t value = 0; + std::memcpy(&value, descriptorBytes.data() + offset, size); + return value; + }; + const auto group = static_cast(field(0, 4)); + const auto privateBytes = static_cast(field(4, 4)); + const auto kernargBytes = static_cast(field(8, 4)); + const auto entry = static_cast(field(16, 8)); + const auto rsrc1 = static_cast(field(48, 4)); + const auto rsrc2 = static_cast(field(52, 4)); + const auto properties = static_cast(field(56, 2)); + Require(group <= MaxGroupBytes, "kernel descriptor group_segment_fixed_size exceeds 65536"); + Require(privateBytes == 0, "kernel descriptor private_segment_fixed_size is not 0"); + Require(kernargBytes == 16, "kernel descriptor kernarg_size is not 16"); + Require(entry == static_cast(probe.value) - static_cast(descriptor.value), "kernel descriptor kernel_code_entry_byte_offset does not point at probe"); + Require((rsrc2 & 1u) == 0, "kernel descriptor compute_pgm_rsrc2 ENABLE_PRIVATE_SEGMENT is set"); + Require(((rsrc2 >> 1) & 0x1fu) == 2, "kernel descriptor compute_pgm_rsrc2 USER_SGPR_COUNT is not 2"); + Require((properties & 0x8u) != 0, "kernel descriptor kernel_code_properties ENABLE_SGPR_KERNARG_SEGMENT_PTR is not set"); + Require((properties & 0x77u) == 0, "kernel descriptor kernel_code_properties enables a user SGPR other than the kernarg pointer"); + + OracleKernel kernel{}; + kernel.text.resize(textBytes.size() / 4); + std::memcpy(kernel.text.data(), textBytes.data(), textBytes.size()); + kernel.ldsBytes = group; + kernel.waveSize = (properties & 0x400u) != 0 ? 32u : 64u; + kernel.floatMode = {(rsrc1 >> 12) & 0xffu, ((rsrc1 >> 21) & 1u) != 0, ((rsrc1 >> 23) & 1u) != 0, ((rsrc1 >> 26) & 1u) != 0}; + kernel.groupIdEnable = {((rsrc2 >> 7) & 1u) != 0, ((rsrc2 >> 8) & 1u) != 0, ((rsrc2 >> 9) & 1u) != 0}; + kernel.tgSizeEnable = ((rsrc2 >> 10) & 1u) != 0; + kernel.threadIdComponentCount = ((rsrc2 >> 11) & 3u) + 1u; + return kernel; +} diff --git a/core/libs/prx/libSceAgcDriver/tools/OracleCodeObject.hpp b/core/libs/prx/libSceAgcDriver/tools/OracleCodeObject.hpp new file mode 100644 index 0000000000..dbff31fd03 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tools/OracleCodeObject.hpp @@ -0,0 +1,26 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TOOLS_ORACLECODEOBJECT_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TOOLS_ORACLECODEOBJECT_HPP + +#include "Recompiler.hpp" +#include +#include +#include +#include +#include +#include + +struct OracleKernel { + std::vector text; + std::uint32_t ldsBytes; + std::uint32_t waveSize; + ShaderRecompiler::ShaderFloatMode floatMode; + std::array groupIdEnable; + bool tgSizeEnable; + std::uint32_t threadIdComponentCount; +}; + +OracleKernel ParseOracleCodeObject(std::span bytes); + +std::uint32_t ParseOracleCount(std::string_view text, const char* what); + +#endif diff --git a/core/libs/prx/libSceAgcDriver/tools/OracleDispatch.cpp b/core/libs/prx/libSceAgcDriver/tools/OracleDispatch.cpp new file mode 100644 index 0000000000..29b8e0577c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tools/OracleDispatch.cpp @@ -0,0 +1,100 @@ +#include "OracleDispatch.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include +#include +#include +#include +#include + +namespace { + +constexpr std::size_t PageBytes = 4096; +constexpr std::size_t BlockAlignment = 65536; +constexpr std::uint32_t MaxThreads = 1024; +constexpr std::uint32_t RowBytes = 16; +constexpr std::uint32_t OutputRowBytes = 64; + +std::size_t AlignUp(std::size_t value, std::size_t alignment) { + return (value + alignment - 1) / alignment * alignment; +} + +class GuestBlock { +public: + explicit GuestBlock(std::size_t size) : size(size) { +#ifdef _WIN32 + block = static_cast(VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); +#else + block = static_cast(std::aligned_alloc(BlockAlignment, size)); +#endif + if (block == nullptr) throw std::runtime_error("cannot allocate the guest block of " + std::to_string(size) + " bytes"); + GuestAllocations::Mutation().Add(block, size, 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() const { return block; } + std::size_t Size() const { return size; } + +private: + std::uint8_t* block = nullptr; + std::size_t size; +}; + +} + +std::vector RunOracleKernel(AgcDriver::VulkanDevice& device, const OracleKernel& kernel, std::uint32_t threads, std::span input) { + if (threads == 0) throw std::runtime_error("threads must be at least 1"); + if (threads > MaxThreads) throw std::runtime_error("threads must not exceed " + std::to_string(MaxThreads)); + if (kernel.waveSize == 0 || threads % kernel.waveSize != 0) throw std::runtime_error("threads must be a multiple of the wave size " + std::to_string(kernel.waveSize)); + const std::size_t rowBytes = static_cast(threads) * RowBytes; + const std::size_t outputBytes = static_cast(threads) * OutputRowBytes; + if (input.size() < rowBytes) throw std::runtime_error("input holds " + std::to_string(input.size()) + " bytes, " + std::to_string(rowBytes) + " are needed"); + if (kernel.text.empty()) throw std::runtime_error("kernel has no instructions"); + + const std::size_t inputOffset = PageBytes; + const std::size_t outputOffset = AlignUp(inputOffset + input.size(), PageBytes); + GuestBlock guest(AlignUp(outputOffset + outputBytes, BlockAlignment)); + std::memset(guest.Data(), 0, guest.Size()); + std::memcpy(guest.Data() + inputOffset, input.data(), input.size()); + std::memset(guest.Data() + outputOffset, 0xee, outputBytes); + const auto base = static_cast(reinterpret_cast(guest.Data())); + const std::array kernarg{base + inputOffset, base + outputOffset}; + std::memcpy(guest.Data(), kernarg.data(), sizeof(kernarg)); + + const std::span code(kernel.text); + const std::array regions{{ + {reinterpret_cast(code.data()), std::as_bytes(code)}, + {base, std::span(reinterpret_cast(guest.Data()), guest.Size())} + }}; + const std::vector userData{static_cast(base), static_cast(base >> 32u)}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{threads, 1, 1}, (kernel.ldsBytes + 3u) / 4u, kernel.groupIdEnable, kernel.tgSizeEnable, kernel.threadIdComponentCount}; + ShaderRecompiler::RecompileRequest request{ + {ShaderRecompiler::ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {kernel.waveSize, 0, userData, compute, std::nullopt, std::nullopt, regions, kernel.floatMode}, + 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(); + std::vector output(outputBytes); + std::memcpy(output.data(), guest.Data() + outputOffset, outputBytes); + return output; +} diff --git a/core/libs/prx/libSceAgcDriver/tools/OracleDispatch.hpp b/core/libs/prx/libSceAgcDriver/tools/OracleDispatch.hpp new file mode 100644 index 0000000000..75d81023b5 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tools/OracleDispatch.hpp @@ -0,0 +1,13 @@ +#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TOOLS_ORACLEDISPATCH_HPP +#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TOOLS_ORACLEDISPATCH_HPP + +#include "OracleCodeObject.hpp" +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include +#include +#include +#include + +std::vector RunOracleKernel(AgcDriver::VulkanDevice& device, const OracleKernel& kernel, std::uint32_t threads, std::span input); + +#endif diff --git a/core/libs/prx/libSceAgcDriver/tools/OracleRunner.cpp b/core/libs/prx/libSceAgcDriver/tools/OracleRunner.cpp new file mode 100644 index 0000000000..06c3608a65 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tools/OracleRunner.cpp @@ -0,0 +1,56 @@ +#include "OracleDispatch.hpp" +#include +#include +#include +#include +#include + +namespace { + +constexpr const char* Usage = "usage: agc_oracle_runner code.co threads in.bin out.bin out_dwords\n"; + +std::vector ReadFile(const char* path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) throw std::runtime_error(std::string("cannot open ") + path); + const auto size = file.tellg(); + if (size < 0) throw std::runtime_error(std::string("cannot size ") + path); + std::vector bytes(static_cast(size)); + file.seekg(0); + file.read(reinterpret_cast(bytes.data()), static_cast(bytes.size())); + if (!file) throw std::runtime_error(std::string("cannot read ") + path); + return bytes; +} + +} + +int main(int argc, char** argv) { + if (argc == 2 && std::string_view(argv[1]) == "--help") { + std::cout << Usage; + return 0; + } + if (argc == 2 && std::string_view(argv[1]) == "--target") { + std::cerr << "oracle runner: --target is not supported\n"; + return 2; + } + if (argc != 6) { + std::cerr << Usage; + return 2; + } + try { + const auto threads = ParseOracleCount(argv[2], "threads"); + const auto outDwords = ParseOracleCount(argv[5], "out_dwords"); + if (outDwords != 16) throw std::runtime_error("out_dwords must be 16, the template stores 16 dwords per lane"); + const auto kernel = ParseOracleCodeObject(ReadFile(argv[1])); + const auto input = ReadFile(argv[3]); + AgcDriver::VulkanDevice device; + const auto output = RunOracleKernel(device, kernel, threads, input); + std::ofstream file(argv[4], std::ios::binary | std::ios::trunc); + file.write(reinterpret_cast(output.data()), static_cast(output.size())); + file.close(); + if (!file) throw std::runtime_error(std::string("cannot write ") + argv[4]); + return 0; + } catch (const std::exception& error) { + std::cerr << "oracle runner: " << error.what() << '\n'; + return 1; + } +} diff --git a/core/libs/prx/libSceAjmi/Export.cpp b/core/libs/prx/libSceAjmi/Export.cpp index 925569074a..6c99d7f4c1 100644 --- a/core/libs/prx/libSceAjmi/Export.cpp +++ b/core/libs/prx/libSceAjmi/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libSceAjmi/include/InventoryExports.hpp" #include "prx/libSceAjmi/include/Api.hpp" #include #include @@ -52,3 +54,115 @@ void* APS5_VABI sceAjmBatchJobRunBufferRa_nid_disambig1(void* position, uint32_t } } + +extern "C" { + +int APS5_VABI sceAjmiBatchCancel() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAjmiBatchWait() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI libSceAjmiUnknown_4MsUK9kAnKg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4MsUK9kAnKg", libSceAjmiUnknown_4MsUK9kAnKg_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_6fY_minus_ci3au2U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6fY-ci3au2U", libSceAjmiUnknown_6fY_minus_ci3au2U_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_9zA4YMlkR9M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("9zA4YMlkR9M", libSceAjmiUnknown_9zA4YMlkR9M_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_BD002VBfhuE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BD002VBfhuE", libSceAjmiUnknown_BD002VBfhuE_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_EYIibEpcHuw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("EYIibEpcHuw", libSceAjmiUnknown_EYIibEpcHuw_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_J3UwQ5e6sNU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("J3UwQ5e6sNU", libSceAjmiUnknown_J3UwQ5e6sNU_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_LESEGgm133w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LESEGgm133w", libSceAjmiUnknown_LESEGgm133w_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_MTdd8ApwO50_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("MTdd8ApwO50", libSceAjmiUnknown_MTdd8ApwO50_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_QhA7rTjJbro_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QhA7rTjJbro", libSceAjmiUnknown_QhA7rTjJbro_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_RbLGSlJB6dc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("RbLGSlJB6dc", libSceAjmiUnknown_RbLGSlJB6dc_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_Uqtj4RSP_minus_JQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Uqtj4RSP-JQ", libSceAjmiUnknown_Uqtj4RSP_minus_JQ_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_X8sT_minus_xi_minus_dVE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("X8sT-xi-dVE", libSceAjmiUnknown_X8sT_minus_xi_minus_dVE_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_YtubBgSeNH0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("YtubBgSeNH0", libSceAjmiUnknown_YtubBgSeNH0_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_cWZnCK7na5Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cWZnCK7na5Y", libSceAjmiUnknown_cWZnCK7na5Y_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_dTsNpEt1AqQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dTsNpEt1AqQ", libSceAjmiUnknown_dTsNpEt1AqQ_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_dmwcwCeItmE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dmwcwCeItmE", libSceAjmiUnknown_dmwcwCeItmE_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_m0xisYC6U2w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("m0xisYC6U2w", libSceAjmiUnknown_m0xisYC6U2w_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_ofRX8Vq0ss0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ofRX8Vq0ss0", libSceAjmiUnknown_ofRX8Vq0ss0_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_pfPwiigOE3A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("pfPwiigOE3A", libSceAjmiUnknown_pfPwiigOE3A_nid_no_patch); + +int APS5_VABI libSceAjmiUnknown_r7NMFYcJi08_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("r7NMFYcJi08", libSceAjmiUnknown_r7NMFYcJi08_nid_no_patch); + +} diff --git a/core/libs/prx/libSceAjmi/include/InventoryExports.hpp b/core/libs/prx/libSceAjmi/include/InventoryExports.hpp new file mode 100644 index 0000000000..67f6606388 --- /dev/null +++ b/core/libs/prx/libSceAjmi/include/InventoryExports.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAjmiBatchCancel(); +int APS5_VABI sceAjmiBatchWait(); +int APS5_VABI libSceAjmiUnknown_4MsUK9kAnKg_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_6fY_minus_ci3au2U_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_9zA4YMlkR9M_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_BD002VBfhuE_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_EYIibEpcHuw_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_J3UwQ5e6sNU_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_LESEGgm133w_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_MTdd8ApwO50_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_QhA7rTjJbro_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_RbLGSlJB6dc_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_Uqtj4RSP_minus_JQ_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_X8sT_minus_xi_minus_dVE_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_YtubBgSeNH0_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_cWZnCK7na5Y_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_dTsNpEt1AqQ_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_dmwcwCeItmE_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_m0xisYC6U2w_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_ofRX8Vq0ss0_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_pfPwiigOE3A_nid_no_patch(); +int APS5_VABI libSceAjmiUnknown_r7NMFYcJi08_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceAjmi/libSceAjmi.hpp b/core/libs/prx/libSceAjmi/libSceAjmi.hpp index 739e63957f..1b39c77efc 100644 --- a/core/libs/prx/libSceAjmi/libSceAjmi.hpp +++ b/core/libs/prx/libSceAjmi/libSceAjmi.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceAjmi/include/Api.hpp" +#include "prx/libSceAjmi/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceAppContent/Export.cpp b/core/libs/prx/libSceAppContent/Export.cpp index 9d53053c3b..cec9ad2536 100644 --- a/core/libs/prx/libSceAppContent/Export.cpp +++ b/core/libs/prx/libSceAppContent/Export.cpp @@ -312,6 +312,22 @@ int APS5_VABI sceAppContentDownload1Shrink(void) { return 0; } +int APS5_VABI sceAppContentDownload2Expand(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceAppContentDownload2Shrink(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +APS5_EXPORT("SWVxsi-ZBlw", sceAppContentUnknown_SWVxsi_ZBlw); +int APS5_VABI sceAppContentUnknown_SWVxsi_ZBlw(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI sceAppContentGetAddcontInfoByEntitlementId(const char* entitlementId, AppContentAddcontInfo* info) { if (!initialized.load()) return SCE_APP_CONTENT_ERROR_NOT_INITIALIZED; if (!entitlementId || !info) return SCE_APP_CONTENT_ERROR_PARAMETER; diff --git a/core/libs/prx/libSceAppContent/include/Api.hpp b/core/libs/prx/libSceAppContent/include/Api.hpp index ae24effec5..896f9793d5 100644 --- a/core/libs/prx/libSceAppContent/include/Api.hpp +++ b/core/libs/prx/libSceAppContent/include/Api.hpp @@ -38,6 +38,9 @@ int APS5_VABI sceAppContentDownload0Expand(void); int APS5_VABI sceAppContentDownload0Shrink(void); int APS5_VABI sceAppContentDownload1Expand(void); int APS5_VABI sceAppContentDownload1Shrink(void); +int APS5_VABI sceAppContentDownload2Expand(void); +int APS5_VABI sceAppContentDownload2Shrink(void); +int APS5_VABI sceAppContentUnknown_SWVxsi_ZBlw(void); int APS5_VABI sceAppContentGetAddcontInfoByEntitlementId(const char* entitlementId, AppContentAddcontInfo* info); int APS5_VABI sceAppContentGetAddcontInfoListByIroTag(uint32_t iroTag, AppContentAddcontInfo* list, uint32_t listNum, uint32_t* hitNum); int APS5_VABI sceAppContentGetDownloadedStoreCountry(void); diff --git a/core/libs/prx/libSceAudioOut/CMakeLists.txt b/core/libs/prx/libSceAudioOut/CMakeLists.txt index a8e939bab4..48000b0f1d 100644 --- a/core/libs/prx/libSceAudioOut/CMakeLists.txt +++ b/core/libs/prx/libSceAudioOut/CMakeLists.txt @@ -137,7 +137,7 @@ if(BUILD_TESTING) add_test_executable(audio_out_resampling_tests tests/AudioOutResampling.cpp) target_include_directories(audio_out_resampling_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) - target_link_libraries(audio_out_resampling_tests PRIVATE libSceAudioOut libc) + target_link_libraries(audio_out_resampling_tests PRIVATE libSceAudioOut libc SDL2-static) set_target_properties(audio_out_resampling_tests PROPERTIES ENABLE_EXPORTS ON) add_test(NAME audio_out_resampling COMMAND audio_out_resampling_tests) set_tests_properties(audio_out_resampling PROPERTIES TIMEOUT 10) @@ -246,6 +246,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(audio_out2_speaker_array_tests tests/AudioOut2SpeakerArray.cpp) + target_include_directories(audio_out2_speaker_array_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(audio_out2_speaker_array_tests PRIVATE libSceAudioOut libc) + configure_windows_unwind(audio_out2_speaker_array_tests) + add_test(NAME audio_out2_speaker_array COMMAND audio_out2_speaker_array_tests) + if(WIN32) + set_tests_properties(audio_out2_speaker_array PROPERTIES ENVIRONMENT_MODIFICATION + "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) @@ -317,6 +327,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_audio_out_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudioOut.cpp) + target_include_directories(guest_audio_out_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_audio_out_tests PRIVATE libSceAudioOut libc) + configure_windows_unwind(guest_audio_out_tests) + add_test(NAME guest_audio_out COMMAND guest_audio_out_tests) + if(WIN32) + set_tests_properties(guest_audio_out PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(audio_out2_context_handles_tests tests/AudioOut2ContextHandles.cpp) target_include_directories(audio_out2_context_handles_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(audio_out2_context_handles_tests PRIVATE libSceAudioOut libc) diff --git a/core/libs/prx/libSceAudioOut/tests/AudioOut2SpeakerArray.cpp b/core/libs/prx/libSceAudioOut/tests/AudioOut2SpeakerArray.cpp new file mode 100644 index 0000000000..d172e7709c --- /dev/null +++ b/core/libs/prx/libSceAudioOut/tests/AudioOut2SpeakerArray.cpp @@ -0,0 +1,81 @@ +#include "prx/libSceAudioOut/include/AudioOut2.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include +#include +#include + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +template +static bool RejectsArgument(TFunction&& function) { + try { + function(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +struct VbapParams { + const AudioOut2Position* positions; + std::uint32_t numSpeakers; + std::uint8_t is3d; + void* memory; + std::size_t memorySize; + std::uint32_t reserved; +}; + +static bool Near(float value, float expected) { + return std::fabs(value - expected) < 1e-5f; +} + +int main() { + const AudioOut2Position square[4] = {{1, 0, 0}, {0, 1, 0}, {-1, 0, 0}, {0, -1, 0}}; + alignas(16) unsigned char memory[512] = {}; + VbapParams params{square, 4, 0, memory, sizeof(memory), 0}; + AudioOut2SpeakerArrayHandle array = nullptr; + Require(RejectsArgument([&] { sceAudioOut2SpeakerArrayCreate(nullptr, ¶ms, nullptr); }), "a null handle output was accepted"); + Require(RejectsArgument([&] { sceAudioOut2SpeakerArrayCreate(&array, nullptr, nullptr); }), "null parameters were accepted"); + VbapParams none = params; + none.numSpeakers = 0; + Require(RejectsArgument([&] { sceAudioOut2SpeakerArrayCreate(&array, &none, nullptr); }), "an array without speakers was accepted"); + VbapParams tooMany = params; + tooMany.numSpeakers = 33; + Require(RejectsArgument([&] { sceAudioOut2SpeakerArrayCreate(&array, &tooMany, nullptr); }), "33 speakers were accepted"); + VbapParams small = params; + small.memorySize = 16; + Require(RejectsArgument([&] { sceAudioOut2SpeakerArrayCreate(&array, &small, nullptr); }), "memory too small for the array was accepted"); + VbapParams misaligned = params; + misaligned.memory = memory + 1; + Require(RejectsArgument([&] { sceAudioOut2SpeakerArrayCreate(&array, &misaligned, nullptr); }), "misaligned memory was accepted"); + Require(array == nullptr, "a rejected creation wrote the handle"); + Require(sceAudioOut2SpeakerArrayCreate(&array, ¶ms, nullptr) == 0 && array == memory, "the array was not placed in the given memory"); + + constexpr std::uint32_t ambisonics = 0x40; + float coefficients[4] = {}; + Require(sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(array, ambisonics | 0, coefficients, 4) == 0, "W channel coefficients failed"); + for (const float coefficient : coefficients) Require(Near(coefficient, 0.25f), "the W channel is not split evenly over four speakers"); + Require(sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(array, ambisonics | 1, coefficients, 4) == 0, "Y channel coefficients failed"); + Require(Near(coefficients[0], 0.0f) && Near(coefficients[1], 0.25f) && Near(coefficients[2], 0.0f) && Near(coefficients[3], -0.25f), "the Y channel does not follow the sine of each azimuth"); + Require(sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(array, ambisonics | 3, coefficients, 4) == 0, "X channel coefficients failed"); + Require(Near(coefficients[0], 0.25f) && Near(coefficients[1], 0.0f) && Near(coefficients[2], -0.25f) && Near(coefficients[3], 0.0f), "the X channel does not follow the cosine of each azimuth"); + Require(sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(array, ambisonics | 4, coefficients, 4) == 0, "second-order coefficients failed"); + for (const float coefficient : coefficients) Require(coefficient == 0.0f, "four speakers decoded a second-order channel"); + Require(RejectsArgument([&] { sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(array, 0, coefficients, 4); }), "a channel without the ambisonics flag was accepted"); + Require(RejectsArgument([&] { sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(array, ambisonics | 36, coefficients, 4); }), "a sixth-order channel was accepted"); + Require(RejectsArgument([&] { sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(array, ambisonics, coefficients, 3); }), "a coefficient count other than the speaker count was accepted"); + Require(RejectsArgument([&] { sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(array, ambisonics, nullptr, 4); }), "null coefficients were accepted"); + alignas(16) unsigned char notArray[512] = {}; + Require(RejectsArgument([&] { sceAudioOut2GetSpeakerArrayAmbisonicsCoefficients(notArray, ambisonics, coefficients, 4); }), "memory that holds no array was accepted"); + std::puts("AudioOut2 speaker array tests passed"); + return 0; +} diff --git a/core/libs/prx/libSceAudioSystem/Export.cpp b/core/libs/prx/libSceAudioSystem/Export.cpp index 09a91b16b5..d85e4945ba 100644 --- a/core/libs/prx/libSceAudioSystem/Export.cpp +++ b/core/libs/prx/libSceAudioSystem/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libSceAudioSystem/include/InventoryExports.hpp" #include "prx/libSceAudioSystem/include/Api.hpp" #include #include @@ -41,3 +43,155 @@ int APS5_VABI sceAudioSystemTerminate() { } } + +extern "C" { + +int APS5_VABI sceAudioOutServerBusGetIoParam() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceAudioOutServerGetOption() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI libSceAudioSystemUnknown__minus_e5aJEqL34Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-e5aJEqL34Y", libSceAudioSystemUnknown__minus_e5aJEqL34Y_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_17nOsxccH5o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("17nOsxccH5o", libSceAudioSystemUnknown_17nOsxccH5o_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_2bKJUeywpeY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2bKJUeywpeY", libSceAudioSystemUnknown_2bKJUeywpeY_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_3HOelmA2vcw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("3HOelmA2vcw", libSceAudioSystemUnknown_3HOelmA2vcw_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_5SM0uQwcgTQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5SM0uQwcgTQ", libSceAudioSystemUnknown_5SM0uQwcgTQ_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_5oOYYvwIb0k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5oOYYvwIb0k", libSceAudioSystemUnknown_5oOYYvwIb0k_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_6PGbkl6OnvE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6PGbkl6OnvE", libSceAudioSystemUnknown_6PGbkl6OnvE_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_9QaKJdd7jKk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("9QaKJdd7jKk", libSceAudioSystemUnknown_9QaKJdd7jKk_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_B_minus_JyTVr90kc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("B-JyTVr90kc", libSceAudioSystemUnknown_B_minus_JyTVr90kc_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_Fd9u7_minus_OKW_plus_Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Fd9u7-OKW+Q", libSceAudioSystemUnknown_Fd9u7_minus_OKW_plus_Q_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_FkFcs7NTFDo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("FkFcs7NTFDo", libSceAudioSystemUnknown_FkFcs7NTFDo_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_GUtNdBeMrcM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("GUtNdBeMrcM", libSceAudioSystemUnknown_GUtNdBeMrcM_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_HxLyu3KUv5U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HxLyu3KUv5U", libSceAudioSystemUnknown_HxLyu3KUv5U_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_IiO_plus_DdVz_minus_4E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("IiO+DdVz-4E", libSceAudioSystemUnknown_IiO_plus_DdVz_minus_4E_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_J2Fx9JLKcqU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("J2Fx9JLKcqU", libSceAudioSystemUnknown_J2Fx9JLKcqU_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_Met2nIMg_plus_Ng_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Met2nIMg+Ng", libSceAudioSystemUnknown_Met2nIMg_plus_Ng_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_WRDd2feDYzg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("WRDd2feDYzg", libSceAudioSystemUnknown_WRDd2feDYzg_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_c4m3OSO0lj8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("c4m3OSO0lj8", libSceAudioSystemUnknown_c4m3OSO0lj8_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_ds20PmJfSSk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ds20PmJfSSk", libSceAudioSystemUnknown_ds20PmJfSSk_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_hjlhfF8Voz0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hjlhfF8Voz0", libSceAudioSystemUnknown_hjlhfF8Voz0_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_jLjHQbmK9_plus_A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jLjHQbmK9+A", libSceAudioSystemUnknown_jLjHQbmK9_plus_A_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_jZPcy9MG4w0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jZPcy9MG4w0", libSceAudioSystemUnknown_jZPcy9MG4w0_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_lSatNsWSmps_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lSatNsWSmps", libSceAudioSystemUnknown_lSatNsWSmps_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_ltR0PMUM270_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ltR0PMUM270", libSceAudioSystemUnknown_ltR0PMUM270_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_mS9gpe47Eio_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mS9gpe47Eio", libSceAudioSystemUnknown_mS9gpe47Eio_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_tHodLaUv04w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("tHodLaUv04w", libSceAudioSystemUnknown_tHodLaUv04w_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_uoPBJTVWhrg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("uoPBJTVWhrg", libSceAudioSystemUnknown_uoPBJTVWhrg_nid_no_patch); + +int APS5_VABI libSceAudioSystemUnknown_xG7GUeOKtBM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xG7GUeOKtBM", libSceAudioSystemUnknown_xG7GUeOKtBM_nid_no_patch); + +} diff --git a/core/libs/prx/libSceAudioSystem/include/InventoryExports.hpp b/core/libs/prx/libSceAudioSystem/include/InventoryExports.hpp new file mode 100644 index 0000000000..a803a8dc45 --- /dev/null +++ b/core/libs/prx/libSceAudioSystem/include/InventoryExports.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceAudioOutServerBusGetIoParam(); +int APS5_VABI sceAudioOutServerGetOption(); +int APS5_VABI libSceAudioSystemUnknown__minus_e5aJEqL34Y_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_17nOsxccH5o_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_2bKJUeywpeY_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_3HOelmA2vcw_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_5SM0uQwcgTQ_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_5oOYYvwIb0k_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_6PGbkl6OnvE_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_9QaKJdd7jKk_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_B_minus_JyTVr90kc_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_Fd9u7_minus_OKW_plus_Q_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_FkFcs7NTFDo_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_GUtNdBeMrcM_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_HxLyu3KUv5U_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_IiO_plus_DdVz_minus_4E_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_J2Fx9JLKcqU_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_Met2nIMg_plus_Ng_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_WRDd2feDYzg_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_c4m3OSO0lj8_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_ds20PmJfSSk_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_hjlhfF8Voz0_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_jLjHQbmK9_plus_A_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_jZPcy9MG4w0_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_lSatNsWSmps_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_ltR0PMUM270_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_mS9gpe47Eio_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_tHodLaUv04w_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_uoPBJTVWhrg_nid_no_patch(); +int APS5_VABI libSceAudioSystemUnknown_xG7GUeOKtBM_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceAudioSystem/libSceAudioSystem.hpp b/core/libs/prx/libSceAudioSystem/libSceAudioSystem.hpp index 092b35b31a..1e0bfce65d 100644 --- a/core/libs/prx/libSceAudioSystem/libSceAudioSystem.hpp +++ b/core/libs/prx/libSceAudioSystem/libSceAudioSystem.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceAudioSystem/include/Api.hpp" +#include "prx/libSceAudioSystem/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceAudiodec.native/CMakeLists.txt b/core/libs/prx/libSceAudiodec.native/CMakeLists.txt index 90195a7712..4c6cd37ac1 100644 --- a/core/libs/prx/libSceAudiodec.native/CMakeLists.txt +++ b/core/libs/prx/libSceAudiodec.native/CMakeLists.txt @@ -13,3 +13,15 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_audiodec_native_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudiodecNative.cpp) + target_include_directories(guest_audiodec_native_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_audiodec_native_tests PRIVATE libSceAudiodec.native libc) + configure_windows_unwind(guest_audiodec_native_tests) + add_test(NAME guest_audiodec_native COMMAND guest_audiodec_native_tests $) + if(WIN32) + set_tests_properties(guest_audiodec_native PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAudiodec/Export.cpp b/core/libs/prx/libSceAudiodec/Export.cpp index 2f84e5ccd0..a2231bd600 100644 --- a/core/libs/prx/libSceAudiodec/Export.cpp +++ b/core/libs/prx/libSceAudiodec/Export.cpp @@ -58,6 +58,7 @@ struct Instance { Audiodec::At9Format at9{}; std::uint32_t channels = 0; std::uint32_t sampleRate = 0; + std::uint32_t surroundChannelInterleaveOrder = 0; }; std::mutex g_mutex; @@ -146,7 +147,6 @@ std::int32_t createAac(const AudiodecParamM4aac* param, AudiodecM4aacInfo* info, if (enableNondelayOutput > 1) return ERROR_M4AAC_INVALID_ENABLE_NONDELAY_OUTPUT; if (param->ui_size == 32) std::memcpy(&surroundChannelInterleaveOrder, bytes + 28, sizeof(surroundChannelInterleaveOrder)); if (surroundChannelInterleaveOrder > 2) return ERROR_M4AAC_INVALID_SURROUND_CHANNEL_INTERLEAVE_ORDER; - if (surroundChannelInterleaveOrder != 0) throw std::runtime_error("M4AAC surround channel interleave order " + std::to_string(surroundChannelInterleaveOrder) + " is 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; @@ -156,6 +156,7 @@ std::int32_t createAac(const AudiodecParamM4aac* param, AudiodecM4aacInfo* info, 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; + instance.surroundChannelInterleaveOrder = surroundChannelInterleaveOrder; info->ui_sampling_freq = sampleRate; info->ui_number_of_channels = channels; info->ui_heaac = 0; @@ -239,6 +240,7 @@ int32_t APS5_VABI sceAudiodecDecode(int32_t handle, AudiodecCtrl* ctrl) { const Audiodec::DecodeResult decoded = instance->decoder->Decode(static_cast(ctrl->pAuInfo->p_au_addr), ctrl->pAuInfo->ui_au_size, static_cast(ctrl->pPcmItem->p_pcm_addr), ctrl->pPcmItem->ui_pcm_size); if (instance->codecType == TYPE_M4AAC) { + if (instance->surroundChannelInterleaveOrder != 0 && decoded.channels > 2) throw std::runtime_error("M4AAC surround channel interleave order " + std::to_string(instance->surroundChannelInterleaveOrder) + " is not implemented for " + std::to_string(decoded.channels) + " decoded channels"); auto* info = static_cast(ctrl->pBsiInfo); info->i_result = decoded.status == Audiodec::DecodeStatus::InvalidData ? M4AAC_RESULT_DECODE_ERROR : decoded.status == Audiodec::DecodeStatus::PartialInput ? M4AAC_RESULT_INSUFFICIENT_DATA : 0; diff --git a/core/libs/prx/libSceAudiodec/src/Codecs.cpp b/core/libs/prx/libSceAudiodec/src/Codecs.cpp index cb91a87bdf..0f96e7b599 100644 --- a/core/libs/prx/libSceAudiodec/src/Codecs.cpp +++ b/core/libs/prx/libSceAudiodec/src/Codecs.cpp @@ -1,4 +1,5 @@ #include "prx/libSceAudiodec/src/Codecs.hpp" +#include "prx/libc/include/General.hpp" #include "libatrac9.h" extern "C" { @@ -8,6 +9,7 @@ extern "C" { #include #include +#include #include #include #include @@ -47,10 +49,13 @@ public: ~FfmpegDecoder() override { avcodec_free_context(&context); } - void Reset() override { avcodec_flush_buffers(context); } + void Reset() override { + if (context) avcodec_flush_buffers(context); + } protected: DecodeResult decodePacket(const std::uint8_t* data, std::size_t size, std::uint8_t* pcm, std::size_t pcmSize) { + if (!context) return {DecodeStatus::InvalidData}; const std::unique_ptr packet(av_packet_alloc()); const std::unique_ptr frame(av_frame_alloc()); if (!packet || !frame) throw std::bad_alloc(); @@ -85,10 +90,16 @@ protected: return result; } - void reopen(std::vector replacement) { + bool tryReopen(std::vector replacement) { avcodec_free_context(&context); extradata = std::move(replacement); - open(); + try { + open(); + return true; + } catch (const std::runtime_error& error) { + APS5_LOG_ERR("%s", error.what()); + return false; + } } private: @@ -102,7 +113,18 @@ private: std::memcpy(context->extradata, extradata.data(), extradata.size()); context->extradata_size = static_cast(extradata.size()); } - if (avcodec_open2(context, codec, nullptr) < 0) throw std::runtime_error("libSceAudiodec: cannot open an FFmpeg decoder"); + if (const int status = avcodec_open2(context, codec, nullptr); status < 0) { + avcodec_free_context(&context); + char reason[AV_ERROR_MAX_STRING_SIZE]{}; + av_strerror(status, reason, sizeof(reason)); + std::string config; + for (const auto byte : extradata) { + char hex[4]; + std::snprintf(hex, sizeof(hex), "%02x", byte); + config += hex; + } + throw std::runtime_error("libSceAudiodec: cannot open the FFmpeg " + std::string(avcodec_get_name(id)) + " decoder" + (config.empty() ? "" : " (config " + config + ")") + ": " + reason); + } } std::int32_t wordSize; @@ -186,14 +208,14 @@ public: if (layout.channelConfiguration == 0) { if (!layouts.empty()) { layouts.clear(); - reopen({}); + if (!tryReopen({})) return {DecodeStatus::InvalidData}; } return decodePacket(data, frameBytes, pcm, pcmSize); } if (layouts.empty() || layouts.front() != layout) { layouts = {layout}; sbr = highEfficiency ? Sbr::Implicit : Sbr::Absent; - reopen(audioSpecificConfig(layout, sbr, 0)); + if (!tryReopen(audioSpecificConfig(layout, sbr, 0))) return {DecodeStatus::InvalidData}; } DecodeResult result = decodeAccessUnit(data + headerBytes, frameBytes - headerBytes, pcm, pcmSize); result.consumed = frameBytes; @@ -203,18 +225,18 @@ public: private: DecodeResult decodeAccessUnit(const std::uint8_t* data, std::size_t size, std::uint8_t* pcm, std::size_t pcmSize) { for (std::size_t index = 0; index < layouts.size(); ++index) { - if (index != 0) reopen(audioSpecificConfig(layouts[index], sbr, 0)); + if (index != 0 && !tryReopen(audioSpecificConfig(layouts[index], sbr, 0))) continue; DecodeResult result = decodePacket(data, size, pcm, pcmSize); if (result.status == DecodeStatus::InvalidData) continue; layouts = {layouts[index]}; if (sbr == Sbr::Implicit && layouts.front().channelConfiguration == 1 && result.channels > 1) { sbr = Sbr::WithoutParametricStereo; - reopen(audioSpecificConfig(layouts.front(), sbr, result.sampleRate)); + if (!tryReopen(audioSpecificConfig(layouts.front(), sbr, result.sampleRate))) return {DecodeStatus::InvalidData}; result = decodePacket(data, size, pcm, pcmSize); } return result; } - if (layouts.size() > 1) reopen(audioSpecificConfig(layouts.front(), sbr, 0)); + if (layouts.size() > 1) tryReopen(audioSpecificConfig(layouts.front(), sbr, 0)); return {DecodeStatus::InvalidData}; } diff --git a/core/libs/prx/libSceAudiodecCpu/Export.cpp b/core/libs/prx/libSceAudiodecCpu/Export.cpp index e4ac1ea15b..8a7875fcc0 100644 --- a/core/libs/prx/libSceAudiodecCpu/Export.cpp +++ b/core/libs/prx/libSceAudiodecCpu/Export.cpp @@ -1,49 +1,247 @@ #include "prx/libSceAudiodecCpu/include/Api.hpp" #include #include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int ErrorAuSize = static_cast(0x80f70006); +constexpr int ErrorPcmSize = static_cast(0x80f70007); +constexpr int ErrorMemorySize = static_cast(0x80f70008); +constexpr int ErrorType = static_cast(0x80f70001); +constexpr int ErrorClearMemory = static_cast(0x80f70002); +constexpr int ErrorCtrl = static_cast(0x80f70009); +constexpr int ErrorParam = static_cast(0x80f7000a); +constexpr int ErrorBsiInfo = static_cast(0x80f7000b); +constexpr int ErrorAuInfo = static_cast(0x80f7000c); +constexpr int ErrorPcmItem = static_cast(0x80f7000d); +constexpr int ErrorMemory = static_cast(0x80f7000e); +constexpr int ErrorAuAddress = static_cast(0x80f7000f); +constexpr int ErrorPcmAddress = static_cast(0x80f70010); +constexpr int ErrorMemoryAddress = static_cast(0x80f70011); +constexpr int ErrorAuBytes = static_cast(0x80f70013); +constexpr int ErrorPcmBytes = static_cast(0x80f70014); +constexpr int ErrorLpcmParam = static_cast(0x80f70100); + +constexpr std::uint32_t StructSize = 0x18; +constexpr std::uint32_t TypeLpcmBd = 0x1003; +constexpr std::uint32_t TypeLpcmDvd = 0x1004; +constexpr std::uint32_t TypeTrhd = 0x100b; +constexpr std::uint32_t LpcmMemorySize = 0x190138; +constexpr std::uint32_t LpcmParamSize = 0x28; +constexpr std::uint32_t LpcmInputCapacity = 0x43800; +constexpr std::uint32_t LpcmOutputCapacity = 0x43800; +constexpr std::uint32_t LpcmStateOffset = 0xb8; +constexpr std::uint32_t PtsStep = 450; + +struct LpcmParam { + std::uint32_t size; + std::uint32_t channelConfig; + std::uint32_t rateIndex; + std::uint32_t bitsIndex; + std::uint32_t auBytes; + std::uint32_t reserved; + std::uint64_t header; + std::uint32_t ptsHigh; + std::uint32_t ptsLow; +}; +static_assert(sizeof(LpcmParam) == LpcmParamSize); + +struct LpcmBsiInfo { + std::uint32_t reserved; + std::uint32_t channelConfig; + std::uint32_t rateIndex; + std::uint32_t pcmBytes; + std::uint64_t header; + std::uint32_t ptsHigh; + std::uint32_t ptsLow; +}; + +bool KnownType(std::uint32_t type) { + return (type >= 0x1001 && type <= 0x1008) || type == TypeTrhd || type == 0x100c; +} + +bool Lpcm(std::uint32_t type) { + return type == TypeLpcmBd || type == TypeLpcmDvd; +} + +std::uint32_t* LpcmState(const AudiodecCpuMemory* memory) { + return reinterpret_cast(static_cast(memory->p_memory) + LpcmStateOffset); +} + +void ResetLpcm(const AudiodecCpuMemory* memory) { + std::uint32_t* state = LpcmState(memory); + state[0] = 0xffffffffu; + state[1] = 0xffffffffu; +} + +void UnknownCodec(const char* function, std::uint32_t type) { + (void)type; + NotImplemented_nid_no_patch(function); +} + +float LpcmSample(std::int32_t value) { + return static_cast(value) * 0x1p-31f; +} + +void LpcmTimestamp(const LpcmParam& param, LpcmBsiInfo& info, const AudiodecCpuMemory* memory) { + std::uint32_t high = param.ptsHigh; + std::uint32_t low = param.ptsLow; + info.ptsHigh = high; + info.ptsLow = low; + std::uint32_t* state = LpcmState(memory); + if ((high & low) == 0xffffffffu) { + if ((state[0] & state[1]) == 0xffffffffu) return; + high = state[0]; + low = state[1] + PtsStep; + info.ptsLow = low; + info.ptsHigh = high; + if (low < state[1]) { + high = (high & 1) ? 0 : (high | 1); + info.ptsHigh = high; + } + } + state[0] = high; + state[1] = low; +} + +int DecodeLpcmBd(const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory) { + static constexpr std::uint32_t frameUnits[] = {10, 10, 20, 20, 20, 20, 30, 30, 40, 40}; + static constexpr std::uint32_t rates[] = {48, 96, 192}; + static constexpr std::uint32_t bits[] = {16, 24, 24}; + auto* param = static_cast(ctrl->pParam); + if (param->size != LpcmParamSize) return ErrorLpcmParam; + const std::uint32_t channelConfig = static_cast(param->header >> 32); + const std::uint32_t rateIndex = static_cast(param->header >> 40); + const std::uint32_t bitsIndex = static_cast(param->header >> 48); + const std::uint32_t frames = static_cast(param->header >> 56); + if (channelConfig - 1 > 9 || rateIndex - 1 > 2 || bitsIndex - 1 > 2) return ErrorLpcmParam; + param->channelConfig = channelConfig; + param->rateIndex = rateIndex; + param->bitsIndex = bitsIndex; + const std::uint32_t auBytes = (frameUnits[channelConfig - 1] * rates[rateIndex - 1] * bits[bitsIndex - 1] >> 3) * frames; + param->auBytes = auBytes; + if (channelConfig != 2) NotImplemented_nid_no_patch("sceAudiodecCpuDecode: LPCM channel layouts other than stereo"); + if (auBytes > LpcmInputCapacity) NotImplemented_nid_no_patch("sceAudiodecCpuDecode: an LPCM frame larger than the input buffer of the module"); + if (ctrl->pAuInfo->ui_au_size < auBytes) NotImplemented_nid_no_patch("sceAudiodecCpuDecode: an AU shorter than its LPCM frame"); + const auto* input = static_cast(ctrl->pAuInfo->p_au_addr); + const std::uint32_t width = bitsIndex == 1 ? 2 : 3; + const std::uint32_t samples = auBytes / width; + if (samples * 4 > LpcmOutputCapacity) NotImplemented_nid_no_patch("sceAudiodecCpuDecode: an LPCM frame producing more PCM than the output buffer capacity of the module"); + if (ctrl->pPcmItem->ui_pcm_size < samples * 4) NotImplemented_nid_no_patch("sceAudiodecCpuDecode: a PCM buffer smaller than the decoded frame"); + std::vector output(samples); + for (std::uint32_t index = 0; index < samples; ++index) { + const std::uint8_t* sample = input + index * width; + std::uint32_t value = static_cast(sample[0]) << 24 | static_cast(sample[1]) << 16; + if (width == 3) value |= static_cast(sample[2]) << 8; + output[index] = LpcmSample(static_cast(value)); + } + if (samples != 0) std::memcpy(ctrl->pPcmItem->p_pcm_addr, output.data(), samples * 4); + ctrl->pPcmItem->ui_pcm_size = samples * 4; + ctrl->pAuInfo->ui_au_size = auBytes; + auto* info = static_cast(ctrl->pBsiInfo); + LpcmTimestamp(*param, *info, memory); + info->header = param->header; + info->channelConfig = channelConfig; + info->rateIndex = rateIndex; + info->pcmBytes = ctrl->pPcmItem->ui_pcm_size; + return 0; +} + +int QueryMemSize(const char* function, const AudiodecCtrl* ctrl, AudiodecCpuMemory* memory, std::uint32_t type) { + if (!ctrl) return ErrorCtrl; + if (!ctrl->pParam) return ErrorParam; + if (!memory) return ErrorMemory; + if (memory->ui_size != StructSize) return ErrorMemorySize; + if (!KnownType(type)) return ErrorType; + if (!Lpcm(type)) UnknownCodec(function, type); + memory->ui_memory_size = LpcmMemorySize; + return 0; +} + +int InitDecoder(const char* function, const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type) { + if (!ctrl) return ErrorCtrl; + if (!ctrl->pParam) return ErrorParam; + if (!ctrl->pBsiInfo) return ErrorBsiInfo; + if (!memory) return ErrorMemory; + if (!memory->p_memory) return ErrorMemoryAddress; + if (memory->ui_size != StructSize) return ErrorMemorySize; + if (!KnownType(type)) return ErrorType; + if (!Lpcm(type)) UnknownCodec(function, type); + if (!ctrl->pAuInfo || !ctrl->pPcmItem) NotImplemented_nid_no_patch("sceAudiodecCpuInitDecoder: LPCM without an AU or PCM item"); + ResetLpcm(memory); + return 0; +} + +int Decode(const char* function, const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type) { + if (!ctrl) return ErrorCtrl; + if (!ctrl->pParam) return ErrorParam; + if (!ctrl->pBsiInfo) return ErrorBsiInfo; + const AudiodecAuInfo* au = ctrl->pAuInfo; + if (!au) return ErrorAuInfo; + if (!au->p_au_addr && !(type == TypeTrhd && au->ui_au_size == 0)) return ErrorAuAddress; + const AudiodecPcmItem* pcm = ctrl->pPcmItem; + if (!pcm) return ErrorPcmItem; + if (!pcm->p_pcm_addr) return ErrorPcmAddress; + if (au->ui_au_size == 0 && type != TypeTrhd) return ErrorAuBytes; + if (pcm->ui_pcm_size == 0) return ErrorPcmBytes; + if (au->ui_size != StructSize) return ErrorAuSize; + if (pcm->ui_size != StructSize) return ErrorPcmSize; + if (!memory) return ErrorMemory; + if (!memory->p_memory) return ErrorMemoryAddress; + if (memory->ui_size != StructSize) return ErrorMemorySize; + if (!KnownType(type)) return ErrorType; + if (type != TypeLpcmBd) UnknownCodec(function, type); + return DecodeLpcmBd(ctrl, memory); +} + +int ClearContext(const char* function, const AudiodecCpuMemory* memory, std::uint32_t type) { + if (!memory) return ErrorClearMemory; + if (!memory->p_memory) return ErrorMemoryAddress; + if (memory->ui_size != StructSize) return ErrorMemorySize; + if (!KnownType(type)) return ErrorType; + if (!Lpcm(type)) UnknownCodec(function, type); + ResetLpcm(memory); + return 0; +} + +} + extern "C" { -int APS5_VABI sceAudiodecCpuClearContext(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudiodecCpuClearContext(const AudiodecCpuMemory* memory, std::uint32_t type) { + return ClearContext(__func__, memory, type); } -int APS5_VABI sceAudiodecCpuDecode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudiodecCpuDecode(const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type) { + return Decode(__func__, ctrl, memory, type); } -int APS5_VABI sceAudiodecCpuInitDecoder(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudiodecCpuInitDecoder(const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type) { + return InitDecoder(__func__, ctrl, memory, type); } -int APS5_VABI sceAudiodecCpuInternalClearContext(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudiodecCpuInternalClearContext(const AudiodecCpuMemory* memory, std::uint32_t type) { + return ClearContext(__func__, memory, type); } -int APS5_VABI sceAudiodecCpuInternalDecode(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudiodecCpuInternalDecode(const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type) { + return Decode(__func__, ctrl, memory, type); } -int APS5_VABI sceAudiodecCpuInternalInitDecoder(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudiodecCpuInternalInitDecoder(const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type) { + return InitDecoder(__func__, ctrl, memory, type); } -int APS5_VABI sceAudiodecCpuInternalQueryMemSize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudiodecCpuInternalQueryMemSize(const AudiodecCtrl* ctrl, AudiodecCpuMemory* memory, std::uint32_t type) { + return QueryMemSize(__func__, ctrl, memory, type); } -int APS5_VABI sceAudiodecCpuQueryMemSize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceAudiodecCpuQueryMemSize(const AudiodecCtrl* ctrl, AudiodecCpuMemory* memory, std::uint32_t type) { + return QueryMemSize(__func__, ctrl, memory, type); } } diff --git a/core/libs/prx/libSceAudiodecCpu/include/Api.hpp b/core/libs/prx/libSceAudiodecCpu/include/Api.hpp index 336933274e..f1758bc9e4 100644 --- a/core/libs/prx/libSceAudiodecCpu/include/Api.hpp +++ b/core/libs/prx/libSceAudiodecCpu/include/Api.hpp @@ -2,15 +2,21 @@ #include "SceTypes.hpp" +struct AudiodecCpuMemory { + std::uint32_t ui_size; + void* p_memory; + std::uint32_t ui_memory_size; +}; + 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); +int APS5_VABI sceAudiodecCpuClearContext(const AudiodecCpuMemory* memory, std::uint32_t type); +int APS5_VABI sceAudiodecCpuDecode(const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type); +int APS5_VABI sceAudiodecCpuInitDecoder(const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type); +int APS5_VABI sceAudiodecCpuInternalClearContext(const AudiodecCpuMemory* memory, std::uint32_t type); +int APS5_VABI sceAudiodecCpuInternalDecode(const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type); +int APS5_VABI sceAudiodecCpuInternalInitDecoder(const AudiodecCtrl* ctrl, const AudiodecCpuMemory* memory, std::uint32_t type); +int APS5_VABI sceAudiodecCpuInternalQueryMemSize(const AudiodecCtrl* ctrl, AudiodecCpuMemory* memory, std::uint32_t type); +int APS5_VABI sceAudiodecCpuQueryMemSize(const AudiodecCtrl* ctrl, AudiodecCpuMemory* memory, std::uint32_t type); } diff --git a/core/libs/prx/libSceAvPlayer.native/CMakeLists.txt b/core/libs/prx/libSceAvPlayer.native/CMakeLists.txt index b9a16defb7..76e32be96c 100644 --- a/core/libs/prx/libSceAvPlayer.native/CMakeLists.txt +++ b/core/libs/prx/libSceAvPlayer.native/CMakeLists.txt @@ -2,3 +2,14 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/../libSceAvPlayer/AvPlayerLibrary.cmake) add_sce_avplayer_library(libSceAvPlayer.native) target_sources(libSceAvPlayer.native PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp PROPERTIES COMPILE_OPTIONS -fvisibility=default) +if(BUILD_TESTING) + add_test_executable(guest_avplayer_native_tests ${LIBS_INCLUDE_DIR}/tests/GuestAvPlayerNative.cpp) + target_include_directories(guest_avplayer_native_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_avplayer_native_tests PRIVATE libSceAvPlayer.native libc) + configure_windows_unwind(guest_avplayer_native_tests) + add_test(NAME guest_avplayer_native COMMAND guest_avplayer_native_tests $) + if(WIN32) + set_tests_properties(guest_avplayer_native PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAvPlayer/AvPlayerLibrary.cmake b/core/libs/prx/libSceAvPlayer/AvPlayerLibrary.cmake index 32fc8bb786..8388c75359 100644 --- a/core/libs/prx/libSceAvPlayer/AvPlayerLibrary.cmake +++ b/core/libs/prx/libSceAvPlayer/AvPlayerLibrary.cmake @@ -4,10 +4,11 @@ function(add_sce_avplayer_library target) ${avPlayerDir}/Export.cpp ${avPlayerDir}/src/Player.cpp ${avPlayerDir}/src/Source.cpp + ${avPlayerDir}/src/HttpStream.cpp ${avPlayerDir}/../libkernel/Time/TimedWait.cpp ) target_include_directories(${target} PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(${target} PRIVATE ffmpeg libc) + target_link_libraries(${target} PRIVATE ffmpeg libSceHttp libc) set_target_properties(${target} PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON diff --git a/core/libs/prx/libSceAvPlayer/CMakeLists.txt b/core/libs/prx/libSceAvPlayer/CMakeLists.txt index d8a652d7ff..f9fb844006 100644 --- a/core/libs/prx/libSceAvPlayer/CMakeLists.txt +++ b/core/libs/prx/libSceAvPlayer/CMakeLists.txt @@ -13,6 +13,61 @@ if(BUILD_TESTING) add_test(NAME guest_avplayer COMMAND guest_avplayer_tests) add_test(NAME guest_avplayer_callback_close COMMAND guest_avplayer_tests --callback-close) add_test(NAME guest_avplayer_paused_first_video COMMAND guest_avplayer_tests --paused-first-video) + find_package(Python3 COMPONENTS Interpreter QUIET) + if(Python3_Interpreter_FOUND) + add_test_executable(avplayer_http_transport_tests ${LIBS_INCLUDE_DIR}/tests/AvPlayerHttpTransport.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/HttpStream.cpp) + target_include_directories(avplayer_http_transport_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(avplayer_http_transport_tests PRIVATE ffmpeg libSceHttp libc) + configure_windows_unwind(avplayer_http_transport_tests) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_avplayer_http) + add_test(NAME guest_avplayer_http COMMAND ${Python3_EXECUTABLE} + ${LIBS_INCLUDE_DIR}/tests/AvPlayerHttpServer.py $) + set_tests_properties(guest_avplayer_http PROPERTIES + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_avplayer_http TIMEOUT 20) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/avplayer_http_transport) + add_test(NAME avplayer_http_transport COMMAND ${Python3_EXECUTABLE} + ${LIBS_INCLUDE_DIR}/tests/AvPlayerHttpServer.py $ transport) + set_tests_properties(avplayer_http_transport PROPERTIES + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/avplayer_http_transport TIMEOUT 10) + if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_test_executable(avplayer_http_patched_modules_tests ${LIBS_INCLUDE_DIR}/tests/AvPlayerHttpModules.cpp) + target_link_libraries(avplayer_http_patched_modules_tests PRIVATE ${CMAKE_DL_LIBS}) + configure_windows_unwind(avplayer_http_patched_modules_tests) + if(MINGW) + target_compile_options(avplayer_http_patched_modules_tests PRIVATE -fasynchronous-unwind-tables) + endif() + add_custom_target(avplayer_http_patched_closure) + add_dependencies(avplayer_http_patched_closure + libc libkernel libSceLibcInternal libSceHttp libSceAvPlayer libSceAvPlayer.native nid_patcher) + set(httpPatchedAllocatorArguments) + if(WIN32 AND TARGET mimalloc) + add_dependencies(avplayer_http_patched_closure mimalloc pe_import_first) + set(httpPatchedAllocatorArguments + -DALLOCATOR_DIRECTORY=$ + -DIMPORT_FIRST=$) + endif() + add_dependencies(avplayer_http_patched_modules_tests avplayer_http_patched_closure + guest_http_client_tests guest_avplayer_tests nid_patcher) + add_test(NAME avplayer_http_patched_modules COMMAND ${CMAKE_COMMAND} + -DPYTHON=${Python3_EXECUTABLE} + -DSERVER=${LIBS_INCLUDE_DIR}/tests/AvPlayerHttpServer.py + -DPATCHER=$ + -DLIBC=$ + -DKERNEL=$ + -DINTERNAL=$ + -DHTTP=$ + -DAVPLAYER=$ + -DNATIVE=$ + -DOBJDUMP=${CMAKE_OBJDUMP} + ${httpPatchedAllocatorArguments} + -DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/avplayer_http_patched_modules + -P ${LIBS_INCLUDE_DIR}/tests/AvPlayerHttpModules.cmake + -- "$" + "$" "$") + set_tests_properties(avplayer_http_patched_modules PROPERTIES TIMEOUT 30) + endif() + endif() set_tests_properties(guest_avplayer_paused_first_video PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_avplayer_paused_first_video TIMEOUT 20) @@ -24,6 +79,10 @@ if(BUILD_TESTING) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_avplayer TIMEOUT 20 RUN_SERIAL TRUE) if(WIN32) + if(TEST guest_avplayer_http) + set_tests_properties(guest_avplayer_http avplayer_http_transport PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() set_tests_properties(guest_avplayer PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") set_tests_properties(guest_avplayer_paused_first_video PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp b/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp index b7134a19c3..7c365c12c2 100644 --- a/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp +++ b/core/libs/prx/libSceAvPlayer/include/AvPlayer.hpp @@ -98,6 +98,8 @@ std::unique_ptr OpenSource(const SourceSettings& settings, const std::s std::uint32_t DetectSourceType(std::string_view path); +extern "C" { + 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); @@ -105,6 +107,8 @@ 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: Player(const AvPlayerInitData& init, std::uint32_t bufferCount); diff --git a/core/libs/prx/libSceAvPlayer/include/HttpStream.hpp b/core/libs/prx/libSceAvPlayer/include/HttpStream.hpp new file mode 100644 index 0000000000..3d76c276ed --- /dev/null +++ b/core/libs/prx/libSceAvPlayer/include/HttpStream.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +struct AVIOContext; + +namespace AvPlayer { + +class HttpStream { +public: + HttpStream(); + ~HttpStream(); + bool Open(const std::string& url); + AVIOContext* Context() const; + void Rethrow() const; + void Start(); + void Stop(); +private: + struct State; + std::unique_ptr state; +}; + +} diff --git a/core/libs/prx/libSceAvPlayer/src/HttpStream.cpp b/core/libs/prx/libSceAvPlayer/src/HttpStream.cpp new file mode 100644 index 0000000000..5b71b9a96c --- /dev/null +++ b/core/libs/prx/libSceAvPlayer/src/HttpStream.cpp @@ -0,0 +1,93 @@ +#include "prx/libSceAvPlayer/include/HttpStream.hpp" +#include "prx/libSceHttp/include/Client.hpp" +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +namespace AvPlayer { + +struct HttpStream::State { + Aps5HttpClient* client = Aps5HttpClient_Create_nid_postfix(); + AVIOContext* context = nullptr; + std::exception_ptr failure; + + ~State() { + if (context) { + av_freep(&context->buffer); + avio_context_free(&context); + } + failure = nullptr; + Aps5HttpClient_Destroy_nid_postfix(client); + } + + static int Read(void* opaque, std::uint8_t* destination, int capacity) { + auto& state = *static_cast(opaque); + try { + const auto count = Aps5HttpClient_Read_nid_postfix(state.client, destination, capacity); + return count == 0 ? AVERROR_EOF : count; + } catch (...) { + state.failure = std::current_exception(); + return AVERROR(EIO); + } + } + + static std::int64_t Seek(void* opaque, std::int64_t offset, int whence) { + auto& state = *static_cast(opaque); + try { + if (whence == AVSEEK_SIZE) { + const auto size = Aps5HttpClient_GetSize_nid_postfix(state.client); + return size < 0 ? AVERROR(ENOSYS) : size; + } + whence &= ~AVSEEK_FORCE; + std::int32_t origin; + if (whence == SEEK_SET) origin = 0; + else if (whence == SEEK_CUR) origin = 1; + else if (whence == SEEK_END) origin = 2; + else return AVERROR(EINVAL); + const auto position = Aps5HttpClient_Seek_nid_postfix(state.client, offset, origin); + return position < 0 ? AVERROR(EINVAL) : position; + } catch (...) { + state.failure = std::current_exception(); + return AVERROR(EIO); + } + } +}; + +HttpStream::HttpStream() : state(std::make_unique()) {} +HttpStream::~HttpStream() = default; + +bool HttpStream::Open(const std::string& url) { + if (state->context) { + av_freep(&state->context->buffer); + avio_context_free(&state->context); + } + state->failure = nullptr; + if (url.find('\0') != std::string::npos) return false; + if (Aps5HttpClient_Open_nid_postfix(state->client, url.c_str()) == 0) return false; + auto* buffer = static_cast(av_malloc(65536)); + if (!buffer) return false; + state->context = avio_alloc_context(buffer, 65536, 0, state.get(), State::Read, nullptr, State::Seek); + if (!state->context) av_free(buffer); + return state->context != nullptr; +} + +AVIOContext* HttpStream::Context() const { return state->context; } +void HttpStream::Rethrow() const { if (state->failure) std::rethrow_exception(state->failure); } +void HttpStream::Start() { + state->failure = nullptr; + Aps5HttpClient_Start_nid_postfix(state->client); + if (state->context) { + state->context->error = 0; + state->context->eof_reached = 0; + } +} +void HttpStream::Stop() { Aps5HttpClient_Stop_nid_postfix(state->client); } + +} diff --git a/core/libs/prx/libSceAvPlayer/src/Source.cpp b/core/libs/prx/libSceAvPlayer/src/Source.cpp index 70fbbc1472..7fedc33deb 100644 --- a/core/libs/prx/libSceAvPlayer/src/Source.cpp +++ b/core/libs/prx/libSceAvPlayer/src/Source.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "prx/libSceAvPlayer/include/AvPlayer.hpp" +#include "prx/libSceAvPlayer/include/HttpStream.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/FilesystemError.hpp" @@ -66,10 +67,12 @@ bool IsStreamSupported(const AVStream& stream) { return codec == AV_CODEC_ID_H264 || codec == AV_CODEC_ID_HEVC || codec == AV_CODEC_ID_AAC || codec == AV_CODEC_ID_VP9 || codec == AV_CODEC_ID_OPUS; } -float DisplayAspect(int width, int height, AVRational sampleAspect) { - if (width <= 0 || height <= 0) return 0.0f; - const double pixel = sampleAspect.num > 0 && sampleAspect.den > 0 ? av_q2d(sampleAspect) : 1.0; - return static_cast(width * pixel / height); +bool HasAspect(AVRational sampleAspect) { + return sampleAspect.num > 0 && sampleAspect.den > 0; +} + +float SampleAspect(AVRational sampleAspect) { + return HasAspect(sampleAspect) ? static_cast(av_q2d(sampleAspect)) : 0.0f; } class FileReplacementStream { @@ -251,19 +254,31 @@ public: } format->pb = replacement->Context(); format->flags |= AVFMT_FLAG_CUSTOM_IO; + } else if (path.find("://") != std::string::npos) { + std::string scheme = path.substr(0, path.find(':')); + for (char& byte : scheme) if (byte >= 'A' && byte <= 'Z') byte += 'a' - 'A'; + if (scheme != "http" && scheme != "https") NotImplemented_nid_no_patch("AvPlayer source URI scheme"); + remote = std::make_unique(); + if (!remote->Open(path)) return false; + format->pb = remote->Context(); + format->flags |= AVFMT_FLAG_CUSTOM_IO; } else { std::filesystem::path hostPath; if (ResolvePathError_nid_no_patch(path.c_str(), hostPath) != 0) return false; const auto resolved = hostPath.u8string(); url = "file:" + std::string(resolved.begin(), resolved.end()); } - if (const int error = avformat_open_input(&format, replacement ? nullptr : url.c_str(), nullptr, nullptr); error < 0) { + const int error = avformat_open_input(&format, replacement || remote ? nullptr : url.c_str(), nullptr, nullptr); + if (remote) remote->Rethrow(); + if (error < 0) { char reason[AV_ERROR_MAX_STRING_SIZE]{}; av_strerror(error, reason, sizeof(reason)); APS5_LOG_ERR("Could not open %s: %s", path.c_str(), reason); return false; } - if (avformat_find_stream_info(format, nullptr) < 0) return true; + const int streamResult = avformat_find_stream_info(format, nullptr); + if (remote) remote->Rethrow(); + if (streamResult < 0) return true; for (unsigned index = 0; index < format->nb_streams; ++index) { const auto* stream = format->streams[index]; if (!IsStreamSupported(*stream)) continue; @@ -331,6 +346,7 @@ public: 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; + if (remote) remote->Start(); for (auto* decoder : {&video, &audio}) { if (decoder->stream < 0) continue; const int result = openDecoder(*decoder); @@ -382,6 +398,7 @@ public: stopping = true; } condition.notify_all(); + if (remote) remote->Stop(); demuxer.join(); for (auto* decoder : {&video, &audio}) { if (decoder->thread.joinable()) decoder->thread.join(); @@ -635,7 +652,7 @@ private: if (info.type == StreamTypeVideo) { info.details.video.width = AlignUp(static_cast(parameters->width), VideoHeightAlignment); info.details.video.height = AlignUp(static_cast(parameters->height), VideoHeightAlignment); - info.details.video.aspect_ratio = DisplayAspect(parameters->width, parameters->height, parameters->sample_aspect_ratio); + info.details.video.aspect_ratio = SampleAspect(parameters->sample_aspect_ratio); std::memcpy(info.details.video.language_code, language, sizeof(language)); } else { info.details.audio.channel_count = static_cast(parameters->ch_layout.nb_channels); @@ -1070,7 +1087,7 @@ private: info.timestamp = timestamp; info.details.video.width = alignedWidth; info.details.video.height = alignedHeight; - info.details.video.aspect_ratio = DisplayAspect(frame.width, frame.height, frame.sample_aspect_ratio); + info.details.video.aspect_ratio = SampleAspect(HasAspect(frame.sample_aspect_ratio) ? frame.sample_aspect_ratio : format->streams[index]->codecpar->sample_aspect_ratio); std::memcpy(info.details.video.language_code, streamLanguage(index), sizeof(info.details.video.language_code)); info.details.video.crop_left_offset = static_cast(frame.crop_left); info.details.video.crop_right_offset = static_cast(frame.crop_right) + pitch - width; @@ -1151,6 +1168,7 @@ private: SourceSettings settings; ISourceEvents& events; std::unique_ptr replacement; + std::unique_ptr remote; AVFormatContext* format = nullptr; std::vector streams; std::uint64_t duration = 0; diff --git a/core/libs/prx/libSceAvSetting/CMakeLists.txt b/core/libs/prx/libSceAvSetting/CMakeLists.txt new file mode 100644 index 0000000000..02e20fee76 --- /dev/null +++ b/core/libs/prx/libSceAvSetting/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libSceAvSetting SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceAvSetting PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceAvSetting) + +if(BUILD_TESTING) + add_test_executable(guest_avsetting_predicates_tests ${LIBS_INCLUDE_DIR}/tests/GuestAvSettingPredicates.cpp) + target_include_directories(guest_avsetting_predicates_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_avsetting_predicates_tests PRIVATE libSceAvSetting libc) + configure_windows_unwind(guest_avsetting_predicates_tests) + add_test(NAME guest_avsetting_predicates COMMAND guest_avsetting_predicates_tests) + if(WIN32) + set_tests_properties(guest_avsetting_predicates PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceAvSetting/Export.cpp b/core/libs/prx/libSceAvSetting/Export.cpp index de1d4295f5..7c8e48f6de 100644 --- a/core/libs/prx/libSceAvSetting/Export.cpp +++ b/core/libs/prx/libSceAvSetting/Export.cpp @@ -1,11 +1,28 @@ #include "prx/libc/include/general/ExportMacros.hpp" #include "prx/libSceAvSetting/include/InventoryExports.hpp" #include "prx/libSceAvSetting/include/Api.hpp" +#include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +template +bool ReadAvSettingField(const void* object, std::size_t offset, TValue& value) noexcept { + const auto address = reinterpret_cast(object); + if (address == 0 || offset > std::numeric_limits::max() - address) return false; + try { + return GuestCopyIn_nid_no_patch(&value, reinterpret_cast(address + offset), sizeof(value)); + } catch (...) { + return false; + } +} + +} + extern "C" { int APS5_VABI sceAvControlGetPuTypeByMonitorInfo(void) { @@ -217,14 +234,20 @@ int APS5_VABI sceAvControlIsAudioOutModeSupportedByMonitorInfo() { return 0; } -int APS5_VABI sceAvControlIsColorimetryHdr() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI sceAvControlIsColorimetryHdr(std::uint32_t colorimetry) { + constexpr std::array isHdr{1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1}; + return colorimetry >= 9 && colorimetry <= 20 && isHdr[colorimetry - 9] != 0; } -int APS5_VABI sceAvControlIsDeepColorSupportedByMonitorInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI sceAvControlIsDeepColorSupportedByMonitorInfo(const void* monitorInfo) { + std::uint8_t deepColorFlags = 0; + if (!ReadAvSettingField(monitorInfo, 0x74, deepColorFlags)) return false; + if ((deepColorFlags & 0x0a) != 0) return true; + std::uint8_t deepColorFlags2 = 0; + if (!ReadAvSettingField(monitorInfo, 0x75, deepColorFlags2)) return false; + if ((deepColorFlags2 & 0x0a) != 0) return true; + std::uint8_t hdrFlags = 0; + return ReadAvSettingField(monitorInfo, 0x352, hdrFlags) && (hdrFlags & 0x03) != 0; } int APS5_VABI sceAvControlIsDeviceConnected() { @@ -232,19 +255,22 @@ int APS5_VABI sceAvControlIsDeviceConnected() { return 0; } -int APS5_VABI sceAvControlIsHdrSupportedByMonitorInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI sceAvControlIsHdrSupportedByMonitorInfo(const void* monitorInfo) { + std::uint8_t capabilityFlags = 0; + if (!ReadAvSettingField(monitorInfo, 0x358, capabilityFlags) || (capabilityFlags & 0x04) == 0) + return false; + std::uint8_t hdrFlags = 0; + return ReadAvSettingField(monitorInfo, 0x78, hdrFlags) && (hdrFlags & 0x30) != 0; } -int APS5_VABI sceAvControlIsModeGen1Vr() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI sceAvControlIsModeGen1Vr(const void* videoOutMode) { + std::uint32_t modeFlags = 0; + return ReadAvSettingField(videoOutMode, 0x30, modeFlags) && (modeFlags & 0x02) != 0; } -int APS5_VABI sceAvControlIsModeSystemDefault() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI sceAvControlIsModeSystemDefault(const void* videoOutMode) { + std::uint32_t modeFlags = 0; + return ReadAvSettingField(videoOutMode, 0x30, modeFlags) && (modeFlags & 0x01) == 0; } int APS5_VABI sceAvControlIsSuspendedProcessOutputModeAvailable() { diff --git a/core/libs/prx/libSceAvSetting/include/Api.hpp b/core/libs/prx/libSceAvSetting/include/Api.hpp index ec0d032743..8c830608ed 100644 --- a/core/libs/prx/libSceAvSetting/include/Api.hpp +++ b/core/libs/prx/libSceAvSetting/include/Api.hpp @@ -46,12 +46,12 @@ int APS5_VABI sceAvControlGetPuStatus(void); int APS5_VABI sceAvControlGetPuType(void); int APS5_VABI sceAvControlInit(void); int APS5_VABI sceAvControlIsAudioOutModeSupportedByMonitorInfo(void); -int APS5_VABI sceAvControlIsColorimetryHdr(void); -int APS5_VABI sceAvControlIsDeepColorSupportedByMonitorInfo(void); +bool APS5_VABI sceAvControlIsColorimetryHdr(std::uint32_t colorimetry); +bool APS5_VABI sceAvControlIsDeepColorSupportedByMonitorInfo(const void* monitorInfo); int APS5_VABI sceAvControlIsDeviceConnected(void); -int APS5_VABI sceAvControlIsHdrSupportedByMonitorInfo(void); -int APS5_VABI sceAvControlIsModeGen1Vr(void); -int APS5_VABI sceAvControlIsModeSystemDefault(void); +bool APS5_VABI sceAvControlIsHdrSupportedByMonitorInfo(const void* monitorInfo); +bool APS5_VABI sceAvControlIsModeGen1Vr(const void* videoOutMode); +bool APS5_VABI sceAvControlIsModeSystemDefault(const void* videoOutMode); int APS5_VABI sceAvControlIsSuspendedProcessOutputModeAvailable(void); int APS5_VABI sceAvControlIsVideoOutModeSupportedByMonitorInfo(void); int APS5_VABI sceAvControlSet2dVrMode(void); diff --git a/core/libs/prx/libSceAvcap2/Export.cpp b/core/libs/prx/libSceAvcap2/Export.cpp index 00f142044e..5e58d0c704 100644 --- a/core/libs/prx/libSceAvcap2/Export.cpp +++ b/core/libs/prx/libSceAvcap2/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libSceAvcap2/include/InventoryExports.hpp" #include "prx/libSceAvcap2/include/Api.hpp" #include #include @@ -182,3 +184,67 @@ int APS5_VABI sceAvcap2UnlockInputDataBuffer() { } } + +extern "C" { + +int APS5_VABI libSceAvcap2Unknown__plus_OspdnZ_minus_8mc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+OspdnZ-8mc", libSceAvcap2Unknown__plus_OspdnZ_minus_8mc_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_1Xp85wUaVTE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1Xp85wUaVTE", libSceAvcap2Unknown_1Xp85wUaVTE_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_32gujAUoIMc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("32gujAUoIMc", libSceAvcap2Unknown_32gujAUoIMc_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_4hkuDvkGu7U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4hkuDvkGu7U", libSceAvcap2Unknown_4hkuDvkGu7U_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_7KBaem5GyBQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("7KBaem5GyBQ", libSceAvcap2Unknown_7KBaem5GyBQ_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_88YEF_plus_XHfRY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("88YEF+XHfRY", libSceAvcap2Unknown_88YEF_plus_XHfRY_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_HAeczEM_minus_m7c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HAeczEM-m7c", libSceAvcap2Unknown_HAeczEM_minus_m7c_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_HrGLkn9o4MY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HrGLkn9o4MY", libSceAvcap2Unknown_HrGLkn9o4MY_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_Tzk7_minus_W5mp0A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Tzk7-W5mp0A", libSceAvcap2Unknown_Tzk7_minus_W5mp0A_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_VNRuuZQx7gE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("VNRuuZQx7gE", libSceAvcap2Unknown_VNRuuZQx7gE_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_iy6ZcsdeoMk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("iy6ZcsdeoMk", libSceAvcap2Unknown_iy6ZcsdeoMk_nid_no_patch); + +int APS5_VABI libSceAvcap2Unknown_k7gz608olKM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("k7gz608olKM", libSceAvcap2Unknown_k7gz608olKM_nid_no_patch); + +} diff --git a/core/libs/prx/libSceAvcap2/include/InventoryExports.hpp b/core/libs/prx/libSceAvcap2/include/InventoryExports.hpp new file mode 100644 index 0000000000..8398a94f6e --- /dev/null +++ b/core/libs/prx/libSceAvcap2/include/InventoryExports.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI libSceAvcap2Unknown__plus_OspdnZ_minus_8mc_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_1Xp85wUaVTE_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_32gujAUoIMc_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_4hkuDvkGu7U_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_7KBaem5GyBQ_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_88YEF_plus_XHfRY_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_HAeczEM_minus_m7c_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_HrGLkn9o4MY_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_Tzk7_minus_W5mp0A_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_VNRuuZQx7gE_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_iy6ZcsdeoMk_nid_no_patch(); +int APS5_VABI libSceAvcap2Unknown_k7gz608olKM_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp b/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp index 7ca2e6f1de..9696a5e65b 100644 --- a/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp +++ b/core/libs/prx/libSceAvcap2/libSceAvcap2.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceAvcap2/include/Api.hpp" +#include "prx/libSceAvcap2/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceCamera/Export.cpp b/core/libs/prx/libSceCamera/Export.cpp index 83a79cac34..77fcbc11f6 100644 --- a/core/libs/prx/libSceCamera/Export.cpp +++ b/core/libs/prx/libSceCamera/Export.cpp @@ -84,7 +84,6 @@ int APS5_VABI sceCameraSetVideoSyncInternal(void) { } int APS5_VABI sceCameraSetAttributeInternal(void) { - NotImplemented_nid_no_patch(__func__); return 0; } @@ -281,7 +280,6 @@ int APS5_VABI sceCamera2GetWhiteBalance(void) { } int APS5_VABI sceCameraDeviceOpen(void) { - NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceCdlgPlayerReview/Export.cpp b/core/libs/prx/libSceCdlgPlayerReview/Export.cpp index 75e23b282e..501c3be382 100644 --- a/core/libs/prx/libSceCdlgPlayerReview/Export.cpp +++ b/core/libs/prx/libSceCdlgPlayerReview/Export.cpp @@ -2,6 +2,7 @@ #include "prx/libSceUserService/UserService.hpp" #include "SceTypes.hpp" #include +#include #include #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceCommonDialog/CommonDialog.hpp" @@ -72,7 +73,11 @@ int APS5_VABI scePlayerReviewDialogGetResult(CommonDialogResultPrefix* result) { 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; + const std::int32_t cancelResult = COMMON_DIALOG_RESULT_USER_CANCELED; + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(&result->result, &cancelResult, sizeof(cancelResult)); + errno = savedErrno; + if (!copied) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; return COMMON_DIALOG_RESULT_USER_CANCELED; } diff --git a/core/libs/prx/libSceCesCs/CMakeLists.txt b/core/libs/prx/libSceCesCs/CMakeLists.txt index 10bf345bad..ba8bdfc234 100644 --- a/core/libs/prx/libSceCesCs/CMakeLists.txt +++ b/core/libs/prx/libSceCesCs/CMakeLists.txt @@ -1,6 +1,23 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/CesLibrary.cmake) add_sce_ces_library(libSceCesCs) +if(BUILD_TESTING) + foreach(variant IN ITEMS sparse_profiles sparse_profiles_module) + add_test_executable(guest_ces_${variant}_tests ${LIBS_INCLUDE_DIR}/tests/GuestCesSparseProfiles.cpp) + target_include_directories(guest_ces_${variant}_tests PRIVATE ${LIBS_INCLUDE_DIR}) + if(variant MATCHES "_module$") + target_link_libraries(guest_ces_${variant}_tests PRIVATE libSceCesCs-module) + else() + target_link_libraries(guest_ces_${variant}_tests PRIVATE libSceCesCs) + endif() + add_test(NAME guest_ces_${variant} COMMAND guest_ces_${variant}_tests) + endforeach() + if(WIN32) + set_tests_properties(guest_ces_sparse_profiles guest_ces_sparse_profiles_module PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test_executable(guest_ces_tests ${LIBS_INCLUDE_DIR}/tests/GuestCes.cpp) target_include_directories(guest_ces_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libSceCesCs/Export.cpp b/core/libs/prx/libSceCesCs/Export.cpp index 4dfe63642a..7e56fd2b7f 100644 --- a/core/libs/prx/libSceCesCs/Export.cpp +++ b/core/libs/prx/libSceCesCs/Export.cpp @@ -162,7 +162,7 @@ bool unicodeToSbc(const uint8_t* profile, uint32_t code, uint8_t& sbc) { } if (code == SingleByte::Unassigned) return false; for (uint32_t i = 0; i < 128; ++i) { - if (high[i] == code) { + if (high[i] != SingleByte::Unassigned && high[i] == code) { sbc = static_cast(0x80 + i); return true; } @@ -1673,6 +1673,38 @@ const void* APS5_VABI sceCesRefersUcsProfileCp1250(void) { return CP1250_PROFILE; } +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_3(void) { + return singleByteProfile(SingleByte::Iso8859_3); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_6(void) { + return singleByteProfile(SingleByte::Iso8859_6); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_7(void) { + return singleByteProfile(SingleByte::Iso8859_7); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_8(void) { + return singleByteProfile(SingleByte::Iso8859_8); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_11(void) { + return singleByteProfile(SingleByte::Iso8859_11); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp857(void) { + return singleByteProfile(SingleByte::Cp857); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp869(void) { + return singleByteProfile(SingleByte::Cp869); +} + +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp874(void) { + return singleByteProfile(SingleByte::Cp874); +} + const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_1(void) { return singleByteProfile(SingleByte::Iso8859_1); } @@ -2327,6 +2359,7 @@ int APS5_VABI sceCesUtf16leToSbc(const uint16_t* utf16, uint32_t utf16max, uint3 } int APS5_VABI sceCesUtf32ToSbc(uint32_t utf32, const uint8_t* profile, uint8_t* sbc) { + if (isSbc(profile) && sbc && utf32 >= 0xD800 && utf32 <= 0xDFFF) return CES_ERROR_ILLEGAL_CODE; return scalarToSbc(utf32, profile, sbc); } diff --git a/core/libs/prx/libSceCesCs/PendingExports.cpp b/core/libs/prx/libSceCesCs/PendingExports.cpp index 6aee7097b6..788d0528b6 100644 --- a/core/libs/prx/libSceCesCs/PendingExports.cpp +++ b/core/libs/prx/libSceCesCs/PendingExports.cpp @@ -73,51 +73,11 @@ int APS5_VABI sceCesRefersUcsProfileCp1257(void) { return 0; } -int APS5_VABI sceCesRefersUcsProfileCp857(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesRefersUcsProfileCp864(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceCesRefersUcsProfileCp869(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileCp874(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_3(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileIso8859_6(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileIso8859_7(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceCesRefersUcsProfileIso8859_8(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceCesSJisUcsProfileSetSbcs(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceCesCs/SingleByte.hpp b/core/libs/prx/libSceCesCs/SingleByte.hpp index be73a4adaa..3a52f6b12c 100644 --- a/core/libs/prx/libSceCesCs/SingleByte.hpp +++ b/core/libs/prx/libSceCesCs/SingleByte.hpp @@ -6,6 +6,14 @@ namespace SingleByte { enum Table : std::size_t { + Iso8859_3, + Iso8859_6, + Iso8859_7, + Iso8859_8, + Iso8859_11, + Cp857, + Cp869, + Cp874, Iso8859_1, Iso8859_2, Iso8859_4, @@ -52,6 +60,86 @@ enum Table : std::size_t { inline constexpr uint16_t Unassigned = 0xFFFF; 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, 0x0126, 0x02D8, 0x00A3, 0x00A4, Unassigned, 0x0124, 0x00A7, 0x00A8, 0x0130, 0x015E, 0x011E, 0x0134, 0x00AD, Unassigned, 0x017B, + 0x00B0, 0x0127, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x0125, 0x00B7, 0x00B8, 0x0131, 0x015F, 0x011F, 0x0135, 0x00BD, Unassigned, 0x017C, + 0x00C0, 0x00C1, 0x00C2, Unassigned, 0x00C4, 0x010A, 0x0108, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + Unassigned, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x0120, 0x00D6, 0x00D7, 0x011C, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x016C, 0x015C, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, Unassigned, 0x00E4, 0x010B, 0x0109, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + Unassigned, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x0121, 0x00F6, 0x00F7, 0x011D, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x016D, 0x015D, 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, Unassigned, Unassigned, Unassigned, 0x00A4, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, 0x060C, 0x00AD, Unassigned, Unassigned, + Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, 0x061B, Unassigned, Unassigned, Unassigned, 0x061F, + Unassigned, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063A, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, + 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, + 0x0650, 0x0651, 0x0652, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, + }, + { + 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, 0x2018, 0x2019, 0x00A3, 0x20AC, 0x20AF, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x037A, 0x00AB, 0x00AC, 0x00AD, Unassigned, 0x2015, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x0384, 0x0385, 0x0386, 0x00B7, 0x0388, 0x0389, 0x038A, 0x00BB, 0x038C, 0x00BD, 0x038E, 0x038F, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, + 0x03A0, 0x03A1, Unassigned, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03AA, 0x03AB, 0x03AC, 0x03AD, 0x03AE, 0x03AF, + 0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, + 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, Unassigned, + }, + { + 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, Unassigned, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00D7, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00F7, 0x00BB, 0x00BC, 0x00BD, 0x00BE, Unassigned, + Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, + Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, 0x2017, + 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, Unassigned, Unassigned, 0x200E, 0x200F, Unassigned, + }, + { + 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, 0x0E01, 0x0E02, 0x0E03, 0x0E04, 0x0E05, 0x0E06, 0x0E07, 0x0E08, 0x0E09, 0x0E0A, 0x0E0B, 0x0E0C, 0x0E0D, 0x0E0E, 0x0E0F, + 0x0E10, 0x0E11, 0x0E12, 0x0E13, 0x0E14, 0x0E15, 0x0E16, 0x0E17, 0x0E18, 0x0E19, 0x0E1A, 0x0E1B, 0x0E1C, 0x0E1D, 0x0E1E, 0x0E1F, + 0x0E20, 0x0E21, 0x0E22, 0x0E23, 0x0E24, 0x0E25, 0x0E26, 0x0E27, 0x0E28, 0x0E29, 0x0E2A, 0x0E2B, 0x0E2C, 0x0E2D, 0x0E2E, 0x0E2F, + 0x0E30, 0x0E31, 0x0E32, 0x0E33, 0x0E34, 0x0E35, 0x0E36, 0x0E37, 0x0E38, 0x0E39, 0x0E3A, Unassigned, Unassigned, Unassigned, Unassigned, 0x0E3F, + 0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45, 0x0E46, 0x0E47, 0x0E48, 0x0E49, 0x0E4A, 0x0E4B, 0x0E4C, 0x0E4D, 0x0E4E, 0x0E4F, + 0x0E50, 0x0E51, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57, 0x0E58, 0x0E59, 0x0E5A, 0x0E5B, Unassigned, Unassigned, Unassigned, Unassigned, + }, + { + 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x0131, 0x00C4, 0x00C5, + 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, 0x0130, 0x00D6, 0x00DC, 0x00F8, 0x00A3, 0x00D8, 0x015E, 0x015F, + 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x011E, 0x011F, 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, + 0x00BA, 0x00AA, 0x00CA, 0x00CB, 0x00C8, Unassigned, 0x00CD, 0x00CE, 0x00CF, 0x2518, 0x250C, 0x2588, 0x2584, 0x00A6, 0x00CC, 0x2580, + 0x00D3, 0x00DF, 0x00D4, 0x00D2, 0x00F5, 0x00D5, 0x00B5, Unassigned, 0x00D7, 0x00DA, 0x00DB, 0x00D9, 0x00EC, 0x00FF, 0x00AF, 0x00B4, + 0x00AD, 0x00B1, Unassigned, 0x00BE, 0x00B6, 0x00A7, 0x00F7, 0x00B8, 0x00B0, 0x00A8, 0x00B7, 0x00B9, 0x00B3, 0x00B2, 0x25A0, 0x00A0, + }, + { + Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, 0x0386, Unassigned, 0x00B7, 0x00AC, 0x00A6, 0x2018, 0x2019, 0x0388, 0x2015, 0x0389, + 0x038A, 0x03AA, 0x038C, Unassigned, Unassigned, 0x038E, 0x03AB, 0x00A9, 0x038F, 0x00B2, 0x00B3, 0x03AC, 0x00A3, 0x03AD, 0x03AE, 0x03AF, + 0x03CA, 0x0390, 0x03CC, 0x03CD, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x00BD, 0x0398, 0x0399, 0x00AB, 0x00BB, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x039A, 0x039B, 0x039C, 0x039D, 0x2563, 0x2551, 0x2557, 0x255D, 0x039E, 0x039F, 0x2510, + 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x03A0, 0x03A1, 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x03A3, + 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x03B1, 0x03B2, 0x03B3, 0x2518, 0x250C, 0x2588, 0x2584, 0x03B4, 0x03B5, 0x2580, + 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x0384, + 0x00AD, 0x00B1, 0x03C5, 0x03C6, 0x03C7, 0x00A7, 0x03C8, 0x0385, 0x00B0, 0x00A8, 0x03C9, 0x03CB, 0x03B0, 0x03CE, 0x25A0, 0x00A0, + }, + { + 0x20AC, Unassigned, Unassigned, Unassigned, Unassigned, 0x2026, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, + Unassigned, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, Unassigned, + 0x00A0, 0x0E01, 0x0E02, 0x0E03, 0x0E04, 0x0E05, 0x0E06, 0x0E07, 0x0E08, 0x0E09, 0x0E0A, 0x0E0B, 0x0E0C, 0x0E0D, 0x0E0E, 0x0E0F, + 0x0E10, 0x0E11, 0x0E12, 0x0E13, 0x0E14, 0x0E15, 0x0E16, 0x0E17, 0x0E18, 0x0E19, 0x0E1A, 0x0E1B, 0x0E1C, 0x0E1D, 0x0E1E, 0x0E1F, + 0x0E20, 0x0E21, 0x0E22, 0x0E23, 0x0E24, 0x0E25, 0x0E26, 0x0E27, 0x0E28, 0x0E29, 0x0E2A, 0x0E2B, 0x0E2C, 0x0E2D, 0x0E2E, 0x0E2F, + 0x0E30, 0x0E31, 0x0E32, 0x0E33, 0x0E34, 0x0E35, 0x0E36, 0x0E37, 0x0E38, 0x0E39, 0x0E3A, Unassigned, Unassigned, Unassigned, Unassigned, 0x0E3F, + 0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45, 0x0E46, 0x0E47, 0x0E48, 0x0E49, 0x0E4A, 0x0E4B, 0x0E4C, 0x0E4D, 0x0E4E, 0x0E4F, + 0x0E50, 0x0E51, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57, 0x0E58, 0x0E59, 0x0E5A, 0x0E5B, Unassigned, Unassigned, Unassigned, Unassigned, + }, { 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, @@ -293,8 +381,8 @@ inline constexpr uint16_t HIGH[Count][128] = { 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, + 0x20AC, 0xFFFF, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0xFFFF, 0xFFFF, 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, @@ -313,8 +401,8 @@ inline constexpr uint16_t HIGH[Count][128] = { 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, + 0x20AC, 0xFFFF, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0xFFFF, 0x2039, 0x0152, 0xFFFF, 0xFFFF, 0xFFFF, + 0xFFFF, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0xFFFF, 0x203A, 0x0153, 0xFFFF, 0xFFFF, 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, @@ -490,6 +578,14 @@ inline constexpr LowCode LOW[Count][2] = { {}, {}, {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, {{0x23, 0x00A3}}, {{0x24, 0x00A5}, {0x7E, 0x203E}}, {{0x24, 0x00A5}}, @@ -503,6 +599,7 @@ inline constexpr LowCode LOW[Count][2] = { }; inline constexpr bool SEVEN_BIT[Count] = { + false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, true, true, false, true, true, true, false, true, true, @@ -543,6 +640,14 @@ inline constexpr bool LOW_GRAPHICS_UNASSIGNED[Count] = { false, false, false, + false, + false, + false, + false, + false, + false, + false, + false, true, false, false, diff --git a/core/libs/prx/libSceCesCs/include/Api.hpp b/core/libs/prx/libSceCesCs/include/Api.hpp index a21ea8a5c7..fe5385ff3b 100644 --- a/core/libs/prx/libSceCesCs/include/Api.hpp +++ b/core/libs/prx/libSceCesCs/include/Api.hpp @@ -235,17 +235,17 @@ const uint8_t* APS5_VABI sceCesRefersUcsProfileBs4730(void); int APS5_VABI sceCesRefersUcsProfileCp1253(void); int APS5_VABI sceCesRefersUcsProfileCp1255(void); int APS5_VABI sceCesRefersUcsProfileCp1257(void); -int APS5_VABI sceCesRefersUcsProfileCp857(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp857(void); int APS5_VABI sceCesRefersUcsProfileCp864(void); -int APS5_VABI sceCesRefersUcsProfileCp869(void); -int APS5_VABI sceCesRefersUcsProfileCp874(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp869(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileCp874(void); const uint8_t* APS5_VABI sceCesRefersUcsProfileGbT1988(void); const uint8_t* 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); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_11(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_3(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_6(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_7(void); +const uint8_t* APS5_VABI sceCesRefersUcsProfileIso8859_8(void); const uint8_t* APS5_VABI sceCesRefersUcsProfileJisX0201(void); const uint8_t* APS5_VABI sceCesRefersUcsProfileJisX0201Katakana(void); const uint8_t* APS5_VABI sceCesRefersUcsProfileJisX0201Roman(void); diff --git a/core/libs/prx/libSceCloudMessaging/Export.cpp b/core/libs/prx/libSceCloudMessaging/Export.cpp new file mode 100644 index 0000000000..f6e741d37f --- /dev/null +++ b/core/libs/prx/libSceCloudMessaging/Export.cpp @@ -0,0 +1,30 @@ +#include "prx/libSceCloudMessaging/include/Api.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI _ZN17SceCloudMessaging19UnregisterMessagingEPKNS_21MessagingRegisterInfoE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN17SceCloudMessaging10InitializeEi() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN17SceCloudMessaging13CreateMessageEPKNS_7MessageEPj() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN17SceCloudMessaging16SendMessageAsyncEjPKNS_17MessageResultInfoE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN17SceCloudMessaging9TerminateEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZN17SceCloudMessaging17RegisterMessagingEPKNS_21MessagingRegisterInfoE() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceCloudMessaging/include/Api.hpp b/core/libs/prx/libSceCloudMessaging/include/Api.hpp new file mode 100644 index 0000000000..52d1b89f52 --- /dev/null +++ b/core/libs/prx/libSceCloudMessaging/include/Api.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI _ZN17SceCloudMessaging19UnregisterMessagingEPKNS_21MessagingRegisterInfoE(void); +int APS5_VABI _ZN17SceCloudMessaging10InitializeEi(void); +int APS5_VABI _ZN17SceCloudMessaging13CreateMessageEPKNS_7MessageEPj(void); +int APS5_VABI _ZN17SceCloudMessaging16SendMessageAsyncEjPKNS_17MessageResultInfoE(void); +int APS5_VABI _ZN17SceCloudMessaging9TerminateEv(void); +int APS5_VABI _ZN17SceCloudMessaging17RegisterMessagingEPKNS_21MessagingRegisterInfoE(void); + +} diff --git a/core/libs/prx/libSceCloudMessaging/libSceCloudMessaging.hpp b/core/libs/prx/libSceCloudMessaging/libSceCloudMessaging.hpp new file mode 100644 index 0000000000..3e78770cef --- /dev/null +++ b/core/libs/prx/libSceCloudMessaging/libSceCloudMessaging.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceCloudMessaging/include/Api.hpp" diff --git a/core/libs/prx/libSceComposite/Export.cpp b/core/libs/prx/libSceComposite/Export.cpp index 92183376c0..6c5e4210a5 100644 --- a/core/libs/prx/libSceComposite/Export.cpp +++ b/core/libs/prx/libSceComposite/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libSceComposite/include/InventoryExports.hpp" #include "prx/libSceComposite/include/Api.hpp" #include #include @@ -419,8 +421,8 @@ int APS5_VABI sceCompositorGetVirtualCanvasHandle() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceCompositorIsVirtualCanvasHandle() { - NotImplemented_nid_no_patch(__func__); +bool APS5_VABI sceCompositorIsVirtualCanvasHandle(std::uint32_t handle) { + return (handle & 0xff00) == 0xff00; } int APS5_VABI sceCompositorMemoryPoolCommit() { @@ -484,3 +486,151 @@ int APS5_VABI sceCompositorWaitEndOfRenderingIfGpuWorking() { } } + +extern "C" { + +int APS5_VABI sceCompositorSecondaryMemoryRelease() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI libSceCompositeUnknown__plus_A5zYfomrKo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+A5zYfomrKo", libSceCompositeUnknown__plus_A5zYfomrKo_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown__plus_DMKcAbpcrQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+DMKcAbpcrQ", libSceCompositeUnknown__plus_DMKcAbpcrQ_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_0hVj7ycuNjY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0hVj7ycuNjY", libSceCompositeUnknown_0hVj7ycuNjY_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_3znAWbdvzNo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("3znAWbdvzNo", libSceCompositeUnknown_3znAWbdvzNo_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_4iL4YvcEKdU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4iL4YvcEKdU", libSceCompositeUnknown_4iL4YvcEKdU_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_5vAisOgay3M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5vAisOgay3M", libSceCompositeUnknown_5vAisOgay3M_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_8l316W539Zw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("8l316W539Zw", libSceCompositeUnknown_8l316W539Zw_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_FDHuP_plus_z8KnM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("FDHuP+z8KnM", libSceCompositeUnknown_FDHuP_plus_z8KnM_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_HXxq9giIX_minus_I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HXxq9giIX-I", libSceCompositeUnknown_HXxq9giIX_minus_I_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_KpuS3CIcckw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KpuS3CIcckw", libSceCompositeUnknown_KpuS3CIcckw_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_NMGq0MJ2iGo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NMGq0MJ2iGo", libSceCompositeUnknown_NMGq0MJ2iGo_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_RPdjyM2WKcw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("RPdjyM2WKcw", libSceCompositeUnknown_RPdjyM2WKcw_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_SpfMcnnpfDA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("SpfMcnnpfDA", libSceCompositeUnknown_SpfMcnnpfDA_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_UEW01n5qkdA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UEW01n5qkdA", libSceCompositeUnknown_UEW01n5qkdA_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_XZzyQX4T5ts_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("XZzyQX4T5ts", libSceCompositeUnknown_XZzyQX4T5ts_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_Yck3L7ExyVc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Yck3L7ExyVc", libSceCompositeUnknown_Yck3L7ExyVc_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_Z25ikyBUYy0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Z25ikyBUYy0", libSceCompositeUnknown_Z25ikyBUYy0_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_aczOzib8mzI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("aczOzib8mzI", libSceCompositeUnknown_aczOzib8mzI_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_asXqPHp3O5g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("asXqPHp3O5g", libSceCompositeUnknown_asXqPHp3O5g_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_bwXAoMVv94M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bwXAoMVv94M", libSceCompositeUnknown_bwXAoMVv94M_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_dTPMTtYqPX4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dTPMTtYqPX4", libSceCompositeUnknown_dTPMTtYqPX4_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_gLAJaS1VbsA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("gLAJaS1VbsA", libSceCompositeUnknown_gLAJaS1VbsA_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_inplO6BVf5k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("inplO6BVf5k", libSceCompositeUnknown_inplO6BVf5k_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_j2CEWhcK0gc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("j2CEWhcK0gc", libSceCompositeUnknown_j2CEWhcK0gc_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_lvaCIMhYNh0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lvaCIMhYNh0", libSceCompositeUnknown_lvaCIMhYNh0_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_mzPjqJ_plus_unSw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mzPjqJ+unSw", libSceCompositeUnknown_mzPjqJ_plus_unSw_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_va_plus_6TiqnE1Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("va+6TiqnE1Y", libSceCompositeUnknown_va_plus_6TiqnE1Y_nid_no_patch); + +int APS5_VABI libSceCompositeUnknown_yZcGEBwnDAM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("yZcGEBwnDAM", libSceCompositeUnknown_yZcGEBwnDAM_nid_no_patch); + +} diff --git a/core/libs/prx/libSceComposite/include/Api.hpp b/core/libs/prx/libSceComposite/include/Api.hpp index 8a37a31779..d6cd40e745 100644 --- a/core/libs/prx/libSceComposite/include/Api.hpp +++ b/core/libs/prx/libSceComposite/include/Api.hpp @@ -90,7 +90,7 @@ int APS5_VABI sceCompositorGetSecondaryMemoryAddress(); int APS5_VABI sceCompositorGetSecondaryMemorySize(); int APS5_VABI sceCompositorGetTFRing(); int APS5_VABI sceCompositorGetVirtualCanvasHandle(); -int APS5_VABI sceCompositorIsVirtualCanvasHandle(); +bool APS5_VABI sceCompositorIsVirtualCanvasHandle(std::uint32_t handle); int APS5_VABI sceCompositorMemoryPoolCommit(); int APS5_VABI sceCompositorMemoryPoolCreate(); int APS5_VABI sceCompositorPermanentMemoryCreate(); diff --git a/core/libs/prx/libSceComposite/include/InventoryExports.hpp b/core/libs/prx/libSceComposite/include/InventoryExports.hpp new file mode 100644 index 0000000000..2a654f169a --- /dev/null +++ b/core/libs/prx/libSceComposite/include/InventoryExports.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCompositorSecondaryMemoryRelease(); +int APS5_VABI libSceCompositeUnknown__plus_A5zYfomrKo_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown__plus_DMKcAbpcrQ_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_0hVj7ycuNjY_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_3znAWbdvzNo_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_4iL4YvcEKdU_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_5vAisOgay3M_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_8l316W539Zw_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_FDHuP_plus_z8KnM_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_HXxq9giIX_minus_I_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_KpuS3CIcckw_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_NMGq0MJ2iGo_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_RPdjyM2WKcw_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_SpfMcnnpfDA_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_UEW01n5qkdA_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_XZzyQX4T5ts_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_Yck3L7ExyVc_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_Z25ikyBUYy0_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_aczOzib8mzI_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_asXqPHp3O5g_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_bwXAoMVv94M_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_dTPMTtYqPX4_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_gLAJaS1VbsA_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_inplO6BVf5k_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_j2CEWhcK0gc_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_lvaCIMhYNh0_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_mzPjqJ_plus_unSw_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_va_plus_6TiqnE1Y_nid_no_patch(); +int APS5_VABI libSceCompositeUnknown_yZcGEBwnDAM_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceComposite/libSceComposite.hpp b/core/libs/prx/libSceComposite/libSceComposite.hpp index 2829781c84..afedaca96d 100644 --- a/core/libs/prx/libSceComposite/libSceComposite.hpp +++ b/core/libs/prx/libSceComposite/libSceComposite.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceComposite/include/Api.hpp" +#include "prx/libSceComposite/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceContentExport/Export.cpp b/core/libs/prx/libSceContentExport/Export.cpp index 5ec29d9900..f57e93465d 100644 --- a/core/libs/prx/libSceContentExport/Export.cpp +++ b/core/libs/prx/libSceContentExport/Export.cpp @@ -44,18 +44,24 @@ 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) { - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard lock(g_sessionMutex); + RequireSession(__func__, expId); + NotImplemented_nid_no_patch(__func__); + return 0; } 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; + std::lock_guard lock(g_sessionMutex); + RequireSession(__func__, expId); + NotImplemented_nid_no_patch(__func__); + return 0; } 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; + std::lock_guard lock(g_sessionMutex); + RequireSession(__func__, expId); + NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceContentExportAddToFavorites(std::int64_t contentId) { diff --git a/core/libs/prx/libSceConvertKeycode/CMakeLists.txt b/core/libs/prx/libSceConvertKeycode/CMakeLists.txt index 341ff705b0..fc616fe47b 100644 --- a/core/libs/prx/libSceConvertKeycode/CMakeLists.txt +++ b/core/libs/prx/libSceConvertKeycode/CMakeLists.txt @@ -32,7 +32,7 @@ if(BUILD_TESTING) 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}) + add_test(NAME guest_convert_keycode_title_${titleCase} COMMAND guest_convert_keycode_title_tests $,ps4,ps5> WORKING_DIRECTORY ${titleRun}) if(WIN32) set_tests_properties(guest_convert_keycode_title_${titleCase} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceConvertKeycode/Export.cpp b/core/libs/prx/libSceConvertKeycode/Export.cpp index fa2cf6af55..585890186a 100644 --- a/core/libs/prx/libSceConvertKeycode/Export.cpp +++ b/core/libs/prx/libSceConvertKeycode/Export.cpp @@ -226,6 +226,7 @@ constexpr auto VirtualKeys = [] { }(); constexpr int kErrInvalidKeyboardType = static_cast(0x80BC001Du); +constexpr int kErrInvalidParameter = static_cast(0x80BC0030u); constexpr auto CharacterTables = [] { std::array, 111> tables{}; tables[1][4] = 327777; @@ -2409,43 +2410,60 @@ bool Ps4Client() { return false; } } + +std::uint32_t CompiledSdkVersion() { + const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); + std::uint32_t sdkVersion; + std::memcpy(&sdkVersion, process + 16, sizeof(sdkVersion)); + return sdkVersion; +} + +bool Ps4ClientOnce() { + static const bool ps4Client = Ps4Client(); + return ps4Client; +} + +bool KeyboardTypeAvailable(std::uint32_t type, std::uint32_t sdkVersion, bool ps4Client) { + if (type >= 31 && ps4Client && sdkVersion < 0x05000000u) return false; + if (type >= 38 && sdkVersion < (ps4Client ? 0x10000000u : 0x06000000u)) return false; + if (type == 39 && (ps4Client || sdkVersion < 0x08000000u)) return false; + return true; +} } // namespace extern "C" { int APS5_VABI sceConvertKeycodeGetImeKeyboardType(int32_t userId, uint32_t* type) { 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); + return ImeKeyboardType::Resolve(userId, type, CompiledSdkVersion(), ps4Client, sceUserServiceGetKeyboardType, sceUserServiceGetJapaneseInputType, sceUserServiceGetTraditionalChineseInputType); } 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"); + const std::uint32_t sdkVersion = CompiledSdkVersion(); + const bool ps4Client = Ps4ClientOnce(); + if (!KeyboardTypeAvailable(keyboardType, sdkVersion, ps4Client)) return kErrInvalidKeyboardType; 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"); + if ((keyboardType == 2 || keyboardType == 3 || keyboardType == 12) && sdkVersion < (ps4Client ? 0x09000000u : 0x04000000u)) { + if (value == 89) value = 90; + else if (value == 90) value = 89; + } *virtualKeycode = static_cast(value); return 0; } int APS5_VABI sceConvertKeycodeGetCharacterFromKeyboardData(const KeyboardData* keyData, std::int32_t index, std::uint32_t state, std::uint32_t keyboardType, char16_t* character) { if (keyData == nullptr || character == nullptr) return kErrInvalidAddress; - if (keyboardType == 0 || keyboardType > 39) return kErrInvalidKeyboardType; - if (keyboardType >= 31) throw std::runtime_error("sceConvertKeycodeGetCharacterFromKeyboardData: guest sdk version is unavailable"); - if (index < 0 || index >= keyData->length || index >= static_cast(KEYBOARD_MAX_KEYCODES)) { - throw std::runtime_error("sceConvertKeycodeGetCharacterFromKeyboardData: result for an index outside the key data is unknown"); - } - const std::uint16_t keycode = keyData->key_code[index]; - *character = ConvertCharacter(keycode, state, keyboardType); - return 0; + const auto length = static_cast(keyData->length); + if (length > KEYBOARD_MAX_KEYCODES || keyData->led >= 8 || keyData->modifier_key >= 0x100 || (state & 0xE3FFFF7Fu) != 0) return kErrInvalidParameter; + if (index < 0 || static_cast(index) >= length) return kErrInvalidParameter; + return sceConvertKeycodeGetCharacter(keyData->key_code[index], state | keyData->modifier_key << 8 | keyData->led << 16, keyboardType, character); } 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 (type == 0 || type > 39) return kErrInvalidKeyboardType; - if (type >= 31) throw std::runtime_error("sceConvertKeycodeGetCharacter: guest sdk version is unavailable"); + if (!KeyboardTypeAvailable(type, CompiledSdkVersion(), Ps4ClientOnce())) return kErrInvalidKeyboardType; *character = ConvertCharacter(keycode, state, type); return 0; } diff --git a/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp b/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp index e64d1e4444..30750a5fc3 100644 --- a/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp +++ b/core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libSceCustomMusicSysCallWrapper/include/InventoryExports.hpp" #include "prx/libSceCustomMusicSysCallWrapper/include/Api.hpp" #include #include @@ -6,9 +8,490 @@ extern "C" { +int APS5_VABI sceCustomMusicNetInetNtop() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetShutdown() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetSocketAbort() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadAttrGet() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadSelf() { + NotImplemented_nid_no_patch(__func__); +} + int APS5_VABI sceCustomMusicSelect() { NotImplemented_nid_no_patch(__func__); return 0; } } + +extern "C" { + +int APS5_VABI sceCustomMusicAudiodecClearContext() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicAudiodecCpuDecode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicAudiodecCreateDecoder() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicAudiodecDecode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicAudiodecDeleteDecoder() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicFileClose() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicFileLseek() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicFileOpenRead() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicFileRead() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicKernelGettimeofday() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicKernelNanosleep() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetAccept() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetBind() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetConnect() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetCtlInit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetCtlTerm() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetEpollAbort() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetEpollControl() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetEpollCreate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetEpollDestroy() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetEpollWait() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetErrnoLoc() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetInetPton() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetListen() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetPoolCreate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetPoolDestroy() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetRecv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetResolverAbort() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetResolverCreate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetResolverDestroy() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetSend() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetSetsockopt() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetSocketClose() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicNetSocket() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadAttrDestroy() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadAttrInit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadCondDestroy() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadCondInit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadCondWait() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadCreate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadDetach() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadExit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadJoin() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadMutexDestroy() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadMutexInit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadMutexLock() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadSetprio() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceCustomMusicPthreadYield() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown__plus_LdekyVYugM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+LdekyVYugM", libSceCustomMusicSysCallWrapperUnknown__plus_LdekyVYugM_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown__minus_f8PcMdF8L0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-f8PcMdF8L0", libSceCustomMusicSysCallWrapperUnknown__minus_f8PcMdF8L0_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_0WBrHbI33Ec_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0WBrHbI33Ec", libSceCustomMusicSysCallWrapperUnknown_0WBrHbI33Ec_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_0d4Aw8u3xzE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0d4Aw8u3xzE", libSceCustomMusicSysCallWrapperUnknown_0d4Aw8u3xzE_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_1auNQHxva3I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1auNQHxva3I", libSceCustomMusicSysCallWrapperUnknown_1auNQHxva3I_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_24ZHuc5b0RA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("24ZHuc5b0RA", libSceCustomMusicSysCallWrapperUnknown_24ZHuc5b0RA_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_2yE7PehS6iI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2yE7PehS6iI", libSceCustomMusicSysCallWrapperUnknown_2yE7PehS6iI_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_3DyCYcJudI4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("3DyCYcJudI4", libSceCustomMusicSysCallWrapperUnknown_3DyCYcJudI4_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_5I2IqwB0Hes_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5I2IqwB0Hes", libSceCustomMusicSysCallWrapperUnknown_5I2IqwB0Hes_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_5p7ZpbyUKuo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5p7ZpbyUKuo", libSceCustomMusicSysCallWrapperUnknown_5p7ZpbyUKuo_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_6PNhaGAw6Ho_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6PNhaGAw6Ho", libSceCustomMusicSysCallWrapperUnknown_6PNhaGAw6Ho_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_70NNIr_minus_nfFo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("70NNIr-nfFo", libSceCustomMusicSysCallWrapperUnknown_70NNIr_minus_nfFo_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_7uOVo0lL97g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("7uOVo0lL97g", libSceCustomMusicSysCallWrapperUnknown_7uOVo0lL97g_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_8t8UK2XkJ1Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("8t8UK2XkJ1Y", libSceCustomMusicSysCallWrapperUnknown_8t8UK2XkJ1Y_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_CQv51NIK3i4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("CQv51NIK3i4", libSceCustomMusicSysCallWrapperUnknown_CQv51NIK3i4_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_DABJz_plus_GrAWg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DABJz+GrAWg", libSceCustomMusicSysCallWrapperUnknown_DABJz_plus_GrAWg_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_EaC0JWOBvy4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("EaC0JWOBvy4", libSceCustomMusicSysCallWrapperUnknown_EaC0JWOBvy4_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_HNx_minus_bHhat18_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("HNx-bHhat18", libSceCustomMusicSysCallWrapperUnknown_HNx_minus_bHhat18_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_J4_plus_14VzlTHM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("J4+14VzlTHM", libSceCustomMusicSysCallWrapperUnknown_J4_plus_14VzlTHM_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_JSZXadyt_minus_nk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("JSZXadyt-nk", libSceCustomMusicSysCallWrapperUnknown_JSZXadyt_minus_nk_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Kxq7i4lQjGg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Kxq7i4lQjGg", libSceCustomMusicSysCallWrapperUnknown_Kxq7i4lQjGg_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_L0MmfM0N_minus_y8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("L0MmfM0N-y8", libSceCustomMusicSysCallWrapperUnknown_L0MmfM0N_minus_y8_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Mk6mbbOvaBM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Mk6mbbOvaBM", libSceCustomMusicSysCallWrapperUnknown_Mk6mbbOvaBM_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_NbAJfB19zUk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NbAJfB19zUk", libSceCustomMusicSysCallWrapperUnknown_NbAJfB19zUk_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_QEVVJH35Fis_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QEVVJH35Fis", libSceCustomMusicSysCallWrapperUnknown_QEVVJH35Fis_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_ROnMWfJ56t8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ROnMWfJ56t8", libSceCustomMusicSysCallWrapperUnknown_ROnMWfJ56t8_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_SneVIJUdrLw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("SneVIJUdrLw", libSceCustomMusicSysCallWrapperUnknown_SneVIJUdrLw_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Tr_minus_kghWgcoI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Tr-kghWgcoI", libSceCustomMusicSysCallWrapperUnknown_Tr_minus_kghWgcoI_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_VDAd516qud8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("VDAd516qud8", libSceCustomMusicSysCallWrapperUnknown_VDAd516qud8_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_VTS_plus_zlRk_minus_wo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("VTS+zlRk-wo", libSceCustomMusicSysCallWrapperUnknown_VTS_plus_zlRk_minus_wo_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Xkm5tRxA0UQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Xkm5tRxA0UQ", libSceCustomMusicSysCallWrapperUnknown_Xkm5tRxA0UQ_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Y3l3L6A4OYE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Y3l3L6A4OYE", libSceCustomMusicSysCallWrapperUnknown_Y3l3L6A4OYE_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_ZiO9aSV1ZKc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZiO9aSV1ZKc", libSceCustomMusicSysCallWrapperUnknown_ZiO9aSV1ZKc_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_bzddXxOnr30_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bzddXxOnr30", libSceCustomMusicSysCallWrapperUnknown_bzddXxOnr30_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_eTHTXNt0B2k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("eTHTXNt0B2k", libSceCustomMusicSysCallWrapperUnknown_eTHTXNt0B2k_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_fsQhNY_plus_RJf8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("fsQhNY+RJf8", libSceCustomMusicSysCallWrapperUnknown_fsQhNY_plus_RJf8_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_gxew9cxe_plus_xk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("gxew9cxe+xk", libSceCustomMusicSysCallWrapperUnknown_gxew9cxe_plus_xk_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_hd0MG4ZvfRA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hd0MG4ZvfRA", libSceCustomMusicSysCallWrapperUnknown_hd0MG4ZvfRA_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_jETJjJLQtas_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jETJjJLQtas", libSceCustomMusicSysCallWrapperUnknown_jETJjJLQtas_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_jVD2jFJAQ28_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jVD2jFJAQ28", libSceCustomMusicSysCallWrapperUnknown_jVD2jFJAQ28_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_kso0cI0kq1U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("kso0cI0kq1U", libSceCustomMusicSysCallWrapperUnknown_kso0cI0kq1U_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_lgRL6Oblmyg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lgRL6Oblmyg", libSceCustomMusicSysCallWrapperUnknown_lgRL6Oblmyg_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_mgSEL99WSwA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mgSEL99WSwA", libSceCustomMusicSysCallWrapperUnknown_mgSEL99WSwA_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_n0aVoomYuOM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("n0aVoomYuOM", libSceCustomMusicSysCallWrapperUnknown_n0aVoomYuOM_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_n4jZ2HjzXUo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("n4jZ2HjzXUo", libSceCustomMusicSysCallWrapperUnknown_n4jZ2HjzXUo_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_oaaW7CuQLV0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("oaaW7CuQLV0", libSceCustomMusicSysCallWrapperUnknown_oaaW7CuQLV0_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_qqNVevi1LlY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("qqNVevi1LlY", libSceCustomMusicSysCallWrapperUnknown_qqNVevi1LlY_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_sWVPeXELNss_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("sWVPeXELNss", libSceCustomMusicSysCallWrapperUnknown_sWVPeXELNss_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_sak_plus_IpFmBkk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("sak+IpFmBkk", libSceCustomMusicSysCallWrapperUnknown_sak_plus_IpFmBkk_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_y2Fu_plus_vFwEXo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("y2Fu+vFwEXo", libSceCustomMusicSysCallWrapperUnknown_y2Fu_plus_vFwEXo_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_yM_minus_etuyf_minus_2M_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("yM-etuyf-2M", libSceCustomMusicSysCallWrapperUnknown_yM_minus_etuyf_minus_2M_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_zRDEHOFDvzQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("zRDEHOFDvzQ", libSceCustomMusicSysCallWrapperUnknown_zRDEHOFDvzQ_nid_no_patch); + +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_zUgaEYLIysQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("zUgaEYLIysQ", libSceCustomMusicSysCallWrapperUnknown_zUgaEYLIysQ_nid_no_patch); + +} diff --git a/core/libs/prx/libSceCustomMusicSysCallWrapper/include/Api.hpp b/core/libs/prx/libSceCustomMusicSysCallWrapper/include/Api.hpp index 63ad57d532..c6ace1453b 100644 --- a/core/libs/prx/libSceCustomMusicSysCallWrapper/include/Api.hpp +++ b/core/libs/prx/libSceCustomMusicSysCallWrapper/include/Api.hpp @@ -4,6 +4,11 @@ extern "C" { +int APS5_VABI sceCustomMusicNetInetNtop(); +int APS5_VABI sceCustomMusicNetShutdown(); +int APS5_VABI sceCustomMusicNetSocketAbort(); +int APS5_VABI sceCustomMusicPthreadAttrGet(); +int APS5_VABI sceCustomMusicPthreadSelf(); int APS5_VABI sceCustomMusicSelect(); } diff --git a/core/libs/prx/libSceCustomMusicSysCallWrapper/include/InventoryExports.hpp b/core/libs/prx/libSceCustomMusicSysCallWrapper/include/InventoryExports.hpp new file mode 100644 index 0000000000..65b7c2db68 --- /dev/null +++ b/core/libs/prx/libSceCustomMusicSysCallWrapper/include/InventoryExports.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceCustomMusicAudiodecClearContext(); +int APS5_VABI sceCustomMusicAudiodecCpuDecode(); +int APS5_VABI sceCustomMusicAudiodecCreateDecoder(); +int APS5_VABI sceCustomMusicAudiodecDecode(); +int APS5_VABI sceCustomMusicAudiodecDeleteDecoder(); +int APS5_VABI sceCustomMusicFileClose(); +int APS5_VABI sceCustomMusicFileLseek(); +int APS5_VABI sceCustomMusicFileOpenRead(); +int APS5_VABI sceCustomMusicFileRead(); +int APS5_VABI sceCustomMusicKernelGettimeofday(); +int APS5_VABI sceCustomMusicKernelNanosleep(); +int APS5_VABI sceCustomMusicNetAccept(); +int APS5_VABI sceCustomMusicNetBind(); +int APS5_VABI sceCustomMusicNetConnect(); +int APS5_VABI sceCustomMusicNetCtlInit(); +int APS5_VABI sceCustomMusicNetCtlTerm(); +int APS5_VABI sceCustomMusicNetEpollAbort(); +int APS5_VABI sceCustomMusicNetEpollControl(); +int APS5_VABI sceCustomMusicNetEpollCreate(); +int APS5_VABI sceCustomMusicNetEpollDestroy(); +int APS5_VABI sceCustomMusicNetEpollWait(); +int APS5_VABI sceCustomMusicNetErrnoLoc(); +int APS5_VABI sceCustomMusicNetInetPton(); +int APS5_VABI sceCustomMusicNetListen(); +int APS5_VABI sceCustomMusicNetPoolCreate(); +int APS5_VABI sceCustomMusicNetPoolDestroy(); +int APS5_VABI sceCustomMusicNetRecv(); +int APS5_VABI sceCustomMusicNetResolverAbort(); +int APS5_VABI sceCustomMusicNetResolverCreate(); +int APS5_VABI sceCustomMusicNetResolverDestroy(); +int APS5_VABI sceCustomMusicNetSend(); +int APS5_VABI sceCustomMusicNetSetsockopt(); +int APS5_VABI sceCustomMusicNetSocketClose(); +int APS5_VABI sceCustomMusicNetSocket(); +int APS5_VABI sceCustomMusicPthreadAttrDestroy(); +int APS5_VABI sceCustomMusicPthreadAttrInit(); +int APS5_VABI sceCustomMusicPthreadCondDestroy(); +int APS5_VABI sceCustomMusicPthreadCondInit(); +int APS5_VABI sceCustomMusicPthreadCondWait(); +int APS5_VABI sceCustomMusicPthreadCreate(); +int APS5_VABI sceCustomMusicPthreadDetach(); +int APS5_VABI sceCustomMusicPthreadExit(); +int APS5_VABI sceCustomMusicPthreadJoin(); +int APS5_VABI sceCustomMusicPthreadMutexDestroy(); +int APS5_VABI sceCustomMusicPthreadMutexInit(); +int APS5_VABI sceCustomMusicPthreadMutexLock(); +int APS5_VABI sceCustomMusicPthreadSetprio(); +int APS5_VABI sceCustomMusicPthreadYield(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown__plus_LdekyVYugM_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown__minus_f8PcMdF8L0_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_0WBrHbI33Ec_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_0d4Aw8u3xzE_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_1auNQHxva3I_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_24ZHuc5b0RA_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_2yE7PehS6iI_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_3DyCYcJudI4_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_5I2IqwB0Hes_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_5p7ZpbyUKuo_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_6PNhaGAw6Ho_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_70NNIr_minus_nfFo_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_7uOVo0lL97g_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_8t8UK2XkJ1Y_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_CQv51NIK3i4_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_DABJz_plus_GrAWg_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_EaC0JWOBvy4_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_HNx_minus_bHhat18_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_J4_plus_14VzlTHM_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_JSZXadyt_minus_nk_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Kxq7i4lQjGg_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_L0MmfM0N_minus_y8_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Mk6mbbOvaBM_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_NbAJfB19zUk_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_QEVVJH35Fis_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_ROnMWfJ56t8_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_SneVIJUdrLw_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Tr_minus_kghWgcoI_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_VDAd516qud8_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_VTS_plus_zlRk_minus_wo_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Xkm5tRxA0UQ_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_Y3l3L6A4OYE_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_ZiO9aSV1ZKc_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_bzddXxOnr30_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_eTHTXNt0B2k_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_fsQhNY_plus_RJf8_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_gxew9cxe_plus_xk_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_hd0MG4ZvfRA_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_jETJjJLQtas_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_jVD2jFJAQ28_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_kso0cI0kq1U_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_lgRL6Oblmyg_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_mgSEL99WSwA_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_n0aVoomYuOM_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_n4jZ2HjzXUo_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_oaaW7CuQLV0_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_qqNVevi1LlY_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_sWVPeXELNss_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_sak_plus_IpFmBkk_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_y2Fu_plus_vFwEXo_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_yM_minus_etuyf_minus_2M_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_zRDEHOFDvzQ_nid_no_patch(); +int APS5_VABI libSceCustomMusicSysCallWrapperUnknown_zUgaEYLIysQ_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceCustomMusicSysCallWrapper/libSceCustomMusicSysCallWrapper.hpp b/core/libs/prx/libSceCustomMusicSysCallWrapper/libSceCustomMusicSysCallWrapper.hpp index 22ad944a67..0365dcbb17 100644 --- a/core/libs/prx/libSceCustomMusicSysCallWrapper/libSceCustomMusicSysCallWrapper.hpp +++ b/core/libs/prx/libSceCustomMusicSysCallWrapper/libSceCustomMusicSysCallWrapper.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceCustomMusicSysCallWrapper/include/Api.hpp" +#include "prx/libSceCustomMusicSysCallWrapper/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceDbg/Export.cpp b/core/libs/prx/libSceDbg/Export.cpp index d81f4a5e32..2bd31bc88d 100644 --- a/core/libs/prx/libSceDbg/Export.cpp +++ b/core/libs/prx/libSceDbg/Export.cpp @@ -1,10 +1,154 @@ -#include +#include "prx/libSceDbg/include/Api.hpp" #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceDbgUserChannelPrint(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgAmprAprGetNumberOfErrors(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgMapperVirtualQuery(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgUserChannelPrintf(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgAssertionHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgGetSystemSwVersion(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgSetBreakOnWarningState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgSetBreakOnErrorState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgDeleteExternalTriggerEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgAmprAmmGetNumberOfErrors(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgGetModuleInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgBacktraceSelf(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgAmprAmmGetErrors(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgGetDebugSuspendCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgUserChannelVPrintf(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgSetMinimumLogLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgGetModuleList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgClearHardwareBreakPoint(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgAddExternalTriggerEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgLoggingHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgEnableExtraHeapDebugInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgAmprAprGetErrors(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgVLoggingHandler(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgGetReleaseCheckMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgSetHardwareBreakPoint(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgGetExecutablePath(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgGetCpuGpuFrequencySetting(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgRequireSystemSwVersion(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDbgIsDebuggerAttached(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceDbg/include/Api.hpp b/core/libs/prx/libSceDbg/include/Api.hpp new file mode 100644 index 0000000000..cbe77edc61 --- /dev/null +++ b/core/libs/prx/libSceDbg/include/Api.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceDbgUserChannelPrint(void); +int APS5_VABI sceDbgAmprAprGetNumberOfErrors(void); +int APS5_VABI sceDbgMapperVirtualQuery(void); +int APS5_VABI sceDbgUserChannelPrintf(void); +int APS5_VABI sceDbgAssertionHandler(void); +int APS5_VABI sceDbgGetSystemSwVersion(void); +int APS5_VABI sceDbgSetBreakOnWarningState(void); +int APS5_VABI sceDbgSetBreakOnErrorState(void); +int APS5_VABI sceDbgDeleteExternalTriggerEvent(void); +int APS5_VABI sceDbgAmprAmmGetNumberOfErrors(void); +int APS5_VABI sceDbgGetModuleInfo(void); +int APS5_VABI sceDbgBacktraceSelf(void); +int APS5_VABI sceDbgAmprAmmGetErrors(void); +int APS5_VABI sceDbgGetDebugSuspendCount(void); +int APS5_VABI sceDbgUserChannelVPrintf(void); +int APS5_VABI sceDbgSetMinimumLogLevel(void); +int APS5_VABI sceDbgGetModuleList(void); +int APS5_VABI sceDbgClearHardwareBreakPoint(void); +int APS5_VABI sceDbgAddExternalTriggerEvent(void); +int APS5_VABI sceDbgLoggingHandler(void); +int APS5_VABI sceDbgEnableExtraHeapDebugInfo(void); +int APS5_VABI sceDbgAmprAprGetErrors(void); +int APS5_VABI sceDbgVLoggingHandler(void); +int APS5_VABI sceDbgGetReleaseCheckMode(void); +int APS5_VABI sceDbgSetHardwareBreakPoint(void); +int APS5_VABI sceDbgGetExecutablePath(void); +int APS5_VABI sceDbgGetCpuGpuFrequencySetting(void); +int APS5_VABI sceDbgRequireSystemSwVersion(void); +int APS5_VABI sceDbgIsDebuggerAttached(void); + +} diff --git a/core/libs/prx/libSceDbg/libSceDbg.hpp b/core/libs/prx/libSceDbg/libSceDbg.hpp index 6f70f09bee..461503f002 100644 --- a/core/libs/prx/libSceDbg/libSceDbg.hpp +++ b/core/libs/prx/libSceDbg/libSceDbg.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libSceDbg/include/Api.hpp" diff --git a/core/libs/prx/libSceDeci5/Export.cpp b/core/libs/prx/libSceDeci5/Export.cpp index d81f4a5e32..c63c8a3623 100644 --- a/core/libs/prx/libSceDeci5/Export.cpp +++ b/core/libs/prx/libSceDeci5/Export.cpp @@ -1,10 +1,44 @@ -#include +#include "prx/libSceDeci5/include/Api.hpp" #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceDeci5Write(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDeci5Read(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDeci5CreateHostProcessAndWait(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDeci5Close(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDeci5CreateHostProcess(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDeci5Open(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceDeci5GetStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceDeci5/include/Api.hpp b/core/libs/prx/libSceDeci5/include/Api.hpp new file mode 100644 index 0000000000..6a3c918b9c --- /dev/null +++ b/core/libs/prx/libSceDeci5/include/Api.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceDeci5Write(void); +int APS5_VABI sceDeci5Read(void); +int APS5_VABI sceDeci5CreateHostProcessAndWait(void); +int APS5_VABI sceDeci5Close(void); +int APS5_VABI sceDeci5CreateHostProcess(void); +int APS5_VABI sceDeci5Open(void); +int APS5_VABI sceDeci5GetStatus(void); + +} diff --git a/core/libs/prx/libSceDeci5/libSceDeci5.hpp b/core/libs/prx/libSceDeci5/libSceDeci5.hpp index 6f70f09bee..bb991c0eee 100644 --- a/core/libs/prx/libSceDeci5/libSceDeci5.hpp +++ b/core/libs/prx/libSceDeci5/libSceDeci5.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libSceDeci5/include/Api.hpp" diff --git a/core/libs/prx/libSceEsvm/Export.cpp b/core/libs/prx/libSceEsvm/Export.cpp index 3935b8b5ad..6ae877dcb1 100644 --- a/core/libs/prx/libSceEsvm/Export.cpp +++ b/core/libs/prx/libSceEsvm/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceEsvm/include/InventoryExports.hpp" #include "prx/libSceEsvm/include/Api.hpp" #include #include @@ -207,4 +208,24 @@ int APS5_VABI sceEsvmVideoOutSetContentType() { } +} + +extern "C" { + +int APS5_VABI sceEsvmInitializePlayReady() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceEsvmInitializeWidevine() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceEsvmRequestMediaKeySystemAccessPlayReady() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceEsvmRequestMediaKeySystemAccessWidevine() { + NotImplemented_nid_no_patch(__func__); +} + } diff --git a/core/libs/prx/libSceEsvm/include/InventoryExports.hpp b/core/libs/prx/libSceEsvm/include/InventoryExports.hpp new file mode 100644 index 0000000000..a56ada51c1 --- /dev/null +++ b/core/libs/prx/libSceEsvm/include/InventoryExports.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceEsvmInitializePlayReady(); +int APS5_VABI sceEsvmInitializeWidevine(); +int APS5_VABI sceEsvmRequestMediaKeySystemAccessPlayReady(); +int APS5_VABI sceEsvmRequestMediaKeySystemAccessWidevine(); + +} diff --git a/core/libs/prx/libSceEsvm/libSceEsvm.hpp b/core/libs/prx/libSceEsvm/libSceEsvm.hpp index 015c133208..fda00d64ee 100644 --- a/core/libs/prx/libSceEsvm/libSceEsvm.hpp +++ b/core/libs/prx/libSceEsvm/libSceEsvm.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceEsvm/include/Api.hpp" +#include "prx/libSceEsvm/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceFios2/CMakeLists.txt b/core/libs/prx/libSceFios2/CMakeLists.txt new file mode 100644 index 0000000000..f242ac0634 --- /dev/null +++ b/core/libs/prx/libSceFios2/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libSceFios2 SHARED EXCLUDE_FROM_ALL Export.cpp src/Runtime.cpp src/Control.cpp src/PathQueries.cpp src/PathOperations.cpp src/FileHandles.cpp src/DirectoryHandles.cpp src/Time.cpp) +target_include_directories(libSceFios2 PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceFios2) + +if(BUILD_TESTING) + add_test_executable(guest_fios2_tests ${LIBS_INCLUDE_DIR}/tests/GuestFios2.cpp) + target_include_directories(guest_fios2_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_fios2_tests PRIVATE libSceFios2 libkernel libc) + configure_windows_unwind(guest_fios2_tests) + add_test(NAME guest_fios2 COMMAND guest_fios2_tests) + set_tests_properties(guest_fios2 PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_fios2 PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceFios2/Export.cpp b/core/libs/prx/libSceFios2/Export.cpp index b97176c204..d20a3d8090 100644 --- a/core/libs/prx/libSceFios2/Export.cpp +++ b/core/libs/prx/libSceFios2/Export.cpp @@ -121,56 +121,11 @@ int APS5_VABI sceFiosCachePrefetchFileSync() { return 0; } -int APS5_VABI sceFiosCancelAllOps() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosClearTimeStamps() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFiosCloseAllFiles() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDHClose() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDHCloseSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDHGetPath() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDHOpen() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDHOpenSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDHRead() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDHReadSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosDLLInitialize() { NotImplemented_nid_no_patch(__func__); return 0; @@ -181,21 +136,6 @@ int APS5_VABI sceFiosDLLTerminate() { return 0; } -int APS5_VABI sceFiosDateFromComponents() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDateGetCurrent() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDateToComponents() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosDeallocatePassthruFH() { NotImplemented_nid_no_patch(__func__); return 0; @@ -226,26 +166,6 @@ int APS5_VABI sceFiosDebugDumpOp() { return 0; } -int APS5_VABI sceFiosDelete() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDeleteSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDirectoryCreate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDirectoryCreateSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosDirectoryCreateWithMode() { NotImplemented_nid_no_patch(__func__); return 0; @@ -256,306 +176,16 @@ int APS5_VABI sceFiosDirectoryCreateWithModeSync() { return 0; } -int APS5_VABI sceFiosDirectoryDelete() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDirectoryDeleteSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDirectoryExists() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosDirectoryExistsSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosExists() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosExistsSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHClose() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHCloseSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHGetOpenParams() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHGetPath() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHGetSize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHOpen() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHOpenSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHOpenWithMode() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHOpenWithModeSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHPread() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHPreadSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHPreadv() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHPreadvSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHPwrite() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHPwriteSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHPwritev() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHPwritevSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHRead() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHReadSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHReadv() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHReadvSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHSeek() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHStat() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHStatSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHSyncSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHTell() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosFHToFileno() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFiosFHTruncate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHTruncateSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHWrite() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHWriteSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHWritev() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFHWritevSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileDelete() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileDeleteSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileExists() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileExistsSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileGetSize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileGetSizeSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileRead() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileReadSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileTruncate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileTruncateSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileWrite() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosFileWriteSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosFilenoToFH() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFiosGetAllDHs() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosGetAllFHs() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosGetAllOps() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosGetDefaultOpAttr() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosGetGlobalDefaultOpAttr() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosGetSuspendCount() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosGetThreadDefaultOpAttr() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosIOFilterAdd() { NotImplemented_nid_no_patch(__func__); return 0; @@ -581,86 +211,6 @@ int APS5_VABI sceFiosIOFilterRemove() { return 0; } -int APS5_VABI sceFiosInitialize() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosIsIdle() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosIsInitialized() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosIsSuspended() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosIsValidHandle() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpCancel() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpDelete() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpGetActualCount() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpGetAttr() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpGetBuffer() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpGetError() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpGetOffset() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpGetPath() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpGetRequestCount() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpIsCancelled() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpIsDone() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosOpReschedule() { NotImplemented_nid_no_patch(__func__); return 0; @@ -676,21 +226,6 @@ int APS5_VABI sceFiosOpSetBuffer() { return 0; } -int APS5_VABI sceFiosOpSyncWait() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpSyncWaitForIO() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosOpWait() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosOpWaitUntil() { NotImplemented_nid_no_patch(__func__); return 0; @@ -751,16 +286,6 @@ int APS5_VABI sceFiosPrintf() { return 0; } -int APS5_VABI sceFiosRename() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosRenameSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosResolve() { NotImplemented_nid_no_patch(__func__); return 0; @@ -771,76 +296,21 @@ int APS5_VABI sceFiosResolveSync() { return 0; } -int APS5_VABI sceFiosResume() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosSaveTimeStamp() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFiosSetGlobalDefaultOpAttr() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosSetThreadDefaultOpAttr() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosShutdownAndCancelOps() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosStat() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosStatSync() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosSuspend() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosTerminate() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosTimeGetCurrent() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFiosTimeIntervalFromNanoseconds() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFiosTimeIntervalToNanoseconds() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosTraceTimestamp() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFiosUpdateParameters() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceFiosVprintf() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceFios2/include/Api.hpp b/core/libs/prx/libSceFios2/include/Api.hpp index 354858a01d..8e9c93d9dd 100644 --- a/core/libs/prx/libSceFios2/include/Api.hpp +++ b/core/libs/prx/libSceFios2/include/Api.hpp @@ -1,9 +1,117 @@ #pragma once #include "SceTypes.hpp" +#include "prx/libSceFios2/include/Types.hpp" extern "C" { +int APS5_VABI sceFiosInitialize(const SceFiosParams* params); +void APS5_VABI sceFiosTerminate(); +bool APS5_VABI sceFiosIsInitialized(SceFiosParams* outParams); +void APS5_VABI sceFiosUpdateParameters(const SceFiosParams* params); +void APS5_VABI sceFiosSetGlobalDefaultOpAttr(const SceFiosOpAttr* attr); +bool APS5_VABI sceFiosGetGlobalDefaultOpAttr(SceFiosOpAttr* outAttr); +void APS5_VABI sceFiosSetThreadDefaultOpAttr(const SceFiosOpAttr* attr); +bool APS5_VABI sceFiosGetThreadDefaultOpAttr(SceFiosOpAttr* outAttr); +void APS5_VABI sceFiosGetDefaultOpAttr(SceFiosOpAttr* outAttr); +void APS5_VABI sceFiosSuspend(); +void APS5_VABI sceFiosResume(); +std::uint32_t APS5_VABI sceFiosGetSuspendCount(); +bool APS5_VABI sceFiosIsSuspended(); +void APS5_VABI sceFiosCancelAllOps(); +void APS5_VABI sceFiosShutdownAndCancelOps(); +void APS5_VABI sceFiosCloseAllFiles(); +bool APS5_VABI sceFiosIsIdle(); +std::uint32_t APS5_VABI sceFiosGetAllFHs(SceFiosFH* outArray, std::uint32_t arraySize); +std::uint32_t APS5_VABI sceFiosGetAllDHs(SceFiosDH* outArray, std::uint32_t arraySize); +std::uint32_t APS5_VABI sceFiosGetAllOps(SceFiosOp* outArray, std::uint32_t arraySize); +bool APS5_VABI sceFiosIsValidHandle(SceFiosHandle handle); +bool APS5_VABI sceFiosOpIsDone(SceFiosOp op); +int APS5_VABI sceFiosOpWait(SceFiosOp op); +void APS5_VABI sceFiosOpDelete(SceFiosOp op); +int APS5_VABI sceFiosOpSyncWait(SceFiosOp op); +std::int64_t APS5_VABI sceFiosOpSyncWaitForIO(SceFiosOp op); +int APS5_VABI sceFiosOpGetError(SceFiosOp op); +void APS5_VABI sceFiosOpCancel(SceFiosOp op); +bool APS5_VABI sceFiosOpIsCancelled(SceFiosOp op); +const SceFiosOpAttr* APS5_VABI sceFiosOpGetAttr(SceFiosOp op); +const char* APS5_VABI sceFiosOpGetPath(SceFiosOp op); +void* APS5_VABI sceFiosOpGetBuffer(SceFiosOp op); +std::int64_t APS5_VABI sceFiosOpGetOffset(SceFiosOp op); +std::int64_t APS5_VABI sceFiosOpGetRequestCount(SceFiosOp op); +std::int64_t APS5_VABI sceFiosOpGetActualCount(SceFiosOp op); +SceFiosOp APS5_VABI sceFiosExists(const SceFiosOpAttr* attr, const char* path, bool* outExists); +bool APS5_VABI sceFiosExistsSync(const SceFiosOpAttr* attr, const char* path); +SceFiosOp APS5_VABI sceFiosFileExists(const SceFiosOpAttr* attr, const char* path, bool* outExists); +bool APS5_VABI sceFiosFileExistsSync(const SceFiosOpAttr* attr, const char* path); +SceFiosOp APS5_VABI sceFiosDirectoryExists(const SceFiosOpAttr* attr, const char* path, bool* outExists); +bool APS5_VABI sceFiosDirectoryExistsSync(const SceFiosOpAttr* attr, const char* path); +SceFiosOp APS5_VABI sceFiosFileGetSize(const SceFiosOpAttr* attr, const char* path); +SceFiosSize APS5_VABI sceFiosFileGetSizeSync(const SceFiosOpAttr* attr, const char* path); +SceFiosOp APS5_VABI sceFiosStat(const SceFiosOpAttr* attr, const char* path, SceFiosStat* outStatus); +int APS5_VABI sceFiosStatSync(const SceFiosOpAttr* attr, const char* path, SceFiosStat* outStatus); +SceFiosOp APS5_VABI sceFiosDelete(const SceFiosOpAttr* attr, const char* path); +int APS5_VABI sceFiosDeleteSync(const SceFiosOpAttr* attr, const char* path); +SceFiosOp APS5_VABI sceFiosFileDelete(const SceFiosOpAttr* attr, const char* path); +int APS5_VABI sceFiosFileDeleteSync(const SceFiosOpAttr* attr, const char* path); +SceFiosOp APS5_VABI sceFiosDirectoryCreate(const SceFiosOpAttr* attr, const char* path); +int APS5_VABI sceFiosDirectoryCreateSync(const SceFiosOpAttr* attr, const char* path); +SceFiosOp APS5_VABI sceFiosDirectoryDelete(const SceFiosOpAttr* attr, const char* path); +int APS5_VABI sceFiosDirectoryDeleteSync(const SceFiosOpAttr* attr, const char* path); +SceFiosOp APS5_VABI sceFiosRename(const SceFiosOpAttr* attr, const char* oldPath, const char* newPath); +int APS5_VABI sceFiosRenameSync(const SceFiosOpAttr* attr, const char* oldPath, const char* newPath); +SceFiosOp APS5_VABI sceFiosFileRead(const SceFiosOpAttr* attr, const char* path, void* buffer, SceFiosSize length, SceFiosOffset offset); +SceFiosSize APS5_VABI sceFiosFileReadSync(const SceFiosOpAttr* attr, const char* path, void* buffer, SceFiosSize length, SceFiosOffset offset); +SceFiosOp APS5_VABI sceFiosFileWrite(const SceFiosOpAttr* attr, const char* path, const void* buffer, SceFiosSize length, SceFiosOffset offset); +SceFiosSize APS5_VABI sceFiosFileWriteSync(const SceFiosOpAttr* attr, const char* path, const void* buffer, SceFiosSize length, SceFiosOffset offset); +SceFiosOp APS5_VABI sceFiosFileTruncate(const SceFiosOpAttr* attr, const char* path, SceFiosSize length); +int APS5_VABI sceFiosFileTruncateSync(const SceFiosOpAttr* attr, const char* path, SceFiosSize length); +SceFiosOp APS5_VABI sceFiosFHOpen(const SceFiosOpAttr* attr, SceFiosFH* outFH, const char* path, const SceFiosOpenParams* openParams); +int APS5_VABI sceFiosFHOpenSync(const SceFiosOpAttr* attr, SceFiosFH* outFH, const char* path, const SceFiosOpenParams* openParams); +SceFiosOp APS5_VABI sceFiosFHOpenWithMode(const SceFiosOpAttr* attr, SceFiosFH* outFH, const char* path, const SceFiosOpenParams* openParams, std::int32_t nativeMode); +int APS5_VABI sceFiosFHOpenWithModeSync(const SceFiosOpAttr* attr, SceFiosFH* outFH, const char* path, const SceFiosOpenParams* openParams, std::int32_t nativeMode); +SceFiosOp APS5_VABI sceFiosFHClose(const SceFiosOpAttr* attr, SceFiosFH fh); +int APS5_VABI sceFiosFHCloseSync(const SceFiosOpAttr* attr, SceFiosFH fh); +SceFiosOp APS5_VABI sceFiosFHStat(const SceFiosOpAttr* attr, SceFiosFH fh, SceFiosStat* outStatus); +int APS5_VABI sceFiosFHStatSync(const SceFiosOpAttr* attr, SceFiosFH fh, SceFiosStat* outStatus); +SceFiosOp APS5_VABI sceFiosFHTruncate(const SceFiosOpAttr* attr, SceFiosFH fh, SceFiosSize length); +int APS5_VABI sceFiosFHTruncateSync(const SceFiosOpAttr* attr, SceFiosFH fh, SceFiosSize length); +SceFiosOp APS5_VABI sceFiosFHSync(const SceFiosOpAttr* attr, SceFiosFH fh); +int APS5_VABI sceFiosFHSyncSync(const SceFiosOpAttr* attr, SceFiosFH fh); +SceFiosOp APS5_VABI sceFiosFHRead(const SceFiosOpAttr* attr, SceFiosFH fh, void* buffer, SceFiosSize length); +SceFiosSize APS5_VABI sceFiosFHReadSync(const SceFiosOpAttr* attr, SceFiosFH fh, void* buffer, SceFiosSize length); +SceFiosOp APS5_VABI sceFiosFHWrite(const SceFiosOpAttr* attr, SceFiosFH fh, const void* buffer, SceFiosSize length); +SceFiosSize APS5_VABI sceFiosFHWriteSync(const SceFiosOpAttr* attr, SceFiosFH fh, const void* buffer, SceFiosSize length); +SceFiosOp APS5_VABI sceFiosFHPread(const SceFiosOpAttr* attr, SceFiosFH fh, void* buffer, SceFiosSize length, SceFiosOffset offset); +SceFiosSize APS5_VABI sceFiosFHPreadSync(const SceFiosOpAttr* attr, SceFiosFH fh, void* buffer, SceFiosSize length, SceFiosOffset offset); +SceFiosOp APS5_VABI sceFiosFHPwrite(const SceFiosOpAttr* attr, SceFiosFH fh, const void* buffer, SceFiosSize length, SceFiosOffset offset); +SceFiosSize APS5_VABI sceFiosFHPwriteSync(const SceFiosOpAttr* attr, SceFiosFH fh, const void* buffer, SceFiosSize length, SceFiosOffset offset); +SceFiosOp APS5_VABI sceFiosFHReadv(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt); +SceFiosSize APS5_VABI sceFiosFHReadvSync(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt); +SceFiosOp APS5_VABI sceFiosFHWritev(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt); +SceFiosSize APS5_VABI sceFiosFHWritevSync(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt); +SceFiosOp APS5_VABI sceFiosFHPreadv(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt, SceFiosOffset offset); +SceFiosSize APS5_VABI sceFiosFHPreadvSync(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt, SceFiosOffset offset); +SceFiosOp APS5_VABI sceFiosFHPwritev(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt, SceFiosOffset offset); +SceFiosSize APS5_VABI sceFiosFHPwritevSync(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt, SceFiosOffset offset); +SceFiosOffset APS5_VABI sceFiosFHSeek(SceFiosFH fh, SceFiosOffset offset, std::int32_t whence); +SceFiosOffset APS5_VABI sceFiosFHTell(SceFiosFH fh); +SceFiosSize APS5_VABI sceFiosFHGetSize(SceFiosFH fh); +const char* APS5_VABI sceFiosFHGetPath(SceFiosFH fh); +const SceFiosOpenParams* APS5_VABI sceFiosFHGetOpenParams(SceFiosFH fh); +SceFiosOp APS5_VABI sceFiosDHOpen(const SceFiosOpAttr* attr, SceFiosDH* outDH, const char* path, SceFiosBuffer buffer); +int APS5_VABI sceFiosDHOpenSync(const SceFiosOpAttr* attr, SceFiosDH* outDH, const char* path, SceFiosBuffer buffer); +SceFiosOp APS5_VABI sceFiosDHRead(const SceFiosOpAttr* attr, SceFiosDH dh, SceFiosDirEntry* outEntry); +int APS5_VABI sceFiosDHReadSync(const SceFiosOpAttr* attr, SceFiosDH dh, SceFiosDirEntry* outEntry); +SceFiosOp APS5_VABI sceFiosDHClose(const SceFiosOpAttr* attr, SceFiosDH dh); +int APS5_VABI sceFiosDHCloseSync(const SceFiosOpAttr* attr, SceFiosDH dh); +const char* APS5_VABI sceFiosDHGetPath(SceFiosDH dh); +SceFiosDate APS5_VABI sceFiosDateGetCurrent(); +SceFiosDate APS5_VABI sceFiosDateFromComponents(const SceFiosTm* components); +SceFiosTm* APS5_VABI sceFiosDateToComponents(SceFiosDate date, SceFiosTm* outComponents); +SceFiosTimeInterval APS5_VABI sceFiosTimeIntervalFromNanoseconds(std::int64_t nanoseconds); +std::int64_t APS5_VABI sceFiosTimeIntervalToNanoseconds(SceFiosTimeInterval interval); + int APS5_VABI sceFiosArchiveGetDecompressorThreadCount(); int APS5_VABI sceFiosArchiveGetMountBufferSize(); int APS5_VABI sceFiosArchiveGetMountBufferSizeSync(); @@ -27,120 +135,27 @@ 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(); @@ -153,24 +168,11 @@ 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/include/Types.hpp b/core/libs/prx/libSceFios2/include/Types.hpp new file mode 100644 index 0000000000..f112aace65 --- /dev/null +++ b/core/libs/prx/libSceFios2/include/Types.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include "prx/libc/include/general/VabiMacros.hpp" + +using SceFiosHandle = std::int32_t; +using SceFiosOp = SceFiosHandle; +using SceFiosFH = SceFiosHandle; +using SceFiosDH = SceFiosHandle; +using SceFiosTime = std::int64_t; +using SceFiosTimeInterval = std::int64_t; +using SceFiosDate = std::uint64_t; +using SceFiosSize = std::int64_t; +using SceFiosOffset = std::int64_t; +using SceFiosOpEvent = std::uint8_t; + +using SceFiosOpCallback = int (APS5_VABI*)(void* context, SceFiosOp op, SceFiosOpEvent event, int error); + +constexpr std::int32_t SCE_FIOS_OK = 0; +constexpr std::int32_t SCE_FIOS_ERROR_UNIMPLEMENTED = static_cast(0x80820000u); +constexpr std::int32_t SCE_FIOS_ERROR_CANT_ALLOCATE_OP = static_cast(0x80820001u); +constexpr std::int32_t SCE_FIOS_ERROR_CANT_ALLOCATE_FH = static_cast(0x80820002u); +constexpr std::int32_t SCE_FIOS_ERROR_CANT_ALLOCATE_DH = static_cast(0x80820003u); +constexpr std::int32_t SCE_FIOS_ERROR_BAD_PATH = static_cast(0x80820005u); +constexpr std::int32_t SCE_FIOS_ERROR_BAD_PTR = static_cast(0x80820006u); +constexpr std::int32_t SCE_FIOS_ERROR_BAD_OFFSET = static_cast(0x80820007u); +constexpr std::int32_t SCE_FIOS_ERROR_BAD_SIZE = static_cast(0x80820008u); +constexpr std::int32_t SCE_FIOS_ERROR_BAD_IOVCNT = static_cast(0x80820009u); +constexpr std::int32_t SCE_FIOS_ERROR_BAD_OP = static_cast(0x8082000Au); +constexpr std::int32_t SCE_FIOS_ERROR_BAD_FH = static_cast(0x8082000Bu); +constexpr std::int32_t SCE_FIOS_ERROR_BAD_DH = static_cast(0x8082000Cu); +constexpr std::int32_t SCE_FIOS_ERROR_NOT_A_FILE = static_cast(0x8082000Eu); +constexpr std::int32_t SCE_FIOS_ERROR_NOT_A_DIRECTORY = static_cast(0x8082000Fu); +constexpr std::int32_t SCE_FIOS_ERROR_EOF = static_cast(0x80820010u); +constexpr std::int32_t SCE_FIOS_ERROR_CANCELLED = static_cast(0x80820012u); +constexpr std::int32_t SCE_FIOS_ERROR_ACCESS = static_cast(0x80820013u); +constexpr std::int32_t SCE_FIOS_ERROR_READ_ONLY = static_cast(0x80820015u); +constexpr std::int32_t SCE_FIOS_ERROR_WRITE_ONLY = static_cast(0x80820016u); +constexpr std::int32_t SCE_FIOS_ERROR_PATH_TOO_LONG = static_cast(0x80820018u); +constexpr std::int32_t SCE_FIOS_ERROR_BAD_FLAGS = static_cast(0x80820021u); +constexpr std::int32_t SCE_FIOS_ERROR_UNKNOWN = static_cast(0x80820022u); +constexpr std::int32_t SCE_FIOS_ERROR_ALREADY_EXISTS = static_cast(0x80820023u); + +constexpr SceFiosOpEvent SCE_FIOS_OPEVENT_COMPLETE = 1; +constexpr SceFiosOpEvent SCE_FIOS_OPEVENT_DELETE = 2; + +constexpr std::int32_t SCE_FIOS_SEEK_SET = 0; +constexpr std::int32_t SCE_FIOS_SEEK_CUR = 1; +constexpr std::int32_t SCE_FIOS_SEEK_END = 2; + +constexpr std::uint32_t SCE_FIOS_O_READ = 1u << 0; +constexpr std::uint32_t SCE_FIOS_O_WRITE = 1u << 1; +constexpr std::uint32_t SCE_FIOS_O_APPEND = 1u << 2; +constexpr std::uint32_t SCE_FIOS_O_CREAT = 1u << 3; +constexpr std::uint32_t SCE_FIOS_O_TRUNC = 1u << 4; +constexpr std::uint32_t SCE_FIOS_O_DIRECT = 1u << 12; + +constexpr std::uint32_t SCE_FIOS_STAT_DIRECTORY = 1u << 0; + +constexpr std::size_t SCE_FIOS_PATH_MAX = 1024; + +struct SceFiosBuffer { + void* pPtr; + std::uint64_t length; +}; + +struct SceFiosOpAttr { + SceFiosTime deadline; + SceFiosOpCallback pCallback; + void* pCallbackContext; + std::int32_t priority : 8; + std::uint32_t opflags : 24; + std::uint32_t userTag; + void* userPtr; + void* pReserved; +}; + +struct SceFiosOpenParams { + std::uint32_t openFlags : 16; + std::uint32_t opFlags : 16; + std::uint32_t reserved; + SceFiosBuffer buffer; +}; + +struct SceFiosStat { + SceFiosOffset fileSize; + SceFiosDate accessDate; + SceFiosDate modificationDate; + SceFiosDate creationDate; + std::uint32_t statFlags; + std::uint32_t reserved; + std::int64_t uid; + std::int64_t gid; + std::int64_t dev; + std::int64_t ino; + std::int64_t mode; +}; + +struct SceFiosDirEntry { + SceFiosOffset fileSize; + std::uint32_t statFlags; + std::uint16_t nameLength; + std::uint16_t fullPathLength; + std::uint16_t offsetToName; + std::uint16_t reserved[3]; + char fullPath[SCE_FIOS_PATH_MAX]; +}; + +struct SceFiosParams; + +static_assert(sizeof(SceFiosBuffer) == 16); +static_assert(sizeof(SceFiosOpAttr) == 48); +static_assert(sizeof(SceFiosOpenParams) == 24); +static_assert(sizeof(SceFiosStat) == 80); +static_assert(sizeof(SceFiosDirEntry) == 24 + SCE_FIOS_PATH_MAX); + +struct SceFiosTm { + std::int32_t tm_sec; + std::int32_t tm_min; + std::int32_t tm_hour; + std::int32_t tm_mday; + std::int32_t tm_mon; + std::int32_t tm_year; + std::int32_t tm_wday; + std::int32_t tm_yday; + std::int32_t tm_isdst; +}; diff --git a/core/libs/prx/libSceFios2/src/Control.cpp b/core/libs/prx/libSceFios2/src/Control.cpp new file mode 100644 index 0000000000..5a46b93b69 --- /dev/null +++ b/core/libs/prx/libSceFios2/src/Control.cpp @@ -0,0 +1,175 @@ +#include "prx/libSceFios2/include/Api.hpp" +#include "Runtime.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" +#include +#include + +extern "C" { + +int APS5_VABI sceFiosInitialize(const SceFiosParams* params) { + return Fios::Initialize(params) ? SCE_FIOS_OK : SCE_FIOS_ERROR_BAD_PTR; +} + +void APS5_VABI sceFiosTerminate() { + Fios::Terminate(); +} + +bool APS5_VABI sceFiosIsInitialized(SceFiosParams* outParams) { + return Fios::IsInitialized(outParams); +} + +void APS5_VABI sceFiosUpdateParameters(const SceFiosParams* params) { + Fios::UpdateParameters(params); +} + +void APS5_VABI sceFiosSetGlobalDefaultOpAttr(const SceFiosOpAttr* attr) { + Fios::SetGlobalAttr(attr); +} + +bool APS5_VABI sceFiosGetGlobalDefaultOpAttr(SceFiosOpAttr* outAttr) { + SceFiosOpAttr attr{}; + const bool present = Fios::GlobalAttr(attr); + if (outAttr != nullptr && present) Fios::CopyOut(outAttr, &attr, sizeof(attr)); + return present; +} + +void APS5_VABI sceFiosSetThreadDefaultOpAttr(const SceFiosOpAttr* attr) { + Fios::SetThreadAttr(attr); +} + +bool APS5_VABI sceFiosGetThreadDefaultOpAttr(SceFiosOpAttr* outAttr) { + SceFiosOpAttr attr{}; + const bool present = Fios::ThreadAttr(attr); + if (outAttr != nullptr && present) Fios::CopyOut(outAttr, &attr, sizeof(attr)); + return present; +} + +void APS5_VABI sceFiosGetDefaultOpAttr(SceFiosOpAttr* outAttr) { + SceFiosOpAttr attr{}; + if (!Fios::ThreadAttr(attr)) Fios::GlobalAttr(attr); + if (outAttr != nullptr) Fios::CopyOut(outAttr, &attr, sizeof(attr)); +} + +void APS5_VABI sceFiosSuspend() { + Fios::Suspend(); +} + +void APS5_VABI sceFiosResume() { + Fios::Resume(); +} + +std::uint32_t APS5_VABI sceFiosGetSuspendCount() { + return Fios::SuspendCount(); +} + +bool APS5_VABI sceFiosIsSuspended() { + return Fios::SuspendCount() != 0; +} + +void APS5_VABI sceFiosCancelAllOps() { + Fios::CancelAll(); +} + +void APS5_VABI sceFiosShutdownAndCancelOps() { + Fios::Terminate(); +} + +void APS5_VABI sceFiosCloseAllFiles() { + Fios::CloseAllFiles(); +} + +bool APS5_VABI sceFiosIsIdle() { + return Fios::IsIdle(); +} + +static std::uint32_t CopyHandles(std::uint32_t (*list)(SceFiosHandle*, std::uint32_t), SceFiosHandle* out, std::uint32_t capacity) { + std::vector handles(capacity); + const std::uint32_t total = list(handles.data(), capacity); + const std::uint32_t count = std::min(total, capacity); + if (out != nullptr && count != 0) Fios::CopyOut(out, handles.data(), count * sizeof(SceFiosHandle)); + return total; +} + +std::uint32_t APS5_VABI sceFiosGetAllFHs(SceFiosFH* outArray, std::uint32_t arraySize) { + return CopyHandles(Fios::ListFiles, outArray, arraySize); +} + +std::uint32_t APS5_VABI sceFiosGetAllDHs(SceFiosDH* outArray, std::uint32_t arraySize) { + return CopyHandles(Fios::ListDirectories, outArray, arraySize); +} + +std::uint32_t APS5_VABI sceFiosGetAllOps(SceFiosOp* outArray, std::uint32_t arraySize) { + return CopyHandles(Fios::ListOps, outArray, arraySize); +} + +bool APS5_VABI sceFiosIsValidHandle(SceFiosHandle handle) { + return Fios::IsValidHandle(handle); +} + +bool APS5_VABI sceFiosOpIsDone(SceFiosOp op) { + return Fios::OpDone(op); +} + +int APS5_VABI sceFiosOpWait(SceFiosOp op) { + return Fios::Wait(op); +} + +void APS5_VABI sceFiosOpDelete(SceFiosOp op) { + Fios::DeleteOp(op); +} + +int APS5_VABI sceFiosOpSyncWait(SceFiosOp op) { + Fios::Completion completion; + if (!Fios::SyncWait(op, completion)) return SCE_FIOS_ERROR_BAD_OP; + return completion.error != SCE_FIOS_OK ? completion.error : static_cast(completion.result); +} + +std::int64_t APS5_VABI sceFiosOpSyncWaitForIO(SceFiosOp op) { + Fios::Completion completion; + if (!Fios::SyncWait(op, completion)) return SCE_FIOS_ERROR_BAD_OP; + return completion.error != SCE_FIOS_OK ? completion.error : completion.result; +} + +int APS5_VABI sceFiosOpGetError(SceFiosOp op) { + std::int32_t error = SCE_FIOS_ERROR_BAD_OP; + Fios::OpError(op, error); + return error; +} + +void APS5_VABI sceFiosOpCancel(SceFiosOp op) { + Fios::CancelOp(op); +} + +bool APS5_VABI sceFiosOpIsCancelled(SceFiosOp op) { + return Fios::OpIsCancelled(op); +} + +const SceFiosOpAttr* APS5_VABI sceFiosOpGetAttr(SceFiosOp op) { + return Fios::OpAttr(op); +} + +const char* APS5_VABI sceFiosOpGetPath(SceFiosOp op) { + return Fios::OpPath(op); +} + +void* APS5_VABI sceFiosOpGetBuffer(SceFiosOp op) { + return Fios::OpBuffer(op); +} + +std::int64_t APS5_VABI sceFiosOpGetOffset(SceFiosOp op) { + std::int64_t request = 0, actual = 0, offset = 0; + return Fios::QueryOp(op, request, actual, offset) ? offset : SCE_FIOS_ERROR_BAD_OP; +} + +std::int64_t APS5_VABI sceFiosOpGetRequestCount(SceFiosOp op) { + std::int64_t request = 0, actual = 0, offset = 0; + return Fios::QueryOp(op, request, actual, offset) ? request : SCE_FIOS_ERROR_BAD_OP; +} + +std::int64_t APS5_VABI sceFiosOpGetActualCount(SceFiosOp op) { + std::int64_t request = 0, actual = 0, offset = 0; + return Fios::QueryOp(op, request, actual, offset) ? actual : SCE_FIOS_ERROR_BAD_OP; +} + +} diff --git a/core/libs/prx/libSceFios2/src/DirectoryHandles.cpp b/core/libs/prx/libSceFios2/src/DirectoryHandles.cpp new file mode 100644 index 0000000000..f35909ad40 --- /dev/null +++ b/core/libs/prx/libSceFios2/src/DirectoryHandles.cpp @@ -0,0 +1,183 @@ +#include "prx/libSceFios2/include/Api.hpp" +#include "Runtime.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include +#include + +namespace { + +constexpr std::size_t DirentHeaderBytes = 8; +constexpr int DirentBufferBytes = 8192; +constexpr std::uint64_t ListingOverheadBytes = 16; + +std::int32_t ListNames(const std::string& path, std::vector& names) { + const int descriptor = sceKernelOpen(path.c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); + if (descriptor < 0) return Fios::FromKernelError(descriptor); + std::vector buffer(DirentBufferBytes); + std::int32_t error = SCE_FIOS_OK; + for (;;) { + const int count = sceKernelGetdirentries(descriptor, buffer.data(), static_cast(buffer.size()), nullptr); + if (count < 0) { + error = Fios::FromKernelError(count); + break; + } + if (count == 0) break; + for (int offset = 0; offset + static_cast(DirentHeaderBytes) <= count;) { + std::uint16_t length = 0; + std::memcpy(&length, buffer.data() + offset + 4, sizeof(length)); + const auto nameLength = static_cast(buffer[static_cast(offset) + 7]); + if (length < DirentHeaderBytes) break; + std::string name(buffer.data() + offset + DirentHeaderBytes, nameLength); + if (name != "." && name != "..") names.push_back(std::move(name)); + offset += length; + } + } + sceKernelClose(descriptor); + return error; +} + +std::uint64_t RequiredBytes(const std::vector& names) { + std::uint64_t bytes = ListingOverheadBytes; + for (const std::string& name : names) bytes += name.size() + 1; + return bytes; +} + +SceFiosOp StartOpen(const SceFiosOpAttr* attr, SceFiosDH* outDH, const char* path, SceFiosBuffer buffer) { + Fios::OpInfo info; + info.buffer = buffer.pPtr; + info.request = static_cast(buffer.length); + const std::int32_t invalid = outDH == nullptr ? SCE_FIOS_ERROR_BAD_PTR : Fios::CopyPath(path, info.path); + const std::string target = info.path; + return Fios::Submit(attr, std::move(info), [=](Fios::Completion& completion) { + if (invalid != SCE_FIOS_OK) { + completion.error = invalid; + return; + } + SceFiosStat stat{}; + if (const std::int32_t error = Fios::StatPath(target, stat)) { + completion.error = error; + return; + } + if ((stat.statFlags & SCE_FIOS_STAT_DIRECTORY) == 0) { + completion.error = SCE_FIOS_ERROR_NOT_A_DIRECTORY; + return; + } + auto directory = std::make_shared(); + directory->path = target; + if (const std::int32_t error = ListNames(target, directory->names)) { + completion.error = error; + return; + } + const std::uint64_t required = RequiredBytes(directory->names); + completion.result = static_cast(required); + if (buffer.pPtr == nullptr || buffer.length < required) { + completion.error = SCE_FIOS_ERROR_BAD_SIZE; + return; + } + const SceFiosDH handle = Fios::AddDirectory(directory); + if (handle < 0) { + completion.error = handle; + return; + } + if (!Fios::CopyOut(outDH, &handle, sizeof(handle))) { + Fios::RemoveDirectory(handle); + completion.error = SCE_FIOS_ERROR_BAD_PTR; + } + }); +} + +SceFiosOp StartRead(const SceFiosOpAttr* attr, SceFiosDH handle, SceFiosDirEntry* outEntry) { + Fios::OpInfo info; + info.buffer = outEntry; + if (const auto directory = Fios::FindDirectory(handle)) info.path = directory->path; + return Fios::Submit(attr, std::move(info), [handle, outEntry](Fios::Completion& completion) { + const auto directory = Fios::FindDirectory(handle); + if (!directory) { + completion.error = SCE_FIOS_ERROR_BAD_DH; + return; + } + if (outEntry == nullptr) { + completion.error = SCE_FIOS_ERROR_BAD_PTR; + return; + } + const std::lock_guard lock(directory->mutex); + if (directory->next >= directory->names.size()) { + completion.error = SCE_FIOS_ERROR_EOF; + return; + } + std::string prefix = directory->path; + while (prefix.size() > 1 && prefix.back() == '/') prefix.pop_back(); + if (prefix.back() != '/') prefix += '/'; + const std::string& name = directory->names[directory->next]; + const std::string full = prefix + name; + if (full.size() >= SCE_FIOS_PATH_MAX) { + completion.error = SCE_FIOS_ERROR_PATH_TOO_LONG; + return; + } + SceFiosStat stat{}; + Fios::StatPath(full, stat); + SceFiosDirEntry entry{}; + entry.fileSize = stat.fileSize; + entry.statFlags = stat.statFlags; + entry.nameLength = static_cast(name.size()); + entry.fullPathLength = static_cast(full.size()); + entry.offsetToName = static_cast(prefix.size()); + std::memcpy(entry.fullPath, full.c_str(), full.size()); + if (!Fios::CopyOut(outEntry, &entry, sizeof(entry))) { + completion.error = SCE_FIOS_ERROR_BAD_PTR; + return; + } + ++directory->next; + }); +} + +SceFiosOp StartClose(const SceFiosOpAttr* attr, SceFiosDH handle) { + Fios::OpInfo info; + if (const auto directory = Fios::FindDirectory(handle)) info.path = directory->path; + return Fios::Submit(attr, std::move(info), [handle](Fios::Completion& completion) { + if (!Fios::RemoveDirectory(handle)) completion.error = SCE_FIOS_ERROR_BAD_DH; + }); +} + +int StatusSync(SceFiosOp op) { + return Fios::SyncStatus(op); +} + +} + +extern "C" { + +SceFiosOp APS5_VABI sceFiosDHOpen(const SceFiosOpAttr* attr, SceFiosDH* outDH, const char* path, SceFiosBuffer buffer) { + return StartOpen(attr, outDH, path, buffer); +} + +int APS5_VABI sceFiosDHOpenSync(const SceFiosOpAttr* attr, SceFiosDH* outDH, const char* path, SceFiosBuffer buffer) { + return StatusSync(StartOpen(attr, outDH, path, buffer)); +} + +SceFiosOp APS5_VABI sceFiosDHRead(const SceFiosOpAttr* attr, SceFiosDH dh, SceFiosDirEntry* outEntry) { + return StartRead(attr, dh, outEntry); +} + +int APS5_VABI sceFiosDHReadSync(const SceFiosOpAttr* attr, SceFiosDH dh, SceFiosDirEntry* outEntry) { + return StatusSync(StartRead(attr, dh, outEntry)); +} + +SceFiosOp APS5_VABI sceFiosDHClose(const SceFiosOpAttr* attr, SceFiosDH dh) { + return StartClose(attr, dh); +} + +int APS5_VABI sceFiosDHCloseSync(const SceFiosOpAttr* attr, SceFiosDH dh) { + return StatusSync(StartClose(attr, dh)); +} + +const char* APS5_VABI sceFiosDHGetPath(SceFiosDH dh) { + const auto directory = Fios::FindDirectory(dh); + return directory ? directory->path.c_str() : nullptr; +} + +} diff --git a/core/libs/prx/libSceFios2/src/FileHandles.cpp b/core/libs/prx/libSceFios2/src/FileHandles.cpp new file mode 100644 index 0000000000..2af25b2e28 --- /dev/null +++ b/core/libs/prx/libSceFios2/src/FileHandles.cpp @@ -0,0 +1,356 @@ +#include "prx/libSceFios2/include/Api.hpp" +#include "Runtime.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" +#include +#include + +namespace { + +constexpr std::int32_t DefaultNativeMode = -1; +constexpr std::uint16_t CreateMode = 0x1ff; +constexpr std::int64_t MaxVectors = 1024; + +using HandleWork = std::function; + +bool CanRead(const Fios::FileHandle& file) { + return (file.params.openFlags & SCE_FIOS_O_READ) != 0; +} + +bool CanWrite(const Fios::FileHandle& file) { + return (file.params.openFlags & SCE_FIOS_O_WRITE) != 0; +} + +SceFiosOp SubmitHandle(const SceFiosOpAttr* attr, SceFiosFH handle, std::int64_t request, std::int64_t offset, void* buffer, std::int32_t invalid, HandleWork work) { + Fios::OpInfo info; + info.request = request; + info.offset = offset; + info.buffer = buffer; + if (const auto file = Fios::FindFile(handle)) info.path = file->path; + return Fios::Submit(attr, std::move(info), [handle, invalid, work = std::move(work)](Fios::Completion& completion) { + if (invalid != SCE_FIOS_OK) { + completion.error = invalid; + return; + } + const auto file = Fios::FindFile(handle); + if (!file) { + completion.error = SCE_FIOS_ERROR_BAD_FH; + return; + } + const std::lock_guard lock(file->mutex); + if (file->descriptor < 0) { + completion.error = SCE_FIOS_ERROR_BAD_FH; + return; + } + work(*file, completion); + }); +} + +std::int32_t CheckRange(const void* buffer, SceFiosSize length, SceFiosOffset offset, bool positioned) { + if (length < 0) return SCE_FIOS_ERROR_BAD_SIZE; + if (positioned && offset < 0) return SCE_FIOS_ERROR_BAD_OFFSET; + if (buffer == nullptr && length != 0) return SCE_FIOS_ERROR_BAD_PTR; + return SCE_FIOS_OK; +} + +void Transfer(Fios::FileHandle& file, Fios::Completion& completion, void* buffer, SceFiosSize length, SceFiosOffset offset, bool positioned, bool write) { + if (write ? !CanWrite(file) : !CanRead(file)) { + completion.error = write ? SCE_FIOS_ERROR_READ_ONLY : SCE_FIOS_ERROR_WRITE_ONLY; + return; + } + const std::int64_t count = write ? Fios::WriteFully(file.descriptor, buffer, static_cast(length), offset, positioned) + : Fios::ReadFully(file.descriptor, buffer, static_cast(length), offset, positioned); + if (count < 0) completion.error = static_cast(count); + else completion.result = count; +} + +SceFiosOp StartTransfer(const SceFiosOpAttr* attr, SceFiosFH handle, void* buffer, SceFiosSize length, SceFiosOffset offset, bool positioned, bool write) { + const std::int32_t invalid = CheckRange(buffer, length, offset, positioned); + return SubmitHandle(attr, handle, length, positioned ? offset : 0, buffer, invalid, [=](Fios::FileHandle& file, Fios::Completion& completion) { + Transfer(file, completion, buffer, length, offset, positioned, write); + }); +} + +SceFiosOp StartVectors(const SceFiosOpAttr* attr, SceFiosFH handle, const SceFiosBuffer* iov, int iovcnt, SceFiosOffset offset, bool positioned, bool write) { + std::int32_t invalid = SCE_FIOS_OK; + std::vector vectors; + if (iovcnt <= 0 || iovcnt > MaxVectors) invalid = SCE_FIOS_ERROR_BAD_IOVCNT; + else if (iov == nullptr) invalid = SCE_FIOS_ERROR_BAD_PTR; + else if (positioned && offset < 0) invalid = SCE_FIOS_ERROR_BAD_OFFSET; + else { + vectors.resize(static_cast(iovcnt)); + if (!Fios::CopyIn(vectors.data(), iov, vectors.size() * sizeof(SceFiosBuffer))) invalid = SCE_FIOS_ERROR_BAD_PTR; + } + std::int64_t request = 0; + for (const SceFiosBuffer& vector : vectors) { + if (vector.pPtr == nullptr && vector.length != 0) invalid = SCE_FIOS_ERROR_BAD_PTR; + if (vector.length > static_cast(INT64_MAX) || request > INT64_MAX - static_cast(vector.length)) invalid = SCE_FIOS_ERROR_BAD_SIZE; + else request += static_cast(vector.length); + } + return SubmitHandle(attr, handle, request, positioned ? offset : 0, nullptr, invalid, [=](Fios::FileHandle& file, Fios::Completion& completion) { + if (write ? !CanWrite(file) : !CanRead(file)) { + completion.error = write ? SCE_FIOS_ERROR_READ_ONLY : SCE_FIOS_ERROR_WRITE_ONLY; + return; + } + std::int64_t position = offset; + for (const SceFiosBuffer& vector : vectors) { + const std::int64_t count = write ? Fios::WriteFully(file.descriptor, vector.pPtr, vector.length, position, positioned) + : Fios::ReadFully(file.descriptor, vector.pPtr, vector.length, position, positioned); + if (count < 0) { + if (completion.result == 0) completion.error = static_cast(count); + return; + } + completion.result += count; + position += count; + if (static_cast(count) < vector.length) return; + } + }); +} + +SceFiosOp StartOpen(const SceFiosOpAttr* attr, SceFiosFH* outFH, const char* path, const SceFiosOpenParams* openParams, std::int32_t nativeMode) { + Fios::OpInfo info; + std::int32_t invalid = outFH == nullptr ? SCE_FIOS_ERROR_BAD_PTR : Fios::CopyPath(path, info.path); + SceFiosOpenParams params{}; + if (openParams == nullptr) params.openFlags = SCE_FIOS_O_READ; + else if (!Fios::CopyIn(¶ms, openParams, sizeof(params)) && invalid == SCE_FIOS_OK) invalid = SCE_FIOS_ERROR_BAD_PTR; + const std::string target = info.path; + return Fios::Submit(attr, std::move(info), [=](Fios::Completion& completion) { + if (invalid != SCE_FIOS_OK) { + completion.error = invalid; + return; + } + const std::uint32_t flags = params.openFlags; + std::int32_t native = (flags & (SCE_FIOS_O_READ | SCE_FIOS_O_WRITE)) == (SCE_FIOS_O_READ | SCE_FIOS_O_WRITE) ? SCE_KERNEL_O_RDWR + : (flags & SCE_FIOS_O_WRITE) != 0 ? SCE_KERNEL_O_WRONLY : SCE_KERNEL_O_RDONLY; + if ((flags & SCE_FIOS_O_APPEND) != 0) native |= SCE_KERNEL_O_APPEND; + if ((flags & SCE_FIOS_O_CREAT) != 0) native |= SCE_KERNEL_O_CREAT; + if ((flags & SCE_FIOS_O_TRUNC) != 0) native |= SCE_KERNEL_O_TRUNC; + const std::uint16_t mode = nativeMode == DefaultNativeMode ? CreateMode : static_cast(nativeMode); + const int descriptor = sceKernelOpen(target.c_str(), native, (flags & SCE_FIOS_O_CREAT) != 0 ? mode : 0); + if (descriptor < 0) { + completion.error = Fios::FromKernelError(descriptor); + return; + } + SceFiosStat stat{}; + if (const std::int32_t error = Fios::StatDescriptor(descriptor, stat); error != SCE_FIOS_OK || (stat.statFlags & SCE_FIOS_STAT_DIRECTORY) != 0) { + sceKernelClose(descriptor); + completion.error = error != SCE_FIOS_OK ? error : SCE_FIOS_ERROR_NOT_A_FILE; + return; + } + auto file = std::make_shared(); + file->descriptor = descriptor; + file->path = target; + file->params = params; + if ((file->params.openFlags & (SCE_FIOS_O_READ | SCE_FIOS_O_WRITE)) == 0) file->params.openFlags |= SCE_FIOS_O_READ; + const SceFiosFH handle = Fios::AddFile(file); + if (handle < 0) { + sceKernelClose(descriptor); + completion.error = handle; + return; + } + if (!Fios::CopyOut(outFH, &handle, sizeof(handle))) { + Fios::RemoveFile(handle); + sceKernelClose(descriptor); + completion.error = SCE_FIOS_ERROR_BAD_PTR; + } + }); +} + +SceFiosOp StartClose(const SceFiosOpAttr* attr, SceFiosFH handle) { + Fios::OpInfo info; + if (const auto file = Fios::FindFile(handle)) info.path = file->path; + return Fios::Submit(attr, std::move(info), [handle](Fios::Completion& completion) { + const auto file = Fios::RemoveFile(handle); + if (!file) { + completion.error = SCE_FIOS_ERROR_BAD_FH; + return; + } + const std::lock_guard lock(file->mutex); + completion.error = Fios::FromKernelError(sceKernelClose(file->descriptor)); + file->descriptor = -1; + }); +} + +SceFiosOp StartStat(const SceFiosOpAttr* attr, SceFiosFH handle, SceFiosStat* outStatus) { + return SubmitHandle(attr, handle, 0, 0, nullptr, SCE_FIOS_OK, [outStatus](Fios::FileHandle& file, Fios::Completion& completion) { + SceFiosStat stat{}; + if (const std::int32_t error = Fios::StatDescriptor(file.descriptor, stat)) completion.error = error; + else if (outStatus == nullptr || !Fios::CopyOut(outStatus, &stat, sizeof(stat))) completion.error = SCE_FIOS_ERROR_BAD_PTR; + }); +} + +SceFiosOp StartTruncate(const SceFiosOpAttr* attr, SceFiosFH handle, SceFiosSize length) { + return SubmitHandle(attr, handle, length, 0, nullptr, length < 0 ? SCE_FIOS_ERROR_BAD_SIZE : SCE_FIOS_OK, [length](Fios::FileHandle& file, Fios::Completion& completion) { + if (!CanWrite(file)) completion.error = SCE_FIOS_ERROR_READ_ONLY; + else completion.error = Fios::FromKernelError(sceKernelFtruncate(file.descriptor, length)); + }); +} + +SceFiosOp StartSync(const SceFiosOpAttr* attr, SceFiosFH handle) { + return SubmitHandle(attr, handle, 0, 0, nullptr, SCE_FIOS_OK, [](Fios::FileHandle& file, Fios::Completion& completion) { + completion.error = Fios::FromKernelError(sceKernelFsync(file.descriptor)); + }); +} + +int StatusSync(SceFiosOp op) { + return Fios::SyncStatus(op); +} + +std::int64_t CountSync(SceFiosOp op) { + return Fios::SyncCount(op); +} + +} + +extern "C" { + +SceFiosOp APS5_VABI sceFiosFHOpen(const SceFiosOpAttr* attr, SceFiosFH* outFH, const char* path, const SceFiosOpenParams* openParams) { + return StartOpen(attr, outFH, path, openParams, DefaultNativeMode); +} + +int APS5_VABI sceFiosFHOpenSync(const SceFiosOpAttr* attr, SceFiosFH* outFH, const char* path, const SceFiosOpenParams* openParams) { + return StatusSync(StartOpen(attr, outFH, path, openParams, DefaultNativeMode)); +} + +SceFiosOp APS5_VABI sceFiosFHOpenWithMode(const SceFiosOpAttr* attr, SceFiosFH* outFH, const char* path, const SceFiosOpenParams* openParams, std::int32_t nativeMode) { + return StartOpen(attr, outFH, path, openParams, nativeMode); +} + +int APS5_VABI sceFiosFHOpenWithModeSync(const SceFiosOpAttr* attr, SceFiosFH* outFH, const char* path, const SceFiosOpenParams* openParams, std::int32_t nativeMode) { + return StatusSync(StartOpen(attr, outFH, path, openParams, nativeMode)); +} + +SceFiosOp APS5_VABI sceFiosFHClose(const SceFiosOpAttr* attr, SceFiosFH fh) { + return StartClose(attr, fh); +} + +int APS5_VABI sceFiosFHCloseSync(const SceFiosOpAttr* attr, SceFiosFH fh) { + return StatusSync(StartClose(attr, fh)); +} + +SceFiosOp APS5_VABI sceFiosFHStat(const SceFiosOpAttr* attr, SceFiosFH fh, SceFiosStat* outStatus) { + return StartStat(attr, fh, outStatus); +} + +int APS5_VABI sceFiosFHStatSync(const SceFiosOpAttr* attr, SceFiosFH fh, SceFiosStat* outStatus) { + return StatusSync(StartStat(attr, fh, outStatus)); +} + +SceFiosOp APS5_VABI sceFiosFHTruncate(const SceFiosOpAttr* attr, SceFiosFH fh, SceFiosSize length) { + return StartTruncate(attr, fh, length); +} + +int APS5_VABI sceFiosFHTruncateSync(const SceFiosOpAttr* attr, SceFiosFH fh, SceFiosSize length) { + return StatusSync(StartTruncate(attr, fh, length)); +} + +SceFiosOp APS5_VABI sceFiosFHSync(const SceFiosOpAttr* attr, SceFiosFH fh) { + return StartSync(attr, fh); +} + +int APS5_VABI sceFiosFHSyncSync(const SceFiosOpAttr* attr, SceFiosFH fh) { + return StatusSync(StartSync(attr, fh)); +} + +SceFiosOp APS5_VABI sceFiosFHRead(const SceFiosOpAttr* attr, SceFiosFH fh, void* buffer, SceFiosSize length) { + return StartTransfer(attr, fh, buffer, length, 0, false, false); +} + +SceFiosSize APS5_VABI sceFiosFHReadSync(const SceFiosOpAttr* attr, SceFiosFH fh, void* buffer, SceFiosSize length) { + return CountSync(StartTransfer(attr, fh, buffer, length, 0, false, false)); +} + +SceFiosOp APS5_VABI sceFiosFHWrite(const SceFiosOpAttr* attr, SceFiosFH fh, const void* buffer, SceFiosSize length) { + return StartTransfer(attr, fh, const_cast(buffer), length, 0, false, true); +} + +SceFiosSize APS5_VABI sceFiosFHWriteSync(const SceFiosOpAttr* attr, SceFiosFH fh, const void* buffer, SceFiosSize length) { + return CountSync(StartTransfer(attr, fh, const_cast(buffer), length, 0, false, true)); +} + +SceFiosOp APS5_VABI sceFiosFHPread(const SceFiosOpAttr* attr, SceFiosFH fh, void* buffer, SceFiosSize length, SceFiosOffset offset) { + return StartTransfer(attr, fh, buffer, length, offset, true, false); +} + +SceFiosSize APS5_VABI sceFiosFHPreadSync(const SceFiosOpAttr* attr, SceFiosFH fh, void* buffer, SceFiosSize length, SceFiosOffset offset) { + return CountSync(StartTransfer(attr, fh, buffer, length, offset, true, false)); +} + +SceFiosOp APS5_VABI sceFiosFHPwrite(const SceFiosOpAttr* attr, SceFiosFH fh, const void* buffer, SceFiosSize length, SceFiosOffset offset) { + return StartTransfer(attr, fh, const_cast(buffer), length, offset, true, true); +} + +SceFiosSize APS5_VABI sceFiosFHPwriteSync(const SceFiosOpAttr* attr, SceFiosFH fh, const void* buffer, SceFiosSize length, SceFiosOffset offset) { + return CountSync(StartTransfer(attr, fh, const_cast(buffer), length, offset, true, true)); +} + +SceFiosOp APS5_VABI sceFiosFHReadv(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt) { + return StartVectors(attr, fh, iov, iovcnt, 0, false, false); +} + +SceFiosSize APS5_VABI sceFiosFHReadvSync(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt) { + return CountSync(StartVectors(attr, fh, iov, iovcnt, 0, false, false)); +} + +SceFiosOp APS5_VABI sceFiosFHWritev(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt) { + return StartVectors(attr, fh, iov, iovcnt, 0, false, true); +} + +SceFiosSize APS5_VABI sceFiosFHWritevSync(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt) { + return CountSync(StartVectors(attr, fh, iov, iovcnt, 0, false, true)); +} + +SceFiosOp APS5_VABI sceFiosFHPreadv(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt, SceFiosOffset offset) { + return StartVectors(attr, fh, iov, iovcnt, offset, true, false); +} + +SceFiosSize APS5_VABI sceFiosFHPreadvSync(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt, SceFiosOffset offset) { + return CountSync(StartVectors(attr, fh, iov, iovcnt, offset, true, false)); +} + +SceFiosOp APS5_VABI sceFiosFHPwritev(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt, SceFiosOffset offset) { + return StartVectors(attr, fh, iov, iovcnt, offset, true, true); +} + +SceFiosSize APS5_VABI sceFiosFHPwritevSync(const SceFiosOpAttr* attr, SceFiosFH fh, const SceFiosBuffer* iov, int iovcnt, SceFiosOffset offset) { + return CountSync(StartVectors(attr, fh, iov, iovcnt, offset, true, true)); +} + +SceFiosOffset APS5_VABI sceFiosFHSeek(SceFiosFH fh, SceFiosOffset offset, std::int32_t whence) { + if (whence < SCE_FIOS_SEEK_SET || whence > SCE_FIOS_SEEK_END) return SCE_FIOS_ERROR_BAD_OFFSET; + const auto file = Fios::FindFile(fh); + if (!file) return SCE_FIOS_ERROR_BAD_FH; + const std::lock_guard lock(file->mutex); + if (file->descriptor < 0) return SCE_FIOS_ERROR_BAD_FH; + const std::int64_t position = sceKernelLseek(file->descriptor, offset, whence); + return position < 0 ? Fios::FromKernelError(position) : position; +} + +SceFiosOffset APS5_VABI sceFiosFHTell(SceFiosFH fh) { + return sceFiosFHSeek(fh, 0, SCE_FIOS_SEEK_CUR); +} + +SceFiosSize APS5_VABI sceFiosFHGetSize(SceFiosFH fh) { + const auto file = Fios::FindFile(fh); + if (!file) return SCE_FIOS_ERROR_BAD_FH; + const std::lock_guard lock(file->mutex); + SceFiosStat stat{}; + if (file->descriptor < 0) return SCE_FIOS_ERROR_BAD_FH; + if (const std::int32_t error = Fios::StatDescriptor(file->descriptor, stat)) return error; + return stat.fileSize; +} + +const char* APS5_VABI sceFiosFHGetPath(SceFiosFH fh) { + const auto file = Fios::FindFile(fh); + return file ? file->path.c_str() : nullptr; +} + +const SceFiosOpenParams* APS5_VABI sceFiosFHGetOpenParams(SceFiosFH fh) { + const auto file = Fios::FindFile(fh); + return file ? &file->params : nullptr; +} + +} diff --git a/core/libs/prx/libSceFios2/src/PathOperations.cpp b/core/libs/prx/libSceFios2/src/PathOperations.cpp new file mode 100644 index 0000000000..2f00137178 --- /dev/null +++ b/core/libs/prx/libSceFios2/src/PathOperations.cpp @@ -0,0 +1,214 @@ +#include "prx/libSceFios2/include/Api.hpp" +#include "Runtime.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" + +namespace { + +constexpr std::uint16_t DefaultFileMode = 0666; +constexpr std::uint16_t DefaultDirectoryMode = 0777; + +using PathWork = std::function; + +SceFiosOp SubmitPath(const SceFiosOpAttr* attr, const char* path, std::int64_t request, std::int64_t offset, void* buffer, PathWork work) { + Fios::OpInfo info; + info.request = request; + info.offset = offset; + info.buffer = buffer; + if (const std::int32_t error = Fios::CopyPath(path, info.path)) return Fios::SubmitFailed(attr, std::move(info), error); + const std::string target = info.path; + return Fios::Submit(attr, std::move(info), [target, work = std::move(work)](Fios::Completion& completion) { work(target, completion); }); +} + +int StatusSync(SceFiosOp op) { + return Fios::SyncStatus(op); +} + +std::int64_t CountSync(SceFiosOp op) { + return Fios::SyncCount(op); +} + +bool IsDirectory(const std::string& path, std::int32_t& error) { + SceFiosStat stat{}; + error = Fios::StatPath(path, stat); + return error == SCE_FIOS_OK && (stat.statFlags & SCE_FIOS_STAT_DIRECTORY) != 0; +} + +void RemoveFile(const std::string& path, Fios::Completion& completion) { + std::int32_t error = SCE_FIOS_OK; + if (IsDirectory(path, error)) completion.error = SCE_FIOS_ERROR_NOT_A_FILE; + else if (error != SCE_FIOS_OK) completion.error = error; + else completion.error = Fios::FromKernelError(sceKernelUnlink(path.c_str())); +} + +void RemoveDirectoryAt(const std::string& path, Fios::Completion& completion) { + std::int32_t error = SCE_FIOS_OK; + if (!IsDirectory(path, error)) completion.error = error != SCE_FIOS_OK ? error : SCE_FIOS_ERROR_NOT_A_DIRECTORY; + else completion.error = Fios::FromKernelError(sceKernelRmdir(path.c_str())); +} + +SceFiosOp StartDelete(const SceFiosOpAttr* attr, const char* path) { + return SubmitPath(attr, path, 0, 0, nullptr, [](const std::string& target, Fios::Completion& completion) { + std::int32_t error = SCE_FIOS_OK; + if (IsDirectory(target, error)) RemoveDirectoryAt(target, completion); + else if (error != SCE_FIOS_OK) completion.error = error; + else RemoveFile(target, completion); + }); +} + +SceFiosOp StartFileDelete(const SceFiosOpAttr* attr, const char* path) { + return SubmitPath(attr, path, 0, 0, nullptr, RemoveFile); +} + +SceFiosOp StartDirectoryCreate(const SceFiosOpAttr* attr, const char* path) { + return SubmitPath(attr, path, 0, 0, nullptr, [](const std::string& target, Fios::Completion& completion) { + completion.error = Fios::FromKernelError(sceKernelMkdir(target.c_str(), DefaultDirectoryMode)); + }); +} + +SceFiosOp StartDirectoryDelete(const SceFiosOpAttr* attr, const char* path) { + return SubmitPath(attr, path, 0, 0, nullptr, RemoveDirectoryAt); +} + +SceFiosOp StartRename(const SceFiosOpAttr* attr, const char* oldPath, const char* newPath) { + std::string destination; + if (const std::int32_t error = Fios::CopyPath(newPath, destination)) { + return SubmitPath(attr, oldPath, 0, 0, nullptr, [error](const std::string&, Fios::Completion& completion) { completion.error = error; }); + } + return SubmitPath(attr, oldPath, 0, 0, nullptr, [destination](const std::string& source, Fios::Completion& completion) { + completion.error = Fios::FromKernelError(sceKernelRename(source.c_str(), destination.c_str())); + }); +} + +SceFiosOp StartFileRead(const SceFiosOpAttr* attr, const char* path, void* buffer, SceFiosSize length, SceFiosOffset offset) { + std::int32_t invalid = SCE_FIOS_OK; + if (length < 0) invalid = SCE_FIOS_ERROR_BAD_SIZE; + else if (offset < 0) invalid = SCE_FIOS_ERROR_BAD_OFFSET; + else if (buffer == nullptr && length != 0) invalid = SCE_FIOS_ERROR_BAD_PTR; + return SubmitPath(attr, path, length, offset, buffer, [=](const std::string& target, Fios::Completion& completion) { + if (invalid != SCE_FIOS_OK) { + completion.error = invalid; + return; + } + const int descriptor = sceKernelOpen(target.c_str(), SCE_KERNEL_O_RDONLY, 0); + if (descriptor < 0) { + completion.error = Fios::FromKernelError(descriptor); + return; + } + const std::int64_t count = Fios::ReadFully(descriptor, buffer, static_cast(length), offset, true); + sceKernelClose(descriptor); + if (count < 0) completion.error = static_cast(count); + else completion.result = count; + }); +} + +SceFiosOp StartFileWrite(const SceFiosOpAttr* attr, const char* path, const void* buffer, SceFiosSize length, SceFiosOffset offset) { + std::int32_t invalid = SCE_FIOS_OK; + if (length < 0) invalid = SCE_FIOS_ERROR_BAD_SIZE; + else if (offset < 0) invalid = SCE_FIOS_ERROR_BAD_OFFSET; + else if (buffer == nullptr && length != 0) invalid = SCE_FIOS_ERROR_BAD_PTR; + return SubmitPath(attr, path, length, offset, const_cast(buffer), [=](const std::string& target, Fios::Completion& completion) { + if (invalid != SCE_FIOS_OK) { + completion.error = invalid; + return; + } + const int descriptor = sceKernelOpen(target.c_str(), SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT, DefaultFileMode); + if (descriptor < 0) { + completion.error = Fios::FromKernelError(descriptor); + return; + } + const std::int64_t count = Fios::WriteFully(descriptor, buffer, static_cast(length), offset, true); + sceKernelClose(descriptor); + if (count < 0) completion.error = static_cast(count); + else completion.result = count; + }); +} + +SceFiosOp StartFileTruncate(const SceFiosOpAttr* attr, const char* path, SceFiosSize length) { + return SubmitPath(attr, path, length, 0, nullptr, [length](const std::string& target, Fios::Completion& completion) { + if (length < 0) { + completion.error = SCE_FIOS_ERROR_BAD_SIZE; + return; + } + const int descriptor = sceKernelOpen(target.c_str(), SCE_KERNEL_O_WRONLY, 0); + if (descriptor < 0) { + completion.error = Fios::FromKernelError(descriptor); + return; + } + completion.error = Fios::FromKernelError(sceKernelFtruncate(descriptor, length)); + sceKernelClose(descriptor); + }); +} + +} + +extern "C" { + +SceFiosOp APS5_VABI sceFiosDelete(const SceFiosOpAttr* attr, const char* path) { + return StartDelete(attr, path); +} + +int APS5_VABI sceFiosDeleteSync(const SceFiosOpAttr* attr, const char* path) { + return StatusSync(StartDelete(attr, path)); +} + +SceFiosOp APS5_VABI sceFiosFileDelete(const SceFiosOpAttr* attr, const char* path) { + return StartFileDelete(attr, path); +} + +int APS5_VABI sceFiosFileDeleteSync(const SceFiosOpAttr* attr, const char* path) { + return StatusSync(StartFileDelete(attr, path)); +} + +SceFiosOp APS5_VABI sceFiosDirectoryCreate(const SceFiosOpAttr* attr, const char* path) { + return StartDirectoryCreate(attr, path); +} + +int APS5_VABI sceFiosDirectoryCreateSync(const SceFiosOpAttr* attr, const char* path) { + return StatusSync(StartDirectoryCreate(attr, path)); +} + +SceFiosOp APS5_VABI sceFiosDirectoryDelete(const SceFiosOpAttr* attr, const char* path) { + return StartDirectoryDelete(attr, path); +} + +int APS5_VABI sceFiosDirectoryDeleteSync(const SceFiosOpAttr* attr, const char* path) { + return StatusSync(StartDirectoryDelete(attr, path)); +} + +SceFiosOp APS5_VABI sceFiosRename(const SceFiosOpAttr* attr, const char* oldPath, const char* newPath) { + return StartRename(attr, oldPath, newPath); +} + +int APS5_VABI sceFiosRenameSync(const SceFiosOpAttr* attr, const char* oldPath, const char* newPath) { + return StatusSync(StartRename(attr, oldPath, newPath)); +} + +SceFiosOp APS5_VABI sceFiosFileRead(const SceFiosOpAttr* attr, const char* path, void* buffer, SceFiosSize length, SceFiosOffset offset) { + return StartFileRead(attr, path, buffer, length, offset); +} + +SceFiosSize APS5_VABI sceFiosFileReadSync(const SceFiosOpAttr* attr, const char* path, void* buffer, SceFiosSize length, SceFiosOffset offset) { + return CountSync(StartFileRead(attr, path, buffer, length, offset)); +} + +SceFiosOp APS5_VABI sceFiosFileWrite(const SceFiosOpAttr* attr, const char* path, const void* buffer, SceFiosSize length, SceFiosOffset offset) { + return StartFileWrite(attr, path, buffer, length, offset); +} + +SceFiosSize APS5_VABI sceFiosFileWriteSync(const SceFiosOpAttr* attr, const char* path, const void* buffer, SceFiosSize length, SceFiosOffset offset) { + return CountSync(StartFileWrite(attr, path, buffer, length, offset)); +} + +SceFiosOp APS5_VABI sceFiosFileTruncate(const SceFiosOpAttr* attr, const char* path, SceFiosSize length) { + return StartFileTruncate(attr, path, length); +} + +int APS5_VABI sceFiosFileTruncateSync(const SceFiosOpAttr* attr, const char* path, SceFiosSize length) { + return StatusSync(StartFileTruncate(attr, path, length)); +} + +} diff --git a/core/libs/prx/libSceFios2/src/PathQueries.cpp b/core/libs/prx/libSceFios2/src/PathQueries.cpp new file mode 100644 index 0000000000..a5a1ff59e2 --- /dev/null +++ b/core/libs/prx/libSceFios2/src/PathQueries.cpp @@ -0,0 +1,108 @@ +#include "prx/libSceFios2/include/Api.hpp" +#include "Runtime.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +namespace { + +enum class Kind { Any, File, Directory }; + +SceFiosOp QueryExists(const SceFiosOpAttr* attr, const char* path, bool* outExists, Kind kind) { + Fios::OpInfo info; + if (const std::int32_t error = Fios::CopyPath(path, info.path)) return Fios::SubmitFailed(attr, std::move(info), error); + const std::string target = info.path; + return Fios::Submit(attr, std::move(info), [target, outExists, kind](Fios::Completion& completion) { + SceFiosStat stat{}; + const std::int32_t error = Fios::StatPath(target, stat); + bool exists = false; + if (error == SCE_FIOS_OK) { + const bool directory = (stat.statFlags & SCE_FIOS_STAT_DIRECTORY) != 0; + exists = kind == Kind::Any || (kind == Kind::Directory) == directory; + } else if (error != SCE_FIOS_ERROR_BAD_PATH) { + completion.error = error; + return; + } + completion.result = exists ? 1 : 0; + if (outExists != nullptr && !Fios::CopyOut(outExists, &exists, sizeof(exists))) completion.error = SCE_FIOS_ERROR_BAD_PTR; + }); +} + +bool ExistsSync(SceFiosOp op) { + return op >= 0 && Fios::SyncCount(op) == 1; +} + +} + +extern "C" { + +SceFiosOp APS5_VABI sceFiosExists(const SceFiosOpAttr* attr, const char* path, bool* outExists) { + return QueryExists(attr, path, outExists, Kind::Any); +} + +bool APS5_VABI sceFiosExistsSync(const SceFiosOpAttr* attr, const char* path) { + return ExistsSync(QueryExists(attr, path, nullptr, Kind::Any)); +} + +SceFiosOp APS5_VABI sceFiosFileExists(const SceFiosOpAttr* attr, const char* path, bool* outExists) { + return QueryExists(attr, path, outExists, Kind::File); +} + +bool APS5_VABI sceFiosFileExistsSync(const SceFiosOpAttr* attr, const char* path) { + return ExistsSync(QueryExists(attr, path, nullptr, Kind::File)); +} + +SceFiosOp APS5_VABI sceFiosDirectoryExists(const SceFiosOpAttr* attr, const char* path, bool* outExists) { + return QueryExists(attr, path, outExists, Kind::Directory); +} + +bool APS5_VABI sceFiosDirectoryExistsSync(const SceFiosOpAttr* attr, const char* path) { + return ExistsSync(QueryExists(attr, path, nullptr, Kind::Directory)); +} + +static SceFiosOp QuerySize(const SceFiosOpAttr* attr, const char* path) { + Fios::OpInfo info; + if (const std::int32_t error = Fios::CopyPath(path, info.path)) return Fios::SubmitFailed(attr, std::move(info), error); + const std::string target = info.path; + return Fios::Submit(attr, std::move(info), [target](Fios::Completion& completion) { + SceFiosStat stat{}; + if (const std::int32_t error = Fios::StatPath(target, stat)) { + completion.error = error; + } else if ((stat.statFlags & SCE_FIOS_STAT_DIRECTORY) != 0) { + completion.error = SCE_FIOS_ERROR_NOT_A_FILE; + } else { + completion.result = stat.fileSize; + } + }); +} + +SceFiosOp APS5_VABI sceFiosFileGetSize(const SceFiosOpAttr* attr, const char* path) { + return QuerySize(attr, path); +} + +SceFiosSize APS5_VABI sceFiosFileGetSizeSync(const SceFiosOpAttr* attr, const char* path) { + return Fios::SyncCount(QuerySize(attr, path)); +} + +static SceFiosOp QueryStat(const SceFiosOpAttr* attr, const char* path, SceFiosStat* outStatus) { + Fios::OpInfo info; + if (const std::int32_t error = Fios::CopyPath(path, info.path)) return Fios::SubmitFailed(attr, std::move(info), error); + const std::string target = info.path; + return Fios::Submit(attr, std::move(info), [target, outStatus](Fios::Completion& completion) { + SceFiosStat stat{}; + if (const std::int32_t error = Fios::StatPath(target, stat)) { + completion.error = error; + } else if (outStatus == nullptr || !Fios::CopyOut(outStatus, &stat, sizeof(stat))) { + completion.error = SCE_FIOS_ERROR_BAD_PTR; + } + }); +} + +SceFiosOp APS5_VABI sceFiosStat(const SceFiosOpAttr* attr, const char* path, SceFiosStat* outStatus) { + return QueryStat(attr, path, outStatus); +} + +int APS5_VABI sceFiosStatSync(const SceFiosOpAttr* attr, const char* path, SceFiosStat* outStatus) { + return Fios::SyncStatus(QueryStat(attr, path, outStatus)); +} + +} diff --git a/core/libs/prx/libSceFios2/src/Runtime.cpp b/core/libs/prx/libSceFios2/src/Runtime.cpp new file mode 100644 index 0000000000..70c2ff6c66 --- /dev/null +++ b/core/libs/prx/libSceFios2/src/Runtime.cpp @@ -0,0 +1,597 @@ +#include "Runtime.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include +#include +#include +#include +#include +#include + +namespace Fios { +namespace { + +constexpr std::size_t MaxStoredParameters = 512; +constexpr std::uint64_t IoChunk = 1ull << 30; + +struct Op { + SceFiosOp id = 0; + SceFiosOpAttr attr{}; + std::string path; + std::int64_t offset = 0; + std::int64_t request = 0; + void* buffer = nullptr; + Work work; + bool done = false; + bool queued = false; + bool cancelled = false; + std::int32_t error = SCE_FIOS_OK; + std::int64_t result = 0; +}; + +struct State { + std::mutex mutex; + std::condition_variable finished; + std::map> ops; + std::map> files; + std::map> directories; + std::deque pending; + SceFiosHandle lastHandle = 0; + bool initialized = false; + std::vector parameters; + std::uint32_t suspendCount = 0; + std::optional globalAttr; +}; + +State& Global() { + static State state; + return state; +} + +thread_local std::optional g_threadAttr; + +bool HandleInUse(const State& state, SceFiosHandle handle) { + return state.ops.count(handle) != 0 || state.files.count(handle) != 0 || state.directories.count(handle) != 0; +} + +SceFiosHandle NextHandle(State& state) { + for (int attempts = 0; attempts < 0x7fffffff; ++attempts) { + state.lastHandle = state.lastHandle >= 0x7ffffffe ? 1 : state.lastHandle + 1; + if (!HandleInUse(state, state.lastHandle)) return state.lastHandle; + } + return 0; +} + +void Finish(const std::shared_ptr& op, const Completion& completion) { + SceFiosOpCallback callback = nullptr; + void* context = nullptr; + { + const std::lock_guard lock(Global().mutex); + op->error = completion.error; + op->result = completion.result; + op->done = true; + op->queued = false; + op->work = nullptr; + callback = op->attr.pCallback; + context = op->attr.pCallbackContext; + Global().finished.notify_all(); + } + if (callback != nullptr) callback(context, op->id, SCE_FIOS_OPEVENT_COMPLETE, completion.error); +} + +void Run(const std::shared_ptr& op) { + Work work; + bool cancelled = false; + { + const std::lock_guard lock(Global().mutex); + work = std::move(op->work); + cancelled = op->cancelled; + } + Completion completion; + if (cancelled || !work) { + completion.error = SCE_FIOS_ERROR_CANCELLED; + Finish(op, completion); + return; + } + try { + work(completion); + } catch (...) { + completion.error = SCE_FIOS_ERROR_UNKNOWN; + completion.result = 0; + Finish(op, completion); + throw; + } + Finish(op, completion); +} + +std::shared_ptr FindOp(State& state, SceFiosOp id) { + const auto found = state.ops.find(id); + return found == state.ops.end() ? nullptr : found->second; +} + +void FireDelete(const std::shared_ptr& op) { + if (op->attr.pCallback != nullptr) op->attr.pCallback(op->attr.pCallbackContext, op->id, SCE_FIOS_OPEVENT_DELETE, SCE_FIOS_OK); +} + +bool CancelQueued(const std::shared_ptr& op) { + { + const std::lock_guard lock(Global().mutex); + if (op->done || !op->queued) return false; + auto& pending = Global().pending; + pending.erase(std::remove(pending.begin(), pending.end(), op->id), pending.end()); + op->cancelled = true; + } + Completion completion; + completion.error = SCE_FIOS_ERROR_CANCELLED; + Finish(op, completion); + return true; +} + +} + +std::int32_t FromKernelError(std::int64_t kernelResult) { + if (kernelResult >= 0) return SCE_FIOS_OK; + const auto code = static_cast(kernelResult); + if ((code & 0xffff0000u) != 0x80020000u) return SCE_FIOS_ERROR_UNKNOWN; + switch (code & 0xffffu) { + case 1: case 13: case 66: return SCE_FIOS_ERROR_ACCESS; + case 2: case 20: return SCE_FIOS_ERROR_BAD_PATH; + case 9: return SCE_FIOS_ERROR_BAD_FH; + case 14: return SCE_FIOS_ERROR_BAD_PTR; + case 17: return SCE_FIOS_ERROR_ALREADY_EXISTS; + case 21: return SCE_FIOS_ERROR_NOT_A_FILE; + case 22: return SCE_FIOS_ERROR_BAD_OFFSET; + case 23: case 24: return SCE_FIOS_ERROR_CANT_ALLOCATE_FH; + case 30: return SCE_FIOS_ERROR_READ_ONLY; + case 63: return SCE_FIOS_ERROR_PATH_TOO_LONG; + default: return SCE_FIOS_ERROR_UNKNOWN; + } +} + +std::int32_t CopyPath(const char* source, std::string& destination) { + if (source == nullptr) return SCE_FIOS_ERROR_BAD_PTR; + switch (CopyGuestString_nid_no_patch(source, SCE_FIOS_PATH_MAX, destination)) { + case 0: break; + case 14: return SCE_FIOS_ERROR_BAD_PTR; + case 63: return SCE_FIOS_ERROR_PATH_TOO_LONG; + default: return SCE_FIOS_ERROR_UNKNOWN; + } + return destination.empty() ? SCE_FIOS_ERROR_BAD_PATH : SCE_FIOS_OK; +} + +bool CopyOut(void* destination, const void* source, std::size_t bytes) { + return GuestCopyOut_nid_no_patch(destination, source, bytes); +} + +bool CopyIn(void* destination, const void* source, std::size_t bytes) { + return GuestCopyIn_nid_no_patch(destination, source, bytes); +} + +std::uint64_t TimespecToDate(std::int64_t seconds, std::int64_t nanoseconds) { + if (seconds < 0) return 0; + return static_cast(seconds) * 1000000000ull + static_cast(nanoseconds); +} + +SceFiosOp Submit(const SceFiosOpAttr* attr, OpInfo info, Work work) { + auto op = std::make_shared(); + bool runNow = false; + SceFiosOpAttr given{}; + if (attr != nullptr && !CopyIn(&given, attr, sizeof(given))) return SCE_FIOS_ERROR_BAD_PTR; + { + const std::lock_guard lock(Global().mutex); + State& state = Global(); + const SceFiosHandle id = NextHandle(state); + if (id == 0) return SCE_FIOS_ERROR_CANT_ALLOCATE_OP; + if (attr != nullptr) op->attr = given; + else if (g_threadAttr) op->attr = *g_threadAttr; + else if (state.globalAttr) op->attr = *state.globalAttr; + op->id = id; + op->path = std::move(info.path); + op->offset = info.offset; + op->request = info.request; + op->buffer = info.buffer; + op->work = std::move(work); + state.ops[id] = op; + if (state.suspendCount > 0) { + op->queued = true; + state.pending.push_back(id); + } else { + runNow = true; + } + } + if (runNow) Run(op); + return op->id; +} + +SceFiosOp SubmitFailed(const SceFiosOpAttr* attr, OpInfo info, std::int32_t error) { + return Submit(attr, std::move(info), [error](Completion& completion) { completion.error = error; }); +} + +std::int32_t Wait(SceFiosOp op) { + std::unique_lock lock(Global().mutex); + const auto found = FindOp(Global(), op); + if (!found) return SCE_FIOS_ERROR_BAD_OP; + Global().finished.wait(lock, [&] { return found->done; }); + return found->error; +} + +std::int32_t SyncStatus(SceFiosOp op) { + if (op < 0) return op; + Completion completion; + return SyncWait(op, completion) ? completion.error : SCE_FIOS_ERROR_BAD_OP; +} + +std::int64_t SyncCount(SceFiosOp op) { + if (op < 0) return op; + Completion completion; + if (!SyncWait(op, completion)) return SCE_FIOS_ERROR_BAD_OP; + return completion.error != SCE_FIOS_OK ? completion.error : completion.result; +} + +bool SyncWait(SceFiosOp op, Completion& completion) { + std::shared_ptr found; + { + std::unique_lock lock(Global().mutex); + found = FindOp(Global(), op); + if (!found) return false; + Global().finished.wait(lock, [&] { return found->done; }); + completion.error = found->error; + completion.result = found->result; + } + DeleteOp(op); + return true; +} + +bool OpDone(SceFiosOp op) { + const std::lock_guard lock(Global().mutex); + const auto found = FindOp(Global(), op); + return found && found->done; +} + +bool DeleteOp(SceFiosOp op) { + std::shared_ptr found; + { + const std::lock_guard lock(Global().mutex); + found = FindOp(Global(), op); + } + if (!found) return false; + CancelQueued(found); + { + std::unique_lock lock(Global().mutex); + Global().finished.wait(lock, [&] { return found->done; }); + if (Global().ops.erase(op) == 0) return false; + } + FireDelete(found); + return true; +} + +bool CancelOp(SceFiosOp op) { + std::shared_ptr found; + { + const std::lock_guard lock(Global().mutex); + found = FindOp(Global(), op); + } + return found && CancelQueued(found); +} + +bool OpIsCancelled(SceFiosOp op) { + const std::lock_guard lock(Global().mutex); + const auto found = FindOp(Global(), op); + return found && found->cancelled; +} + +bool QueryOp(SceFiosOp op, std::int64_t& request, std::int64_t& actual, std::int64_t& offset) { + const std::lock_guard lock(Global().mutex); + const auto found = FindOp(Global(), op); + if (!found) return false; + request = found->request; + actual = found->done ? found->result : 0; + offset = found->offset; + return true; +} + +bool OpError(SceFiosOp op, std::int32_t& error) { + const std::lock_guard lock(Global().mutex); + const auto found = FindOp(Global(), op); + if (!found) return false; + error = found->done ? found->error : 1; + return true; +} + +const SceFiosOpAttr* OpAttr(SceFiosOp op) { + const std::lock_guard lock(Global().mutex); + const auto found = FindOp(Global(), op); + return found ? &found->attr : nullptr; +} + +const char* OpPath(SceFiosOp op) { + const std::lock_guard lock(Global().mutex); + const auto found = FindOp(Global(), op); + return found ? found->path.c_str() : nullptr; +} + +void* OpBuffer(SceFiosOp op) { + const std::lock_guard lock(Global().mutex); + const auto found = FindOp(Global(), op); + return found ? found->buffer : nullptr; +} + +template +std::uint32_t ListKeys(const TMap& map, SceFiosHandle* out, std::uint32_t capacity) { + std::uint32_t index = 0; + for (const auto& entry : map) { + if (out != nullptr && index < capacity) out[index] = entry.first; + ++index; + } + return index; +} + +std::uint32_t ListOps(SceFiosOp* out, std::uint32_t capacity) { + const std::lock_guard lock(Global().mutex); + return ListKeys(Global().ops, out, capacity); +} + +void CancelAll() { + std::vector> queued; + { + const std::lock_guard lock(Global().mutex); + for (const SceFiosOp id : Global().pending) { + if (const auto found = FindOp(Global(), id)) queued.push_back(found); + } + } + for (const auto& op : queued) CancelQueued(op); +} + +SceFiosFH AddFile(std::shared_ptr file) { + const std::lock_guard lock(Global().mutex); + const SceFiosHandle id = NextHandle(Global()); + if (id == 0) return SCE_FIOS_ERROR_CANT_ALLOCATE_FH; + Global().files[id] = std::move(file); + return id; +} + +std::shared_ptr FindFile(SceFiosFH handle) { + const std::lock_guard lock(Global().mutex); + const auto found = Global().files.find(handle); + return found == Global().files.end() ? nullptr : found->second; +} + +std::shared_ptr RemoveFile(SceFiosFH handle) { + const std::lock_guard lock(Global().mutex); + const auto found = Global().files.find(handle); + if (found == Global().files.end()) return nullptr; + auto file = std::move(found->second); + Global().files.erase(found); + return file; +} + +std::uint32_t ListFiles(SceFiosFH* out, std::uint32_t capacity) { + const std::lock_guard lock(Global().mutex); + return ListKeys(Global().files, out, capacity); +} + +void CloseAllFiles() { + std::map> files; + { + const std::lock_guard lock(Global().mutex); + files.swap(Global().files); + } + for (auto& entry : files) { + const std::lock_guard lock(entry.second->mutex); + if (entry.second->descriptor >= 0) sceKernelClose(entry.second->descriptor); + entry.second->descriptor = -1; + } +} + +SceFiosDH AddDirectory(std::shared_ptr directory) { + const std::lock_guard lock(Global().mutex); + const SceFiosHandle id = NextHandle(Global()); + if (id == 0) return SCE_FIOS_ERROR_CANT_ALLOCATE_DH; + Global().directories[id] = std::move(directory); + return id; +} + +std::shared_ptr FindDirectory(SceFiosDH handle) { + const std::lock_guard lock(Global().mutex); + const auto found = Global().directories.find(handle); + return found == Global().directories.end() ? nullptr : found->second; +} + +std::shared_ptr RemoveDirectory(SceFiosDH handle) { + const std::lock_guard lock(Global().mutex); + const auto found = Global().directories.find(handle); + if (found == Global().directories.end()) return nullptr; + auto directory = std::move(found->second); + Global().directories.erase(found); + return directory; +} + +std::uint32_t ListDirectories(SceFiosDH* out, std::uint32_t capacity) { + const std::lock_guard lock(Global().mutex); + return ListKeys(Global().directories, out, capacity); +} + +bool IsValidHandle(SceFiosHandle handle) { + const std::lock_guard lock(Global().mutex); + return HandleInUse(Global(), handle); +} + +bool StoreParameters(State& state, const void* params) { + if (params == nullptr) { + state.parameters.clear(); + return true; + } + std::uint32_t first = 0; + if (!CopyIn(&first, params, sizeof(first))) return false; + const std::size_t size = std::min((first >> 1) & 0x7fff, MaxStoredParameters); + std::vector copy(size); + if (size != 0 && !CopyIn(copy.data(), params, size)) return false; + state.parameters = std::move(copy); + return true; +} + +bool Initialize(const void* params) { + const std::lock_guard lock(Global().mutex); + if (!StoreParameters(Global(), params)) return false; + Global().initialized = true; + return true; +} + +bool UpdateParameters(const void* params) { + const std::lock_guard lock(Global().mutex); + return Global().initialized && StoreParameters(Global(), params); +} + +void Terminate() { + CancelAll(); + CloseAllFiles(); + const std::lock_guard lock(Global().mutex); + State& state = Global(); + state.directories.clear(); + for (auto found = state.ops.begin(); found != state.ops.end();) { + found = found->second->done ? state.ops.erase(found) : std::next(found); + } + state.parameters.clear(); + state.suspendCount = 0; + state.initialized = false; +} + +bool IsInitialized(void* out) { + std::vector copy; + { + const std::lock_guard lock(Global().mutex); + if (!Global().initialized) return false; + copy = Global().parameters; + } + if (out != nullptr && !copy.empty()) CopyOut(out, copy.data(), copy.size()); + return true; +} + +void Suspend() { + const std::lock_guard lock(Global().mutex); + ++Global().suspendCount; +} + +void Resume() { + std::vector> ready; + { + const std::lock_guard lock(Global().mutex); + State& state = Global(); + if (state.suspendCount == 0) return; + if (--state.suspendCount != 0) return; + for (const SceFiosOp id : state.pending) { + if (const auto found = FindOp(state, id)) { + found->queued = false; + ready.push_back(found); + } + } + state.pending.clear(); + } + for (const auto& op : ready) Run(op); +} + +std::uint32_t SuspendCount() { + const std::lock_guard lock(Global().mutex); + return Global().suspendCount; +} + +bool IsIdle() { + const std::lock_guard lock(Global().mutex); + return Global().pending.empty(); +} + +bool SetGlobalAttr(const SceFiosOpAttr* attr) { + SceFiosOpAttr copy{}; + if (attr != nullptr && !CopyIn(©, attr, sizeof(copy))) return false; + const std::lock_guard lock(Global().mutex); + if (attr == nullptr) Global().globalAttr.reset(); + else Global().globalAttr = copy; + return true; +} + +bool GlobalAttr(SceFiosOpAttr& attr) { + const std::lock_guard lock(Global().mutex); + if (!Global().globalAttr) return false; + attr = *Global().globalAttr; + return true; +} + +bool SetThreadAttr(const SceFiosOpAttr* attr) { + SceFiosOpAttr copy{}; + if (attr != nullptr && !CopyIn(©, attr, sizeof(copy))) return false; + if (attr == nullptr) g_threadAttr.reset(); + else g_threadAttr = copy; + return true; +} + +bool ThreadAttr(SceFiosOpAttr& attr) { + if (!g_threadAttr) return false; + attr = *g_threadAttr; + return true; +} + +std::int64_t ReadFully(int descriptor, void* buffer, std::uint64_t length, std::int64_t offset, bool positioned) { + std::uint64_t total = 0; + while (total < length) { + const std::uint64_t chunk = std::min(length - total, IoChunk); + auto* destination = static_cast(buffer) + total; + const std::int64_t count = positioned ? sceKernelPread(descriptor, destination, chunk, offset + static_cast(total)) + : sceKernelRead(descriptor, destination, chunk); + if (count < 0) return total == 0 ? FromKernelError(count) : static_cast(total); + if (count == 0) break; + total += static_cast(count); + } + return static_cast(total); +} + +std::int64_t WriteFully(int descriptor, const void* buffer, std::uint64_t length, std::int64_t offset, bool positioned) { + std::uint64_t total = 0; + while (total < length) { + const std::uint64_t chunk = std::min(length - total, IoChunk); + const auto* source = static_cast(buffer) + total; + const std::int64_t count = positioned ? sceKernelPwrite(descriptor, source, chunk, offset + static_cast(total)) + : sceKernelWrite(descriptor, source, chunk); + if (count < 0) return total == 0 ? FromKernelError(count) : static_cast(total); + if (count == 0) break; + total += static_cast(count); + } + return static_cast(total); +} + +namespace { + +constexpr std::uint16_t FileTypeMask = 0170000; +constexpr std::uint16_t DirectoryType = 0040000; + +void FillStat(const FileStat& native, SceFiosStat& stat) { + stat = SceFiosStat{}; + stat.fileSize = native.st_size; + stat.accessDate = TimespecToDate(native.st_atim.tv_sec, native.st_atim.tv_nsec); + stat.modificationDate = TimespecToDate(native.st_mtim.tv_sec, native.st_mtim.tv_nsec); + stat.creationDate = TimespecToDate(native.st_birthtim.tv_sec, native.st_birthtim.tv_nsec); + stat.statFlags = (native.st_mode & FileTypeMask) == DirectoryType ? SCE_FIOS_STAT_DIRECTORY : 0; + stat.uid = native.st_uid; + stat.gid = native.st_gid; + stat.dev = native.st_dev; + stat.ino = native.st_ino; + stat.mode = native.st_mode; +} + +} + +std::int32_t StatPath(const std::string& path, SceFiosStat& stat) { + FileStat native{}; + if (const int result = sceKernelStat(path.c_str(), &native); result < 0) return FromKernelError(result); + FillStat(native, stat); + return SCE_FIOS_OK; +} + +std::int32_t StatDescriptor(int descriptor, SceFiosStat& stat) { + FileStat native{}; + if (const int result = sceKernelFstat(descriptor, &native); result < 0) return FromKernelError(result); + FillStat(native, stat); + return SCE_FIOS_OK; +} + +} diff --git a/core/libs/prx/libSceFios2/src/Runtime.hpp b/core/libs/prx/libSceFios2/src/Runtime.hpp new file mode 100644 index 0000000000..81d924bf3b --- /dev/null +++ b/core/libs/prx/libSceFios2/src/Runtime.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "prx/libSceFios2/include/Types.hpp" +#include +#include +#include +#include +#include +#include + +namespace Fios { + +struct Completion { + std::int32_t error = SCE_FIOS_OK; + std::int64_t result = 0; +}; + +using Work = std::function; + +struct OpInfo { + std::string path; + std::int64_t offset = 0; + std::int64_t request = 0; + void* buffer = nullptr; +}; + +struct FileHandle { + std::mutex mutex; + int descriptor = -1; + std::string path; + SceFiosOpenParams params{}; +}; + +struct DirectoryHandle { + std::mutex mutex; + std::string path; + std::vector names; + std::size_t next = 0; +}; + +std::int32_t FromKernelError(std::int64_t kernelResult); +std::int32_t CopyPath(const char* source, std::string& destination); +bool CopyOut(void* destination, const void* source, std::size_t bytes); +bool CopyIn(void* destination, const void* source, std::size_t bytes); +std::int32_t SyncStatus(SceFiosOp op); +std::int64_t SyncCount(SceFiosOp op); +std::uint64_t TimespecToDate(std::int64_t seconds, std::int64_t nanoseconds); + +SceFiosOp Submit(const SceFiosOpAttr* attr, OpInfo info, Work work); +SceFiosOp SubmitFailed(const SceFiosOpAttr* attr, OpInfo info, std::int32_t error); +std::int32_t Wait(SceFiosOp op); +bool SyncWait(SceFiosOp op, Completion& completion); +bool OpDone(SceFiosOp op); +bool DeleteOp(SceFiosOp op); +bool CancelOp(SceFiosOp op); +bool OpIsCancelled(SceFiosOp op); +bool QueryOp(SceFiosOp op, std::int64_t& request, std::int64_t& actual, std::int64_t& offset); +bool OpError(SceFiosOp op, std::int32_t& error); +const SceFiosOpAttr* OpAttr(SceFiosOp op); +const char* OpPath(SceFiosOp op); +void* OpBuffer(SceFiosOp op); +std::uint32_t ListOps(SceFiosOp* out, std::uint32_t capacity); +void CancelAll(); + +SceFiosFH AddFile(std::shared_ptr file); +std::shared_ptr FindFile(SceFiosFH handle); +std::shared_ptr RemoveFile(SceFiosFH handle); +std::uint32_t ListFiles(SceFiosFH* out, std::uint32_t capacity); +void CloseAllFiles(); + +SceFiosDH AddDirectory(std::shared_ptr directory); +std::shared_ptr FindDirectory(SceFiosDH handle); +std::shared_ptr RemoveDirectory(SceFiosDH handle); +std::uint32_t ListDirectories(SceFiosDH* out, std::uint32_t capacity); + +bool IsValidHandle(SceFiosHandle handle); + +bool Initialize(const void* params); +bool UpdateParameters(const void* params); +void Terminate(); +bool IsInitialized(void* out); +void Suspend(); +void Resume(); +std::uint32_t SuspendCount(); +bool IsIdle(); + +bool SetGlobalAttr(const SceFiosOpAttr* attr); +bool GlobalAttr(SceFiosOpAttr& attr); +bool SetThreadAttr(const SceFiosOpAttr* attr); +bool ThreadAttr(SceFiosOpAttr& attr); + +std::int64_t ReadFully(int descriptor, void* buffer, std::uint64_t length, std::int64_t offset, bool positioned); +std::int64_t WriteFully(int descriptor, const void* buffer, std::uint64_t length, std::int64_t offset, bool positioned); +std::int32_t StatPath(const std::string& path, SceFiosStat& stat); +std::int32_t StatDescriptor(int descriptor, SceFiosStat& stat); + +} diff --git a/core/libs/prx/libSceFios2/src/Time.cpp b/core/libs/prx/libSceFios2/src/Time.cpp new file mode 100644 index 0000000000..f77013d804 --- /dev/null +++ b/core/libs/prx/libSceFios2/src/Time.cpp @@ -0,0 +1,87 @@ +#include "prx/libSceFios2/include/Api.hpp" +#include "Runtime.hpp" +#include "prx/libc/include/General.hpp" +#include +#include + +namespace { + +constexpr std::int64_t NanosecondsPerSecond = 1000000000; +constexpr std::int64_t SecondsPerDay = 86400; + +std::int64_t DaysFromCivil(std::int64_t year, std::int64_t month, std::int64_t day) { + year -= month <= 2 ? 1 : 0; + const std::int64_t era = (year >= 0 ? year : year - 399) / 400; + const std::int64_t yearOfEra = year - era * 400; + const std::int64_t dayOfYear = (153 * (month + (month > 2 ? -3 : 9)) + 2) / 5 + day - 1; + const std::int64_t dayOfEra = yearOfEra * 365 + yearOfEra / 4 - yearOfEra / 100 + dayOfYear; + return era * 146097 + dayOfEra - 719468; +} + +void CivilFromDays(std::int64_t days, std::int64_t& year, std::int64_t& month, std::int64_t& day) { + days += 719468; + const std::int64_t era = (days >= 0 ? days : days - 146096) / 146097; + const std::int64_t dayOfEra = days - era * 146097; + const std::int64_t yearOfEra = (dayOfEra - dayOfEra / 1460 + dayOfEra / 36524 - dayOfEra / 146096) / 365; + const std::int64_t dayOfYear = dayOfEra - (365 * yearOfEra + yearOfEra / 4 - yearOfEra / 100); + const std::int64_t shifted = (5 * dayOfYear + 2) / 153; + day = dayOfYear - (153 * shifted + 2) / 5 + 1; + month = shifted < 10 ? shifted + 3 : shifted - 9; + year = yearOfEra + era * 400 + (month <= 2 ? 1 : 0); +} + +} + +extern "C" { + +SceFiosDate APS5_VABI sceFiosDateGetCurrent() { + const auto since = std::chrono::system_clock::now().time_since_epoch(); + return static_cast(std::chrono::duration_cast(since).count()); +} + +SceFiosDate APS5_VABI sceFiosDateFromComponents(const SceFiosTm* components) { + if (components == nullptr) return 0; + SceFiosTm tm{}; + if (!Fios::CopyIn(&tm, components, sizeof(tm))) return 0; + std::int64_t year = static_cast(tm.tm_year) + 1900; + std::int64_t month = tm.tm_mon; + year += month / 12; + month %= 12; + if (month < 0) { + month += 12; + --year; + } + const std::int64_t days = DaysFromCivil(year, month + 1, 1) + tm.tm_mday - 1; + const std::int64_t seconds = days * SecondsPerDay + static_cast(tm.tm_hour) * 3600 + static_cast(tm.tm_min) * 60 + tm.tm_sec; + return seconds < 0 ? 0 : static_cast(seconds) * NanosecondsPerSecond; +} + +SceFiosTm* APS5_VABI sceFiosDateToComponents(SceFiosDate date, SceFiosTm* outComponents) { + if (outComponents == nullptr) return nullptr; + const auto seconds = static_cast(date / NanosecondsPerSecond); + const std::int64_t days = seconds / SecondsPerDay; + const std::int64_t rest = seconds % SecondsPerDay; + std::int64_t year = 0, month = 0, day = 0; + CivilFromDays(days, year, month, day); + SceFiosTm tm{}; + tm.tm_sec = static_cast(rest % 60); + tm.tm_min = static_cast(rest / 60 % 60); + tm.tm_hour = static_cast(rest / 3600); + tm.tm_mday = static_cast(day); + tm.tm_mon = static_cast(month - 1); + tm.tm_year = static_cast(year - 1900); + tm.tm_wday = static_cast((days + 4) % 7); + tm.tm_yday = static_cast(days - DaysFromCivil(year, 1, 1)); + tm.tm_isdst = 0; + return Fios::CopyOut(outComponents, &tm, sizeof(tm)) ? outComponents : nullptr; +} + +SceFiosTimeInterval APS5_VABI sceFiosTimeIntervalFromNanoseconds(std::int64_t nanoseconds) { + return nanoseconds; +} + +std::int64_t APS5_VABI sceFiosTimeIntervalToNanoseconds(SceFiosTimeInterval interval) { + return interval; +} + +} diff --git a/core/libs/prx/libSceFont/CMakeLists.txt b/core/libs/prx/libSceFont/CMakeLists.txt index 2725519b48..41e715f805 100644 --- a/core/libs/prx/libSceFont/CMakeLists.txt +++ b/core/libs/prx/libSceFont/CMakeLists.txt @@ -7,6 +7,16 @@ if(BUILD_TESTING) target_link_libraries(guest_font_tests PRIVATE libSceFont libSceFontFt libc) configure_windows_unwind(guest_font_tests) add_test(NAME guest_font COMMAND guest_font_tests) + add_test_executable(font_render_surface_tests ${LIBS_INCLUDE_DIR}/tests/FontRenderSurface.cpp) + target_include_directories(font_render_surface_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(font_render_surface_tests PRIVATE libSceFont libSceFontFt libc) + configure_windows_unwind(font_render_surface_tests) + add_test(NAME font_render_surface COMMAND font_render_surface_tests) + set_tests_properties(font_render_surface PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(font_render_surface PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_font_scissor_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontScissor.cpp) target_include_directories(guest_font_scissor_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_font_scissor_tests PRIVATE libSceFont libSceFontFt libc) @@ -27,6 +37,16 @@ if(BUILD_TESTING) set_tests_properties(guest_font_renderer_lifecycle PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_font_vertical_layout_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontVerticalLayout.cpp) + target_include_directories(guest_font_vertical_layout_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_font_vertical_layout_tests PRIVATE libSceFont libSceFontFt libc) + configure_windows_unwind(guest_font_vertical_layout_tests) + add_test(NAME guest_font_vertical_layout COMMAND guest_font_vertical_layout_tests) + set_tests_properties(guest_font_vertical_layout PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_font_vertical_layout PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_system_font_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemFont.cpp) target_include_directories(guest_system_font_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_font_tests PRIVATE libSceFont libSceFontFt libc) diff --git a/core/libs/prx/libSceFont/src/Layout.cpp b/core/libs/prx/libSceFont/src/Layout.cpp index d52a854c55..95ddae64bc 100644 --- a/core/libs/prx/libSceFont/src/Layout.cpp +++ b/core/libs/prx/libSceFont/src/Layout.cpp @@ -99,8 +99,7 @@ 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); - 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; + if (!surface->buffer || surface->width < 0 || surface->height < 0 || 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); diff --git a/core/libs/prx/libSceFont/src/Library.cpp b/core/libs/prx/libSceFont/src/Library.cpp index 54d3f48d0c..1df3df0008 100644 --- a/core/libs/prx/libSceFont/src/Library.cpp +++ b/core/libs/prx/libSceFont/src/Library.cpp @@ -413,6 +413,7 @@ int APS5_VABI sceFontDettachDeviceCacheBuffer(FontLibrary library, void** buffer } int APS5_VABI sceFontSupportExternalFonts(FontLibrary library, std::uint32_t fontMax, std::uint32_t formats) { + if (fontMax > (UINT32_MAX >> 6u)) return SCE_FONT_ERROR_INVALID_PARAMETER; return SupportFonts(library, &FontLibNative::external_fonts_ctx, fontMax, (fontMax << 6) | 0x20u, formats); } diff --git a/core/libs/prx/libSceFontGs.native/CMakeLists.txt b/core/libs/prx/libSceFontGs.native/CMakeLists.txt new file mode 100644 index 0000000000..5469275d06 --- /dev/null +++ b/core/libs/prx/libSceFontGs.native/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(libSceFontGs.native SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp +) +target_include_directories(libSceFontGs.native PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceFontGs.native PRIVATE libSceFont) +configure_windows_unwind(libSceFontGs.native) + +if(BUILD_TESTING) + add_test_executable(guest_font_gs_agc_tests ${LIBS_INCLUDE_DIR}/tests/GuestFontGsAgc.cpp) + target_include_directories(guest_font_gs_agc_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_font_gs_agc_tests PRIVATE libSceFontGs.native libSceFont libkernel libc) + configure_windows_unwind(guest_font_gs_agc_tests) + add_test(NAME guest_font_gs_agc COMMAND guest_font_gs_agc_tests) + if(WIN32) + set_tests_properties(guest_font_gs_agc PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceFontGs.native/Export.cpp b/core/libs/prx/libSceFontGs.native/Export.cpp index 258ca4598f..d1fd85ff53 100644 --- a/core/libs/prx/libSceFontGs.native/Export.cpp +++ b/core/libs/prx/libSceFontGs.native/Export.cpp @@ -1,38 +1,203 @@ #include "prx/libSceFontGs.native/include/Api.hpp" +#include "prx/libSceFont/include/Api.hpp" #include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int FontGsInvalidParameter = static_cast(0x80460002); +constexpr int FontGsNoCommands = static_cast(0x80460001); +constexpr int FontGsInvalidSequence = static_cast(0x8046009a); +constexpr int FontGsSequenceLocked = static_cast(0x804600b8); +constexpr std::uint8_t FontAgcSurfaceTag = 0x58; +constexpr std::uint16_t FontAgcSequenceTag = 0xf80; +constexpr float FontAgcDrawupLimit = 16834.0f; +constexpr std::size_t FontAgcRecordStateOffset = 0x10; + +struct FontAgcSurface { + void* head; + std::uint8_t reserved8[5]; + std::uint8_t tag; + std::uint8_t reserved0e; + std::uint8_t mode; + std::uint8_t reserved10[0xc]; + std::uint32_t busy; + std::uint8_t reserved20[8]; + const void* fillObject; + std::uint8_t reserved30[8]; + std::uint32_t initialized; +}; + +static_assert(offsetof(FontAgcSurface, tag) == 0x0d); +static_assert(offsetof(FontAgcSurface, mode) == 0x0f); +static_assert(offsetof(FontAgcSurface, busy) == 0x1c); +static_assert(offsetof(FontAgcSurface, fillObject) == 0x28); +static_assert(offsetof(FontAgcSurface, initialized) == 0x38); + +struct FontAgcCommands { + std::uint8_t reserved[0x28]; + const void* first; + std::uint32_t firstSize; + std::uint32_t secondCount; + const void* second; +}; + +static_assert(offsetof(FontAgcCommands, first) == 0x28); +static_assert(offsetof(FontAgcCommands, firstSize) == 0x30); +static_assert(offsetof(FontAgcCommands, secondCount) == 0x34); +static_assert(offsetof(FontAgcCommands, second) == 0x38); + +struct FontAgcSequence { + std::uint16_t tag; + std::uint8_t reserved2[0x19]; + std::uint8_t lock; + std::uint8_t reserved1c[0xc]; + FontAgcCommands* commands; +}; + +static_assert(offsetof(FontAgcSequence, lock) == 0x1b); +static_assert(offsetof(FontAgcSequence, commands) == 0x28); + +const FontAgcSurface* ReadAgcSurface(const void* surface) { + const auto* const agc = static_cast(surface); + if (agc->tag != FontAgcSurfaceTag || agc->head == nullptr || agc->initialized != 1 || agc->busy != 0) { + return nullptr; + } + return agc; +} + +void MarkAgcRecord(void* output) { + *reinterpret_cast(static_cast(output) + FontAgcRecordStateOffset) = 1; +} + +} + extern "C" { -int APS5_VABI sceFontSelectGraphicsAgc(void) { +void* APS5_VABI sceFontSelectGraphicsAgc(std::uint32_t index) { + if (index == 0) { + NotImplemented_nid_no_patch(__func__); + } + return nullptr; +} + +int APS5_VABI sceFontGraphicsAgcSurfaceInit(void* surface, const void* config) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceFontGraphicsAgcSurfaceInit(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceFontGraphicsAgcDrawupFillTextureImage(FontGraphicsFrame* graphicsObject, const void* surface, void** output, + float x, float y, float width, float height) { + if (output == nullptr) { + return FontGsInvalidParameter; + } + *output = nullptr; + if (graphicsObject == nullptr || surface == nullptr) { + return FontGsInvalidParameter; + } + const auto* const agc = static_cast(surface); + if (agc->tag != FontAgcSurfaceTag || agc->head == nullptr || agc->initialized != 1 || !(x >= 0.0f) || agc->busy != 0 || + !(y >= 0.0f)) { + return FontGsInvalidParameter; + } + const float right = x + width; + const float bottom = y + height; + if (!(right >= x) || !(right <= FontAgcDrawupLimit) || !(bottom >= y) || !(bottom <= FontAgcDrawupLimit)) { + return FontGsInvalidParameter; + } + const int error = sceFontGraphicsDrawupFillTextureImageObject(graphicsObject, agc->fillObject, agc->mode, output, x, y, + width, height); + if (error != 0) { + return error; + } + MarkAgcRecord(*output); return 0; } -int APS5_VABI sceFontGraphicsAgcDrawupFillTextureImage(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceFontGraphicsAgcDrawupFillTexturePattern(FontGraphicsFrame* graphicsObject, const void* surface, std::uint32_t format, + std::uint32_t select, std::uint32_t dimension1, std::uint32_t option1, + std::uint32_t option2, std::uint32_t dimension2, void** output) { + if (output == nullptr) { + return FontGsInvalidParameter; + } + *output = nullptr; + if (graphicsObject == nullptr || surface == nullptr) { + return FontGsInvalidParameter; + } + const auto* const agc = ReadAgcSurface(surface); + if (agc == nullptr) { + return FontGsInvalidParameter; + } + const int error = sceFontGraphicsDrawupFillTexturePatternObject(graphicsObject, agc->fillObject, agc->mode, format, + select, dimension1, option1, option2, dimension2, output); + if (error != 0) { + return error; + } + MarkAgcRecord(*output); return 0; } -int APS5_VABI sceFontGraphicsAgcDrawupFillTexturePattern(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceFontGraphicsSequenceGetAgcCommandsForJump(FontGraphicsCanvasSequence* sequence, void** commands, + std::uint32_t* numCommands, void** commands2, std::uint32_t* numCommands2) { + if (commands != nullptr) { + *commands = nullptr; + } + if (numCommands != nullptr) { + *numCommands = 0; + } + if (commands2 != nullptr) { + *commands2 = nullptr; + } + if (numCommands2 != nullptr) { + *numCommands2 = 0; + } + const auto* const seq = reinterpret_cast(sequence); + if (seq == nullptr || seq->tag != FontAgcSequenceTag) { + return FontGsInvalidSequence; + } + if (commands == nullptr || numCommands == nullptr || commands2 == nullptr || numCommands2 == nullptr) { + return FontGsInvalidParameter; + } + if ((seq->lock & 1) != 0) { + return FontGsSequenceLocked; + } + const auto* const agc = seq->commands; + if (agc == nullptr) { + return FontGsNoCommands; + } + *commands = const_cast(agc->first); + *numCommands = agc->firstSize >> 2; + *commands2 = const_cast(agc->second); + *numCommands2 = agc->secondCount; return 0; } -int APS5_VABI sceFontGraphicsSequenceGetAgcCommandsForJump(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGraphicsSequenceGetAgcCommandsForSubmit(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceFontGraphicsSequenceGetAgcCommandsForSubmit(FontGraphicsCanvasSequence* sequence, void** commands, + std::uint32_t* numCommands) { + if (commands != nullptr) { + *commands = nullptr; + } + if (numCommands != nullptr) { + *numCommands = 0; + } + const auto* const seq = reinterpret_cast(sequence); + if (seq == nullptr || seq->tag != FontAgcSequenceTag) { + return FontGsInvalidSequence; + } + if (commands == nullptr || numCommands == nullptr) { + return FontGsInvalidParameter; + } + if ((seq->lock & 1) != 0) { + return FontGsSequenceLocked; + } + const auto* const agc = seq->commands; + if (agc == nullptr) { + return FontGsNoCommands; + } + *commands = const_cast(agc->first); + *numCommands = agc->firstSize; return 0; } diff --git a/core/libs/prx/libSceFontGs.native/include/Api.hpp b/core/libs/prx/libSceFontGs.native/include/Api.hpp index d2de1415bc..94c4d61421 100644 --- a/core/libs/prx/libSceFontGs.native/include/Api.hpp +++ b/core/libs/prx/libSceFontGs.native/include/Api.hpp @@ -1,14 +1,28 @@ #pragma once #include "SceTypes.hpp" +#include + +struct FontGraphicsFrame; +struct FontGraphicsCanvasSequence; extern "C" { -int APS5_VABI sceFontSelectGraphicsAgc(void); -int APS5_VABI sceFontGraphicsAgcSurfaceInit(void); -int APS5_VABI sceFontGraphicsAgcDrawupFillTextureImage(void); -int APS5_VABI sceFontGraphicsAgcDrawupFillTexturePattern(void); -int APS5_VABI sceFontGraphicsSequenceGetAgcCommandsForJump(void); -int APS5_VABI sceFontGraphicsSequenceGetAgcCommandsForSubmit(void); +void* APS5_VABI sceFontSelectGraphicsAgc(std::uint32_t index); + +int APS5_VABI sceFontGraphicsAgcSurfaceInit(void* surface, const void* config); + +int APS5_VABI sceFontGraphicsAgcDrawupFillTextureImage(FontGraphicsFrame* graphicsObject, const void* surface, void** output, + float x, float y, float width, float height); + +int APS5_VABI sceFontGraphicsAgcDrawupFillTexturePattern(FontGraphicsFrame* graphicsObject, const void* surface, std::uint32_t format, + std::uint32_t select, std::uint32_t dimension1, std::uint32_t option1, + std::uint32_t option2, std::uint32_t dimension2, void** output); + +int APS5_VABI sceFontGraphicsSequenceGetAgcCommandsForJump(FontGraphicsCanvasSequence* sequence, void** commands, + std::uint32_t* numCommands, void** commands2, std::uint32_t* numCommands2); + +int APS5_VABI sceFontGraphicsSequenceGetAgcCommandsForSubmit(FontGraphicsCanvasSequence* sequence, void** commands, + std::uint32_t* numCommands); } diff --git a/core/libs/prx/libSceFreeType/BuildInfo.cmake b/core/libs/prx/libSceFreeType/BuildInfo.cmake new file mode 100644 index 0000000000..8c7b580e70 --- /dev/null +++ b/core/libs/prx/libSceFreeType/BuildInfo.cmake @@ -0,0 +1,13 @@ +target_sources(libSceFreeType PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/BuildInfo.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_freetype_build_info_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeBuildInfo.cpp) + target_include_directories(guest_freetype_build_info_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_build_info_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_build_info_tests) + add_test(NAME guest_freetype_build_info COMMAND guest_freetype_build_info_tests) + if(WIN32) + set_tests_properties(guest_freetype_build_info PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceFreeType/CMakeLists.txt b/core/libs/prx/libSceFreeType/CMakeLists.txt index eb5d62f8b5..99c2a3fcc8 100644 --- a/core/libs/prx/libSceFreeType/CMakeLists.txt +++ b/core/libs/prx/libSceFreeType/CMakeLists.txt @@ -4,7 +4,7 @@ add_library(libSceFreeType SHARED EXCLUDE_FROM_ALL src/Outline.cpp src/Charmap.cpp src/FaceInfo.cpp - src/Stroker.cpp + src/Stroker.cpp src/OutlineStorage.cpp src/FaceCompat.cpp src/GuestObjects.cpp src/Names.cpp src/Trigonometry.cpp @@ -25,6 +25,7 @@ if(BUILD_TESTING) endif() endif() +target_sources(libSceFreeType PRIVATE src/Glyph.cpp) target_link_libraries(libSceFreeType PRIVATE freetype) target_sources(libSceFreeType PRIVATE src/SizeRequest.cpp) @@ -94,6 +95,15 @@ if(BUILD_TESTING) 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_executable(guest_freetype_glyph_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeGlyph.cpp) + target_include_directories(guest_freetype_glyph_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_glyph_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_glyph_tests) + add_test(NAME guest_freetype_glyph COMMAND guest_freetype_glyph_tests) + if(WIN32) + set_tests_properties(guest_freetype_glyph PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() 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}) @@ -123,6 +133,24 @@ if(BUILD_TESTING) set_tests_properties(guest_freetype_stroker PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_freetype_face_compat_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeFaceCompat.cpp) + target_include_directories(guest_freetype_face_compat_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_face_compat_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_face_compat_tests) + add_test(NAME guest_freetype_face_compat COMMAND guest_freetype_face_compat_tests) + if(WIN32) + set_tests_properties(guest_freetype_face_compat PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_freetype_outline_storage_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeOutlineStorage.cpp) + target_include_directories(guest_freetype_outline_storage_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_outline_storage_tests PRIVATE libSceFreeType freetype libc) + configure_windows_unwind(guest_freetype_outline_storage_tests) + add_test(NAME guest_freetype_outline_storage COMMAND guest_freetype_outline_storage_tests) + if(WIN32) + set_tests_properties(guest_freetype_outline_storage PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_freetype_charmap_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeCharmap.cpp) target_include_directories(guest_freetype_charmap_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_freetype_charmap_tests PRIVATE libSceFreeType freetype libc) @@ -171,4 +199,6 @@ if(BUILD_TESTING) endif() endif() +include("${CMAKE_CURRENT_LIST_DIR}/MemoryRecord.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/ModuleProperties.cmake") diff --git a/core/libs/prx/libSceFreeType/MemoryRecord.cmake b/core/libs/prx/libSceFreeType/MemoryRecord.cmake new file mode 100644 index 0000000000..ae81636b8e --- /dev/null +++ b/core/libs/prx/libSceFreeType/MemoryRecord.cmake @@ -0,0 +1,21 @@ +target_sources(libSceFreeType PRIVATE ${LIBS_INCLUDE_DIR}/prx/libSceFreeType/src/MemoryRecord.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_freetype_memory_record_tests ${LIBS_INCLUDE_DIR}/tests/GuestFreeTypeMemoryRecord.cpp) + target_include_directories(guest_freetype_memory_record_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_freetype_memory_record_tests PRIVATE ${CMAKE_DL_LIBS}) + if(WIN32) + target_link_libraries(guest_freetype_memory_record_tests PRIVATE libc) + endif() + configure_windows_unwind(guest_freetype_memory_record_tests) + add_dependencies(guest_freetype_memory_record_tests libs) + add_test(NAME guest_freetype_memory_record COMMAND guest_freetype_memory_record_tests $ unpatched) + add_test(NAME guest_freetype_memory_record_nid COMMAND guest_freetype_memory_record_tests ${PATCHED_DIR}/libSceFreeType.prx patched) + set_tests_properties(guest_freetype_memory_record guest_freetype_memory_record_nid PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_freetype_memory_record PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_freetype_memory_record_nid PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:${PATCHED_DIR}") + endif() +endif() diff --git a/core/libs/prx/libSceFreeType/include/Api.hpp b/core/libs/prx/libSceFreeType/include/Api.hpp index cbd3d30a96..9a1e2bfa64 100644 --- a/core/libs/prx/libSceFreeType/include/Api.hpp +++ b/core/libs/prx/libSceFreeType/include/Api.hpp @@ -4,6 +4,7 @@ #include "prx/libSceFreeType/include/GuestFreeType.hpp" #include "prx/libSceFreeType/include/PostScriptInfo.hpp" #include "SceTypes.hpp" +#include "prx/libSceFreeType/include/MemoryRecord.hpp" extern "C" { @@ -12,6 +13,9 @@ std::int32_t APS5_VABI FT_Outline_GetOutsideBorder_nid_postfix(const FreeType::G int APS5_VABI FT_Init_FreeType_nid_postfix(FreeType::GuestLibrary** alibrary); int APS5_VABI FT_Done_FreeType_nid_postfix(FreeType::GuestLibrary* library); +void APS5_VABI FT_Library_Version_nid_postfix(FreeType::GuestLibrary* library, std::int32_t* major, std::int32_t* minor, std::int32_t* patch); +int APS5_VABI sceFreeTypeOtLibraryVersion_nid_no_patch(); +std::int32_t APS5_VABI FT_Get_TrueType_Engine_Type_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); @@ -32,6 +36,9 @@ int APS5_VABI FT_Get_SubGlyph_Info_nid_postfix(FreeType::GuestGlyphSlotRec* slot std::int32_t* arg1, std::int32_t* arg2, FreeType::GuestMatrix* transform); int APS5_VABI FT_Get_Advance_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t glyphIndex, std::int32_t flags, std::int64_t* advance); int APS5_VABI FT_Get_Advances_nid_postfix(FreeType::GuestFaceRec* face, std::uint32_t start, std::uint32_t count, std::int32_t flags, std::int64_t* advances); +int APS5_VABI FT_Get_Glyph_nid_postfix(FreeType::GuestGlyphSlotRec* slot, FreeType::GuestGlyphRec** target); +int APS5_VABI FT_Glyph_Copy_nid_postfix(FreeType::GuestGlyphRec* source, FreeType::GuestGlyphRec** target); +void APS5_VABI FT_Done_Glyph_nid_postfix(FreeType::GuestGlyphRec* glyph); 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); @@ -103,5 +110,12 @@ int APS5_VABI FT_Stroker_BeginSubPath_nid_postfix(void* stroker, const FreeType: int APS5_VABI FT_Stroker_ConicTo_nid_postfix(void* stroker, const FreeType::GuestVector* control, const FreeType::GuestVector* to); int APS5_VABI ft_corner_is_flat_nid_postfix(std::int64_t in_x, std::int64_t in_y, std::int64_t out_x, std::int64_t out_y); int APS5_VABI ft_corner_orientation_nid_postfix(std::int64_t in_x, std::int64_t in_y, std::int64_t out_x, std::int64_t out_y); +const char* APS5_VABI FT_Get_Font_Format_nid_postfix(FreeType::GuestFaceRec* face); +std::uint8_t APS5_VABI FT_Face_CheckTrueTypePatents_nid_postfix(FreeType::GuestFaceRec* face); +std::uint8_t APS5_VABI FT_Face_SetUnpatentedHinting_nid_postfix(FreeType::GuestFaceRec* face, std::uint8_t value); +int APS5_VABI FT_Outline_New_nid_postfix(FreeType::GuestLibrary* library, std::uint32_t numPoints, std::int32_t numContours, FreeType::GuestOutline* outline); +int APS5_VABI FT_Outline_Done_nid_postfix(FreeType::GuestLibrary* library, FreeType::GuestOutline* outline); +int APS5_VABI sceFreeTypeOtOutlineDone_nid_no_patch(); +int APS5_VABI sceFreeTypeOtOutlineNew_nid_no_patch(); } diff --git a/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp b/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp index 67af76996e..957a1e4a6d 100644 --- a/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp +++ b/core/libs/prx/libSceFreeType/include/GuestFreeType.hpp @@ -115,6 +115,32 @@ struct GuestOutline { std::int32_t flags; }; +struct GuestGlyphRec { + void* library; + const void* clazz; + std::uint32_t format; + GuestVector advance; +}; + +struct GuestBitmapGlyphRec { + GuestGlyphRec root; + std::int32_t left; + std::int32_t top; + GuestBitmap bitmap; +}; + +struct GuestOutlineGlyphRec { + GuestGlyphRec root; + GuestOutline outline; +}; + +static_assert(sizeof(GuestGlyphRec) == 40 && alignof(GuestGlyphRec) == 8); +static_assert(offsetof(GuestGlyphRec, library) == 0 && offsetof(GuestGlyphRec, clazz) == 8); +static_assert(offsetof(GuestGlyphRec, format) == 16 && offsetof(GuestGlyphRec, advance) == 24); +static_assert(sizeof(GuestBitmapGlyphRec) == 88 && offsetof(GuestBitmapGlyphRec, root) == 0); +static_assert(offsetof(GuestBitmapGlyphRec, left) == 40 && offsetof(GuestBitmapGlyphRec, top) == 44 && offsetof(GuestBitmapGlyphRec, bitmap) == 48); +static_assert(sizeof(GuestOutlineGlyphRec) == 80 && offsetof(GuestOutlineGlyphRec, root) == 0 && offsetof(GuestOutlineGlyphRec, outline) == 40); + struct GuestOutlineFuncs { int (APS5_VABI *move_to)(const GuestVector*, void*); int (APS5_VABI *line_to)(const GuestVector*, void*); diff --git a/core/libs/prx/libSceFreeType/include/MemoryRecord.hpp b/core/libs/prx/libSceFreeType/include/MemoryRecord.hpp new file mode 100644 index 0000000000..a84868e4ca --- /dev/null +++ b/core/libs/prx/libSceFreeType/include/MemoryRecord.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" +#include "prx/libSceFreeType/include/GuestList.hpp" +#include + +extern "C" { + +FreeType::GuestMemoryRec* APS5_VABI FT_New_Memory_nid_postfix(); +void APS5_VABI FT_Done_Memory_nid_postfix(FreeType::GuestMemoryRec* memory); +int APS5_VABI sceFreeTypeOtNewMemory_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceFreeType/src/BuildInfo.cpp b/core/libs/prx/libSceFreeType/src/BuildInfo.cpp new file mode 100644 index 0000000000..25f486a317 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/BuildInfo.cpp @@ -0,0 +1,24 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" + +#include FT_MODULE_H + +#include + +extern "C" { + +void APS5_VABI FT_Library_Version_nid_postfix(FreeType::GuestLibrary* library, std::int32_t* major, std::int32_t* minor, std::int32_t* patch) { + FT_Int hostMajor = 0; + FT_Int hostMinor = 0; + FT_Int hostPatch = 0; + FT_Library_Version(library ? library->host : nullptr, &hostMajor, &hostMinor, &hostPatch); + if (major) *major = hostMajor; + if (minor) *minor = hostMinor; + if (patch) *patch = hostPatch; +} + +std::int32_t APS5_VABI FT_Get_TrueType_Engine_Type_nid_postfix(FreeType::GuestLibrary* library) { + return static_cast(FT_Get_TrueType_Engine_Type(library ? library->host : nullptr)); +} + +} diff --git a/core/libs/prx/libSceFreeType/src/FaceCompat.cpp b/core/libs/prx/libSceFreeType/src/FaceCompat.cpp new file mode 100644 index 0000000000..3d9f60c004 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/FaceCompat.cpp @@ -0,0 +1,28 @@ +#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_Font_Format_nid_postfix(GuestFaceRec* face) { + const GuestFace* owner = FaceOf(face); + return FT_Get_Font_Format(owner ? owner->host : nullptr); +} + +std::uint8_t APS5_VABI FT_Face_CheckTrueTypePatents_nid_postfix(GuestFaceRec* face) { + const GuestFace* owner = FaceOf(face); + return FT_Face_CheckTrueTypePatents(owner ? owner->host : nullptr); +} + +std::uint8_t APS5_VABI FT_Face_SetUnpatentedHinting_nid_postfix(GuestFaceRec* face, std::uint8_t value) { + const GuestFace* owner = FaceOf(face); + return FT_Face_SetUnpatentedHinting(owner ? owner->host : nullptr, value); +} + +} diff --git a/core/libs/prx/libSceFreeType/src/Glyph.cpp b/core/libs/prx/libSceFreeType/src/Glyph.cpp new file mode 100644 index 0000000000..6cd3875e8d --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/Glyph.cpp @@ -0,0 +1,198 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/General.hpp" + +#include FT_GLYPH_H + +#include +#include +#include +#include + +using namespace FreeType; + +namespace { + +template TTarget Count(TSource value, const char* function) { + if (!std::in_range(value)) NotImplemented_nid_no_patch(function); + return static_cast(value); +} + +struct Glyph { + GuestOutlineGlyphRec outline{}; + GuestBitmapGlyphRec bitmap{}; + GuestLibrary* library = nullptr; + bool isBitmap = false; + std::unique_ptr host{nullptr, &FT_Done_Glyph}; + std::vector points; + + GuestGlyphRec* Root() { return isBitmap ? &bitmap.root : &outline.root; } + + void Publish(const GuestOutline& source, const char* function) { + *Root() = {library, host->clazz, static_cast(host->format), {host->advance.x, host->advance.y}}; + if (isBitmap) { + const auto* native = reinterpret_cast(host.get()); + bitmap.left = native->left; + bitmap.top = native->top; + const auto& image = native->bitmap; + bitmap.bitmap = {image.rows, image.width, image.pitch, image.buffer, image.num_grays, image.pixel_mode, image.palette_mode, image.palette}; + } else { + const auto& native = reinterpret_cast(host.get())->outline; + std::copy_n(source.points, points.size(), points.begin()); + outline.outline = {Count(native.n_contours, function), Count(native.n_points, function), + points.empty() ? nullptr : points.data(), reinterpret_cast(native.tags), + reinterpret_cast(native.contours), native.flags}; + } + } +}; + +std::mutex glyphMutex; +std::unordered_map> glyphs; + +FT_Vector Advance(const GuestVector& advance) { + constexpr std::int64_t limit = 0x200000; + return {static_cast(std::clamp(advance.x, -limit, limit)), static_cast(std::clamp(advance.y, -limit, limit))}; +} + +bool ValidAdvance(const GuestVector& advance) { + return advance.x > -0x200000 && advance.x < 0x200000 && advance.y > -0x200000 && advance.y < 0x200000; +} + +FT_Bitmap Bitmap(const GuestBitmap& bitmap) { + return {bitmap.rows, bitmap.width, bitmap.pitch, bitmap.buffer, bitmap.num_grays, bitmap.pixel_mode, bitmap.palette_mode, bitmap.palette}; +} + +struct Outline { + FT_Outline native{}; + std::vector points; + + Outline(const GuestOutline& outline, const char* function) { + native = {Count(outline.n_contours, function), Count(outline.n_points, function), nullptr, + reinterpret_cast(outline.tags), reinterpret_cast(outline.contours), outline.flags}; + if (outline.n_contours > outline.n_points) return; + points.resize(outline.n_points); + native.points = points.empty() ? nullptr : points.data(); + } +}; + +Glyph& Find(GuestGlyphRec* glyph, const char* function) { + const auto found = glyphs.find(glyph); + if (found == glyphs.end()) NotImplemented_nid_no_patch(function); + auto& owner = *found->second; + if (glyph->library != owner.library || glyph->clazz != owner.host->clazz) NotImplemented_nid_no_patch(function); + return owner; +} + +Glyph& Register(GuestLibrary* library, bool isBitmap, std::size_t pointCount) { + auto owner = std::make_unique(); + owner->library = library; + owner->isBitmap = isBitmap; + owner->points.resize(pointCount); + auto* address = owner->Root(); + return *glyphs.emplace(address, std::move(owner)).first->second; +} + +} + +extern "C" { + +int APS5_VABI FT_Get_Glyph_nid_postfix(GuestGlyphSlotRec* slot, GuestGlyphRec** target) { + if (!slot) return FT_Err_Invalid_Slot_Handle; + if (!target) return FT_Err_Invalid_Argument; + auto* library = static_cast(slot->library); + FT_GlyphSlotRec native{}; + native.library = library ? library->host : nullptr; + native.format = static_cast(slot->format); + native.advance = Advance(slot->advance); + if (!library || !ValidAdvance(slot->advance)) { + FT_Glyph result = reinterpret_cast(&native); + const FT_Error error = FT_Get_Glyph(&native, &result); + if (!result) *target = nullptr; + return error; + } + if (native.format != FT_GLYPH_FORMAT_BITMAP && native.format != FT_GLYPH_FORMAT_OUTLINE) { + FT_Glyph probe = nullptr; + const FT_Error error = FT_New_Glyph(native.library, native.format, &probe); + if (error) return error; + FT_Done_Glyph(probe); + NotImplemented_nid_no_patch(__func__); + } + Outline outline(native.format == FT_GLYPH_FORMAT_OUTLINE ? slot->outline : GuestOutline{}, __func__); + native.outline = outline.native; + native.bitmap = Bitmap(slot->bitmap); + native.bitmap_left = slot->bitmap_left; + native.bitmap_top = slot->bitmap_top; + if (native.format == FT_GLYPH_FORMAT_BITMAP) { + const auto* face = FaceOf(slot); + if (!face || slot != &face->slot || slot->library != face->library) NotImplemented_nid_no_patch(__func__); + const auto host = face->host->glyph; + if (slot->bitmap.buffer != host->bitmap.buffer) NotImplemented_nid_no_patch(__func__); + native.internal = host->internal; + } + std::lock_guard lock(glyphMutex); + auto& owner = Register(library, native.format == FT_GLYPH_FORMAT_BITMAP, native.outline.n_points); + auto* address = owner.Root(); + FT_Glyph result = reinterpret_cast(&native); + const FT_Error error = FT_Get_Glyph(&native, &result); + if (error) { + glyphs.erase(address); + if (!result) *target = nullptr; + return error; + } + owner.host.reset(result); + owner.Publish(slot->outline, __func__); + *target = address; + return 0; +} + +int APS5_VABI FT_Glyph_Copy_nid_postfix(GuestGlyphRec* source, GuestGlyphRec** target) { + if (!target || !source || !source->clazz) return FT_Err_Invalid_Argument; + std::lock_guard lock(glyphMutex); + auto& original = Find(source, __func__); + *target = nullptr; + if (!source->clazz) return FT_Err_Invalid_Argument; + if (source->library != original.library || source->clazz != original.host->clazz) NotImplemented_nid_no_patch(__func__); + const GuestVector advance = source->advance; + FT_OutlineGlyphRec nativeOutline{}; + FT_BitmapGlyphRec nativeBitmap{}; + FT_Glyph native = original.isBitmap ? &nativeBitmap.root : &nativeOutline.root; + *native = *original.host; + native->format = static_cast(source->format); + Outline outline(original.isBitmap ? GuestOutline{} : original.outline.outline, __func__); + nativeOutline.outline = outline.native; + nativeBitmap.bitmap = Bitmap(original.bitmap.bitmap); + nativeBitmap.left = original.bitmap.left; + nativeBitmap.top = original.bitmap.top; + auto& copy = Register(original.library, original.isBitmap, outline.native.n_points); + auto* address = copy.Root(); + FT_Glyph result = nullptr; + const FT_Error error = FT_Glyph_Copy(native, &result); + if (error) { + glyphs.erase(address); + return error; + } + copy.host.reset(result); + copy.Publish(original.outline.outline, __func__); + address->advance = advance; + *target = address; + return 0; +} + +void APS5_VABI FT_Done_Glyph_nid_postfix(GuestGlyphRec* glyph) { + if (!glyph) return; + std::lock_guard lock(glyphMutex); + auto& owner = Find(glyph, __func__); + if (owner.isBitmap) { + if (owner.bitmap.bitmap.buffer != reinterpret_cast(owner.host.get())->bitmap.buffer) NotImplemented_nid_no_patch(__func__); + } else { + const auto& outline = owner.outline.outline; + const auto& native = reinterpret_cast(owner.host.get())->outline; + if (outline.points != (owner.points.empty() ? nullptr : owner.points.data()) || + static_cast(outline.tags) != static_cast(native.tags) || + static_cast(outline.contours) != static_cast(native.contours) || + (outline.flags & FT_OUTLINE_OWNER) != (native.flags & FT_OUTLINE_OWNER)) NotImplemented_nid_no_patch(__func__); + } + glyphs.erase(glyph); +} + +} diff --git a/core/libs/prx/libSceFreeType/src/MemoryRecord.cpp b/core/libs/prx/libSceFreeType/src/MemoryRecord.cpp new file mode 100644 index 0000000000..f19c7dd010 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/MemoryRecord.cpp @@ -0,0 +1,29 @@ +#include "prx/libSceFreeType/include/MemoryRecord.hpp" + +#include + +namespace { + +void* APS5_VABI HostAlloc(FreeType::GuestMemoryRec* memory, std::int64_t size) { + return std::malloc(static_cast(size)); +} + +void APS5_VABI HostFree(FreeType::GuestMemoryRec* memory, void* block) { + std::free(block); +} + +void* APS5_VABI HostRealloc(FreeType::GuestMemoryRec* memory, std::int64_t current, std::int64_t size, void* block) { + return std::realloc(block, static_cast(size)); +} + +} + +extern "C" FreeType::GuestMemoryRec* APS5_VABI FT_New_Memory_nid_postfix() { + const auto memory = static_cast(std::malloc(sizeof(FreeType::GuestMemoryRec))); + if (memory) *memory = {nullptr, HostAlloc, HostFree, HostRealloc}; + return memory; +} + +extern "C" void APS5_VABI FT_Done_Memory_nid_postfix(FreeType::GuestMemoryRec* memory) { + std::free(memory); +} diff --git a/core/libs/prx/libSceFreeType/src/OutlineStorage.cpp b/core/libs/prx/libSceFreeType/src/OutlineStorage.cpp new file mode 100644 index 0000000000..f2ae5ebae0 --- /dev/null +++ b/core/libs/prx/libSceFreeType/src/OutlineStorage.cpp @@ -0,0 +1,50 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_OUTLINE_H + +#include +#include + +using namespace FreeType; + +extern "C" { + +int APS5_VABI FT_Outline_Done_nid_postfix(GuestLibrary* library, GuestOutline* outline) { + if (!library) return FT_Err_Invalid_Library_Handle; + if (!outline) return FT_Err_Invalid_Outline; + if (!library->host) return FT_Err_Invalid_Argument; + if (outline->flags & FT_OUTLINE_OWNER) { + std::free(outline->points); + std::free(outline->tags); + std::free(outline->contours); + } + *outline = {}; + return 0; +} + +int APS5_VABI FT_Outline_New_nid_postfix(GuestLibrary* library, std::uint32_t numPoints, std::int32_t numContours, GuestOutline* outline) { + if (!library) return FT_Err_Invalid_Library_Handle; + if (!outline) return FT_Err_Invalid_Argument; + if (!library->host) return FT_Err_Invalid_Argument; + *outline = {}; + if (numContours < 0 || static_cast(numContours) > numPoints) return FT_Err_Invalid_Argument; + if (numPoints > FT_OUTLINE_POINTS_MAX) return FT_Err_Array_Too_Large; + if (numPoints != 0) { + outline->points = static_cast(std::calloc(numPoints, sizeof(GuestVector))); + outline->tags = static_cast(std::calloc(numPoints, sizeof(unsigned char))); + } + if (numContours != 0) outline->contours = static_cast(std::calloc(static_cast(numContours), sizeof(std::uint16_t))); + const bool failed = numPoints != 0 && (!outline->points || !outline->tags) || numContours != 0 && !outline->contours; + outline->flags |= FT_OUTLINE_OWNER; + if (failed) { + FT_Outline_Done_nid_postfix(library, outline); + return FT_Err_Out_Of_Memory; + } + outline->n_points = static_cast(numPoints); + outline->n_contours = static_cast(numContours); + return 0; +} + +} diff --git a/core/libs/prx/libSceFreeTypeOl/CMakeLists.txt b/core/libs/prx/libSceFreeTypeOl/CMakeLists.txt index 6d5e65cab1..d206765a57 100644 --- a/core/libs/prx/libSceFreeTypeOl/CMakeLists.txt +++ b/core/libs/prx/libSceFreeTypeOl/CMakeLists.txt @@ -11,6 +11,8 @@ if(ANYPS5_USE_SYSTEM_DEPS) endif() if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_options(libSceFreeTypeOl PRIVATE LINKER:--exclude-libs,ALL) +elseif(APPLE) + target_link_options(libSceFreeTypeOl PRIVATE "LINKER:-exported_symbol,*_nid_*") endif() configure_windows_unwind(libSceFreeTypeOl) diff --git a/core/libs/prx/libSceFreeTypeOt/Export.cpp b/core/libs/prx/libSceFreeTypeOt/Export.cpp index 91243f68ee..8a34f36711 100644 --- a/core/libs/prx/libSceFreeTypeOt/Export.cpp +++ b/core/libs/prx/libSceFreeTypeOt/Export.cpp @@ -124,10 +124,10 @@ int APS5_VABI sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch() { } APS5_EXPORT("nSIux4OQyJM", sceFreeTypeOtUnknown_nSIux4OQyJM_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_Dr3F3qrSQ2c_nid_no_patch() { +int APS5_VABI sceFreeTypeOtGlyphCopy_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Dr3F3qrSQ2c", sceFreeTypeOtUnknown_Dr3F3qrSQ2c_nid_no_patch); +APS5_EXPORT("Dr3F3qrSQ2c", sceFreeTypeOtGlyphCopy_nid_no_patch); int APS5_VABI sceFreeTypeOtUnknown_avLzN0tTxLI_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -169,10 +169,10 @@ int APS5_VABI sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch() { } APS5_EXPORT("GiuNQjoj4fs", sceFreeTypeOtUnknown_GiuNQjoj4fs_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_5qqbXOGlHw4_nid_no_patch() { +int APS5_VABI sceFreeTypeOtLibraryVersion_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("5qqbXOGlHw4", sceFreeTypeOtUnknown_5qqbXOGlHw4_nid_no_patch); +APS5_EXPORT("5qqbXOGlHw4", sceFreeTypeOtLibraryVersion_nid_no_patch); int APS5_VABI sceFreeTypeOtUnknown_4LY5pyZ5IQk_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -194,10 +194,10 @@ int APS5_VABI sceFreeTypeOtUnknown_yrtsLmTfa48_nid_no_patch() { } APS5_EXPORT("yrtsLmTfa48", sceFreeTypeOtUnknown_yrtsLmTfa48_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_tCMhTh7h8D4_nid_no_patch() { +int APS5_VABI sceFreeTypeOtNewMemory_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("tCMhTh7h8D4", sceFreeTypeOtUnknown_tCMhTh7h8D4_nid_no_patch); +APS5_EXPORT("tCMhTh7h8D4", sceFreeTypeOtNewMemory_nid_no_patch); int APS5_VABI sceFreeTypeOtNewSize_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -229,10 +229,10 @@ int APS5_VABI sceFreeTypeOtOutlineDecompose_nid_no_patch() { } APS5_EXPORT("++lgV9pRsO4", sceFreeTypeOtOutlineDecompose_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch() { +int APS5_VABI sceFreeTypeOtOutlineDone_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("BqGJsJcFYaM", sceFreeTypeOtUnknown_BqGJsJcFYaM_nid_no_patch); +APS5_EXPORT("BqGJsJcFYaM", sceFreeTypeOtOutlineDone_nid_no_patch); int APS5_VABI sceFreeTypeOtUnknown_umIqupy8UU0_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -264,10 +264,10 @@ int APS5_VABI sceFreeTypeOtOutlineGetOrientation_nid_no_patch() { } APS5_EXPORT("weVsBCT+TTQ", sceFreeTypeOtOutlineGetOrientation_nid_no_patch); -int APS5_VABI sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch() { +int APS5_VABI sceFreeTypeOtOutlineNew_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("QH2emcgeFck", sceFreeTypeOtUnknown_QH2emcgeFck_nid_no_patch); +APS5_EXPORT("QH2emcgeFck", sceFreeTypeOtOutlineNew_nid_no_patch); int APS5_VABI sceFreeTypeOtUnknown_NG7OgYsgcF8_nid_no_patch() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceFsInternalForVsh/Export.cpp b/core/libs/prx/libSceFsInternalForVsh/Export.cpp index e98aa4a828..6806f0c3c7 100644 --- a/core/libs/prx/libSceFsInternalForVsh/Export.cpp +++ b/core/libs/prx/libSceFsInternalForVsh/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libSceFsInternalForVsh/include/InventoryExports.hpp" #include "prx/libSceFsInternalForVsh/include/Api.hpp" #include #include @@ -885,3 +887,734 @@ int APS5_VABI sceFsWsUmount() { } } + +extern "C" { + +int APS5_VABI sceFsBfsAllocateAdditionalContents() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsAllocateGameImage() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsAllocatePatchImage() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsAllocateSaveData() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsAllocateTrophyData() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsApplyPatch() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsCancelPatch() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsCommitPatch() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsContainerBind() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsContainerCreate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsContainerQuery() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsContainerUnbind() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsContainerUpdate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsExtendFile() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsFormat() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsFsck() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsGcGetStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsGcMove() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsGetAvailableSize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsGetBindStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsGetContainers() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsGetFileSize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsGetMapping() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsGetSCFStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsGetStatus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsM2Format() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsMount() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsTrim() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsBfsUnprotect() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsCreatePprPfsSaveDataImage() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsCreateWorkspace() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDeleteWorkspace() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDevpfsAllocateFile() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDevpfsAllocateSparseFile() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDevpfsMountForVSH() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDevpfsSystemPowerEvent() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDevpfsTermFetchMetaEntryCtx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDevpfsUmountForFSMP() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDevpfsUmountForVSH() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDevpfsWriteChunk() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDrfsDestroyContext() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDrfsDisconnect() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDrfsInitContext() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDrfsMount() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDrfsSuspendSocket() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDrfsUmount() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDrfsUnregisterSocket() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsDrfsWaitEvent() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsFetchWorkspaceFileMetadata() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsInitCreatePprPfsSaveDataOpt() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsInitCreateWorkspaceOpt() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsInitDeleteWorkspaceOpt() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsInitMountWorkspaceOpt() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsInitUmountWorkspaceOpt() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsIsActiveBankSystemEx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsIsActiveBankSystem() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsM2StorageCheckCleanFlag() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsM2StorageDeconfigure() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsM2StorageFsckWithMM() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsM2StorageGetM2UniqueId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsM2StorageInitialize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsM2StorageMount() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsM2StorageUnmount() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsM2StorageVerifyMetadata() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsMountPprSaveData() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsMountWorkspace() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsNewfsDefault() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsNsfsMount() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsPartitionSwitchBank() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsPathMappingResolve() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsPlaygoDisable() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsPlaygoEnable() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsPlaygoInitializeParams() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsPlaygoSetParams() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsTransactionCommit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsTransactionPprNoCowSaveDataMount() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsTransactionPprSaveDataMount() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsTransactionSaveDataReadParam() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsTransactionSaveDataUmount() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsTransactionSaveDataWriteParam() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsUfsCoerceCryptMode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsUfsMkfsProsperoApplicationTemporaryData() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsUfsMkfsProsperoDownloadData() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsUmountPprPkg() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsUpdatePackageMetadataApp0() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsWorkspaceCoreDump() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsWsWriteFile() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceFsWsWriteFileExtdata() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI libSceFsInternalForVshUnknown__plus_gVdjPejLwc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("+gVdjPejLwc", libSceFsInternalForVshUnknown__plus_gVdjPejLwc_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown__minus_HW6neCER_plus_A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-HW6neCER+A", libSceFsInternalForVshUnknown__minus_HW6neCER_plus_A_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_0JhonchgIa8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0JhonchgIa8", libSceFsInternalForVshUnknown_0JhonchgIa8_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_0XlyVxRNeIw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0XlyVxRNeIw", libSceFsInternalForVshUnknown_0XlyVxRNeIw_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_1lLh9mukbz0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1lLh9mukbz0", libSceFsInternalForVshUnknown_1lLh9mukbz0_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_2UgTmJt2dsc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("2UgTmJt2dsc", libSceFsInternalForVshUnknown_2UgTmJt2dsc_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_3TFUof4RLWM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("3TFUof4RLWM", libSceFsInternalForVshUnknown_3TFUof4RLWM_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_41PqBeun5cU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("41PqBeun5cU", libSceFsInternalForVshUnknown_41PqBeun5cU_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_4uqQOvVpISA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("4uqQOvVpISA", libSceFsInternalForVshUnknown_4uqQOvVpISA_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_68F1C7m2pO4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("68F1C7m2pO4", libSceFsInternalForVshUnknown_68F1C7m2pO4_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_6Bku_plus_Im1Pew_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6Bku+Im1Pew", libSceFsInternalForVshUnknown_6Bku_plus_Im1Pew_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_89zs8_plus_EWQYQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("89zs8+EWQYQ", libSceFsInternalForVshUnknown_89zs8_plus_EWQYQ_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_9X3ydcDhbhw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("9X3ydcDhbhw", libSceFsInternalForVshUnknown_9X3ydcDhbhw_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_AnMWzsLhsYk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("AnMWzsLhsYk", libSceFsInternalForVshUnknown_AnMWzsLhsYk_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_CFlYeKz_plus_HYs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("CFlYeKz+HYs", libSceFsInternalForVshUnknown_CFlYeKz_plus_HYs_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_DEHwNbsX2BA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DEHwNbsX2BA", libSceFsInternalForVshUnknown_DEHwNbsX2BA_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_DNjr3qYr_minus_lY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DNjr3qYr-lY", libSceFsInternalForVshUnknown_DNjr3qYr_minus_lY_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_DSmBrz8enws_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("DSmBrz8enws", libSceFsInternalForVshUnknown_DSmBrz8enws_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_Elqj7jkJd5s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Elqj7jkJd5s", libSceFsInternalForVshUnknown_Elqj7jkJd5s_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_GNeCtmqY_minus_58_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("GNeCtmqY-58", libSceFsInternalForVshUnknown_GNeCtmqY_minus_58_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_GsHxp1IXkfA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("GsHxp1IXkfA", libSceFsInternalForVshUnknown_GsHxp1IXkfA_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_KesAmkrIb9Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KesAmkrIb9Y", libSceFsInternalForVshUnknown_KesAmkrIb9Y_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_KrZEB_plus_TsZ5k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KrZEB+TsZ5k", libSceFsInternalForVshUnknown_KrZEB_plus_TsZ5k_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_LGPQXeAAFdc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LGPQXeAAFdc", libSceFsInternalForVshUnknown_LGPQXeAAFdc_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_LrpsS5hGlyY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LrpsS5hGlyY", libSceFsInternalForVshUnknown_LrpsS5hGlyY_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_M9342PuS_plus_y0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("M9342PuS+y0", libSceFsInternalForVshUnknown_M9342PuS_plus_y0_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_MQs7RB_minus_KfYM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("MQs7RB-KfYM", libSceFsInternalForVshUnknown_MQs7RB_minus_KfYM_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_N7SnQKgSGA4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("N7SnQKgSGA4", libSceFsInternalForVshUnknown_N7SnQKgSGA4_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_NHH7572Mh30_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NHH7572Mh30", libSceFsInternalForVshUnknown_NHH7572Mh30_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_NUfgeY29b9E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NUfgeY29b9E", libSceFsInternalForVshUnknown_NUfgeY29b9E_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_NdL2fJF8e78_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NdL2fJF8e78", libSceFsInternalForVshUnknown_NdL2fJF8e78_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_NrSqjlYMetU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NrSqjlYMetU", libSceFsInternalForVshUnknown_NrSqjlYMetU_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_O6akvoAE6k0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("O6akvoAE6k0", libSceFsInternalForVshUnknown_O6akvoAE6k0_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_P8F9U9uTPqA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("P8F9U9uTPqA", libSceFsInternalForVshUnknown_P8F9U9uTPqA_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_Rwk7z377rco_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Rwk7z377rco", libSceFsInternalForVshUnknown_Rwk7z377rco_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_SyNglOzKCV8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("SyNglOzKCV8", libSceFsInternalForVshUnknown_SyNglOzKCV8_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_T3LiDkpiS7s_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("T3LiDkpiS7s", libSceFsInternalForVshUnknown_T3LiDkpiS7s_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_UD35isX9168_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UD35isX9168", libSceFsInternalForVshUnknown_UD35isX9168_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_W8CmA_plus_wZ9_plus_w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("W8CmA+wZ9+w", libSceFsInternalForVshUnknown_W8CmA_plus_wZ9_plus_w_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_WwEWgYYuAmo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("WwEWgYYuAmo", libSceFsInternalForVshUnknown_WwEWgYYuAmo_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_XwPXO_plus_lxKy0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("XwPXO+lxKy0", libSceFsInternalForVshUnknown_XwPXO_plus_lxKy0_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_Y_minus_AXyBKGn8I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Y-AXyBKGn8I", libSceFsInternalForVshUnknown_Y_minus_AXyBKGn8I_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_Z9o0nKENoCQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Z9o0nKENoCQ", libSceFsInternalForVshUnknown_Z9o0nKENoCQ_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_ZcrLUvsmmrU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZcrLUvsmmrU", libSceFsInternalForVshUnknown_ZcrLUvsmmrU_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_a5ex0HGaufY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("a5ex0HGaufY", libSceFsInternalForVshUnknown_a5ex0HGaufY_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_b5kZJiwZPT8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("b5kZJiwZPT8", libSceFsInternalForVshUnknown_b5kZJiwZPT8_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_bGBEeJ2B6XE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bGBEeJ2B6XE", libSceFsInternalForVshUnknown_bGBEeJ2B6XE_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_cDZDkhZeK4o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cDZDkhZeK4o", libSceFsInternalForVshUnknown_cDZDkhZeK4o_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_cUyUz68FKBc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cUyUz68FKBc", libSceFsInternalForVshUnknown_cUyUz68FKBc_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_czWRa7s1ogE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("czWRa7s1ogE", libSceFsInternalForVshUnknown_czWRa7s1ogE_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_dKQBYtuMOWA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dKQBYtuMOWA", libSceFsInternalForVshUnknown_dKQBYtuMOWA_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_eV9ctY_plus_OiOo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("eV9ctY+OiOo", libSceFsInternalForVshUnknown_eV9ctY_plus_OiOo_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_g3Lf8TiVEBM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("g3Lf8TiVEBM", libSceFsInternalForVshUnknown_g3Lf8TiVEBM_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_gcOITai35ls_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("gcOITai35ls", libSceFsInternalForVshUnknown_gcOITai35ls_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_gerE9e47NoU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("gerE9e47NoU", libSceFsInternalForVshUnknown_gerE9e47NoU_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_gtzIEP_plus_e2jo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("gtzIEP+e2jo", libSceFsInternalForVshUnknown_gtzIEP_plus_e2jo_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_hfb5Y906dYA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hfb5Y906dYA", libSceFsInternalForVshUnknown_hfb5Y906dYA_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_hhPrQak7oFg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("hhPrQak7oFg", libSceFsInternalForVshUnknown_hhPrQak7oFg_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_iJQ7aolqS1A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("iJQ7aolqS1A", libSceFsInternalForVshUnknown_iJQ7aolqS1A_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_juY2ugdopus_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("juY2ugdopus", libSceFsInternalForVshUnknown_juY2ugdopus_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_k_minus_WaWvM74jI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("k-WaWvM74jI", libSceFsInternalForVshUnknown_k_minus_WaWvM74jI_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_lZ_plus_oSeAyI8Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lZ+oSeAyI8Q", libSceFsInternalForVshUnknown_lZ_plus_oSeAyI8Q_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_lxyJjALNmp4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lxyJjALNmp4", libSceFsInternalForVshUnknown_lxyJjALNmp4_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_mvNIgoNVeWA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("mvNIgoNVeWA", libSceFsInternalForVshUnknown_mvNIgoNVeWA_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_nIMoeyl3SRY_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("nIMoeyl3SRY", libSceFsInternalForVshUnknown_nIMoeyl3SRY_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_noVGZAYgjI8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("noVGZAYgjI8", libSceFsInternalForVshUnknown_noVGZAYgjI8_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_oP09BdVEs7o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("oP09BdVEs7o", libSceFsInternalForVshUnknown_oP09BdVEs7o_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_pt_plus_umHHWgvM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("pt+umHHWgvM", libSceFsInternalForVshUnknown_pt_plus_umHHWgvM_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_rOoqs69ugy8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("rOoqs69ugy8", libSceFsInternalForVshUnknown_rOoqs69ugy8_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_rakMRjDRGS4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("rakMRjDRGS4", libSceFsInternalForVshUnknown_rakMRjDRGS4_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_s7X_minus_JxrF650_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("s7X-JxrF650", libSceFsInternalForVshUnknown_s7X_minus_JxrF650_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_v3jcGxw1mR8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("v3jcGxw1mR8", libSceFsInternalForVshUnknown_v3jcGxw1mR8_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_v9uqOhwlhjs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("v9uqOhwlhjs", libSceFsInternalForVshUnknown_v9uqOhwlhjs_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_yadgQseaDKA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("yadgQseaDKA", libSceFsInternalForVshUnknown_yadgQseaDKA_nid_no_patch); + +int APS5_VABI libSceFsInternalForVshUnknown_ydRWehw26Kk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ydRWehw26Kk", libSceFsInternalForVshUnknown_ydRWehw26Kk_nid_no_patch); + +} diff --git a/core/libs/prx/libSceFsInternalForVsh/include/InventoryExports.hpp b/core/libs/prx/libSceFsInternalForVsh/include/InventoryExports.hpp new file mode 100644 index 0000000000..4209df27dd --- /dev/null +++ b/core/libs/prx/libSceFsInternalForVsh/include/InventoryExports.hpp @@ -0,0 +1,171 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceFsBfsAllocateAdditionalContents(); +int APS5_VABI sceFsBfsAllocateGameImage(); +int APS5_VABI sceFsBfsAllocatePatchImage(); +int APS5_VABI sceFsBfsAllocateSaveData(); +int APS5_VABI sceFsBfsAllocateTrophyData(); +int APS5_VABI sceFsBfsApplyPatch(); +int APS5_VABI sceFsBfsCancelPatch(); +int APS5_VABI sceFsBfsCommitPatch(); +int APS5_VABI sceFsBfsContainerBind(); +int APS5_VABI sceFsBfsContainerCreate(); +int APS5_VABI sceFsBfsContainerQuery(); +int APS5_VABI sceFsBfsContainerUnbind(); +int APS5_VABI sceFsBfsContainerUpdate(); +int APS5_VABI sceFsBfsExtendFile(); +int APS5_VABI sceFsBfsFormat(); +int APS5_VABI sceFsBfsFsck(); +int APS5_VABI sceFsBfsGcGetStatus(); +int APS5_VABI sceFsBfsGcMove(); +int APS5_VABI sceFsBfsGetAvailableSize(); +int APS5_VABI sceFsBfsGetBindStatus(); +int APS5_VABI sceFsBfsGetContainers(); +int APS5_VABI sceFsBfsGetFileSize(); +int APS5_VABI sceFsBfsGetMapping(); +int APS5_VABI sceFsBfsGetSCFStatus(); +int APS5_VABI sceFsBfsGetStatus(); +int APS5_VABI sceFsBfsM2Format(); +int APS5_VABI sceFsBfsMount(); +int APS5_VABI sceFsBfsTrim(); +int APS5_VABI sceFsBfsUnprotect(); +int APS5_VABI sceFsCreatePprPfsSaveDataImage(); +int APS5_VABI sceFsCreateWorkspace(); +int APS5_VABI sceFsDeleteWorkspace(); +int APS5_VABI sceFsDevpfsAllocateFile(); +int APS5_VABI sceFsDevpfsAllocateSparseFile(); +int APS5_VABI sceFsDevpfsMountForVSH(); +int APS5_VABI sceFsDevpfsSystemPowerEvent(); +int APS5_VABI sceFsDevpfsTermFetchMetaEntryCtx(); +int APS5_VABI sceFsDevpfsUmountForFSMP(); +int APS5_VABI sceFsDevpfsUmountForVSH(); +int APS5_VABI sceFsDevpfsWriteChunk(); +int APS5_VABI sceFsDrfsDestroyContext(); +int APS5_VABI sceFsDrfsDisconnect(); +int APS5_VABI sceFsDrfsInitContext(); +int APS5_VABI sceFsDrfsMount(); +int APS5_VABI sceFsDrfsSuspendSocket(); +int APS5_VABI sceFsDrfsUmount(); +int APS5_VABI sceFsDrfsUnregisterSocket(); +int APS5_VABI sceFsDrfsWaitEvent(); +int APS5_VABI sceFsFetchWorkspaceFileMetadata(); +int APS5_VABI sceFsInitCreatePprPfsSaveDataOpt(); +int APS5_VABI sceFsInitCreateWorkspaceOpt(); +int APS5_VABI sceFsInitDeleteWorkspaceOpt(); +int APS5_VABI sceFsInitMountWorkspaceOpt(); +int APS5_VABI sceFsInitUmountWorkspaceOpt(); +int APS5_VABI sceFsIsActiveBankSystemEx(); +int APS5_VABI sceFsIsActiveBankSystem(); +int APS5_VABI sceFsM2StorageCheckCleanFlag(); +int APS5_VABI sceFsM2StorageDeconfigure(); +int APS5_VABI sceFsM2StorageFsckWithMM(); +int APS5_VABI sceFsM2StorageGetM2UniqueId(); +int APS5_VABI sceFsM2StorageInitialize(); +int APS5_VABI sceFsM2StorageMount(); +int APS5_VABI sceFsM2StorageUnmount(); +int APS5_VABI sceFsM2StorageVerifyMetadata(); +int APS5_VABI sceFsMountPprSaveData(); +int APS5_VABI sceFsMountWorkspace(); +int APS5_VABI sceFsNewfsDefault(); +int APS5_VABI sceFsNsfsMount(); +int APS5_VABI sceFsPartitionSwitchBank(); +int APS5_VABI sceFsPathMappingResolve(); +int APS5_VABI sceFsPlaygoDisable(); +int APS5_VABI sceFsPlaygoEnable(); +int APS5_VABI sceFsPlaygoInitializeParams(); +int APS5_VABI sceFsPlaygoSetParams(); +int APS5_VABI sceFsTransactionCommit(); +int APS5_VABI sceFsTransactionPprNoCowSaveDataMount(); +int APS5_VABI sceFsTransactionPprSaveDataMount(); +int APS5_VABI sceFsTransactionSaveDataReadParam(); +int APS5_VABI sceFsTransactionSaveDataUmount(); +int APS5_VABI sceFsTransactionSaveDataWriteParam(); +int APS5_VABI sceFsUfsCoerceCryptMode(); +int APS5_VABI sceFsUfsMkfsProsperoApplicationTemporaryData(); +int APS5_VABI sceFsUfsMkfsProsperoDownloadData(); +int APS5_VABI sceFsUmountPprPkg(); +int APS5_VABI sceFsUpdatePackageMetadataApp0(); +int APS5_VABI sceFsWorkspaceCoreDump(); +int APS5_VABI sceFsWsWriteFile(); +int APS5_VABI sceFsWsWriteFileExtdata(); +int APS5_VABI libSceFsInternalForVshUnknown__plus_gVdjPejLwc_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown__minus_HW6neCER_plus_A_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_0JhonchgIa8_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_0XlyVxRNeIw_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_1lLh9mukbz0_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_2UgTmJt2dsc_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_3TFUof4RLWM_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_41PqBeun5cU_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_4uqQOvVpISA_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_68F1C7m2pO4_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_6Bku_plus_Im1Pew_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_89zs8_plus_EWQYQ_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_9X3ydcDhbhw_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_AnMWzsLhsYk_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_CFlYeKz_plus_HYs_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_DEHwNbsX2BA_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_DNjr3qYr_minus_lY_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_DSmBrz8enws_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_Elqj7jkJd5s_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_GNeCtmqY_minus_58_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_GsHxp1IXkfA_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_KesAmkrIb9Y_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_KrZEB_plus_TsZ5k_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_LGPQXeAAFdc_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_LrpsS5hGlyY_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_M9342PuS_plus_y0_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_MQs7RB_minus_KfYM_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_N7SnQKgSGA4_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_NHH7572Mh30_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_NUfgeY29b9E_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_NdL2fJF8e78_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_NrSqjlYMetU_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_O6akvoAE6k0_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_P8F9U9uTPqA_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_Rwk7z377rco_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_SyNglOzKCV8_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_T3LiDkpiS7s_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_UD35isX9168_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_W8CmA_plus_wZ9_plus_w_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_WwEWgYYuAmo_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_XwPXO_plus_lxKy0_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_Y_minus_AXyBKGn8I_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_Z9o0nKENoCQ_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_ZcrLUvsmmrU_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_a5ex0HGaufY_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_b5kZJiwZPT8_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_bGBEeJ2B6XE_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_cDZDkhZeK4o_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_cUyUz68FKBc_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_czWRa7s1ogE_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_dKQBYtuMOWA_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_eV9ctY_plus_OiOo_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_g3Lf8TiVEBM_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_gcOITai35ls_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_gerE9e47NoU_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_gtzIEP_plus_e2jo_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_hfb5Y906dYA_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_hhPrQak7oFg_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_iJQ7aolqS1A_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_juY2ugdopus_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_k_minus_WaWvM74jI_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_lZ_plus_oSeAyI8Q_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_lxyJjALNmp4_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_mvNIgoNVeWA_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_nIMoeyl3SRY_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_noVGZAYgjI8_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_oP09BdVEs7o_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_pt_plus_umHHWgvM_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_rOoqs69ugy8_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_rakMRjDRGS4_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_s7X_minus_JxrF650_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_v3jcGxw1mR8_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_v9uqOhwlhjs_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_yadgQseaDKA_nid_no_patch(); +int APS5_VABI libSceFsInternalForVshUnknown_ydRWehw26Kk_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceFsInternalForVsh/libSceFsInternalForVsh.hpp b/core/libs/prx/libSceFsInternalForVsh/libSceFsInternalForVsh.hpp index bba8925b6e..65d7230487 100644 --- a/core/libs/prx/libSceFsInternalForVsh/libSceFsInternalForVsh.hpp +++ b/core/libs/prx/libSceFsInternalForVsh/libSceFsInternalForVsh.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceFsInternalForVsh/include/Api.hpp" +#include "prx/libSceFsInternalForVsh/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceGLSlimVSH/CMakeLists.txt b/core/libs/prx/libSceGLSlimVSH/CMakeLists.txt index de516797aa..6127f40fd9 100644 --- a/core/libs/prx/libSceGLSlimVSH/CMakeLists.txt +++ b/core/libs/prx/libSceGLSlimVSH/CMakeLists.txt @@ -1,3 +1,22 @@ -add_library(libSceGLSlimVSH SHARED EXCLUDE_FROM_ALL Export.cpp) +add_library(libSceGLSlimVSH SHARED EXCLUDE_FROM_ALL + Export.cpp + src/State.cpp + src/Egl.cpp + src/GlState.cpp + src/Objects.cpp + src/Images.cpp) target_include_directories(libSceGLSlimVSH PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceGLSlimVSH) + +if(BUILD_TESTING) + add_test_executable(guest_glslim_state_tests tests/State.cpp) + target_include_directories(guest_glslim_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_glslim_state_tests PRIVATE libSceGLSlimVSH libc) + configure_windows_unwind(guest_glslim_state_tests) + add_test(NAME guest_glslim_state COMMAND guest_glslim_state_tests) + set_tests_properties(guest_glslim_state PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_glslim_state PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceGLSlimVSH/Export.cpp b/core/libs/prx/libSceGLSlimVSH/Export.cpp index 2989577c60..ef33acc1b9 100644 --- a/core/libs/prx/libSceGLSlimVSH/Export.cpp +++ b/core/libs/prx/libSceGLSlimVSH/Export.cpp @@ -7,14 +7,6 @@ extern "C" { -EGLBoolean APS5_VABI eglBindTexImage_nid_postfix(EGLDisplay dpy, EGLSurface surface, EGLint buffer) { - (void)dpy; - (void)surface; - (void)buffer; - NotImplemented_nid_no_patch(__func__); - return {}; -} - EGLSurface APS5_VABI eglCreatePbufferFromClientBuffer_nid_postfix(EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list) { (void)dpy; (void)buftype; @@ -25,323 +17,13 @@ EGLSurface APS5_VABI eglCreatePbufferFromClientBuffer_nid_postfix(EGLDisplay dpy return {}; } -EGLSurface APS5_VABI eglCreatePbufferSurface_nid_postfix(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list) { - (void)dpy; - (void)config; - (void)attrib_list; - NotImplemented_nid_no_patch(__func__); - return {}; -} -EGLBoolean APS5_VABI eglGetConfigAttrib_nid_postfix(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value) { - (void)dpy; - (void)config; - (void)attribute; - (void)value; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLBoolean APS5_VABI eglGetConfigs_nid_postfix(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config) { - (void)dpy; - (void)configs; - (void)config_size; - (void)num_config; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLContext APS5_VABI eglGetCurrentContext_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLDisplay APS5_VABI eglGetCurrentDisplay_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLSurface APS5_VABI eglGetCurrentSurface_nid_postfix(EGLint readdraw) { - (void)readdraw; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -__eglMustCastToProperFunctionPointerType APS5_VABI eglGetProcAddress_nid_postfix(const char *procname) { - (void)procname; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLenum APS5_VABI eglQueryAPI_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLBoolean APS5_VABI eglQueryContext_nid_postfix(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value) { - (void)dpy; - (void)ctx; - (void)attribute; - (void)value; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -const char * APS5_VABI eglQueryString_nid_postfix(EGLDisplay dpy, EGLint name) { - (void)dpy; - (void)name; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLBoolean APS5_VABI eglQuerySurface_nid_postfix(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value) { - (void)dpy; - (void)surface; - (void)attribute; - (void)value; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLBoolean APS5_VABI eglReleaseTexImage_nid_postfix(EGLDisplay dpy, EGLSurface surface, EGLint buffer) { - (void)dpy; - (void)surface; - (void)buffer; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLBoolean APS5_VABI eglReleaseThread_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLBoolean APS5_VABI eglSurfaceAttrib_nid_postfix(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value) { - (void)dpy; - (void)surface; - (void)attribute; - (void)value; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLBoolean APS5_VABI eglWaitClient_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLBoolean APS5_VABI eglWaitGL_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return {}; -} - -EGLBoolean APS5_VABI eglWaitNative_nid_postfix(EGLint engine) { - (void)engine; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -void APS5_VABI glBindAttribLocation_nid_postfix(GLuint program, GLuint index, const GLchar *name) { - (void)program; - (void)index; - (void)name; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glBindBuffer_nid_postfix(GLenum target, GLuint buffer) { - (void)target; - (void)buffer; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glBindFramebuffer_nid_postfix(GLenum target, GLuint framebuffer) { - (void)target; - (void)framebuffer; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glBindRenderbuffer_nid_postfix(GLenum target, GLuint renderbuffer) { - (void)target; - (void)renderbuffer; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glBlendColor_nid_postfix(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { - (void)red; - (void)green; - (void)blue; - (void)alpha; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glBlendEquation_nid_postfix(GLenum mode) { - (void)mode; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glBlendEquationSeparate_nid_postfix(GLenum modeRGB, GLenum modeAlpha) { - (void)modeRGB; - (void)modeAlpha; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glBlendFuncSeparate_nid_postfix(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha) { - (void)sfactorRGB; - (void)dfactorRGB; - (void)sfactorAlpha; - (void)dfactorAlpha; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glBufferData_nid_postfix(GLenum target, GLsizeiptr size, const void *data, GLenum usage) { - (void)target; - (void)size; - (void)data; - (void)usage; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glBufferSubData_nid_postfix(GLenum target, GLintptr offset, GLsizeiptr size, const void *data) { - (void)target; - (void)offset; - (void)size; - (void)data; - NotImplemented_nid_no_patch(__func__); -} - -GLenum APS5_VABI glCheckFramebufferStatus_nid_postfix(GLenum target) { - (void)target; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -void APS5_VABI glClearDepthf_nid_postfix(GLfloat d) { - (void)d; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glClearStencil_nid_postfix(GLint s) { - (void)s; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glColorMask_nid_postfix(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) { - (void)red; - (void)green; - (void)blue; - (void)alpha; - NotImplemented_nid_no_patch(__func__); -} void APS5_VABI glCompileShader_nid_postfix(GLuint shader) { (void)shader; NotImplemented_nid_no_patch(__func__); } -void APS5_VABI glCompressedTexImage2D_nid_postfix(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data) { - (void)target; - (void)level; - (void)internalformat; - (void)width; - (void)height; - (void)border; - (void)imageSize; - (void)data; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glCompressedTexSubImage2D_nid_postfix(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data) { - (void)target; - (void)level; - (void)xoffset; - (void)yoffset; - (void)width; - (void)height; - (void)format; - (void)imageSize; - (void)data; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glCopyTexImage2D_nid_postfix(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { - (void)target; - (void)level; - (void)internalformat; - (void)x; - (void)y; - (void)width; - (void)height; - (void)border; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glCopyTexSubImage2D_nid_postfix(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { - (void)target; - (void)level; - (void)xoffset; - (void)yoffset; - (void)x; - (void)y; - (void)width; - (void)height; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glCullFace_nid_postfix(GLenum mode) { - (void)mode; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glDeleteBuffers_nid_postfix(GLsizei n, const GLuint *buffers) { - (void)n; - (void)buffers; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glDeleteFramebuffers_nid_postfix(GLsizei n, const GLuint *framebuffers) { - (void)n; - (void)framebuffers; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glDeleteRenderbuffers_nid_postfix(GLsizei n, const GLuint *renderbuffers) { - (void)n; - (void)renderbuffers; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glDepthFunc_nid_postfix(GLenum func) { - (void)func; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glDepthMask_nid_postfix(GLboolean flag) { - (void)flag; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glDepthRangef_nid_postfix(GLfloat n, GLfloat f) { - (void)n; - (void)f; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glDetachShader_nid_postfix(GLuint program, GLuint shader) { - (void)program; - (void)shader; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glDisable_nid_postfix(GLenum cap) { - (void)cap; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glDisableVertexAttribArray_nid_postfix(GLuint index) { - (void)index; - NotImplemented_nid_no_patch(__func__); -} - void APS5_VABI glDrawElements_nid_postfix(GLenum mode, GLsizei count, GLenum type, const void *indices) { (void)mode; (void)count; @@ -350,123 +32,6 @@ void APS5_VABI glDrawElements_nid_postfix(GLenum mode, GLsizei count, GLenum typ NotImplemented_nid_no_patch(__func__); } -void APS5_VABI glFinish_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glFlush_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glFramebufferRenderbuffer_nid_postfix(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) { - (void)target; - (void)attachment; - (void)renderbuffertarget; - (void)renderbuffer; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glFramebufferTexture2D_nid_postfix(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) { - (void)target; - (void)attachment; - (void)textarget; - (void)texture; - (void)level; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glFrontFace_nid_postfix(GLenum mode) { - (void)mode; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGenBuffers_nid_postfix(GLsizei n, GLuint *buffers) { - (void)n; - (void)buffers; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGenFramebuffers_nid_postfix(GLsizei n, GLuint *framebuffers) { - (void)n; - (void)framebuffers; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGenRenderbuffers_nid_postfix(GLsizei n, GLuint *renderbuffers) { - (void)n; - (void)renderbuffers; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGenerateMipmap_nid_postfix(GLenum target) { - (void)target; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetAttachedShaders_nid_postfix(GLuint program, GLsizei maxCount, GLsizei *count, GLuint *shaders) { - (void)program; - (void)maxCount; - (void)count; - (void)shaders; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetBooleanv_nid_postfix(GLenum pname, GLboolean *data) { - (void)pname; - (void)data; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetBufferParameteriv_nid_postfix(GLenum target, GLenum pname, GLint *params) { - (void)target; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetFloatv_nid_postfix(GLenum pname, GLfloat *data) { - (void)pname; - (void)data; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetFramebufferAttachmentParameteriv_nid_postfix(GLenum target, GLenum attachment, GLenum pname, GLint *params) { - (void)target; - (void)attachment; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetIntegerv_nid_postfix(GLenum pname, GLint *data) { - (void)pname; - (void)data; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetProgramInfoLog_nid_postfix(GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog) { - (void)program; - (void)bufSize; - (void)length; - (void)infoLog; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetRenderbufferParameteriv_nid_postfix(GLenum target, GLenum pname, GLint *params) { - (void)target; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetShaderInfoLog_nid_postfix(GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog) { - (void)shader; - (void)bufSize; - (void)length; - (void)infoLog; - NotImplemented_nid_no_patch(__func__); -} - void APS5_VABI glGetShaderPrecisionFormat_nid_postfix(GLenum shadertype, GLenum precisiontype, GLint *range, GLint *precision) { (void)shadertype; (void)precisiontype; @@ -475,27 +40,6 @@ void APS5_VABI glGetShaderPrecisionFormat_nid_postfix(GLenum shadertype, GLenum NotImplemented_nid_no_patch(__func__); } -void APS5_VABI glGetShaderiv_nid_postfix(GLuint shader, GLenum pname, GLint *params) { - (void)shader; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetTexParameterfv_nid_postfix(GLenum target, GLenum pname, GLfloat *params) { - (void)target; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetTexParameteriv_nid_postfix(GLenum target, GLenum pname, GLint *params) { - (void)target; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - void APS5_VABI glGetUniformfv_nid_postfix(GLuint program, GLint location, GLfloat *params) { (void)program; (void)location; @@ -510,206 +54,6 @@ void APS5_VABI glGetUniformiv_nid_postfix(GLuint program, GLint location, GLint NotImplemented_nid_no_patch(__func__); } -void APS5_VABI glGetVertexAttribPointerv_nid_postfix(GLuint index, GLenum pname, void **pointer) { - (void)index; - (void)pname; - (void)pointer; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetVertexAttribfv_nid_postfix(GLuint index, GLenum pname, GLfloat *params) { - (void)index; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glGetVertexAttribiv_nid_postfix(GLuint index, GLenum pname, GLint *params) { - (void)index; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glHint_nid_postfix(GLenum target, GLenum mode) { - (void)target; - (void)mode; - NotImplemented_nid_no_patch(__func__); -} - -GLboolean APS5_VABI glIsBuffer_nid_postfix(GLuint buffer) { - (void)buffer; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -GLboolean APS5_VABI glIsEnabled_nid_postfix(GLenum cap) { - (void)cap; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -GLboolean APS5_VABI glIsFramebuffer_nid_postfix(GLuint framebuffer) { - (void)framebuffer; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -GLboolean APS5_VABI glIsProgram_nid_postfix(GLuint program) { - (void)program; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -GLboolean APS5_VABI glIsRenderbuffer_nid_postfix(GLuint renderbuffer) { - (void)renderbuffer; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -GLboolean APS5_VABI glIsShader_nid_postfix(GLuint shader) { - (void)shader; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -GLboolean APS5_VABI glIsTexture_nid_postfix(GLuint texture) { - (void)texture; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -void APS5_VABI glLineWidth_nid_postfix(GLfloat width) { - (void)width; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glPolygonOffset_nid_postfix(GLfloat factor, GLfloat units) { - (void)factor; - (void)units; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glReadPixels_nid_postfix(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels) { - (void)x; - (void)y; - (void)width; - (void)height; - (void)format; - (void)type; - (void)pixels; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glReleaseShaderCompiler_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glRenderbufferStorage_nid_postfix(GLenum target, GLenum internalformat, GLsizei width, GLsizei height) { - (void)target; - (void)internalformat; - (void)width; - (void)height; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glSampleCoverage_nid_postfix(GLfloat value, GLboolean invert) { - (void)value; - (void)invert; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glScissor_nid_postfix(GLint x, GLint y, GLsizei width, GLsizei height) { - (void)x; - (void)y; - (void)width; - (void)height; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glShaderSource_nid_postfix(GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length) { - (void)shader; - (void)count; - (void)string; - (void)length; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glStencilFunc_nid_postfix(GLenum func, GLint ref, GLuint mask) { - (void)func; - (void)ref; - (void)mask; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glStencilFuncSeparate_nid_postfix(GLenum face, GLenum func, GLint ref, GLuint mask) { - (void)face; - (void)func; - (void)ref; - (void)mask; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glStencilMask_nid_postfix(GLuint mask) { - (void)mask; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glStencilMaskSeparate_nid_postfix(GLenum face, GLuint mask) { - (void)face; - (void)mask; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glStencilOp_nid_postfix(GLenum fail, GLenum zfail, GLenum zpass) { - (void)fail; - (void)zfail; - (void)zpass; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glStencilOpSeparate_nid_postfix(GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass) { - (void)face; - (void)sfail; - (void)dpfail; - (void)dppass; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glTexParameterf_nid_postfix(GLenum target, GLenum pname, GLfloat param) { - (void)target; - (void)pname; - (void)param; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glTexParameterfv_nid_postfix(GLenum target, GLenum pname, const GLfloat *params) { - (void)target; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glTexParameteriv_nid_postfix(GLenum target, GLenum pname, const GLint *params) { - (void)target; - (void)pname; - (void)params; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glTexSubImage2D_nid_postfix(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels) { - (void)target; - (void)level; - (void)xoffset; - (void)yoffset; - (void)width; - (void)height; - (void)format; - (void)type; - (void)pixels; - NotImplemented_nid_no_patch(__func__); -} - void APS5_VABI glUniform1fv_nid_postfix(GLint location, GLsizei count, const GLfloat *value) { (void)location; (void)count; @@ -835,191 +179,120 @@ void APS5_VABI glValidateProgram_nid_postfix(GLuint program) { NotImplemented_nid_no_patch(__func__); } -void APS5_VABI glVertexAttrib1f_nid_postfix(GLuint index, GLfloat x) { - (void)index; - (void)x; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glVertexAttrib1fv_nid_postfix(GLuint index, const GLfloat *v) { - (void)index; - (void)v; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glVertexAttrib2f_nid_postfix(GLuint index, GLfloat x, GLfloat y) { - (void)index; - (void)x; - (void)y; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glVertexAttrib2fv_nid_postfix(GLuint index, const GLfloat *v) { - (void)index; - (void)v; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glVertexAttrib3f_nid_postfix(GLuint index, GLfloat x, GLfloat y, GLfloat z) { - (void)index; - (void)x; - (void)y; - (void)z; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glVertexAttrib3fv_nid_postfix(GLuint index, const GLfloat *v) { - (void)index; - (void)v; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glVertexAttrib4f_nid_postfix(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w) { - (void)index; - (void)x; - (void)y; - (void)z; - (void)w; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI glVertexAttrib4fv_nid_postfix(GLuint index, const GLfloat *v) { - (void)index; - (void)v; - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceGLSlimVSHUnknown_iIsF4NzYgD4_nid_no_patch() { +int APS5_VABI glOrbisMapTextureResourceSCE_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("iIsF4NzYgD4", sceGLSlimVSHUnknown_iIsF4NzYgD4_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_hA8TWZkwPRU_nid_no_patch() { +int APS5_VABI glOrbisTexImageCanvas2DSCE_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("hA8TWZkwPRU", sceGLSlimVSHUnknown_hA8TWZkwPRU_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_LuP1plTAR7g_nid_no_patch() { +int APS5_VABI glOrbisTexImageResourceSCE_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("LuP1plTAR7g", sceGLSlimVSHUnknown_LuP1plTAR7g_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_J5IG3lL56FI_nid_no_patch() { +int APS5_VABI glOrbisUnmapTextureResourceSCE_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("J5IG3lL56FI", sceGLSlimVSHUnknown_J5IG3lL56FI_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_oXkQEVitkCs_nid_no_patch() { +int APS5_VABI scePigletAllocateSystemMemoryEx() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("oXkQEVitkCs", sceGLSlimVSHUnknown_oXkQEVitkCs_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_sDotUt1uEhA_nid_no_patch() { +int APS5_VABI scePigletAllocateVideoMemoryEx() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("sDotUt1uEhA", sceGLSlimVSHUnknown_sDotUt1uEhA_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_3gtNzvkq_XY_nid_no_patch() { +int APS5_VABI scePigletGetConfigurationVSH() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("3gtNzvkq-XY", sceGLSlimVSHUnknown_3gtNzvkq_XY_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_T__xooU3VvQ_nid_no_patch() { +int APS5_VABI scePigletSetConfigurationVSH() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("T-+xooU3VvQ", sceGLSlimVSHUnknown_T__xooU3VvQ_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_AcruVcmKz78_nid_no_patch() { +int APS5_VABI scePigletReleaseSystemMemoryEx() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("AcruVcmKz78", sceGLSlimVSHUnknown_AcruVcmKz78_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_QvXbiLqpn6c_nid_no_patch() { +int APS5_VABI scePigletReleaseVideoMemoryEx() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("QvXbiLqpn6c", sceGLSlimVSHUnknown_QvXbiLqpn6c_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_mgKTS0PIvd8_nid_no_patch() { +int APS5_VABI sceSlimglClientStartIPC() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("mgKTS0PIvd8", sceGLSlimVSHUnknown_mgKTS0PIvd8_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_RP5cXUjm48U_nid_no_patch() { +int APS5_VABI sceSlimglCompositorAllocateIndex() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("RP5cXUjm48U", sceGLSlimVSHUnknown_RP5cXUjm48U_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_srJHx3p48v0_nid_no_patch() { +int APS5_VABI sceSlimglCompositorCreateIndirectRenderTarget() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("srJHx3p48v0", sceGLSlimVSHUnknown_srJHx3p48v0_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_6ZQXcSKKjyo_nid_no_patch() { +int APS5_VABI sceSlimglCompositorDeleteIndirectRenderTarget() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("6ZQXcSKKjyo", sceGLSlimVSHUnknown_6ZQXcSKKjyo_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_wdRBDMIxI7I_nid_no_patch() { +int APS5_VABI sceSlimglCompositorFlush() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("wdRBDMIxI7I", sceGLSlimVSHUnknown_wdRBDMIxI7I_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_CzmA_OALrH8_nid_no_patch() { +int APS5_VABI sceSlimglCompositorFlushWithRepeat() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("CzmA-OALrH8", sceGLSlimVSHUnknown_CzmA_OALrH8_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_aRqvgeH_oe0_nid_no_patch() { +int APS5_VABI sceSlimglCompositorGetCanvasHandle() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("aRqvgeH+oe0", sceGLSlimVSHUnknown_aRqvgeH_oe0_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_gI_XEd5mWPU_nid_no_patch() { +int APS5_VABI sceSlimglCompositorGetError() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("gI+XEd5mWPU", sceGLSlimVSHUnknown_gI_XEd5mWPU_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_1TFdFqQBGqY_nid_no_patch() { +int APS5_VABI sceSlimglCompositorReleaseIndex() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("1TFdFqQBGqY", sceGLSlimVSHUnknown_1TFdFqQBGqY_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_7Mp6qE235Qc_nid_no_patch() { +int APS5_VABI sceSlimglCompositorSetEndOfFrameCommand() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("7Mp6qE235Qc", sceGLSlimVSHUnknown_7Mp6qE235Qc_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_BrNgn7z6A40_nid_no_patch() { +int APS5_VABI sceSlimglCompositorSetIndirectCanvasCommand() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("BrNgn7z6A40", sceGLSlimVSHUnknown_BrNgn7z6A40_nid_no_patch); -int APS5_VABI sceGLSlimVSHUnknown_iyTnezpw1jE_nid_no_patch() { +int APS5_VABI sceSlimglCompositorSetIndirectCompositionCommand() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceSlimglCompositorSetIndirectRenderTargetConfigCommand() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("iyTnezpw1jE", sceGLSlimVSHUnknown_iyTnezpw1jE_nid_no_patch); } @@ -1093,3 +366,51 @@ int APS5_VABI libSceGLSlimVSHInventory_qSuVNKHENqs_nid_no_patch() { APS5_EXPORT("qSuVNKHENqs", libSceGLSlimVSHInventory_qSuVNKHENqs_nid_no_patch); } + +extern "C" void APS5_VABI glDrawArrays_nid_postfix(GLenum mode, GLint first, GLsizei count) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" void APS5_VABI glLinkProgram_nid_postfix(GLuint program) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" void APS5_VABI glShaderBinary_nid_postfix(GLsizei count, const GLuint* shaders, GLenum format, const void* binary, GLsizei length) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" const unsigned char* APS5_VABI glGetString_nid_postfix(GLenum name) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" GLint APS5_VABI glGetAttribLocation_nid_postfix(GLuint program, const GLchar* name) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" GLint APS5_VABI glGetUniformLocation_nid_postfix(GLuint program, const GLchar* name) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" void APS5_VABI glGetActiveAttrib_nid_postfix(GLuint program, GLuint index, GLsizei size, GLsizei* length, GLint* count, GLenum* type, GLchar* name) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" void APS5_VABI glGetActiveUniform_nid_postfix(GLuint program, GLuint index, GLsizei size, GLsizei* length, GLint* count, GLenum* type, GLchar* name) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" void APS5_VABI glUniform1f_nid_postfix(GLint location, GLfloat value) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" void APS5_VABI glUniform1i_nid_postfix(GLint location, GLint value) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" void APS5_VABI glUniformMatrix4fv_nid_postfix(GLint location, GLsizei count, GLboolean transpose, const GLfloat* values) { + NotImplemented_nid_no_patch(__func__); +} + +extern "C" EGLSurface APS5_VABI eglCreateWindowSurface_nid_postfix(EGLDisplay display, EGLConfig config, void* window, const EGLint* attributes) { + NotImplemented_nid_no_patch(__func__); +} diff --git a/core/libs/prx/libSceGLSlimVSH/include/Api.hpp b/core/libs/prx/libSceGLSlimVSH/include/Api.hpp index 9559fb141a..db10bb29f6 100644 --- a/core/libs/prx/libSceGLSlimVSH/include/Api.hpp +++ b/core/libs/prx/libSceGLSlimVSH/include/Api.hpp @@ -152,5 +152,51 @@ void APS5_VABI glVertexAttrib3f_nid_postfix(GLuint index, GLfloat x, GLfloat y, void APS5_VABI glVertexAttrib3fv_nid_postfix(GLuint index, const GLfloat *v); void APS5_VABI glVertexAttrib4f_nid_postfix(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); void APS5_VABI glVertexAttrib4fv_nid_postfix(GLuint index, const GLfloat *v); +EGLDisplay APS5_VABI eglGetDisplay_nid_postfix(void* nativeDisplay); +EGLint APS5_VABI eglGetError_nid_postfix(); +EGLBoolean APS5_VABI eglInitialize_nid_postfix(EGLDisplay display, EGLint* major, EGLint* minor); +EGLBoolean APS5_VABI eglTerminate_nid_postfix(EGLDisplay display); +EGLBoolean APS5_VABI eglBindAPI_nid_postfix(EGLenum api); +EGLBoolean APS5_VABI eglChooseConfig_nid_postfix(EGLDisplay display, const EGLint* attributes, EGLConfig* configs, EGLint size, EGLint* count); +EGLContext APS5_VABI eglCreateContext_nid_postfix(EGLDisplay display, EGLConfig config, EGLContext shared, const EGLint* attributes); +EGLBoolean APS5_VABI eglDestroyContext_nid_postfix(EGLDisplay display, EGLContext handle); +EGLBoolean APS5_VABI eglDestroySurface_nid_postfix(EGLDisplay display, EGLSurface handle); +EGLBoolean APS5_VABI eglMakeCurrent_nid_postfix(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext handle); +EGLBoolean APS5_VABI eglSwapBuffers_nid_postfix(EGLDisplay display, EGLSurface surface); +EGLBoolean APS5_VABI eglSwapInterval_nid_postfix(EGLDisplay display, EGLint interval); +GLenum APS5_VABI glGetError_nid_postfix(); +void APS5_VABI glEnable_nid_postfix(GLenum cap); +void APS5_VABI glBlendFunc_nid_postfix(GLenum source, GLenum destination); +void APS5_VABI glPixelStorei_nid_postfix(GLenum name, GLint value); +void APS5_VABI glVertexAttribPointer_nid_postfix(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); +void APS5_VABI glEnableVertexAttribArray_nid_postfix(GLuint index); +void APS5_VABI glClearColor_nid_postfix(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void APS5_VABI glViewport_nid_postfix(GLint x, GLint y, GLsizei width, GLsizei height); +void APS5_VABI glActiveTexture_nid_postfix(GLenum texture); +void APS5_VABI glGenTextures_nid_postfix(GLsizei count, GLuint* textures); +void APS5_VABI glBindTexture_nid_postfix(GLenum target, GLuint name); +void APS5_VABI glDeleteTextures_nid_postfix(GLsizei count, const GLuint* textures); +void APS5_VABI glTexParameteri_nid_postfix(GLenum target, GLenum name, GLint value); +void APS5_VABI glTexImage2D_nid_postfix(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels); +void APS5_VABI glClear_nid_postfix(GLenum mask); +GLuint APS5_VABI glCreateShader_nid_postfix(GLenum type); +GLuint APS5_VABI glCreateProgram_nid_postfix(); +void APS5_VABI glDeleteShader_nid_postfix(GLuint name); +void APS5_VABI glDeleteProgram_nid_postfix(GLuint name); +void APS5_VABI glAttachShader_nid_postfix(GLuint program, GLuint shader); +void APS5_VABI glGetProgramiv_nid_postfix(GLuint program, GLenum name, GLint* value); +void APS5_VABI glUseProgram_nid_postfix(GLuint program); +void APS5_VABI glDrawArrays_nid_postfix(GLenum mode, GLint first, GLsizei count); +void APS5_VABI glLinkProgram_nid_postfix(GLuint program); +void APS5_VABI glShaderBinary_nid_postfix(GLsizei count, const GLuint* shaders, GLenum format, const void* binary, GLsizei length); +const unsigned char* APS5_VABI glGetString_nid_postfix(GLenum name); +GLint APS5_VABI glGetAttribLocation_nid_postfix(GLuint program, const GLchar* name); +GLint APS5_VABI glGetUniformLocation_nid_postfix(GLuint program, const GLchar* name); +void APS5_VABI glGetActiveAttrib_nid_postfix(GLuint program, GLuint index, GLsizei size, GLsizei* length, GLint* count, GLenum* type, GLchar* name); +void APS5_VABI glGetActiveUniform_nid_postfix(GLuint program, GLuint index, GLsizei size, GLsizei* length, GLint* count, GLenum* type, GLchar* name); +void APS5_VABI glUniform1f_nid_postfix(GLint location, GLfloat value); +void APS5_VABI glUniform1i_nid_postfix(GLint location, GLint value); +void APS5_VABI glUniformMatrix4fv_nid_postfix(GLint location, GLsizei count, GLboolean transpose, const GLfloat* values); +EGLSurface APS5_VABI eglCreateWindowSurface_nid_postfix(EGLDisplay display, EGLConfig config, void* window, const EGLint* attributes); } diff --git a/core/libs/prx/libSceGLSlimVSH/include/InventoryExports.hpp b/core/libs/prx/libSceGLSlimVSH/include/InventoryExports.hpp index 596047ecd8..29b14f05bd 100644 --- a/core/libs/prx/libSceGLSlimVSH/include/InventoryExports.hpp +++ b/core/libs/prx/libSceGLSlimVSH/include/InventoryExports.hpp @@ -21,4 +21,50 @@ int APS5_VABI libSceGLSlimVSHInventory_Yf0LATiXPsg_nid_no_patch(); int APS5_VABI libSceGLSlimVSHInventory_ZLvm5korFRA_nid_no_patch(); int APS5_VABI libSceGLSlimVSHInventory_qSuVNKHENqs_nid_no_patch(); +int APS5_VABI glOrbisMapTextureResourceSCE_nid_postfix(); + +int APS5_VABI glOrbisTexImageCanvas2DSCE_nid_postfix(); + +int APS5_VABI glOrbisTexImageResourceSCE_nid_postfix(); + +int APS5_VABI glOrbisUnmapTextureResourceSCE_nid_postfix(); + +int APS5_VABI scePigletAllocateSystemMemoryEx(); + +int APS5_VABI scePigletAllocateVideoMemoryEx(); + +int APS5_VABI scePigletGetConfigurationVSH(); + +int APS5_VABI scePigletSetConfigurationVSH(); + +int APS5_VABI scePigletReleaseSystemMemoryEx(); + +int APS5_VABI scePigletReleaseVideoMemoryEx(); + +int APS5_VABI sceSlimglClientStartIPC(); + +int APS5_VABI sceSlimglCompositorAllocateIndex(); + +int APS5_VABI sceSlimglCompositorCreateIndirectRenderTarget(); + +int APS5_VABI sceSlimglCompositorDeleteIndirectRenderTarget(); + +int APS5_VABI sceSlimglCompositorFlush(); + +int APS5_VABI sceSlimglCompositorFlushWithRepeat(); + +int APS5_VABI sceSlimglCompositorGetCanvasHandle(); + +int APS5_VABI sceSlimglCompositorGetError(); + +int APS5_VABI sceSlimglCompositorReleaseIndex(); + +int APS5_VABI sceSlimglCompositorSetEndOfFrameCommand(); + +int APS5_VABI sceSlimglCompositorSetIndirectCanvasCommand(); + +int APS5_VABI sceSlimglCompositorSetIndirectCompositionCommand(); + +int APS5_VABI sceSlimglCompositorSetIndirectRenderTargetConfigCommand(); + } diff --git a/core/libs/prx/libSceGLSlimVSH/src/Egl.cpp b/core/libs/prx/libSceGLSlimVSH/src/Egl.cpp new file mode 100644 index 0000000000..1edfd46575 --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/src/Egl.cpp @@ -0,0 +1,549 @@ +#include "State.hpp" + +using namespace Slim; + +namespace { + +const std::map configAttributes{ + {0x3020, 32}, {0x3021, 8}, {0x3022, 8}, {0x3023, 8}, {0x3024, 8}, + {0x3025, 16}, {0x3026, 8}, {0x3027, 0x3051}, {0x3028, 1}, + {0x3029, 0}, {0x302a, 2048}, {0x302b, 4194304}, {0x302c, 2048}, + {0x302d, 0}, {0x302e, 0}, {0x302f, 0}, {0x3031, 0}, {0x3032, 0}, + {0x3033, 1}, {0x3034, 0x3038}, {0x3035, 0}, {0x3036, 0}, {0x3037, 0}, + {0x3039, 0}, {0x303a, 0}, {0x303b, 0}, {0x303c, 0}, + {0x303d, 0}, {0x303e, 0}, {0x303f, 0x308e}, {0x3040, 4}, {0x3042, 0} +}; + +std::shared_ptr SurfaceFor(EGLSurface handle) { + auto it = surfaces.find(handle); + if (it == surfaces.end() || it->second->deleted) { EglError_nid_no_patch(0x300d); return {}; } + return it->second; +} + +std::shared_ptr ContextFor(EGLContext handle) { + auto it = contexts.find(handle); + if (it == contexts.end() || it->second->deleted) { EglError_nid_no_patch(0x3006); return {}; } + return it->second; +} + +void Collect() { + std::erase_if(contexts, [](const auto& item) { return item.second->deleted && item.second.use_count() == 1; }); + std::erase_if(surfaces, [](const auto& item) { return item.second->deleted && item.second.use_count() == 1; }); +} + +} + +extern "C" { + +EGLDisplay APS5_VABI eglGetDisplay_nid_postfix(void* nativeDisplay) { + if (nativeDisplay) return nullptr; + return &displayToken; +} + +EGLint APS5_VABI eglGetError_nid_postfix() { + EGLint result = Thread().error; + Thread().error = 0x3000; + return result; +} + +EGLBoolean APS5_VABI eglInitialize_nid_postfix(EGLDisplay display, EGLint* major, EGLint* minor) { + std::lock_guard lock(mutex); + if (display != &displayToken) return EglError_nid_no_patch(0x3008); + initialized = true; + if (major) *major = 1; + if (minor) *minor = 4; + return 1; +} + +EGLBoolean APS5_VABI eglTerminate_nid_postfix(EGLDisplay display) { + std::lock_guard lock(mutex); + if (display != &displayToken) return EglError_nid_no_patch(0x3008); + initialized = false; + for (auto& item : contexts) item.second->deleted = true; + for (auto& item : surfaces) item.second->deleted = true; + Collect(); + return 1; +} + +EGLBoolean APS5_VABI eglBindAPI_nid_postfix(EGLenum api) { + if (api != 0x30a0) return EglError_nid_no_patch(0x300c); + return 1; +} + +EGLenum APS5_VABI eglQueryAPI_nid_postfix() { return 0x30a0; } + +EGLBoolean APS5_VABI eglGetConfigs_nid_postfix(EGLDisplay display, EGLConfig* configs, EGLint size, EGLint* count) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return 0; + if (!count || (configs && size < 0)) return EglError_nid_no_patch(0x300c); + *count = configs ? std::min(size, 1) : 1; + if (configs && size) configs[0] = &configToken; + return 1; +} + +EGLBoolean APS5_VABI eglGetConfigAttrib_nid_postfix(EGLDisplay display, EGLConfig config, EGLint attribute, EGLint* value) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display) || !Config_nid_no_patch(config)) return 0; + auto it = configAttributes.find(attribute); + if (it == configAttributes.end()) return EglError_nid_no_patch(0x3004); + if (!value) return EglError_nid_no_patch(0x300c); + *value = it->second; + return 1; +} + +EGLBoolean APS5_VABI eglChooseConfig_nid_postfix(EGLDisplay display, const EGLint* attributes, EGLConfig* configs, EGLint size, EGLint* count) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return 0; + if (!count || (configs && size < 0)) return EglError_nid_no_patch(0x300c); + std::map criteria{{0x3033, 4}, {0x3040, 1}, {0x303f, 0x308e}, {0x3034, 0x3038}, {0x3029, 0}}; + if (attributes) { + for (auto p = attributes; *p != 0x3038; p += 2) { + if (!configAttributes.contains(p[0]) && p[0] != 0x3041) return EglError_nid_no_patch(0x3004); + if (p[1] < 0 && p[1] != -1 && p[0] != 0x3029 && p[0] != 0x3041) return EglError_nid_no_patch(0x3004); + if (p[0] == 0x3027 && p[1] != -1 && p[1] != 0x3038 && p[1] != 0x3050 && p[1] != 0x3051) return EglError_nid_no_patch(0x3004); + if (p[0] == 0x3034 && p[1] != -1 && p[1] != 0x3038 && p[1] != 0x3052) return EglError_nid_no_patch(0x3004); + if (p[0] == 0x303f && p[1] != -1 && p[1] != 0x308e && p[1] != 0x308f) return EglError_nid_no_patch(0x3004); + if ((p[0] == 0x3039 || p[0] == 0x303a || p[0] == 0x302d) && p[1] != -1 && p[1] > 1) return EglError_nid_no_patch(0x3004); + criteria[p[0]] = p[1]; + } + } + bool match = true; + if (criteria.contains(0x3028) && criteria[0x3028] != -1) match = criteria[0x3028] == 1; + else for (auto [key, value] : criteria) { + if ((value == -1 && key != 0x3029 && key != 0x3041) || key == 0x302a || key == 0x302b || key == 0x302c || key == 0x302e || key == 0x302f) continue; + if (key == 0x3041) { if (value != 0x3038) match = false; continue; } + EGLint actual = configAttributes.at(key); + if (key == 0x3033 || key == 0x3040 || key == 0x3042) match &= (actual & value) == value; + else if ((key >= 0x3020 && key <= 0x3026) || key == 0x3031 || key == 0x3032 || key == 0x303d || key == 0x303e) match &= actual >= value; + else if (key != 0x3035 && key != 0x3036 && key != 0x3037) match &= actual == value; + } + *count = match && (!configs || size > 0) ? 1 : 0; + if (configs && *count) configs[0] = &configToken; + return 1; +} + +EGLContext APS5_VABI eglCreateContext_nid_postfix(EGLDisplay display, EGLConfig config, EGLContext shared, const EGLint* attributes) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display) || !Config_nid_no_patch(config)) return nullptr; + EGLint version = 1; + if (attributes) for (auto p = attributes; *p != 0x3038; p += 2) { + if (*p != 0x3098) { EglError_nid_no_patch(0x3004); return nullptr; } + version = p[1]; + } + if (version != 2) { EglError_nid_no_patch(0x3005); return nullptr; } + std::shared_ptr other; + if (shared && !(other = ContextFor(shared))) return nullptr; + try { + auto context = std::make_shared(); + if (other) context->share = other->share; + auto handle = context.get(); + contexts.emplace(handle, std::move(context)); + return handle; + } catch (const std::bad_alloc&) { EglError_nid_no_patch(0x3003); return nullptr; } +} + +EGLSurface APS5_VABI eglCreatePbufferSurface_nid_postfix(EGLDisplay display, EGLConfig config, const EGLint* attributes) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display) || !Config_nid_no_patch(config)) return nullptr; + EGLint width = 0, height = 0, largest = 0, textureFormat = 0x305c, textureTarget = 0x305c, mipmap = 0; + EGLint colorspace = 0x3089, alphaFormat = 0x308b; + if (attributes) for (auto p = attributes; *p != 0x3038; p += 2) { + switch (*p) { + case 0x3057: width = p[1]; break; + case 0x3056: height = p[1]; break; + case 0x3058: largest = p[1]; break; + case 0x3080: textureFormat = p[1]; break; + case 0x3081: textureTarget = p[1]; break; + case 0x3082: mipmap = p[1]; break; + case 0x3087: colorspace = p[1]; break; + case 0x3088: alphaFormat = p[1]; break; + default: EglError_nid_no_patch(0x3004); return nullptr; + } + } + if (width < 0 || height < 0) { EglError_nid_no_patch(0x300c); return nullptr; } + if ((colorspace != 0x3089 && colorspace != 0x308a) || (alphaFormat != 0x308b && alphaFormat != 0x308c) || + (largest != 0 && largest != 1) || (mipmap != 0 && mipmap != 1) || + (textureFormat != 0x305c && textureFormat != 0x305d && textureFormat != 0x305e) || + (textureTarget != 0x305c && textureTarget != 0x305f)) { EglError_nid_no_patch(0x3004); return nullptr; } + if (textureFormat != 0x305c || textureTarget != 0x305c) { EglError_nid_no_patch(0x3009); return nullptr; } + if (!largest && (width > 2048 || height > 2048)) { EglError_nid_no_patch(0x3003); return nullptr; } + try { + auto surface = std::make_shared(); + surface->largest = largest; + surface->mipmap = mipmap; + surface->colorspace = colorspace; + surface->alphaFormat = alphaFormat; + surface->image.Allocate(std::min(width, 2048), std::min(height, 2048), 0x1908); + surface->image.depth.resize(surface->image.color.size()); + surface->image.stencil.resize(surface->image.color.size()); + auto handle = surface.get(); + surfaces.emplace(handle, std::move(surface)); + return handle; + } catch (const std::bad_alloc&) { EglError_nid_no_patch(0x3003); return nullptr; } +} + +EGLBoolean APS5_VABI eglDestroyContext_nid_postfix(EGLDisplay display, EGLContext handle) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return 0; + auto context = ContextFor(handle); + if (!context) return 0; + context->deleted = true; + context.reset(); + Collect(); + return 1; +} + +EGLBoolean APS5_VABI eglDestroySurface_nid_postfix(EGLDisplay display, EGLSurface handle) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return 0; + auto surface = SurfaceFor(handle); + if (!surface) return 0; + surface->deleted = true; + surface.reset(); + Collect(); + return 1; +} + +EGLBoolean APS5_VABI eglMakeCurrent_nid_postfix(EGLDisplay display, EGLSurface draw, EGLSurface read, EGLContext handle) { + std::lock_guard lock(mutex); + if (!handle && !draw && !read) { + if (display != &displayToken) return EglError_nid_no_patch(0x3008); + if (Thread().context) Thread().context->bound = false; + Thread().context.reset(); Thread().draw.reset(); Thread().read.reset(); + Collect(); + return 1; + } + if (!Display_nid_no_patch(display)) return 0; + if (!handle || !draw || !read) return EglError_nid_no_patch(0x3009); + auto context = ContextFor(handle); + if (!context) return 0; + if (context != Thread().context && context->bound) return EglError_nid_no_patch(0x3002); + auto drawSurface = SurfaceFor(draw); + if (!drawSurface) return 0; + if (drawSurface != Thread().draw && drawSurface != Thread().read && drawSurface.use_count() > 2) return EglError_nid_no_patch(0x3002); + auto readSurface = SurfaceFor(read); + if (!readSurface) return 0; + if (readSurface != drawSurface && readSurface != Thread().draw && readSurface != Thread().read && readSurface.use_count() > 2) return EglError_nid_no_patch(0x3002); + if (!context->initializedViewport) { + context->state[0x0ba2] = {0, 0, double(drawSurface->image.width), double(drawSurface->image.height)}; + context->state[0x0c10] = context->state[0x0ba2]; + context->initializedViewport = true; + } + if (Thread().context) Thread().context->bound = false; + context->bound = true; + Thread().context = std::move(context); + Thread().draw = std::move(drawSurface); + Thread().read = std::move(readSurface); + Collect(); + return 1; +} + +EGLContext APS5_VABI eglGetCurrentContext_nid_postfix() { return Thread().context.get(); } +EGLDisplay APS5_VABI eglGetCurrentDisplay_nid_postfix() { return Thread().context ? &displayToken : nullptr; } + +EGLSurface APS5_VABI eglGetCurrentSurface_nid_postfix(EGLint which) { + if (which == 0x3059) return Thread().draw.get(); + if (which == 0x305a) return Thread().read.get(); + EglError_nid_no_patch(0x300c); + return nullptr; +} + +EGLBoolean APS5_VABI eglReleaseThread_nid_postfix() { + std::lock_guard lock(mutex); + if (Thread().context) Thread().context->bound = false; + Thread().context.reset(); Thread().draw.reset(); Thread().read.reset(); + Thread().error = 0x3000; + Collect(); + return 1; +} + +EGLBoolean APS5_VABI eglQueryContext_nid_postfix(EGLDisplay display, EGLContext handle, EGLint attribute, EGLint* value) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return 0; + auto context = ContextFor(handle); + if (!context) return 0; + if (!value) return EglError_nid_no_patch(0x300c); + switch (attribute) { + case 0x3028: *value = 1; break; + case 0x3097: *value = 0x30a0; break; + case 0x3098: *value = 2; break; + case 0x3086: *value = context->bound ? 0x3084 : 0x3038; break; + default: return EglError_nid_no_patch(0x3004); + } + return 1; +} + +EGLBoolean APS5_VABI eglQuerySurface_nid_postfix(EGLDisplay display, EGLSurface handle, EGLint attribute, EGLint* value) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return 0; + auto surface = SurfaceFor(handle); + if (!surface) return 0; + if (!value) return EglError_nid_no_patch(0x300c); + switch (attribute) { + case 0x3028: *value = 1; break; + case 0x3057: *value = surface->image.width; break; + case 0x3056: *value = surface->image.height; break; + case 0x3058: *value = surface->largest; break; + case 0x3080: case 0x3081: *value = 0x305c; break; + case 0x3082: *value = surface->mipmap; break; + case 0x3083: *value = surface->mipmapLevel; break; + case 0x3086: *value = 0x3084; break; + case 0x3087: *value = surface->colorspace; break; + case 0x3088: *value = surface->alphaFormat; break; + case 0x3090: case 0x3091: case 0x3092: *value = -1; break; + case 0x3093: *value = 0x3094; break; + case 0x3099: *value = 0x309a; break; + default: return EglError_nid_no_patch(0x3004); + } + return 1; +} + +EGLBoolean APS5_VABI eglSurfaceAttrib_nid_postfix(EGLDisplay display, EGLSurface handle, EGLint attribute, EGLint value) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return 0; + auto surface = SurfaceFor(handle); + if (!surface) return 0; + switch (attribute) { + case 0x3083: surface->mipmapLevel = value; return 1; + case 0x3099: + if (value == 0x309a) return 1; + if (value == 0x309b) return EglError_nid_no_patch(0x3009); + return EglError_nid_no_patch(0x3004); + case 0x3093: + if (value == 0x3094) return 1; + if (value == 0x3095) return EglError_nid_no_patch(0x3009); + return EglError_nid_no_patch(0x3004); + default: return EglError_nid_no_patch(0x3004); + } +} + +EGLBoolean APS5_VABI eglSwapBuffers_nid_postfix(EGLDisplay display, EGLSurface surface) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return 0; + auto object = SurfaceFor(surface); + if (!object) return 0; + if (object != Thread().draw) return EglError_nid_no_patch(0x300d); + return 1; +} + +EGLBoolean APS5_VABI eglSwapInterval_nid_postfix(EGLDisplay display, EGLint interval) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return 0; + if (!Thread().context) return EglError_nid_no_patch(0x3006); + if (!Thread().draw) return EglError_nid_no_patch(0x300d); + return 1; +} + +EGLBoolean APS5_VABI eglWaitClient_nid_postfix() { return 1; } +EGLBoolean APS5_VABI eglWaitGL_nid_postfix() { return 1; } +EGLBoolean APS5_VABI eglWaitNative_nid_postfix(EGLint engine) { + if (engine != 0x305b) return EglError_nid_no_patch(0x300c); + return 1; +} + +EGLBoolean APS5_VABI eglBindTexImage_nid_postfix(EGLDisplay display, EGLSurface handle, EGLint buffer) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display) || !SurfaceFor(handle)) return 0; + if (buffer != 0x3084) return EglError_nid_no_patch(0x300c); + return EglError_nid_no_patch(0x3009); +} + +EGLBoolean APS5_VABI eglReleaseTexImage_nid_postfix(EGLDisplay display, EGLSurface handle, EGLint buffer) { + return eglBindTexImage_nid_postfix(display, handle, buffer); +} + +const char* APS5_VABI eglQueryString_nid_postfix(EGLDisplay display, EGLint name) { + std::lock_guard lock(mutex); + if (!Display_nid_no_patch(display)) return nullptr; + switch (name) { + case 0x3053: return "AnyPS5"; + case 0x3054: return "1.4 AnyPS5"; + case 0x3055: return ""; + case 0x308d: return "OpenGL_ES"; + default: EglError_nid_no_patch(0x300c); return nullptr; + } +} + + +__eglMustCastToProperFunctionPointerType APS5_VABI eglGetProcAddress_nid_postfix(const char* name) { + if (!name) return nullptr; + static const std::pair functions[]{ + {"eglBindAPI", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglBindAPI_nid_postfix)}, + {"eglBindTexImage", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglBindTexImage_nid_postfix)}, + {"eglChooseConfig", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglChooseConfig_nid_postfix)}, + {"eglCreateContext", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglCreateContext_nid_postfix)}, + {"eglCreatePbufferFromClientBuffer", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglCreatePbufferFromClientBuffer_nid_postfix)}, + {"eglCreatePbufferSurface", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglCreatePbufferSurface_nid_postfix)}, + {"eglCreateWindowSurface", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglCreateWindowSurface_nid_postfix)}, + {"eglDestroyContext", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglDestroyContext_nid_postfix)}, + {"eglDestroySurface", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglDestroySurface_nid_postfix)}, + {"eglGetConfigAttrib", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglGetConfigAttrib_nid_postfix)}, + {"eglGetConfigs", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglGetConfigs_nid_postfix)}, + {"eglGetCurrentContext", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglGetCurrentContext_nid_postfix)}, + {"eglGetCurrentDisplay", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglGetCurrentDisplay_nid_postfix)}, + {"eglGetCurrentSurface", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglGetCurrentSurface_nid_postfix)}, + {"eglGetDisplay", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglGetDisplay_nid_postfix)}, + {"eglGetError", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglGetError_nid_postfix)}, + {"eglGetProcAddress", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglGetProcAddress_nid_postfix)}, + {"eglInitialize", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglInitialize_nid_postfix)}, + {"eglMakeCurrent", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglMakeCurrent_nid_postfix)}, + {"eglQueryAPI", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglQueryAPI_nid_postfix)}, + {"eglQueryContext", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglQueryContext_nid_postfix)}, + {"eglQueryString", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglQueryString_nid_postfix)}, + {"eglQuerySurface", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglQuerySurface_nid_postfix)}, + {"eglReleaseTexImage", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglReleaseTexImage_nid_postfix)}, + {"eglReleaseThread", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglReleaseThread_nid_postfix)}, + {"eglSurfaceAttrib", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglSurfaceAttrib_nid_postfix)}, + {"eglSwapBuffers", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglSwapBuffers_nid_postfix)}, + {"eglSwapInterval", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglSwapInterval_nid_postfix)}, + {"eglTerminate", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglTerminate_nid_postfix)}, + {"eglWaitClient", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglWaitClient_nid_postfix)}, + {"eglWaitGL", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglWaitGL_nid_postfix)}, + {"eglWaitNative", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&eglWaitNative_nid_postfix)}, + {"glActiveTexture", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glActiveTexture_nid_postfix)}, + {"glAttachShader", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glAttachShader_nid_postfix)}, + {"glBindAttribLocation", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBindAttribLocation_nid_postfix)}, + {"glBindBuffer", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBindBuffer_nid_postfix)}, + {"glBindFramebuffer", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBindFramebuffer_nid_postfix)}, + {"glBindRenderbuffer", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBindRenderbuffer_nid_postfix)}, + {"glBindTexture", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBindTexture_nid_postfix)}, + {"glBlendColor", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBlendColor_nid_postfix)}, + {"glBlendEquation", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBlendEquation_nid_postfix)}, + {"glBlendEquationSeparate", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBlendEquationSeparate_nid_postfix)}, + {"glBlendFunc", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBlendFunc_nid_postfix)}, + {"glBlendFuncSeparate", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBlendFuncSeparate_nid_postfix)}, + {"glBufferData", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBufferData_nid_postfix)}, + {"glBufferSubData", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glBufferSubData_nid_postfix)}, + {"glCheckFramebufferStatus", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glCheckFramebufferStatus_nid_postfix)}, + {"glClear", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glClear_nid_postfix)}, + {"glClearColor", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glClearColor_nid_postfix)}, + {"glClearDepthf", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glClearDepthf_nid_postfix)}, + {"glClearStencil", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glClearStencil_nid_postfix)}, + {"glColorMask", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glColorMask_nid_postfix)}, + {"glCompileShader", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glCompileShader_nid_postfix)}, + {"glCompressedTexImage2D", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glCompressedTexImage2D_nid_postfix)}, + {"glCompressedTexSubImage2D", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glCompressedTexSubImage2D_nid_postfix)}, + {"glCopyTexImage2D", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glCopyTexImage2D_nid_postfix)}, + {"glCopyTexSubImage2D", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glCopyTexSubImage2D_nid_postfix)}, + {"glCreateProgram", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glCreateProgram_nid_postfix)}, + {"glCreateShader", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glCreateShader_nid_postfix)}, + {"glCullFace", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glCullFace_nid_postfix)}, + {"glDeleteBuffers", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDeleteBuffers_nid_postfix)}, + {"glDeleteFramebuffers", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDeleteFramebuffers_nid_postfix)}, + {"glDeleteProgram", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDeleteProgram_nid_postfix)}, + {"glDeleteRenderbuffers", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDeleteRenderbuffers_nid_postfix)}, + {"glDeleteShader", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDeleteShader_nid_postfix)}, + {"glDeleteTextures", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDeleteTextures_nid_postfix)}, + {"glDepthFunc", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDepthFunc_nid_postfix)}, + {"glDepthMask", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDepthMask_nid_postfix)}, + {"glDepthRangef", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDepthRangef_nid_postfix)}, + {"glDetachShader", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDetachShader_nid_postfix)}, + {"glDisable", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDisable_nid_postfix)}, + {"glDisableVertexAttribArray", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDisableVertexAttribArray_nid_postfix)}, + {"glDrawArrays", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDrawArrays_nid_postfix)}, + {"glDrawElements", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glDrawElements_nid_postfix)}, + {"glEnable", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glEnable_nid_postfix)}, + {"glEnableVertexAttribArray", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glEnableVertexAttribArray_nid_postfix)}, + {"glFinish", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glFinish_nid_postfix)}, + {"glFlush", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glFlush_nid_postfix)}, + {"glFramebufferRenderbuffer", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glFramebufferRenderbuffer_nid_postfix)}, + {"glFramebufferTexture2D", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glFramebufferTexture2D_nid_postfix)}, + {"glFrontFace", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glFrontFace_nid_postfix)}, + {"glGenBuffers", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGenBuffers_nid_postfix)}, + {"glGenFramebuffers", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGenFramebuffers_nid_postfix)}, + {"glGenRenderbuffers", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGenRenderbuffers_nid_postfix)}, + {"glGenTextures", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGenTextures_nid_postfix)}, + {"glGenerateMipmap", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGenerateMipmap_nid_postfix)}, + {"glGetActiveAttrib", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetActiveAttrib_nid_postfix)}, + {"glGetActiveUniform", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetActiveUniform_nid_postfix)}, + {"glGetAttachedShaders", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetAttachedShaders_nid_postfix)}, + {"glGetAttribLocation", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetAttribLocation_nid_postfix)}, + {"glGetBooleanv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetBooleanv_nid_postfix)}, + {"glGetBufferParameteriv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetBufferParameteriv_nid_postfix)}, + {"glGetError", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetError_nid_postfix)}, + {"glGetFloatv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetFloatv_nid_postfix)}, + {"glGetFramebufferAttachmentParameteriv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetFramebufferAttachmentParameteriv_nid_postfix)}, + {"glGetIntegerv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetIntegerv_nid_postfix)}, + {"glGetProgramInfoLog", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetProgramInfoLog_nid_postfix)}, + {"glGetProgramiv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetProgramiv_nid_postfix)}, + {"glGetRenderbufferParameteriv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetRenderbufferParameteriv_nid_postfix)}, + {"glGetShaderInfoLog", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetShaderInfoLog_nid_postfix)}, + {"glGetShaderPrecisionFormat", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetShaderPrecisionFormat_nid_postfix)}, + {"glGetShaderiv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetShaderiv_nid_postfix)}, + {"glGetString", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetString_nid_postfix)}, + {"glGetTexParameterfv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetTexParameterfv_nid_postfix)}, + {"glGetTexParameteriv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetTexParameteriv_nid_postfix)}, + {"glGetUniformLocation", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetUniformLocation_nid_postfix)}, + {"glGetUniformfv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetUniformfv_nid_postfix)}, + {"glGetUniformiv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetUniformiv_nid_postfix)}, + {"glGetVertexAttribPointerv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetVertexAttribPointerv_nid_postfix)}, + {"glGetVertexAttribfv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetVertexAttribfv_nid_postfix)}, + {"glGetVertexAttribiv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glGetVertexAttribiv_nid_postfix)}, + {"glHint", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glHint_nid_postfix)}, + {"glIsBuffer", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glIsBuffer_nid_postfix)}, + {"glIsEnabled", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glIsEnabled_nid_postfix)}, + {"glIsFramebuffer", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glIsFramebuffer_nid_postfix)}, + {"glIsProgram", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glIsProgram_nid_postfix)}, + {"glIsRenderbuffer", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glIsRenderbuffer_nid_postfix)}, + {"glIsShader", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glIsShader_nid_postfix)}, + {"glIsTexture", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glIsTexture_nid_postfix)}, + {"glLineWidth", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glLineWidth_nid_postfix)}, + {"glLinkProgram", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glLinkProgram_nid_postfix)}, + {"glPixelStorei", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glPixelStorei_nid_postfix)}, + {"glPolygonOffset", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glPolygonOffset_nid_postfix)}, + {"glReadPixels", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glReadPixels_nid_postfix)}, + {"glReleaseShaderCompiler", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glReleaseShaderCompiler_nid_postfix)}, + {"glRenderbufferStorage", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glRenderbufferStorage_nid_postfix)}, + {"glSampleCoverage", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glSampleCoverage_nid_postfix)}, + {"glScissor", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glScissor_nid_postfix)}, + {"glShaderBinary", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glShaderBinary_nid_postfix)}, + {"glShaderSource", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glShaderSource_nid_postfix)}, + {"glStencilFunc", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glStencilFunc_nid_postfix)}, + {"glStencilFuncSeparate", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glStencilFuncSeparate_nid_postfix)}, + {"glStencilMask", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glStencilMask_nid_postfix)}, + {"glStencilMaskSeparate", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glStencilMaskSeparate_nid_postfix)}, + {"glStencilOp", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glStencilOp_nid_postfix)}, + {"glStencilOpSeparate", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glStencilOpSeparate_nid_postfix)}, + {"glTexImage2D", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glTexImage2D_nid_postfix)}, + {"glTexParameterf", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glTexParameterf_nid_postfix)}, + {"glTexParameterfv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glTexParameterfv_nid_postfix)}, + {"glTexParameteri", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glTexParameteri_nid_postfix)}, + {"glTexParameteriv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glTexParameteriv_nid_postfix)}, + {"glTexSubImage2D", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glTexSubImage2D_nid_postfix)}, + {"glUniform1f", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform1f_nid_postfix)}, + {"glUniform1fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform1fv_nid_postfix)}, + {"glUniform1i", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform1i_nid_postfix)}, + {"glUniform1iv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform1iv_nid_postfix)}, + {"glUniform2f", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform2f_nid_postfix)}, + {"glUniform2fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform2fv_nid_postfix)}, + {"glUniform2i", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform2i_nid_postfix)}, + {"glUniform2iv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform2iv_nid_postfix)}, + {"glUniform3f", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform3f_nid_postfix)}, + {"glUniform3fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform3fv_nid_postfix)}, + {"glUniform3i", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform3i_nid_postfix)}, + {"glUniform3iv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform3iv_nid_postfix)}, + {"glUniform4f", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform4f_nid_postfix)}, + {"glUniform4fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform4fv_nid_postfix)}, + {"glUniform4i", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform4i_nid_postfix)}, + {"glUniform4iv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniform4iv_nid_postfix)}, + {"glUniformMatrix2fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniformMatrix2fv_nid_postfix)}, + {"glUniformMatrix3fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniformMatrix3fv_nid_postfix)}, + {"glUniformMatrix4fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUniformMatrix4fv_nid_postfix)}, + {"glUseProgram", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glUseProgram_nid_postfix)}, + {"glValidateProgram", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glValidateProgram_nid_postfix)}, + {"glVertexAttrib1f", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glVertexAttrib1f_nid_postfix)}, + {"glVertexAttrib1fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glVertexAttrib1fv_nid_postfix)}, + {"glVertexAttrib2f", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glVertexAttrib2f_nid_postfix)}, + {"glVertexAttrib2fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glVertexAttrib2fv_nid_postfix)}, + {"glVertexAttrib3f", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glVertexAttrib3f_nid_postfix)}, + {"glVertexAttrib3fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glVertexAttrib3fv_nid_postfix)}, + {"glVertexAttrib4f", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glVertexAttrib4f_nid_postfix)}, + {"glVertexAttrib4fv", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glVertexAttrib4fv_nid_postfix)}, + {"glVertexAttribPointer", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glVertexAttribPointer_nid_postfix)}, + {"glViewport", reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&glViewport_nid_postfix)}, + }; + for (auto& entry : functions) if (std::strcmp(entry.first, name) == 0) return entry.second; + return nullptr; +} + +} diff --git a/core/libs/prx/libSceGLSlimVSH/src/GlState.cpp b/core/libs/prx/libSceGLSlimVSH/src/GlState.cpp new file mode 100644 index 0000000000..333f9cc5c5 --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/src/GlState.cpp @@ -0,0 +1,327 @@ +#include "State.hpp" +#include + +using namespace Slim; + +namespace { + +bool Compare(GLenum value) { return value >= 0x0200 && value <= 0x0207; } +bool Face(GLenum value) { return value == 0x0404 || value == 0x0405 || value == 0x0408; } +bool Equation(GLenum value) { return value == 0x8006 || value == 0x800a || value == 0x800b; } +bool Factor(GLenum value) { + return value <= 1 || (value >= 0x0300 && value <= 0x0308) || (value >= 0x8001 && value <= 0x8004); +} +bool Operation(GLenum value) { + return value == 0 || value == 0x1e00 || value == 0x1e01 || value == 0x1e02 || value == 0x1e03 || value == 0x150a || value == 0x8507 || value == 0x8508; +} + +GLint Integer(double value) { + if (std::isnan(value)) return 0; + return static_cast(std::clamp(std::round(value), double(std::numeric_limits::min()), double(std::numeric_limits::max()))); +} + +Attribute* Attrib(GLuint index) { + auto& c = Current_nid_no_patch(); + if (index >= c.attributes.size()) { Error_nid_no_patch(0x0501); return nullptr; } + return &c.attributes[index]; +} + +bool AttribQuery(GLuint index, GLenum name, std::vector& result) { + auto* a = Attrib(index); + if (!a) return false; + switch (name) { + case 0x8626: result.assign(a->value.begin(), a->value.end()); break; + case 0x8622: result = {double(a->enabled)}; break; + case 0x8623: result = {double(a->size)}; break; + case 0x8624: result = {double(a->stride)}; break; + case 0x8625: result = {double(a->type)}; break; + case 0x886a: result = {double(a->normalized)}; break; + case 0x889f: result = {double(a->buffer ? a->buffer->name : 0)}; break; + default: Error_nid_no_patch(0x0500); return false; + } + return true; +} + +} + +extern "C" { + +GLenum APS5_VABI glGetError_nid_postfix() { + std::lock_guard lock(mutex); + auto& c = Current_nid_no_patch(); + auto error = c.error; + c.error = 0; + return error; +} + +void APS5_VABI glGetIntegerv_nid_postfix(GLenum name, GLint* values) { + std::lock_guard lock(mutex); + std::vector data; + if (!Query_nid_no_patch(name, data)) return; + for (std::size_t i = 0; i < data.size(); ++i) { + if (name == 0x0b93 || name == 0x0b98 || name == 0x8ca4 || name == 0x8ca5) values[i] = static_cast(static_cast(data[i])); + else if (name == 0x0b70 || name == 0x0b73 || name == 0x0c22 || name == 0x8005) values[i] = Integer(std::floor((data[i] * 4294967295.0 - 1.0) / 2.0 + 0.5)); + else values[i] = Integer(data[i]); + } +} + +void APS5_VABI glGetFloatv_nid_postfix(GLenum name, GLfloat* values) { + std::lock_guard lock(mutex); + std::vector data; + if (Query_nid_no_patch(name, data)) std::copy(data.begin(), data.end(), values); +} + +void APS5_VABI glGetBooleanv_nid_postfix(GLenum name, GLboolean* values) { + std::lock_guard lock(mutex); + std::vector data; + if (Query_nid_no_patch(name, data)) for (std::size_t i = 0; i < data.size(); ++i) values[i] = data[i] != 0; +} + +void APS5_VABI glEnable_nid_postfix(GLenum cap) { + std::lock_guard lock(mutex); + if (!Capability_nid_no_patch(cap)) { Error_nid_no_patch(0x0500); return; } + Current_nid_no_patch().enabled.insert(cap); +} + +void APS5_VABI glDisable_nid_postfix(GLenum cap) { + std::lock_guard lock(mutex); + if (!Capability_nid_no_patch(cap)) { Error_nid_no_patch(0x0500); return; } + Current_nid_no_patch().enabled.erase(cap); +} + +GLboolean APS5_VABI glIsEnabled_nid_postfix(GLenum cap) { + std::lock_guard lock(mutex); + if (!Capability_nid_no_patch(cap)) { Error_nid_no_patch(0x0500); return 0; } + return Current_nid_no_patch().enabled.contains(cap); +} + +void APS5_VABI glBlendEquationSeparate_nid_postfix(GLenum rgb, GLenum alpha) { + std::lock_guard lock(mutex); + if (!Equation(rgb) || !Equation(alpha)) { Error_nid_no_patch(0x0500); return; } + auto& s = Current_nid_no_patch().state; + s[0x8009] = {double(rgb)}; s[0x883d] = {double(alpha)}; +} + +void APS5_VABI glBlendEquation_nid_postfix(GLenum mode) { glBlendEquationSeparate_nid_postfix(mode, mode); } + +void APS5_VABI glBlendFuncSeparate_nid_postfix(GLenum srcRgb, GLenum dstRgb, GLenum srcAlpha, GLenum dstAlpha) { + std::lock_guard lock(mutex); + if (!Factor(srcRgb) || !Factor(dstRgb) || !Factor(srcAlpha) || !Factor(dstAlpha) || dstRgb == 0x0308 || dstAlpha == 0x0308) { Error_nid_no_patch(0x0500); return; } + if (((srcRgb == 0x8001 || srcRgb == 0x8002) && (dstRgb == 0x8003 || dstRgb == 0x8004)) || + ((dstRgb == 0x8001 || dstRgb == 0x8002) && (srcRgb == 0x8003 || srcRgb == 0x8004))) { Error_nid_no_patch(0x0502); return; } + auto& s = Current_nid_no_patch().state; + s[0x80c9] = {double(srcRgb)}; s[0x80c8] = {double(dstRgb)}; + s[0x80cb] = {double(srcAlpha)}; s[0x80ca] = {double(dstAlpha)}; +} + +void APS5_VABI glBlendFunc_nid_postfix(GLenum source, GLenum destination) { + glBlendFuncSeparate_nid_postfix(source, destination, source, destination); +} + +void APS5_VABI glStencilFuncSeparate_nid_postfix(GLenum face, GLenum func, GLint reference, GLuint mask) { + std::lock_guard lock(mutex); + if (!Face(face) || !Compare(func)) { Error_nid_no_patch(0x0500); return; } + auto& s = Current_nid_no_patch().state; + if (face != 0x0405) { s[0x0b92] = {double(func)}; s[0x0b97] = {double(std::max(reference, 0))}; s[0x0b93] = {double(mask)}; } + if (face != 0x0404) { s[0x8800] = {double(func)}; s[0x8ca3] = {double(std::max(reference, 0))}; s[0x8ca4] = {double(mask)}; } +} + +void APS5_VABI glStencilFunc_nid_postfix(GLenum func, GLint reference, GLuint mask) { glStencilFuncSeparate_nid_postfix(0x0408, func, reference, mask); } + +void APS5_VABI glStencilMaskSeparate_nid_postfix(GLenum face, GLuint mask) { + std::lock_guard lock(mutex); + if (!Face(face)) { Error_nid_no_patch(0x0500); return; } + auto& s = Current_nid_no_patch().state; + if (face != 0x0405) s[0x0b98] = {double(mask)}; + if (face != 0x0404) s[0x8ca5] = {double(mask)}; +} + +void APS5_VABI glStencilMask_nid_postfix(GLuint mask) { glStencilMaskSeparate_nid_postfix(0x0408, mask); } + +void APS5_VABI glStencilOpSeparate_nid_postfix(GLenum face, GLenum fail, GLenum depthFail, GLenum pass) { + std::lock_guard lock(mutex); + if (!Face(face) || !Operation(fail) || !Operation(depthFail) || !Operation(pass)) { Error_nid_no_patch(0x0500); return; } + auto& s = Current_nid_no_patch().state; + if (face != 0x0405) { s[0x0b94] = {double(fail)}; s[0x0b95] = {double(depthFail)}; s[0x0b96] = {double(pass)}; } + if (face != 0x0404) { s[0x8801] = {double(fail)}; s[0x8802] = {double(depthFail)}; s[0x8803] = {double(pass)}; } +} + +void APS5_VABI glStencilOp_nid_postfix(GLenum fail, GLenum depthFail, GLenum pass) { glStencilOpSeparate_nid_postfix(0x0408, fail, depthFail, pass); } + +void APS5_VABI glPixelStorei_nid_postfix(GLenum name, GLint value) { + std::lock_guard lock(mutex); + if (name != 0x0d05 && name != 0x0cf5) { Error_nid_no_patch(0x0500); return; } + if (value != 1 && value != 2 && value != 4 && value != 8) { Error_nid_no_patch(0x0501); return; } + Current_nid_no_patch().state[name] = {double(value)}; +} + +void APS5_VABI glHint_nid_postfix(GLenum target, GLenum mode) { + std::lock_guard lock(mutex); + if (target != 0x8192 || mode < 0x1100 || mode > 0x1102) { Error_nid_no_patch(0x0500); return; } + Current_nid_no_patch().state[target] = {double(mode)}; +} + +void APS5_VABI glVertexAttribPointer_nid_postfix(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer) { + std::lock_guard lock(mutex); + auto* a = Attrib(index); + if (!a) return; + if (size < 1 || size > 4 || stride < 0) { Error_nid_no_patch(0x0501); return; } + if (type != 0x1400 && type != 0x1401 && type != 0x1402 && type != 0x1403 && type != 0x1406 && type != 0x140c) { Error_nid_no_patch(0x0500); return; } + a->size = size; a->type = type; a->normalized = normalized != 0; a->stride = stride; a->pointer = pointer; + a->buffer = Current_nid_no_patch().arrayBuffer; +} + +void APS5_VABI glEnableVertexAttribArray_nid_postfix(GLuint index) { + std::lock_guard lock(mutex); + if (auto* a = Attrib(index)) a->enabled = true; +} + +void APS5_VABI glDisableVertexAttribArray_nid_postfix(GLuint index) { + std::lock_guard lock(mutex); + if (auto* a = Attrib(index)) a->enabled = false; +} + +void APS5_VABI glGetVertexAttribPointerv_nid_postfix(GLuint index, GLenum name, void** pointer) { + std::lock_guard lock(mutex); + auto* a = Attrib(index); + if (!a) return; + if (name != 0x8645) { Error_nid_no_patch(0x0500); return; } + *pointer = const_cast(a->pointer); +} + +void APS5_VABI glGetVertexAttribfv_nid_postfix(GLuint index, GLenum name, GLfloat* values) { + std::lock_guard lock(mutex); + std::vector data; + if (AttribQuery(index, name, data)) std::copy(data.begin(), data.end(), values); +} + +void APS5_VABI glGetVertexAttribiv_nid_postfix(GLuint index, GLenum name, GLint* values) { + std::lock_guard lock(mutex); + std::vector data; + if (AttribQuery(index, name, data)) for (std::size_t i = 0; i < data.size(); ++i) values[i] = Integer(data[i]); +} + +void APS5_VABI glFinish_nid_postfix() { std::lock_guard lock(mutex); Current_nid_no_patch(); } +void APS5_VABI glFlush_nid_postfix() { std::lock_guard lock(mutex); Current_nid_no_patch(); } + + +void APS5_VABI glClearColor_nid_postfix(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { + std::lock_guard lock(mutex); + Current_nid_no_patch().state[0x0c22] = {Clamp_nid_no_patch(red), Clamp_nid_no_patch(green), Clamp_nid_no_patch(blue), Clamp_nid_no_patch(alpha)}; +} + +void APS5_VABI glBlendColor_nid_postfix(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) { + std::lock_guard lock(mutex); + Current_nid_no_patch().state[0x8005] = {Clamp_nid_no_patch(red), Clamp_nid_no_patch(green), Clamp_nid_no_patch(blue), Clamp_nid_no_patch(alpha)}; +} + +void APS5_VABI glClearDepthf_nid_postfix(GLfloat depth) { + std::lock_guard lock(mutex); + Current_nid_no_patch().state[0x0b73] = {Clamp_nid_no_patch(depth)}; +} + +void APS5_VABI glClearStencil_nid_postfix(GLint value) { + std::lock_guard lock(mutex); + Current_nid_no_patch().state[0x0b91] = {double(value)}; +} + +void APS5_VABI glDepthMask_nid_postfix(GLboolean value) { + std::lock_guard lock(mutex); + Current_nid_no_patch().state[0x0b72] = {double(value != 0)}; +} + +void APS5_VABI glDepthRangef_nid_postfix(GLfloat nearValue, GLfloat farValue) { + std::lock_guard lock(mutex); + Current_nid_no_patch().state[0x0b70] = {Clamp_nid_no_patch(nearValue), Clamp_nid_no_patch(farValue)}; +} + +void APS5_VABI glColorMask_nid_postfix(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) { + std::lock_guard lock(mutex); + Current_nid_no_patch().state[0x0c23] = {double(red != 0), double(green != 0), double(blue != 0), double(alpha != 0)}; +} + +void APS5_VABI glDepthFunc_nid_postfix(GLenum value) { + std::lock_guard lock(mutex); + if (!Compare(value)) { Error_nid_no_patch(0x0500); return; } + Current_nid_no_patch().state[0x0b74] = {double(value)}; +} + +void APS5_VABI glCullFace_nid_postfix(GLenum value) { + std::lock_guard lock(mutex); + if (!Face(value)) { Error_nid_no_patch(0x0500); return; } + Current_nid_no_patch().state[0x0b45] = {double(value)}; +} + +void APS5_VABI glFrontFace_nid_postfix(GLenum value) { + std::lock_guard lock(mutex); + if (value != 0x0900 && value != 0x0901) { Error_nid_no_patch(0x0500); return; } + Current_nid_no_patch().state[0x0b46] = {double(value)}; +} + +void APS5_VABI glLineWidth_nid_postfix(GLfloat value) { + std::lock_guard lock(mutex); + if (value <= 0) { Error_nid_no_patch(0x0501); return; } + Current_nid_no_patch().state[0x0b21] = {value}; +} + +void APS5_VABI glScissor_nid_postfix(GLint x, GLint y, GLsizei width, GLsizei height) { + std::lock_guard lock(mutex); + if (width < 0 || height < 0) { Error_nid_no_patch(0x0501); return; } + Current_nid_no_patch().state[0x0c10] = {double(x), double(y), double(width), double(height)}; +} + +void APS5_VABI glViewport_nid_postfix(GLint x, GLint y, GLsizei width, GLsizei height) { + std::lock_guard lock(mutex); + if (width < 0 || height < 0) { Error_nid_no_patch(0x0501); return; } + Current_nid_no_patch().state[0x0ba2] = {double(x), double(y), double(std::min(width, 2048)), double(std::min(height, 2048))}; +} + +void APS5_VABI glPolygonOffset_nid_postfix(GLfloat factor, GLfloat units) { + std::lock_guard lock(mutex); + auto& state = Current_nid_no_patch().state; + state[0x8038] = {factor}; state[0x2a00] = {units}; +} + +void APS5_VABI glSampleCoverage_nid_postfix(GLfloat value, GLboolean invert) { + std::lock_guard lock(mutex); + auto& state = Current_nid_no_patch().state; + state[0x80aa] = {Clamp_nid_no_patch(value)}; state[0x80ab] = {double(invert != 0)}; +} + +void APS5_VABI glVertexAttrib1f_nid_postfix(GLuint index, GLfloat x) { + std::lock_guard lock(mutex); + if (auto* a = Attrib(index)) a->value = {x, 0, 0, 1}; +} + +void APS5_VABI glVertexAttrib1fv_nid_postfix(GLuint index, const GLfloat* values) { + glVertexAttrib1f_nid_postfix(index, values[0]); +} + +void APS5_VABI glVertexAttrib2f_nid_postfix(GLuint index, GLfloat x, GLfloat y) { + std::lock_guard lock(mutex); + if (auto* a = Attrib(index)) a->value = {x, y, 0, 1}; +} + +void APS5_VABI glVertexAttrib2fv_nid_postfix(GLuint index, const GLfloat* values) { + glVertexAttrib2f_nid_postfix(index, values[0], values[1]); +} + +void APS5_VABI glVertexAttrib3f_nid_postfix(GLuint index, GLfloat x, GLfloat y, GLfloat z) { + std::lock_guard lock(mutex); + if (auto* a = Attrib(index)) a->value = {x, y, z, 1}; +} + +void APS5_VABI glVertexAttrib3fv_nid_postfix(GLuint index, const GLfloat* values) { + glVertexAttrib3f_nid_postfix(index, values[0], values[1], values[2]); +} + +void APS5_VABI glVertexAttrib4f_nid_postfix(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w) { + std::lock_guard lock(mutex); + if (auto* a = Attrib(index)) a->value = {x, y, z, w}; +} + +void APS5_VABI glVertexAttrib4fv_nid_postfix(GLuint index, const GLfloat* values) { + glVertexAttrib4f_nid_postfix(index, values[0], values[1], values[2], values[3]); +} + +} diff --git a/core/libs/prx/libSceGLSlimVSH/src/Images.cpp b/core/libs/prx/libSceGLSlimVSH/src/Images.cpp new file mode 100644 index 0000000000..c2fc165e9f --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/src/Images.cpp @@ -0,0 +1,573 @@ +#include "State.hpp" + +using namespace Slim; + +namespace { + +int Target(GLenum target) { + if (target == 0x0de1) return 0; + if (target == 0x8513) return 1; + Error_nid_no_patch(0x0500); + return -1; +} + +int ImageTarget(GLenum target) { + if (target == 0x0de1) return 0; + if (target >= 0x8515 && target <= 0x851a) return 1; + Error_nid_no_patch(0x0500); + return -1; +} + +std::shared_ptr BoundTexture(int target) { + auto& c = Current_nid_no_patch(); + auto texture = c.textures[c.activeTexture][target]; + return texture ? texture : c.defaultTextures[target]; +} + +bool Dimensions(int target, GLint level, GLsizei width, GLsizei height, GLint border) { + if (level < 0 || level > 11 || width < 0 || height < 0 || border != 0 || + width > (2048 >> level) || height > (2048 >> level) || (target == 1 && width != height) || + (level > 0 && ((width & (width - 1)) || (height & (height - 1))))) { + Error_nid_no_patch(0x0501); return false; + } + return true; +} + +bool Format(GLenum format) { return format == 0x1906 || format == 0x1907 || format == 0x1908 || format == 0x1909 || format == 0x190a; } + +int PixelSize(GLenum format, GLenum type) { + if (!Format(format) || (type != 0x1401 && type != 0x8033 && type != 0x8034 && type != 0x8363)) { Error_nid_no_patch(0x0500); return 0; } + if (type != 0x1401) { + if ((type == 0x8363 && format != 0x1907) || (type != 0x8363 && format != 0x1908)) { Error_nid_no_patch(0x0502); return 0; } + return 2; + } + return format == 0x1908 ? 4 : format == 0x1907 ? 3 : format == 0x190a ? 2 : 1; +} + +unsigned char Expand(unsigned value, unsigned maximum) { return static_cast((value * 255 + maximum / 2) / maximum); } + +std::array Decode(const unsigned char* data, GLenum format, GLenum type) { + if (type != 0x1401) { + std::uint16_t value; + std::memcpy(&value, data, 2); + if (type == 0x8363) return {Expand(value >> 11, 31), Expand((value >> 5) & 63, 63), Expand(value & 31, 31), 255}; + if (type == 0x8033) return {Expand(value >> 12, 15), Expand((value >> 8) & 15, 15), Expand((value >> 4) & 15, 15), Expand(value & 15, 15)}; + return {Expand(value >> 11, 31), Expand((value >> 6) & 31, 31), Expand((value >> 1) & 31, 31), static_cast((value & 1) * 255)}; + } + switch (format) { + case 0x1906: return {0, 0, 0, data[0]}; + case 0x1907: return {data[0], data[1], data[2], 255}; + case 0x1908: return {data[0], data[1], data[2], data[3]}; + case 0x1909: return {data[0], data[0], data[0], 255}; + default: return {data[0], data[0], data[0], data[1]}; + } +} + +std::array Convert(std::array color, GLenum format) { + if (format == 0x1906) color = {0, 0, 0, color[3]}; + if (format == 0x1909 || format == 0x190a) color = {color[0], color[0], color[0], color[3]}; + if (format == 0x1907 || format == 0x1909 || format == 0x8d62) color[3] = 255; + if (format == 0x8056 || format == 0x8057 || format == 0x8d62) for (unsigned i = 0; i < 4; ++i) { + unsigned maximum = format == 0x8056 ? 15 : i == 3 ? (format == 0x8057 ? 1 : 255) : (format == 0x8d62 && i == 1 ? 63 : 31); + color[i] = Expand((unsigned(color[i]) * maximum + 127) / 255, maximum); + } + return color; +} + +void Upload(Image& image, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels, int size) { + if (!pixels || !width || !height) return; + auto& c = Current_nid_no_patch(); + std::size_t alignment = static_cast(c.state[0x0cf5][0]); + std::size_t stride = (std::size_t(width) * size + alignment - 1) & ~(alignment - 1); + auto* bytes = static_cast(pixels); + for (GLsizei row = 0; row < height; ++row) for (GLsizei col = 0; col < width; ++col) + image.color[std::size_t(y + row) * image.width + x + col] = Decode(bytes + std::size_t(row) * stride + std::size_t(col) * size, format, type); +} + +bool Parameter(GLenum name, GLint value) { + switch (name) { + case 0x2800: if (value == 0x2600 || value == 0x2601) return true; break; + case 0x2801: if (value == 0x2600 || value == 0x2601 || (value >= 0x2700 && value <= 0x2703)) return true; break; + case 0x2802: case 0x2803: if (value == 0x2901 || value == 0x812f || value == 0x8370) return true; break; + default: break; + } + Error_nid_no_patch(0x0500); + return false; +} + +int AttachmentIndex(GLenum attachment) { + if (attachment == 0x8ce0) return 0; + if (attachment == 0x8d00) return 1; + if (attachment == 0x8d20) return 2; + Error_nid_no_patch(0x0500); + return -1; +} + +Attachment* BoundAttachment(GLenum target, GLenum attachment) { + if (target != 0x8d40) { Error_nid_no_patch(0x0500); return nullptr; } + int index = AttachmentIndex(attachment); + if (index < 0) return nullptr; + auto& c = Current_nid_no_patch(); + if (!c.framebuffer) { Error_nid_no_patch(0x0502); return nullptr; } + return &c.framebuffer->attachments[index]; +} + +bool Readable(Context& c) { + if (Complete_nid_no_patch(c) != 0x8cd5) { Error_nid_no_patch(0x0506); return false; } + return true; +} + +bool Region(Image& image, GLint level, GLint x, GLint y, GLsizei width, GLsizei height) { + if (level < 0 || level > 11 || x < 0 || y < 0 || width < 0 || height < 0 || + x > image.width || y > image.height || width > image.width - x || height > image.height - y) { + Error_nid_no_patch(0x0501); return false; + } + return true; +} + +bool CopyFormat(GLenum destination, GLenum source) { + bool sourceAlpha = source == 0x1908 || source == 0x8056 || source == 0x8057; + if ((destination == 0x1908 || destination == 0x1906 || destination == 0x190a) && !sourceAlpha) { + Error_nid_no_patch(0x0502); return false; + } + return true; +} + +void Copy(Image& destination, GLint dx, GLint dy, Image& source, GLint sx, GLint sy, GLsizei width, GLsizei height) { + for (GLsizei y = 0; y < height; ++y) for (GLsizei x = 0; x < width; ++x) { + auto xx = std::int64_t(sx) + x, yy = std::int64_t(sy) + y; + if (xx >= 0 && yy >= 0 && xx < source.width && yy < source.height) + destination.color[std::size_t(dy + y) * destination.width + dx + x] = Convert(source.color[std::size_t(yy) * source.width + xx], destination.format); + } +} + +} + +extern "C" { + +void APS5_VABI glGenerateMipmap_nid_postfix(GLenum target) { + std::lock_guard lock(mutex); + int index = Target(target); + if (index < 0) return; + auto texture = BoundTexture(index); + GLenum firstFace = index ? 0x8515 : 0x0de1; + unsigned faces = index ? 6 : 1; + GLint width = 0, height = 0; + GLenum format = 0; + for (unsigned face = 0; face < faces; ++face) { + auto it = texture->images.find({firstFace + face, 0}); + if (it == texture->images.end()) { Error_nid_no_patch(0x0502); return; } + auto& base = it->second; + if (base.width <= 0 || base.height <= 0 || (base.width & (base.width - 1)) || (base.height & (base.height - 1)) || + (face && (base.width != width || base.height != height || base.format != format))) { Error_nid_no_patch(0x0502); return; } + width = base.width; height = base.height; format = base.format; + } + try { + auto images = texture->images; + for (unsigned face = 0; face < faces; ++face) { + GLint level = 0; + while (images.at({firstFace + face, level}).width > 1 || images.at({firstFace + face, level}).height > 1) { + const auto& source = images.at({firstFace + face, level}); + Image next; + next.Allocate(std::max(source.width / 2, 1), std::max(source.height / 2, 1), format); + unsigned nx = source.width > 1 ? 2 : 1, ny = source.height > 1 ? 2 : 1; + for (GLint y = 0; y < next.height; ++y) for (GLint x = 0; x < next.width; ++x) { + std::array color{}; + for (unsigned component = 0; component < 4; ++component) { + unsigned sum = 0; + for (unsigned dy = 0; dy < ny; ++dy) for (unsigned dx = 0; dx < nx; ++dx) + sum += source.color[std::size_t(y * ny + dy) * source.width + x * nx + dx][component]; + color[component] = static_cast((sum + nx * ny / 2) / (nx * ny)); + } + next.color[std::size_t(y) * next.width + x] = Convert(color, format); + } + images[{firstFace + face, ++level}] = std::move(next); + } + } + texture->images.swap(images); + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +void APS5_VABI glCompressedTexImage2D_nid_postfix(GLenum target, GLint level, GLenum format, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* data) { + std::lock_guard lock(mutex); + int index = ImageTarget(target); + if (index < 0 || !Dimensions(index, level, width, height, border)) return; + Error_nid_no_patch(0x0500); +} + +void APS5_VABI glCompressedTexSubImage2D_nid_postfix(GLenum target, GLint level, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) { + std::lock_guard lock(mutex); + if (ImageTarget(target) < 0) return; + Error_nid_no_patch(0x0500); +} + +void APS5_VABI glActiveTexture_nid_postfix(GLenum texture) { + std::lock_guard lock(mutex); + if (texture < 0x84c0 || texture >= 0x84c8) { Error_nid_no_patch(0x0500); return; } + Current_nid_no_patch().activeTexture = texture - 0x84c0; +} + +void APS5_VABI glGenTextures_nid_postfix(GLsizei count, GLuint* textures) { + std::lock_guard lock(mutex); + Generate_nid_no_patch(count, textures, Current_nid_no_patch().share->textures); +} + +void APS5_VABI glBindTexture_nid_postfix(GLenum target, GLuint name) { + std::lock_guard lock(mutex); + int index = Target(target); + if (index < 0) return; + auto& c = Current_nid_no_patch(); + if (!name) { c.textures[c.activeTexture][index].reset(); return; } + try { + auto& object = c.share->textures[name]; + if (object && object->target != target) { Error_nid_no_patch(0x0502); return; } + if (!object) { object = std::make_shared(); object->name = name; object->target = target; } + c.textures[c.activeTexture][index] = object; + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +GLboolean APS5_VABI glIsTexture_nid_postfix(GLuint name) { + std::lock_guard lock(mutex); + auto& objects = Current_nid_no_patch().share->textures; + auto it = objects.find(name); + return it != objects.end() && bool(it->second); +} + +void APS5_VABI glDeleteTextures_nid_postfix(GLsizei count, const GLuint* textures) { + std::lock_guard lock(mutex); + if (count < 0) { Error_nid_no_patch(0x0501); return; } + auto& c = Current_nid_no_patch(); + for (GLsizei i = 0; i < count; ++i) { + auto it = c.share->textures.find(textures[i]); + if (it == c.share->textures.end()) continue; + auto object = it->second; + if (object) { + for (auto& unit : c.textures) for (auto& binding : unit) if (binding == object) binding.reset(); + if (c.framebuffer) for (auto& attachment : c.framebuffer->attachments) if (attachment.texture == object) attachment = {}; + } + c.share->textures.erase(it); + } +} + +void APS5_VABI glTexParameteri_nid_postfix(GLenum target, GLenum name, GLint value) { + std::lock_guard lock(mutex); + int index = Target(target); + if (index < 0 || !Parameter(name, value)) return; + BoundTexture(index)->parameters[name] = value; +} + +void APS5_VABI glTexParameterf_nid_postfix(GLenum target, GLenum name, GLfloat value) { + if (!std::isfinite(value) || value < 0 || value > 65535 || value != std::round(value)) { + std::lock_guard lock(mutex); Error_nid_no_patch(0x0500); return; + } + glTexParameteri_nid_postfix(target, name, static_cast(std::round(value))); +} + +void APS5_VABI glTexParameteriv_nid_postfix(GLenum target, GLenum name, const GLint* values) { glTexParameteri_nid_postfix(target, name, *values); } +void APS5_VABI glTexParameterfv_nid_postfix(GLenum target, GLenum name, const GLfloat* values) { glTexParameterf_nid_postfix(target, name, *values); } + +void APS5_VABI glGetTexParameteriv_nid_postfix(GLenum target, GLenum name, GLint* values) { + std::lock_guard lock(mutex); + int index = Target(target); + if (index < 0) return; + auto object = BoundTexture(index); + auto it = object->parameters.find(name); + if (it == object->parameters.end()) { Error_nid_no_patch(0x0500); return; } + *values = it->second; +} + +void APS5_VABI glGetTexParameterfv_nid_postfix(GLenum target, GLenum name, GLfloat* values) { + std::lock_guard lock(mutex); + int index = Target(target); + if (index < 0) return; + auto object = BoundTexture(index); + auto it = object->parameters.find(name); + if (it == object->parameters.end()) { Error_nid_no_patch(0x0500); return; } + *values = static_cast(it->second); +} + +void APS5_VABI glTexImage2D_nid_postfix(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) { + std::lock_guard lock(mutex); + int index = ImageTarget(target); + if (index < 0 || !Dimensions(index, level, width, height, border)) return; + int size = PixelSize(format, type); + if (!size) return; + if (!Format(internalFormat)) { Error_nid_no_patch(0x0501); return; } + if (internalFormat != static_cast(format)) { Error_nid_no_patch(0x0502); return; } + try { + Image image; + image.Allocate(width, height, format); + Upload(image, 0, 0, width, height, format, type, pixels, size); + BoundTexture(index)->images[{target, level}] = std::move(image); + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +void APS5_VABI glTexSubImage2D_nid_postfix(GLenum target, GLint level, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) { + std::lock_guard lock(mutex); + int index = ImageTarget(target); + if (index < 0) return; + if (level < 0 || level > 11) { Error_nid_no_patch(0x0501); return; } + int size = PixelSize(format, type); + if (!size) return; + auto texture = BoundTexture(index); + auto it = texture->images.find({target, level}); + if (it == texture->images.end()) { Error_nid_no_patch(0x0502); return; } + if (!Region(it->second, level, x, y, width, height)) return; + if (format != it->second.format) { Error_nid_no_patch(0x0502); return; } + Upload(it->second, x, y, width, height, format, type, pixels, size); +} + +void APS5_VABI glGenRenderbuffers_nid_postfix(GLsizei count, GLuint* names) { + std::lock_guard lock(mutex); + Generate_nid_no_patch(count, names, Current_nid_no_patch().share->renderbuffers); +} + +void APS5_VABI glBindRenderbuffer_nid_postfix(GLenum target, GLuint name) { + std::lock_guard lock(mutex); + if (target != 0x8d41) { Error_nid_no_patch(0x0500); return; } + auto& c = Current_nid_no_patch(); + if (!name) { c.renderbuffer.reset(); return; } + try { + auto& object = c.share->renderbuffers[name]; + if (!object) { object = std::make_shared(); object->name = name; } + c.renderbuffer = object; + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +GLboolean APS5_VABI glIsRenderbuffer_nid_postfix(GLuint name) { + std::lock_guard lock(mutex); + auto& objects = Current_nid_no_patch().share->renderbuffers; + auto it = objects.find(name); + return it != objects.end() && bool(it->second); +} + +void APS5_VABI glDeleteRenderbuffers_nid_postfix(GLsizei count, const GLuint* names) { + std::lock_guard lock(mutex); + if (count < 0) { Error_nid_no_patch(0x0501); return; } + auto& c = Current_nid_no_patch(); + for (GLsizei i = 0; i < count; ++i) { + auto it = c.share->renderbuffers.find(names[i]); + if (it == c.share->renderbuffers.end()) continue; + auto object = it->second; + if (object) { + if (c.renderbuffer == object) c.renderbuffer.reset(); + if (c.framebuffer) for (auto& a : c.framebuffer->attachments) if (a.renderbuffer == object) a = {}; + } + c.share->renderbuffers.erase(it); + } +} + +void APS5_VABI glRenderbufferStorage_nid_postfix(GLenum target, GLenum format, GLsizei width, GLsizei height) { + std::lock_guard lock(mutex); + if (target != 0x8d41) { Error_nid_no_patch(0x0500); return; } + if (format != 0x8056 && format != 0x8057 && format != 0x8d62 && format != 0x81a5 && format != 0x8d48) { Error_nid_no_patch(0x0500); return; } + if (width < 0 || height < 0 || width > 2048 || height > 2048) { Error_nid_no_patch(0x0501); return; } + auto object = Current_nid_no_patch().renderbuffer; + if (!object) { Error_nid_no_patch(0x0502); return; } + try { object->image.Allocate(width, height, format); } + catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +void APS5_VABI glGetRenderbufferParameteriv_nid_postfix(GLenum target, GLenum name, GLint* value) { + std::lock_guard lock(mutex); + if (target != 0x8d41) { Error_nid_no_patch(0x0500); return; } + auto object = Current_nid_no_patch().renderbuffer; + if (!object) { Error_nid_no_patch(0x0502); return; } + const auto& image = object->image; + if (name == 0x8d42) *value = image.width; + else if (name == 0x8d43) *value = image.height; + else if (name == 0x8d44) *value = image.format ? image.format : 0x8056; + else if (name >= 0x8d50 && name <= 0x8d55) { + GLint bits = 0; + if (name <= 0x8d53) { + if (image.format == 0x8056) bits = 4; + if (image.format == 0x8057) bits = name == 0x8d53 ? 1 : 5; + if (image.format == 0x8d62) bits = name == 0x8d53 ? 0 : name == 0x8d51 ? 6 : 5; + } else if (name == 0x8d54 && image.format == 0x81a5) bits = 16; + else if (name == 0x8d55 && image.format == 0x8d48) bits = 8; + *value = bits; + } else Error_nid_no_patch(0x0500); +} + +void APS5_VABI glGenFramebuffers_nid_postfix(GLsizei count, GLuint* names) { + std::lock_guard lock(mutex); + Generate_nid_no_patch(count, names, Current_nid_no_patch().framebuffers); +} + +void APS5_VABI glBindFramebuffer_nid_postfix(GLenum target, GLuint name) { + std::lock_guard lock(mutex); + if (target != 0x8d40) { Error_nid_no_patch(0x0500); return; } + auto& c = Current_nid_no_patch(); + if (!name) { c.framebuffer.reset(); return; } + try { + auto& object = c.framebuffers[name]; + if (!object) { object = std::make_shared(); object->name = name; } + c.framebuffer = object; + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +GLboolean APS5_VABI glIsFramebuffer_nid_postfix(GLuint name) { + std::lock_guard lock(mutex); + auto& objects = Current_nid_no_patch().framebuffers; + auto it = objects.find(name); + return it != objects.end() && bool(it->second); +} + +void APS5_VABI glDeleteFramebuffers_nid_postfix(GLsizei count, const GLuint* names) { + std::lock_guard lock(mutex); + if (count < 0) { Error_nid_no_patch(0x0501); return; } + auto& c = Current_nid_no_patch(); + for (GLsizei i = 0; i < count; ++i) { + auto it = c.framebuffers.find(names[i]); + if (it == c.framebuffers.end()) continue; + if (c.framebuffer == it->second) c.framebuffer.reset(); + c.framebuffers.erase(it); + } +} + +void APS5_VABI glFramebufferRenderbuffer_nid_postfix(GLenum target, GLenum attachment, GLenum renderbufferTarget, GLuint name) { + std::lock_guard lock(mutex); + auto* a = BoundAttachment(target, attachment); + if (!a) return; + if (renderbufferTarget != 0x8d41) { Error_nid_no_patch(0x0500); return; } + if (!name) { *a = {}; return; } + auto& objects = Current_nid_no_patch().share->renderbuffers; + auto it = objects.find(name); + if (it == objects.end() || !it->second) { Error_nid_no_patch(0x0502); return; } + *a = {}; a->renderbuffer = it->second; +} + +void APS5_VABI glFramebufferTexture2D_nid_postfix(GLenum target, GLenum attachment, GLenum textureTarget, GLuint name, GLint level) { + std::lock_guard lock(mutex); + auto* a = BoundAttachment(target, attachment); + if (!a) return; + if (!name) { *a = {}; return; } + int index = ImageTarget(textureTarget); + if (index < 0) return; + if (level != 0) { Error_nid_no_patch(0x0501); return; } + auto& objects = Current_nid_no_patch().share->textures; + auto it = objects.find(name); + if (it == objects.end() || !it->second || it->second->target != (index ? 0x8513u : 0x0de1u)) { Error_nid_no_patch(0x0502); return; } + *a = {}; a->texture = it->second; a->face = textureTarget; a->level = level; +} + +GLenum APS5_VABI glCheckFramebufferStatus_nid_postfix(GLenum target) { + std::lock_guard lock(mutex); + if (target != 0x8d40) { Error_nid_no_patch(0x0500); return 0; } + return Complete_nid_no_patch(Current_nid_no_patch()); +} + +void APS5_VABI glGetFramebufferAttachmentParameteriv_nid_postfix(GLenum target, GLenum attachment, GLenum name, GLint* value) { + std::lock_guard lock(mutex); + auto* a = BoundAttachment(target, attachment); + if (!a) return; + if (name == 0x8cd0) *value = a->texture ? 0x1702 : a->renderbuffer ? 0x8d41 : 0; + else if (name == 0x8cd1 && (a->texture || a->renderbuffer)) *value = a->texture ? a->texture->name : a->renderbuffer->name; + else if (name == 0x8cd2 && a->texture) *value = a->level; + else if (name == 0x8cd3 && a->texture) *value = a->face == 0x0de1 ? 0 : a->face; + else Error_nid_no_patch(0x0500); +} + +void APS5_VABI glClear_nid_postfix(GLenum mask) { + std::lock_guard lock(mutex); + auto& c = Current_nid_no_patch(); + if (mask & ~(0x4000u | 0x0100u | 0x0400u)) { Error_nid_no_patch(0x0501); return; } + if (!Readable(c)) return; + for (unsigned component = 0; component < 3; ++component) { + if (!(mask & (component == 0 ? 0x4000 : component == 1 ? 0x0100 : 0x0400))) continue; + Image* image = c.framebuffer ? c.framebuffer->attachments[component].Get() : &Thread().draw->image; + if (!image) continue; + std::int64_t x0 = 0, y0 = 0, x1 = image->width, y1 = image->height; + if (c.enabled.contains(0x0c11)) { + auto& box = c.state[0x0c10]; + x0 = std::max(0, box[0]); y0 = std::max(0, box[1]); + x1 = std::min(x1, std::int64_t(box[0]) + std::int64_t(box[2])); + y1 = std::min(y1, std::int64_t(box[1]) + std::int64_t(box[3])); + } + std::array color{}; + for (unsigned i = 0; i < 4; ++i) { + double value = c.state[0x0c22][i]; + unsigned maximum = 255; + if (image->format == 0x8056) maximum = 15; + if (image->format == 0x8057) maximum = i == 3 ? 1 : 31; + if (image->format == 0x8d62 && i != 3) maximum = i == 1 ? 63 : 31; + color[i] = std::isnan(value) ? 0 : Expand(static_cast(std::round(value * maximum)), maximum); + } + color = Convert(color, image->format); + auto stencilMask = static_cast(static_cast(c.state[0x0b98][0])); + auto stencil = static_cast(static_cast(c.state[0x0b91][0])); + for (auto y = y0; y < y1; ++y) for (auto x = x0; x < x1; ++x) { + auto index = std::size_t(y) * image->width + x; + if (component == 0 && !image->color.empty()) for (unsigned i = 0; i < 4; ++i) { + if (c.state[0x0c23][i]) image->color[index][i] = color[i]; + } + if (component == 1 && !image->depth.empty() && c.state[0x0b72][0]) { + double value = c.state[0x0b73][0]; + image->depth[index] = std::isnan(value) ? 0 : static_cast(std::round(value * 65535) / 65535); + } + if (component == 2 && !image->stencil.empty()) image->stencil[index] = (image->stencil[index] & ~stencilMask) | (stencil & stencilMask); + } + } +} + +void APS5_VABI glReadPixels_nid_postfix(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) { + std::lock_guard lock(mutex); + auto& c = Current_nid_no_patch(); + if (width < 0 || height < 0) { Error_nid_no_patch(0x0501); return; } + if (!Readable(c)) return; + if (format != 0x1908 || type != 0x1401) { + if (!Format(format) || (type != 0x1401 && type != 0x8033 && type != 0x8034 && type != 0x8363)) Error_nid_no_patch(0x0500); + else Error_nid_no_patch(0x0502); + return; + } + auto* image = Color_nid_no_patch(c, true); + if (!image) { Error_nid_no_patch(0x0502); return; } + std::size_t alignment = static_cast(c.state[0x0d05][0]); + std::size_t stride = (std::size_t(width) * 4 + alignment - 1) & ~(alignment - 1); + auto* bytes = static_cast(pixels); + std::int64_t firstColumn = std::max(0, -std::int64_t(x)); + std::int64_t lastColumn = std::min(width, std::int64_t(image->width) - x); + std::int64_t firstRow = std::max(0, -std::int64_t(y)); + std::int64_t lastRow = std::min(height, std::int64_t(image->height) - y); + for (auto row = firstRow; row < lastRow; ++row) for (auto col = firstColumn; col < lastColumn; ++col) { + auto color = Convert(image->color[std::size_t(y + row) * image->width + std::size_t(x + col)], image->format); + std::memcpy(bytes + std::size_t(row) * stride + std::size_t(col) * 4, color.data(), 4); + } +} + +void APS5_VABI glCopyTexImage2D_nid_postfix(GLenum target, GLint level, GLenum format, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { + std::lock_guard lock(mutex); + int index = ImageTarget(target); + if (index < 0 || !Dimensions(index, level, width, height, border)) return; + if (!Format(format)) { Error_nid_no_patch(0x0501); return; } + auto& c = Current_nid_no_patch(); + if (!Readable(c)) return; + auto* source = Color_nid_no_patch(c, true); + if (!source) { Error_nid_no_patch(0x0502); return; } + if (!CopyFormat(format, source->format)) return; + try { + Image image; + image.Allocate(width, height, format); + Copy(image, 0, 0, *source, x, y, width, height); + BoundTexture(index)->images[{target, level}] = std::move(image); + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +void APS5_VABI glCopyTexSubImage2D_nid_postfix(GLenum target, GLint level, GLint dx, GLint dy, GLint x, GLint y, GLsizei width, GLsizei height) { + std::lock_guard lock(mutex); + int index = ImageTarget(target); + if (index < 0) return; + if (level < 0 || level > 11) { Error_nid_no_patch(0x0501); return; } + auto texture = BoundTexture(index); + auto it = texture->images.find({target, level}); + if (it == texture->images.end()) { Error_nid_no_patch(0x0502); return; } + if (!Region(it->second, level, dx, dy, width, height)) return; + auto& c = Current_nid_no_patch(); + if (!Readable(c)) return; + auto* source = Color_nid_no_patch(c, true); + if (!source) { Error_nid_no_patch(0x0502); return; } + if (!CopyFormat(it->second.format, source->format)) return; + Copy(it->second, dx, dy, *source, x, y, width, height); +} + +} diff --git a/core/libs/prx/libSceGLSlimVSH/src/Objects.cpp b/core/libs/prx/libSceGLSlimVSH/src/Objects.cpp new file mode 100644 index 0000000000..492e7c0604 --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/src/Objects.cpp @@ -0,0 +1,281 @@ +#include "State.hpp" +#include + +using namespace Slim; + +namespace { + +std::shared_ptr* BufferBinding(GLenum target) { + auto& c = Current_nid_no_patch(); + if (target == 0x8892) return &c.arrayBuffer; + if (target == 0x8893) return &c.elementBuffer; + Error_nid_no_patch(0x0500); + return nullptr; +} + +Buffer* BoundBuffer(GLenum target) { + auto binding = BufferBinding(target); + if (!binding) return nullptr; + if (!*binding) { Error_nid_no_patch(0x0502); return nullptr; } + return binding->get(); +} + +Slim::Shader* FindShader(GLuint name) { + auto& share = *Current_nid_no_patch().share; + auto it = share.shaders.find(name); + if (it != share.shaders.end()) return it->second.get(); + Error_nid_no_patch(share.programs.contains(name) ? 0x0502 : 0x0501); + return nullptr; +} + +Program* FindProgram(GLuint name) { + auto& share = *Current_nid_no_patch().share; + auto it = share.programs.find(name); + if (it != share.programs.end()) return it->second.get(); + Error_nid_no_patch(share.shaders.contains(name) ? 0x0502 : 0x0501); + return nullptr; +} + +GLuint ShaderName(ShareGroup& share) { + for (GLuint name = 1; name; ++name) if (!share.shaders.contains(name) && !share.programs.contains(name)) return name; + throw std::bad_alloc(); +} + +void CollectShaders(ShareGroup& share) { + std::erase_if(share.shaders, [](const auto& item) { return item.second->deleted && item.second.use_count() == 1; }); +} + +void CopyString(const std::string& source, GLsizei size, GLsizei* length, GLchar* output) { + GLsizei count = size ? static_cast(std::min(source.size(), size - 1)) : 0; + if (size) { std::memcpy(output, source.data(), count); output[count] = 0; } + if (length) *length = count; +} + +} + +extern "C" { + +void APS5_VABI glGenBuffers_nid_postfix(GLsizei count, GLuint* names) { + std::lock_guard lock(mutex); + Generate_nid_no_patch(count, names, Current_nid_no_patch().share->buffers); +} + +void APS5_VABI glBindBuffer_nid_postfix(GLenum target, GLuint name) { + std::lock_guard lock(mutex); + auto* binding = BufferBinding(target); + if (!binding) return; + if (!name) { binding->reset(); return; } + try { + auto& object = Current_nid_no_patch().share->buffers[name]; + if (!object) { object = std::make_shared(); object->name = name; } + *binding = object; + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +GLboolean APS5_VABI glIsBuffer_nid_postfix(GLuint name) { + std::lock_guard lock(mutex); + auto& objects = Current_nid_no_patch().share->buffers; + auto it = objects.find(name); + return it != objects.end() && bool(it->second); +} + +void APS5_VABI glDeleteBuffers_nid_postfix(GLsizei count, const GLuint* names) { + std::lock_guard lock(mutex); + if (count < 0) { Error_nid_no_patch(0x0501); return; } + auto& c = Current_nid_no_patch(); + for (GLsizei i = 0; i < count; ++i) { + auto it = c.share->buffers.find(names[i]); + if (it == c.share->buffers.end()) continue; + auto object = it->second; + if (object) { + if (c.arrayBuffer == object) c.arrayBuffer.reset(); + if (c.elementBuffer == object) c.elementBuffer.reset(); + for (auto& a : c.attributes) if (a.buffer == object) a.buffer.reset(); + } + c.share->buffers.erase(it); + } +} + +void APS5_VABI glBufferData_nid_postfix(GLenum target, GLsizeiptr size, const void* data, GLenum usage) { + std::lock_guard lock(mutex); + auto* buffer = BoundBuffer(target); + if (!buffer) return; + if (size < 0) { Error_nid_no_patch(0x0501); return; } + if (usage != 0x88e0 && usage != 0x88e4 && usage != 0x88e8) { Error_nid_no_patch(0x0500); return; } + try { + std::vector next; + if (static_cast(size) > next.max_size()) { Error_nid_no_patch(0x0505); return; } + next.resize(size); + if (size && data) std::memcpy(next.data(), data, size); + buffer->data.swap(next); + buffer->usage = usage; + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +void APS5_VABI glBufferSubData_nid_postfix(GLenum target, GLintptr offset, GLsizeiptr size, const void* data) { + std::lock_guard lock(mutex); + auto* buffer = BoundBuffer(target); + if (!buffer) return; + if (offset < 0 || size < 0 || static_cast(offset) > buffer->data.size() || static_cast(size) > buffer->data.size() - offset) { Error_nid_no_patch(0x0501); return; } + if (size) std::memcpy(buffer->data.data() + offset, data, size); +} + +void APS5_VABI glGetBufferParameteriv_nid_postfix(GLenum target, GLenum name, GLint* value) { + std::lock_guard lock(mutex); + auto* buffer = BoundBuffer(target); + if (!buffer) return; + if (name == 0x8764) *value = static_cast(std::min(buffer->data.size(), std::numeric_limits::max())); + else if (name == 0x8765) *value = buffer->usage; + else Error_nid_no_patch(0x0500); +} + +GLuint APS5_VABI glCreateShader_nid_postfix(GLenum type) { + std::lock_guard lock(mutex); + if (type != 0x8b30 && type != 0x8b31) { Error_nid_no_patch(0x0500); return 0; } + try { + auto& share = *Current_nid_no_patch().share; + auto name = ShaderName(share); + auto shader = std::make_shared(); + shader->type = type; + share.shaders.emplace(name, std::move(shader)); + return name; + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); return 0; } +} + +GLuint APS5_VABI glCreateProgram_nid_postfix() { + std::lock_guard lock(mutex); + try { + auto& share = *Current_nid_no_patch().share; + auto name = ShaderName(share); + share.programs.emplace(name, std::make_shared()); + return name; + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); return 0; } +} + +GLboolean APS5_VABI glIsShader_nid_postfix(GLuint name) { + std::lock_guard lock(mutex); + return Current_nid_no_patch().share->shaders.contains(name); +} + +GLboolean APS5_VABI glIsProgram_nid_postfix(GLuint name) { + std::lock_guard lock(mutex); + return Current_nid_no_patch().share->programs.contains(name); +} + +void APS5_VABI glDeleteShader_nid_postfix(GLuint name) { + std::lock_guard lock(mutex); + if (!name) return; + if (auto* shader = FindShader(name)) shader->deleted = true; + CollectShaders(*Current_nid_no_patch().share); +} + +void APS5_VABI glDeleteProgram_nid_postfix(GLuint name) { + std::lock_guard lock(mutex); + if (!name || !FindProgram(name)) return; + auto& share = *Current_nid_no_patch().share; + share.programs.erase(name); + CollectShaders(share); +} + +void APS5_VABI glAttachShader_nid_postfix(GLuint program, GLuint shader) { + std::lock_guard lock(mutex); + auto* p = FindProgram(program); + if (!p) return; + auto* object = FindShader(shader); + if (!object) return; + for (auto& entry : p->shaders) if (entry.second->type == object->type) { Error_nid_no_patch(0x0502); return; } + try { p->shaders.emplace(shader, Current_nid_no_patch().share->shaders.at(shader)); } + catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +void APS5_VABI glDetachShader_nid_postfix(GLuint program, GLuint shader) { + std::lock_guard lock(mutex); + auto* p = FindProgram(program); + if (!p || !FindShader(shader)) return; + if (!p->shaders.erase(shader)) { Error_nid_no_patch(0x0502); return; } + CollectShaders(*Current_nid_no_patch().share); +} + +void APS5_VABI glShaderSource_nid_postfix(GLuint shader, GLsizei count, const GLchar* const* strings, const GLint* lengths) { + std::lock_guard lock(mutex); + auto* object = FindShader(shader); + if (!object) return; + if (count < 0) { Error_nid_no_patch(0x0501); return; } + try { + std::string source; + for (GLsizei i = 0; i < count; ++i) { + if (lengths && lengths[i] >= 0) source.append(strings[i], lengths[i]); + else source.append(strings[i]); + } + object->source.swap(source); + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +void APS5_VABI glGetShaderiv_nid_postfix(GLuint shader, GLenum name, GLint* value) { + std::lock_guard lock(mutex); + auto* object = FindShader(shader); + if (!object) return; + switch (name) { + case 0x8b4f: *value = object->type; break; + case 0x8b80: *value = object->deleted; break; + case 0x8b81: case 0x8b84: *value = 0; break; + case 0x8b88: *value = object->source.empty() ? 0 : static_cast(std::min(object->source.size() + 1, std::numeric_limits::max())); break; + default: Error_nid_no_patch(0x0500); break; + } +} + +void APS5_VABI glGetProgramiv_nid_postfix(GLuint program, GLenum name, GLint* value) { + std::lock_guard lock(mutex); + auto* object = FindProgram(program); + if (!object) return; + switch (name) { + case 0x8b80: *value = object->deleted; break; + case 0x8b85: *value = static_cast(object->shaders.size()); break; + case 0x8b82: case 0x8b83: case 0x8b84: case 0x8b86: case 0x8b87: case 0x8b89: case 0x8b8a: *value = 0; break; + default: Error_nid_no_patch(0x0500); break; + } +} + +void APS5_VABI glGetAttachedShaders_nid_postfix(GLuint program, GLsizei size, GLsizei* count, GLuint* shaders) { + std::lock_guard lock(mutex); + if (size < 0) { Error_nid_no_patch(0x0501); return; } + auto* object = FindProgram(program); + if (!object) return; + GLsizei written = 0; + for (auto& entry : object->shaders) { + if (written == size) break; + shaders[written++] = entry.first; + } + if (count) *count = written; +} + +void APS5_VABI glGetShaderInfoLog_nid_postfix(GLuint shader, GLsizei size, GLsizei* length, GLchar* log) { + std::lock_guard lock(mutex); + if (size < 0) { Error_nid_no_patch(0x0501); return; } + if (FindShader(shader)) CopyString({}, size, length, log); +} + +void APS5_VABI glGetProgramInfoLog_nid_postfix(GLuint program, GLsizei size, GLsizei* length, GLchar* log) { + std::lock_guard lock(mutex); + if (size < 0) { Error_nid_no_patch(0x0501); return; } + if (FindProgram(program)) CopyString({}, size, length, log); +} + +void APS5_VABI glBindAttribLocation_nid_postfix(GLuint program, GLuint index, const GLchar* name) { + std::lock_guard lock(mutex); + auto* object = FindProgram(program); + if (!object) return; + if (index >= 8) { Error_nid_no_patch(0x0501); return; } + if (std::strncmp(name, "gl_", 3) == 0) { Error_nid_no_patch(0x0502); return; } + try { object->attributes[name] = index; } + catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +void APS5_VABI glUseProgram_nid_postfix(GLuint program) { + std::lock_guard lock(mutex); + if (program && FindProgram(program)) Error_nid_no_patch(0x0502); +} + +void APS5_VABI glReleaseShaderCompiler_nid_postfix() { std::lock_guard lock(mutex); Current_nid_no_patch(); } + +} diff --git a/core/libs/prx/libSceGLSlimVSH/src/State.cpp b/core/libs/prx/libSceGLSlimVSH/src/State.cpp new file mode 100644 index 0000000000..f5b1f74af3 --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/src/State.cpp @@ -0,0 +1,186 @@ +#include "State.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" + +namespace Slim { + +std::recursive_mutex mutex; +struct ThreadTag; + +ThreadState& Thread() { + return HostThreadLocal(); +} +std::map> contexts; +std::map> surfaces; +bool initialized = false; +int displayToken; +int configToken; + +ThreadState::~ThreadState() { + std::lock_guard lock(mutex); + if (context) context->bound = false; + context.reset(); draw.reset(); read.reset(); + std::erase_if(contexts, [](const auto& item) { return item.second->deleted && item.second.use_count() == 1; }); + std::erase_if(surfaces, [](const auto& item) { return item.second->deleted && item.second.use_count() == 1; }); +} + +float Clamp_nid_no_patch(float value) { return std::clamp(value, 0.0f, 1.0f); } + +void Image::Allocate(GLint w, GLint h, GLenum f) { + Image next; + next.width = w; + next.height = h; + next.format = f; + auto size = static_cast(w) * h; + if (f == 0x81a5) next.depth.resize(size); + else if (f == 0x8d48) next.stencil.resize(size); + else next.color.resize(size); + *this = std::move(next); +} + +Image* Attachment::Get() { + if (renderbuffer) return &renderbuffer->image; + if (texture) { + auto it = texture->images.find({face, level}); + if (it != texture->images.end()) return &it->second; + } + return nullptr; +} + +Context::Context() : state{ + {0x0b21, {1}}, {0x0b44, {0}}, {0x0b45, {0x0405}}, {0x0b46, {0x0901}}, + {0x0b70, {0, 1}}, {0x0b72, {1}}, {0x0b73, {1}}, {0x0b74, {0x0201}}, + {0x0b91, {0}}, {0x0b92, {0x0207}}, {0x0b93, {4294967295.0}}, + {0x0b94, {0x1e00}}, {0x0b95, {0x1e00}}, {0x0b96, {0x1e00}}, + {0x0b97, {0}}, {0x0b98, {4294967295.0}}, + {0x8800, {0x0207}}, {0x8801, {0x1e00}}, {0x8802, {0x1e00}}, {0x8803, {0x1e00}}, + {0x8ca3, {0}}, {0x8ca4, {4294967295.0}}, {0x8ca5, {4294967295.0}}, + {0x0ba2, {0, 0, 0, 0}}, {0x0c10, {0, 0, 0, 0}}, + {0x0c22, {0, 0, 0, 0}}, {0x0c23, {1, 1, 1, 1}}, + {0x8005, {0, 0, 0, 0}}, {0x8009, {0x8006}}, {0x883d, {0x8006}}, + {0x80c8, {0}}, {0x80c9, {1}}, {0x80ca, {0}}, {0x80cb, {1}}, + {0x8038, {0}}, {0x2a00, {0}}, {0x80aa, {1}}, {0x80ab, {0}}, + {0x0d05, {4}}, {0x0cf5, {4}}, {0x8192, {0x1100}} +} { + for (unsigned i = 0; i < 2; ++i) { + defaultTextures[i] = std::make_shared(); + defaultTextures[i]->target = i ? 0x8513 : 0x0de1; + } +} + +Context& Current_nid_no_patch() { + if (!Thread().context) NotImplemented_nid_no_patch(__func__); + return *Thread().context; +} + +void Error_nid_no_patch(GLenum error) { + auto& context = Current_nid_no_patch(); + if (!context.error) context.error = error; +} + +EGLBoolean EglError_nid_no_patch(EGLint error) { Thread().error = error; return 0; } + +bool Display_nid_no_patch(EGLDisplay display) { + if (display != &displayToken) return EglError_nid_no_patch(0x3008); + if (!initialized) return EglError_nid_no_patch(0x3001); + return true; +} + +bool Config_nid_no_patch(EGLConfig config) { + if (config != &configToken) return EglError_nid_no_patch(0x3005); + return true; +} + +bool Capability_nid_no_patch(GLenum cap) { + switch (cap) { + case 0x0b44: case 0x0be2: case 0x0bd0: case 0x0b71: case 0x0b90: + case 0x0c11: case 0x8037: case 0x809e: case 0x80a0: return true; + default: return false; + } +} + +GLenum Complete_nid_no_patch(Context& context) { + if (!context.framebuffer) return Thread().draw ? 0x8cd5 : 0x8cd7; + GLint width = -1, height = -1; + bool any = false; + for (unsigned i = 0; i < 3; ++i) { + auto& attachment = context.framebuffer->attachments[i]; + if (!attachment.texture && !attachment.renderbuffer) continue; + any = true; + auto* image = attachment.Get(); + if (!image || !image->width || !image->height) return 0x8cd6; + if (i == 0 && image->format != 0x1907 && image->format != 0x1908 && image->format != 0x8056 && image->format != 0x8057 && image->format != 0x8d62) return 0x8cd6; + if (i == 1 && image->format != 0x81a5) return 0x8cd6; + if (i == 2 && image->format != 0x8d48) return 0x8cd6; + if (width >= 0 && (width != image->width || height != image->height)) return 0x8cd9; + width = image->width; + height = image->height; + } + return any ? 0x8cd5 : 0x8cd7; +} + +Image* Color_nid_no_patch(Context& context, bool read) { + if (context.framebuffer) return context.framebuffer->attachments[0].Get(); + auto surface = read ? Thread().read : Thread().draw; + return surface ? &surface->image : nullptr; +} + +bool Query_nid_no_patch(GLenum name, std::vector& result) { + auto& c = Current_nid_no_patch(); + if (Capability_nid_no_patch(name)) { result = {double(c.enabled.contains(name))}; return true; } + if (name == 0x0b97 || name == 0x8ca3) { + Image* image = c.framebuffer ? c.framebuffer->attachments[2].Get() : Thread().draw ? &Thread().draw->image : nullptr; + double maximum = image && (!c.framebuffer || image->format == 0x8d48) ? 255 : 0; + result = {std::clamp(c.state.at(name)[0], 0.0, maximum)}; + return true; + } + if (auto it = c.state.find(name); it != c.state.end()) { result = it->second; return true; } + switch (name) { + case 0x8894: result = {double(c.arrayBuffer ? c.arrayBuffer->name : 0)}; break; + case 0x8895: result = {double(c.elementBuffer ? c.elementBuffer->name : 0)}; break; + case 0x8ca6: result = {double(c.framebuffer ? c.framebuffer->name : 0)}; break; + case 0x8ca7: result = {double(c.renderbuffer ? c.renderbuffer->name : 0)}; break; + case 0x84e0: result = {double(0x84c0 + c.activeTexture)}; break; + case 0x8069: case 0x8514: { + auto t = c.textures[c.activeTexture][name == 0x8514]; + result = {double(t ? t->name : 0)}; break; + } + case 0x8b8d: result = {0}; break; + case 0x0d33: case 0x851c: case 0x84e8: result = {2048}; break; + case 0x0d3a: result = {2048, 2048}; break; + case 0x8869: case 0x8872: case 0x8b4d: result = {8}; break; + case 0x8b4c: result = {0}; break; + case 0x8dfb: result = {128}; break; + case 0x8dfc: result = {8}; break; + case 0x8dfd: result = {16}; break; + case 0x846d: case 0x846e: result = {1, 1}; break; + case 0x0d50: result = {4}; break; + case 0x80a8: case 0x80a9: case 0x86a2: case 0x8df9: result = {0}; break; + case 0x86a3: case 0x8df8: result.clear(); break; + case 0x8dfa: NotImplemented_nid_no_patch(__func__); + case 0x8b9a: result = {0x1401}; break; + case 0x8b9b: result = {0x1908}; break; + case 0x0d52: case 0x0d53: case 0x0d54: case 0x0d55: case 0x0d56: case 0x0d57: { + Image* image = nullptr; + unsigned index = name < 0x0d56 ? 0 : name - 0x0d55; + if (c.framebuffer) image = c.framebuffer->attachments[index].Get(); + else if (Thread().draw) image = &Thread().draw->image; + GLint bits = 0; + if (image) { + if (index == 1) bits = !c.framebuffer || image->format == 0x81a5 ? 16 : 0; + else if (index == 2) bits = !c.framebuffer || image->format == 0x8d48 ? 8 : 0; + else if (image->format && image->format != 0x81a5 && image->format != 0x8d48) { + bits = 8; + if (image->format == 0x8056) bits = 4; + if (image->format == 0x8057) bits = name == 0x0d55 ? 1 : 5; + if (image->format == 0x8d62) bits = name == 0x0d55 ? 0 : name == 0x0d53 ? 6 : 5; + if (image->format == 0x1907 && name == 0x0d55) bits = 0; + } + } + result = {double(bits)}; break; + } + default: Error_nid_no_patch(0x0500); return false; + } + return true; +} + +} diff --git a/core/libs/prx/libSceGLSlimVSH/src/State.hpp b/core/libs/prx/libSceGLSlimVSH/src/State.hpp new file mode 100644 index 0000000000..65d1545280 --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/src/State.hpp @@ -0,0 +1,173 @@ +#pragma once + +#include "prx/libSceGLSlimVSH/libSceGLSlimVSH.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Slim { + +struct Buffer { + GLuint name = 0; + GLenum usage = 0x88e4; + std::vector data; +}; + +struct Image { + GLint width = 0; + GLint height = 0; + GLenum format = 0; + std::vector> color; + std::vector depth; + std::vector stencil; + void Allocate(GLint w, GLint h, GLenum f); +}; + +struct Texture { + GLuint name = 0; + GLenum target = 0; + std::map, Image> images; + std::map parameters{{0x2800, 0x2601}, {0x2801, 0x2702}, {0x2802, 0x2901}, {0x2803, 0x2901}}; +}; + +struct Renderbuffer { + GLuint name = 0; + Image image; +}; + +struct Attachment { + std::shared_ptr texture; + std::shared_ptr renderbuffer; + GLenum face = 0; + GLint level = 0; + Image* Get(); +}; + +struct Framebuffer { + GLuint name = 0; + std::array attachments; +}; + +struct Shader { + GLenum type = 0; + bool deleted = false; + std::string source; +}; + +struct Program { + bool deleted = false; + std::map> shaders; + std::map attributes; +}; + +struct ShareGroup { + std::map> buffers; + std::map> textures; + std::map> renderbuffers; + std::map> shaders; + std::map> programs; +}; + +struct Attribute { + std::array value{0, 0, 0, 1}; + bool enabled = false; + GLint size = 4; + GLenum type = 0x1406; + bool normalized = false; + GLsizei stride = 0; + const void* pointer = nullptr; + std::shared_ptr buffer; +}; + +struct Surface { + Image image; + bool deleted = false; + bool largest = false; + bool mipmap = false; + EGLint colorspace = 0x3089; + EGLint alphaFormat = 0x308b; + GLint mipmapLevel = 0; +}; + +struct Context { + bool deleted = false; + bool bound = false; + bool initializedViewport = false; + std::shared_ptr share = std::make_shared(); + GLenum error = 0; + std::map> state; + std::set enabled{0x0bd0}; + std::array attributes; + std::shared_ptr arrayBuffer; + std::shared_ptr elementBuffer; + std::shared_ptr renderbuffer; + std::map> framebuffers; + std::shared_ptr framebuffer; + std::array, 2>, 8> textures; + std::array, 2> defaultTextures; + unsigned activeTexture = 0; + Context(); +}; + +struct ThreadState { + EGLint error = 0x3000; + std::shared_ptr context; + std::shared_ptr draw; + std::shared_ptr read; + ~ThreadState(); +}; + +extern std::recursive_mutex mutex; +ThreadState& Thread(); +extern std::map> contexts; +extern std::map> surfaces; +extern bool initialized; +extern int displayToken; +extern int configToken; + +Context& Current_nid_no_patch(); +void Error_nid_no_patch(GLenum error); +EGLBoolean EglError_nid_no_patch(EGLint error); +bool Display_nid_no_patch(EGLDisplay display); +bool Config_nid_no_patch(EGLConfig config); +bool Capability_nid_no_patch(GLenum cap); +bool Query_nid_no_patch(GLenum name, std::vector& result); +GLenum Complete_nid_no_patch(Context& context); +Image* Color_nid_no_patch(Context& context, bool read); +float Clamp_nid_no_patch(float value); + +template +GLuint Name_nid_no_patch(const std::map>& objects) { + GLuint name = 1; + for (const auto& entry : objects) { + if (entry.first != name) break; + if (++name == 0) throw std::bad_alloc(); + } + return name; +} + +template +void Generate_nid_no_patch(GLsizei count, GLuint* names, std::map>& objects) { + if (count < 0) { Error_nid_no_patch(0x0501); return; } + try { + auto next = objects; + std::vector result; + for (GLsizei i = 0; i < count; ++i) { + GLuint name = Name_nid_no_patch(next); + next.emplace(name, nullptr); + result.push_back(name); + } + objects.swap(next); + std::copy(result.begin(), result.end(), names); + } catch (const std::bad_alloc&) { Error_nid_no_patch(0x0505); } +} + +} diff --git a/core/libs/prx/libSceGLSlimVSH/tests/State.cpp b/core/libs/prx/libSceGLSlimVSH/tests/State.cpp new file mode 100644 index 0000000000..d2268b0139 --- /dev/null +++ b/core/libs/prx/libSceGLSlimVSH/tests/State.cpp @@ -0,0 +1,336 @@ +#include "prx/libSceGLSlimVSH/libSceGLSlimVSH.hpp" +#include +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "GLSlim state check failed at line %d\n", line); + std::abort(); + } +} + +#define Require(value) Check((value), __LINE__) + +static void Error(GLenum expected) { + Require(glGetError_nid_postfix() == expected); + Require(glGetError_nid_postfix() == 0); +} + +static GLint Integer(GLenum name) { + GLint value = -99; + glGetIntegerv_nid_postfix(name, &value); + return value; +} + +static void Defaults() { + Require(Integer(0x80c9) == 1); + Require(Integer(0x80c8) == 0); + Require(Integer(0x80cb) == 1); + Require(Integer(0x80ca) == 0); + Require(Integer(0x0b74) == 0x0201); + Require(Integer(0x0d05) == 4); + Require(Integer(0x0cf5) == 4); + Require(Integer(0x0b98) == -1); + Require(glIsEnabled_nid_postfix(0x0bd0)); + Require(!glIsEnabled_nid_postfix(0x0be2)); + std::array box{}; + glGetIntegerv_nid_postfix(0x0ba2, box.data()); + Require((box == std::array{0, 0, 4, 3})); + std::array attr{}; + glGetVertexAttribfv_nid_postfix(3, 0x8626, attr.data()); + Require((attr == std::array{0, 0, 0, 1})); + glVertexAttrib2f_nid_postfix(3, 2, -3); + glGetVertexAttribfv_nid_postfix(3, 0x8626, attr.data()); + Require((attr == std::array{2, -3, 0, 1})); + glVertexAttrib1f_nid_postfix(8, 1); + Error(0x0501); + glEnable_nid_postfix(0xffffffff); + glDepthFunc_nid_postfix(0); + Error(0x0500); + glScissor_nid_postfix(0, 0, -1, 1); + Error(0x0501); + glBlendFuncSeparate_nid_postfix(1, 0x0308, 1, 0); + Error(0x0500); + glBlendFuncSeparate_nid_postfix(0x8001, 0x8003, 1, 0); + Error(0x0502); + Require(Integer(0x80c9) == 1); + glClearColor_nid_postfix(-1, 2, 0.5f, 1); + glGetFloatv_nid_postfix(0x0c22, attr.data()); + Require((attr == std::array{0, 1, 0.5f, 1})); + glGetIntegerv_nid_postfix(0x0c22, box.data()); + Require(box[0] == 0 && box[1] == std::numeric_limits::max()); + GLint untouched = 55; + glGetIntegerv_nid_postfix(0xffffffff, &untouched); + Require(untouched == 55); + Error(0x0500); +} + +static void Buffers() { + GLuint names[2]{}; + glGenBuffers_nid_postfix(2, names); + Require(names[0] && names[1] && names[0] != names[1]); + Require(!glIsBuffer_nid_postfix(names[0])); + glBindBuffer_nid_postfix(0x8892, names[0]); + Require(glIsBuffer_nid_postfix(names[0])); + const std::array initial{1, 2, 3, 4, 5}; + glBufferData_nid_postfix(0x8892, initial.size(), initial.data(), 0x88e8); + const unsigned char patch[2]{9, 8}; + glBufferSubData_nid_postfix(0x8892, 2, 2, patch); + Error(0); + GLint size = 0; + glGetBufferParameteriv_nid_postfix(0x8892, 0x8764, &size); + Require(size == 5); + glBufferSubData_nid_postfix(0x8892, std::numeric_limits::max(), 2, patch); + Error(0x0501); + glBufferData_nid_postfix(0x8892, 8, nullptr, 0); + Error(0x0500); + glGetBufferParameteriv_nid_postfix(0x8892, 0x8764, &size); + Require(size == 5); + glVertexAttribPointer_nid_postfix(1, 2, 0x1406, 0, 8, nullptr); + glDeleteBuffers_nid_postfix(2, names); + Require(!glIsBuffer_nid_postfix(names[0])); + Require(Integer(0x8894) == 0); + glGetVertexAttribiv_nid_postfix(1, 0x889f, &size); + Require(size == 0); + glBufferData_nid_postfix(0x8892, 4, nullptr, 0x88e4); + Error(0x0502); +} + +static void Pixels() { + glClearColor_nid_postfix(1, 0, 0, 1); + glClearDepthf_nid_postfix(0.5f); + glClearStencil_nid_postfix(0xab); + GLfloat clearDepth = 0; + glGetFloatv_nid_postfix(0x0b73, &clearDepth); + Require(clearDepth == 0.5f); + Require(Integer(0x0b91) == 0xab); + glClear_nid_postfix(0x4500); + std::array data{}; + glReadPixels_nid_postfix(0, 0, 4, 3, 0x1908, 0x1401, data.data()); + for (unsigned i = 0; i < 12; ++i) Require(data[i * 4] == 255 && data[i * 4 + 1] == 0 && data[i * 4 + 3] == 255); + Error(0); + std::array edge{}; + glReadPixels_nid_postfix(-2, 0, 3, 1, 0x1908, 0x1401, edge.data()); + Require((edge == std::array{0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255})); + std::array outside{7, 7, 7, 7}; + glReadPixels_nid_postfix(-536870912, 0, 1, 1, 0x1908, 0x1401, outside.data()); + Require((outside == std::array{7, 7, 7, 7})); + Error(0); + glEnable_nid_postfix(0x0c11); + glScissor_nid_postfix(1, 1, 2, 1); + glColorMask_nid_postfix(0, 1, 1, 0); + glStencilMask_nid_postfix(0x0f); + glClearColor_nid_postfix(0, 1, 0, 0); + glClearStencil_nid_postfix(0x05); + glClear_nid_postfix(0x4400); + glReadPixels_nid_postfix(0, 0, 4, 3, 0x1908, 0x1401, data.data()); + Require(data[5 * 4] == 255 && data[5 * 4 + 1] == 255 && data[5 * 4 + 3] == 255); + Require(data[1] == 0); + Error(0); + glDisable_nid_postfix(0x0c11); + glColorMask_nid_postfix(1, 1, 1, 1); + glStencilMask_nid_postfix(0xffffffff); + + GLuint texture, framebuffer; + glGenTextures_nid_postfix(1, &texture); + Require(!glIsTexture_nid_postfix(texture)); + glBindTexture_nid_postfix(0x0de1, texture); + Require(glIsTexture_nid_postfix(texture)); + GLint parameter; + glGetTexParameteriv_nid_postfix(0x0de1, 0x2801, ¶meter); + Require(parameter == 0x2702); + glCopyTexImage2D_nid_postfix(0x0de1, 0, 0x1908, 0, 0, 4, 3, 0); + glGenFramebuffers_nid_postfix(1, &framebuffer); + Require(!glIsFramebuffer_nid_postfix(framebuffer)); + glBindFramebuffer_nid_postfix(0x8d40, framebuffer); + Require(glCheckFramebufferStatus_nid_postfix(0x8d40) == 0x8cd7); + glClear_nid_postfix(0x4000); + Error(0x0506); + glFramebufferTexture2D_nid_postfix(0x8d40, 0x8ce0, 0x0de1, texture, 0); + Require(glCheckFramebufferStatus_nid_postfix(0x8d40) == 0x8cd5); + std::array copied{}; + glReadPixels_nid_postfix(0, 0, 4, 3, 0x1908, 0x1401, copied.data()); + Require(copied == data); + const unsigned char patch[]{12, 34, 56, 78}; + glTexSubImage2D_nid_postfix(0x0de1, 0, 2, 1, 1, 1, 0x1908, 0x1401, patch); + std::array pixel{}; + glReadPixels_nid_postfix(2, 1, 1, 1, 0x1908, 0x1401, pixel.data()); + Require((pixel == std::array{12, 34, 56, 78})); + glTexSubImage2D_nid_postfix(0x0de1, 0, 4, 0, 1, 1, 0x1908, 0x1401, patch); + Error(0x0501); + glBindTexture_nid_postfix(0x8513, texture); + Error(0x0502); + + GLuint renderbuffer; + glGenRenderbuffers_nid_postfix(1, &renderbuffer); + glBindRenderbuffer_nid_postfix(0x8d41, renderbuffer); + glRenderbufferStorage_nid_postfix(0x8d41, 0x81a5, 2, 2); + glFramebufferRenderbuffer_nid_postfix(0x8d40, 0x8d00, 0x8d41, renderbuffer); + Require(glCheckFramebufferStatus_nid_postfix(0x8d40) == 0x8cd9); + glRenderbufferStorage_nid_postfix(0x8d41, 0x81a5, 4, 3); + Require(glCheckFramebufferStatus_nid_postfix(0x8d40) == 0x8cd5); + glDeleteRenderbuffers_nid_postfix(1, &renderbuffer); + glDeleteTextures_nid_postfix(1, &texture); + Require(Integer(0x8069) == 0); + Require(glCheckFramebufferStatus_nid_postfix(0x8d40) == 0x8cd7); + glDeleteFramebuffers_nid_postfix(1, &framebuffer); + Require(Integer(0x8ca6) == 0); + Error(0); +} + +static void Shaders() { + GLuint shader = glCreateShader_nid_postfix(0x8b31); + GLuint program = glCreateProgram_nid_postfix(); + Require(shader && program && shader != program); + const char first[]{'a', 0, 'b'}; + const char* strings[]{first, "tail"}; + GLint lengths[]{3, -1}; + glShaderSource_nid_postfix(shader, 2, strings, lengths); + GLint sourceLength = 0; + glGetShaderiv_nid_postfix(shader, 0x8b88, &sourceLength); + Require(sourceLength == 8); + GLint status = 1; + glGetShaderiv_nid_postfix(shader, 0x8b81, &status); + Require(status == 0); + glAttachShader_nid_postfix(program, shader); + glAttachShader_nid_postfix(program, shader); + Error(0x0502); + GLuint duplicateStage = glCreateShader_nid_postfix(0x8b31); + glAttachShader_nid_postfix(program, duplicateStage); + Error(0x0502); + glDeleteShader_nid_postfix(duplicateStage); + glDeleteShader_nid_postfix(shader); + Require(glIsShader_nid_postfix(shader)); + glGetShaderiv_nid_postfix(shader, 0x8b80, &status); + Require(status == 1); + glDetachShader_nid_postfix(program, shader); + Require(!glIsShader_nid_postfix(shader)); + glUseProgram_nid_postfix(program); + Error(0x0502); + glDeleteProgram_nid_postfix(program); + Require(!glIsProgram_nid_postfix(program)); +} + +static void TextureFormats() { + GLuint texture; + glGenTextures_nid_postfix(1, &texture); + glBindTexture_nid_postfix(0x0de1, texture); + glPixelStorei_nid_postfix(0x0cf5, 8); + const unsigned char pixels[]{255, 0, 0, 0, 255, 0, 99, 99, 0, 0, 255, 255, 255, 255, 99, 99}; + glTexImage2D_nid_postfix(0x0de1, 0, 0x1907, 2, 2, 0, 0x1907, 0x1401, pixels); + glGenerateMipmap_nid_postfix(0x0de1); + Error(0); + GLuint framebuffer; + glGenFramebuffers_nid_postfix(1, &framebuffer); + glBindFramebuffer_nid_postfix(0x8d40, framebuffer); + glFramebufferTexture2D_nid_postfix(0x8d40, 0x8ce0, 0x0de1, texture, 0); + Require(glCheckFramebufferStatus_nid_postfix(0x8d40) == 0x8cd5); + glPixelStorei_nid_postfix(0x0d05, 1); + std::array pixel{}; + glReadPixels_nid_postfix(0, 1, 1, 1, 0x1908, 0x1401, pixel.data()); + Require((pixel == std::array{0, 0, 255, 255})); + glReadPixels_nid_postfix(1, 1, 1, 1, 0x1908, 0x1401, pixel.data()); + Require((pixel == std::array{255, 255, 255, 255})); + const std::uint16_t packed = 0xf800; + glTexImage2D_nid_postfix(0x0de1, 0, 0x1907, 1, 1, 0, 0x1907, 0x8363, &packed); + glReadPixels_nid_postfix(0, 0, 1, 1, 0x1908, 0x1401, pixel.data()); + Require((pixel == std::array{255, 0, 0, 255})); + const unsigned char luminance[]{17, 42}; + glTexImage2D_nid_postfix(0x0de1, 0, 0x190a, 1, 1, 0, 0x190a, 0x1401, luminance); + Require(glCheckFramebufferStatus_nid_postfix(0x8d40) == 0x8cd6); + glBindFramebuffer_nid_postfix(0x8d40, 0); + glDeleteFramebuffers_nid_postfix(1, &framebuffer); + glPixelStorei_nid_postfix(0x0d05, 4); + Error(0); + glTexImage2D_nid_postfix(0x0de1, 0, 0x1908, 3, 2, 0, 0x1908, 0x1401, nullptr); + glGenerateMipmap_nid_postfix(0x0de1); + Error(0x0502); + glTexImage2D_nid_postfix(0x0de1, 0, 0x1908, 1, 1, 0, 0x1908, 0x8363, &packed); + Error(0x0502); + glTexImage2D_nid_postfix(0x0de1, 0, 0, 1, 1, 0, 0x1908, 0x1401, nullptr); + Error(0x0501); + glTexImage2D_nid_postfix(0x0de1, 1, 0x1908, 3, 2, 0, 0x1908, 0x1401, nullptr); + Error(0x0501); + glDeleteTextures_nid_postfix(1, &texture); + glPixelStorei_nid_postfix(0x0cf5, 4); + Error(0); +} + +int main() { + Require(eglGetCurrentContext_nid_postfix() == nullptr); + Require(eglQueryAPI_nid_postfix() == 0x30a0); + Require(eglGetError_nid_postfix() == 0x3000); + EGLDisplay display = eglGetDisplay_nid_postfix(nullptr); + EGLConfig config{}; + EGLint count; + Require(!eglGetConfigs_nid_postfix(display, &config, 1, &count)); + Require(eglGetError_nid_postfix() == 0x3001); + EGLint major, minor; + Require(eglInitialize_nid_postfix(display, &major, &minor)); + Require(major == 1 && minor == 4); + Require(eglGetConfigs_nid_postfix(display, &config, 1, &count) && count == 1); + EGLint attribute; + Require(eglGetConfigAttrib_nid_postfix(display, config, 0x3029, &attribute) && attribute == 0); + Require(eglGetConfigAttrib_nid_postfix(display, config, 0x302c, &attribute) && attribute == 2048); + Require(eglGetConfigAttrib_nid_postfix(display, config, 0x3042, &attribute) && attribute == 0); + const EGLint criteria[]{0x3033, 1, 0x3040, 4, 0x3038}; + Require(eglChooseConfig_nid_postfix(display, criteria, &config, 1, &count) && count == 1); + Require(eglChooseConfig_nid_postfix(display, nullptr, nullptr, 0, &count) && count == 0); + Require(!eglCreateContext_nid_postfix(display, config, nullptr, nullptr)); + Require(eglGetError_nid_postfix() == 0x3005); + const EGLint contextAttributes[]{0x3098, 2, 0x3038}; + const EGLint surfaceAttributes[]{0x3057, 4, 0x3056, 3, 0x3038}; + EGLContext context = eglCreateContext_nid_postfix(display, config, nullptr, contextAttributes); + EGLSurface surface = eglCreatePbufferSurface_nid_postfix(display, config, surfaceAttributes); + Require(context && surface); + Require(eglQueryContext_nid_postfix(display, context, 0x3086, &attribute) && attribute == 0x3038); + const EGLint invalidSurface[]{0x3057, -1, 0x3038}; + Require(!eglCreatePbufferSurface_nid_postfix(display, config, invalidSurface)); + Require(eglGetError_nid_postfix() == 0x300c); + Require(eglMakeCurrent_nid_postfix(display, surface, surface, context)); + Defaults(); + Buffers(); + Pixels(); + TextureFormats(); + Shaders(); + GLuint sharedBuffer; + glGenBuffers_nid_postfix(1, &sharedBuffer); + glBindBuffer_nid_postfix(0x8892, sharedBuffer); + EGLContext shared = eglCreateContext_nid_postfix(display, config, context, contextAttributes); + EGLSurface otherSurface = eglCreatePbufferSurface_nid_postfix(display, config, surfaceAttributes); + Require(eglSwapBuffers_nid_postfix(display, surface)); + Require(!eglSwapBuffers_nid_postfix(display, otherSurface)); + Require(eglGetError_nid_postfix() == 0x300d); + std::thread worker([&] { + Require(!eglSwapBuffers_nid_postfix(display, otherSurface)); + Require(eglGetError_nid_postfix() == 0x300d); + Require(!eglGetCurrentContext_nid_postfix()); + Require(!eglMakeCurrent_nid_postfix(display, surface, surface, context)); + Require(eglGetError_nid_postfix() == 0x3002); + Require(eglMakeCurrent_nid_postfix(display, otherSurface, otherSurface, shared)); + Require(glIsBuffer_nid_postfix(sharedBuffer)); + Require(Integer(0x8894) == 0); + glEnable_nid_postfix(0x0be2); + Require(eglReleaseThread_nid_postfix()); + }); + worker.join(); + Require(!glIsEnabled_nid_postfix(0x0be2)); + Require(eglGetError_nid_postfix() == 0x3000); + Require(eglDestroyContext_nid_postfix(display, shared)); + Require(eglDestroySurface_nid_postfix(display, otherSurface)); + Require(eglDestroyContext_nid_postfix(display, context)); + Require(eglDestroySurface_nid_postfix(display, surface)); + Require(eglGetCurrentContext_nid_postfix() == context); + glClear_nid_postfix(0x4000); + Error(0); + Require(eglReleaseThread_nid_postfix()); + Require(!eglGetCurrentContext_nid_postfix()); + Require(!eglDestroyContext_nid_postfix(display, context)); + Require(eglGetError_nid_postfix() == 0x3006); + Require(eglTerminate_nid_postfix(display)); + std::puts("GLSlim state tests passed"); +} diff --git a/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp b/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp index bc1b64b7b7..11c37236a8 100644 --- a/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp +++ b/core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp @@ -117,6 +117,10 @@ std::uint32_t* UpdatePsRegisters(std::uint32_t* cmdbuf, const std::uint32_t* reg extern "C" { +int APS5_VABI sceGnmIsDebugResetAvailable() { + 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; @@ -348,8 +352,8 @@ int APS5_VABI sceGnmDrawInitToDefaultContextStateInternalCommand(std::uint32_t* } int APS5_VABI sceGnmDrawInitToDefaultContextStateInternalSize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (CompiledSdkVersion() < ContextState400SdkVersion) return 0x20; + return 0x100; } int APS5_VABI sceGnmDrawOpaqueAuto(void) { diff --git a/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp b/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp index eb8f0bfa36..75fa7ac5a1 100644 --- a/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp +++ b/core/libs/prx/libSceGnmDriverForNeoMode/include/Api.hpp @@ -4,6 +4,8 @@ extern "C" { +int APS5_VABI sceGnmIsDebugResetAvailable(); + 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); diff --git a/core/libs/prx/libSceGpuTrace/Export.cpp b/core/libs/prx/libSceGpuTrace/Export.cpp index d81f4a5e32..e518015e73 100644 --- a/core/libs/prx/libSceGpuTrace/Export.cpp +++ b/core/libs/prx/libSceGpuTrace/Export.cpp @@ -1,10 +1,49 @@ -#include +#include "prx/libSceGpuTrace/include/Api.hpp" #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceGpuTraceParametersSetDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceGpuTraceParametersInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceGpuTraceParametersSetMemorySize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceGpuTraceParametersSetGroup(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceGpuTraceStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceGpuTraceStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceGpuTraceMonitorTrigger(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceGpuTraceCancel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceGpuTrace/include/Api.hpp b/core/libs/prx/libSceGpuTrace/include/Api.hpp new file mode 100644 index 0000000000..411bb3aa7f --- /dev/null +++ b/core/libs/prx/libSceGpuTrace/include/Api.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceGpuTraceParametersSetDuration(void); +int APS5_VABI sceGpuTraceParametersInit(void); +int APS5_VABI sceGpuTraceParametersSetMemorySize(void); +int APS5_VABI sceGpuTraceParametersSetGroup(void); +int APS5_VABI sceGpuTraceStart(void); +int APS5_VABI sceGpuTraceStop(void); +int APS5_VABI sceGpuTraceMonitorTrigger(void); +int APS5_VABI sceGpuTraceCancel(void); + +} diff --git a/core/libs/prx/libSceGpuTrace/libSceGpuTrace.hpp b/core/libs/prx/libSceGpuTrace/libSceGpuTrace.hpp index 6f70f09bee..60cc36c983 100644 --- a/core/libs/prx/libSceGpuTrace/libSceGpuTrace.hpp +++ b/core/libs/prx/libSceGpuTrace/libSceGpuTrace.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libSceGpuTrace/include/Api.hpp" diff --git a/core/libs/prx/libSceHmd2/CMakeLists.txt b/core/libs/prx/libSceHmd2/CMakeLists.txt index ee0e0ffd8c..8c6c0c183d 100644 --- a/core/libs/prx/libSceHmd2/CMakeLists.txt +++ b/core/libs/prx/libSceHmd2/CMakeLists.txt @@ -26,3 +26,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_hmd2_gaze_args_tests tests/GazeArgumentChecks.cpp) + target_include_directories(guest_hmd2_gaze_args_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_hmd2_gaze_args_tests PRIVATE libSceHmd2 libc) + configure_windows_unwind(guest_hmd2_gaze_args_tests) + add_test(NAME guest_hmd2_gaze_args COMMAND guest_hmd2_gaze_args_tests) + set_tests_properties(guest_hmd2_gaze_args PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_hmd2_gaze_args 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 ffaa366f13..78fe4c0b2c 100644 --- a/core/libs/prx/libSceHmd2/Export.cpp +++ b/core/libs/prx/libSceHmd2/Export.cpp @@ -80,6 +80,10 @@ std::mutex stateMutex; bool initialized = false; std::int32_t openHandle = 0; +std::mutex reprojectionMutex; +Hmd2::ReprojectionState* reprojectionState = nullptr; +std::uint8_t allowPositionalReprojection = 0; + std::int32_t BeforeInitialize(std::int32_t answer, const char* name) { std::lock_guard lock(stateMutex); if (initialized) NotImplemented_nid_no_patch(name); @@ -233,12 +237,21 @@ int APS5_VABI sceHmd2ReprojectionDisableVrMode() { } int APS5_VABI sceHmd2ReprojectionEnableMirroring(const Hmd2::MirroringParameter* parameter) { - return Hmd2::ReprojectionNotInitialized; + { + std::lock_guard lock(reprojectionMutex); + if (reprojectionState == nullptr) return Hmd2::ReprojectionNotInitialized; + } + NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceHmd2ReprojectionEnableVrMode(std::uint64_t mode) { - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard lock(reprojectionMutex); + if (reprojectionState == nullptr) return Hmd2::ReprojectionNotInitialized; + if (mode != Hmd2::OutputMode89_91Hz && mode != Hmd2::OutputMode119_88Hz) { + return Hmd2::VideoOutInvalidValue; + } + return Hmd2::VideoOutNoDevice; } Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionGetMirroringDisplayBufferSizeAlign() { @@ -250,17 +263,43 @@ Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionGetMirroringWorkMemorySizeAlign() { } int APS5_VABI sceHmd2ReprojectionGetPredictedDisplayTime(std::uint64_t frameId, std::uint64_t* time) { - return Hmd2::ReprojectionNotInitialized; + { + std::lock_guard lock(reprojectionMutex); + if (reprojectionState == nullptr) return Hmd2::ReprojectionNotInitialized; + } + if (time == nullptr) return Hmd2::ParameterNull; + NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceHmd2ReprojectionInitialize(const Hmd2::ReprojectionInitializeParameter* parameter, const void* reserved) { - NotImplemented_nid_no_patch(__func__); + (void)reserved; + std::lock_guard lock(reprojectionMutex); + if (reprojectionState != nullptr) return Hmd2::ReprojectionAlreadyInitialized; + if (parameter == nullptr || parameter->workMemory == nullptr || parameter->displayMemory == nullptr) { + return Hmd2::ParameterNull; + } + if (parameter->auxiliaryMemory != nullptr) { + throw std::runtime_error("sceHmd2ReprojectionInitialize: auxiliary memory not supported"); + } + auto* state = static_cast(parameter->workMemory); + state->outputMode = 0; + state->timingUs = parameter->timingInterval != 0 + ? parameter->timingInterval + : Hmd2::ReprojectionTimingDefault; + reprojectionState = state; return 0; } Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionQueryBufferSizeAlign() { - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard lock(reprojectionMutex); + if (allowPositionalReprojection == 0) { + return Hmd2::SizeAlign{Hmd2::ReprojectionBufferSize, Hmd2::ReprojectionBufferAlign}; + } + return Hmd2::SizeAlign{Hmd2::ReprojectionPositionalBufferSize + + static_cast(allowPositionalReprojection) + * Hmd2::ReprojectionPositionalBufferSizeStep, + Hmd2::ReprojectionPositionalBufferAlign}; } Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionQueryDisplayBufferSizeAlign() { @@ -268,7 +307,9 @@ Hmd2::SizeAlign APS5_VABI sceHmd2ReprojectionQueryDisplayBufferSizeAlign() { } int APS5_VABI sceHmd2ReprojectionSetAllowPositionalReprojection(std::uint8_t mode) { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(reprojectionMutex); + if (mode > Hmd2::AllowPositionalReprojectionMax) return Hmd2::ParameterInvalid; + allowPositionalReprojection = mode; return 0; } @@ -288,16 +329,30 @@ int APS5_VABI sceHmd2ReprojectionSetParamWithBuffer(std::uint32_t bufferSetId, s } int APS5_VABI sceHmd2ReprojectionSetRenderConfig(std::uint64_t flags, std::uint64_t reserved, std::uint64_t reserved2) { - return Hmd2::ReprojectionNotInitialized; -} - -int APS5_VABI sceHmd2ReprojectionSetTiming(std::int32_t timing) { + { + std::lock_guard lock(reprojectionMutex); + if (reprojectionState == nullptr) return Hmd2::ReprojectionNotInitialized; + } + if ((flags & ~(1ull | (1ull << 32))) != 0 || reserved != 0 || reserved2 != 0) return Hmd2::ParameterInvalid; NotImplemented_nid_no_patch(__func__); return 0; } +int APS5_VABI sceHmd2ReprojectionSetTiming(std::int32_t timing) { + std::lock_guard lock(reprojectionMutex); + if (reprojectionState == nullptr) return Hmd2::ReprojectionNotInitialized; + if (timing < Hmd2::ReprojectionTimingMin || timing > Hmd2::ReprojectionTimingMax) { + return Hmd2::ParameterInvalid; + } + const auto rounded = static_cast((timing + 99) / 100 * 100); + if (rounded == reprojectionState->timingUs) return 0; + reprojectionState->timingUs = rounded; + return 0; +} + int APS5_VABI sceHmd2ReprojectionTerminate() { - NotImplemented_nid_no_patch(__func__); + std::lock_guard lock(reprojectionMutex); + reprojectionState = nullptr; return 0; } diff --git a/core/libs/prx/libSceHmd2/Hmd2.hpp b/core/libs/prx/libSceHmd2/Hmd2.hpp index 4623331f91..0bcdbf35f6 100644 --- a/core/libs/prx/libSceHmd2/Hmd2.hpp +++ b/core/libs/prx/libSceHmd2/Hmd2.hpp @@ -16,6 +16,9 @@ inline constexpr std::int32_t ParameterNull = static_cast(0x8a7200 inline constexpr std::int32_t ParameterInvalid = static_cast(0x8a720009); inline constexpr std::int32_t Error0004 = static_cast(0x8a720004); inline constexpr std::int32_t ReprojectionNotInitialized = static_cast(0x8a72000b); +inline constexpr std::int32_t ReprojectionAlreadyInitialized = static_cast(0x8a72000c); +inline constexpr std::int32_t VideoOutInvalidValue = static_cast(0x80290001); +inline constexpr std::int32_t VideoOutNoDevice = static_cast(0x80290018); inline constexpr std::int32_t Error002C = static_cast(0x8a72002c); inline constexpr std::int32_t Error1002 = static_cast(0x8a721002); inline constexpr std::int32_t Error1003 = static_cast(0x8a721003); @@ -25,6 +28,19 @@ inline constexpr std::int32_t Error100F = static_cast(0x8a72100f); inline constexpr std::uint32_t UserIdSdkVersion = 0x07000000; +inline constexpr std::uint64_t OutputMode89_91Hz = 0x2000c; +inline constexpr std::uint64_t OutputMode119_88Hz = 0x2000f; +inline constexpr std::int32_t ReprojectionTimingMin = 2000; +inline constexpr std::int32_t ReprojectionTimingMax = 7000; +inline constexpr std::uint32_t ReprojectionTimingDefault = 3000; + +inline constexpr std::uint64_t ReprojectionBufferSize = 0x100828; +inline constexpr std::uint64_t ReprojectionBufferAlign = 0x100; +inline constexpr std::int32_t AllowPositionalReprojectionMax = 5; +inline constexpr std::uint64_t ReprojectionPositionalBufferSize = 0x1103000; +inline constexpr std::uint64_t ReprojectionPositionalBufferSizeStep = 0x1000000; +inline constexpr std::uint64_t ReprojectionPositionalBufferAlign = 0x1000; + enum class DeviceStatus : std::uint32_t { NotDetected = 2, }; @@ -57,6 +73,15 @@ static_assert(offsetof(DeviceInformation, hmuMount) == 20); static_assert(offsetof(DeviceInformation, virtualImageDistance) == 24); static_assert(sizeof(FieldOfView) == 16); +struct ReprojectionState { + std::uint64_t outputMode; + std::uint32_t timingUs; +}; + +static_assert(sizeof(ReprojectionState) == 16); +static_assert(alignof(ReprojectionState) == 8); +static_assert(offsetof(ReprojectionState, timingUs) == 8); + struct SizeAlign { std::uint64_t size; std::uint64_t align; diff --git a/core/libs/prx/libSceHmd2/tests/GazeArgumentChecks.cpp b/core/libs/prx/libSceHmd2/tests/GazeArgumentChecks.cpp new file mode 100644 index 0000000000..9a06d93385 --- /dev/null +++ b/core/libs/prx/libSceHmd2/tests/GazeArgumentChecks.cpp @@ -0,0 +1,74 @@ +#include "prx/libSceHmd2/include/Api.hpp" +#include "prx/libSceHmd2/Hmd2.hpp" + +#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); + +constexpr std::uint8_t Sentinel = 0x5a; +constexpr std::size_t ProbeSize = 16 * 1024; + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "FAIL: %s\n", what); + std::abort(); +} + +struct Probe { + unsigned char* bytes; + Probe() : bytes(new unsigned char[ProbeSize]) { + for (std::size_t index = 0; index < ProbeSize; ++index) bytes[index] = Sentinel; + } + ~Probe() { delete[] bytes; } + bool Untouched() const { + for (std::size_t index = 0; index < ProbeSize; ++index) { + if (bytes[index] != Sentinel) return false; + } + return true; + } +}; + +} + +int main() { + Probe first; + Probe second; + Require(sceHmd2GazeObjectSelectionQueryMemory(nullptr, nullptr) == ParameterNull, "query memory with null arguments"); + Require(sceHmd2GazeObjectSelectionQueryMemory(nullptr, reinterpret_cast(second.bytes)) == ParameterNull, + "query memory with a null parameter"); + Require(second.Untouched(), "query memory left the output buffer untouched with a null parameter"); + Require(sceHmd2GazeObjectSelectionQueryMemory(reinterpret_cast(first.bytes), nullptr) == ParameterNull, + "query memory with a null info"); + Require(first.Untouched(), "query memory left the parameter buffer untouched with a null info"); + Require(sceHmd2GazeObjectSelectionQueryMemory(reinterpret_cast(first.bytes), + reinterpret_cast(second.bytes)) == ParameterInvalid, + "query memory before initialization with present arguments"); + Require(first.Untouched() && second.Untouched(), "query memory wrote nothing before initialization"); + + Require(sceHmd2GazeObjectSelectionGetSelectedObject(nullptr, nullptr) == ParameterNull, "get selected object with null arguments"); + Require(sceHmd2GazeObjectSelectionGetSelectedObject(nullptr, reinterpret_cast(second.bytes)) == ParameterNull, + "get selected object with a null parameter"); + Require(sceHmd2GazeObjectSelectionGetSelectedObject(reinterpret_cast(first.bytes), nullptr) == ParameterNull, + "get selected object with a null result"); + Require(sceHmd2GazeObjectSelectionGetSelectedObject(reinterpret_cast(first.bytes), + reinterpret_cast(second.bytes)) == ParameterInvalid, + "get selected object before initialization with present arguments"); + Require(first.Untouched() && second.Untouched(), "get selected object wrote nothing before initialization"); + + Require(sceHmd2GazeGetCalibrationDataMaxSize(0, nullptr) == ParameterNull, "calibration data max size with a null output"); + Require(sceHmd2GazeGetCalibrationDataMaxSize(0, reinterpret_cast(first.bytes)) == ParameterInvalid, + "calibration data max size before initialization with a present output"); + Require(first.Untouched(), "calibration data max size wrote nothing before initialization"); + + Require(sceHmd2GazeInsertRawDataWithHandle(0, nullptr) == ParameterNull, "insert raw data with a null buffer"); + Require(sceHmd2GazeInsertRawDataWithHandle(0, reinterpret_cast(first.bytes)) == InvalidHandle, + "insert raw data before initialization with a present buffer"); + Require(first.Untouched(), "insert raw data wrote nothing before initialization"); + return 0; +} diff --git a/core/libs/prx/libSceHmdSetupDialog/Export.cpp b/core/libs/prx/libSceHmdSetupDialog/Export.cpp index 156643bf24..a5985d5a03 100644 --- a/core/libs/prx/libSceHmdSetupDialog/Export.cpp +++ b/core/libs/prx/libSceHmdSetupDialog/Export.cpp @@ -32,11 +32,12 @@ int APS5_VABI sceHmdSetupDialogInitialize(void) { } 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; + int status = g_status.load(); + do { + 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; + } while (!g_status.compare_exchange_weak(status, COMMON_DIALOG_STATUS_FINISHED)); return 0; } @@ -59,8 +60,10 @@ int APS5_VABI sceHmdSetupDialogGetResult(void* result) { } 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; + int status = g_status.load(); + do { + if (status == COMMON_DIALOG_STATUS_NONE) return COMMON_DIALOG_ERROR_NOT_INITIALIZED; + } while (!g_status.compare_exchange_weak(status, COMMON_DIALOG_STATUS_FINISHED)); return 0; } diff --git a/core/libs/prx/libSceHttp/CMakeLists.txt b/core/libs/prx/libSceHttp/CMakeLists.txt index ab630f375c..c055b5b40a 100644 --- a/core/libs/prx/libSceHttp/CMakeLists.txt +++ b/core/libs/prx/libSceHttp/CMakeLists.txt @@ -1,6 +1,9 @@ -add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Options.cpp src/Parse.cpp src/Uri.cpp) +add_library(libSceHttp SHARED EXCLUDE_FROM_ALL Export.cpp src/Options.cpp src/Parse.cpp src/Uri.cpp src/Client.cpp) target_include_directories(libSceHttp PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceHttp) +if(WIN32) + target_link_libraries(libSceHttp PRIVATE ws2_32) +endif() if(BUILD_TESTING) add_test_executable(guest_http_tests ${LIBS_INCLUDE_DIR}/tests/GuestHttp.cpp) @@ -44,6 +47,22 @@ if(BUILD_TESTING) target_link_libraries(guest_http_options_tests PRIVATE libSceHttp libc) configure_windows_unwind(guest_http_options_tests) add_test(NAME guest_http_options COMMAND guest_http_options_tests) + find_package(Python3 COMPONENTS Interpreter QUIET) + if(Python3_Interpreter_FOUND) + add_test_executable(guest_http_client_tests ${LIBS_INCLUDE_DIR}/tests/GuestHttpClient.cpp) + target_include_directories(guest_http_client_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_http_client_tests PRIVATE libSceHttp libc) + configure_windows_unwind(guest_http_client_tests) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_http_client) + add_test(NAME guest_http_client COMMAND ${Python3_EXECUTABLE} + ${LIBS_INCLUDE_DIR}/tests/AvPlayerHttpServer.py $ client) + set_tests_properties(guest_http_client PROPERTIES + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/guest_http_client TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_http_client PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endif() if(WIN32) set_tests_properties(guest_http guest_http_uri_locale guest_http_response_header guest_http_options PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceHttp/include/Client.hpp b/core/libs/prx/libSceHttp/include/Client.hpp new file mode 100644 index 0000000000..f9bc0788cb --- /dev/null +++ b/core/libs/prx/libSceHttp/include/Client.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +struct Aps5HttpClient {}; + +extern "C" { + +Aps5HttpClient* APS5_VABI Aps5HttpClient_Create_nid_postfix(); +void APS5_VABI Aps5HttpClient_Destroy_nid_postfix(Aps5HttpClient* client); +std::int32_t APS5_VABI Aps5HttpClient_Open_nid_postfix(Aps5HttpClient* client, const char* url); +std::int32_t APS5_VABI Aps5HttpClient_Read_nid_postfix(Aps5HttpClient* client, std::uint8_t* destination, std::int32_t capacity); +std::int64_t APS5_VABI Aps5HttpClient_Seek_nid_postfix(Aps5HttpClient* client, std::int64_t offset, std::int32_t origin); +std::int64_t APS5_VABI Aps5HttpClient_GetSize_nid_postfix(const Aps5HttpClient* client); +void APS5_VABI Aps5HttpClient_Start_nid_postfix(Aps5HttpClient* client); +void APS5_VABI Aps5HttpClient_Stop_nid_postfix(Aps5HttpClient* client); + +} diff --git a/core/libs/prx/libSceHttp/libSceHttp.hpp b/core/libs/prx/libSceHttp/libSceHttp.hpp index 69e0774038..7e3ab1361b 100644 --- a/core/libs/prx/libSceHttp/libSceHttp.hpp +++ b/core/libs/prx/libSceHttp/libSceHttp.hpp @@ -1,3 +1,4 @@ #pragma once +#include "prx/libSceHttp/include/Client.hpp" #include "prx/libSceHttp/include/Http.hpp" diff --git a/core/libs/prx/libSceHttp/src/Client.cpp b/core/libs/prx/libSceHttp/src/Client.cpp new file mode 100644 index 0000000000..c9c65c91db --- /dev/null +++ b/core/libs/prx/libSceHttp/src/Client.cpp @@ -0,0 +1,513 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#include +#include +#include +#include +#endif + +#include "prx/libSceHttp/include/Client.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +class HttpFailure : public std::runtime_error { +public: + explicit HttpFailure(const char* message) : std::runtime_error(message) {} +}; + +std::string Lower(std::string text) { + for (char& byte : text) if (byte >= 'A' && byte <= 'Z') byte += 'a' - 'A'; + return text; +} + +std::string_view Trim(std::string_view text) { + while (!text.empty() && (text.front() == ' ' || text.front() == '\t')) text.remove_prefix(1); + while (!text.empty() && (text.back() == ' ' || text.back() == '\t')) text.remove_suffix(1); + return text; +} + +std::uint64_t Number(std::string_view text, int base = 10) { + std::uint64_t number = 0; + const auto result = std::from_chars(text.data(), text.data() + text.size(), number, base); + if (text.empty() || result.ec != std::errc{} || result.ptr != text.data() + text.size()) throw HttpFailure("invalid HTTP size"); + return number; +} + +std::optional HttpDate(const std::string& text) { + std::tm time{}; + std::istringstream input(text); + input.imbue(std::locale::classic()); + input >> std::get_time(&time, "%a, %d %b %Y %H:%M:%S GMT"); + if (input.fail() || input.peek() != std::char_traits::eof()) return {}; + const std::chrono::year_month_day day{std::chrono::year{time.tm_year + 1900}, std::chrono::month{static_cast(time.tm_mon + 1)}, std::chrono::day{static_cast(time.tm_mday)}}; + if (!day.ok() || time.tm_hour > 23 || time.tm_min > 59 || time.tm_sec > 60) return {}; + return std::chrono::sys_days{day} + std::chrono::hours{time.tm_hour} + std::chrono::minutes{time.tm_min} + std::chrono::seconds{time.tm_sec}; +} + +struct Url { + std::string authority; + std::string host; + std::string port; + std::string target; +}; + +Url Parse(const std::string& text) { + if (Lower(text.substr(0, 8)) == "https://") NotImplemented_nid_no_patch("HTTP client HTTPS sources"); + if (Lower(text.substr(0, 7)) != "http://") throw HttpFailure("unsupported HTTP source scheme"); + for (const unsigned char byte : text) { + if (byte >= 0x80) NotImplemented_nid_no_patch("HTTP client non-ASCII HTTP URLs"); + if (byte <= 0x20 || byte == 0x7f) throw HttpFailure("invalid HTTP URL byte"); + } + const auto end = text.find_first_of("/?#", 7); + Url url; + url.authority = text.substr(7, end == std::string::npos ? end : end - 7); + if (url.authority.find('@') != std::string::npos) NotImplemented_nid_no_patch("HTTP client HTTP URL user information"); + if (url.authority.empty()) throw HttpFailure("invalid HTTP authority"); + url.port = "80"; + std::size_t colon; + if (url.authority.front() == '[') { + const auto close = url.authority.find(']'); + if (close == std::string::npos) throw HttpFailure("invalid IPv6 HTTP authority"); + url.host = url.authority.substr(1, close - 1); + colon = close + 1; + if (colon < url.authority.size() && url.authority[colon] != ':') throw HttpFailure("invalid HTTP port"); + } else { + colon = url.authority.find(':'); + url.host = url.authority.substr(0, colon); + } + if (colon != std::string::npos && colon < url.authority.size()) { + url.port = url.authority.substr(colon + 1); + const auto port = Number(url.port); + if (port == 0 || port > 65535) throw HttpFailure("invalid HTTP port"); + } + if (url.host.empty()) throw HttpFailure("empty HTTP host"); + url.target = end == std::string::npos ? "/" : text.substr(end); + url.target.erase(url.target.find('#') == std::string::npos ? url.target.size() : url.target.find('#')); + if (url.target.empty() || url.target.front() != '/') url.target.insert(0, "/"); + return url; +} + +std::string Redirect(const Url& base, std::string location) { + const auto fragment = location.find('#'); + if (fragment != std::string::npos) location.erase(fragment); + if (location.find(':') < location.find_first_of("/?")) return location; + if (location.starts_with("//")) return "http:" + location; + std::string target; + if (location.empty()) target = base.target; + else if (location.front() == '/') target = location; + else if (location.front() == '?') target = base.target.substr(0, base.target.find('?')) + location; + else { + target = base.target.substr(0, base.target.find('?')); + target.erase(target.rfind('/') + 1); + target += location; + } + const auto queryAt = target.find('?'); + const std::string query = queryAt == std::string::npos ? "" : target.substr(queryAt); + std::string input = target.substr(0, queryAt); + std::string output; + while (!input.empty()) { + if (input.starts_with("../")) input.erase(0, 3); + else if (input.starts_with("./")) input.erase(0, 2); + else if (input.starts_with("/./")) input.erase(0, 2); + else if (input == "/.") input = "/"; + else if (input.starts_with("/../") || input == "/..") { + input.erase(0, 3); + if (input.empty()) input = "/"; + const auto last = output.rfind('/'); + output.erase(last == std::string::npos ? 0 : last); + } else if (input == "." || input == "..") input.clear(); + else { + const auto next = input.find('/', input.front() == '/' ? 1 : 0); + output += input.substr(0, next); + input.erase(0, next == std::string::npos ? input.size() : next); + } + } + return "http://" + base.authority + output + query; +} + +class Connection { +public: + Connection(const Url& url, std::atomic_bool& stop) : stop(stop) { + addrinfo hint{}; + hint.ai_socktype = SOCK_STREAM; + addrinfo* raw = nullptr; + if (getaddrinfo(url.host.c_str(), url.port.c_str(), &hint, &raw) != 0) throw HttpFailure("HTTP name resolution failed"); + const std::unique_ptr addresses(raw, freeaddrinfo); + for (auto* address = raw; address != nullptr; address = address->ai_next) { + socket = ::socket(address->ai_family, address->ai_socktype, address->ai_protocol); + if (socket == invalid) continue; +#ifdef _WIN32 + u_long enabled = 1; + if (ioctlsocket(socket, FIONBIO, &enabled) != 0) { Close(); continue; } +#else + if (fcntl(socket, F_SETFL, fcntl(socket, F_GETFL, 0) | O_NONBLOCK) < 0) { Close(); continue; } +#endif +#ifdef SO_NOSIGPIPE + const int enabled = 1; + setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof(enabled)); +#endif + if (::connect(socket, address->ai_addr, static_cast(address->ai_addrlen)) == 0) return; +#ifdef _WIN32 + const int connectError = WSAGetLastError(); + const bool pending = connectError == WSAEWOULDBLOCK || connectError == WSAEINPROGRESS || connectError == WSAEINTR; +#else + const bool pending = errno == EINPROGRESS || errno == EINTR; +#endif + if (pending) { + try { + Wait(true); + int error = 0; +#ifdef _WIN32 + int length = sizeof(error); + if (getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &length) == 0 && error == 0) return; +#else + socklen_t length = sizeof(error); + if (getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, &length) == 0 && error == 0) return; +#endif + } catch (...) { + Close(); + throw; + } + } + Close(); + } + throw HttpFailure("HTTP connection failed"); + } + + ~Connection() { + Close(); + } + + void Send(const std::string& data) { + std::size_t sent = 0; + while (sent < data.size()) { + Wait(true); +#ifdef MSG_NOSIGNAL + constexpr int flags = MSG_NOSIGNAL; +#else + constexpr int flags = 0; +#endif + const auto length = static_cast(std::min(data.size() - sent, std::numeric_limits::max())); + const auto result = ::send(socket, data.data() + sent, length, flags); + if (result < 0 && Retry()) continue; + if (result <= 0) throw HttpFailure("HTTP request failed"); + sent += static_cast(result); + } + } + + std::string Line() { + for (;;) { + const auto end = pending.find("\r\n"); + if (end != std::string::npos) { + auto line = pending.substr(0, end); + pending.erase(0, end + 2); + return line; + } + if (!Receive()) throw HttpFailure("truncated HTTP headers"); + } + } + + void Append(std::vector& body, std::uint64_t size) { + while (size != 0) { + if (pending.empty() && !Receive()) throw HttpFailure("truncated HTTP body"); + const auto take = static_cast(std::min(size, pending.size())); + body.insert(body.end(), pending.begin(), pending.begin() + static_cast(take)); + pending.erase(0, take); + size -= take; + } + } + + void UntilClosed(std::vector& body) { + do { + body.insert(body.end(), pending.begin(), pending.end()); + pending.clear(); + } while (Receive()); + } + +private: + bool Receive() { + std::array bytes{}; + for (;;) { + Wait(false); + const auto count = ::recv(socket, bytes.data(), static_cast(bytes.size()), 0); + if (count < 0 && Retry()) continue; + if (count < 0) throw HttpFailure("HTTP read failed"); + pending.append(bytes.data(), static_cast(count)); + return count != 0; + } + } + + static bool Retry() { +#ifdef _WIN32 + const int error = WSAGetLastError(); + return error == WSAEWOULDBLOCK || error == WSAEINTR; +#else + return errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR; +#endif + } + + void Wait(bool write) { + for (;;) { + if (stop.load()) throw HttpFailure("HTTP source stopped"); +#ifdef _WIN32 + WSAPOLLFD entry{socket, static_cast(write ? POLLWRNORM : POLLRDNORM), 0}; + const int result = WSAPoll(&entry, 1, 100); +#else + pollfd entry{socket, static_cast(write ? POLLOUT : POLLIN), 0}; + const int result = poll(&entry, 1, 100); +#endif + if (result > 0) return; + if (result < 0 && !Retry()) throw HttpFailure("HTTP socket polling failed"); + } + } + + void Close() { + if (socket == invalid) return; +#ifdef _WIN32 + closesocket(socket); +#else + ::close(socket); +#endif + socket = invalid; + } +#ifdef _WIN32 + struct Runtime { + Runtime() { + WSADATA data{}; + if (WSAStartup(MAKEWORD(2, 2), &data) != 0) throw HttpFailure("socket initialization failed"); + } + ~Runtime() { WSACleanup(); } + } runtime; + static constexpr SOCKET invalid = INVALID_SOCKET; + SOCKET socket = invalid; +#else + static constexpr int invalid = -1; + int socket = invalid; +#endif + std::string pending; + std::atomic_bool& stop; +}; + +struct HttpClientState : Aps5HttpClient { + std::string url; + std::string validator; + std::string etag; + std::string modified; + std::vector cache; + std::uint64_t cacheStart = 0; + std::uint64_t position = 0; + std::uint64_t size = 0; + bool initialized = false; + std::atomic_bool stopped = false; + + void Reset(const char* source) { + url = source; + validator.clear(); + etag.clear(); + modified.clear(); + cache.clear(); + cacheStart = 0; + position = 0; + size = 0; + initialized = false; + } + + void CheckStopped() const { + if (stopped.load()) throw HttpFailure("HTTP client stopped"); + } + + void Fetch(std::uint64_t start, std::uint64_t length) { + CheckStopped(); + std::set visited; + for (;;) { + if (!visited.insert(url).second) throw HttpFailure("HTTP redirect loop"); + const auto parsed = Parse(url); + Connection connection(parsed, stopped); + connection.Send("GET " + parsed.target + " HTTP/1.1\r\nHost: " + parsed.authority + "\r\nRange: bytes=" + std::to_string(start) + "-" + std::to_string(start + length - 1) + (validator.empty() ? "" : "\r\nIf-Range: " + validator) + "\r\nAccept-Encoding: identity\r\nConnection: close\r\n\r\n"); + std::map headers; + int status; + for (;;) { + const auto line = connection.Line(); + if (line.size() < 12 || (!line.starts_with("HTTP/1.1 ") && !line.starts_with("HTTP/1.0 "))) throw HttpFailure("invalid HTTP status line"); + status = static_cast(Number(std::string_view(line).substr(9, 3))); + headers.clear(); + for (;;) { + const auto field = connection.Line(); + if (field.empty()) break; + const auto colon = field.find(':'); + if (colon == std::string::npos || colon == 0) throw HttpFailure("invalid HTTP header"); + const auto name = Lower(field.substr(0, colon)); + const std::string value(Trim(std::string_view(field).substr(colon + 1))); + for (const unsigned char byte : value) if ((byte < 0x20 && byte != '\t') || byte == 0x7f) throw HttpFailure("invalid HTTP header value"); + if (name != "content-range" && name != "content-length" && name != "content-encoding" && name != "transfer-encoding" && name != "location" && name != "etag" && name != "last-modified" && name != "date") continue; + const auto [found, inserted] = headers.emplace(name, value); + if (!inserted && found->second != value) throw HttpFailure("conflicting HTTP headers"); + } + if (status < 100 || status >= 200) break; + if (status == 101) throw HttpFailure("HTTP upgrade is not a media file"); + } + if (status == 301 || status == 302 || status == 303 || status == 307 || status == 308) { + if (!headers.contains("location")) throw HttpFailure("HTTP redirect has no location"); + url = Redirect(parsed, headers.at("location")); + continue; + } + if (status != 200 && status != 206) throw HttpFailure("HTTP source request was refused"); + if (headers.contains("content-encoding") && Lower(headers.at("content-encoding")) != "identity") NotImplemented_nid_no_patch("HTTP client encoded HTTP representations"); + std::vector body; + if (headers.contains("transfer-encoding")) { + if (Lower(headers.at("transfer-encoding")) != "chunked") NotImplemented_nid_no_patch("HTTP client HTTP transfer coding"); + for (;;) { + const auto chunk = connection.Line(); + const auto count = Number(Trim(std::string_view(chunk).substr(0, chunk.find(';'))), 16); + if (count == 0) { + while (!connection.Line().empty()) {} + break; + } + connection.Append(body, count); + if (!connection.Line().empty()) throw HttpFailure("invalid HTTP chunk terminator"); + } + } else if (headers.contains("content-length")) connection.Append(body, Number(headers.at("content-length"))); + else connection.UntilClosed(body); + std::uint64_t total = body.size(); + std::uint64_t first = 0; + if (status == 206) { + if (!headers.contains("content-range")) throw HttpFailure("HTTP range response has no bounds"); + const auto& range = headers.at("content-range"); + const auto dash = range.find('-'); + const auto slash = range.find('/'); + if (!range.starts_with("bytes ") || dash == std::string::npos || slash <= dash) throw HttpFailure("invalid HTTP content range"); + first = Number(std::string_view(range).substr(6, dash - 6)); + const auto last = Number(std::string_view(range).substr(dash + 1, slash - dash - 1)); + total = Number(std::string_view(range).substr(slash + 1)); + if (first != start || last < first || last >= total || last - first + 1 != body.size() || body.size() > length) throw HttpFailure("HTTP range response does not match request"); + } + if (total > static_cast(std::numeric_limits::max()) || (initialized && total != size)) throw HttpFailure("HTTP source size changed or is not representable"); + if (initialized && ((!etag.empty() && headers.contains("etag") && headers.at("etag") != etag) || (!modified.empty() && headers.contains("last-modified") && headers.at("last-modified") != modified))) throw HttpFailure("HTTP source representation changed"); + if (!initialized) { + if (headers.contains("etag")) etag = headers.at("etag"); + if (headers.contains("last-modified")) modified = headers.at("last-modified"); + if (etag.starts_with('"') && etag.ends_with('"')) validator = etag; + else if (etag.empty() && headers.contains("date")) { + const auto date = HttpDate(headers.at("date")); + const auto last = HttpDate(modified); + if (date && last && *date - *last >= std::chrono::seconds{60}) validator = modified; + } + } + CheckStopped(); + cache = std::move(body); + cacheStart = first; + size = total; + initialized = true; + return; + } + } + +}; + +HttpClientState& Checked(Aps5HttpClient* client) { + if (!client) throw std::invalid_argument("HTTP client is null"); + return *static_cast(client); +} + +const HttpClientState& Checked(const Aps5HttpClient* client) { + if (!client) throw std::invalid_argument("HTTP client is null"); + return *static_cast(client); +} + +} + +extern "C" { + +Aps5HttpClient* APS5_VABI Aps5HttpClient_Create_nid_postfix() { + return new HttpClientState; +} + +void APS5_VABI Aps5HttpClient_Destroy_nid_postfix(Aps5HttpClient* client) { + delete static_cast(client); +} + +std::int32_t APS5_VABI Aps5HttpClient_Open_nid_postfix(Aps5HttpClient* client, const char* url) { + auto& state = Checked(client); + if (!url) throw std::invalid_argument("HTTP client URL is null"); + state.Reset(url); + try { + state.Fetch(0, 65536); + return 1; + } catch (const HttpFailure&) { + return 0; + } +} + +std::int32_t APS5_VABI Aps5HttpClient_Read_nid_postfix(Aps5HttpClient* client, std::uint8_t* destination, std::int32_t capacity) { + auto& state = Checked(client); + if (capacity < 0) throw std::invalid_argument("HTTP client read capacity is negative"); + if (capacity == 0) return 0; + if (!destination) throw std::invalid_argument("HTTP client read destination is null"); + if (!state.initialized) throw HttpFailure("HTTP client is not open"); + state.CheckStopped(); + if (state.position >= state.size) return 0; + if (state.position < state.cacheStart || state.position - state.cacheStart >= state.cache.size()) + state.Fetch(state.position, std::min(65536, state.size - state.position)); + const auto offset = static_cast(state.position - state.cacheStart); + const auto count = std::min(static_cast(capacity), state.cache.size() - offset); + if (count == 0) throw HttpFailure("HTTP client read made no progress"); + std::memcpy(destination, state.cache.data() + offset, count); + state.position += count; + return static_cast(count); +} + +std::int64_t APS5_VABI Aps5HttpClient_Seek_nid_postfix(Aps5HttpClient* client, std::int64_t offset, std::int32_t origin) { + auto& state = Checked(client); + if (!state.initialized) return -1; + std::int64_t base; + if (origin == 0) base = 0; + else if (origin == 1) base = static_cast(state.position); + else if (origin == 2) base = static_cast(state.size); + else return -1; + if (offset < -base || offset > std::numeric_limits::max() - base) return -1; + state.position = static_cast(base + offset); + return static_cast(state.position); +} + +std::int64_t APS5_VABI Aps5HttpClient_GetSize_nid_postfix(const Aps5HttpClient* client) { + const auto& state = Checked(client); + return state.initialized ? static_cast(state.size) : -1; +} + +void APS5_VABI Aps5HttpClient_Start_nid_postfix(Aps5HttpClient* client) { + Checked(client).stopped.store(false); +} + +void APS5_VABI Aps5HttpClient_Stop_nid_postfix(Aps5HttpClient* client) { + Checked(client).stopped.store(true); +} + +} diff --git a/core/libs/prx/libSceHttp2/CMakeLists.txt b/core/libs/prx/libSceHttp2/CMakeLists.txt index 45e3b07081..055de1611c 100644 --- a/core/libs/prx/libSceHttp2/CMakeLists.txt +++ b/core/libs/prx/libSceHttp2/CMakeLists.txt @@ -73,6 +73,12 @@ if(BUILD_TESTING) set_tests_properties(guest_http2 guest_http2_cookies guest_http2_options guest_http2_transport guest_http2_early_response guest_http2_dns_first guest_http2_system_roots PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test(NAME guest_http2_redirects COMMAND guest_http2_transport_tests --redirects) + set_tests_properties(guest_http2_redirects PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_http2_redirects PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() if(BUILD_TESTING) diff --git a/core/libs/prx/libSceHttp2/Export.cpp b/core/libs/prx/libSceHttp2/Export.cpp index 443ffdaed4..5d137842a5 100644 --- a/core/libs/prx/libSceHttp2/Export.cpp +++ b/core/libs/prx/libSceHttp2/Export.cpp @@ -116,7 +116,7 @@ static std::shared_ptr Transport(int id, bool create) { if (!handle) return nullptr; if (create && !handle->transport) { if (handle->version != 3) NotImplemented_nid_no_patch("sceHttp2SendRequest: HTTP/1.x transport is not implemented"); - handle->transport = Http2::CreateRequest_nid_no_patch(handle->sslContext, handle->capacity, handle->settings, handle->method, handle->url); + handle->transport = Http2::CreateRequest_nid_no_patch(id, handle->sslContext, handle->capacity, handle->settings, handle->method, handle->url); } if (!handle->transport) NotImplemented_nid_no_patch("sceHttp2: request has not been sent"); return handle->transport; @@ -264,8 +264,11 @@ int APS5_VABI sceHttp2SetRecvTimeOut(int id, uint32_t usec) { }); } -int APS5_VABI sceHttp2SetRedirectCallback(int id, void* cb_func, void* user_arg) { - return Update(id, __func__, [&](auto&) { if (cb_func || user_arg) NotImplemented_nid_no_patch("sceHttp2SetRedirectCallback"); }); +int APS5_VABI sceHttp2SetRedirectCallback(int id, Http2RedirectCallback cb_func, void* user_arg) { + return Update(id, __func__, [&](auto& settings) { + settings.redirectCallback = cb_func; + settings.redirectArgument = user_arg; + }); } int APS5_VABI sceHttp2SetRequestContentLength(int id, uint64_t content_length) { diff --git a/core/libs/prx/libSceHttp2/Transport.cpp b/core/libs/prx/libSceHttp2/Transport.cpp index 60ba971b06..ef4f845543 100644 --- a/core/libs/prx/libSceHttp2/Transport.cpp +++ b/core/libs/prx/libSceHttp2/Transport.cpp @@ -106,6 +106,52 @@ Url Parse(std::string_view text) { return url; } +std::string NormalizePath(std::string_view path) { + std::string result; + while (!path.empty()) { + if (path.starts_with("../")) path.remove_prefix(3); + else if (path.starts_with("./")) path.remove_prefix(2); + else if (path.starts_with("/./")) path.remove_prefix(2); + else if (path == "/.") path = "/"; + else if (path.starts_with("/../") || path == "/..") { + path = path == "/.." ? "/" : path.substr(3); + const auto slash = result.rfind('/'); + result.erase(slash == std::string::npos ? 0 : slash); + } else if (path == "." || path == "..") path = {}; + else { + const auto slash = path.find('/', path.front() == '/' ? 1 : 0); + result.append(path.substr(0, slash)); + path = slash == std::string_view::npos ? std::string_view{} : path.substr(slash); + } + } + return result; +} + +Url RedirectTarget(const Url& base, std::string_view reference) { + reference = reference.substr(0, reference.find('#')); + const auto colon = reference.find(':'); + const auto delimiter = reference.find_first_of("/?"); + Url result; + if (colon != std::string_view::npos && (delimiter == std::string_view::npos || colon < delimiter)) result = Parse(reference); + else if (reference.starts_with("//")) result = Parse("https:" + std::string(reference)); + else { + std::string path; + if (reference.empty()) path = base.path; + else if (reference.starts_with('?')) path = base.path.substr(0, base.path.find('?')) + std::string(reference); + else if (reference.starts_with('/')) path = reference; + else { + path = base.path.substr(0, base.path.find('?')); + path.erase(path.rfind('/') + 1); + path += reference; + } + result = Parse("https://" + base.authority + path); + } + const auto query = result.path.find('?'); + result.path = NormalizePath(std::string_view(result.path).substr(0, query)) + + (query == std::string::npos ? std::string{} : result.path.substr(query)); + return result; +} + struct Address { std::array bytes{}; std::uint32_t size = 0; @@ -234,8 +280,10 @@ int AddHeader(Settings& settings, const char* name, const char* value, std::uint "sceHttp2AddRequestHeader: invalid header name"); Require(nghttp2_check_header_value_rfc9113(reinterpret_cast(value), std::strlen(value)), "sceHttp2AddRequestHeader: invalid header value"); + if (key == "host") Require(value[0] != '\0' && std::string_view(value).find_first_of(" \t") == std::string_view::npos, + "sceHttp2AddRequestHeader: invalid host header value"); Require(key != "connection" && key != "proxy-connection" && key != "keep-alive" && key != "upgrade" && - key != "transfer-encoding" && key != "host" && key != "content-length" && + key != "transfer-encoding" && key != "content-length" && (key != "te" || std::string_view(value) == "trailers"), "sceHttp2AddRequestHeader: unsupported HTTP/2 control header"); auto updated = settings.headers; if (mode == 0) std::erase_if(updated, [&](const auto& header) { return header.first == key; }); @@ -245,21 +293,25 @@ int AddHeader(Settings& settings, const char* name, const char* value, std::uint } struct Request::State { + const int id; int sslContext; const std::shared_ptr capacity; bool acquired = false; - const Settings settings; - const std::string method; - const Url url; + Settings settings; + std::string method; + Url url; std::mutex mutex; std::atomic cancelled{false}; bool started = false; + bool callbackActive = false; bool ready = false; bool ended = false; int status = 0; std::string contentEncoding; bool inflatedGzip = false; int blockStatus = 0; + std::string location; + bool hasLocation = false; int networkResult = 0; int socket = -1; int ssl = -1; @@ -279,8 +331,8 @@ struct Request::State { std::exception_ptr callbackFailure; Clock::time_point totalDeadline; - State(int context, std::shared_ptr capacity, Settings options, std::string verb, std::string target) - : sslContext(context), capacity(std::move(capacity)), settings(std::move(options)), method(std::move(verb)), url(Parse(target)) { + State(int id, int context, std::shared_ptr capacity, Settings options, std::string verb, std::string target) + : id(id), sslContext(context), capacity(std::move(capacity)), settings(std::move(options)), method(std::move(verb)), url(Parse(target)) { Require(!method.empty() && nghttp2_check_method(reinterpret_cast(method.data()), method.size()), "sceHttp2CreateRequestWithURL: invalid method"); Require(method != "CONNECT", "sceHttp2: CONNECT is not implemented"); @@ -452,6 +504,8 @@ struct Request::State { state.blockStatus = 0; state.hasLength = false; state.contentLength = 0; + state.location.clear(); + state.hasLocation = false; } }); } @@ -467,6 +521,11 @@ struct Request::State { const std::string_view key(reinterpret_cast(name), nameSize); const std::string_view text(reinterpret_cast(value), valueSize); if (key == ":status") state.blockStatus = static_cast(Number(text)); + if (key == "location") { + Require(!state.hasLocation, "sceHttp2: duplicate redirect location; console error mapping is unknown"); + state.location = text; + state.hasLocation = true; + } if (key == "content-length") { state.contentLength = Number(text); state.hasLength = true; @@ -484,8 +543,6 @@ struct Request::State { if (frame->hd.stream_id != state.stream) return; if (frame->hd.type == NGHTTP2_HEADERS && state.blockStatus >= 200) { state.status = state.blockStatus; - Require(!state.settings.redirect || (state.status != 301 && state.status != 302 && state.status != 303 && state.status != 307 && state.status != 308), - "sceHttp2: automatic redirects are not implemented"); Require(!state.settings.auth || (state.status != 401 && state.status != 407), "sceHttp2: authentication is not implemented"); state.ready = true; } @@ -548,11 +605,13 @@ struct Request::State { CheckNg(nghttp2_session_client_new2(&session, raw, this, optionsRaw)); const nghttp2_settings_entry settingsEntry{NGHTTP2_SETTINGS_ENABLE_PUSH, 0}; CheckNg(nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &settingsEntry, 1)); + std::string authority = url.authority; + for (const auto& [name, value] : settings.headers) if (name == "host") authority = value; std::vector> fields{ - {":method", method}, {":scheme", "https"}, {":authority", url.authority}, {":path", url.path}}; + {":method", method}, {":scheme", "https"}, {":authority", authority}, {":path", url.path}}; if (!settings.userAgent.empty() && std::none_of(settings.headers.begin(), settings.headers.end(), [](const auto& header) { return header.first == "user-agent"; })) fields.emplace_back("user-agent", settings.userAgent); - fields.insert(fields.end(), settings.headers.begin(), settings.headers.end()); + for (const auto& header : settings.headers) if (header.first != "host") fields.push_back(header); if (!settings.noContentLength) fields.emplace_back("content-length", std::to_string(settings.contentLength)); std::vector headers; for (auto& [name, value] : fields) headers.push_back({reinterpret_cast(name.data()), @@ -605,6 +664,7 @@ struct Request::State { std::unique_lock Lock() { std::unique_lock lock(mutex, std::try_to_lock); Require(lock.owns_lock(), "sceHttp2: concurrent operation on one request is not implemented"); + Require(!callbackActive, "sceHttp2: operation during redirect callback is not implemented"); if (failure) std::rethrow_exception(failure); Require(!cancelled.load(), "sceHttp2: request cancelled; console error mapping is unknown"); return lock; @@ -617,8 +677,8 @@ struct Request::State { } }; -Request::Request(int context, std::shared_ptr capacity, Settings settings, std::string method, std::string url) - : state(std::make_unique(context, std::move(capacity), std::move(settings), std::move(method), std::move(url))) {} +Request::Request(int id, int context, std::shared_ptr capacity, Settings settings, std::string method, std::string url) + : state(std::make_unique(id, context, std::move(capacity), std::move(settings), std::move(method), std::move(url))) {} Request::~Request() = default; int Request::Send(const void* data, std::size_t size) { @@ -629,24 +689,82 @@ int Request::Send(const void* data, std::size_t size) { state->started = true; state->totalDeadline = Clock::now() + std::chrono::microseconds(state->settings.timeout); try { - const int active = state->capacity->active.fetch_add(1); - state->acquired = true; - Require(active < state->capacity->maximum, "sceHttp2: connection slot waiting is not implemented"); - state->networkResult = state->Connect(); - if (state->networkResult < 0) { state->Close(); return state->networkResult; } - state->Setup(data, size); - const auto sendDeadline = state->Deadline(state->settings.sendTimeout); - auto receiveDeadline = sendDeadline; - bool uploaded = false; - for (;;) { - state->Flush(uploaded ? state->Deadline(state->settings.sendTimeout) : sendDeadline); - if (!uploaded && state->uploadOffset == size) { - uploaded = true; - receiveDeadline = state->Deadline(state->settings.recvTimeout); + const void* payload = data; + std::size_t payloadSize = size; + for (unsigned redirects = 0;; ++redirects) { + const int active = state->capacity->active.fetch_add(1); + state->acquired = true; + Require(active < state->capacity->maximum, "sceHttp2: connection slot waiting is not implemented"); + state->networkResult = state->Connect(); + if (state->networkResult < 0) { state->Close(); return state->networkResult; } + state->Setup(payload, payloadSize); + const auto sendDeadline = state->Deadline(state->settings.sendTimeout); + auto receiveDeadline = sendDeadline; + bool uploaded = false; + for (;;) { + state->Flush(uploaded ? state->Deadline(state->settings.sendTimeout) : sendDeadline); + if (!uploaded && state->uploadOffset == payloadSize) { + uploaded = true; + receiveDeadline = state->Deadline(state->settings.recvTimeout); + } + if (state->ready) break; + state->Receive(uploaded ? receiveDeadline : sendDeadline); + if (state->ready) break; } - if (state->ready) break; - state->Receive(uploaded ? receiveDeadline : sendDeadline); - if (state->ready) break; + const bool redirectStatus = state->status == 301 || state->status == 302 || state->status == 303 || state->status == 307 || state->status == 308; + if (!state->settings.redirect || !redirectStatus || state->location.empty()) break; + const bool retrieve = (state->status == 303 && state->method != "HEAD") || + ((state->status == 301 || state->status == 302) && state->method == "POST"); + std::string method = retrieve ? "GET" : state->method; + if (const auto callback = state->settings.redirectCallback) { + const char* selected = method.c_str(); + state->callbackActive = true; + lock.unlock(); + int result; + try { + result = callback(state->id, state->status, &selected, state->location.c_str(), state->settings.redirectArgument); + } catch (...) { + lock.lock(); + state->callbackActive = false; + throw; + } + lock.lock(); + state->callbackActive = false; + state->Check(state->totalDeadline); + if (result != 0) break; + Require(selected, "sceHttp2: redirect callback returned a null method"); + method = selected; + } + Require(redirects < 6, "sceHttp2: redirect limit exceeded; console error mapping is unknown"); + Require(!method.empty() && nghttp2_check_method(reinterpret_cast(method.data()), method.size()) && method != "CONNECT", + "sceHttp2: redirect callback returned an unsupported method"); + auto target = RedirectTarget(state->url, state->location); + const bool differentOrigin = Lower(target.host) != Lower(state->url.host) || target.port != state->url.port; + const bool dropBody = (retrieve || method != state->method) && (method == "GET" || method == "HEAD"); + std::erase_if(state->settings.headers, [&](const auto& header) { + const auto& key = header.first; + return (differentOrigin && (key == "authorization" || key == "proxy-authorization" || key == "cookie" || key == "origin" || key == "referer")) || + (dropBody && (key.starts_with("content-") || key == "digest" || key == "last-modified")); + }); + if (dropBody) { payload = nullptr; payloadSize = 0; } + state->settings.contentLength = payloadSize; + state->Close(); + state->method = std::move(method); + state->url = std::move(target); + state->ready = state->ended = false; + state->status = state->blockStatus = 0; + state->location.clear(); + state->hasLocation = state->hasLength = false; + state->contentLength = 0; + state->headerBytes = 0; + state->body.clear(); + state->bodyOffset = 0; + state->contentEncoding.clear(); + state->inflatedGzip = false; + state->upload = nullptr; + state->uploadSize = state->uploadOffset = 0; + state->callbackFailure = nullptr; + state->stream = -1; } state->upload = nullptr; if (state->ended && state->body.empty()) state->Close(); @@ -711,8 +829,8 @@ std::shared_ptr CreateCapacity_nid_no_patch(int maximum) { return std::make_shared(maximum); } -std::shared_ptr CreateRequest_nid_no_patch(int sslContext, std::shared_ptr capacity, Settings settings, std::string method, std::string url) { - return std::make_shared(sslContext, std::move(capacity), std::move(settings), std::move(method), std::move(url)); +std::shared_ptr CreateRequest_nid_no_patch(int id, int sslContext, std::shared_ptr capacity, Settings settings, std::string method, std::string url) { + return std::make_shared(id, sslContext, std::move(capacity), std::move(settings), std::move(method), std::move(url)); } } diff --git a/core/libs/prx/libSceHttp2/Transport.hpp b/core/libs/prx/libSceHttp2/Transport.hpp index 1595ab1048..c6e43a29b9 100644 --- a/core/libs/prx/libSceHttp2/Transport.hpp +++ b/core/libs/prx/libSceHttp2/Transport.hpp @@ -1,5 +1,7 @@ #pragma once +#include "prx/libSceHttp2/include/Http2.hpp" + #include #include #include @@ -18,6 +20,8 @@ inline std::atomic& LookupHookSlot() { } struct Settings { + Http2RedirectCallback redirectCallback = nullptr; + void* redirectArgument = nullptr; std::string userAgent; std::vector> headers; std::uint64_t contentLength = 0; @@ -46,7 +50,7 @@ struct Capacity { class Request { public: - Request(int sslContext, std::shared_ptr capacity, Settings settings, std::string method, std::string url); + Request(int id, int sslContext, std::shared_ptr capacity, Settings settings, std::string method, std::string url); ~Request(); Request(const Request&) = delete; Request& operator=(const Request&) = delete; @@ -63,7 +67,7 @@ private: int AddHeader(Settings& settings, const char* name, const char* value, std::uint32_t mode); std::shared_ptr CreateCapacity_nid_no_patch(int maximum); -std::shared_ptr CreateRequest_nid_no_patch(int sslContext, std::shared_ptr capacity, Settings settings, std::string method, std::string url); +std::shared_ptr CreateRequest_nid_no_patch(int id, int sslContext, std::shared_ptr capacity, Settings settings, std::string method, std::string url); } diff --git a/core/libs/prx/libSceHttp2/include/Http2.hpp b/core/libs/prx/libSceHttp2/include/Http2.hpp index c9157d4713..f63eaf315f 100644 --- a/core/libs/prx/libSceHttp2/include/Http2.hpp +++ b/core/libs/prx/libSceHttp2/include/Http2.hpp @@ -2,6 +2,8 @@ #include "SceTypes.hpp" +using Http2RedirectCallback = int (APS5_VABI*)(int requestId, int statusCode, const char** method, const char* location, void* userArg); + struct Http2MemoryPoolStats { size_t pool_size; size_t max_inuse_size; @@ -48,7 +50,7 @@ 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 sceHttp2SetRedirectCallback(int id, Http2RedirectCallback 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); diff --git a/core/libs/prx/libSceIcu/CMakeLists.txt b/core/libs/prx/libSceIcu/CMakeLists.txt index 0bbdfb93f1..b67f4053a4 100644 --- a/core/libs/prx/libSceIcu/CMakeLists.txt +++ b/core/libs/prx/libSceIcu/CMakeLists.txt @@ -1,5 +1,6 @@ -add_library(libSceIcu SHARED EXCLUDE_FROM_ALL Export.cpp Invariant.cpp Version.cpp Unescape.cpp) +add_library(libSceIcu SHARED EXCLUDE_FROM_ALL ConstrainedFieldPosition.cpp ErrorName.cpp Export.cpp Invariant.cpp Numeric.cpp Tokenize.cpp Unescape.cpp Version.cpp) target_include_directories(libSceIcu PRIVATE ${LIBS_INCLUDE_DIR}) +target_sources(libSceIcu PRIVATE RecoveredInventory.cpp) configure_windows_unwind(libSceIcu) target_sources(libSceIcu PRIVATE ConverterCallbacks.cpp) target_sources(libSceIcu PRIVATE LegacyExports.cpp) @@ -55,8 +56,26 @@ if(BUILD_TESTING) configure_windows_unwind(guest_icu_version_tests) add_test(NAME guest_icu_version COMMAND guest_icu_version_tests) set_tests_properties(guest_icu_version PROPERTIES TIMEOUT 10) + add_test_executable(guest_icu_numeric_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuNumeric.cpp) + target_include_directories(guest_icu_numeric_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_numeric_tests PRIVATE libSceIcu libc) + configure_windows_unwind(guest_icu_numeric_tests) + add_test(NAME guest_icu_numeric COMMAND guest_icu_numeric_tests) + set_tests_properties(guest_icu_numeric PROPERTIES TIMEOUT 10) + add_test_executable(guest_icu_error_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuErrorName.cpp) + target_include_directories(guest_icu_error_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_error_name_tests PRIVATE libSceIcu libc) + configure_windows_unwind(guest_icu_error_name_tests) + add_test(NAME guest_icu_error_name COMMAND guest_icu_error_name_tests) + set_tests_properties(guest_icu_error_name PROPERTIES TIMEOUT 10) + add_test_executable(guest_icu_constrained_field_position_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuConstrainedFieldPosition.cpp) + target_include_directories(guest_icu_constrained_field_position_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_constrained_field_position_tests PRIVATE libSceIcu libc) + configure_windows_unwind(guest_icu_constrained_field_position_tests) + add_test(NAME guest_icu_constrained_field_position COMMAND guest_icu_constrained_field_position_tests) + set_tests_properties(guest_icu_constrained_field_position PROPERTIES TIMEOUT 10) if(WIN32) - set_tests_properties(guest_icu_version PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_icu_version guest_icu_numeric guest_icu_error_name guest_icu_constrained_field_position PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() @@ -112,3 +131,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_icu_tokenize_tests ${LIBS_INCLUDE_DIR}/tests/GuestIcuTokenize.cpp) + target_include_directories(guest_icu_tokenize_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_icu_tokenize_tests PRIVATE libSceIcu libkernel libc) + configure_windows_unwind(guest_icu_tokenize_tests) + add_test(NAME guest_icu_tokenize COMMAND guest_icu_tokenize_tests) + set_tests_properties(guest_icu_tokenize PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_icu_tokenize PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceIcu/ConstrainedFieldPosition.cpp b/core/libs/prx/libSceIcu/ConstrainedFieldPosition.cpp new file mode 100644 index 0000000000..e1ece68921 --- /dev/null +++ b/core/libs/prx/libSceIcu/ConstrainedFieldPosition.cpp @@ -0,0 +1,105 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include + +namespace { + +constexpr std::int32_t Magic = 0x55434600; +constexpr std::int32_t IllegalArgumentError = 1; +constexpr std::int32_t InvalidFormatError = 3; +constexpr std::int32_t MemoryAllocationError = 7; +constexpr std::int32_t CategoryUndefined = 0; + +enum class Constraint : std::int8_t { + None, + Category, + Field +}; + +struct Position { + std::int32_t magic = Magic; + std::int32_t field = 0; + std::int32_t start = 0; + std::int32_t limit = 0; + std::int32_t category = CategoryUndefined; + Constraint constraint = Constraint::None; +}; + +Position* Validate(const UConstrainedFieldPosition* handle, std::int32_t& error) { + if (error > 0) return nullptr; + if (!handle) { + error = IllegalArgumentError; + return nullptr; + } + std::int32_t magic = 0; + std::memcpy(&magic, handle, sizeof(magic)); + if (magic != Magic) { + error = InvalidFormatError; + return nullptr; + } + return reinterpret_cast(const_cast(handle)); +} + +Position* Validate(const UConstrainedFieldPosition* handle, std::int32_t* error) { + if (!error) APS5_INVALID_ARG_EX; + return Validate(handle, *error); +} + +} + +extern "C" { + +UConstrainedFieldPosition* APS5_VABI ucfpos_open_nid_postfix(std::int32_t* error) { + auto* position = new (std::nothrow) Position(); + if (!position) { + if (!error) APS5_INVALID_ARG_EX; + *error = MemoryAllocationError; + return nullptr; + } + return reinterpret_cast(position); +} + +void APS5_VABI ucfpos_close_nid_postfix(UConstrainedFieldPosition* handle) { + std::int32_t error = 0; + auto* position = Validate(handle, error); + if (!position) return; + position->magic = 0; + delete position; +} + +void APS5_VABI ucfpos_constrainCategory_nid_postfix(UConstrainedFieldPosition* handle, std::int32_t category, std::int32_t* error) { + auto* position = Validate(handle, error); + if (!position) return; + position->constraint = Constraint::Category; + position->category = category; +} + +void APS5_VABI ucfpos_constrainField_nid_postfix(UConstrainedFieldPosition* handle, std::int32_t category, std::int32_t field, std::int32_t* error) { + auto* position = Validate(handle, error); + if (!position) return; + position->constraint = Constraint::Field; + position->category = category; + position->field = field; +} + +std::int32_t APS5_VABI ucfpos_getCategory_nid_postfix(const UConstrainedFieldPosition* handle, std::int32_t* error) { + const auto* position = Validate(handle, error); + return position ? position->category : CategoryUndefined; +} + +std::int32_t APS5_VABI ucfpos_getField_nid_postfix(const UConstrainedFieldPosition* handle, std::int32_t* error) { + const auto* position = Validate(handle, error); + return position ? position->field : 0; +} + +void APS5_VABI ucfpos_getIndexes_nid_postfix(const UConstrainedFieldPosition* handle, std::int32_t* start, std::int32_t* limit, std::int32_t* error) { + const auto* position = Validate(handle, error); + if (!position) return; + if (!start || !limit) APS5_INVALID_ARG_EX; + *start = position->start; + *limit = position->limit; +} + +} diff --git a/core/libs/prx/libSceIcu/ConstrainedFieldPosition.hpp b/core/libs/prx/libSceIcu/ConstrainedFieldPosition.hpp new file mode 100644 index 0000000000..15778f9894 --- /dev/null +++ b/core/libs/prx/libSceIcu/ConstrainedFieldPosition.hpp @@ -0,0 +1,15 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +struct UConstrainedFieldPosition; + +extern "C" { +UConstrainedFieldPosition* APS5_VABI ucfpos_open_nid_postfix(std::int32_t*); +void APS5_VABI ucfpos_close_nid_postfix(UConstrainedFieldPosition*); +void APS5_VABI ucfpos_constrainCategory_nid_postfix(UConstrainedFieldPosition*, std::int32_t, std::int32_t*); +void APS5_VABI ucfpos_constrainField_nid_postfix(UConstrainedFieldPosition*, std::int32_t, std::int32_t, std::int32_t*); +std::int32_t APS5_VABI ucfpos_getCategory_nid_postfix(const UConstrainedFieldPosition*, std::int32_t*); +std::int32_t APS5_VABI ucfpos_getField_nid_postfix(const UConstrainedFieldPosition*, std::int32_t*); +void APS5_VABI ucfpos_getIndexes_nid_postfix(const UConstrainedFieldPosition*, std::int32_t*, std::int32_t*, std::int32_t*); +} diff --git a/core/libs/prx/libSceIcu/ErrorName.cpp b/core/libs/prx/libSceIcu/ErrorName.cpp new file mode 100644 index 0000000000..065074ec38 --- /dev/null +++ b/core/libs/prx/libSceIcu/ErrorName.cpp @@ -0,0 +1,217 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include + +namespace { + +constexpr std::array WarningNames{ + "U_USING_FALLBACK_WARNING", + "U_USING_DEFAULT_WARNING", + "U_SAFECLONE_ALLOCATED_WARNING", + "U_STATE_OLD_WARNING", + "U_STRING_NOT_TERMINATED_WARNING", + "U_SORT_KEY_TOO_SHORT_WARNING", + "U_AMBIGUOUS_ALIAS_WARNING", + "U_DIFFERENT_UCA_VERSION", + "U_PLUGIN_CHANGED_LEVEL_WARNING" +}; + +constexpr std::array StandardNames{ + "U_ZERO_ERROR", + "U_ILLEGAL_ARGUMENT_ERROR", + "U_MISSING_RESOURCE_ERROR", + "U_INVALID_FORMAT_ERROR", + "U_FILE_ACCESS_ERROR", + "U_INTERNAL_PROGRAM_ERROR", + "U_MESSAGE_PARSE_ERROR", + "U_MEMORY_ALLOCATION_ERROR", + "U_INDEX_OUTOFBOUNDS_ERROR", + "U_PARSE_ERROR", + "U_INVALID_CHAR_FOUND", + "U_TRUNCATED_CHAR_FOUND", + "U_ILLEGAL_CHAR_FOUND", + "U_INVALID_TABLE_FORMAT", + "U_INVALID_TABLE_FILE", + "U_BUFFER_OVERFLOW_ERROR", + "U_UNSUPPORTED_ERROR", + "U_RESOURCE_TYPE_MISMATCH", + "U_ILLEGAL_ESCAPE_SEQUENCE", + "U_UNSUPPORTED_ESCAPE_SEQUENCE", + "U_NO_SPACE_AVAILABLE", + "U_CE_NOT_FOUND_ERROR", + "U_PRIMARY_TOO_LONG_ERROR", + "U_STATE_TOO_OLD_ERROR", + "U_TOO_MANY_ALIASES_ERROR", + "U_ENUM_OUT_OF_SYNC_ERROR", + "U_INVARIANT_CONVERSION_ERROR", + "U_INVALID_STATE_ERROR", + "U_COLLATOR_VERSION_MISMATCH", + "U_USELESS_COLLATOR_ERROR", + "U_NO_WRITE_PERMISSION", + "U_INPUT_TOO_LONG_ERROR" +}; + +constexpr std::array TransliteratorNames{ + "U_BAD_VARIABLE_DEFINITION", + "U_MALFORMED_RULE", + "U_MALFORMED_SET", + "U_MALFORMED_SYMBOL_REFERENCE", + "U_MALFORMED_UNICODE_ESCAPE", + "U_MALFORMED_VARIABLE_DEFINITION", + "U_MALFORMED_VARIABLE_REFERENCE", + "U_MISMATCHED_SEGMENT_DELIMITERS", + "U_MISPLACED_ANCHOR_START", + "U_MISPLACED_CURSOR_OFFSET", + "U_MISPLACED_QUANTIFIER", + "U_MISSING_OPERATOR", + "U_MISSING_SEGMENT_CLOSE", + "U_MULTIPLE_ANTE_CONTEXTS", + "U_MULTIPLE_CURSORS", + "U_MULTIPLE_POST_CONTEXTS", + "U_TRAILING_BACKSLASH", + "U_UNDEFINED_SEGMENT_REFERENCE", + "U_UNDEFINED_VARIABLE", + "U_UNQUOTED_SPECIAL", + "U_UNTERMINATED_QUOTE", + "U_RULE_MASK_ERROR", + "U_MISPLACED_COMPOUND_FILTER", + "U_MULTIPLE_COMPOUND_FILTERS", + "U_INVALID_RBT_SYNTAX", + "U_INVALID_PROPERTY_PATTERN", + "U_MALFORMED_PRAGMA", + "U_UNCLOSED_SEGMENT", + "U_ILLEGAL_CHAR_IN_SEGMENT", + "U_VARIABLE_RANGE_EXHAUSTED", + "U_VARIABLE_RANGE_OVERLAP", + "U_ILLEGAL_CHARACTER", + "U_INTERNAL_TRANSLITERATOR_ERROR", + "U_INVALID_ID", + "U_INVALID_FUNCTION" +}; + +constexpr std::array FormatNames{ + "U_UNEXPECTED_TOKEN", + "U_MULTIPLE_DECIMAL_SEPARATORS", + "U_MULTIPLE_EXPONENTIAL_SYMBOLS", + "U_MALFORMED_EXPONENTIAL_PATTERN", + "U_MULTIPLE_PERCENT_SYMBOLS", + "U_MULTIPLE_PERMILL_SYMBOLS", + "U_MULTIPLE_PAD_SPECIFIERS", + "U_PATTERN_SYNTAX_ERROR", + "U_ILLEGAL_PAD_POSITION", + "U_UNMATCHED_BRACES", + "U_UNSUPPORTED_PROPERTY", + "U_UNSUPPORTED_ATTRIBUTE", + "U_ARGUMENT_TYPE_MISMATCH", + "U_DUPLICATE_KEYWORD", + "U_UNDEFINED_KEYWORD", + "U_DEFAULT_KEYWORD_MISSING", + "U_DECIMAL_NUMBER_SYNTAX_ERROR", + "U_FORMAT_INEXACT_ERROR", + "U_NUMBER_ARG_OUTOFBOUNDS_ERROR", + "U_NUMBER_SKELETON_SYNTAX_ERROR", + "U_MF_UNRESOLVED_VARIABLE_ERROR", + "U_MF_SYNTAX_ERROR", + "U_MF_UNKNOWN_FUNCTION_ERROR", + "U_MF_VARIANT_KEY_MISMATCH_ERROR", + "U_MF_FORMATTING_ERROR", + "U_MF_NONEXHAUSTIVE_PATTERN_ERROR", + "U_MF_DUPLICATE_OPTION_NAME_ERROR", + "U_MF_SELECTOR_ERROR", + "U_MF_MISSING_SELECTOR_ANNOTATION_ERROR", + "U_MF_DUPLICATE_DECLARATION_ERROR", + "U_MF_OPERAND_MISMATCH_ERROR", + "U_MF_DUPLICATE_VARIANT_ERROR", + "U_MF_BAD_OPTION" +}; + +constexpr std::array BreakIteratorNames{ + "U_BRK_INTERNAL_ERROR", + "U_BRK_HEX_DIGITS_EXPECTED", + "U_BRK_SEMICOLON_EXPECTED", + "U_BRK_RULE_SYNTAX", + "U_BRK_UNCLOSED_SET", + "U_BRK_ASSIGN_ERROR", + "U_BRK_VARIABLE_REDFINITION", + "U_BRK_MISMATCHED_PAREN", + "U_BRK_NEW_LINE_IN_QUOTED_STRING", + "U_BRK_UNDEFINED_VARIABLE", + "U_BRK_INIT_ERROR", + "U_BRK_RULE_EMPTY_SET", + "U_BRK_UNRECOGNIZED_OPTION", + "U_BRK_MALFORMED_RULE_TAG" +}; + +constexpr std::array RegexNames{ + "U_REGEX_INTERNAL_ERROR", + "U_REGEX_RULE_SYNTAX", + "U_REGEX_INVALID_STATE", + "U_REGEX_BAD_ESCAPE_SEQUENCE", + "U_REGEX_PROPERTY_SYNTAX", + "U_REGEX_UNIMPLEMENTED", + "U_REGEX_MISMATCHED_PAREN", + "U_REGEX_NUMBER_TOO_BIG", + "U_REGEX_BAD_INTERVAL", + "U_REGEX_MAX_LT_MIN", + "U_REGEX_INVALID_BACK_REF", + "U_REGEX_INVALID_FLAG", + "U_REGEX_LOOK_BEHIND_LIMIT", + "U_REGEX_SET_CONTAINS_STRING", + "U_REGEX_OCTAL_TOO_BIG", + "U_REGEX_MISSING_CLOSE_BRACKET", + "U_REGEX_INVALID_RANGE", + "U_REGEX_STACK_OVERFLOW", + "U_REGEX_TIME_OUT", + "U_REGEX_STOPPED_BY_CALLER", + "U_REGEX_PATTERN_TOO_BIG", + "U_REGEX_INVALID_CAPTURE_GROUP_NAME" +}; + +constexpr std::array IdnaNames{ + "U_STRINGPREP_PROHIBITED_ERROR", + "U_STRINGPREP_UNASSIGNED_ERROR", + "U_STRINGPREP_CHECK_BIDI_ERROR", + "U_IDNA_STD3_ASCII_RULES_ERROR", + "U_IDNA_ACE_PREFIX_ERROR", + "U_IDNA_VERIFICATION_ERROR", + "U_IDNA_LABEL_TOO_LONG_ERROR", + "U_IDNA_ZERO_LENGTH_LABEL_ERROR", + "U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR" +}; + +constexpr std::array PluginNames{ + "U_PLUGIN_TOO_HIGH", + "U_PLUGIN_DIDNT_SET_LEVEL" +}; + +struct ErrorRange { + std::int64_t start; + std::span names; +}; + +constexpr std::array ErrorRanges{{ + {-128, WarningNames}, + {0, StandardNames}, + {0x10000, TransliteratorNames}, + {0x10100, FormatNames}, + {0x10200, BreakIteratorNames}, + {0x10300, RegexNames}, + {0x10400, IdnaNames}, + {0x10500, PluginNames} +}}; + +} + +extern "C" { + +const char* APS5_VABI u_errorName_nid_postfix(std::int32_t code) { + for (const auto& range : ErrorRanges) { + const std::int64_t index = code - range.start; + if (index >= 0 && index < static_cast(range.names.size())) return range.names[static_cast(index)]; + } + return "[BOGUS UErrorCode]"; +} + +} diff --git a/core/libs/prx/libSceIcu/ErrorName.hpp b/core/libs/prx/libSceIcu/ErrorName.hpp new file mode 100644 index 0000000000..0fb50447aa --- /dev/null +++ b/core/libs/prx/libSceIcu/ErrorName.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +const char* APS5_VABI u_errorName_nid_postfix(std::int32_t); +} diff --git a/core/libs/prx/libSceIcu/Numeric.cpp b/core/libs/prx/libSceIcu/Numeric.cpp new file mode 100644 index 0000000000..ce6e610c5e --- /dev/null +++ b/core/libs/prx/libSceIcu/Numeric.cpp @@ -0,0 +1,137 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include + +namespace { + +constexpr std::int64_t InfinityBits = 0x7ff0000000000000; +constexpr std::int64_t NaNBits = 0x7ff8000000000000; + +std::int64_t Magnitude(double value) { + return std::bit_cast(value) & std::numeric_limits::max(); +} + +bool SignBit(double value) { + return std::bit_cast(value) < 0; +} + +} + +extern "C" { + +std::int8_t APS5_VABI uprv_isNaN_nid_postfix(double number) { + return Magnitude(number) > InfinityBits; +} + +std::int8_t APS5_VABI uprv_isInfinite_nid_postfix(double number) { + return Magnitude(number) == InfinityBits; +} + +std::int8_t APS5_VABI uprv_isPositiveInfinity_nid_postfix(double number) { + return number > 0 && uprv_isInfinite_nid_postfix(number); +} + +std::int8_t APS5_VABI uprv_isNegativeInfinity_nid_postfix(double number) { + return number < 0 && uprv_isInfinite_nid_postfix(number); +} + +double APS5_VABI uprv_getNaN_nid_postfix() { + return std::bit_cast(NaNBits); +} + +double APS5_VABI uprv_getInfinity_nid_postfix() { + return std::bit_cast(InfinityBits); +} + +double APS5_VABI uprv_floor_nid_postfix(double x) { + return floor_nid_postfix(x); +} + +double APS5_VABI uprv_ceil_nid_postfix(double x) { + return ceil_nid_postfix(x); +} + +double APS5_VABI uprv_round_nid_postfix(double x) { + return uprv_floor_nid_postfix(x + 0.5); +} + +double APS5_VABI uprv_fabs_nid_postfix(double x) { + return fabs_nid_postfix(x); +} + +double APS5_VABI uprv_modf_nid_postfix(double x, double* y) { + if (!y) APS5_INVALID_ARG_EX; + return modf_nid_postfix(x, y); +} + +double APS5_VABI uprv_fmod_nid_postfix(double x, double y) { + return fmod_nid_postfix(x, y); +} + +double APS5_VABI uprv_pow_nid_postfix(double x, double y) { + return pow_nid_postfix(x, y); +} + +double APS5_VABI uprv_pow10_nid_postfix(std::int32_t x) { + return pow_nid_postfix(10.0, static_cast(x)); +} + +double APS5_VABI uprv_fmax_nid_postfix(double x, double y) { + if (uprv_isNaN_nid_postfix(x) || uprv_isNaN_nid_postfix(y)) return uprv_getNaN_nid_postfix(); + if (x == 0.0 && y == 0.0 && SignBit(x)) return y; + return x > y ? x : y; +} + +double APS5_VABI uprv_fmin_nid_postfix(double x, double y) { + if (uprv_isNaN_nid_postfix(x) || uprv_isNaN_nid_postfix(y)) return uprv_getNaN_nid_postfix(); + if (x == 0.0 && y == 0.0 && SignBit(y)) return y; + return x > y ? y : x; +} + +std::int8_t APS5_VABI uprv_add32_overflow_nid_postfix(std::int32_t a, std::int32_t b, std::int32_t* res) { + if (!res) APS5_INVALID_ARG_EX; + const std::int64_t result = static_cast(a) + b; + *res = static_cast(result); + return result != *res; +} + +std::int8_t APS5_VABI uprv_mul32_overflow_nid_postfix(std::int32_t a, std::int32_t b, std::int32_t* res) { + if (!res) APS5_INVALID_ARG_EX; + const std::int64_t result = static_cast(a) * b; + *res = static_cast(result); + return result != *res; +} + +double APS5_VABI uprv_trunc_nid_postfix(double d) { + if (uprv_isNaN_nid_postfix(d)) return uprv_getNaN_nid_postfix(); + if (uprv_isInfinite_nid_postfix(d)) return uprv_getInfinity_nid_postfix(); + return SignBit(d) ? ceil_nid_postfix(d) : floor_nid_postfix(d); +} + +double APS5_VABI uprv_maxMantissa_nid_postfix() { + return pow_nid_postfix(2.0, DBL_MANT_DIG + 1.0) - 1.0; +} + +double APS5_VABI uprv_log_nid_postfix(double d) { + return log_nid_postfix(d); +} + +void* APS5_VABI uprv_maximumPtr_nid_postfix(void* base) { + const auto address = reinterpret_cast(base); + const std::uintptr_t limit = address + 0x7fffffffu; + return reinterpret_cast(limit > address ? limit : std::numeric_limits::max()); +} + +std::int32_t APS5_VABI uprv_max_nid_postfix(std::int32_t x, std::int32_t y) { + return x > y ? x : y; +} + +std::int32_t APS5_VABI uprv_min_nid_postfix(std::int32_t x, std::int32_t y) { + return x > y ? y : x; +} + +} diff --git a/core/libs/prx/libSceIcu/Numeric.hpp b/core/libs/prx/libSceIcu/Numeric.hpp new file mode 100644 index 0000000000..71bd499e5d --- /dev/null +++ b/core/libs/prx/libSceIcu/Numeric.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +std::int8_t APS5_VABI uprv_isNaN_nid_postfix(double); +std::int8_t APS5_VABI uprv_isInfinite_nid_postfix(double); +std::int8_t APS5_VABI uprv_isPositiveInfinity_nid_postfix(double); +std::int8_t APS5_VABI uprv_isNegativeInfinity_nid_postfix(double); +double APS5_VABI uprv_getNaN_nid_postfix(); +double APS5_VABI uprv_getInfinity_nid_postfix(); +double APS5_VABI uprv_floor_nid_postfix(double); +double APS5_VABI uprv_ceil_nid_postfix(double); +double APS5_VABI uprv_round_nid_postfix(double); +double APS5_VABI uprv_fabs_nid_postfix(double); +double APS5_VABI uprv_modf_nid_postfix(double, double*); +double APS5_VABI uprv_fmod_nid_postfix(double, double); +double APS5_VABI uprv_pow_nid_postfix(double, double); +double APS5_VABI uprv_pow10_nid_postfix(std::int32_t); +double APS5_VABI uprv_fmax_nid_postfix(double, double); +double APS5_VABI uprv_fmin_nid_postfix(double, double); +std::int8_t APS5_VABI uprv_add32_overflow_nid_postfix(std::int32_t, std::int32_t, std::int32_t*); +std::int8_t APS5_VABI uprv_mul32_overflow_nid_postfix(std::int32_t, std::int32_t, std::int32_t*); +double APS5_VABI uprv_trunc_nid_postfix(double); +double APS5_VABI uprv_maxMantissa_nid_postfix(); +double APS5_VABI uprv_log_nid_postfix(double); +void* APS5_VABI uprv_maximumPtr_nid_postfix(void*); +std::int32_t APS5_VABI uprv_max_nid_postfix(std::int32_t, std::int32_t); +std::int32_t APS5_VABI uprv_min_nid_postfix(std::int32_t, std::int32_t); +} diff --git a/core/libs/prx/libSceIcu/RecoveredInventory.cpp b/core/libs/prx/libSceIcu/RecoveredInventory.cpp new file mode 100644 index 0000000000..3d5a7b8836 --- /dev/null +++ b/core/libs/prx/libSceIcu/RecoveredInventory.cpp @@ -0,0 +1,542 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI u_strToTitle_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_countRuns_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_getBaseDirection_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_getLogicalRun_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_getParaLevel_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_getVisualRun_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_reorderVisual_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_setClassCallback_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ubidi_setPara_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_clone_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_get_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_getAttribute_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_getCanonicalTimeZoneID_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_getDayOfWeekType_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_getDefaultTimeZone_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_getKeywordValuesForLocale_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_getTimeZoneDisplayName_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_openTimeZoneIDEnumeration_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_openTimeZones_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_setGregorianChange_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucal_setMillis_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_countAvailable_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_getAttribute_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_getAvailable_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_getContractionsAndExpansions_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_getKeywordValues_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_getKeywordValuesForLocale_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_getReorderCodes_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_getRules_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_getStrength_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_getTailoredSet_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_setAttribute_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_setStrength_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_strcoll_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_strcollIter_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucol_strcollUTF8_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucsdet_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucsdet_detectAll_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucsdet_enableInputFilter_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucsdet_getConfidence_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucsdet_getName_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucsdet_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ucsdet_setText_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udat_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udat_countSymbols_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udat_format_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udat_formatForFields_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udat_getCalendar_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udat_getSymbols_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udat_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udat_toPattern_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udatpg_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udatpg_getBestPattern_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udatpg_getBestPatternWithOptions_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udatpg_getFieldDisplayName_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udatpg_getSkeleton_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udatpg_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udtitvfmt_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udtitvfmt_closeResult_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udtitvfmt_formatCalendarToResult_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udtitvfmt_formatToResult_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udtitvfmt_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udtitvfmt_openResult_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI udtitvfmt_resultAsValue_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ufieldpositer_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ufieldpositer_next_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ufieldpositer_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ufmtval_getString_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ufmtval_nextPosition_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uidna_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uidna_nameToASCII_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uidna_nameToUnicode_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uidna_openUTS46_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uldn_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uldn_keyValueDisplayName_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uldn_localeDisplayName_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uldn_openForContext_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uldn_regionDisplayName_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uldn_scriptDisplayName_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ulistfmt_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ulistfmt_closeResult_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ulistfmt_format_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ulistfmt_formatStringsToResult_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ulistfmt_openForType_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ulistfmt_openResult_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ulistfmt_resultAsValue_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_clone_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_formatDecimal_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_formatDouble_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_formatDoubleForFields_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_getSymbol_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_getTextAttribute_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_setAttribute_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unum_setTextAttribute_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumf_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumf_closeResult_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumf_formatDecimal_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumf_formatDouble_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumf_openForSkeletonAndLocale_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumf_openResult_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumf_resultGetAllFieldPositions_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumf_resultToString_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumsys_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumsys_getName_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumsys_isAlgorithmic_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumsys_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumsys_openAvailableNames_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI unumsys_openByName_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uplrules_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uplrules_getKeywords_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uplrules_openForType_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uplrules_selectWithFormat_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ureldatefmt_close_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ureldatefmt_format_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ureldatefmt_formatNumeric_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI ureldatefmt_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI uscript_getCode_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI usearch_getCollator_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI usearch_getMatchedLength_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI usearch_next_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI usearch_open_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI usearch_reset_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI usearch_setAttribute_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI usearch_setOffset_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI usearch_setPattern_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI usearch_setText_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceIcu/RecoveredInventory.hpp b/core/libs/prx/libSceIcu/RecoveredInventory.hpp new file mode 100644 index 0000000000..466642db3b --- /dev/null +++ b/core/libs/prx/libSceIcu/RecoveredInventory.hpp @@ -0,0 +1,139 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" + +extern "C" { +int APS5_VABI u_strToTitle_nid_postfix(); +int APS5_VABI ubidi_close_nid_postfix(); +int APS5_VABI ubidi_countRuns_nid_postfix(); +int APS5_VABI ubidi_getBaseDirection_nid_postfix(); +int APS5_VABI ubidi_getLogicalRun_nid_postfix(); +int APS5_VABI ubidi_getParaLevel_nid_postfix(); +int APS5_VABI ubidi_getVisualRun_nid_postfix(); +int APS5_VABI ubidi_open_nid_postfix(); +int APS5_VABI ubidi_reorderVisual_nid_postfix(); +int APS5_VABI ubidi_setClassCallback_nid_postfix(); +int APS5_VABI ubidi_setPara_nid_postfix(); +int APS5_VABI ucal_clone_nid_postfix(); +int APS5_VABI ucal_close_nid_postfix(); +int APS5_VABI ucal_get_nid_postfix(); +int APS5_VABI ucal_getAttribute_nid_postfix(); +int APS5_VABI ucal_getCanonicalTimeZoneID_nid_postfix(); +int APS5_VABI ucal_getDayOfWeekType_nid_postfix(); +int APS5_VABI ucal_getDefaultTimeZone_nid_postfix(); +int APS5_VABI ucal_getKeywordValuesForLocale_nid_postfix(); +int APS5_VABI ucal_getTimeZoneDisplayName_nid_postfix(); +int APS5_VABI ucal_open_nid_postfix(); +int APS5_VABI ucal_openTimeZoneIDEnumeration_nid_postfix(); +int APS5_VABI ucal_openTimeZones_nid_postfix(); +int APS5_VABI ucal_setGregorianChange_nid_postfix(); +int APS5_VABI ucal_setMillis_nid_postfix(); +int APS5_VABI ucol_close_nid_postfix(); +int APS5_VABI ucol_countAvailable_nid_postfix(); +int APS5_VABI ucol_getAttribute_nid_postfix(); +int APS5_VABI ucol_getAvailable_nid_postfix(); +int APS5_VABI ucol_getContractionsAndExpansions_nid_postfix(); +int APS5_VABI ucol_getKeywordValues_nid_postfix(); +int APS5_VABI ucol_getKeywordValuesForLocale_nid_postfix(); +int APS5_VABI ucol_getReorderCodes_nid_postfix(); +int APS5_VABI ucol_getRules_nid_postfix(); +int APS5_VABI ucol_getStrength_nid_postfix(); +int APS5_VABI ucol_getTailoredSet_nid_postfix(); +int APS5_VABI ucol_open_nid_postfix(); +int APS5_VABI ucol_setAttribute_nid_postfix(); +int APS5_VABI ucol_setStrength_nid_postfix(); +int APS5_VABI ucol_strcoll_nid_postfix(); +int APS5_VABI ucol_strcollIter_nid_postfix(); +int APS5_VABI ucol_strcollUTF8_nid_postfix(); +int APS5_VABI ucsdet_close_nid_postfix(); +int APS5_VABI ucsdet_detectAll_nid_postfix(); +int APS5_VABI ucsdet_enableInputFilter_nid_postfix(); +int APS5_VABI ucsdet_getConfidence_nid_postfix(); +int APS5_VABI ucsdet_getName_nid_postfix(); +int APS5_VABI ucsdet_open_nid_postfix(); +int APS5_VABI ucsdet_setText_nid_postfix(); +int APS5_VABI udat_close_nid_postfix(); +int APS5_VABI udat_countSymbols_nid_postfix(); +int APS5_VABI udat_format_nid_postfix(); +int APS5_VABI udat_formatForFields_nid_postfix(); +int APS5_VABI udat_getCalendar_nid_postfix(); +int APS5_VABI udat_getSymbols_nid_postfix(); +int APS5_VABI udat_open_nid_postfix(); +int APS5_VABI udat_toPattern_nid_postfix(); +int APS5_VABI udatpg_close_nid_postfix(); +int APS5_VABI udatpg_getBestPattern_nid_postfix(); +int APS5_VABI udatpg_getBestPatternWithOptions_nid_postfix(); +int APS5_VABI udatpg_getFieldDisplayName_nid_postfix(); +int APS5_VABI udatpg_getSkeleton_nid_postfix(); +int APS5_VABI udatpg_open_nid_postfix(); +int APS5_VABI udtitvfmt_close_nid_postfix(); +int APS5_VABI udtitvfmt_closeResult_nid_postfix(); +int APS5_VABI udtitvfmt_formatCalendarToResult_nid_postfix(); +int APS5_VABI udtitvfmt_formatToResult_nid_postfix(); +int APS5_VABI udtitvfmt_open_nid_postfix(); +int APS5_VABI udtitvfmt_openResult_nid_postfix(); +int APS5_VABI udtitvfmt_resultAsValue_nid_postfix(); +int APS5_VABI ufieldpositer_close_nid_postfix(); +int APS5_VABI ufieldpositer_next_nid_postfix(); +int APS5_VABI ufieldpositer_open_nid_postfix(); +int APS5_VABI ufmtval_getString_nid_postfix(); +int APS5_VABI ufmtval_nextPosition_nid_postfix(); +int APS5_VABI uidna_close_nid_postfix(); +int APS5_VABI uidna_nameToASCII_nid_postfix(); +int APS5_VABI uidna_nameToUnicode_nid_postfix(); +int APS5_VABI uidna_openUTS46_nid_postfix(); +int APS5_VABI uldn_close_nid_postfix(); +int APS5_VABI uldn_keyValueDisplayName_nid_postfix(); +int APS5_VABI uldn_localeDisplayName_nid_postfix(); +int APS5_VABI uldn_openForContext_nid_postfix(); +int APS5_VABI uldn_regionDisplayName_nid_postfix(); +int APS5_VABI uldn_scriptDisplayName_nid_postfix(); +int APS5_VABI ulistfmt_close_nid_postfix(); +int APS5_VABI ulistfmt_closeResult_nid_postfix(); +int APS5_VABI ulistfmt_format_nid_postfix(); +int APS5_VABI ulistfmt_formatStringsToResult_nid_postfix(); +int APS5_VABI ulistfmt_openForType_nid_postfix(); +int APS5_VABI ulistfmt_openResult_nid_postfix(); +int APS5_VABI ulistfmt_resultAsValue_nid_postfix(); +int APS5_VABI unum_clone_nid_postfix(); +int APS5_VABI unum_close_nid_postfix(); +int APS5_VABI unum_formatDecimal_nid_postfix(); +int APS5_VABI unum_formatDouble_nid_postfix(); +int APS5_VABI unum_formatDoubleForFields_nid_postfix(); +int APS5_VABI unum_getSymbol_nid_postfix(); +int APS5_VABI unum_getTextAttribute_nid_postfix(); +int APS5_VABI unum_open_nid_postfix(); +int APS5_VABI unum_setAttribute_nid_postfix(); +int APS5_VABI unum_setTextAttribute_nid_postfix(); +int APS5_VABI unumf_close_nid_postfix(); +int APS5_VABI unumf_closeResult_nid_postfix(); +int APS5_VABI unumf_formatDecimal_nid_postfix(); +int APS5_VABI unumf_formatDouble_nid_postfix(); +int APS5_VABI unumf_openForSkeletonAndLocale_nid_postfix(); +int APS5_VABI unumf_openResult_nid_postfix(); +int APS5_VABI unumf_resultGetAllFieldPositions_nid_postfix(); +int APS5_VABI unumf_resultToString_nid_postfix(); +int APS5_VABI unumsys_close_nid_postfix(); +int APS5_VABI unumsys_getName_nid_postfix(); +int APS5_VABI unumsys_isAlgorithmic_nid_postfix(); +int APS5_VABI unumsys_open_nid_postfix(); +int APS5_VABI unumsys_openAvailableNames_nid_postfix(); +int APS5_VABI unumsys_openByName_nid_postfix(); +int APS5_VABI uplrules_close_nid_postfix(); +int APS5_VABI uplrules_getKeywords_nid_postfix(); +int APS5_VABI uplrules_openForType_nid_postfix(); +int APS5_VABI uplrules_selectWithFormat_nid_postfix(); +int APS5_VABI ureldatefmt_close_nid_postfix(); +int APS5_VABI ureldatefmt_format_nid_postfix(); +int APS5_VABI ureldatefmt_formatNumeric_nid_postfix(); +int APS5_VABI ureldatefmt_open_nid_postfix(); +int APS5_VABI uscript_getCode_nid_postfix(); +int APS5_VABI usearch_getCollator_nid_postfix(); +int APS5_VABI usearch_getMatchedLength_nid_postfix(); +int APS5_VABI usearch_next_nid_postfix(); +int APS5_VABI usearch_open_nid_postfix(); +int APS5_VABI usearch_reset_nid_postfix(); +int APS5_VABI usearch_setAttribute_nid_postfix(); +int APS5_VABI usearch_setOffset_nid_postfix(); +int APS5_VABI usearch_setPattern_nid_postfix(); +int APS5_VABI usearch_setText_nid_postfix(); +} diff --git a/core/libs/prx/libSceIcu/Tokenize.cpp b/core/libs/prx/libSceIcu/Tokenize.cpp new file mode 100644 index 0000000000..38e4b9dc2c --- /dev/null +++ b/core/libs/prx/libSceIcu/Tokenize.cpp @@ -0,0 +1,118 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include +#include +#include + +namespace { + +struct Character { + std::uint32_t value; + std::size_t width; +}; + +bool Lead(char16_t value) { return value >= 0xd800 && value <= 0xdbff; } +bool Trail(char16_t value) { return value >= 0xdc00 && value <= 0xdfff; } + +Character Next(const char16_t* text) { + if (Lead(text[0]) && Trail(text[1])) + return {0x10000u + ((text[0] - 0xd800u) << 10) + (text[1] - 0xdc00u), 2}; + return {text[0], 1}; +} + +bool Contains(const char16_t* set, std::size_t length, std::uint32_t value) { + for (std::size_t position = 0; position < length;) { + const auto character = Next(set + position); + if (character.value == value) return true; + position += character.width; + } + return false; +} + +std::size_t FindSet(const char16_t* text, const char16_t* set, bool matches) { + constexpr auto maximum = static_cast(std::numeric_limits::max()); + std::size_t length = 0; + while (set[length] != 0) if (++length > maximum) return std::numeric_limits::max(); + std::size_t position = 0; + while (text[position] != 0) { + const auto character = Next(text + position); + if (Contains(set, length, character.value) == matches) break; + position += character.width; + if (position > maximum) return std::numeric_limits::max(); + } + return position; +} + +} // namespace + +extern "C" { + +std::int32_t APS5_VABI u_strspn_nid_postfix(const char16_t* text, const char16_t* set) { + if (!text || !set) NotImplemented_nid_no_patch(__func__); + const auto position = FindSet(text, set, false); + if (position > static_cast(std::numeric_limits::max())) NotImplemented_nid_no_patch(__func__); + return static_cast(position); +} + +std::int32_t APS5_VABI u_strcspn_nid_postfix(const char16_t* text, const char16_t* set) { + if (!text || !set) NotImplemented_nid_no_patch(__func__); + const auto position = FindSet(text, set, true); + if (position > static_cast(std::numeric_limits::max())) NotImplemented_nid_no_patch(__func__); + return static_cast(position); +} + +char16_t* APS5_VABI u_strpbrk_nid_postfix(const char16_t* text, const char16_t* set) { + if (!text || !set) NotImplemented_nid_no_patch(__func__); + const auto position = FindSet(text, set, true); + if (position > static_cast(std::numeric_limits::max())) NotImplemented_nid_no_patch(__func__); + return text[position] != 0 ? const_cast(text + position) : nullptr; +} + +char16_t* APS5_VABI u_strtok_r_nid_postfix(char16_t* source, const char16_t* delimiters, char16_t** state) { + if (!state) NotImplemented_nid_no_patch(__func__); + auto* text = source ? source : *state; + if (!text) return nullptr; + if (!delimiters) NotImplemented_nid_no_patch(__func__); + GuestArena::HostWrite stateAccess(state, sizeof(*state)); + if (!stateAccess.Open()) NotImplemented_nid_no_patch(__func__); + if (source) *state = source; + const auto skipped = FindSet(text, delimiters, false); + if (skipped > static_cast(std::numeric_limits::max())) NotImplemented_nid_no_patch(__func__); + text += skipped; + if (*text == 0) { + *state = nullptr; + return nullptr; + } + const auto length = FindSet(text, delimiters, true); + if (length > static_cast(std::numeric_limits::max())) NotImplemented_nid_no_patch(__func__); + auto* next = text + length; + if (*next != 0) { + GuestArena::HostWrite textAccess(next, sizeof(*next)); + if (!textAccess.Open()) NotImplemented_nid_no_patch(__func__); + *next++ = 0; + *state = next; + } else *state = nullptr; + return text; +} + +char16_t* APS5_VABI u_memrchr32_nid_postfix(const char16_t* text, std::int32_t value, std::int32_t count) { + if (count <= 0 || static_cast(value) > 0x10ffffu || (value > 0xffff && count < 2)) return nullptr; + if (!text) { + if (value >= 0xd800 && value <= 0xdfff) return nullptr; + NotImplemented_nid_no_patch(__func__); + } + auto position = static_cast(count); + while (position != 0) { + --position; + std::uint32_t character = text[position]; + if (Trail(text[position]) && position != 0 && Lead(text[position - 1])) { + --position; + character = 0x10000u + ((text[position] - 0xd800u) << 10) + (text[position + 1] - 0xdc00u); + } + if (character == static_cast(value)) return const_cast(text + position); + } + return nullptr; +} + +} diff --git a/core/libs/prx/libSceIcu/Version.cpp b/core/libs/prx/libSceIcu/Version.cpp index 3c55a14e17..c558a37716 100644 --- a/core/libs/prx/libSceIcu/Version.cpp +++ b/core/libs/prx/libSceIcu/Version.cpp @@ -8,6 +8,7 @@ constexpr std::uint16_t VersionLength = 4; constexpr std::int32_t VersionStringLength = 20; constexpr char VersionDelimiter = '.'; constexpr std::uint8_t UnicodeVersion[VersionLength] = {17, 0, 0, 0}; +constexpr const char* IcuVersion = "78.1"; char* WriteField(char* text, std::uint8_t field) { if (field >= 100) { @@ -72,4 +73,8 @@ void APS5_VABI u_getUnicodeVersion_nid_postfix(std::uint8_t* version) { for (std::uint16_t part = 0; part < VersionLength; ++part) version[part] = UnicodeVersion[part]; } +void APS5_VABI u_getVersion_nid_postfix(std::uint8_t* version) { + u_versionFromString_nid_postfix(version, IcuVersion); +} + } diff --git a/core/libs/prx/libSceIcu/Version.hpp b/core/libs/prx/libSceIcu/Version.hpp index 7bfee06d7b..63b5039624 100644 --- a/core/libs/prx/libSceIcu/Version.hpp +++ b/core/libs/prx/libSceIcu/Version.hpp @@ -7,4 +7,5 @@ void APS5_VABI u_versionFromString_nid_postfix(std::uint8_t*, const char*); void APS5_VABI u_versionFromUString_nid_postfix(std::uint8_t*, const char16_t*); void APS5_VABI u_versionToString_nid_postfix(const std::uint8_t*, char*); void APS5_VABI u_getUnicodeVersion_nid_postfix(std::uint8_t*); +void APS5_VABI u_getVersion_nid_postfix(std::uint8_t*); } diff --git a/core/libs/prx/libSceIcu/include/Api.hpp b/core/libs/prx/libSceIcu/include/Api.hpp new file mode 100644 index 0000000000..04d4567174 --- /dev/null +++ b/core/libs/prx/libSceIcu/include/Api.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { +std::int32_t APS5_VABI u_strspn_nid_postfix(const char16_t* text, const char16_t* set); +std::int32_t APS5_VABI u_strcspn_nid_postfix(const char16_t* text, const char16_t* set); +char16_t* APS5_VABI u_strpbrk_nid_postfix(const char16_t* text, const char16_t* set); +char16_t* APS5_VABI u_strtok_r_nid_postfix(char16_t* source, const char16_t* delimiters, char16_t** state); +char16_t* APS5_VABI u_memrchr32_nid_postfix(const char16_t* text, std::int32_t value, std::int32_t count); +} diff --git a/core/libs/prx/libSceIcu/libSceIcu.hpp b/core/libs/prx/libSceIcu/libSceIcu.hpp index 300ae3ebb7..12d2bf54c3 100644 --- a/core/libs/prx/libSceIcu/libSceIcu.hpp +++ b/core/libs/prx/libSceIcu/libSceIcu.hpp @@ -1,12 +1,18 @@ #pragma once #include "prx/libSceIcu/include/LegacyExports.hpp" +#include "ConstrainedFieldPosition.hpp" +#include "ErrorName.hpp" #include "Invariant.hpp" #include "Iterator.hpp" #include "prx/libSceIcu/include/InventoryDispatch.hpp" #include "Utf8.hpp" +#include "Numeric.hpp" #include "Version.hpp" +#include "RecoveredInventory.hpp" #include "ConverterCallbacks.hpp" #include "CString.hpp" #include "Space.hpp" #include "Unescape.hpp" #include "Identified.hpp" + +#include "prx/libSceIcu/include/Api.hpp" diff --git a/core/libs/prx/libSceIme/CMakeLists.txt b/core/libs/prx/libSceIme/CMakeLists.txt index e91845fe5a..2b0080363a 100644 --- a/core/libs/prx/libSceIme/CMakeLists.txt +++ b/core/libs/prx/libSceIme/CMakeLists.txt @@ -13,6 +13,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(ime_host_text_tests tests/HostText.cpp) + target_include_directories(ime_host_text_tests PRIVATE ${LIBS_INCLUDE_DIR}) + configure_host_test_executable(ime_host_text_tests) + add_test(NAME ime_host_text COMMAND ime_host_text_tests) + + add_test_executable(ime_composition_tests tests/Composition.cpp) + target_include_directories(ime_composition_tests PRIVATE ${LIBS_INCLUDE_DIR}) + configure_host_test_executable(ime_composition_tests) + add_test(NAME ime_composition COMMAND ime_composition_tests) + add_test_executable(guest_ime_pad_route_tests ${LIBS_INCLUDE_DIR}/tests/GuestImePadRoute.cpp) target_include_directories(guest_ime_pad_route_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_ime_pad_route_tests PRIVATE libSceIme libc) @@ -68,6 +78,26 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_ime_composition_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeComposition.cpp) + target_include_directories(guest_ime_composition_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ime_composition_tests PRIVATE libSceIme libc) + configure_windows_unwind(guest_ime_composition_tests) + add_test(NAME guest_ime_composition COMMAND guest_ime_composition_tests) + if(WIN32) + set_tests_properties(guest_ime_composition PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_ime_paste_tests ${LIBS_INCLUDE_DIR}/tests/GuestImePaste.cpp) + target_include_directories(guest_ime_paste_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ime_paste_tests PRIVATE libSceIme libc) + configure_windows_unwind(guest_ime_paste_tests) + add_test(NAME guest_ime_paste COMMAND guest_ime_paste_tests) + if(WIN32) + set_tests_properties(guest_ime_paste PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_ime_open_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeOpen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../libSceVideoOut/src/ImeTextInput.cpp) target_include_directories(guest_ime_open_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) @@ -78,4 +108,14 @@ if(BUILD_TESTING) set_tests_properties(guest_ime_open PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_ime_for_test_function_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeForTestFunction.cpp) + target_include_directories(guest_ime_for_test_function_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ime_for_test_function_tests PRIVATE libSceIme libc) + configure_windows_unwind(guest_ime_for_test_function_tests) + add_test(NAME guest_ime_for_test_function COMMAND guest_ime_for_test_function_tests) + if(WIN32) + set_tests_properties(guest_ime_for_test_function PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceIme/Export.cpp b/core/libs/prx/libSceIme/Export.cpp index 29b09103c1..b34475101f 100644 --- a/core/libs/prx/libSceIme/Export.cpp +++ b/core/libs/prx/libSceIme/Export.cpp @@ -15,7 +15,9 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libSceIme/include/ImeComposition.hpp" #include "prx/libSceIme/include/ImeFilterText.hpp" +#include "prx/libSceIme/include/ImeHostText.hpp" #include "prx/libSceIme/include/ImeInput.hpp" #include "prx/libSceIme/include/ImeKeyboardView.hpp" @@ -69,13 +71,19 @@ constexpr uint32_t EventPressEnter = 5; constexpr uint32_t CaretMoveLeft = 1; constexpr uint32_t CaretMoveRight = 2; +struct PendingInput { + ImeHostInput input; + std::u16string paste; +}; + struct Session { Param param{}; ImeExtKeyboardFilter extKeyboardFilter = nullptr; std::u16string text; std::uint32_t caret = 0; std::deque events; - std::deque hostInputs; + std::deque hostInputs; + ImeComposition composition; std::uint64_t id = 0; std::uint64_t revision = 0; }; @@ -89,6 +97,7 @@ std::set g_openKeyboards; std::mutex g_imeMutex; std::optional g_session; std::atomic g_textSink{nullptr}; +std::atomic g_preeditSink{nullptr}; std::atomic g_padSink{nullptr}; std::atomic g_viewProvider{nullptr}; std::uint64_t g_nextSessionId = 0; @@ -271,7 +280,15 @@ std::size_t UnitsAfter(const Session& session) { return caret + 1 < session.text.size() && IsHighSurrogate(session.text[caret]) && IsLowSurrogate(session.text[caret + 1]) ? 2 : 1; } -std::optional Apply(Session& session, const ImeHostInput& input) { +std::optional Apply(Session& session, const PendingInput& pending) { + const ImeHostInput& input = pending.input; + if (input.kind == ImeHostInputKind::Preedit) { + std::size_t size = 0; + while (size < sizeof(input.text) && input.text[size] != '\0') ++size; + session.composition.Set(ImeHostText::PreeditUnits(input.text, size), input.start, input.length); + return std::nullopt; + } + if (ImeRouteWhileComposing(session.composition, input.kind) == ImeCompositionRoute::Swallow) return std::nullopt; switch (input.kind) { case ImeHostInputKind::Text: { std::size_t size = 0; @@ -326,6 +343,10 @@ std::optional Apply(Session& session, const ImeHostInput& input) case ImeHostInputKind::Escape: QueueText(session, EventPressClose, session.caret, 0); return std::nullopt; + case ImeHostInputKind::Paste: + return Insert(session, pending.paste); + case ImeHostInputKind::Preedit: + return std::nullopt; } throw std::invalid_argument("IME host input: unknown input kind"); } @@ -369,10 +390,19 @@ void ImePublishInput_nid_postfix(const ImeHostInput& input) { { std::lock_guard lock(g_imeMutex); if (g_session) { - g_session->hostInputs.push_back(input); + g_session->hostInputs.push_back({input, {}}); return; } } + if (input.kind == ImeHostInputKind::Preedit) { + const ImeHostPreeditSink preeditSink = g_preeditSink.load(); + if (preeditSink == nullptr) return; + std::size_t size = 0; + while (size < sizeof(input.text) && input.text[size] != '\0') ++size; + const std::u16string units = ImeHostText::PreeditUnits(input.text, size); + preeditSink(units.data(), units.size(), input.start, input.length); + return; + } const ImeHostTextSink sink = g_textSink.load(); if (sink == nullptr) return; std::u16string units; @@ -384,6 +414,26 @@ void ImePublishInput_nid_postfix(const ImeHostInput& input) { sink(input.kind, units.data(), units.size()); } +void ImePublishPaste_nid_postfix(const char* text, std::size_t size) { + if (text == nullptr) return; + { + std::lock_guard lock(g_imeMutex); + if (g_session) { + std::u16string units = ImeHostText::ClipboardUnits(text, size, (g_session->param.option & OptionMultiline) != 0); + if (units.empty()) return; + ImeHostInput input; + input.kind = ImeHostInputKind::Paste; + g_session->hostInputs.push_back({input, std::move(units)}); + return; + } + } + const ImeHostTextSink sink = g_textSink.load(); + if (sink == nullptr) return; + const std::u16string units = ImeHostText::ClipboardUnits(text, size, false); + if (units.empty()) return; + sink(ImeHostInputKind::Paste, units.data(), units.size()); +} + bool ImeCapturesKeyboard_nid_postfix() { std::lock_guard lock(g_imeMutex); return g_session.has_value() || g_textSink.load() != nullptr; @@ -393,6 +443,10 @@ void ImeSetHostTextSink_nid_no_patch(ImeHostTextSink sink) { g_textSink = sink; } +void ImeSetHostPreeditSink_nid_no_patch(ImeHostPreeditSink sink) { + g_preeditSink = sink; +} + bool ImeCapturesPad_nid_postfix() { return g_padSink.load() != nullptr; } @@ -573,9 +627,9 @@ int APS5_VABI sceImeUpdate(EventHandler handler) { std::lock_guard lock(g_imeMutex); if (!g_session || g_session->param.handler != handler) return 0; if (!g_session->hostInputs.empty()) { - const ImeHostInput input = g_session->hostInputs.front(); + const PendingInput pending = std::move(g_session->hostInputs.front()); g_session->hostInputs.pop_front(); - if (const std::optional deferred = Apply(*g_session, input)) { + if (const std::optional deferred = Apply(*g_session, pending)) { runFilter = true; filter = g_session->param.filter; filterText = g_session->text; diff --git a/core/libs/prx/libSceIme/include/ImeComposition.hpp b/core/libs/prx/libSceIme/include/ImeComposition.hpp new file mode 100644 index 0000000000..6954aa918b --- /dev/null +++ b/core/libs/prx/libSceIme/include/ImeComposition.hpp @@ -0,0 +1,70 @@ +#ifndef CORE_LIBS_PRX_LIBSCEIME_INCLUDE_IMECOMPOSITION_HPP +#define CORE_LIBS_PRX_LIBSCEIME_INCLUDE_IMECOMPOSITION_HPP + +#include +#include +#include +#include + +#include "prx/libSceIme/include/ImeInput.hpp" + +struct ImeComposition { + std::u16string text; + std::size_t cursor = 0; + std::size_t selection = 0; + + bool Active() const { return !text.empty(); } + + void Clear() { + text.clear(); + cursor = 0; + selection = 0; + } + + void Set(std::u16string units, std::int32_t startCharacters, std::int32_t lengthCharacters) { + text = std::move(units); + const std::int64_t start = startCharacters > 0 ? startCharacters : 0; + const std::int64_t length = lengthCharacters > 0 ? lengthCharacters : 0; + const std::size_t begin = Offset(start); + cursor = begin; + selection = Offset(start + length) - begin; + } + +private: + std::size_t Offset(std::int64_t characters) const { + std::size_t unit = 0; + for (std::int64_t seen = 0; seen < characters && unit < text.size(); ++seen) { + const bool pair = unit + 1 < text.size() && text[unit] >= 0xd800 && text[unit] <= 0xdbff && text[unit + 1] >= 0xdc00 && text[unit + 1] <= 0xdfff; + unit += pair ? 2 : 1; + } + return unit; + } +}; + +enum class ImeCompositionRoute { Apply, Swallow }; + +inline ImeCompositionRoute ImeRouteWhileComposing(ImeComposition& composition, ImeHostInputKind kind) { + switch (kind) { + case ImeHostInputKind::Text: + composition.Clear(); + return ImeCompositionRoute::Apply; + case ImeHostInputKind::Enter: + case ImeHostInputKind::Escape: + if (!composition.Active()) return ImeCompositionRoute::Apply; + composition.Clear(); + return ImeCompositionRoute::Swallow; + case ImeHostInputKind::Preedit: + return ImeCompositionRoute::Swallow; + case ImeHostInputKind::Backspace: + case ImeHostInputKind::Delete: + case ImeHostInputKind::Left: + case ImeHostInputKind::Right: + case ImeHostInputKind::Home: + case ImeHostInputKind::End: + case ImeHostInputKind::Paste: + return composition.Active() ? ImeCompositionRoute::Swallow : ImeCompositionRoute::Apply; + } + return ImeCompositionRoute::Apply; +} + +#endif diff --git a/core/libs/prx/libSceIme/include/ImeHostText.hpp b/core/libs/prx/libSceIme/include/ImeHostText.hpp new file mode 100644 index 0000000000..f96d42bc2a --- /dev/null +++ b/core/libs/prx/libSceIme/include/ImeHostText.hpp @@ -0,0 +1,98 @@ +#ifndef CORE_LIBS_PRX_LIBSCEIME_INCLUDE_IMEHOSTTEXT_HPP +#define CORE_LIBS_PRX_LIBSCEIME_INCLUDE_IMEHOSTTEXT_HPP + +#include +#include +#include + +namespace ImeHostText { + +inline constexpr std::size_t MaxClipboardBytes = 65536; + +inline std::size_t BoundedUtf8Size(const char* text, std::size_t size) { + if (size <= MaxClipboardBytes) return size; + std::size_t end = MaxClipboardBytes; + while (end > 0 && (static_cast(text[end]) & 0xc0) == 0x80) --end; + return end; +} + +inline std::u32string DecodeUtf8Lenient(const char* text, std::size_t size, bool dropIncompleteTail) { + std::u32string points; + std::size_t i = 0; + while (i < size) { + const auto lead = static_cast(text[i]); + if (lead < 0x80) { + points.push_back(lead); + ++i; + continue; + } + std::size_t extra = 0; + char32_t point = 0; + char32_t minimum = 0; + if (lead >= 0xc2 && lead <= 0xdf) { extra = 1; point = lead & 0x1f; minimum = 0x80; } + else if ((lead & 0xf0) == 0xe0) { extra = 2; point = lead & 0x0f; minimum = 0x800; } + else if (lead >= 0xf0 && lead <= 0xf4) { extra = 3; point = lead & 0x07; minimum = 0x10000; } + else { + points.push_back(0xfffd); + ++i; + continue; + } + std::size_t present = 0; + while (present < extra && i + 1 + present < size && (static_cast(text[i + 1 + present]) & 0xc0) == 0x80) { + point = (point << 6) | (static_cast(text[i + 1 + present]) & 0x3f); + ++present; + } + if (present < extra) { + const bool endOfInput = i + 1 + present >= size; + if (!(endOfInput && dropIncompleteTail)) points.push_back(0xfffd); + i += 1 + present; + continue; + } + i += 1 + extra; + const bool surrogate = point >= 0xd800 && point <= 0xdfff; + points.push_back(point < minimum || point > 0x10ffff || surrogate ? char32_t{0xfffd} : point); + } + return points; +} + +inline void AppendUtf16(std::u16string& units, char32_t point) { + if (point >= 0x10000) { + point -= 0x10000; + units.push_back(static_cast(0xd800 + (point >> 10))); + units.push_back(static_cast(0xdc00 + (point & 0x3ff))); + } else { + units.push_back(static_cast(point)); + } +} + +inline bool IsControl(char32_t point) { + return point < 0x20 || (point >= 0x7f && point <= 0x9f); +} + +inline std::u16string PreeditUnits(const char* text, std::size_t size) { + std::u16string units; + for (const char32_t point : DecodeUtf8Lenient(text, size, true)) { + if (!IsControl(point)) AppendUtf16(units, point); + } + return units; +} + +inline std::u16string ClipboardUnits(const char* text, std::size_t size, bool multiline) { + std::u16string units; + const std::u32string points = DecodeUtf8Lenient(text, BoundedUtf8Size(text, size), false); + for (std::size_t i = 0; i < points.size(); ++i) { + const char32_t point = points[i]; + const bool lineBreak = point == U'\n' || point == U'\r' || point == 0x85 || point == 0x2028 || point == 0x2029; + if (lineBreak) { + if (point == U'\r' && i + 1 < points.size() && points[i + 1] == U'\n') ++i; + if (multiline) units.push_back(u'\n'); + continue; + } + if (!IsControl(point)) AppendUtf16(units, point); + } + return units; +} + +} + +#endif diff --git a/core/libs/prx/libSceIme/include/ImeInput.hpp b/core/libs/prx/libSceIme/include/ImeInput.hpp index c04b2dd9a3..90b7a0bcb5 100644 --- a/core/libs/prx/libSceIme/include/ImeInput.hpp +++ b/core/libs/prx/libSceIme/include/ImeInput.hpp @@ -14,20 +14,29 @@ enum class ImeHostInputKind : std::uint32_t { End, Enter, Escape, + Preedit, + Paste, }; struct ImeHostInput { ImeHostInputKind kind = ImeHostInputKind::Text; char text[32] = {}; + std::int32_t start = 0; + std::int32_t length = 0; }; extern "C" void ImePublishInput_nid_postfix(const ImeHostInput& input); +extern "C" void ImePublishPaste_nid_postfix(const char* utf8, std::size_t size); extern "C" bool ImeCapturesKeyboard_nid_postfix(); using ImeHostTextSink = void (*)(ImeHostInputKind kind, const char16_t* text, std::size_t length); extern "C" void ImeSetHostTextSink_nid_no_patch(ImeHostTextSink sink); +using ImeHostPreeditSink = void (*)(const char16_t* text, std::size_t size, std::int32_t start, std::int32_t selected); + +extern "C" void ImeSetHostPreeditSink_nid_no_patch(ImeHostPreeditSink sink); + struct ImeHostPad { std::uint32_t buttons = 0; std::uint8_t leftX = 128; diff --git a/core/libs/prx/libSceIme/include/ImeKeyboardView.hpp b/core/libs/prx/libSceIme/include/ImeKeyboardView.hpp index ef8d9dc57d..544bd24f20 100644 --- a/core/libs/prx/libSceIme/include/ImeKeyboardView.hpp +++ b/core/libs/prx/libSceIme/include/ImeKeyboardView.hpp @@ -22,6 +22,9 @@ struct ImeKeyCell { struct ImeKeyboardView { std::u16string text; std::size_t caret = 0; + std::u16string preedit; + std::size_t preeditCursor = 0; + std::size_t preeditSelection = 0; std::uint8_t rows = 0; std::uint8_t columns = 0; std::vector keys; diff --git a/core/libs/prx/libSceIme/tests/Composition.cpp b/core/libs/prx/libSceIme/tests/Composition.cpp new file mode 100644 index 0000000000..dc5544a2c8 --- /dev/null +++ b/core/libs/prx/libSceIme/tests/Composition.cpp @@ -0,0 +1,91 @@ +#include "prx/libSceIme/include/ImeComposition.hpp" + +#include +#include + +namespace { + +void check(bool condition, const char* reason) { + if (!condition) throw std::runtime_error(reason); +} + +ImeComposition composing(const std::u16string& text, std::int32_t start = 0, std::int32_t length = 0) { + ImeComposition composition; + composition.Set(text, start, length); + return composition; +} + +void sets_cursor_and_selection_in_characters() { + auto composition = composing(u"\u3053\u3093\u306B\u3061\u306F", 2, 2); + check(composition.Active() && composition.cursor == 2 && composition.selection == 2, "start and length count characters"); + composition = composing(u"a\U0001F600b", 2, 1); + check(composition.cursor == 3 && composition.selection == 1, "a surrogate pair is one character"); + composition = composing(u"ab", 9, 4); + check(composition.cursor == 2 && composition.selection == 0, "a start past the end is clamped"); + composition = composing(u"abc", 1, 99); + check(composition.cursor == 1 && composition.selection == 2, "a length past the end is clamped"); + composition = composing(u"abc", -3, -1); + check(composition.cursor == 0 && composition.selection == 0, "negative values are zero"); + composition = composing(u"abc", -1, 3); + check(composition.cursor == 0 && composition.selection == 3, "a negative start is zero and the length is kept"); + composition = composing(u"abc", 2147483647, 2147483647); + check(composition.cursor == 3 && composition.selection == 0, "huge values do not overflow"); +} + +void empty_text_is_not_active() { + auto composition = composing(u"", 3, 3); + check(!composition.Active() && composition.cursor == 0 && composition.selection == 0, "an empty composition is inactive"); + composition = composing(u"x"); + composition.Clear(); + check(!composition.Active() && composition.text.empty(), "Clear empties the composition"); +} + +void commit_clears_the_composition() { + auto composition = composing(u"ni"); + check(ImeRouteWhileComposing(composition, ImeHostInputKind::Text) == ImeCompositionRoute::Apply, "a commit is applied"); + check(!composition.Active(), "a commit ends the composition"); + check(ImeRouteWhileComposing(composition, ImeHostInputKind::Text) == ImeCompositionRoute::Apply, "text without a composition is applied"); +} + +void enter_and_escape_end_a_composition_first() { + for (const auto kind : {ImeHostInputKind::Enter, ImeHostInputKind::Escape}) { + auto composition = composing(u"ni"); + check(ImeRouteWhileComposing(composition, kind) == ImeCompositionRoute::Swallow, "the first press only discards the composition"); + check(!composition.Active(), "the composition is gone after that press"); + check(ImeRouteWhileComposing(composition, kind) == ImeCompositionRoute::Apply, "the next press reaches the dialog"); + } +} + +void edit_keys_belong_to_the_input_method_while_composing() { + for (const auto kind : {ImeHostInputKind::Backspace, ImeHostInputKind::Delete, ImeHostInputKind::Left, ImeHostInputKind::Right, ImeHostInputKind::Home, ImeHostInputKind::End, ImeHostInputKind::Paste}) { + auto composition = composing(u"ni"); + check(ImeRouteWhileComposing(composition, kind) == ImeCompositionRoute::Swallow, "an edit key is swallowed while composing"); + check(composition.Active(), "an edit key does not end the composition"); + composition.Clear(); + check(ImeRouteWhileComposing(composition, kind) == ImeCompositionRoute::Apply, "an edit key is applied when not composing"); + } +} + +void preedit_is_never_applied() { + ImeComposition composition; + check(ImeRouteWhileComposing(composition, ImeHostInputKind::Preedit) == ImeCompositionRoute::Swallow, "a preedit is swallowed"); + composition = composing(u"ni"); + check(ImeRouteWhileComposing(composition, ImeHostInputKind::Preedit) == ImeCompositionRoute::Swallow && composition.Active(), "a preedit leaves the state to the caller"); +} + +} + +int main() { + try { + sets_cursor_and_selection_in_characters(); + empty_text_is_not_active(); + commit_clears_the_composition(); + enter_and_escape_end_a_composition_first(); + edit_keys_belong_to_the_input_method_while_composing(); + preedit_is_never_applied(); + } catch (const std::exception& error) { + std::fprintf(stderr, "ime composition: %s\n", error.what()); + return 1; + } + return 0; +} diff --git a/core/libs/prx/libSceIme/tests/HostText.cpp b/core/libs/prx/libSceIme/tests/HostText.cpp new file mode 100644 index 0000000000..2fb4e5a2c5 --- /dev/null +++ b/core/libs/prx/libSceIme/tests/HostText.cpp @@ -0,0 +1,99 @@ +#include "prx/libSceIme/include/ImeHostText.hpp" + +#include +#include +#include + +namespace { + +using namespace ImeHostText; + +void check(bool condition, const char* reason) { + if (!condition) throw std::runtime_error(reason); +} + +std::u16string clip(const std::string& utf8, bool multiline = false) { + return ClipboardUnits(utf8.data(), utf8.size(), multiline); +} + +std::u16string preedit(const std::string& utf8) { + return PreeditUnits(utf8.data(), utf8.size()); +} + +void decodes_valid_utf8() { + const std::string text = "a\xc3\xa9\xe3\x81\x82\xf0\x9f\x98\x80"; + const auto points = DecodeUtf8Lenient(text.data(), text.size(), false); + check(points == std::u32string(U"a\u00E9\u3042\U0001F600"), "valid UTF-8 decodes to its code points"); + check(clip(text) == u"a\u00E9\u3042\U0001F600", "valid UTF-8 becomes UTF-16 with a surrogate pair"); + check(clip("\xf0\x9f\x98\x80")[0] == 0xd83d && clip("\xf0\x9f\x98\x80")[1] == 0xde00, "the pair is high then low"); +} + +void replaces_invalid_utf8() { + check(clip("a\xffz") == u"a\uFFFDz", "a byte that never starts a sequence is replaced"); + check(clip("\xc0\x80") == u"\uFFFD\uFFFD", "an overlong NUL is replaced and not decoded"); + check(clip("\xe0\x80\xaf") == u"\uFFFD", "an overlong three-byte slash is replaced"); + check(clip("\xf0\x80\x80\x80") == u"\uFFFD", "an overlong four-byte NUL is replaced"); + check(clip("\xed\xa0\x80") == u"\uFFFD", "an encoded surrogate is replaced"); + check(clip("\xf4\x90\x80\x80") == u"\uFFFD", "a code point past U+10FFFF is replaced"); + check(clip("\xe3\x81" "A") == u"\uFFFDA", "a sequence cut by an ASCII byte is replaced once"); + check(clip("a\xe3\x81") == u"a\uFFFD", "an incomplete tail is replaced in a clipboard"); + check(preedit("a\xe3\x81") == u"a", "an incomplete tail is dropped in a preedit cut at 32 bytes"); + check(preedit("\xe3\x81\x82\xe3\x81") == u"\u3042", "only the cut sequence of a preedit is dropped"); + check(preedit("\xe3\x81" "A") == u"\uFFFDA", "a sequence broken inside a preedit is still replaced"); +} + +void strips_control_characters() { + check(clip(std::string("a\0b", 3)) == u"ab", "NUL is stripped"); + check(clip("a\tb\x01" "c\x7f" "d") == u"abcd", "tab and other C0 controls and DEL are stripped"); + check(clip("a\xc2\x80" "b\xc2\x9f" "c") == u"abc", "C1 controls are stripped"); + check(clip("a\x1b[31mb") == u"a[31mb", "an escape byte is stripped and the rest stays text"); + check(preedit("a\x01" "b") == u"ab", "a preedit loses control characters too"); + check(clip("\xc2\xa0z") == u"\u00A0z", "a no-break space is not a control character"); +} + +void handles_line_breaks() { + check(clip("a\nb") == u"ab", "a single-line field drops LF"); + check(clip("a\r\nb") == u"ab", "a single-line field drops CRLF as one break"); + check(clip("a\rb") == u"ab", "a single-line field drops CR"); + check(clip("a\xe2\x80\xa8" "b\xe2\x80\xa9" "c") == u"abc", "a single-line field drops U+2028 and U+2029"); + check(clip("a\nb", true) == u"a\nb", "a multi-line field keeps LF"); + check(clip("a\r\nb", true) == u"a\nb", "CRLF is one LF"); + check(clip("a\rb", true) == u"a\nb", "CR is one LF"); + check(clip("a\n\nb", true) == u"a\n\nb", "two LFs stay two"); + check(clip("a\r\n\r\nb", true) == u"a\n\nb", "two CRLFs are two LFs"); + check(clip("a\xe2\x80\xa8" "b", true) == u"a\nb", "U+2028 is a line break in a multi-line field"); + check(clip("a\xc2\x85" "b", true) == u"a\nb", "NEL is a line break in a multi-line field"); +} + +void bounds_huge_clipboards() { + check(BoundedUtf8Size("abc", 3) == 3, "a small clipboard is not cut"); + std::string huge(MaxClipboardBytes * 4, 'x'); + check(BoundedUtf8Size(huge.data(), huge.size()) == MaxClipboardBytes, "an ASCII clipboard is cut at the bound"); + check(clip(huge).size() == MaxClipboardBytes, "the converted text is bounded"); + std::string wide; + while (wide.size() < MaxClipboardBytes + 16) wide += "\xe3\x81\x82"; + const std::size_t cut = BoundedUtf8Size(wide.data(), wide.size()); + check(cut <= MaxClipboardBytes && cut % 3 == 0, "the cut falls between code points"); + check(clip(wide).find(u'\uFFFD') == std::u16string::npos, "a bounded clipboard has no replacement character"); + std::string pairs; + while (pairs.size() < MaxClipboardBytes + 16) pairs += "\xf0\x9f\x98\x80"; + const std::u16string converted = clip(pairs); + check(converted.size() % 2 == 0 && converted.size() <= MaxClipboardBytes, "the cut never leaves half a pair"); + check(BoundedUtf8Size(pairs.data(), pairs.size()) % 4 == 0, "a four-byte cut stays on a boundary"); +} + +} + +int main() { + try { + decodes_valid_utf8(); + replaces_invalid_utf8(); + strips_control_characters(); + handles_line_breaks(); + bounds_huge_clipboards(); + } catch (const std::exception& error) { + std::fprintf(stderr, "ime host text: %s\n", error.what()); + return 1; + } + return 0; +} diff --git a/core/libs/prx/libSceImeDialog/CMakeLists.txt b/core/libs/prx/libSceImeDialog/CMakeLists.txt index e3b25d8fa2..4379838a9c 100644 --- a/core/libs/prx/libSceImeDialog/CMakeLists.txt +++ b/core/libs/prx/libSceImeDialog/CMakeLists.txt @@ -22,4 +22,35 @@ if(BUILD_TESTING) set_tests_properties(guest_ime_dialog PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_ime_dialog_composition_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeDialogComposition.cpp) + target_include_directories(guest_ime_dialog_composition_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ime_dialog_composition_tests PRIVATE libSceImeDialog libSceIme libc) + configure_windows_unwind(guest_ime_dialog_composition_tests) + add_test(NAME guest_ime_dialog_composition COMMAND guest_ime_dialog_composition_tests) + if(WIN32) + set_tests_properties(guest_ime_dialog_composition PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_ime_dialog_paste_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeDialogPaste.cpp) + target_include_directories(guest_ime_dialog_paste_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ime_dialog_paste_tests PRIVATE libSceImeDialog libSceIme libc) + configure_windows_unwind(guest_ime_dialog_paste_tests) + add_test(NAME guest_ime_dialog_paste COMMAND guest_ime_dialog_paste_tests) + if(WIN32) + set_tests_properties(guest_ime_dialog_paste PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_ime_text_input_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeTextInput.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../libSceVideoOut/src/ImeTextInput.cpp) + target_include_directories(guest_ime_text_input_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR}) + target_link_libraries(guest_ime_text_input_tests PRIVATE libSceImeDialog libSceIme libc) + configure_windows_unwind(guest_ime_text_input_tests) + add_test(NAME guest_ime_text_input COMMAND guest_ime_text_input_tests) + if(WIN32) + set_tests_properties(guest_ime_text_input PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceImeDialog/Export.cpp b/core/libs/prx/libSceImeDialog/Export.cpp index 60d826a5d9..0b01e3f984 100644 --- a/core/libs/prx/libSceImeDialog/Export.cpp +++ b/core/libs/prx/libSceImeDialog/Export.cpp @@ -8,6 +8,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libSceIme/include/ImeComposition.hpp" #include "prx/libSceIme/include/ImeInput.hpp" #include "prx/libSceIme/include/ImeKeyboardView.hpp" @@ -43,6 +44,7 @@ struct Dialog { std::u16string text; std::size_t caret = 0; ImeDialog::PadKeyboardState keyboard; + ImeComposition composition; }; std::mutex g_mutex; @@ -63,6 +65,7 @@ std::size_t UnitsAfter(const Dialog& dialog) { void ReleaseInput() { ImeSetHostTextSink_nid_no_patch(nullptr); + ImeSetHostPreeditSink_nid_no_patch(nullptr); ImeSetHostPadSink_nid_no_patch(nullptr); ImeSetKeyboardViewProvider_nid_no_patch(nullptr); } @@ -70,6 +73,7 @@ void ReleaseInput() { void Finish(std::uint32_t endStatus) { g_dialog.endStatus = endStatus; g_dialog.status = StatusFinished; + g_dialog.composition.Clear(); ReleaseInput(); } @@ -119,15 +123,27 @@ void Apply(Dialog& dialog, ImeHostInputKind kind, const char16_t* units, std::si case ImeHostInputKind::Escape: Finish(EndStatusUserCanceled); return; + case ImeHostInputKind::Paste: + Insert(dialog, units, length); + return; + case ImeHostInputKind::Preedit: + return; } } void Receive(ImeHostInputKind kind, const char16_t* units, std::size_t length) { std::lock_guard lock(g_mutex); if (g_dialog.status != StatusRunning) return; + if (ImeRouteWhileComposing(g_dialog.composition, kind) == ImeCompositionRoute::Swallow) return; Apply(g_dialog, kind, units, length); } +void ReceivePreedit(const char16_t* units, std::size_t length, std::int32_t start, std::int32_t selected) { + std::lock_guard lock(g_mutex); + if (g_dialog.status != StatusRunning) return; + g_dialog.composition.Set(std::u16string(units, length), start, selected); +} + void ReceivePad(const ImeHostPad& pad) { std::lock_guard lock(g_mutex); Dialog& dialog = g_dialog; @@ -183,6 +199,9 @@ bool ProvideView(ImeKeyboardView& view) { if (dialog.status != StatusRunning) return false; view.text = dialog.text; view.caret = dialog.caret; + view.preedit = dialog.composition.text; + view.preeditCursor = dialog.composition.cursor; + view.preeditSelection = dialog.composition.selection; view.rows = ImeDialog::PadKeyboardRows(dialog.keyboard); view.columns = ImeDialog::PadKeyboardColumns(); view.keys.clear(); @@ -238,10 +257,13 @@ int APS5_VABI sceImeDialogGetPanelSizeExtended(const Param* param, const Extende } int APS5_VABI sceImeDialogGetResult(Result* result) { - std::lock_guard lock(g_mutex); - if (result == nullptr || g_dialog.status != StatusFinished) return ErrorNotOpened; - *result = Result{}; - result->endstatus = g_dialog.endStatus; + Result res{}; + { + std::lock_guard lock(g_mutex); + if (result == nullptr || g_dialog.status != StatusFinished) return ErrorNotOpened; + res.endstatus = g_dialog.endStatus; + } + if (!GuestCopyOut_nid_no_patch(result, &res, sizeof(res))) return ErrorNotOpened; return 0; } @@ -266,6 +288,7 @@ int APS5_VABI sceImeDialogInit(const Param* param, const ExtendedParam* extended g_dialog.keyboard = ImeDialog::PadKeyboardOpen(KeyboardOptions(*param, extended)); g_dialog.status = StatusRunning; ImeSetHostTextSink_nid_no_patch(&Receive); + ImeSetHostPreeditSink_nid_no_patch(&ReceivePreedit); ImeSetHostPadSink_nid_no_patch(&ReceivePad); ImeSetKeyboardViewProvider_nid_no_patch(&ProvideView); return 0; diff --git a/core/libs/prx/libSceIpmi/Export.cpp b/core/libs/prx/libSceIpmi/Export.cpp index 6bd6f70161..7d74d75265 100644 --- a/core/libs/prx/libSceIpmi/Export.cpp +++ b/core/libs/prx/libSceIpmi/Export.cpp @@ -34,205 +34,165 @@ int APS5_VABI sceIpmiMgrStartDump(void) { return 0; } -int APS5_VABI sceIpmiUnknown_nJthG9040U8_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl10disconnectEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("nJthG9040U8", sceIpmiUnknown_nJthG9040U8_nid_no_patch); -int APS5_VABI sceIpmiUnknown_64umtbwiizI_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl10initializeEPKNS_6Client6ConfigEPv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("64umtbwiizI", sceIpmiUnknown_64umtbwiizI_nid_no_patch); -int APS5_VABI sceIpmiUnknown_X8bM2L_G59E_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl11getUserDataEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("X8bM2L-G59E", sceIpmiUnknown_X8bM2L_G59E_nid_no_patch); -int APS5_VABI sceIpmiUnknown_pLNZVjiFPKE_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("pLNZVjiFPKE", sceIpmiUnknown_pLNZVjiFPKE_nid_no_patch); -int APS5_VABI sceIpmiUnknown_exZt85cnx08_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjjPiPNS_10BufferInfoEj() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("exZt85cnx08", sceIpmiUnknown_exZt85cnx08_nid_no_patch); -int APS5_VABI sceIpmiUnknown_y29rp3N5qf4_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl13pollEventFlagEjmjPm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("y29rp3N5qf4", sceIpmiUnknown_y29rp3N5qf4_nid_no_patch); -int APS5_VABI sceIpmiUnknown_eVn6vS9O8EI_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl13waitEventFlagEjmjPmPj() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("eVn6vS9O8EI", sceIpmiUnknown_eVn6vS9O8EI_nid_no_patch); -int APS5_VABI sceIpmiUnknown_lsbUGZCjQy8_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl16invokeSyncMethodEjPKNS_8DataInfoEjPiPNS_10BufferInfoEj() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("lsbUGZCjQy8", sceIpmiUnknown_lsbUGZCjQy8_nid_no_patch); -int APS5_VABI sceIpmiUnknown_57XmD42P9gQ_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl16invokeSyncMethodEjPKvmPiPvPmm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("57XmD42P9gQ", sceIpmiUnknown_57XmD42P9gQ_nid_no_patch); -int APS5_VABI sceIpmiUnknown_FsiuQCKSE5Q_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKNS_8DataInfoEjPjPKNS_6Client12EventNotifeeE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("FsiuQCKSE5Q", sceIpmiUnknown_FsiuQCKSE5Q_nid_no_patch); -int APS5_VABI sceIpmiUnknown_8kU11lW12ss_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("8kU11lW12ss", sceIpmiUnknown_8kU11lW12ss_nid_no_patch); -int APS5_VABI sceIpmiUnknown_cOli1uePSwY_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl19terminateConnectionEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("cOli1uePSwY", sceIpmiUnknown_cOli1uePSwY_nid_no_patch); -int APS5_VABI sceIpmiUnknown_rU0UzNMZDIs_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl6getMsgEjPvPmmPj() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("rU0UzNMZDIs", sceIpmiUnknown_rU0UzNMZDIs_nid_no_patch); -int APS5_VABI sceIpmiUnknown_vogma5QshU4_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl7connectEPKvmPi() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("vogma5QshU4", sceIpmiUnknown_vogma5QshU4_nid_no_patch); -int APS5_VABI sceIpmiUnknown_R9CB6TmSpeU_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl7destroyEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("R9CB6TmSpeU", sceIpmiUnknown_R9CB6TmSpeU_nid_no_patch); -int APS5_VABI sceIpmiUnknown_k9ixmB0yrP4_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImpl9tryGetMsgEjPvPmm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("k9ixmB0yrP4", sceIpmiUnknown_k9ixmB0yrP4_nid_no_patch); -int APS5_VABI sceIpmiUnknown_o8Es_aQYa2Q_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImplC1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("o8Es+aQYa2Q", sceIpmiUnknown_o8Es_aQYa2Q_nid_no_patch); -int APS5_VABI sceIpmiUnknown_N0iXfw1xf1Q_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImplC2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("N0iXfw1xf1Q", sceIpmiUnknown_N0iXfw1xf1Q_nid_no_patch); -int APS5_VABI sceIpmiUnknown_EsoUVUbGMdg_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImplD0Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("EsoUVUbGMdg", sceIpmiUnknown_EsoUVUbGMdg_nid_no_patch); -int APS5_VABI sceIpmiUnknown_H2HLRQerr_g_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImplD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("H2HLRQerr+g", sceIpmiUnknown_H2HLRQerr_g_nid_no_patch); -int APS5_VABI sceIpmiUnknown_lxtXq6qmrC8_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ClientImplD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("lxtXq6qmrC8", sceIpmiUnknown_lxtXq6qmrC8_nid_no_patch); -int APS5_VABI sceIpmiUnknown_w9uM8jBkjhw_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImpl11getUserDataEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("w9uM8jBkjhw", sceIpmiUnknown_w9uM8jBkjhw_nid_no_patch); -int APS5_VABI sceIpmiUnknown_GIniJssLcts_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImpl11tryDispatchEPvm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("GIniJssLcts", sceIpmiUnknown_GIniJssLcts_nid_no_patch); -int APS5_VABI sceIpmiUnknown_O_Rk6bioKMY_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImpl13createSessionEPPNS_7SessionEPvS5_() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("O+Rk6bioKMY", sceIpmiUnknown_O_Rk6bioKMY_nid_no_patch); -int APS5_VABI sceIpmiUnknown_P17jonIyIB8_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImpl13runDispatcherEPvm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("P17jonIyIB8", sceIpmiUnknown_P17jonIyIB8_nid_no_patch); -int APS5_VABI sceIpmiUnknown_e_OnyPe42tI_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImpl18shutdownDispatcherEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("e-OnyPe42tI", sceIpmiUnknown_e_OnyPe42tI_nid_no_patch); -int APS5_VABI sceIpmiUnknown_dPOHeORqhbw_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImpl7destroyEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("dPOHeORqhbw", sceIpmiUnknown_dPOHeORqhbw_nid_no_patch); -int APS5_VABI sceIpmiUnknown_w_zWDsN_0bQ_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImplC1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("w-zWDsN+0bQ", sceIpmiUnknown_w_zWDsN_0bQ_nid_no_patch); -int APS5_VABI sceIpmiUnknown_Gzah1AMZDS8_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImplC2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Gzah1AMZDS8", sceIpmiUnknown_Gzah1AMZDS8_nid_no_patch); -int APS5_VABI sceIpmiUnknown_2VzR2bMe3iU_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImplD0Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("2VzR2bMe3iU", sceIpmiUnknown_2VzR2bMe3iU_nid_no_patch); -int APS5_VABI sceIpmiUnknown_D1A_mAFwjT4_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImplD1Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("D1A-mAFwjT4", sceIpmiUnknown_D1A_mAFwjT4_nid_no_patch); -int APS5_VABI sceIpmiUnknown_uMWav34mMq8_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl10ServerImplD2Ev() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("uMWav34mMq8", sceIpmiUnknown_uMWav34mMq8_nid_no_patch); -int APS5_VABI sceIpmiUnknown_32j2cyCl3Yo_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl11SessionImpl10trySendMsgEjPKvm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("32j2cyCl3Yo", sceIpmiUnknown_32j2cyCl3Yo_nid_no_patch); -int APS5_VABI sceIpmiUnknown_bNAnspllfsc_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl11SessionImpl11getUserDataEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("bNAnspllfsc", sceIpmiUnknown_bNAnspllfsc_nid_no_patch); -int APS5_VABI sceIpmiUnknown_tFY6zzpnlOs_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("tFY6zzpnlOs", sceIpmiUnknown_tFY6zzpnlOs_nid_no_patch); -int APS5_VABI sceIpmiUnknown_vk71LUV2VVw_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl11SessionImpl12getClientPidEv() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("vk71LUV2VVw", sceIpmiUnknown_vk71LUV2VVw_nid_no_patch); -int APS5_VABI sceIpmiUnknown__S36x8s8CAw_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl11SessionImpl12setEventFlagEjm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("-S36x8s8CAw", sceIpmiUnknown__S36x8s8CAw_nid_no_patch); -int APS5_VABI sceIpmiUnknown_03U1u_RK3vg_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl11SessionImpl13emptyMsgQueueEj() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("03U1u-RK3vg", sceIpmiUnknown_03U1u_RK3vg_nid_no_patch); -int APS5_VABI sceIpmiUnknown_AYyxvDy_SFU_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl11SessionImpl26respondToSyncMethodRequestEiPKNS_10BufferInfoEj() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("AYyxvDy-SFU", sceIpmiUnknown_AYyxvDy_SFU_nid_no_patch); -int APS5_VABI sceIpmiUnknown_r0t1NxWUNwI_nid_no_patch() { +int APS5_VABI _ZN4IPMI4impl11SessionImpl26respondToSyncMethodRequestEiPKvm() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("r0t1NxWUNwI", sceIpmiUnknown_r0t1NxWUNwI_nid_no_patch); } diff --git a/core/libs/prx/libSceIpmi/include/Api.hpp b/core/libs/prx/libSceIpmi/include/Api.hpp index 200ce258ea..ae6dfa84ed 100644 --- a/core/libs/prx/libSceIpmi/include/Api.hpp +++ b/core/libs/prx/libSceIpmi/include/Api.hpp @@ -59,4 +59,45 @@ int APS5_VABI _ZN4IPMI6Client6Config26SyncMethodInvocationConfigC2Ev(void); int APS5_VABI _ZN4IPMI6Client6Config14MsgQueueConfigC2Ev(void); int APS5_VABI _ZN4IPMI6Server6ConfigC2Ev(void); +int APS5_VABI _ZN4IPMI4impl10ClientImpl10disconnectEv(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl10initializeEPKNS_6Client6ConfigEPv(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl11getUserDataEv(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjPiPvPmm(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl12tryGetResultEjjPiPNS_10BufferInfoEj(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl13pollEventFlagEjmjPm(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl13waitEventFlagEjmjPmPj(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl16invokeSyncMethodEjPKNS_8DataInfoEjPiPNS_10BufferInfoEj(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl16invokeSyncMethodEjPKvmPiPvPmm(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKNS_8DataInfoEjPjPKNS_6Client12EventNotifeeE(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl17invokeAsyncMethodEjPKvmPjPKNS_6Client12EventNotifeeE(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl19terminateConnectionEv(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl6getMsgEjPvPmmPj(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl7connectEPKvmPi(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl7destroyEv(); +int APS5_VABI _ZN4IPMI4impl10ClientImpl9tryGetMsgEjPvPmm(); +int APS5_VABI _ZN4IPMI4impl10ClientImplC1Ev(); +int APS5_VABI _ZN4IPMI4impl10ClientImplC2Ev(); +int APS5_VABI _ZN4IPMI4impl10ClientImplD0Ev(); +int APS5_VABI _ZN4IPMI4impl10ClientImplD1Ev(); +int APS5_VABI _ZN4IPMI4impl10ClientImplD2Ev(); +int APS5_VABI _ZN4IPMI4impl10ServerImpl11getUserDataEv(); +int APS5_VABI _ZN4IPMI4impl10ServerImpl11tryDispatchEPvm(); +int APS5_VABI _ZN4IPMI4impl10ServerImpl13createSessionEPPNS_7SessionEPvS5_(); +int APS5_VABI _ZN4IPMI4impl10ServerImpl13runDispatcherEPvm(); +int APS5_VABI _ZN4IPMI4impl10ServerImpl18shutdownDispatcherEv(); +int APS5_VABI _ZN4IPMI4impl10ServerImpl7destroyEv(); +int APS5_VABI _ZN4IPMI4impl10ServerImplC1Ev(); +int APS5_VABI _ZN4IPMI4impl10ServerImplC2Ev(); +int APS5_VABI _ZN4IPMI4impl10ServerImplD0Ev(); +int APS5_VABI _ZN4IPMI4impl10ServerImplD1Ev(); +int APS5_VABI _ZN4IPMI4impl10ServerImplD2Ev(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl10trySendMsgEjPKvm(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl11getUserDataEv(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl11tryDispatchEPvm(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl12getClientPidEv(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl12setEventFlagEjm(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl13emptyMsgQueueEj(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl26respondToSyncMethodRequestEiPKNS_10BufferInfoEj(); +int APS5_VABI _ZN4IPMI4impl11SessionImpl26respondToSyncMethodRequestEiPKvm(); + } diff --git a/core/libs/prx/libSceJpegDec/CMakeLists.txt b/core/libs/prx/libSceJpegDec/CMakeLists.txt index 141c7e25d0..1d0317a746 100644 --- a/core/libs/prx/libSceJpegDec/CMakeLists.txt +++ b/core/libs/prx/libSceJpegDec/CMakeLists.txt @@ -18,4 +18,14 @@ if(BUILD_TESTING) set_tests_properties(guest_jpeg_dec PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_jpeg_dec_input_control_tests ${LIBS_INCLUDE_DIR}/tests/GuestJpegDecInputControl.cpp) + target_include_directories(guest_jpeg_dec_input_control_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_jpeg_dec_input_control_tests PRIVATE libSceJpegDec decoder_jpeg libc) + configure_windows_unwind(guest_jpeg_dec_input_control_tests) + add_test(NAME guest_jpeg_dec_input_control COMMAND guest_jpeg_dec_input_control_tests) + if(WIN32) + set_tests_properties(guest_jpeg_dec_input_control PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceJpegDec/Export.cpp b/core/libs/prx/libSceJpegDec/Export.cpp index 90a1918e2f..7d9e63e235 100644 --- a/core/libs/prx/libSceJpegDec/Export.cpp +++ b/core/libs/prx/libSceJpegDec/Export.cpp @@ -1,13 +1,19 @@ #include "prx/libSceJpegDec/include/Api.hpp" +#include +#include #include #include #include +#include +#include #include #include #include #include #include +#include #include +#include #include "Decoder/Jpeg.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -16,6 +22,8 @@ static_assert(sizeof(JpegDecCreateParam) == 0xC); static_assert(sizeof(JpegDecParseParam) == 0x10); static_assert(sizeof(JpegDecDecodeParam) == 0x30); static_assert(sizeof(JpegDecImageInfo) == 0x24); +static_assert(sizeof(JpegDecInputChunk) == 0x10); +static_assert(sizeof(JpegDecInputControlParam) == 0x38 && offsetof(JpegDecInputControlParam, operation) == 0x28 && offsetof(JpegDecInputControlParam, reserved) == 0x34); namespace { @@ -48,6 +56,10 @@ constexpr std::uint16_t COLOR_SPACE_GRAY = 2; constexpr std::uint32_t BYTES_PER_PIXEL = 4; constexpr std::uint32_t MAX_PACKED_DIMENSION = 32767; +constexpr std::uint32_t INPUT_BLOCK_SIZE = 0x800; + +constexpr std::uint16_t OPERATION_PARSE = 0x10; +constexpr std::uint16_t OPERATION_DECODE = 0x20; struct Context { Context* self; @@ -144,66 +156,54 @@ std::span toBytes(const void* data, std::uint32_t size) { return {static_cast(data), size}; } -} // namespace +struct DecodeTarget { + void* image; + std::uint32_t imageSize; + const void* coefficient; + std::uint32_t coefficientSize; + std::uint32_t downScale; + std::uint16_t pixelFormat; + std::uint16_t alpha; + std::uint32_t pitch; +}; -extern "C" { +std::uint64_t imagePitch(const DecodeTarget& target, std::uint32_t outputWidth) { + const std::uint64_t rowSize = static_cast(outputWidth) * BYTES_PER_PIXEL; + return target.pitch == 0 ? rowSize : target.pitch; +} -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 (!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; +std::int32_t checkTarget(const Context& context, const Decoder::Jpeg::Header& header, const DecodeTarget& target) { + const std::uint32_t downScale = target.downScale; + JpegDecImageInfo parsedInfo{}; + if (!fillImageInfo(header, downScale, &parsedInfo)) return SCE_JPEG_DEC_ERROR_COEFFICIENT_MEMORY; + if (parsedInfo.coefficient_mem_size != 0 && (!target.coefficient || target.coefficientSize < parsedInfo.coefficient_mem_size)) return SCE_JPEG_DEC_ERROR_COEFFICIENT_MEMORY; + 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 = imagePitch(target, outputWidth); + if (target.pitch != 0) { + if (pitch < rowSize || pitch % 4 != 0 || pitch * outputHeight >= 0x80000000 || pitch * outputHeight > target.imageSize) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + } else if (pitch * outputHeight > target.imageSize) { + 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; return 0; } -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; - 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 (!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{}; - if (!fillImageInfo(*header, downScale, &parsedInfo)) return SCE_JPEG_DEC_ERROR_COEFFICIENT_MEMORY; - if (parsedInfo.coefficient_mem_size != 0 && (!param->coefficient_mem_addr || param->coefficient_mem_size < parsedInfo.coefficient_mem_size)) return SCE_JPEG_DEC_ERROR_COEFFICIENT_MEMORY; - 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 (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; +std::int32_t writeImage(const Decoder::Jpeg::Header& header, std::span jpeg, const DecodeTarget& target, JpegDecImageInfo* imageInfo) { + const std::uint32_t downScale = target.downScale; + 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 pitch = imagePitch(target, outputWidth); 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; - const bool swapRedBlue = param->pixel_format == PIXEL_FORMAT_B8G8R8A8; - const auto alpha = static_cast(param->alpha_value); - auto* output = static_cast(param->image_mem_addr); + const bool swapRedBlue = target.pixelFormat == PIXEL_FORMAT_B8G8R8A8; + const auto alpha = static_cast(target.alpha); + auto* output = static_cast(target.image); for (std::uint32_t y = 0; y < image->height; ++y) { std::uint8_t* out = output + y * pitch; for (std::uint32_t x = 0; x < image->width; ++x) { @@ -223,14 +223,211 @@ int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param } } - if (imageInfo) fillImageInfo(*header, downScale, imageInfo); + if (imageInfo) fillImageInfo(header, downScale, imageInfo); if (outputWidth > MAX_PACKED_DIMENSION || outputHeight > MAX_PACKED_DIMENSION) return 0; return static_cast(outputWidth << 16 | outputHeight); } +struct InputStream { + JpegDecInputCallback input = nullptr; + void* argument = nullptr; + const std::uint8_t* chunk = nullptr; + std::uint32_t chunkSize = 0; + std::int32_t status = 0; + std::vector data; + std::optional header; + std::uint32_t downScale = 0; + std::size_t scanOffset = 2; + bool inScan = false; + std::optional end; + std::atomic busy = false; +}; + +std::mutex streamMutex; +std::unordered_map> streams; + +class StreamUse { +public: + explicit StreamUse(std::shared_ptr stream) : stream(std::move(stream)) { + if (this->stream->busy.exchange(true)) throw std::runtime_error("sceJpegDecDecodeWithInputControl: a call on a handle whose input stream another call is still reading is not implemented"); + } + ~StreamUse() { stream->busy = false; } + StreamUse(const StreamUse&) = delete; + StreamUse& operator=(const StreamUse&) = delete; + InputStream& operator*() const { return *stream; } + +private: + std::shared_ptr stream; +}; + +void forgetStream(const Context* context) { + std::lock_guard lock(streamMutex); + streams.erase(context); +} + +void pull(InputStream& stream, std::uint32_t size) { + while (size > 0 && stream.status == 0) { + if (stream.chunkSize == 0) { + JpegDecInputChunk chunk{}; + const std::int32_t result = stream.input(&chunk, stream.argument); + if (result < 0 || !chunk.data || chunk.size == 0) { + stream.status = result < 0 ? result : 1; + return; + } + stream.chunk = static_cast(chunk.data); + stream.chunkSize = chunk.size; + } + const std::uint32_t count = std::min(size, stream.chunkSize); + stream.data.insert(stream.data.end(), stream.chunk, stream.chunk + count); + stream.chunk += count; + stream.chunkSize -= count; + size -= count; + } +} + +bool endFound(InputStream& stream) { + const std::vector& data = stream.data; + std::size_t& at = stream.scanOffset; + while (true) { + if (stream.inScan) { + while (at + 1 < data.size() && !(data[at] == 0xFF && data[at + 1] != 0x00 && data[at + 1] != 0xFF && (data[at + 1] < 0xD0 || data[at + 1] > 0xD7))) ++at; + if (at + 1 >= data.size()) return false; + stream.inScan = false; + } + if (at + 1 >= data.size()) return false; + if (data[at] != 0xFF) return true; + const std::uint8_t marker = data[at + 1]; + if (marker == 0xFF) { + ++at; + continue; + } + if (marker == 0xD9) { + stream.end = at + 2; + return true; + } + if (marker == 0x01 || (marker >= 0xD0 && marker <= 0xD8)) { + at += 2; + continue; + } + if (at + 3 >= data.size()) return false; + const std::size_t length = (static_cast(data[at + 2]) << 8) | data[at + 3]; + if (length < 2) return true; + if (at + 2 + length > data.size()) return false; + at += 2 + length; + if (marker == 0xDA) stream.inScan = true; + } +} + +bool mayBecomeHeader(InputStream& stream) { + if (stream.data.size() < 2) return true; + if (stream.data[0] != 0xFF || stream.data[1] != 0xD8) return false; + return !endFound(stream); +} + +std::int32_t parseStream(const Context* context, const JpegDecInputControlParam* param, JpegDecImageInfo* imageInfo) { + if (!imageInfo) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + requireDecodeMode("sceJpegDecDecodeWithInputControl", param->operation & 1); + if (!validDecodeOptions(0, param->down_scale)) throw std::runtime_error("sceJpegDecDecodeWithInputControl: the result for this down scale is not known"); + auto created = std::make_shared(); + const StreamUse use(created); + { + std::lock_guard lock(streamMutex); + streams[context] = std::move(created); + } + InputStream& stream = *use; + stream.input = param->input; + stream.argument = param->input_argument; + while (!(stream.header = Decoder::Jpeg::ParseHeader(stream.data)) && stream.status == 0 && mayBecomeHeader(stream)) pull(stream, INPUT_BLOCK_SIZE); + if (stream.status < 0) return stream.status; + if (!stream.header) return SCE_JPEG_DEC_ERROR_DECODE_ERROR; + stream.downScale = param->down_scale; + if (!fillImageInfo(*stream.header, stream.downScale, imageInfo)) return SCE_JPEG_DEC_ERROR_COEFFICIENT_MEMORY; + return 0; +} + +std::int32_t decodeStream(const Context* context, const JpegDecInputControlParam* param) { + if (!param->image_mem_addr) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + const std::uint16_t format = param->pixel_format; + if ((format == 0 || format == 1 || format == 9 || format == 10) && (reinterpret_cast(param->image_mem_addr) & 3) != 0) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + if (param->image_mem_size == 0) return SCE_JPEG_DEC_ERROR_INVALID_SIZE; + if (!validDecodeOptions(0, param->down_scale)) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + if (format != 0 && format != 1 && format != 9 && format != 10 && format != 11) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + if (param->alpha_value > 255) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + requireDecodeMode("sceJpegDecDecodeWithInputControl", param->operation & 1); + if (format != PIXEL_FORMAT_R8G8B8A8 && format != PIXEL_FORMAT_B8G8R8A8) throw std::runtime_error("sceJpegDecDecodeWithInputControl: pixel format is not implemented"); + std::shared_ptr found; + { + std::lock_guard lock(streamMutex); + const auto entry = streams.find(context); + if (entry != streams.end()) found = entry->second; + } + if (!found) throw std::runtime_error("sceJpegDecDecodeWithInputControl: decoding without a parsed header is not implemented"); + const StreamUse use(std::move(found)); + InputStream& stream = *use; + if (!stream.header) throw std::runtime_error("sceJpegDecDecodeWithInputControl: decoding without a parsed header is not implemented"); + if (param->down_scale != stream.downScale) throw std::runtime_error("sceJpegDecDecodeWithInputControl: decoding at another down scale than the parse is not implemented"); + stream.status = 0; + stream.input = param->input; + stream.argument = param->input_argument; + const DecodeTarget target{param->image_mem_addr, param->image_mem_size, param->coefficient_mem_addr, param->coefficient_mem_size, + param->down_scale, format, param->alpha_value, param->image_pitch}; + const std::int32_t checked = checkTarget(*context, *stream.header, target); + if (checked != 0) return checked; + while (!stream.end && !endFound(stream) && stream.status == 0) pull(stream, INPUT_BLOCK_SIZE); + if (stream.status < 0) return stream.status; + const std::span jpeg(stream.data.data(), stream.end.value_or(stream.data.size())); + return writeImage(*stream.header, jpeg, target, nullptr); +} + +} // namespace + +extern "C" { + +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 (!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); + forgetStream(context); + *handle = context; + return 0; +} + +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; + 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 (!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::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; + const DecodeTarget target{param->image_mem_addr, param->image_mem_size, param->coefficient_mem_addr, param->coefficient_mem_size, + param->down_scale, param->pixel_format, param->alpha_value, param->image_pitch}; + const std::int32_t checked = checkTarget(*context, *header, target); + if (checked != 0) return checked; + return writeImage(*header, jpeg, target, imageInfo); +} + int32_t APS5_VABI sceJpegDecDelete(void* handle) { Context* context = toContext(handle); if (!context) return SCE_JPEG_DEC_ERROR_INVALID_HANDLE; + forgetStream(context); context->self = nullptr; return 0; } @@ -255,9 +452,13 @@ int32_t APS5_VABI sceJpegDecQueryMemorySize(const JpegDecCreateParam* param) { return static_cast(workMemorySize(*param)); } -int APS5_VABI sceJpegDecDecodeWithInputControl() { - NotImplemented_nid_no_patch(__func__); - return 0; +int32_t APS5_VABI sceJpegDecDecodeWithInputControl(void* handle, const JpegDecInputControlParam* param, JpegDecImageInfo* imageInfo) { + const Context* context = toContext(handle); + if (!context) return SCE_JPEG_DEC_ERROR_INVALID_HANDLE; + if (!param || !param->input) return SCE_JPEG_DEC_ERROR_INVALID_ADDR; + const std::uint16_t operation = param->operation & ~std::uint16_t{1}; + if ((operation != OPERATION_PARSE && operation != OPERATION_DECODE) || param->reserved != 0) return SCE_JPEG_DEC_ERROR_INVALID_PARAM; + return operation == OPERATION_PARSE ? parseStream(context, param, imageInfo) : decodeStream(context, param); } } diff --git a/core/libs/prx/libSceJpegDec/include/Api.hpp b/core/libs/prx/libSceJpegDec/include/Api.hpp index 0f0892be0a..4522cfc832 100644 --- a/core/libs/prx/libSceJpegDec/include/Api.hpp +++ b/core/libs/prx/libSceJpegDec/include/Api.hpp @@ -9,6 +9,6 @@ int32_t APS5_VABI sceJpegDecDecode(void* handle, const JpegDecDecodeParam* param 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(); +int32_t APS5_VABI sceJpegDecDecodeWithInputControl(void* handle, const JpegDecInputControlParam* param, JpegDecImageInfo* imageInfo); } diff --git a/core/libs/prx/libSceJson2/CMakeLists.txt b/core/libs/prx/libSceJson2/CMakeLists.txt index e7b2794541..391e67b634 100644 --- a/core/libs/prx/libSceJson2/CMakeLists.txt +++ b/core/libs/prx/libSceJson2/CMakeLists.txt @@ -1,8 +1,256 @@ add_library(libSceJson2 SHARED EXCLUDE_FROM_ALL Export.cpp InventoryExports.cpp) +target_link_libraries(libSceJson2 PRIVATE libkernel libc) target_include_directories(libSceJson2 PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceJson2) +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/Api.hpp") +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/Api.hpp" jsonDeclarations REGEX "_ZN") +set(jsonExports "") +set(jsonWindowsExports "") +foreach(declaration IN LISTS jsonDeclarations) + string(REGEX MATCH "(_ZN[A-Za-z0-9_]+)" match "${declaration}") + set(symbol "${CMAKE_MATCH_1}") + list(APPEND jsonExports "${symbol}") + if(declaration MATCHES "\\(") + list(APPEND jsonWindowsExports "${symbol}") + else() + list(APPEND jsonWindowsExports "${symbol} DATA") + endif() +endforeach() +if(APPLE) + set(exportList "") + foreach(symbol IN LISTS jsonExports) + string(APPEND exportList "_${symbol}\n") + endforeach() + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/json2.exports" "${exportList}") + target_link_options(libSceJson2 PRIVATE "LINKER:-exported_symbols_list,${CMAKE_CURRENT_BINARY_DIR}/json2.exports") +elseif(WIN32) + string(JOIN "\n" exportList ${jsonWindowsExports}) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/json2.def" "EXPORTS\n${exportList}\n") + target_sources(libSceJson2 PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/json2.def") +else() + string(JOIN ";\n" exportList ${jsonExports}) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/json2.exports" "{ global:\n${exportList};\nlocal: *; };\n") + target_link_options(libSceJson2 PRIVATE "LINKER:--version-script=${CMAKE_CURRENT_BINARY_DIR}/json2.exports") +endif() + if(BUILD_TESTING) + add_test_executable(guest_json_legacy_self_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonLegacySelf.cpp Export.cpp) + target_include_directories(guest_json_legacy_self_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_legacy_self_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_legacy_self_tests) + add_test(NAME guest_json_legacy_self COMMAND guest_json_legacy_self_tests) + if(WIN32) + set_tests_properties(guest_json_legacy_self PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + add_test_executable(guest_json_termination_exceptions_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonTerminationExceptions.cpp) + target_include_directories(guest_json_termination_exceptions_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_termination_exceptions_tests PRIVATE libSceJson2 libkernel libc) + configure_windows_unwind(guest_json_termination_exceptions_tests) + add_test_executable(guest_json_legacy_termination_exceptions_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonTerminationExceptions.cpp Export.cpp) + target_compile_definitions(guest_json_legacy_termination_exceptions_tests PRIVATE JSON2_TERMINATION_LEGACY_SDK) + target_include_directories(guest_json_legacy_termination_exceptions_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_legacy_termination_exceptions_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_legacy_termination_exceptions_tests) + foreach(flavor modern legacy) + if(flavor STREQUAL "legacy") + set(exception_target guest_json_legacy_termination_exceptions_tests) + else() + set(exception_target guest_json_termination_exceptions_tests) + endif() + add_test(NAME guest_json_${flavor}_termination_healthy COMMAND ${exception_target} 0 before) + foreach(position RANGE 1 12) + foreach(boundary before after) + add_test(NAME guest_json_${flavor}_termination_exception_${boundary}_${position} COMMAND ${exception_target} ${position} ${boundary}) + if(WIN32 AND flavor STREQUAL "legacy") + set_tests_properties(guest_json_${flavor}_termination_exception_${boundary}_${position} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() + foreach(position 1 5 6 8 9 11 12) + add_test(NAME guest_json_${flavor}_termination_retry_${position} COMMAND ${exception_target} ${position} before retry) + if(WIN32 AND flavor STREQUAL "legacy") + set_tests_properties(guest_json_${flavor}_termination_retry_${position} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() + if(flavor STREQUAL "legacy") + add_test(NAME guest_json_legacy_termination_retry_2 COMMAND ${exception_target} 2 before retry) + add_test(NAME guest_json_legacy_termination_retry_null_after COMMAND ${exception_target} 1 after retry) + if(WIN32) + set_tests_properties(guest_json_legacy_termination_retry_2 guest_json_legacy_termination_retry_null_after PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endif() + if(WIN32 AND flavor STREQUAL "legacy") + set_tests_properties(guest_json_${flavor}_termination_healthy PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() + add_test_executable(guest_json_initialization_exceptions_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationExceptions.cpp) + target_include_directories(guest_json_initialization_exceptions_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_initialization_exceptions_tests PRIVATE libSceJson2 libkernel libc) + configure_windows_unwind(guest_json_initialization_exceptions_tests) + foreach(position RANGE 0 12) + foreach(callback allocate notify) + add_test(NAME guest_json_initialization_exception_${callback}_${position} COMMAND guest_json_initialization_exceptions_tests ${position} ${callback}) + endforeach() + endforeach() + add_test_executable(guest_json_legacy_initialization_exceptions_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationExceptions.cpp Export.cpp) + target_compile_definitions(guest_json_legacy_initialization_exceptions_tests PRIVATE JSON2_EXCEPTION_LEGACY_SDK) + target_include_directories(guest_json_legacy_initialization_exceptions_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_legacy_initialization_exceptions_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_legacy_initialization_exceptions_tests) + foreach(position RANGE 0 11) + foreach(callback allocate notify) + add_test(NAME guest_json_legacy_initialization_exception_${callback}_${position} COMMAND guest_json_legacy_initialization_exceptions_tests ${position} ${callback}) + if(WIN32) + set_tests_properties(guest_json_legacy_initialization_exception_${callback}_${position} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() + add_test_executable(guest_json_legacy_composite_destruction_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCompositeDestruction.cpp Export.cpp) + target_compile_definitions(guest_json_legacy_composite_destruction_tests PRIVATE JSON2_DESTRUCTION_LEGACY_SDK) + target_include_directories(guest_json_legacy_composite_destruction_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_legacy_composite_destruction_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_legacy_composite_destruction_tests) + add_test(NAME guest_json_legacy_composite_destruction COMMAND guest_json_legacy_composite_destruction_tests) + if(WIN32) + set_tests_properties(guest_json_legacy_composite_destruction PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + add_test_executable(guest_json_composite_destruction_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCompositeDestruction.cpp) + target_include_directories(guest_json_composite_destruction_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_composite_destruction_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_composite_destruction_tests) + add_test(NAME guest_json_composite_destruction COMMAND guest_json_composite_destruction_tests) + add_test_executable(guest_json_legacy_initialization_publication_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationPublication.cpp Export.cpp) + target_compile_definitions(guest_json_legacy_initialization_publication_tests PRIVATE JSON2_PUBLICATION_LEGACY_SDK) + target_include_directories(guest_json_legacy_initialization_publication_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_legacy_initialization_publication_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_legacy_initialization_publication_tests) + add_test(NAME guest_json_legacy_initialization_publication COMMAND guest_json_legacy_initialization_publication_tests) + if(WIN32) + set_tests_properties(guest_json_legacy_initialization_publication PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + add_test_executable(guest_json_initialization_publication_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationPublication.cpp) + target_include_directories(guest_json_initialization_publication_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_initialization_publication_tests PRIVATE libSceJson2 libkernel libc) + configure_windows_unwind(guest_json_initialization_publication_tests) + add_test(NAME guest_json_initialization_publication COMMAND guest_json_initialization_publication_tests) + add_test_executable(guest_json_callback_lifecycle_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCallbackLifecycle.cpp) + target_include_directories(guest_json_callback_lifecycle_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_callback_lifecycle_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_callback_lifecycle_tests) + add_test(NAME guest_json_callback_lifecycle COMMAND guest_json_callback_lifecycle_tests) + add_test_executable(guest_json_callback_copies_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCallbackCopies.cpp) + target_include_directories(guest_json_callback_copies_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_callback_copies_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_callback_copies_tests) + add_test(NAME guest_json_callback_copies COMMAND guest_json_callback_copies_tests) + add_test_executable(guest_json_pair_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonPairAllocator.cpp) + target_include_directories(guest_json_pair_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_pair_allocator_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_pair_allocator_tests) + add_test(NAME guest_json_pair_allocator COMMAND guest_json_pair_allocator_tests) + add_test_executable(guest_json_pair_layout_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonPairLayout.cpp) + target_include_directories(guest_json_pair_layout_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_pair_layout_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_pair_layout_tests) + add_test(NAME guest_json_pair_layout COMMAND guest_json_pair_layout_tests) + add_test_executable(guest_json_kernel_initialization_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonKernelInitialization.cpp Export.cpp) + target_include_directories(guest_json_kernel_initialization_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_kernel_initialization_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_kernel_initialization_tests) + add_test(NAME guest_json_kernel_initialization COMMAND guest_json_kernel_initialization_tests) + if(WIN32) + set_tests_properties(guest_json_kernel_initialization PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + add_test_executable(guest_json_inherited_recovery_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInheritedRecovery.cpp) + target_include_directories(guest_json_inherited_recovery_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_inherited_recovery_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_inherited_recovery_tests) + add_test(NAME guest_json_inherited_recovery COMMAND guest_json_inherited_recovery_tests) + add_test_executable(guest_json_initialization_release_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationRelease.cpp) + target_include_directories(guest_json_initialization_release_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_initialization_release_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_initialization_release_tests) + add_test(NAME guest_json_initialization_release COMMAND guest_json_initialization_release_tests) + add_test_executable(guest_json_legacy_initialization_release_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInitializationRelease.cpp Export.cpp) + target_compile_definitions(guest_json_legacy_initialization_release_tests PRIVATE JSON2_LEGACY_TEST) + target_include_directories(guest_json_legacy_initialization_release_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_legacy_initialization_release_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_legacy_initialization_release_tests) + add_test(NAME guest_json_legacy_initialization_release COMMAND guest_json_legacy_initialization_release_tests) + if(WIN32) + set_tests_properties(guest_json_initialization_release guest_json_legacy_initialization_release PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + add_test_executable(guest_json_recovery_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonRecoveryAllocator.cpp) + target_include_directories(guest_json_recovery_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_recovery_allocator_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_recovery_allocator_tests) + add_test(NAME guest_json_recovery_allocator COMMAND guest_json_recovery_allocator_tests) + add_test_executable(guest_json_parser_metadata_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonParserMetadata.cpp) + target_include_directories(guest_json_parser_metadata_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_parser_metadata_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_parser_metadata_tests) + add_test(NAME guest_json_parser_metadata COMMAND guest_json_parser_metadata_tests) + add_test_executable(guest_json_decimal_range_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonDecimalRange.cpp) + target_include_directories(guest_json_decimal_range_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_decimal_range_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_decimal_range_tests) + add_test(NAME guest_json_decimal_range COMMAND guest_json_decimal_range_tests) + add_test_executable(guest_json_serialization_callback_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonSerializationCallback.cpp) + target_include_directories(guest_json_serialization_callback_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_serialization_callback_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_serialization_callback_tests) + add_test(NAME guest_json_serialization_callback COMMAND guest_json_serialization_callback_tests) + add_test_executable(guest_json_key_lookup_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonKeyLookup.cpp) + target_include_directories(guest_json_key_lookup_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_key_lookup_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_key_lookup_tests) + add_test(NAME guest_json_key_lookup COMMAND guest_json_key_lookup_tests) + add_test_executable(guest_json_array_insertion_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonArrayInsertion.cpp) + target_include_directories(guest_json_array_insertion_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_array_insertion_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_array_insertion_tests) + add_test(NAME guest_json_array_insertion COMMAND guest_json_array_insertion_tests) + add_test_executable(guest_json_array_mutation_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonArrayMutation.cpp) + target_include_directories(guest_json_array_mutation_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_array_mutation_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_array_mutation_tests) + add_test(NAME guest_json_array_mutation COMMAND guest_json_array_mutation_tests) + add_test_executable(guest_json_internal_initializer_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonInternalInitializer.cpp) + target_include_directories(guest_json_internal_initializer_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_internal_initializer_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_internal_initializer_tests) + add_test(NAME guest_json_internal_initializer COMMAND guest_json_internal_initializer_tests) + add_test_executable(guest_json_allocation_entry_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonAllocationEntry.cpp) + target_include_directories(guest_json_allocation_entry_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_allocation_entry_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_allocation_entry_tests) + add_test(NAME guest_json_allocation_entry COMMAND guest_json_allocation_entry_tests) + add_test_executable(guest_json_string_search_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonStringSearch.cpp) + target_include_directories(guest_json_string_search_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_string_search_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_string_search_tests) + add_test(NAME guest_json_string_search COMMAND guest_json_string_search_tests) + add_test_executable(guest_json_reference_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonReferences.cpp) + target_include_directories(guest_json_reference_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_reference_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_reference_tests) + add_test(NAME guest_json_references COMMAND guest_json_reference_tests) + add_test_executable(guest_json_alias_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonAliases.cpp) + target_include_directories(guest_json_alias_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_alias_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_alias_tests) + add_test(NAME guest_json_aliases COMMAND guest_json_alias_tests) add_test_executable(guest_json_tests ${LIBS_INCLUDE_DIR}/tests/GuestJson.cpp) target_include_directories(guest_json_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_json_tests PRIVATE libSceJson2 libc) @@ -10,6 +258,76 @@ if(BUILD_TESTING) 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) + add_test_executable(guest_json_float_settings_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonFloatSettings.cpp) + target_include_directories(guest_json_float_settings_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_float_settings_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_float_settings_tests) + add_test(NAME guest_json_float_settings COMMAND guest_json_float_settings_tests) + add_test_executable(guest_json_allocator_settings_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonAllocatorSettings.cpp) + target_include_directories(guest_json_allocator_settings_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_allocator_settings_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_allocator_settings_tests) + add_test(NAME guest_json_allocator_settings COMMAND guest_json_allocator_settings_tests) + add_test_executable(guest_json_parser_state_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonParserState.cpp) + target_include_directories(guest_json_parser_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_parser_state_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_parser_state_tests) + add_test(NAME guest_json_parser_state COMMAND guest_json_parser_state_tests) + add_test_executable(guest_json_value_layout_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonValueLayout.cpp) + target_include_directories(guest_json_value_layout_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_value_layout_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_value_layout_tests) + add_test(NAME guest_json_value_layout COMMAND guest_json_value_layout_tests) + add_test_executable(guest_json_value_ownership_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonValueOwnership.cpp) + target_include_directories(guest_json_value_ownership_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_value_ownership_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_value_ownership_tests) + add_test(NAME guest_json_value_ownership COMMAND guest_json_value_ownership_tests) + add_test_executable(guest_json_special_float_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonSpecialFloat.cpp) + target_include_directories(guest_json_special_float_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_special_float_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_special_float_tests) + add_test(NAME guest_json_special_float COMMAND guest_json_special_float_tests) + add_test_executable(guest_json_element_recovery_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonElementRecovery.cpp) + target_include_directories(guest_json_element_recovery_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_element_recovery_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_element_recovery_tests) + add_test(NAME guest_json_element_recovery COMMAND guest_json_element_recovery_tests) + add_test_executable(guest_json_null_recovery_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonNullRecovery.cpp) + target_include_directories(guest_json_null_recovery_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_null_recovery_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_null_recovery_tests) + add_test(NAME guest_json_null_recovery COMMAND guest_json_null_recovery_tests) + add_test_executable(guest_json_global_recovery_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonGlobalRecovery.cpp) + target_include_directories(guest_json_global_recovery_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_global_recovery_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_global_recovery_tests) + add_test(NAME guest_json_global_recovery COMMAND guest_json_global_recovery_tests) + add_test_executable(guest_json_string_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonStringAllocator.cpp) + target_include_directories(guest_json_string_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_string_allocator_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_string_allocator_tests) + add_test(NAME guest_json_string_allocator COMMAND guest_json_string_allocator_tests) + add_test_executable(guest_json_array_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonArrayAllocator.cpp) + target_include_directories(guest_json_array_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_array_allocator_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_array_allocator_tests) + add_test(NAME guest_json_array_allocator COMMAND guest_json_array_allocator_tests) + add_test_executable(guest_json_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonAllocator.cpp) + target_include_directories(guest_json_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_allocator_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_allocator_tests) + add_test(NAME guest_json_allocator COMMAND guest_json_allocator_tests) + add_test_executable(guest_json_value_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonValueAllocator.cpp) + target_include_directories(guest_json_value_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_value_allocator_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_value_allocator_tests) + add_test(NAME guest_json_value_allocator COMMAND guest_json_value_allocator_tests) + add_test_executable(guest_json_object_allocator_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonObjectAllocator.cpp) + target_include_directories(guest_json_object_allocator_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_object_allocator_tests PRIVATE libSceJson2 libc) + configure_windows_unwind(guest_json_object_allocator_tests) + add_test(NAME guest_json_object_allocator COMMAND guest_json_object_allocator_tests) 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) @@ -20,23 +338,130 @@ if(BUILD_TESTING) ENVIRONMENT "LOCPATH=${numeric_locale_dir}") endif() if(WIN32) - set_tests_properties(guest_json guest_json_numeric_locale PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") + get_property(json2_module_exception_tests DIRECTORY PROPERTY TESTS) + list(FILTER json2_module_exception_tests INCLUDE REGEX "^guest_json_(modern_termination_|initialization_exception_)") + set_tests_properties(${json2_module_exception_tests} guest_json_composite_destruction PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$") + set_tests_properties(guest_json guest_json_initialization_publication guest_json_callback_lifecycle guest_json_callback_copies guest_json_pair_allocator guest_json_pair_layout guest_json_inherited_recovery guest_json_recovery_allocator guest_json_parser_metadata guest_json_decimal_range guest_json_serialization_callback guest_json_key_lookup guest_json_array_insertion guest_json_array_mutation guest_json_internal_initializer guest_json_allocation_entry guest_json_string_search guest_json_references guest_json_aliases guest_json_parser_state guest_json_numeric_locale guest_json_float_settings guest_json_allocator_settings guest_json_value_layout guest_json_value_ownership guest_json_special_float guest_json_element_recovery guest_json_null_recovery guest_json_global_recovery guest_json_string_allocator guest_json_array_allocator guest_json_object_allocator guest_json_value_allocator guest_json_allocator PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$;PATH=path_list_prepend:$") endif() endif() if(BUILD_TESTING) add_test_executable(guest_json_copy_failure_tests ${LIBS_INCLUDE_DIR}/tests/JsonCopyFailure.cpp Export.cpp) target_include_directories(guest_json_copy_failure_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_copy_failure_tests PRIVATE libkernel libc) target_compile_features(guest_json_copy_failure_tests PRIVATE cxx_std_23) if(WIN32) - target_link_libraries(guest_json_copy_failure_tests PRIVATE libc) configure_windows_unwind(guest_json_copy_failure_tests) endif() add_test(NAME guest_json_copy_failure COMMAND guest_json_copy_failure_tests) set_tests_properties(guest_json_copy_failure PROPERTIES TIMEOUT 30) if(WIN32) set_tests_properties(guest_json_copy_failure PROPERTIES ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_json_float_failure_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonFloatFailure.cpp Export.cpp) + target_include_directories(guest_json_float_failure_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_float_failure_tests PRIVATE libkernel libc) + if(WIN32) + configure_windows_unwind(guest_json_float_failure_tests) + endif() + add_test(NAME guest_json_float_failure COMMAND guest_json_float_failure_tests) + set_tests_properties(guest_json_float_failure PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_json_float_failure PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_json_file_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonFile.cpp Export.cpp) + target_include_directories(guest_json_file_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_file_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_file_tests) + add_test(NAME guest_json_file COMMAND guest_json_file_tests) + set_tests_properties(guest_json_file PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_json_file PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_json_legacy_handlers_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonLegacyHandlers.cpp Export.cpp) + target_include_directories(guest_json_legacy_handlers_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_legacy_handlers_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_legacy_handlers_tests) + add_test(NAME guest_json_legacy_handlers COMMAND guest_json_legacy_handlers_tests) + foreach(position RANGE 0 2) + foreach(alias RANGE 0 1) + foreach(wrapper direct wrapped) + add_test(NAME guest_json_legacy_object_heap_exception_${position}_${alias}_${wrapper} COMMAND guest_json_legacy_handlers_tests object-heap ${position} ${alias} ${wrapper}) + if(WIN32) + set_tests_properties(guest_json_legacy_object_heap_exception_${position}_${alias}_${wrapper} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() + endforeach() + foreach(position RANGE 0 2) + foreach(alias RANGE 0 1) + add_test(NAME guest_json_legacy_array_heap_exception_${position}_${alias} COMMAND guest_json_legacy_handlers_tests array-heap ${position} ${alias}) + add_test(NAME guest_json_legacy_value_array_heap_exception_${position}_${alias} COMMAND guest_json_legacy_handlers_tests value-array-heap ${position} ${alias}) + if(WIN32) + set_tests_properties(guest_json_legacy_array_heap_exception_${position}_${alias} guest_json_legacy_value_array_heap_exception_${position}_${alias} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() + foreach(shape RANGE 0 3) + foreach(alias RANGE 0 2) + add_test(NAME guest_json_legacy_copy_heap_exception_${shape}_${alias} COMMAND guest_json_legacy_handlers_tests ${shape} ${alias}) + if(WIN32) + set_tests_properties(guest_json_legacy_copy_heap_exception_${shape}_${alias} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() + foreach(position RANGE 0 2) + add_test(NAME guest_json_legacy_heap_exception_${position} COMMAND guest_json_legacy_handlers_tests ${position}) + if(WIN32) + set_tests_properties(guest_json_legacy_heap_exception_${position} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() + endforeach() + if(WIN32) + set_tests_properties(guest_json_legacy_handlers PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_json_legacy_serialization_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonLegacySerialization.cpp Export.cpp) + target_include_directories(guest_json_legacy_serialization_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_legacy_serialization_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_json_legacy_serialization_tests) + add_test(NAME guest_json_legacy_serialization COMMAND guest_json_legacy_serialization_tests) + if(WIN32) + set_tests_properties(guest_json_legacy_serialization PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + find_package(Threads REQUIRED) + add_test_executable(guest_json_callback_threads_tests ${LIBS_INCLUDE_DIR}/tests/GuestJsonCallbackThreads.cpp Export.cpp) + target_include_directories(guest_json_callback_threads_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_json_callback_threads_tests PRIVATE libkernel libc Threads::Threads) + configure_windows_unwind(guest_json_callback_threads_tests) + add_test(NAME guest_json_callback_threads COMMAND guest_json_callback_threads_tests) + set_tests_properties(guest_json_callback_threads PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_json_callback_threads PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libSceJson2/Export.cpp b/core/libs/prx/libSceJson2/Export.cpp index 18ddc3103b..25ade15f99 100644 --- a/core/libs/prx/libSceJson2/Export.cpp +++ b/core/libs/prx/libSceJson2/Export.cpp @@ -1,5 +1,7 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libSceJson2/include/Api.hpp" #include +#include #include #include #include @@ -7,32 +9,132 @@ #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" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" namespace { using namespace SceJson2; constexpr int JsonErrorParse = static_cast(0x80848101); +constexpr int JsonErrorOutOfMemory = static_cast(0x80848102); +constexpr int JsonErrorNotInitialized = static_cast(0x80848110); +constexpr int JsonErrorAlreadyInitialized = static_cast(0x80848111); +constexpr int JsonErrorSpecialFloat = static_cast(0x80848114); +constexpr int JsonErrorInvalidArgument = static_cast(0x80848120); -static_assert(sizeof(Value) == 8 && sizeof(String) == 8 && sizeof(Array) == 8 && sizeof(Object) == 8); +struct Handlers; +struct LegacyHandlerMap; + +static_assert(sizeof(Text) == 24); + +struct ArrayLink; + +struct ObjectLink; + +} + +struct SceJson2::ArrayIteratorState { + ArrayLink* position; +}; + +struct SceJson2::ObjectIteratorState { + ObjectLink* position; +}; + +namespace { + +struct Handlers { + TypeMismatchHandler typeMismatch = nullptr; + void* typeMismatchContext = nullptr; + SpecialFloatHandler specialFloat = nullptr; + void* specialFloatContext = nullptr; + ElementAccessFailureHandler elementAccessFailure = nullptr; + void* elementAccessFailureContext = nullptr; +}; + +using Node = Value; +static_assert(offsetof(Value, parent) == 0 && offsetof(Value, auxiliary) == 8); +static_assert(offsetof(Value, payload) == 16 && offsetof(Value, type) == 28); + +static_assert(sizeof(Value) == 32 && sizeof(String) == 8 && sizeof(Array) == 8 && sizeof(Object) == 8); static_assert(sizeof(ArrayIterator) == 8 && sizeof(ObjectIterator) == 8); static_assert(offsetof(Pair, value) == 0x10); +template +struct HandlerRecord { TCallback callback = nullptr; void* context = nullptr; }; + +} + +struct SceJson2::Auxiliary { + NullAccessCallback nullAccess = nullptr; + void* nullAccessContext = nullptr; + Value* fallback = nullptr; + HandlerRecord* typeMismatch = nullptr; + HandlerRecord* specialFloat = nullptr; + HandlerRecord* elementAccessFailure = nullptr; + std::uint64_t reserved[3]{}; +}; + +namespace { + +static_assert(sizeof(Auxiliary) == 72 && offsetof(Auxiliary, fallback) == 16); +static_assert(offsetof(Auxiliary, typeMismatch) == 24 && offsetof(Auxiliary, specialFloat) == 32); +static_assert(offsetof(Auxiliary, elementAccessFailure) == 40); + +using RttiAllocate = void* (APS5_VABI*)(std::size_t, void*); +using RttiFree = void (APS5_VABI*)(void*, void*); +using RttiNotify = void (APS5_VABI*)(int, std::size_t, void*); + +struct AllocParamRtti { + RttiAllocate allocate; + RttiFree free; + RttiNotify notify; +}; + +struct AllocationContext { + void* allocator = nullptr; + void* userData = nullptr; + std::size_t fileBufferSize = 0; +}; +static_assert(sizeof(AllocationContext) == 24); + struct Globals { - std::mutex mutex; + std::recursive_mutex mutex; NullAccessCallback callback = nullptr; void* context = nullptr; + std::int32_t floatFormat = 2; + bool floatFormatEnabled = false; + bool paddedStringGrowth = false; + bool initialized = false; + bool terminating = false; + AllocParamRtti rtti{}; + Handlers handlers{}; + AllocationContext* allocation = nullptr; + Value* fallback = nullptr; + LegacyHandlerMap* legacyHandlers = nullptr; + PthreadMutex* internalMutex = nullptr; + PthreadMutex* recoveryMutex = nullptr; + bool internalMutexRetired = false; + bool recoveryMutexRetired = false; + std::uint8_t retiredDefaultHeaders = 0; + std::recursive_mutex callbackMutex; + bool callbackActive = false; + std::uint32_t allocationDepth = 0; + bool allocationFailed = false; }; Globals& State() { @@ -40,189 +142,1648 @@ Globals& State() { return globals; } -void Construct(String& s, const std::string& text) { s.text = new std::string(text); } -void Construct(Array& a) { a.items = new std::list(); } -void Construct(Object& o) { o.items = new std::list(); } +thread_local std::uint32_t allocatorCallbackDepth = 0; + +struct AllocatorCallbackScope { + AllocatorCallbackScope() { ++allocatorCallbackDepth; } + ~AllocatorCallbackScope() { --allocatorCallbackDepth; } + AllocatorCallbackScope(const AllocatorCallbackScope&) = delete; + AllocatorCallbackScope& operator=(const AllocatorCallbackScope&) = delete; +}; + +struct TerminationScope { + bool& active; + explicit TerminationScope(bool& value) : active(value) { active = true; } + ~TerminationScope() { active = false; } + TerminationScope(const TerminationScope&) = delete; + TerminationScope& operator=(const TerminationScope&) = delete; +}; + +struct KernelMutexLock { + PthreadMutex* mutex; + explicit KernelMutexLock(PthreadMutex* value) : mutex(value) { + if (mutex && scePthreadMutexLock(mutex)) NotImplemented_nid_no_patch(__func__); + } + ~KernelMutexLock() { if (mutex) scePthreadMutexUnlock(mutex); } + KernelMutexLock(const KernelMutexLock&) = delete; + KernelMutexLock& operator=(const KernelMutexLock&) = delete; +}; + +class AllocationOperation { +public: + AllocationOperation() { + auto& state = State(); + std::lock_guard lock(state.mutex); + ++state.allocationDepth; + } + ~AllocationOperation() { if (active) Finish(); } + AllocationOperation(const AllocationOperation&) = delete; + AllocationOperation& operator=(const AllocationOperation&) = delete; + static bool Failed() { + auto& state = State(); + std::lock_guard lock(state.mutex); + return state.allocationFailed; + } + bool Finish() { + auto& state = State(); + std::lock_guard lock(state.mutex); + const bool failed = state.allocationFailed; + if (--state.allocationDepth == 0) state.allocationFailed = false; + active = false; + return failed; + } +private: + bool active = true; +}; + +struct CallbackScope { + Globals& state = State(); + std::unique_lock lock{state.callbackMutex}; + KernelMutexLock kernelLock{state.recoveryMutex}; + bool entered = !state.callbackActive; + CallbackScope() { if (entered) state.callbackActive = true; } + ~CallbackScope() { if (entered) state.callbackActive = false; } +}; + +AllocParamRtti RttiCallbacks() { + auto& state = State(); + std::lock_guard lock(state.mutex); + return state.rtti; +} + +void* Allocate(const AllocationContext& context, std::size_t size) { + auto* const* table = *static_cast(context.allocator); + const auto allocate = reinterpret_cast(table[2]); + void* memory; + { + AllocatorCallbackScope scope; + memory = allocate(context.allocator, size, context.userData); + } + if (!memory) { + const auto notify = reinterpret_cast(table[4]); + { + AllocatorCallbackScope scope; + notify(context.allocator, JsonErrorOutOfMemory, size, context.userData); + } + auto& state = State(); + std::lock_guard lock(state.mutex); + if (state.allocationDepth) state.allocationFailed = true; + } + return memory; +} + +void Release(const AllocationContext& context, void* memory) { + auto* const* table = *static_cast(context.allocator); + const auto free = reinterpret_cast(table[3]); + AllocatorCallbackScope scope; + free(context.allocator, memory, context.userData); +} + +struct FileReader { + AllocationContext allocation; + void* buffer = nullptr; + int descriptor = -1; + std::size_t available = 0; + std::size_t position = 0; + + ~FileReader() { + if (descriptor >= 0) sceKernelClose(descriptor); + Release(allocation, buffer); + } + + static int APS5_VABI Read(char& character, void* context) { + auto& reader = *static_cast(context); + if (reader.descriptor < 0) { + if (reader.descriptor == -1) return JsonErrorNotInitialized; + if (reader.descriptor == static_cast(0x80020002)) return static_cast(0x80848103); + return reader.descriptor; + } + if (reader.position == reader.available) { + const auto count = sceKernelRead(reader.descriptor, reader.buffer, reader.allocation.fileBufferSize); + if (count < 0) return static_cast(count); + if (!count) return JsonErrorParse; + reader.available = static_cast(count); + reader.position = 0; + } + character = static_cast(reader.buffer)[reader.position++]; + return 0; + } +}; + +void APS5_VABI RttiDestroy(void*) {} + +void APS5_VABI RttiDelete(void* self) { ::operator delete(self); } + +void* APS5_VABI RttiAllocateMemory(void*, std::size_t size, void* context) { + return RttiCallbacks().allocate(size, context); +} + +void APS5_VABI RttiFreeMemory(void*, void* memory, void* context) { + RttiCallbacks().free(memory, context); +} + +void APS5_VABI RttiNotifyError(void*, int error, std::size_t size, void* context) { + const auto callback = RttiCallbacks().notify; + if (callback) callback(error, size, context); + else if (error == JsonErrorOutOfMemory) + std::printf("[libSceJson] Allocation Fail. size = %zu\n", size); + else std::printf("[libSceJson] unknown notification[%#x]\n", static_cast(error)); +} + +void* const RttiAllocatorVtable[] = { + reinterpret_cast(&RttiDestroy), reinterpret_cast(&RttiDelete), + reinterpret_cast(&RttiAllocateMemory), reinterpret_cast(&RttiFreeMemory), + reinterpret_cast(&RttiNotifyError), +}; + +struct RttiAllocator { void* const* vtable = RttiAllocatorVtable; }; +RttiAllocator rttiAllocator; + +char emptyCharacters[1]{}; + +AllocationContext ActiveAllocation() { + auto& state = State(); + std::lock_guard lock(state.mutex); + return state.allocation ? *state.allocation : AllocationContext{}; +} + +void* Allocate(std::size_t size) { + const auto context = ActiveAllocation(); + return context.allocator ? Allocate(context, size) : nullptr; +} + +void Release(void* memory) { + const auto context = ActiveAllocation(); + if (context.allocator) Release(context, memory); +} + +const char* Characters(const String& string) { + return string.text ? string.text->characters : emptyCharacters; +} + +bool SearchEqual(const char* left, const char* right, std::size_t count) { + const auto bounded = std::bit_cast(static_cast(count)); + return bounded <= 0 || std::strncmp(left, right, static_cast(bounded)) == 0; +} + +std::size_t FindText(const String& string, const char* needle, std::size_t position, std::size_t count) { + constexpr auto missing = std::numeric_limits::max(); + if (!string.text) return missing; + const std::size_t length = string.text->length; + if (!count) return position <= length ? position : missing; + if (position >= length || count > length - position) return missing; + if (!needle) { NotImplemented_nid_no_patch(__func__); return missing; } + const char* begin = string.text->characters; + const char* cursor = begin + position; + while (cursor < begin + length) { + const char* candidate = std::strchr(cursor, static_cast(*needle)); + if (!candidate) return missing; + if (SearchEqual(candidate, needle, count)) return static_cast(candidate - begin); + cursor = candidate + 1; + } + return missing; +} + +std::size_t ReverseFindText(const String& string, const char* needle, std::size_t position, std::size_t count) { + constexpr auto missing = std::numeric_limits::max(); + if (!string.text) return missing; + const std::size_t length = string.text->length; + if (!count) return std::min(position, length); + if (count > length) return missing; + if (!needle) { NotImplemented_nid_no_patch(__func__); return missing; } + std::size_t index = std::min(position, length - count); + for (;;) { + const char* candidate = string.text->characters + index; + if (*candidate == *needle && SearchEqual(candidate, needle, count)) return index; + if (!index) return missing; + --index; + } +} + +std::string_view View(const String& string) { + return {Characters(string), string.text ? string.text->length : 0}; +} + +std::uint32_t CompiledSdk() { + static std::atomic lookupFailed{false}; + std::uint32_t sdk = std::numeric_limits::max(); + if (lookupFailed.load(std::memory_order_relaxed)) return sdk; + try { + const auto* process = static_cast(ApplicationProcessParameters_nid_no_patch()); + if (process) std::memcpy(&sdk, process + 0x10, sizeof(sdk)); + } catch (const std::runtime_error&) { + lookupFailed.store(true, std::memory_order_relaxed); + } + return sdk; +} + +bool ModernValues() { + const auto sdk = CompiledSdk(); + return sdk >= 0x06000000 || (sdk >= 0x05508700 && sdk < 0x05510000); +} + +bool Grow(Text& text, std::size_t requested) { + if (requested > std::numeric_limits::max()) + throw std::length_error("sce::Json::String: capacity exceeds 32 bits"); + if (requested <= text.capacity) return true; + std::uint32_t capacity = static_cast(requested); + bool padded; + { + auto& state = State(); + std::lock_guard lock(state.mutex); + padded = state.paddedStringGrowth; + } + if (padded) { + capacity |= 0x10; + if (capacity / 3 < text.capacity / 2) { + const auto grown = std::uint64_t{text.capacity} + text.capacity / 2; + capacity = static_cast(std::min(grown, std::uint64_t{std::numeric_limits::max()})); + } + } + auto* memory = static_cast(Allocate(capacity)); + if (!memory) return false; + std::memcpy(memory, text.characters, text.length); + memory[text.length] = 0; + if (text.characters != emptyCharacters) Release(text.characters); + text.characters = memory; + text.capacity = capacity; + return true; +} + +void ClearText(String& string) { + if (!string.text) return; + if (string.text->characters != emptyCharacters) Release(string.text->characters); + string.text->characters = emptyCharacters; + string.text->length = 0; + string.text->capacity = 0; +} + +void AssignText(String& string, std::string_view bytes, bool preserveBinary = false) { + if (!string.text || bytes.data() == string.text->characters) return; + if (bytes.size() >= std::numeric_limits::max()) + throw std::length_error("sce::Json::String: length exceeds 32 bits"); + ClearText(string); + if (bytes.empty() || (!preserveBinary && !bytes.front())) return; + auto* memory = static_cast(Allocate(bytes.size() + 1)); + if (!memory) return; + std::memcpy(memory, bytes.data(), bytes.size()); + memory[bytes.size()] = 0; + string.text->characters = memory; + string.text->length = static_cast(bytes.size()); + string.text->capacity = static_cast(bytes.size() + 1); +} + +void AppendText(String& string, std::string_view bytes) { + if (!string.text) return; + if (bytes.size() >= std::numeric_limits::max() - string.text->length) + throw std::length_error("sce::Json::String: length exceeds 32 bits"); + const auto source = reinterpret_cast(bytes.data()); + const auto destination = reinterpret_cast(string.text->characters); + const bool aliases = source >= destination && source - destination < string.text->capacity; + const auto offset = source - destination; + if (!Grow(*string.text, string.text->length + bytes.size() + 1)) return; + const char* data = aliases ? string.text->characters + offset : bytes.data(); + std::memmove(string.text->characters + string.text->length, data, bytes.size()); + string.text->length += static_cast(bytes.size()); + string.text->characters[string.text->length] = 0; +} + +void Construct(String& string, std::string_view bytes) { + string.text = static_cast(Allocate(sizeof(Text))); + if (!string.text) return; + string.text->characters = emptyCharacters; + string.text->length = 0; + string.text->capacity = 0; + AssignText(string, bytes); +} + +void Construct(String& string, const String& source) { + if (!source.text) { string.text = nullptr; return; } + Construct(string, std::string_view{}); + AssignText(string, View(source), true); +} +struct GuestOutOfMemory {}; + +void Construct(Value& value); +void Destroy(Value& value); +void CopyInto(Value& destination, const Value& source); + +struct ArrayLink { + ArrayLink* previous; + ArrayLink* next; + Value value; +}; +static_assert(sizeof(ArrayLink) == 48 && offsetof(ArrayLink, value) == 16); + +} + +struct SceJson2::ArrayStorage { + ArrayLink* head; + std::size_t count; + std::uint64_t reserved; + + struct iterator { + using value_type = Value; + using difference_type = std::ptrdiff_t; + using pointer = Value*; + using reference = Value&; + using iterator_category = std::bidirectional_iterator_tag; + ArrayLink* node = nullptr; + Value& operator*() const { return node->value; } + Value* operator->() const { return &node->value; } + iterator& operator++() { node = node->next; return *this; } + iterator operator++(int) { const auto previous = *this; ++*this; return previous; } + iterator& operator--() { node = node->previous; return *this; } + iterator operator--(int) { const auto previous = *this; --*this; return previous; } + bool operator==(const iterator&) const = default; + }; + + Value* Owner() const { + Value* owner = nullptr; + if (ModernValues()) std::memcpy(&owner, reinterpret_cast(this) + 24, sizeof(owner)); + return owner; + } + void SetOwner(Value* owner) { + if (ModernValues()) std::memcpy(reinterpret_cast(this) + 24, &owner, sizeof(owner)); + } + iterator begin() const { return {head ? head->next : nullptr}; } + iterator end() const { return {head}; } + bool empty() const { return !count; } + std::size_t size() const { return count; } + Value& front() { return head->next->value; } + Value& back() { return head->previous->value; } + const Value& front() const { return head->next->value; } + const Value& back() const { return head->previous->value; } + Value& emplace_back() { + if (!head) throw GuestOutOfMemory{}; + auto* link = static_cast(Allocate(sizeof(ArrayLink))); + if (!link) throw GuestOutOfMemory{}; + Construct(link->value); + link->previous = head->previous; + link->next = head; + head->previous->next = link; + head->previous = link; + ++count; + return link->value; + } + void ReleaseContents() { + if (!head) return; + auto* link = head->next; + while (link != head) { + auto* next = link->next; + Destroy(link->value); + Release(link); + link = next; + } + } + void clear() { + ReleaseContents(); + if (head) { head->previous = head; head->next = head; } + count = 0; + } + void PopBack() { + if (!head || !count) return; + auto* link = head->previous; + link->previous->next = head; + head->previous = link->previous; + Destroy(link->value); + Release(link); + --count; + } +}; + +namespace { + +static_assert(sizeof(ArrayStorage) == 24 && sizeof(ArrayIteratorState) == 8); + +ArrayLink* NewArrayHead() { + auto* head = static_cast(Allocate(sizeof(ArrayLink))); + if (head) { head->previous = head; head->next = head; } + return head; +} + +void Construct(Array& array) { + array.items = static_cast(Allocate(ModernValues() ? 32 : 24)); + if (!array.items) return; + array.items->count = 0; + array.items->head = NewArrayHead(); + array.items->SetOwner(nullptr); +} +void Destroy(String& string); +void BindChildren(Value& value); +void AssignNode(Value& destination, const Value& source); +void TransferPayload(Value& destination, Value& source); + +void PoisonText(Text& text) { + text.characters = reinterpret_cast(std::uintptr_t{0xffffffff}); + text.length = std::numeric_limits::max(); + text.capacity = std::numeric_limits::max(); +} + +struct InlineText { + Text text; + InlineText() { text.characters = emptyCharacters; text.length = 0; text.capacity = 0; } + ~InlineText() { String string{&text}; ClearText(string); PoisonText(text); } + InlineText(const InlineText&) = delete; + InlineText& operator=(const InlineText&) = delete; + void Assign(std::string_view bytes) { String string{&text}; AssignText(string, bytes, true); } +}; + +template +void RefreshTreeEndpoints(Node* head) { + auto* first = head->parent; + auto* last = first; + if (!first->nil) { + while (!first->left->nil) first = first->left; + while (!last->right->nil) last = last->right; + } + head->left = first; + head->right = last; +} + +template +void RotateTreeLeft(Node* head, Node* node) { + auto* pivot = node->right; + node->right = pivot->left; + if (!pivot->left->nil) pivot->left->parent = node; + pivot->parent = node->parent; + if (node->parent->nil) head->parent = pivot; + else if (node == node->parent->left) node->parent->left = pivot; + else node->parent->right = pivot; + pivot->left = node; + node->parent = pivot; +} + +template +void RotateTreeRight(Node* head, Node* node) { + auto* pivot = node->left; + node->left = pivot->right; + if (!pivot->right->nil) pivot->right->parent = node; + pivot->parent = node->parent; + if (node->parent->nil) head->parent = pivot; + else if (node == node->parent->right) node->parent->right = pivot; + else node->parent->left = pivot; + pivot->right = node; + node->parent = pivot; +} + +template +void BalanceTreeInsertion(Node* head, Node* node) { + while (node->parent->color == 1) { + auto* parent = node->parent; + auto* grandparent = parent->parent; + if (parent == grandparent->left) { + auto* uncle = grandparent->right; + if (uncle->color == 1) { parent->color = 0; uncle->color = 0; grandparent->color = 1; node = grandparent; } + else { + if (node == parent->right) { node = parent; RotateTreeLeft(head, node); parent = node->parent; grandparent = parent->parent; } + parent->color = 0; grandparent->color = 1; RotateTreeRight(head, grandparent); + } + } else { + auto* uncle = grandparent->left; + if (uncle->color == 1) { parent->color = 0; uncle->color = 0; grandparent->color = 1; node = grandparent; } + else { + if (node == parent->left) { node = parent; RotateTreeRight(head, node); parent = node->parent; grandparent = parent->parent; } + parent->color = 0; grandparent->color = 1; RotateTreeLeft(head, grandparent); + } + } + } + head->parent->color = 0; +} + +template +void ReplaceTreeNode(Node* head, Node* node, Node* replacement) { + if (node->parent->nil) head->parent = replacement; + else if (node == node->parent->left) node->parent->left = replacement; + else node->parent->right = replacement; + if (!replacement->nil) replacement->parent = node->parent; +} + +template +void BalanceTreeErasure(Node* head, Node* node, Node* parent) { + while (node != head->parent && node->color == 0) { + if (node == parent->left) { + auto* sibling = parent->right; + if (sibling->color == 1) { sibling->color = 0; parent->color = 1; RotateTreeLeft(head, parent); sibling = parent->right; } + if (sibling->left->color == 0 && sibling->right->color == 0) { + if (!sibling->nil) sibling->color = 1; + node = parent; parent = node->parent; + } else { + if (sibling->right->color == 0) { sibling->left->color = 0; sibling->color = 1; RotateTreeRight(head, sibling); sibling = parent->right; } + sibling->color = parent->color; parent->color = 0; sibling->right->color = 0; + RotateTreeLeft(head, parent); node = head->parent; + } + } else { + auto* sibling = parent->left; + if (sibling->color == 1) { sibling->color = 0; parent->color = 1; RotateTreeRight(head, parent); sibling = parent->left; } + if (sibling->right->color == 0 && sibling->left->color == 0) { + if (!sibling->nil) sibling->color = 1; + node = parent; parent = node->parent; + } else { + if (sibling->left->color == 0) { sibling->right->color = 0; sibling->color = 1; RotateTreeLeft(head, sibling); sibling = parent->left; } + sibling->color = parent->color; parent->color = 0; sibling->left->color = 0; + RotateTreeRight(head, parent); node = head->parent; + } + } + } + node->color = 0; +} + +template +void UnlinkTreeNode(Node* head, Node* removed) { + if (!removed || removed->nil) return; + auto* successor = removed; + auto color = successor->color; + Node* child; + Node* parent; + if (removed->left->nil) { child = removed->right; parent = removed->parent; ReplaceTreeNode(head, removed, child); } + else if (removed->right->nil) { child = removed->left; parent = removed->parent; ReplaceTreeNode(head, removed, child); } + else { + successor = removed->right; + while (!successor->left->nil) successor = successor->left; + color = successor->color; + child = successor->right; + if (successor->parent == removed) parent = successor; + else { + parent = successor->parent; + ReplaceTreeNode(head, successor, child); + successor->right = removed->right; + successor->right->parent = successor; + } + ReplaceTreeNode(head, removed, successor); + successor->left = removed->left; + successor->left->parent = successor; + successor->color = removed->color; + } + if (color == 0) BalanceTreeErasure(head, child, parent); + RefreshTreeEndpoints(head); +} + +struct ObjectLink { + ObjectLink* parent; + ObjectLink* left; + ObjectLink* right; + Text key; + Pair pair; + std::uint8_t color; + std::uint8_t nil; + std::uint8_t reserved[6]; +}; +static_assert(sizeof(ObjectLink) == 104 && offsetof(ObjectLink, key) == 24); +static_assert(offsetof(ObjectLink, pair) == 48 && offsetof(ObjectLink, color) == 96); + +ObjectLink* NextObjectLink(ObjectLink* node) { + if (!node) return nullptr; + if (node->nil) throw std::out_of_range("sce::Json::Object::iterator: advance past the end"); + if (!node->right->nil) { + node = node->right; + while (!node->left->nil) node = node->left; + return node; + } + auto* parent = node->parent; + while (!parent->nil && node == parent->right) { node = parent; parent = parent->parent; } + return parent; +} + +void DestroyObjectLink(ObjectLink* node) { + Destroy(node->pair.value); + Destroy(node->pair.key); + String key{&node->key}; + ClearText(key); + PoisonText(node->key); + Release(node); +} + +void ReleaseObjectTree(ObjectLink* node) { + if (!node || node->nil) return; + ReleaseObjectTree(node->left); + ReleaseObjectTree(node->right); + if (!node->parent->nil) { + if (node->parent->left == node) node->parent->left = node->left; + else node->parent->right = node->right; + } + DestroyObjectLink(node); +} + +ObjectLink* NewObjectHead() { + auto* head = static_cast(Allocate(sizeof(ObjectLink))); + if (head) { head->parent = head; head->left = head; head->right = head; head->color = 0; head->nil = 1; } + return head; +} + +} + +struct SceJson2::ObjectStorage { + ObjectLink* head; + std::size_t count; + std::uint64_t reserved; + + struct iterator { + using value_type = Pair; + using difference_type = std::ptrdiff_t; + using pointer = Pair*; + using reference = Pair&; + using iterator_category = std::bidirectional_iterator_tag; + ObjectLink* node = nullptr; + Pair& operator*() const { return node->pair; } + Pair* operator->() const { return &node->pair; } + iterator& operator++() { node = NextObjectLink(node); return *this; } + iterator operator++(int) { const auto previous = *this; ++*this; return previous; } + iterator& operator--() { + if (node->nil) node = node->right; + else if (!node->left->nil) { node = node->left; while (!node->right->nil) node = node->right; } + else { auto* parent = node->parent; while (!parent->nil && node == parent->left) { node = parent; parent = parent->parent; } node = parent; } + return *this; + } + bool operator==(const iterator&) const = default; + }; + + Value* Owner() const { + Value* owner = nullptr; + if (ModernValues()) std::memcpy(&owner, reinterpret_cast(this) + 24, sizeof(owner)); + return owner; + } + void SetOwner(Value* owner) { if (ModernValues()) std::memcpy(reinterpret_cast(this) + 24, &owner, sizeof(owner)); } + iterator begin() const { return {head ? head->left : nullptr}; } + iterator end() const { return {head}; } + bool empty() const { return !count; } + std::size_t size() const { return count; } + ObjectLink* Find(const char* key) const { + if (!head) return nullptr; + auto* node = head->parent; + while (!node->nil) { + const int comparison = std::strcmp(key, node->key.characters); + if (!comparison) return node; + node = comparison < 0 ? node->left : node->right; + } + return head; + } + void RefreshEndpoints() { RefreshTreeEndpoints(head); } + + void BalanceInsertion(ObjectLink* node) { BalanceTreeInsertion(head, node); } + ObjectLink* Insert(const Text& key, const Pair& pair, bool& inserted) { + inserted = false; + if (!head) return nullptr; + auto* parent = head; + auto* position = head->parent; + int comparison = 0; + while (!position->nil) { + parent = position; + comparison = std::strcmp(key.characters, position->key.characters); + if (!comparison) return position; + position = comparison < 0 ? position->left : position->right; + } + auto* node = static_cast(Allocate(sizeof(ObjectLink))); + if (!node) return head; + node->parent = parent; node->left = head; node->right = head; + node->key.characters = emptyCharacters; node->key.length = 0; node->key.capacity = 0; + node->pair.key.text = nullptr; + Construct(node->pair.value); + node->color = 1; node->nil = 0; + try { + String keyStorage{&node->key}; AssignText(keyStorage, {key.characters, key.length}, true); + Construct(node->pair.key, pair.key); + std::memcpy(&node->pair.reserved, &pair.reserved, sizeof(std::uint32_t)); + CopyInto(node->pair.value, pair.value); + } catch (...) { DestroyObjectLink(node); throw; } + if (parent == head) head->parent = node; + else if (comparison < 0) parent->left = node; + else parent->right = node; + ++count; + BalanceInsertion(node); + RefreshEndpoints(); + inserted = true; + return node; + } + + void Erase(ObjectLink* removed) { + if (!removed || removed->nil) return; + UnlinkTreeNode(head, removed); + DestroyObjectLink(removed); + --count; + } + void clear() { + if (!head) return; + ReleaseObjectTree(head->parent); + head->parent = head; head->left = head; head->right = head; + count = 0; + } +}; + +namespace { + +static_assert(sizeof(ObjectStorage) == 24 && sizeof(ObjectIteratorState) == 8); + +struct LegacyHandlerNode { + LegacyHandlerNode* parent; + LegacyHandlerNode* left; + LegacyHandlerNode* right; + const Value* key; + HandlerRecord record; + std::uint8_t color; + std::uint8_t nil; + std::uint8_t reserved[6]; +}; +static_assert(sizeof(LegacyHandlerNode) == 56 && offsetof(LegacyHandlerNode, record) == 32 && offsetof(LegacyHandlerNode, nil) == 49); + +struct LegacyHandlerMap { + LegacyHandlerNode* head; + std::size_t count; + std::uint64_t reserved; + LegacyHandlerNode* Find(const Value* key) const { + auto* node = head->parent; + while (!node->nil) { + const auto requested = reinterpret_cast(key); + const auto stored = reinterpret_cast(node->key); + if (requested == stored) return node; + node = requested < stored ? node->left : node->right; + } + return head; + } + void RefreshEndpoints() { RefreshTreeEndpoints(head); } + + void BalanceInsertion(LegacyHandlerNode* node) { BalanceTreeInsertion(head, node); } + LegacyHandlerNode* Insert(const Value* key) { + auto* parent = head; + auto* position = head->parent; + bool left = false; + while (!position->nil) { + parent = position; + const auto requested = reinterpret_cast(key); + const auto stored = reinterpret_cast(position->key); + if (requested == stored) return position; + left = requested < stored; + position = left ? position->left : position->right; + } + auto* node = static_cast(_Znwm_nid_postfix(sizeof(LegacyHandlerNode))); + node->parent = parent; node->left = head; node->right = head; + node->key = key; node->record = {}; + node->color = 1; node->nil = 0; + if (parent->nil) head->parent = node; + else if (left) parent->left = node; + else parent->right = node; + ++count; + BalanceInsertion(node); + RefreshEndpoints(); + return node; + } + + void Erase(LegacyHandlerNode* removed) { + if (!removed || removed->nil) return; + UnlinkTreeNode(head, removed); + _ZdlPv_nid_postfix(removed); + --count; + } + void ReleaseTree(LegacyHandlerNode* node) { + if (node->nil) return; + ReleaseTree(node->left); ReleaseTree(node->right); + _ZdlPv_nid_postfix(node); + } +}; +static_assert(sizeof(LegacyHandlerMap) == 24); + +HandlerRecord LegacyHandlerOf(const Value& value) { + auto& state = State(); + KernelMutexLock lock(state.recoveryMutex); + if (!state.legacyHandlers) return {}; + const auto* node = state.legacyHandlers->Find(&value); + return node->nil ? HandlerRecord{} : node->record; +} + +int RegisterLegacyHandler(Value& value, TypeMismatchHandler callback, void* context) { + auto& state = State(); + KernelMutexLock lock(state.recoveryMutex); + if (!state.legacyHandlers) { + auto* map = static_cast(Allocate(sizeof(LegacyHandlerMap))); + if (!map) return JsonErrorOutOfMemory; + map->count = 0; + map->head = static_cast(_Znwm_nid_postfix(sizeof(LegacyHandlerNode))); + map->head->parent = map->head; map->head->left = map->head; map->head->right = map->head; + map->head->color = 0; map->head->nil = 1; + state.legacyHandlers = map; + } + AllocationOperation operation; + auto* node = state.legacyHandlers->Insert(&value); + if (operation.Finish()) return JsonErrorOutOfMemory; + node->record = {callback, context}; + return 0; +} + +void EraseLegacyHandler(const Value& value) { + auto& state = State(); + if (!state.recoveryMutex) NotImplemented_nid_no_patch(__func__); + KernelMutexLock lock(state.recoveryMutex); + if (state.legacyHandlers) state.legacyHandlers->Erase(state.legacyHandlers->Find(&value)); +} + +void CopyLegacyHandler(Value& destination, const Value& source) { + auto& state = State(); + KernelMutexLock lock(state.recoveryMutex); + if (!state.legacyHandlers) return; + const auto* sourceNode = state.legacyHandlers->Find(&source); + if (!sourceNode->nil) { + const auto record = sourceNode->record; + RegisterLegacyHandler(destination, record.callback, record.context); + } +} + +void ReleaseLegacyHandlers() { + auto& state = State(); + auto* map = state.legacyHandlers; + if (!map) return; + map->ReleaseTree(map->head->parent); + _ZdlPv_nid_postfix(map->head); + map->head = reinterpret_cast(std::uintptr_t{0xffffffff}); + map->count = std::numeric_limits::max(); + Release(map); + state.legacyHandlers = nullptr; +} + +void Construct(Object& object, bool releaseMissingHeader = false) { + object.items = static_cast(Allocate(ModernValues() ? 32 : 24)); + if (!object.items) { if (releaseMissingHeader) Release(nullptr); return; } + object.items->count = 0; + object.items->head = NewObjectHead(); + object.items->SetOwner(nullptr); + if (!object.items->head) { + object.items->head = reinterpret_cast(std::uintptr_t{0xffffffff}); + object.items->count = std::numeric_limits::max(); + Release(object.items); + object.items = nullptr; + } +} + +ObjectLink* CloneObjectTree(ObjectLink* source, ObjectLink* parent, ObjectLink* head) { + auto* node = static_cast(Allocate(sizeof(ObjectLink))); + if (!node) return head; + node->parent = parent; node->left = head; node->right = head; + node->key.characters = emptyCharacters; node->key.length = 0; node->key.capacity = 0; + node->pair.key.text = nullptr; + Construct(node->pair.value); + node->color = source->color; node->nil = 0; + String key{&node->key}; AssignText(key, {source->key.characters, source->key.length}, true); + Construct(node->pair.key, source->pair.key); + std::memcpy(&node->pair.reserved, &source->pair.reserved, sizeof(std::uint32_t)); + CopyInto(node->pair.value, source->pair.value); + if (!source->left->nil) node->left = CloneObjectTree(source->left, node, head); + if (!source->right->nil) node->right = CloneObjectTree(source->right, node, head); + return node; +} + +void CopyObjectStorage(ObjectStorage& destination, const ObjectStorage& source) { + auto* replacement = NewObjectHead(); + ObjectStorage pending{replacement, 0, 0}; + if (replacement && source.head && !source.head->parent->nil) { + replacement->parent = CloneObjectTree(source.head->parent, replacement, replacement); + if (replacement->parent != replacement) { pending.count = source.count; pending.RefreshEndpoints(); } + } + destination.clear(); + if (destination.head) Release(destination.head); + destination.head = replacement; + destination.count = pending.count; +} + void Destroy(Value& v); void CopyInto(Value& destination, const Value& source); -void Destroy(String& s) { delete s.text; s.text = nullptr; } +void SetDefaultHeaderRetired(ValueType type, const void* owner, bool retired); + +void Destroy(String& s) { + if (!s.text) return; + ClearText(s); + SetDefaultHeaderRetired(TypeString, &s, true); + PoisonText(*s.text); + Release(s.text); + s.text = nullptr; + SetDefaultHeaderRetired(TypeString, &s, false); +} void Destroy(Array& a) { if (!a.items) return; - for (auto& item : *a.items) Destroy(item); - delete a.items; + a.items->ReleaseContents(); + if (a.items->head) Release(a.items->head); + SetDefaultHeaderRetired(TypeArray, &a, true); + a.items->head = reinterpret_cast(std::uintptr_t{0xffffffff}); + a.items->count = std::numeric_limits::max(); + Release(a.items); a.items = nullptr; + SetDefaultHeaderRetired(TypeArray, &a, false); } -void Destroy(Object& o) { - if (!o.items) return; - for (auto& pair : *o.items) { - Destroy(pair.key); - Destroy(pair.value); +void Destroy(Object& object) { + if (!object.items) return; + object.items->clear(); + if (object.items->head) Release(object.items->head); + SetDefaultHeaderRetired(TypeObject, &object, true); + object.items->head = reinterpret_cast(std::uintptr_t{0xffffffff}); + object.items->count = std::numeric_limits::max(); + Release(object.items); + object.items = nullptr; + SetDefaultHeaderRetired(TypeObject, &object, false); +} + +const Node& RootOf(const Node& n) { + const Node* root = &n; + while (root->parent) root = root->parent; + return *root; +} + +Handlers HandlersOf(const Node& n) { + const auto* auxiliary = RootOf(n).auxiliary; + Handlers handlers; + { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (state.initialized) handlers = state.handlers; + } + if (!ModernValues()) { + const auto record = LegacyHandlerOf(RootOf(n)); + handlers.typeMismatch = record.callback; + handlers.typeMismatchContext = record.context; + return handlers; + } + if (!auxiliary) return handlers; + if (auxiliary->typeMismatch && auxiliary->typeMismatch->callback) { + handlers.typeMismatch = auxiliary->typeMismatch->callback; + handlers.typeMismatchContext = auxiliary->typeMismatch->context; + } + if (auxiliary->specialFloat && auxiliary->specialFloat->callback) { + handlers.specialFloat = auxiliary->specialFloat->callback; + handlers.specialFloatContext = auxiliary->specialFloat->context; + } + if (auxiliary->elementAccessFailure && auxiliary->elementAccessFailure->callback) { + handlers.elementAccessFailure = auxiliary->elementAccessFailure->callback; + handlers.elementAccessFailureContext = auxiliary->elementAccessFailure->context; + } + return handlers; +} + +void BindChildren(Node& n) { + if (n.type == TypeArray && n.array && n.array->items) { + n.array->items->SetOwner(&n); + for (auto& item : *n.array->items) item.parent = &n; + } + if (n.type == TypeObject && n.object && n.object->items) { + n.object->items->SetOwner(&n); + for (auto& pair : *n.object->items) pair.value.parent = &n; + } +} + +template +void Destroy(T*& pointer) { + if (pointer) { + Destroy(*pointer); + Release(pointer); + pointer = nullptr; } - delete o.items; - o.items = nullptr; } void Clear(Node& n) { if (n.type == TypeString) Destroy(n.string); if (n.type == TypeArray) Destroy(n.array); if (n.type == TypeObject) Destroy(n.object); - const Handlers handlers = n.handlers; - n = Node{}; - n.handlers = handlers; + n.type = TypeNull; +} + +void ReleaseValueAuxiliary(Value& v) { + if (v.auxiliary) { + if (ModernValues()) { + if (v.auxiliary->typeMismatch) Release(v.auxiliary->typeMismatch); + v.auxiliary->typeMismatch = nullptr; + if (v.auxiliary->specialFloat) Release(v.auxiliary->specialFloat); + v.auxiliary->specialFloat = nullptr; + if (v.auxiliary->elementAccessFailure) Release(v.auxiliary->elementAccessFailure); + v.auxiliary->elementAccessFailure = nullptr; + } + if (v.auxiliary->fallback) { + Destroy(*v.auxiliary->fallback); + Release(v.auxiliary->fallback); + v.auxiliary->fallback = nullptr; + } + Release(v.auxiliary); + v.auxiliary = nullptr; + } } void Destroy(Value& v) { - if (!v.node) return; - Clear(*v.node); - delete v.node; - v.node = nullptr; -} - -Node& NodeOf(const Value& v) { - if (!v.node) throw std::runtime_error("sce::Json::Value used before construction"); - return *v.node; -} - -void Construct(Value& v) { v.node = new Node(); } - -template -void Construct(Value& destination, TInitialize initialize) { - Value pending{}; - Construct(pending); - try { - initialize(*pending.node); - } catch (...) { - Destroy(pending); - throw; + Clear(v); + { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.recoveryMutex) return; } - destination = pending; + ReleaseValueAuxiliary(v); + if (!ModernValues()) EraseLegacyHandler(v); +} + +int EnsureAuxiliary(Node& n) { + if (n.auxiliary) return 0; + const auto bytes = ModernValues() ? sizeof(Auxiliary) : std::size_t{24}; + auto* auxiliary = static_cast(Allocate(bytes)); + if (!auxiliary) return JsonErrorOutOfMemory; + auxiliary->nullAccess = nullptr; + auxiliary->nullAccessContext = nullptr; + auxiliary->fallback = static_cast(Allocate(sizeof(Value))); + if (auxiliary->fallback) Construct(*auxiliary->fallback); + if (ModernValues()) std::memset(reinterpret_cast(auxiliary) + 24, 0, sizeof(Auxiliary) - 24); + n.auxiliary = auxiliary; + if (!auxiliary->fallback) return JsonErrorOutOfMemory; + auxiliary->fallback->parent = &n; + return 0; +} + +Node& NodeOf(const Value& v) { return const_cast(v); } + +void Construct(Value& v) { + v.parent = nullptr; + v.auxiliary = nullptr; + v.type = TypeNull; +} + +template +void TransferChildren(Value& destination, TLink* first, TLink* head, TNext next, TValue value) { + auto* begin = static_cast(Allocate(sizeof(TLink*))); + if (begin) *begin = first; + for (;;) { + auto* end = static_cast(Allocate(sizeof(TLink*))); + if (end) *end = head; + const bool more = begin && end && *begin != *end; + Release(end); + if (!more) break; + if (!*begin) { Release(begin); NotImplemented_nid_no_patch(__func__); } + value(*begin).parent = &destination; + *begin = next(*begin); + } + Release(begin); +} + +void TransferPayload(Node& destination, Node& source) { + if (destination.auxiliary || source.auxiliary) return; + Clear(destination); + if (source.type <= TypeReal) AssignNode(destination, source); + else if (source.type <= TypeObject) { + destination.payload = source.payload; + source.payload = 0; + if (source.type == TypeArray && destination.array) { + auto* items = destination.array->items; + auto* head = items ? items->head : nullptr; + TransferChildren(destination, head ? head->next : nullptr, head, + [](ArrayLink* link) { return link->next; }, [](ArrayLink* link) -> Value& { return link->value; }); + } else if (source.type == TypeObject && destination.object) { + auto* items = destination.object->items; + auto* head = items ? items->head : nullptr; + TransferChildren(destination, head ? head->left : nullptr, head, + [](ObjectLink* link) { return NextObjectLink(link); }, [](ObjectLink* link) -> Value& { return link->pair.value; }); + } + } else return; + destination.parent = source.parent; + source.parent = nullptr; + destination.type = source.type; + Clear(source); +} + +bool HasStorage(const String& value) { return value.text != nullptr; } +bool HasStorage(const Array& value) { return value.items != nullptr; } +bool HasStorage(const Object& value) { return value.items != nullptr; } + +template +void ConstructHandle(T*& destination, TInitialize initialize) { + destination = static_cast(Allocate(sizeof(T))); + if (!destination) return; + *destination = {}; + try { initialize(*destination); } + catch (...) { Destroy(destination); throw; } + if (!HasStorage(*destination)) { Release(destination); destination = nullptr; } +} + +template +void Construct(T*& destination, const TArgs&... arguments) { + ConstructHandle(destination, [&](T& value) { Construct(value, arguments...); }); +} + +void CopyArrayStorage(ArrayStorage& destination, const ArrayStorage& source) { + ArrayStorage pending{NewArrayHead(), 0, 0}; + if (pending.head) { + for (const auto& item : source) { + try { CopyInto(pending.emplace_back(), item); } + catch (const GuestOutOfMemory&) { break; } + } + } + destination.clear(); + if (destination.head) Release(destination.head); + destination.head = pending.head; + destination.count = pending.count; } void CopyArray(Array& destination, const Array& source) { - Array pending{}; - Construct(pending); - try { - for (const auto& item : *source.items) { - CopyInto(pending.items->emplace_back(), item); - } - } catch (...) { - Destroy(pending); - throw; - } - destination = pending; + if (!source.items) { destination = {}; return; } + Construct(destination); + if (!destination.items) return; + CopyArrayStorage(*destination.items, *source.items); } void CopyObject(Object& destination, const Object& source) { - Object pending{}; - Construct(pending); - try { - for (const auto& pair : *source.items) { - auto& copy = pending.items->emplace_back(); - Construct(copy.key, *pair.key.text); - CopyInto(copy.value, pair.value); - } - } catch (...) { - Destroy(pending); - throw; + if (!source.items) { destination = {}; return; } + destination.items = static_cast(Allocate(ModernValues() ? 32 : 24)); + if (!destination.items) { destination = {}; return; } + destination.items->count = 0; + destination.items->head = NewObjectHead(); + destination.items->SetOwner(nullptr); + CopyObjectStorage(*destination.items, *source.items); +} + +void AssignReal(Node& n, double value); + +void SetType(Node& n, ValueType type); +void AssignArray(Array& destination, const Array& source); +void AssignObject(Object& destination, const Object& source); + +bool HasPayloadStorage(const Node& value) { + if (value.type == TypeString) return value.string && value.string->text; + if (value.type == TypeArray) return value.array && value.array->items; + if (value.type == TypeObject) return value.object && value.object->items; + return value.type < TypeString; +} + +bool OwnsPayload(const Node& owner, std::uintptr_t payload) { + if (owner.type < TypeString || !owner.payload) return false; + if (owner.payload == payload) return true; + if (owner.type == TypeArray && owner.array->items) { + for (const auto& item : *owner.array->items) + if (OwnsPayload(item, payload)) return true; + } else if (owner.type == TypeObject && owner.object->items) { + for (const auto& pair : *owner.object->items) + if (reinterpret_cast(&pair.key) == payload || OwnsPayload(pair.value, payload)) return true; } - destination = pending; + return false; +} + +void AssignPayload(Node& destination, const Node& source) { + if (source.type >= TypeString && HasPayloadStorage(source) && destination.type != source.type && OwnsPayload(destination, source.payload)) { + Node temporary{}; + try { + AssignPayload(temporary, source); + AssignPayload(destination, temporary); + } catch (...) { Destroy(temporary); throw; } + Destroy(temporary); + return; + } + if (source.type == TypeNull) { SetType(destination, TypeNull); return; } + if (source.type == TypeBoolean) { + const auto value = source.boolean; + SetType(destination, TypeBoolean); + destination.boolean = value; + return; + } + if (source.type == TypeInteger || source.type == TypeUInteger) { + const auto value = source.payload; + SetType(destination, source.type); + destination.payload = value; + return; + } + if (source.type == TypeReal) { AssignReal(destination, source.real); return; } + if (source.type == TypeString) { + const auto* string = source.string; + if (!string) NotImplemented_nid_no_patch(__func__); + if (!string->text) return; + SetType(destination, TypeString); + if (destination.string && destination.string->text != string->text) AssignText(*destination.string, View(*string), true); + return; + } + if (source.type == TypeArray) { + const auto* array = source.array; + if (!array) NotImplemented_nid_no_patch(__func__); + if (!array->items) return; + SetType(destination, TypeArray); + if (destination.array) AssignArray(*destination.array, *array); + return; + } + if (source.type == TypeObject) { + const auto* object = source.object; + if (!object) NotImplemented_nid_no_patch(__func__); + if (!object->items) return; + SetType(destination, TypeObject); + if (destination.object) AssignObject(*destination.object, *object); + return; + } + NotImplemented_nid_no_patch(__func__); } void AssignNode(Node& destination, const Node& source) { - Node copy{}; - copy.type = source.type; - copy.boolean = source.boolean; - copy.integer = source.integer; - copy.uinteger = source.uinteger; - copy.real = source.real; + for (auto* parent = source.parent; parent; parent = parent->parent) { + if (parent != &destination || !HasPayloadStorage(source)) continue; + Node temporary{}; + try { + AssignNode(temporary, source); + AssignNode(destination, temporary); + } catch (...) { Destroy(temporary); throw; } + Destroy(temporary); + return; + } + AssignPayload(destination, source); + if (!ModernValues()) CopyLegacyHandler(destination, source); +} + +void RecoverSpecial(const Node& source, const Handlers& handlers, int function, double number, Value& result, bool* error) { + Value returned{}; try { - if (source.type == TypeString) Construct(copy.string, *source.string.text); - if (source.type == TypeArray) CopyArray(copy.array, source.array); - if (source.type == TypeObject) CopyObject(copy.object, source.object); + handlers.specialFloat(&returned, function, number, source.parent, handlers.specialFloatContext, error); + AssignNode(result, returned); } catch (...) { - Clear(copy); + Destroy(returned); throw; } - Clear(destination); - copy.handlers = destination.handlers; - destination = copy; + Destroy(returned); } void CopyInto(Value& destination, const Value& source) { - Construct(destination, [&](Node& node) { AssignNode(node, NodeOf(source)); }); + Construct(destination); + AssignNode(destination, source); } void SetType(Node& n, ValueType type) { + if (n.type == type) return; if (type < TypeNull || type > TypeObject) throw std::invalid_argument("sce::Json::Value::set: invalid value type"); - Node pending{}; - pending.type = type; - if (type == TypeString) Construct(pending.string, std::string()); - if (type == TypeArray) Construct(pending.array); - if (type == TypeObject) Construct(pending.object); Clear(n); - pending.handlers = n.handlers; - n = pending; + if (type == TypeBoolean) n.boolean = false; + else if (type == TypeInteger || type == TypeUInteger || type == TypeReal) n.payload = 0; + else if (type == TypeString) { + Construct(n.string, std::string_view{}); + if (!n.string) return; + } else if (type == TypeArray) { + Construct(n.array); + if (!n.array) return; + } else if (type == TypeObject) { + Construct(n.object); + if (!n.object) return; + } + n.type = type; + BindChildren(n); +} + +struct DefaultStorage { + Value values[8]{}; + bool ready[8]{}; +}; + +DefaultStorage& SharedDefaults() { + static DefaultStorage defaults; + return defaults; +} + +void SetDefaultHeaderRetired(ValueType type, const void* owner, bool retired) { + auto& state = State(); + std::lock_guard lock(state.mutex); + auto& defaults = SharedDefaults(); + if (!defaults.ready[type] || defaults.values[type].payload != reinterpret_cast(owner)) return; + const auto mask = static_cast(1u << type); + if (retired) state.retiredDefaultHeaders |= mask; + else state.retiredDefaultHeaders &= static_cast(~mask); } const Value& Defaults(ValueType type) { - static Value values[8]{}; - static std::once_flag once; - std::call_once(once, [] { - for (int i = 0; i < 8; ++i) { - Construct(values[i]); - SetType(*values[i].node, static_cast(i)); - } - }); - return values[type]; + auto& state = State(); + std::lock_guard lock(state.mutex); + if (type == TypeNull && state.fallback) return *state.fallback; + auto& defaults = SharedDefaults(); + if (!defaults.ready[type]) { + Construct(defaults.values[type]); + SetType(defaults.values[type], type); + defaults.ready[type] = true; + } + return defaults.values[type]; } -const Value& NullAccess(ValueType requested, const Value* parent) { +void ResetDefaults(bool releaseMissing = false) { + auto& defaults = SharedDefaults(); + for (unsigned type = 0; type < 8; ++type) { + if (defaults.ready[type]) { + if (releaseMissing && type == TypeString) defaults.values[type].string = _ZN3sce4Json5Value12s_nullstringE; + if (releaseMissing && type == TypeArray) defaults.values[type].array = _ZN3sce4Json5Value11s_nullarrayE; + if (releaseMissing && type == TypeObject) defaults.values[type].object = _ZN3sce4Json5Value12s_nullobjectE; + const bool missing = type >= TypeString && !defaults.values[type].payload; + Destroy(defaults.values[type]); + if (releaseMissing && missing) Release(nullptr); + } else if (releaseMissing && type >= TypeString) Release(nullptr); + if (releaseMissing && type == TypeString) _ZN3sce4Json5Value12s_nullstringE = nullptr; + if (releaseMissing && type == TypeArray) _ZN3sce4Json5Value11s_nullarrayE = nullptr; + if (releaseMissing && type == TypeObject) _ZN3sce4Json5Value12s_nullobjectE = nullptr; + defaults.values[type] = {}; + defaults.ready[type] = false; + } +} + +void ReleaseInitialization(); + +PthreadMutex* CreateKernelMutex() { + auto* mutex = static_cast(Allocate(sizeof(PthreadMutex))); + if (!mutex) return nullptr; + *mutex = nullptr; + PthreadMutexattr attribute = nullptr; + try { + if (scePthreadMutexattrInit(&attribute)) NotImplemented_nid_no_patch(__func__); + const auto typeStatus = scePthreadMutexattrSettype(&attribute, 2); + const auto status = typeStatus ? typeStatus : scePthreadMutexInit(mutex, &attribute, "SceJson_internal"); + scePthreadMutexattrDestroy(&attribute); + if (status) NotImplemented_nid_no_patch(__func__); + return mutex; + } catch (...) { + if (attribute) scePthreadMutexattrDestroy(&attribute); + if (*mutex) scePthreadMutexDestroy(mutex); + Release(mutex); + ReleaseInitialization(); + throw; + } +} + +void ReleaseInitialization() { auto& state = State(); - NullAccessCallback callback; - void* context; + if (!ModernValues() && !state.legacyHandlers) Release(nullptr); + ReleaseLegacyHandlers(); + if (state.fallback) Destroy(*state.fallback); + Release(state.fallback); + state.fallback = nullptr; + if (ModernValues()) { + if (state.recoveryMutex) { + scePthreadMutexDestroy(state.recoveryMutex); + state.recoveryMutexRetired = true; + } + Release(state.recoveryMutex); + } + state.recoveryMutex = nullptr; + state.recoveryMutexRetired = false; + if (state.internalMutex) { + scePthreadMutexDestroy(state.internalMutex); + state.internalMutexRetired = true; + } + Release(state.internalMutex); + state.internalMutex = nullptr; + state.internalMutexRetired = false; + ResetDefaults(true); + if (state.allocation) { + auto* allocation = state.allocation; + const auto context = *allocation; + Release(context, allocation); + state.allocation = nullptr; + } + state.floatFormat = 2; + state.floatFormatEnabled = false; + state.paddedStringGrowth = false; + state.initialized = false; +} + +template +void CreateDefault(ValueType type) { + auto& defaults = SharedDefaults(); + auto& value = defaults.values[type]; + Construct(value); + auto* handle = static_cast(Allocate(sizeof(T))); + value.payload = reinterpret_cast(handle); + value.type = type; + defaults.ready[type] = true; + if (handle) { + *handle = {}; + if constexpr (std::is_same_v) Construct(*handle, std::string_view{}); + else Construct(*handle); + } + if constexpr (std::is_same_v) _ZN3sce4Json5Value12s_nullstringE = handle; + else if constexpr (std::is_same_v) _ZN3sce4Json5Value11s_nullarrayE = handle; + else _ZN3sce4Json5Value12s_nullobjectE = handle; +} + +int InitializeResources(void* self, const void* parameter, std::int32_t format, bool modern) { + auto& state = State(); + AllocationContext context; + std::memcpy(&context, parameter, offsetof(AllocationContext, fileBufferSize)); + auto* const* table = *static_cast(context.allocator); + const auto allocate = reinterpret_cast(table[2]); + auto* allocation = static_cast(allocate(context.allocator, sizeof(AllocationContext), context.userData)); + if (!allocation) return JsonErrorOutOfMemory; + std::memcpy(&context.fileBufferSize, static_cast(parameter) + offsetof(AllocationContext, fileBufferSize), sizeof(context.fileBufferSize)); + *allocation = context; + state.allocation = allocation; + state.floatFormat = format; + state.floatFormatEnabled = modern; + CreateDefault(TypeString); + CreateDefault(TypeArray); + CreateDefault(TypeObject); + state.fallback = static_cast(Allocate(sizeof(Value))); + if (state.fallback) Construct(*state.fallback); + state.internalMutex = CreateKernelMutex(); + const bool admitted = _ZN3sce4Json5Value12s_nullstringE && _ZN3sce4Json5Value11s_nullarrayE && + _ZN3sce4Json5Value12s_nullobjectE && state.fallback && state.internalMutex; + if (admitted) state.recoveryMutex = ModernValues() ? CreateKernelMutex() : state.internalMutex; + if (!admitted || !state.recoveryMutex) { ReleaseInitialization(); return JsonErrorOutOfMemory; } + state.paddedStringGrowth = CompiledSdk() > 0x044fffff; + state.initialized = true; + *static_cast(self) = 1; + return 0; +} + +const Node& Typed(const Value* self, ValueType requested, bool* usedDefault = nullptr) { + const auto fallback = [&]() -> const Node& { + if (usedDefault) *usedDefault = true; + if (usedDefault && requested >= TypeString) return SharedDefaults().values[requested]; + return Defaults(requested); + }; + if (self->type == requested || + ((requested == TypeInteger || requested == TypeUInteger) && + (self->type == TypeInteger || self->type == TypeUInteger))) return *self; + if (!ModernValues() && self->type == TypeNull) { + const auto* auxiliary = RootOf(*self).auxiliary; + if (auxiliary && auxiliary->nullAccess) + return Typed(&auxiliary->nullAccess(requested, self->parent, auxiliary->nullAccessContext), requested, usedDefault); + } + CallbackScope scope; + if (!scope.entered) return fallback(); + NullAccessCallback callback = nullptr; + void* context = nullptr; + bool initialized; { + auto& state = State(); std::lock_guard lock(state.mutex); + initialized = state.initialized; callback = state.callback; context = state.context; } - if (callback) { - const Value& result = callback(requested, parent, context); - if (result.node && (requested == TypeNull || result.node->type == requested)) return result; + if (!initialized) return fallback(); + if (!ModernValues()) { + const auto record = LegacyHandlerOf(RootOf(*self)); + if (record.callback) return Typed(&record.callback(requested, self, self->parent, record.context), requested, usedDefault); + return fallback(); } - return Defaults(requested); + if (self->type == TypeNull) { + const auto* auxiliary = RootOf(*self).auxiliary; + if (auxiliary && auxiliary->nullAccess) + return Typed(&auxiliary->nullAccess(requested, self->parent, auxiliary->nullAccessContext), requested, usedDefault); + } + const auto* auxiliary = RootOf(*self).auxiliary; + if (ModernValues() && auxiliary && auxiliary->typeMismatch && auxiliary->typeMismatch->callback) + return Typed(&auxiliary->typeMismatch->callback(requested, self, self->parent, auxiliary->typeMismatch->context), requested, usedDefault); + if (self->type == TypeNull && callback) + return Typed(&callback(requested, self->parent, context), requested, usedDefault); + const auto handlers = HandlersOf(*self); + if (handlers.typeMismatch) + return Typed(&handlers.typeMismatch(requested, self, self->parent, handlers.typeMismatchContext), requested, usedDefault); + return fallback(); } -const Value& TypeMismatch(ValueType requested, const Value* self) { - const Handlers& handlers = NodeOf(*self).handlers; - const Value& result = handlers.typeMismatch(requested, self, nullptr, handlers.typeMismatchContext); - if (result.node && result.node->type == requested) return result; - return Defaults(requested); +const Value& MissingElement(const Value* self, int error, const String* key, const std::size_t* index) { + bool initialized; + { + auto& state = State(); + std::lock_guard lock(state.mutex); + initialized = state.initialized; + } + const auto* auxiliary = RootOf(*self).auxiliary; + if (initialized) { + CallbackScope scope; + const auto handlers = HandlersOf(*self); + if (scope.entered && handlers.elementAccessFailure && + (!auxiliary || self != auxiliary->fallback) && self != &Defaults(TypeNull)) + handlers.elementAccessFailure(error, key, index, self->parent, self, handlers.elementAccessFailureContext); + return auxiliary && auxiliary->fallback ? *auxiliary->fallback : Defaults(TypeNull); + } + return Defaults(TypeNull); } -const Node& Typed(const Value* self, ValueType requested) { - const Node& n = NodeOf(*self); - if (n.type == requested) return n; - if (n.type != TypeNull && n.handlers.typeMismatch) return NodeOf(TypeMismatch(requested, self)); - return NodeOf(NullAccess(requested, self)); +const Value* LookupKey(const Value* self, const String* key) { + if (!key) { NotImplemented_nid_no_patch(__func__); return nullptr; } + auto moduleFallback = [] { + auto& state = State(); + std::lock_guard lock(state.mutex); + return state.fallback; + }; + if (!key->text) return moduleFallback(); + struct IteratorState { + ObjectIteratorState* state = static_cast(Allocate(sizeof(ObjectIteratorState))); + IteratorState() { if (state) state->position = nullptr; } + ~IteratorState() { Release(state); } + }; + if (self->type == TypeObject) { + IteratorState found; + if (!self->object) { NotImplemented_nid_no_patch(__func__); return nullptr; } + auto* items = self->object->items; + if (found.state) found.state->position = items ? items->Find(Characters(*key)) : nullptr; + if (items && !items->head) { Release(found.state); found.state = nullptr; } + bool present; + { + IteratorState end; + if (end.state) end.state->position = items ? items->head : nullptr; + if (items && !items->head) { Release(end.state); end.state = nullptr; } + present = found.state && end.state && found.state->position != end.state->position; + } + if (present) return &found.state->position->pair.value; + if (ModernValues()) { + const auto* result = &MissingElement(self, static_cast(0x80848115), key, nullptr); + if (moduleFallback() || RootOf(*self).auxiliary) return result; + } + } else if (ModernValues()) { + const auto* result = &MissingElement(self, static_cast(0x80848117), key, nullptr); + if (moduleFallback() || RootOf(*self).auxiliary) return result; + } + const auto* auxiliary = RootOf(*self).auxiliary; + return auxiliary && auxiliary->fallback ? auxiliary->fallback : moduleFallback(); } -const Value& MissingElement(const Value* self) { - if (NodeOf(*self).handlers.elementAccessFailure) - throw std::runtime_error("sce::Json::Value: element access failure handler: the error code the console passes is unverified"); - return NullAccess(TypeNull, self); +template +int SetHandler(Node& n, HandlerRecord* Auxiliary::* member, TCallback callback, void* context, bool modernOnly = false) { + if (n.parent) return static_cast(0x80848104); + const bool legacyMismatch = !ModernValues() && std::is_same_v; + if (legacyMismatch && !n.auxiliary) return static_cast(0x80848104); + { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.allocation) return JsonErrorNotInitialized; + if (modernOnly && !state.floatFormatEnabled) return static_cast(0x80848113); + } + if (!ModernValues()) { + if constexpr (std::is_same_v) return RegisterLegacyHandler(n, callback, context); + else NotImplemented_nid_no_patch(__func__); + } + const auto status = EnsureAuxiliary(n); + if (status) return status; + auto& record = n.auxiliary->*member; + if (!record) { + record = static_cast*>(Allocate(sizeof(HandlerRecord))); + if (!record) return JsonErrorOutOfMemory; + } + record->callback = callback; + record->context = context; + return 0; } -template -void SetHandlers(Node& n, const TSetter& setter) { - setter(n.handlers); - if (n.type == TypeArray) - for (auto& item : *n.array.items) SetHandlers(NodeOf(item), setter); - if (n.type == TypeObject) - for (auto& pair : *n.object.items) SetHandlers(NodeOf(pair.value), setter); +template +int SetGlobalHandler(void* self, TCallback Handlers::* callbackMember, void* Handlers::* contextMember, + TCallback callback, void* context, bool modernOnly = false) { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.initialized || !*static_cast(self)) return JsonErrorNotInitialized; + if (modernOnly && !state.floatFormatEnabled) return static_cast(0x80848113); + if (!callback) return JsonErrorInvalidArgument; + if (state.handlers.*callbackMember) return static_cast(0x80848112); + state.handlers.*callbackMember = callback; + state.handlers.*contextMember = context; + return 0; } -void AppendEscaped(std::string& out, const std::string& text) { +const char* SpecialFloatText(double value) { + return std::isnan(value) ? "nan" : value < 0 ? "-inf" : "inf"; +} + +struct FloatSettings { + std::int32_t format; + bool enabled; +}; + +FloatSettings FloatConfiguration() { + auto& state = State(); + std::lock_guard lock(state.mutex); + return {state.floatFormat, state.floatFormatEnabled}; +} + +void AssignReal(Node& n, double value) { + if (std::isfinite(value)) { + SetType(n, TypeReal); + n.real = value; + return; + } + const auto settings = FloatConfiguration(); + if (settings.enabled) { + CallbackScope scope; + const auto handlers = HandlersOf(n); + if (scope.entered && handlers.specialFloat) { + Value result{}; + try { + RecoverSpecial(n, handlers, 1, value, result, nullptr); + AssignNode(n, result); + } catch (...) { + Destroy(result); + throw; + } + Destroy(result); + return; + } + } + if (settings.enabled && settings.format != 2) { + if (settings.format == 1) { + SetType(n, TypeNull); + } else { + String temporary{}; + Construct(temporary, SpecialFloatText(value)); + try { + if (temporary.text) { + SetType(n, TypeString); + if (n.string) AssignText(*n.string, View(temporary), true); + } + } catch (...) { Destroy(temporary); throw; } + Destroy(temporary); + } + return; + } + SetType(n, TypeReal); + n.real = value; +} + +void AppendEscaped(std::string& out, std::string_view text) { out += '"'; for (const unsigned char c : text) { switch (c) { @@ -252,41 +1813,72 @@ std::string RealText(double value) { return std::string(buffer, result.ptr); } -void Serialize(std::string& out, const Node& n) { - switch (n.type) { - case TypeNull: out += "null"; return; - case TypeBoolean: out += n.boolean ? "true" : "false"; return; - case TypeInteger: out += std::to_string(n.integer); return; - case TypeUInteger: out += std::to_string(n.uinteger); return; - case TypeReal: - if (!std::isfinite(n.real) && n.handlers.specialFloat) - throw std::runtime_error("sce::Json::Value: special float handler: the FunctionType the console passes is unverified"); - out += RealText(n.real); +void AppendReal(std::string& out, double value) { + if (!std::isfinite(value)) { + const auto settings = FloatConfiguration(); + if (settings.enabled) { + if (settings.format == 0) AppendEscaped(out, SpecialFloatText(value)); + else out += settings.format == 1 ? "null" : SpecialFloatText(value); return; - case TypeString: AppendEscaped(out, *n.string.text); return; + } + } + out += RealText(value); +} + +int Serialize(std::string& out, const Node& n) { + switch (n.type) { + case TypeNull: out += "null"; return 0; + case TypeBoolean: out += n.boolean ? "true" : "false"; return 0; + case TypeInteger: out += std::to_string(n.integer); return 0; + case TypeUInteger: out += std::to_string(n.uinteger); return 0; + case TypeReal: { + if (!std::isfinite(n.real) && FloatConfiguration().enabled) { + CallbackScope scope; + const auto handlers = HandlersOf(n); + if (scope.entered && handlers.specialFloat) { + Value replacement{}; + int result = 0; + try { + bool error = false; + RecoverSpecial(n, handlers, 0, n.real, replacement, &error); + result = error ? JsonErrorSpecialFloat : Serialize(out, replacement); + } catch (...) { + Destroy(replacement); + throw; + } + Destroy(replacement); + return result; + } + } + AppendReal(out, n.real); + return 0; + } + case TypeString: AppendEscaped(out, View(*n.string)); return 0; case TypeArray: { out += '['; bool first = true; - for (const auto& item : *n.array.items) { + for (const auto& item : *n.array->items) { if (!first) out += ','; first = false; - Serialize(out, NodeOf(item)); + const int result = Serialize(out, item); + if (result < 0) return result; } out += ']'; - return; + return 0; } case TypeObject: { out += '{'; bool first = true; - for (const auto& pair : *n.object.items) { + for (const auto& pair : *n.object->items) { if (!first) out += ','; first = false; - AppendEscaped(out, *pair.key.text); + AppendEscaped(out, Characters(pair.key)); out += ':'; - Serialize(out, NodeOf(pair.value)); + const int result = Serialize(out, pair.value); + if (result < 0) return result; } out += '}'; - return; + return 0; } } throw std::runtime_error("sce::Json: invalid value type"); @@ -300,34 +1892,423 @@ int CompareText(const char* left, const char* 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; }); +struct ScopedString { + String string{}; + explicit ScopedString(std::string_view text) { Construct(string, text); } + explicit ScopedString(const String& source) { Construct(string, source); } + ~ScopedString() { Destroy(string); } +}; + +struct ScopedValue { + Value value{}; + ~ScopedValue() { Destroy(value); } +}; + +struct TemporaryTreeEntry { + InlineText key; + Pair pair{}; + TemporaryTreeEntry(const Text& sourceKey, const Pair& source) { + try { + key.Assign({sourceKey.characters, sourceKey.length}); + Construct(pair.key, source.key); + std::memcpy(&pair.reserved, &source.reserved, sizeof(std::uint32_t)); + CopyInto(pair.value, source.value); + } catch (...) { Destroy(pair.value); Destroy(pair.key); throw; } + } + ~TemporaryTreeEntry() { Destroy(pair.value); Destroy(pair.key); } +}; + +void AssignArray(Array& destination, const Array& source) { + if (!destination.items || !source.items) return; + if (!ModernValues()) { + CopyArrayStorage(*destination.items, *source.items); + return; + } + Array pending{}; + Construct(pending); + if (!pending.items) { Release(nullptr); return; } + if (!pending.items->head) { Destroy(pending); return; } + pending.items->SetOwner(destination.items->Owner()); + try { + for (const auto& item : *source.items) { + ScopedValue placeholder; + SetType(placeholder.value, item.type); + AllocationOperation operation; + try { CopyInto(pending.items->emplace_back(), placeholder.value); } + catch (const GuestOutOfMemory&) {} + if (!operation.Finish()) { + auto& value = pending.items->back(); + value.parent = destination.items->Owner(); + AssignNode(value, item); + } + } + } catch (...) { Destroy(pending); throw; } + auto previous = destination; + destination = pending; + Destroy(previous); } -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; +struct ScopedPair { Pair pair{}; - Construct(pair.key, key); + ~ScopedPair() { Destroy(pair.value); Destroy(pair.key); } +}; + +void AssignObject(Object& destination, const Object& source) { + if (!destination.items || !source.items) return; + if (!ModernValues()) { + CopyObjectStorage(*destination.items, *source.items); + return; + } + Object pending{}; + Construct(pending, true); + if (!pending.items) return; + pending.items->SetOwner(destination.items->Owner()); try { - Construct(pair.value); - return object.items->insert(position, pair)->value; + for (auto position = source.items->begin(), end = source.items->end(); position != end; ++position) { + const auto& pair = *position; + ScopedValue placeholder; + SetType(placeholder.value, pair.value.type); + ScopedPair copied; + { + ScopedString key{std::string_view{Characters(pair.key)}}; + Construct(copied.pair.key, key.string); + CopyInto(copied.pair.value, placeholder.value); + } + AllocationOperation operation; + ObjectLink* node; + bool inserted; + { + InlineText key; key.Assign(position.node->key.characters); + TemporaryTreeEntry entry(key.text, copied.pair); + node = pending.items->Insert(entry.key.text, entry.pair, inserted); + } + const bool failed = operation.Finish(); + if (inserted && !failed) { + node->pair.value.parent = destination.items->Owner(); + AssignNode(node->pair.value, pair.value); + } + } + } catch (...) { Destroy(pending); throw; } + auto previous = destination; + destination = pending; + Destroy(previous); +} + +Value* ObjectAccess(Object& object, const String& input) { + if (!object.items || !input.text) return const_cast(&Defaults(TypeNull)); + if (!object.items->head) NotImplemented_nid_no_patch(__func__); + AllocationOperation operation; + ObjectLink* node; + { + InlineText key; key.Assign(Characters(input)); + ScopedString defaultKey{std::string_view{}}; + Pair source{}; source.key = defaultKey.string; + TemporaryTreeEntry entry(key.text, source); + bool inserted; + node = object.items->Insert(entry.key.text, entry.pair, inserted); + } + if (operation.Finish()) return const_cast(&Defaults(TypeNull)); + if (!node || node->nil) NotImplemented_nid_no_patch(__func__); + AssignText(node->pair.key, View(input), true); + node->pair.value.parent = object.items->Owner(); + return &node->pair.value; +} + +void AppendArray(Array* self, const Value* value) { + if (!self->items || !self->items->head) return; + AllocationOperation operation; + if (!ModernValues()) { + try { CopyInto(self->items->emplace_back(), *value); } + catch (const GuestOutOfMemory&) {} + if (operation.Finish()) self->items->PopBack(); + return; + } + Value copy{}; + copy.parent = self->items->Owner(); + try { + AssignNode(copy, *value); + auto& inserted = self->items->emplace_back(); + inserted.parent = self->items->Owner(); + TransferPayload(inserted, copy); + } catch (const GuestOutOfMemory&) { } catch (...) { - Destroy(pair.value); - Destroy(pair.key); + Destroy(copy); throw; } + Destroy(copy); + if (operation.Finish()) self->items->PopBack(); +} + +ArrayLink* InsertArrayLink(ArrayStorage& items, ArrayLink* position, const Value& value) { + auto* link = static_cast(Allocate(sizeof(ArrayLink))); + if (!link) return items.head; + try { CopyInto(link->value, value); } + catch (...) { Release(link); throw; } + link->previous = position->previous; + link->next = position; + position->previous->next = link; + position->previous = link; + ++items.count; + return link; +} + +void PrependArray(Array* self, const Value* value) { + if (!self->items) return; + AllocationOperation operation; + if (!ModernValues()) { + if (self->items->head) InsertArrayLink(*self->items, self->items->head->next, *value); + } else { + ScopedValue copy; + copy.value.parent = self->items->Owner(); + AssignNode(copy.value, *value); + Value empty{}; + if (self->items->head) InsertArrayLink(*self->items, self->items->head->next, empty); + if (self->items->count) TransferPayload(self->items->head->next->value, copy.value); + } + if (operation.Finish() && self->items->head && self->items->count) { + auto* link = self->items->head->next; + link->next->previous = self->items->head; + self->items->head->next = link->next; + Destroy(link->value); + Release(link); + --self->items->count; + } +} + +template +struct SerializationIterator { + TLink** state; + SerializationIterator(TLink* position, bool invalid) : state(static_cast(Allocate(sizeof(TLink*)))) { + if (state) *state = position; + if (invalid) { Release(state); state = nullptr; } + } + ~SerializationIterator() { Release(state); } + bool Different(TLink* position, bool invalid) const { + SerializationIterator other(position, invalid); + return state && other.state && *state != *other.state; + } +}; + +template +struct SerializationPosition { TLink* first; TLink* head; bool invalid; }; + +void PrepareValueAdmission(Value& value, const Value* parent) { + value.parent = const_cast(parent); + if (parent) ReleaseValueAuxiliary(value); + else { + EnsureAuxiliary(value); + if (!ModernValues() && value.auxiliary) { value.auxiliary->nullAccess = nullptr; value.auxiliary->nullAccessContext = nullptr; } + } +} + +void AppendLegacyReal(String& output, double value) { + float magnitude = static_cast(value); + const bool negative = magnitude < 0; + if (negative) magnitude = -magnitude; + const bool unordered = std::isnan(magnitude); + const std::uint64_t integer = unordered ? UINT64_C(0x8000000000000000) : + magnitude <= 2147483648.0f ? static_cast(magnitude) : UINT64_C(2147483647); + float fraction = unordered || magnitude > 2147483648.0f ? 0.0f : magnitude - static_cast(integer); + char digits[6]; + bool nonzero = false; + for (char& digit : digits) { + fraction *= 10.0f; + const auto number = static_cast(fraction) % 10; + digit = static_cast('0' + number); + nonzero = nonzero || number != 0; + } + char text[32]; + char* cursor = text; + if (negative) *cursor++ = '-'; + char whole[20]; + unsigned length = 0; + auto remaining = integer; + do { whole[length++] = static_cast('0' + remaining % 10); remaining /= 10; } while (remaining); + while (length) *cursor++ = whole[--length]; + if (nonzero) { *cursor++ = '.'; std::memcpy(cursor, digits, sizeof(digits)); cursor += sizeof(digits); } + AppendText(output, std::string_view(text, cursor - text)); +} + +class CallbackSerializer { +public: + CallbackSerializer(String& buffer, String* destination, SerializationCallback callback, void* context, bool collect) + : buffer(buffer), destination(destination), callback(callback), context(context), collect(collect) {} + int SerializeValue(const Value& value, const Value* parent = nullptr) { + if (!ModernValues()) PrepareValueAdmission(const_cast(value), parent); + if (value.type == TypeReal && !std::isfinite(value.real) && FloatConfiguration().enabled) { + CallbackScope scope; + const auto handlers = HandlersOf(value); + if (scope.entered && handlers.specialFloat) { + ScopedValue replacement; + bool error = false; + RecoverSpecial(value, handlers, 0, value.real, replacement.value, &error); + return error ? JsonErrorSpecialFloat : SerializeValue(replacement.value, parent); + } + } + if (value.type == TypeReal && !FloatConfiguration().enabled) { AppendLegacyReal(buffer, value.real); return Emit(); } + if (value.type >= TypeNull && value.type <= TypeReal) { + std::string scalar; + const int status = Serialize(scalar, value); + if (status < 0) return status; + AppendText(buffer, scalar); + return Emit(); + } + if (value.type == TypeString) { + if (!value.string) { NotImplemented_nid_no_patch(__func__); return 0; } + Escape(*value.string); + return Emit(); + } + if (value.type == TypeArray) { + if (!value.array) { NotImplemented_nid_no_patch(__func__); return 0; } + auto position = [&value] { + if (value.type != TypeArray || !value.array) NotImplemented_nid_no_patch(__func__); + auto* items = value.array->items; + auto* head = items ? items->head : nullptr; + return SerializationPosition{head ? head->next : nullptr, head, items && !head}; + }; + return Collection(position, '[', ']', + [](ArrayLink* link) { return link->next; }, + [this, &value](ArrayLink* link) { return SerializeValue(link->value, &value); }); + } + if (value.type == TypeObject) { + if (!value.object) { NotImplemented_nid_no_patch(__func__); return 0; } + auto position = [&value] { + if (value.type != TypeObject || !value.object) NotImplemented_nid_no_patch(__func__); + auto* items = value.object->items; + auto* head = items ? items->head : nullptr; + return SerializationPosition{head ? head->left : nullptr, head, items && !head}; + }; + return Collection(position, '{', '}', + [](ObjectLink* link) { return NextObjectLink(link); }, + [this, &value](ObjectLink* link) { Escape(link->pair.key); Character(':'); return SerializeValue(link->pair.value, &value); }); + } + return 0; + } +private: + String& buffer; + String* destination; + SerializationCallback callback; + void* context; + bool collect; + void Character(char character) { if (character) AppendText(buffer, std::string_view(&character, 1)); } + int Emit() { + if (collect) { + if (destination) { AppendText(*destination, Characters(buffer)); ClearText(buffer); } + return callback(*destination, context); + } + const int result = callback(buffer, context); + ClearText(buffer); + return result; + } + void Escape(const String& string) { + Character('"'); + for (const unsigned char character : View(string)) { + switch (character) { + case '\b': AppendText(buffer, "\\b"); break; + case '\t': AppendText(buffer, "\\t"); break; + case '\n': AppendText(buffer, "\\n"); break; + case '\f': AppendText(buffer, "\\f"); break; + case '\r': AppendText(buffer, "\\r"); break; + case '"': AppendText(buffer, "\\\""); break; + case '/': AppendText(buffer, "\\/"); break; + case '\\': AppendText(buffer, "\\\\"); break; + default: + if (character < 0x20 || character == 0x7f) { + char escaped[7]; + std::snprintf(escaped, sizeof(escaped), "\\u%04x", character); + AppendText(buffer, escaped); + } else Character(static_cast(character)); + } + } + Character('"'); + } + template + int Collection(TPosition position, char opening, char closing, TNext next, TSerialize serialize) { + Character(opening); + int status = Emit(); + if (status < 0) return status; + { + auto current = position(); + SerializationIterator iterator(current.first, current.invalid); + current = position(); + if (iterator.Different(current.head, current.invalid)) { + do { + current = position(); + if (iterator.Different(current.first, current.invalid)) Character(','); + if (!*iterator.state) { NotImplemented_nid_no_patch(__func__); return 0; } + status = serialize(*iterator.state); + if (status < 0) break; + *iterator.state = next(*iterator.state); + current = position(); + } while (iterator.Different(current.head, current.invalid)); + } + } + if (status < 0) return status; + Character(closing); + return Emit(); + } +}; + +int APS5_VABI SerializationSucceeded(String&, void*) { return 0; } + +int SerializeCallbackValue(Value* value, String* destination, SerializationCallback callback, void* context, bool collect) { + { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.allocation) return JsonErrorNotInitialized; + } + if (!callback || (collect && !destination)) { NotImplemented_nid_no_patch(__func__); return 0; } + ScopedString buffer(std::string_view{}); + CallbackSerializer serializer(buffer.string, destination, callback, context, collect); + return serializer.SerializeValue(*value); +} + +double ParseDecimal(std::string_view text) { + std::size_t cursor = 0; + while (cursor < text.size() && (text[cursor] == ' ' || (text[cursor] >= '\t' && text[cursor] <= '\r'))) ++cursor; + double sign = 1.0; + while (cursor < text.size() && (text[cursor] == '+' || text[cursor] == '-')) { + if (text[cursor++] == '-') sign = -sign; + } + auto digit = [&](std::size_t index) { return index < text.size() && text[index] >= '0' && text[index] <= '9'; }; + double magnitude = 0; + bool admitted = false; + while (digit(cursor)) { + volatile double scaled = magnitude * 10.0; + magnitude = scaled + (text[cursor++] - '0'); + admitted = true; + } + if (cursor < text.size() && text[cursor] == '.') { + ++cursor; + double divisor = 10.0; + while (digit(cursor)) { + magnitude += (text[cursor++] - '0') / divisor; + divisor *= 10.0; + admitted = true; + } + } + if (!admitted) return 0; + if (cursor < text.size() && (text[cursor] == 'e' || text[cursor] == 'E' || text[cursor] == 'd' || text[cursor] == 'D')) { + ++cursor; + bool divide = false; + if (cursor < text.size() && (text[cursor] == '+' || text[cursor] == '-')) divide = text[cursor++] == '-'; + if (!digit(cursor)) return 0; + std::uint32_t exponent = 0; + while (digit(cursor)) exponent = exponent * 10 + static_cast(text[cursor++] - '0'); + while (exponent-- && magnitude != 0 && !std::isinf(magnitude)) { + if (divide) magnitude /= 10.0; + else magnitude *= 10.0; + } + } + return magnitude * sign; } class Parser { public: - 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) {} + Parser(const char* text, std::size_t size) : text(text), end(text + size) {} + Parser(ParserReadCallback read, void* context) : read(read), context(context) {} - bool Parse(Node& out) { - skip(); - return value(out); - } + bool Parse(Node& out) { return value(out); } int Error() const { return readError < 0 ? readError : JsonErrorParse; } @@ -338,7 +2319,6 @@ private: const char* end = nullptr; ParserReadCallback read = nullptr; void* context = nullptr; - Handlers handlers; std::size_t depth = 0; int current = -2; int readError = 0; @@ -370,7 +2350,9 @@ private: } return true; } - static void appendUtf8(std::string& out, std::uint32_t code) { + template + static void appendUtf8(Output& out, std::uint32_t code) { + if (!code) return; if (code < 0x80) out += static_cast(code); else if (code < 0x800) { out += static_cast(0xc0 | (code >> 6)); out += static_cast(0x80 | (code & 0x3f)); } else if (code < 0x10000) { out += static_cast(0xe0 | (code >> 12)); out += static_cast(0x80 | ((code >> 6) & 0x3f)); out += static_cast(0x80 | (code & 0x3f)); } @@ -388,7 +2370,13 @@ private: } return true; } - bool string(std::string& out) { + struct ParsedText { + String& string; + void operator+=(char character) { AppendText(string, {&character, 1}); } + }; + template + bool string(Output& out) { + if (AllocationOperation::Failed()) return false; if (peek() < 0 || peek() != '"') return false; take(); while (peek() >= 0 && peek() != '"') { @@ -423,80 +2411,89 @@ private: take(); return true; } - bool number(Node& out) { - std::string token; - if (peek() >= 0 && peek() == '-') token.push_back(static_cast(take())); + void Admit(Node& out, const Value* parent) { PrepareValueAdmission(out, parent); } + bool number(Node& out, const Value* parent) { + ScopedString token{std::string_view{}}; + auto append = [&](char character) { AppendText(token.string, {&character, 1}); }; + if (peek() >= 0 && peek() == '-') append(static_cast(take())); if (peek() < 0 || peek() < '0' || peek() > '9') return false; - while (peek() >= '0' && peek() <= '9') token.push_back(static_cast(take())); + while (peek() >= '0' && peek() <= '9') append(static_cast(take())); bool integral = true; if (peek() >= 0 && peek() == '.') { integral = false; - token.push_back(static_cast(take())); + append(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())); + while (peek() >= 0 && peek() >= '0' && peek() <= '9') append(static_cast(take())); } if (peek() >= 0 && (peek() == 'e' || peek() == 'E')) { integral = false; - token.push_back(static_cast(take())); - if (peek() >= 0 && (peek() == '+' || peek() == '-')) token.push_back(static_cast(take())); + append(static_cast(take())); + if (peek() >= 0 && (peek() == '+' || peek() == '-')) append(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())); + while (peek() >= 0 && peek() >= '0' && peek() <= '9') append(static_cast(take())); + } + const auto text = View(token.string); + if (text.empty()) { + SetType(out, TypeUInteger); + out.uinteger = 0; + Admit(out, parent); + return true; } - const std::string& text = token; if (integral) { errno = 0; if (text[0] == '-') { - const long long value = std::strtoll(text.c_str(), nullptr, 10); - if (errno == 0) { out.type = TypeInteger; out.integer = value; return true; } + const long long value = std::strtoll(Characters(token.string), nullptr, 10); + if (errno == 0) { SetType(out, TypeInteger); out.integer = value; Admit(out, parent); return true; } } else { - const unsigned long long value = std::strtoull(text.c_str(), nullptr, 10); + const unsigned long long value = std::strtoull(Characters(token.string), nullptr, 10); if (errno == 0) { - out.type = TypeUInteger; + SetType(out, TypeUInteger); out.uinteger = value; + Admit(out, parent); return true; } } } - 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.eof() && (!stream.fail() || real == 0); - } - if (!converted || !std::isfinite(real)) throw std::runtime_error("sce::Json::Parser::parse: number " + text + " overflows a double; the console's behaviour is unverified"); - out.type = TypeReal; - out.real = real; + const double real = ParseDecimal(text); + Admit(out, parent); + AssignReal(out, real); return true; } - bool value(Node& out) { + bool value(Node& out, const Value* parent = nullptr) { + if (AllocationOperation::Failed()) return false; + skip(); 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; + case 'n': if (!literal("null")) return false; SetType(out, TypeNull); Admit(out, parent); return true; + case 't': if (!literal("true")) return false; SetType(out, TypeBoolean); out.boolean = true; Admit(out, parent); return true; + case 'f': if (!literal("false")) return false; SetType(out, TypeBoolean); out.boolean = false; Admit(out, parent); return true; case '"': { - std::string text; - if (!string(text)) return false; SetType(out, TypeString); - *out.string.text = std::move(text); - return true; + Admit(out, parent); + if (out.type != TypeString) throw GuestOutOfMemory{}; + if (AllocationOperation::Failed()) return false; + ParsedText text{*out.string}; + return string(text); } case '[': { if (depth == MaxNestingDepth) return false; take(); ++depth; SetType(out, TypeArray); + Admit(out, parent); + if (out.type != TypeArray) throw GuestOutOfMemory{}; skip(); if (peek() >= 0 && peek() == ']') { take(); --depth; return true; } for (;;) { - auto& item = out.array.items->emplace_back(); - Construct(item); - item.node->handlers = handlers; + if (!out.array->items) throw GuestOutOfMemory{}; + if (!out.array->items->head) NotImplemented_nid_no_patch(__func__); + Value placeholder{}; + AppendArray(out.array, &placeholder); + if (AllocationOperation::Failed()) return false; + auto& item = out.array->items->back(); + item.parent = &out; skip(); - if (!value(*item.node)) return false; + if (!value(item, &out)) return false; skip(); if (peek() >= 0 && peek() == ',') { take(); continue; } if (peek() >= 0 && peek() == ']') { take(); --depth; return true; } @@ -508,20 +2505,22 @@ private: take(); ++depth; SetType(out, TypeObject); + Admit(out, parent); + if (out.type != TypeObject) throw GuestOutOfMemory{}; skip(); if (peek() >= 0 && peek() == '}') { take(); --depth; return true; } + ScopedString key{std::string_view{}}; for (;;) { skip(); - std::string key; - if (!string(key)) return false; + ParsedText text{key.string}; + if (!string(text)) return false; skip(); if (peek() < 0 || peek() != ':') return false; take(); skip(); - Value& entry = ObjectEntry(out.object, key); - Clear(*entry.node); - entry.node->handlers = handlers; - if (!value(*entry.node)) return false; + Value& entry = *ObjectAccess(*out.object, key.string); + if (!value(entry, &out)) return false; + ClearText(key.string); skip(); if (peek() >= 0 && peek() == ',') { take(); continue; } if (peek() >= 0 && peek() == '}') { take(); --depth; return true; } @@ -529,28 +2528,23 @@ private: } } default: - return number(out); + if (peek() != '-' && (peek() < '0' || peek() > '9')) return false; + return number(out, parent); } } }; int ParseValue(Value* out, Parser& parser) { - Node parsed{}; - parsed.handlers = NodeOf(*out).handlers; + AllocationOperation operation; bool parsedOk = false; try { - parsedOk = parser.Parse(parsed); - } catch (...) { - Clear(parsed); - throw; + parsedOk = parser.Parse(NodeOf(*out)); + } catch (const GuestOutOfMemory&) { + operation.Finish(); + return JsonErrorOutOfMemory; } - if (!parsedOk) { - Clear(parsed); - return parser.Error(); - } - Clear(NodeOf(*out)); - *out->node = parsed; - return 0; + if (operation.Finish()) return JsonErrorOutOfMemory; + return parsedOk ? 0 : parser.Error(); } [[noreturn]] void APS5_VABI JsonMemAllocatorPureVirtual() { @@ -568,35 +2562,68 @@ void* const JsonMemAllocatorVtable[] = { extern "C" { -int APS5_VABI _ZN3sce4Json11InitializerC1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce4Json11InitializerC1Ev(void* self) { + *static_cast(self) = 0; } -int APS5_VABI _ZN3sce4Json11InitializerD1Ev(void* self) { - (void)self; - return 0; +void APS5_VABI _ZN3sce4Json11InitializerD1Ev(void* self) { + _ZN3sce4Json11Initializer9terminateEv(self); } int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(void* self, const void* parameter) { - (void)self; - if (parameter == nullptr) throw std::invalid_argument("sce::Json::Initializer::initialize: null parameter"); - return 0; + auto& state = State(); + std::lock_guard callbackLock(state.callbackMutex); + std::lock_guard lock(state.mutex); + if (state.allocation || state.initialized || *static_cast(self)) return JsonErrorAlreadyInitialized; + if (!parameter) return JsonErrorInvalidArgument; + if (!*static_cast(parameter)) return JsonErrorOutOfMemory; + ResetDefaults(); + return InitializeResources(self, parameter, 2, false); +} + +void APS5_VABI _ZN3sce4Json19InternalInitializerC1Ev(void* self) { _ZN3sce4Json11InitializerC1Ev(self); } + +void APS5_VABI _ZN3sce4Json19InternalInitializerC2Ev(void* self) { _ZN3sce4Json11InitializerC1Ev(self); } + +void APS5_VABI _ZN3sce4Json19InternalInitializerD1Ev(void* self) { + _ZN3sce4Json11Initializer9terminateEv(self); + _ZN3sce4Json11Initializer9terminateEv(self); +} + +void APS5_VABI _ZN3sce4Json19InternalInitializerD2Ev(void* self) { _ZN3sce4Json19InternalInitializerD1Ev(self); } + +int APS5_VABI _ZN3sce4Json19InternalInitializer19terminate_staticlibEv(void* self) { return _ZN3sce4Json11Initializer9terminateEv(self); } + +int APS5_VABI _ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE(void* self, const void* parameter, std::int32_t option) { + auto& state = State(); + std::lock_guard callbackLock(state.callbackMutex); + std::lock_guard lock(state.mutex); + state.paddedStringGrowth = option == 1; + return _ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(self, parameter); } int APS5_VABI _ZN3sce4Json11Initializer9terminateEv(void* self) { - (void)self; auto& state = State(); + std::lock_guard callbackLock(state.callbackMutex); std::lock_guard lock(state.mutex); + if (!state.initialized || !*static_cast(self)) return JsonErrorNotInitialized; + if (state.internalMutexRetired || state.recoveryMutexRetired || state.retiredDefaultHeaders) NotImplemented_nid_no_patch(__func__); + if (state.terminating || state.callbackActive || (allocatorCallbackDepth && state.allocationDepth)) NotImplemented_nid_no_patch(__func__); + TerminationScope termination(state.terminating); state.callback = nullptr; state.context = nullptr; + state.handlers = {}; + ReleaseInitialization(); + *static_cast(self) = 0; return 0; } int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void* self, NullAccessCallback callback, void* context) { - (void)self; auto& state = State(); std::lock_guard lock(state.mutex); + if (!state.initialized || !*static_cast(self)) return JsonErrorNotInitialized; + if (!callback) return JsonErrorInvalidArgument; + if (state.callback) return static_cast(0x80848112); state.callback = callback; state.context = context; return 0; @@ -606,342 +2633,424 @@ int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5Va return _ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(self, callback, context); } +int APS5_VABI _ZN3sce4Json11Initializer28setGlobalTypeMismatchHandlerEPFRKNS0_5ValueENS0_9ValueTypeEPS3_S6_PvES7_(void* self, TypeMismatchHandler callback, void* context) { + return SetGlobalHandler(self, &Handlers::typeMismatch, &Handlers::typeMismatchContext, callback, context); +} + +int APS5_VABI _ZN3sce4Json11Initializer28setGlobalSpecialFloatHandlerEPFKNS0_5ValueENS0_12FunctionTypeEdPS3_PvPbES6_(void* self, SpecialFloatHandler callback, void* context) { + return SetGlobalHandler(self, &Handlers::specialFloat, &Handlers::specialFloatContext, callback, context, true); +} + +int APS5_VABI _ZN3sce4Json11Initializer36setGlobalElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKNS0_5ValueES9_PvESA_(void* self, ElementAccessFailureHandler callback, void* context) { + return SetGlobalHandler(self, &Handlers::elementAccessFailure, &Handlers::elementAccessFailureContext, callback, context); +} + void APS5_VABI _ZN3sce4Json12MemAllocatorC2Ev(void* self) { *static_cast(self) = &JsonMemAllocatorVtable[2]; } -void APS5_VABI _ZN3sce4Json12MemAllocatorD2Ev(void* self) { - *static_cast(self) = &JsonMemAllocatorVtable[2]; +void APS5_VABI _ZN3sce4Json12MemAllocatorD2Ev(void*) {} + +void APS5_VABI _ZN3sce4Json12MemAllocatorD1Ev(void*) {} + +void* APS5_VABI _ZN3sce4Json6MallocEm(std::size_t size) { return Allocate(size); } + +void APS5_VABI _ZN3sce4Json4FreeEPv(void* memory) { Release(memory); } + +int APS5_VABI _ZN3sce4Json11Initializer24setAllocatorInfoCallBackEPFviNS0_9ValueTypeEPvES3_(void*, void (APS5_VABI*)(int, ValueType, void*), void*) { + auto& state = State(); + std::lock_guard lock(state.mutex); + return state.allocation ? 0 : JsonErrorNotInitialized; } +namespace SceJson2 { +extern const bool _ZN3sce4Json5Value10s_nullboolE = false; +extern const std::int64_t _ZN3sce4Json5Value13s_nullintegerE = 0; +extern const std::uint64_t _ZN3sce4Json5Value14s_nulluintegerE = 0; +extern const double _ZN3sce4Json5Value10s_nullrealE = 0; +String* _ZN3sce4Json5Value12s_nullstringE = nullptr; +Array* _ZN3sce4Json5Value11s_nullarrayE = nullptr; +Object* _ZN3sce4Json5Value12s_nullobjectE = nullptr; +} std::size_t _ZN3sce4Json6String4nposE = static_cast(-1); -void APS5_VABI _ZN3sce4Json6StringC1Ev(String* self) { Construct(*self, std::string()); } -void APS5_VABI _ZN3sce4Json6StringC1EPKc(String* self, const char* text) { - if (text == nullptr) throw std::invalid_argument("sce::Json::String: null text"); - Construct(*self, text); -} -void APS5_VABI _ZN3sce4Json6StringC1ERKS1_(String* self, const String* other) { Construct(*self, *other->text); } +void APS5_VABI _ZN3sce4Json6StringC1Ev(String* self) { Construct(*self, std::string_view{}); } +void APS5_VABI _ZN3sce4Json6StringC1EPKc(String* self, const char* text) { Construct(*self, text ? text : ""); } +void APS5_VABI _ZN3sce4Json6StringC1ERKS1_(String* self, const String* other) { Construct(*self, *other); } void APS5_VABI _ZN3sce4Json6StringD1Ev(String* self) { Destroy(*self); } String* APS5_VABI _ZN3sce4Json6StringaSERKS1_(String* self, const String* other) { - if (self != other) *self->text = *other->text; + if (self->text != other->text && other->text) AssignText(*self, View(*other), true); return self; } -const char* APS5_VABI _ZNK3sce4Json6String5c_strEv(const String* self) { return self->text->c_str(); } -bool APS5_VABI _ZNK3sce4Json6String5emptyEv(const String* self) { return self->text->empty(); } -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 && 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); } +const char* APS5_VABI _ZNK3sce4Json6String5c_strEv(const String* self) { return Characters(*self); } +bool APS5_VABI _ZNK3sce4Json6String5emptyEv(const String* self) { return View(*self).empty(); } +std::size_t APS5_VABI _ZNK3sce4Json6String6lengthEv(const String* self) { return View(*self).size(); } +std::size_t APS5_VABI _ZNK3sce4Json6String4sizeEv(const String* self) { return View(*self).size(); } +std::size_t APS5_VABI _ZNK3sce4Json6String8capacityEv(const String* self) { return self->text ? self->text->capacity : 0; } +bool APS5_VABI _ZNK3sce4Json6StringeqEPKc(const String* self, const char* text) { return text != nullptr && CompareText(Characters(*self), text) == 0; } +bool APS5_VABI _ZNK3sce4Json6StringeqERKS1_(const String* self, const String* other) { return other != nullptr && CompareText(Characters(*self), Characters(*other)) == 0; } +void APS5_VABI _ZN3sce4Json6String5clearEv(String* self) { ClearText(*self); } +void APS5_VABI _ZN3sce4Json6String6resizeEm(String* self, std::size_t count) { + if (!self->text || count == self->text->length) return; + auto& text = *self->text; + if (count < text.length) { text.characters[count] = 0; text.length = static_cast(count); } + else if (count < std::numeric_limits::max() && Grow(text, count + 1)) { + std::memset(text.characters + text.length, 0, count - text.length + 1); + text.length = static_cast(count); + } else if (count >= std::numeric_limits::max()) { + throw std::length_error("sce::Json::String: length exceeds 32 bits"); + } +} +void APS5_VABI _ZN3sce4Json6String7reserveEm(String* self, std::size_t capacity) { if (self->text) Grow(*self->text, capacity); } String* APS5_VABI _ZN3sce4Json6String6appendERKS1_(String* self, const String* other) { - const std::string copy = *other->text; - self->text->append(copy); + AppendText(*self, Characters(*other)); return self; } String* APS5_VABI _ZN3sce4Json6String6appendEPKc(String* self, const char* text) { - if (text == nullptr) throw std::invalid_argument("sce::Json::String::append: null text"); - self->text->append(text); + if (!text) throw std::invalid_argument("sce::Json::String::append: null text"); + AppendText(*self, text); return self; } String* APS5_VABI _ZN3sce4Json6String6appendEPKcm(String* self, const char* text, std::size_t length) { - if (text == nullptr && length > 0) throw std::invalid_argument("sce::Json::String::append: null text"); - self->text->append(text, length); + if (!text && length) throw std::invalid_argument("sce::Json::String::append: null text"); + AppendText(*self, {text ? text : "", length}); return self; } -String* APS5_VABI _ZN3sce4Json6StringpLEPKc(String* self, const char* text) { - return _ZN3sce4Json6String6appendEPKc(self, text); -} +String* APS5_VABI _ZN3sce4Json6StringpLEPKc(String* self, const char* text) { return _ZN3sce4Json6String6appendEPKc(self, text); } String* APS5_VABI _ZN3sce4Json6StringpLEh(String* self, unsigned char ch) { - self->text->push_back(static_cast(ch)); + const char text[]{static_cast(ch), 0}; + AppendText(*self, text); return self; } String* APS5_VABI _ZNK3sce4Json6String6substrEmm(String* result, const String* self, std::size_t position, std::size_t length) { - if (position > self->text->size()) throw std::out_of_range("sce::Json::String::substr: position past the end"); - Construct(*result, self->text->substr(position, length)); + if (position > View(*self).size()) throw std::out_of_range("sce::Json::String::substr: position past the end"); + Construct(*result, View(*self).substr(position, length)); return result; } -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); +char APS5_VABI _ZNK3sce4Json6String2atEm(const String* self, std::size_t index) { + return !self->text ? 0 : index < self->text->length ? self->text->characters[index] : emptyCharacters[0]; } +std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcm(const String* self, const char* needle, std::size_t position) { return FindText(*self, needle, position, needle ? std::strlen(needle) : 0); } +std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcmm(const String* self, const char* needle, std::size_t position, std::size_t count) { return FindText(*self, needle, position, count); } +std::size_t APS5_VABI _ZNK3sce4Json6String4findERKS1_m(const String* self, const String* needle, std::size_t position) { return FindText(*self, Characters(*needle), position, std::strlen(Characters(*needle))); } +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcm(const String* self, const char* needle, std::size_t position) { return ReverseFindText(*self, needle, position, needle ? std::strlen(needle) : 0); } +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcmm(const String* self, const char* needle, std::size_t position, std::size_t count) { return ReverseFindText(*self, needle, position, count); } +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindERKS1_m(const String* self, const String* needle, std::size_t position) { return ReverseFindText(*self, Characters(*needle), position, std::strlen(Characters(*needle))); } +std::size_t APS5_VABI _ZNK3sce4Json6String4findEcm(const String* self, char character, std::size_t position) { return FindText(*self, &character, position, 1); } +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEcm(const String* self, char character, std::size_t position) { return ReverseFindText(*self, &character, position, 1); } int APS5_VABI _ZNK3sce4Json6String7compareEPKc(const String* self, const char* text) { - if (text == nullptr) throw std::invalid_argument("sce::Json::String::compare: null text"); - return CompareText(self->text->c_str(), text); + if (!text) throw std::invalid_argument("sce::Json::String::compare: null text"); + return CompareText(Characters(*self), 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 CompareText(self->text->c_str(), other->text->c_str()); + if (!other) throw std::invalid_argument("sce::Json::String::compare: null other"); + return CompareText(Characters(*self), Characters(*other)); } 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); - try { - self->items->push_back(copy); - } catch (...) { - Destroy(copy); - throw; +Array* APS5_VABI _ZN3sce4Json5ArrayaSERKS1_(Array* self, const Array* other) { AssignArray(*self, *other); return self; } +void APS5_VABI _ZN3sce4Json5Array10push_frontERKNS0_5ValueE(Array* self, const Value* value) { PrependArray(self, value); } + +ArrayIterator* APS5_VABI _ZN3sce4Json5Array6insertERKNS1_8iteratorERKNS0_5ValueE(ArrayIterator* result, Array* self, const ArrayIterator* position, const Value* value) { + result->state = static_cast(Allocate(sizeof(ArrayIteratorState))); + if (result->state) result->state->position = nullptr; + if (!self->items) return result; + if (!position || !position->state || !position->state->position || !self->items->head) { + Release(result->state); result->state = nullptr; + NotImplemented_nid_no_patch(__func__); } + ArrayLink* inserted; + if (!ModernValues()) inserted = InsertArrayLink(*self->items, position->state->position, *value); + else { + ScopedValue copy; + copy.value.parent = self->items->Owner(); + AssignNode(copy.value, *value); + AllocationOperation operation; + Value empty{}; + inserted = InsertArrayLink(*self->items, position->state->position, empty); + if (!operation.Finish()) TransferPayload(inserted->value, copy.value); + } + if (inserted) { if (result->state) result->state->position = inserted; } + else { Release(result->state); result->state = nullptr; } + 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 _ZN3sce4Json5Array9push_backERKNS0_5ValueE(Array* self, const Value* value) { AppendArray(self, value); } + +ArrayIterator* APS5_VABI _ZNK3sce4Json5Array5beginEv(ArrayIterator* result, const Array* self) { + result->state = static_cast(Allocate(sizeof(ArrayIteratorState))); + if (result->state) result->state->position = self->items && self->items->head ? self->items->head->next : nullptr; + if (self->items && (!self->items->head || !self->items->head->next)) { + Release(result->state); + result->state = nullptr; + } + return result; +} +ArrayIterator* APS5_VABI _ZNK3sce4Json5Array3endEv(ArrayIterator* result, const Array* self) { + result->state = static_cast(Allocate(sizeof(ArrayIteratorState))); + if (result->state) result->state->position = self->items ? self->items->head : nullptr; + if (self->items && !self->items->head) { + Release(result->state); + result->state = nullptr; + } + return result; +} +bool APS5_VABI _ZNK3sce4Json5Array5emptyEv(const Array* self) { return !self->items || self->items->empty(); } +std::size_t APS5_VABI _ZNK3sce4Json5Array4sizeEv(const Array* self) { return self->items ? self->items->size() : 0; } void APS5_VABI _ZN3sce4Json5Array5clearEv(Array* self) { - for (auto& item : *self->items) Destroy(item); + if (!self->items) return; self->items->clear(); } const Value* APS5_VABI _ZNK3sce4Json5Array5frontEv(const Array* self) { - if (self->items->empty()) throw std::out_of_range("sce::Json::Array::front: empty array"); + if (!self->items) return &Defaults(TypeNull); + if (!self->items->head) NotImplemented_nid_no_patch(__func__); return &self->items->front(); } const Value* APS5_VABI _ZNK3sce4Json5Array4backEv(const Array* self) { - if (self->items->empty()) throw std::out_of_range("sce::Json::Array::back: empty array"); + if (!self->items) return &Defaults(TypeNull); + if (!self->items->head) return reinterpret_cast(16); return &self->items->back(); } -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 _ZN3sce4Json5Array8pop_backEv(Array* self) { if (self->items) self->items->PopBack(); } + +ArrayIterator* APS5_VABI _ZN3sce4Json5Array5eraseERKNS1_8iteratorE(ArrayIterator* result, Array* self, const ArrayIterator* position) { + result->state = static_cast(Allocate(sizeof(ArrayIteratorState))); + if (result->state) result->state->position = nullptr; + if (!self->items) return result; + if (!position || !position->state || !position->state->position || position->state->position == self->items->head) { + Release(result->state); + result->state = nullptr; + NotImplemented_nid_no_patch(__func__); + } + auto* link = position->state->position; + auto* next = link->next; + next->previous = link->previous; + link->previous->next = next; + Destroy(link->value); + Release(link); + --self->items->count; + if (next) { + if (result->state) result->state->position = next; + } else { + Release(result->state); + result->state = nullptr; + } + return result; } -void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(ArrayIterator* self) { delete self->state; self->state = nullptr; } + +void APS5_VABI _ZN3sce4Json5Array9pop_frontEv(Array* self) { + if (!self->items || !self->items->head) NotImplemented_nid_no_patch(__func__); + if (self->items->empty()) throw std::out_of_range("sce::Json::Array::pop_front: empty array"); + auto* link = self->items->head->next; + self->items->head->next = link->next; + link->next->previous = self->items->head; + Destroy(link->value); + Release(link); + --self->items->count; +} +void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(ArrayIterator* self) { Release(self->state); self->state = nullptr; } ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(ArrayIterator* self) { - if (self->state && self->state->position != self->state->end) ++self->state->position; + if (self->state && self->state->position) self->state->position = self->state->position->next; 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; + if (!self->state) return const_cast(&Defaults(TypeNull)); + return reinterpret_cast(reinterpret_cast(self->state->position) + 16); } 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; + return self->state && other->state && self->state->position != other->state->position; } void APS5_VABI _ZN3sce4Json5Array8iteratorC1ERKS2_(ArrayIterator* self, const ArrayIterator* other) { - self->state = other->state ? new ArrayIteratorState(*other->state) : nullptr; + self->state = other->state ? static_cast(Allocate(sizeof(ArrayIteratorState))) : nullptr; + if (self->state) *self->state = *other->state; } -ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iterator7advanceEm(ArrayIterator* self, std::size_t count) { - if (self->state) { - while (count && self->state->position != self->state->end) { - ++self->state->position; - --count; - } - } - return self; +void APS5_VABI _ZN3sce4Json5Array8iterator7advanceEm(ArrayIterator* self, std::size_t count) { + if (!self->state || !self->state->position || !count) return; + auto* start = self->state->position; + auto* node = start; + std::size_t traversed = 0; + do { + node = node->next; + ++traversed; + } while (node && node != start && traversed < count); + if (node == start) count %= traversed; + else { self->state->position = node; return; } + while (count--) node = node->next; + self->state->position = node; } void APS5_VABI _ZN3sce4Json6ObjectC1Ev(Object* self) { Construct(*self); } void APS5_VABI _ZN3sce4Json6ObjectC1ERKS1_(Object* self, const Object* other) { CopyObject(*self, *other); } void APS5_VABI _ZN3sce4Json6ObjectD1Ev(Object* self) { Destroy(*self); } Object* APS5_VABI _ZN3sce4Json6ObjectaSERKS1_(Object* self, const Object* other) { - if (self == other) return self; - Object copy{}; - CopyObject(copy, *other); - Destroy(*self); - *self = copy; + AssignObject(*self, *other); return self; } -void APS5_VABI _ZN3sce4Json6Object5clearEv(Object* self) { - for (auto& pair : *self->items) { - Destroy(pair.key); - Destroy(pair.value); - } - self->items->clear(); +void APS5_VABI _ZN3sce4Json6Object5clearEv(Object* self) { if (self->items) self->items->clear(); } +Value* APS5_VABI _ZN3sce4Json6ObjectixERKNS0_6StringE(Object* self, const String* key) { return ObjectAccess(*self, *key); } +ObjectIterator* APS5_VABI _ZNK3sce4Json6Object5beginEv(ObjectIterator* result, const Object* self) { + result->state = static_cast(Allocate(sizeof(ObjectIteratorState))); + if (result->state) result->state->position = self->items && self->items->head ? self->items->head->left : nullptr; + if (self->items && (!self->items->head || !self->items->head->left)) { Release(result->state); result->state = nullptr; } + return result; } -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->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 _ZNK3sce4Json6Object3endEv(ObjectIterator* result, const Object* self) { + result->state = static_cast(Allocate(sizeof(ObjectIteratorState))); + if (result->state) result->state->position = self->items ? self->items->head : nullptr; + if (self->items && !self->items->head) { Release(result->state); result->state = nullptr; } + return result; +} +void APS5_VABI _ZN3sce4Json6Object8iteratorD1Ev(ObjectIterator* self) { Release(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; - } + if (self->state) self->state->position = NextObjectLink(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; + if (self->state) return reinterpret_cast(reinterpret_cast(self->state->position) + 48); + const auto* object = Defaults(TypeObject).object; + if (!object || !object->items) NotImplemented_nid_no_patch(__func__); + ObjectIterator fallback{}; + _ZNK3sce4Json6Object5beginEv(&fallback, object); + if (!fallback.state) NotImplemented_nid_no_patch(__func__); + auto* pair = reinterpret_cast(reinterpret_cast(fallback.state->position) + 48); + Release(fallback.state); + return pair; } Pair* APS5_VABI _ZNK3sce4Json6Object8iteratorptEv(const ObjectIterator* self) { return _ZNK3sce4Json6Object8iteratordeEv(self); } ObjectIterator* APS5_VABI _ZNK3sce4Json6Object4findERKNS0_6StringE(ObjectIterator* result, const Object* self, const String* key) { - 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}; + result->state = static_cast(Allocate(sizeof(ObjectIteratorState))); + if (result->state) result->state->position = self->items && key->text ? self->items->Find(Characters(*key)) : nullptr; + if (self->items && key->text && !self->items->head) { Release(result->state); result->state = nullptr; } return result; } 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; + return self->state && other->state && 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); + return self->state && other->state && self->state->position != other->state->position; } void APS5_VABI _ZN3sce4Json6Object8iteratorC1ERKS2_(ObjectIterator* self, const ObjectIterator* other) { - self->state = other->state ? new ObjectIteratorState(*other->state) : nullptr; + self->state = other->state ? static_cast(Allocate(sizeof(ObjectIteratorState))) : nullptr; + if (self->state) *self->state = *other->state; } -ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iterator7advanceEm(ObjectIterator* self, std::size_t count) { - if (self->state && self->state->owner) { - while (count) { - _ZN3sce4Json6Object8iteratorppEv(self); - - --count; - } - } - return self; +void APS5_VABI _ZN3sce4Json6Object8iterator7advanceEm(ObjectIterator* self, std::size_t count) { + if (self->state) while (count--) self->state->position = NextObjectLink(self->state->position); } -bool APS5_VABI _ZNK3sce4Json6Object5emptyEv(const Object* self) { return self->items->empty(); } -std::size_t APS5_VABI _ZNK3sce4Json6Object4sizeEv(const Object* self) { return self->items->size(); } +bool APS5_VABI _ZNK3sce4Json6Object5emptyEv(const Object* self) { return !self->items || self->items->empty(); } +std::size_t APS5_VABI _ZNK3sce4Json6Object4sizeEv(const Object* self) { return self->items ? self->items->size() : 0; } 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 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); + if (!self->items || !key->text) return; + self->items->Erase(self->items->Find(Characters(*key))); } void APS5_VABI _ZN3sce4Json5ValueC1Ev(Value* self) { Construct(*self); } -void APS5_VABI _ZN3sce4Json5ValueC1Eb(Value* self, bool value) { Construct(*self); self->node->type = TypeBoolean; self->node->boolean = value; } -void APS5_VABI _ZN3sce4Json5ValueC1El(Value* self, std::int64_t value) { Construct(*self); self->node->type = TypeInteger; self->node->integer = value; } -void APS5_VABI _ZN3sce4Json5ValueC1Em(Value* self, std::uint64_t value) { Construct(*self); self->node->type = TypeUInteger; self->node->uinteger = value; } -void APS5_VABI _ZN3sce4Json5ValueC1Ed(Value* self, double value) { Construct(*self); self->node->type = TypeReal; self->node->real = value; } +void APS5_VABI _ZN3sce4Json5ValueC1Eb(Value* self, bool value) { Construct(*self); self->type = TypeBoolean; self->boolean = value; } +void APS5_VABI _ZN3sce4Json5ValueC1El(Value* self, std::int64_t value) { Construct(*self); self->type = TypeInteger; self->integer = value; } +void APS5_VABI _ZN3sce4Json5ValueC1Em(Value* self, std::uint64_t value) { Construct(*self); self->type = TypeUInteger; self->uinteger = value; } +void APS5_VABI _ZN3sce4Json5ValueC1Ed(Value* self, double value) { Construct(*self); AssignReal(*self, value); } void APS5_VABI _ZN3sce4Json5ValueC1EPKc(Value* self, const char* text) { - if (text == nullptr) throw std::invalid_argument("sce::Json::Value: null text"); - Construct(*self, [text](Node& node) { - SetType(node, TypeString); - *node.string.text = text; - }); + Construct(*self); + ScopedString temporary{std::string_view{text ? text : ""}}; + if (!temporary.string.text) return; + SetType(*self, TypeString); + if (self->type == TypeString) AssignText(*self->string, View(temporary.string), true); } void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6StringE(Value* self, const String* text) { - Construct(*self, [text](Node& node) { - SetType(node, TypeString); - *node.string.text = *text->text; - }); + Construct(*self); + if (!text->text) return; + SetType(*self, TypeString); + if (self->type == TypeString) AssignText(*self->string, View(*text), true); } void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(Value* self, const Array* array) { - Construct(*self, [array](Node& node) { - node.type = TypeArray; - CopyArray(node.array, *array); - }); + Construct(*self); + if (!array->items) return; + SetType(*self, TypeArray); + if (self->array) AssignArray(*self->array, *array); } void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(Value* self, const Object* object) { - Construct(*self, [object](Node& node) { - node.type = TypeObject; - CopyObject(node.object, *object); - }); + Construct(*self); + if (!object->items) return; + SetType(*self, TypeObject); + if (self->object) AssignObject(*self->object, *object); } void APS5_VABI _ZN3sce4Json5ValueC1ERKS1_(Value* self, const Value* other) { CopyInto(*self, *other); } void APS5_VABI _ZN3sce4Json5ValueD1Ev(Value* self) { Destroy(*self); } Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(Value* self, const Value* other) { - if (self != other) AssignNode(NodeOf(*self), NodeOf(*other)); + AssignNode(NodeOf(*self), NodeOf(*other)); return self; } void APS5_VABI _ZN3sce4Json5Value4swapERS1_(Value* self, Value* other) { + for (auto* parent = other->parent; parent; parent = parent->parent) + if (parent == self) NotImplemented_nid_no_patch(__func__); + for (auto* parent = self->parent; parent; parent = parent->parent) + if (parent == other) NotImplemented_nid_no_patch(__func__); Node temporary{}; try { AssignNode(temporary, NodeOf(*self)); AssignNode(NodeOf(*self), NodeOf(*other)); AssignNode(NodeOf(*other), temporary); } catch (...) { - Clear(temporary); + Destroy(temporary); throw; } - Clear(temporary); + Destroy(temporary); } -int APS5_VABI _ZN3sce4Json5Value3setENS0_9ValueTypeE(Value* self, ValueType type) { SetType(NodeOf(*self), type); return 0; } -int APS5_VABI _ZN3sce4Json5Value3setEb(Value* self, bool value) { SetType(NodeOf(*self), TypeBoolean); self->node->boolean = value; return 0; } -int APS5_VABI _ZN3sce4Json5Value3setEl(Value* self, std::int64_t value) { SetType(NodeOf(*self), TypeInteger); self->node->integer = value; return 0; } -int APS5_VABI _ZN3sce4Json5Value3setEm(Value* self, std::uint64_t value) { SetType(NodeOf(*self), TypeUInteger); self->node->uinteger = value; return 0; } -int APS5_VABI _ZN3sce4Json5Value3setEd(Value* self, double value) { SetType(NodeOf(*self), TypeReal); self->node->real = value; return 0; } -int APS5_VABI _ZN3sce4Json5Value3setEPKc(Value* self, const char* text) { +void APS5_VABI _ZN3sce4Json5Value3setENS0_9ValueTypeE(Value* self, ValueType type) { SetType(NodeOf(*self), type); } +void APS5_VABI _ZN3sce4Json5Value3setEb(Value* self, bool value) { SetType(NodeOf(*self), TypeBoolean); self->boolean = value; } +void APS5_VABI _ZN3sce4Json5Value3setEl(Value* self, std::int64_t value) { SetType(NodeOf(*self), TypeInteger); self->integer = value; } +void APS5_VABI _ZN3sce4Json5Value3setEm(Value* self, std::uint64_t value) { SetType(NodeOf(*self), TypeUInteger); self->uinteger = value; } +void APS5_VABI _ZN3sce4Json5Value3setEd(Value* self, double value) { AssignReal(NodeOf(*self), value); } +void APS5_VABI _ZN3sce4Json5Value3setEPKc(Value* self, const char* text) { if (text == nullptr) throw std::invalid_argument("sce::Json::Value::set: null text"); - const std::string copy(text); + ScopedString temporary{std::string_view{text}}; + if (!temporary.string.text) return; SetType(NodeOf(*self), TypeString); - *self->node->string.text = copy; - return 0; + if (self->type == TypeString) AssignText(*self->string, View(temporary.string), true); } -int APS5_VABI _ZN3sce4Json5Value3setERKNS0_6StringE(Value* self, const String* text) { - const std::string copy(*text->text); - SetType(NodeOf(*self), TypeString); - *self->node->string.text = copy; - return 0; +void APS5_VABI _ZN3sce4Json5Value3setERKNS0_6StringE(Value* self, const String* text) { + if (!text->text) return; + Value source{}; + source.type = TypeString; + source.string = const_cast(text); + AssignPayload(*self, source); } -int APS5_VABI _ZN3sce4Json5Value3setERKNS0_5ArrayE(Value* self, const Array* array) { - Array copy{}; - CopyArray(copy, *array); - Clear(NodeOf(*self)); - self->node->type = TypeArray; - self->node->array = copy; - return 0; +void APS5_VABI _ZN3sce4Json5Value3setERKNS0_5ArrayE(Value* self, const Array* array) { + if (!array->items) return; + Value source{}; + source.type = TypeArray; + source.array = const_cast(array); + AssignPayload(*self, source); } -int APS5_VABI _ZN3sce4Json5Value3setERKNS0_6ObjectE(Value* self, const Object* object) { - Object copy{}; - CopyObject(copy, *object); - Clear(NodeOf(*self)); - self->node->type = TypeObject; - self->node->object = copy; - return 0; +void APS5_VABI _ZN3sce4Json5Value3setERKNS0_6ObjectE(Value* self, const Object* object) { + if (!object->items) return; + Value source{}; + source.type = TypeObject; + source.object = const_cast(object); + AssignPayload(*self, source); } -int APS5_VABI _ZN3sce4Json5Value3setERKS1_(Value* self, const Value* other) { - if (self != other) AssignNode(NodeOf(*self), NodeOf(*other)); - return 0; +void APS5_VABI _ZN3sce4Json5Value3setERKS1_(Value* self, const Value* other) { + AssignNode(NodeOf(*self), NodeOf(*other)); } ValueType APS5_VABI _ZNK3sce4Json5Value7getTypeEv(const Value* self) { return NodeOf(*self).type; } -const bool* APS5_VABI _ZNK3sce4Json5Value10getBooleanEv(const Value* self) { return &Typed(self, TypeBoolean).boolean; } +const bool* APS5_VABI _ZNK3sce4Json5Value10getBooleanEv(const Value* self) { + bool usedDefault = false; + const auto& value = Typed(self, TypeBoolean, &usedDefault); + return usedDefault ? &_ZN3sce4Json5Value10s_nullboolE : &value.boolean; +} const std::int64_t* APS5_VABI _ZNK3sce4Json5Value10getIntegerEv(const Value* self) { Node& n = const_cast(NodeOf(*self)); if (n.type == TypeUInteger) { n.integer = std::bit_cast(n.uinteger); return &n.integer; } - return &Typed(self, TypeInteger).integer; + bool usedDefault = false; + const auto& value = Typed(self, TypeInteger, &usedDefault); + return usedDefault ? &_ZN3sce4Json5Value13s_nullintegerE : &value.integer; } const std::uint64_t* APS5_VABI _ZNK3sce4Json5Value11getUIntegerEv(const Value* self) { Node& n = const_cast(NodeOf(*self)); @@ -949,111 +3058,148 @@ const std::uint64_t* APS5_VABI _ZNK3sce4Json5Value11getUIntegerEv(const Value* s n.uinteger = static_cast(n.integer); return &n.uinteger; } - return &Typed(self, TypeUInteger).uinteger; + bool usedDefault = false; + const auto& value = Typed(self, TypeUInteger, &usedDefault); + return usedDefault ? &_ZN3sce4Json5Value14s_nulluintegerE : &value.uinteger; } const double* APS5_VABI _ZNK3sce4Json5Value7getRealEv(const Value* self) { - Node& n = const_cast(NodeOf(*self)); - if (n.type == TypeInteger || n.type == TypeUInteger) { - n.real = n.type == TypeInteger ? static_cast(n.integer) : static_cast(n.uinteger); - return &n.real; - } - return &Typed(self, TypeReal).real; + bool usedDefault = false; + const auto& value = Typed(self, TypeReal, &usedDefault); + return usedDefault ? &_ZN3sce4Json5Value10s_nullrealE : &value.real; } -const String* APS5_VABI _ZNK3sce4Json5Value9getStringEv(const Value* self) { return &Typed(self, TypeString).string; } -const Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const Value* self) { return &Typed(self, TypeArray).array; } -const Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const Value* self) { return &Typed(self, TypeObject).object; } +const String* APS5_VABI _ZNK3sce4Json5Value9getStringEv(const Value* self) { bool fallback = false; const auto& value = Typed(self, TypeString, &fallback); return fallback ? _ZN3sce4Json5Value12s_nullstringE : value.string; } +const Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const Value* self) { bool fallback = false; const auto& value = Typed(self, TypeArray, &fallback); return fallback ? _ZN3sce4Json5Value11s_nullarrayE : value.array; } +const Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const Value* self) { bool fallback = false; const auto& value = Typed(self, TypeObject, &fallback); return fallback ? _ZN3sce4Json5Value12s_nullobjectE : value.object; } std::size_t APS5_VABI _ZNK3sce4Json5Value5countEv(const Value* self) { const Node& n = NodeOf(*self); - if (n.type == TypeArray) return n.array.items->size(); - if (n.type == TypeObject) return n.object.items->size(); + if (n.type == TypeArray) return n.array->items->size(); + if (n.type == TypeObject) return n.object->items->size(); return 0; } +const Value* APS5_VABI _ZNK3sce4Json5ValueixERKNS0_6StringE(const Value* self, const String* key) { return LookupKey(self, key); } const Value* APS5_VABI _ZNK3sce4Json5ValueixEPKc(const Value* self, const char* key) { - const Node& n = NodeOf(*self); - if (key != nullptr && n.type == TypeObject) - for (const auto& pair : *n.object.items) - if (std::strcmp(pair.key.text->c_str(), key) == 0) return &pair.value; - return &MissingElement(self); + ScopedString text(key ? key : ""); + return LookupKey(self, &text.string); } const Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const Value* self, std::size_t index) { - const Node& n = NodeOf(*self); - if (n.type == TypeArray && index < n.array.items->size()) { - auto it = n.array.items->begin(); + if (self->type == TypeArray && index < self->array->items->size()) { + auto it = self->array->items->begin(); std::advance(it, static_cast(index)); return &*it; } - return &MissingElement(self); + if (self->type == TypeObject && index < self->object->items->size()) { + auto it = self->object->items->begin(); + std::advance(it, static_cast(index)); + return &it->value; + } + const auto error = self->type == TypeArray || self->type == TypeObject ? 0x80848116 : 0x80848118; + return &MissingElement(self, static_cast(error), nullptr, &index); } void APS5_VABI _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(Value* self, ValueType type) { - Construct(*self, [type](Node& node) { SetType(node, type); }); + Construct(*self); + SetType(*self, type); } const Value* APS5_VABI _ZNK3sce4Json5Value8getValueEm(const Value* self, std::size_t index) { return _ZNK3sce4Json5ValueixEm(self, index); } -const Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const Value* self, const String* key) { - const Node& n = NodeOf(*self); - if (key != nullptr && n.type == TypeObject) - for (const auto& pair : *n.object.items) - if (std::strcmp(pair.key.text->c_str(), key->text->c_str()) == 0) return &pair.value; - return &MissingElement(self); -} +const Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const Value* self, const String* key) { return LookupKey(self, key); } Value* APS5_VABI _ZN3sce4Json5Value10referValueEm(Value* self, std::size_t index) { - Node& n = NodeOf(*self); - if (n.type != TypeArray || index >= n.array.items->size()) return nullptr; - auto it = n.array.items->begin(); - std::advance(it, static_cast(index)); - return &*it; + if (self->type == TypeArray && index < self->array->items->size()) { + auto it = self->array->items->begin(); + std::advance(it, static_cast(index)); + return &*it; + } + if (self->type == TypeObject && index < self->object->items->size()) { + auto it = self->object->items->begin(); + std::advance(it, static_cast(index)); + return &it->value; + } + return nullptr; } Value* APS5_VABI _ZN3sce4Json5Value10referValueERKNS0_6StringE(Value* self, const String* key) { Node& n = NodeOf(*self); if (key == nullptr || n.type != TypeObject) return nullptr; - for (auto& pair : *n.object.items) - if (std::strcmp(pair.key.text->c_str(), key->text->c_str()) == 0) return &pair.value; - return nullptr; + if (!n.object->items) return nullptr; + auto* node = n.object->items->Find(Characters(*key)); + return node && !node->nil ? &node->pair.value : nullptr; } Array* APS5_VABI _ZN3sce4Json5Value10referArrayEv(Value* self) { Node& n = NodeOf(*self); if (n.type == TypeNull) SetType(n, TypeArray); if (n.type != TypeArray) throw std::runtime_error("sce::Json::Value::referArray: value is not an array"); - return &n.array; + return n.array; } Object* APS5_VABI _ZN3sce4Json5Value11referObjectEv(Value* self) { Node& n = NodeOf(*self); if (n.type == TypeNull) SetType(n, TypeObject); if (n.type != TypeObject) throw std::runtime_error("sce::Json::Value::referObject: value is not an object"); - return &n.object; + return n.object; +} +int APS5_VABI _ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(Value* self, SerializationCallback callback, void* context) { + return SerializeCallbackValue(self, nullptr, callback, context, false); +} +int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_(Value* self, String* output, SerializationCallback callback, void* context) { + return SerializeCallbackValue(self, output, callback, context, true); } int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringE(Value* self, String* out) { - std::string text; - Serialize(text, NodeOf(*self)); - *out->text = std::move(text); - return 0; -} -int APS5_VABI _ZNK3sce4Json5Value8toStringERNS0_6StringE(const Value* self, String* out) { - const Node& n = NodeOf(*self); - if (n.type == TypeString) { - *out->text = *n.string.text; - return 0; + { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.allocation) return JsonErrorNotInitialized; } + return SerializeCallbackValue(self, out, SerializationSucceeded, nullptr, true); +} +void APS5_VABI _ZNK3sce4Json5Value8toStringERNS0_6StringE(const Value* self, String* out) { std::string text; - Serialize(text, n); - *out->text = std::move(text); - return 0; + if (self->type == TypeString) AppendText(*out, Characters(*self->string)); + else { + if (self->type == TypeArray) text = "array"; + else if (self->type == TypeObject) text = "object"; + else if (self->type == TypeReal) { + if (!FloatConfiguration().enabled) { AppendLegacyReal(*out, self->real); return; } + AppendReal(text, self->real); + } + else Serialize(text, *self); + AppendText(*out, text); + } } 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"); - Parser parser(read, context, NodeOf(*out).handlers); + Parser parser(read, context); return ParseValue(out, parser); } int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value* out, const char* text, std::size_t size) { 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); + Parser parser(text, size); + return ParseValue(out, parser); +} + +int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKc(Value* out, const char* path) { + AllocationContext allocation; + { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (!state.initialized) return JsonErrorNotInitialized; + allocation = state.allocation ? *state.allocation : AllocationContext{}; + } + if (!allocation.fileBufferSize) return static_cast(0x80848105); + FileReader reader{allocation}; + reader.buffer = Allocate(allocation, allocation.fileBufferSize); + if (reader.buffer) reader.descriptor = sceKernelOpen(path, 0, 0555); + Parser parser(FileReader::Read, &reader); return ParseValue(out, parser); } void APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize) { - self->allocParam = allocParam; + *self = {}; + auto& state = State(); + std::lock_guard lock(state.mutex); + if (state.allocation) return; + const auto& callbacks = *static_cast(allocParam); + if (!callbacks.allocate || !callbacks.free) return; + state.rtti = callbacks; + self->allocParam = &rttiAllocator; self->userData = userData; self->fileBufferSize = fileBufferSize; } @@ -1076,7 +3222,14 @@ void APS5_VABI _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv } void APS5_VABI _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(InitParameter2* self, void* allocParam, void* userData) { - self->allocator = allocParam; + if (!allocParam) return; + auto& state = State(); + std::lock_guard lock(state.mutex); + if (state.allocation) return; + const auto& callbacks = *static_cast(allocParam); + if (!callbacks.allocate || !callbacks.free) return; + state.rtti = callbacks; + self->allocator = &rttiAllocator; self->userData = userData; } @@ -1089,9 +3242,14 @@ void APS5_VABI _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22Spe } int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(void* self, const InitParameter2* parameter) { - (void)self; - if (parameter == nullptr) throw std::invalid_argument("sce::Json::Initializer::initialize: null parameter"); - return 0; + auto& state = State(); + std::lock_guard callbackLock(state.callbackMutex); + std::lock_guard lock(state.mutex); + if (state.allocation || state.initialized || *static_cast(self)) return JsonErrorAlreadyInitialized; + if (!parameter || !parameter->allocator || static_cast(parameter->specialFloatFormatType) > 2) + return JsonErrorInvalidArgument; + ResetDefaults(); + return InitializeResources(self, parameter, parameter->specialFloatFormatType, true); } void APS5_VABI _ZN3sce4Json5Value5clearEv(Value* self) { @@ -1106,46 +3264,50 @@ void APS5_VABI _ZN3sce4Json12MemAllocator11notifyErrorEimPv(void* self, int erro (void)userData; } -void APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(NullAccessCallback callback, void* context) { - auto& state = State(); - std::lock_guard lock(state.mutex); - state.callback = callback; - state.context = context; +int APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(Value* self, NullAccessCallback callback, void* context) { + if (self->parent) return static_cast(0x80848104); + { + auto& state = State(); + std::lock_guard lock(state.mutex); + if (ModernValues() && !state.allocation) return JsonErrorNotInitialized; + } + if (!ModernValues() && !self->auxiliary) return static_cast(0x80848104); + const auto status = EnsureAuxiliary(*self); + if (status) return status; + self->auxiliary->nullAccess = callback; + self->auxiliary->nullAccessContext = context; + return 0; } int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(Value* self, ElementAccessFailureHandler handler, void* context) { - SetHandlers(NodeOf(*self), [&](Handlers& handlers) { - handlers.elementAccessFailure = handler; - handlers.elementAccessFailureContext = context; - }); - return 0; + return SetHandler(*self, &Auxiliary::elementAccessFailure, handler, context); } int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(Value* self, SpecialFloatHandler handler, void* context) { - SetHandlers(NodeOf(*self), [&](Handlers& handlers) { - handlers.specialFloat = handler; - handlers.specialFloatContext = context; - }); - return 0; + return SetHandler(*self, &Auxiliary::specialFloat, handler, context, true); } int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(Value* self, TypeMismatchHandler handler, void* context) { - SetHandlers(NodeOf(*self), [&](Handlers& handlers) { - handlers.typeMismatch = handler; - handlers.typeMismatchContext = context; - }); - return 0; + return SetHandler(*self, &Auxiliary::typeMismatch, handler, context); } bool APS5_VABI _ZNK3sce4Json5ValuecvbEv(const Value* self) { const Node& n = NodeOf(*self); + if (n.type != TypeBoolean) { + CallbackScope scope; + const auto handlers = HandlersOf(n); + if (scope.entered && handlers.typeMismatch) { + const Value& replacement = handlers.typeMismatch(TypeBoolean, self, self->parent, handlers.typeMismatchContext); + return Typed(&replacement, TypeBoolean).boolean; + } + } 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 TypeString: return !View(*n.string).empty(); case TypeArray: case TypeObject: return true; default: throw std::runtime_error("sce::Json::Value: invalid value type"); @@ -1153,102 +3315,141 @@ bool APS5_VABI _ZNK3sce4Json5ValuecvbEv(const Value* self) { } void APS5_VABI _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(Pair* self, const String* key, const Value* value) { - Pair pair{}; - pair.reserved = 0; - Construct(pair.key, *key->text); - try { - CopyInto(pair.value, *value); - } catch (...) { - Destroy(pair.key); - throw; - } - *self = pair; + Construct(self->key, *key); + CopyInto(self->value, *value); } void APS5_VABI _ZN3sce4Json6Object4PairD1Ev(Pair* self) { - Destroy(self->key); Destroy(self->value); + Destroy(self->key); } ObjectIterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(ObjectIterator* result, Object* self, const Pair* pair) { - 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(); + result->state = static_cast(Allocate(sizeof(ObjectIteratorState))); + if (result->state) result->state->position = nullptr; + if (!self->items) return result; + if (!ModernValues()) { + ObjectLink* node; + { + InlineText key; key.Assign(Characters(pair->key)); + TemporaryTreeEntry entry(key.text, *pair); + bool inserted; + node = self->items->Insert(entry.key.text, entry.pair, inserted); + } + if (!node) { Release(result->state); result->state = nullptr; } + else if (result->state) result->state->position = node; return result; } - Pair copy{}; - copy.reserved = 0; - Construct(copy.key, *pair->key.text); - try { - CopyInto(copy.value, pair->value); - state->position = self->items->insert(it, copy); - result->state = state.release(); - return result; - } catch (...) { - Destroy(copy.value); - Destroy(copy.key); - throw; + ScopedValue value; + value.value.parent = self->items->Owner(); + AssignNode(value.value, pair->value); + AllocationOperation operation; + ObjectLink* node; + bool inserted; + { + InlineText key; key.Assign(Characters(pair->key)); + ScopedString copiedKey(pair->key); + Pair source{}; source.key = copiedKey.string; + TemporaryTreeEntry entry(key.text, source); + node = self->items->Insert(entry.key.text, entry.pair, inserted); } + const bool failed = operation.Finish(); + if (inserted && !failed) { + TransferPayload(node->pair.value, value.value); + node->pair.value.parent = self->items->Owner(); + BindChildren(node->pair.value); + } + if (!node) { Release(result->state); result->state = nullptr; } + else if (result->state) result->state->position = node; + return result; } -ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEi(ArrayIterator* result, ArrayIterator* self, int) { - _ZN3sce4Json5Array8iteratorC1ERKS2_(result, self); - _ZN3sce4Json5Array8iteratorppEv(self); - - return result; +ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEi(ArrayIterator* self, int) { + return _ZN3sce4Json5Array8iteratorppEv(self); } ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratoraSERKS2_(ArrayIterator* self, const ArrayIterator* other) { if (self->state) { - if (other->state && other->state->position != other->state->end) { - *self->state = *other->state; - } else { - delete self->state; - self->state = nullptr; - } + if (other->state && other->state->position) *self->state = *other->state; + else { Release(self->state); self->state = nullptr; } } return self; } void APS5_VABI _ZN3sce4Json5Array8iteratorC1Ev(ArrayIterator* self) { - self->state = new ArrayIteratorState{}; + self->state = static_cast(Allocate(sizeof(ArrayIteratorState))); + if (self->state) self->state->position = nullptr; } -ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEi(ObjectIterator* result, ObjectIterator* self, int) { - auto state = self->state ? std::make_unique(*self->state) : nullptr; - _ZN3sce4Json6Object8iteratorppEv(self); - - result->state = state.release(); - return result; +ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEi(ObjectIterator* self, int) { + return _ZN3sce4Json6Object8iteratorppEv(self); } ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratoraSERKS2_(ObjectIterator* self, const ObjectIterator* other) { if (self->state) { - if (other->state && other->state->owner) { - *self->state = *other->state; - } else { - delete self->state; - self->state = nullptr; - } + if (other->state && other->state->position) *self->state = *other->state; + else { Release(self->state); self->state = nullptr; } } return self; } void APS5_VABI _ZN3sce4Json6Object8iteratorC1Ev(ObjectIterator* self) { - self->state = new ObjectIteratorState{}; + self->state = static_cast(Allocate(sizeof(ObjectIteratorState))); + if (self->state) self->state->position = nullptr; } void APS5_VABI _ZN3sce4Json6Object4PairC1Ev(Pair* self) { - Pair pair{}; - pair.reserved = 0; - Construct(pair.key, std::string()); + Construct(self->key, std::string_view{}); try { - Construct(pair.value); + Construct(self->value); } catch (...) { - Destroy(pair.key); + Destroy(self->key); throw; } - *self = pair; } + +void APS5_VABI _ZN3sce4Json11InitializerC2Ev(void* self) { _ZN3sce4Json11InitializerC1Ev(self); } +void APS5_VABI _ZN3sce4Json11InitializerD2Ev(void* self) { _ZN3sce4Json11InitializerD1Ev(self); } +void APS5_VABI _ZN3sce4Json17InitParameterRttiC2EPNS0_14AllocParamRttiEPvm(InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize) { _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(self, allocParam, userData, fileBufferSize); } +void APS5_VABI _ZN3sce4Json5Array8iteratorC2ERKS2_(ArrayIterator* self, const ArrayIterator* other) { _ZN3sce4Json5Array8iteratorC1ERKS2_(self, other); } +void APS5_VABI _ZN3sce4Json5Array8iteratorC2Ev(ArrayIterator* self) { _ZN3sce4Json5Array8iteratorC1Ev(self); } +void APS5_VABI _ZN3sce4Json5Array8iteratorD2Ev(ArrayIterator* self) { _ZN3sce4Json5Array8iteratorD1Ev(self); } +void APS5_VABI _ZN3sce4Json5ArrayC2ERKS1_(Array* self, const Array* other) { _ZN3sce4Json5ArrayC1ERKS1_(self, other); } +void APS5_VABI _ZN3sce4Json5ArrayC2Ev(Array* self) { _ZN3sce4Json5ArrayC1Ev(self); } +void APS5_VABI _ZN3sce4Json5ArrayD2Ev(Array* self) { _ZN3sce4Json5ArrayD1Ev(self); } +void APS5_VABI _ZN3sce4Json5ValueC2ENS0_9ValueTypeE(Value* self, ValueType type) { _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(self, type); } +void APS5_VABI _ZN3sce4Json5ValueC2EPKc(Value* self, const char* text) { _ZN3sce4Json5ValueC1EPKc(self, text); } +void APS5_VABI _ZN3sce4Json5ValueC2ERKNS0_5ArrayE(Value* self, const Array* array) { _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(self, array); } +void APS5_VABI _ZN3sce4Json5ValueC2ERKNS0_6ObjectE(Value* self, const Object* object) { _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(self, object); } +void APS5_VABI _ZN3sce4Json5ValueC2ERKNS0_6StringE(Value* self, const String* text) { _ZN3sce4Json5ValueC1ERKNS0_6StringE(self, text); } +void APS5_VABI _ZN3sce4Json5ValueC2ERKS1_(Value* self, const Value* other) { _ZN3sce4Json5ValueC1ERKS1_(self, other); } +void APS5_VABI _ZN3sce4Json5ValueC2Eb(Value* self, bool value) { _ZN3sce4Json5ValueC1Eb(self, value); } +void APS5_VABI _ZN3sce4Json5ValueC2Ed(Value* self, double value) { _ZN3sce4Json5ValueC1Ed(self, value); } +void APS5_VABI _ZN3sce4Json5ValueC2El(Value* self, std::int64_t value) { _ZN3sce4Json5ValueC1El(self, value); } +void APS5_VABI _ZN3sce4Json5ValueC2Em(Value* self, std::uint64_t value) { _ZN3sce4Json5ValueC1Em(self, value); } +void APS5_VABI _ZN3sce4Json5ValueC2Ev(Value* self) { _ZN3sce4Json5ValueC1Ev(self); } +void APS5_VABI _ZN3sce4Json5ValueD2Ev(Value* self) { _ZN3sce4Json5ValueD1Ev(self); } +void APS5_VABI _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(Pair* self, const String* key, const Value* value) { _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(self, key, value); } +void APS5_VABI _ZN3sce4Json6Object4PairC2Ev(Pair* self) { _ZN3sce4Json6Object4PairC1Ev(self); } +void APS5_VABI _ZN3sce4Json6Object4PairD2Ev(Pair* self) { _ZN3sce4Json6Object4PairD1Ev(self); } +void APS5_VABI _ZN3sce4Json6Object8iteratorC2ERKS2_(ObjectIterator* self, const ObjectIterator* other) { _ZN3sce4Json6Object8iteratorC1ERKS2_(self, other); } +void APS5_VABI _ZN3sce4Json6Object8iteratorC2Ev(ObjectIterator* self) { _ZN3sce4Json6Object8iteratorC1Ev(self); } +void APS5_VABI _ZN3sce4Json6Object8iteratorD2Ev(ObjectIterator* self) { _ZN3sce4Json6Object8iteratorD1Ev(self); } +void APS5_VABI _ZN3sce4Json6ObjectC2ERKS1_(Object* self, const Object* other) { _ZN3sce4Json6ObjectC1ERKS1_(self, other); } +void APS5_VABI _ZN3sce4Json6ObjectC2Ev(Object* self) { _ZN3sce4Json6ObjectC1Ev(self); } +void APS5_VABI _ZN3sce4Json6ObjectD2Ev(Object* self) { _ZN3sce4Json6ObjectD1Ev(self); } +void APS5_VABI _ZN3sce4Json6StringC2EPKc(String* self, const char* text) { _ZN3sce4Json6StringC1EPKc(self, text); } +void APS5_VABI _ZN3sce4Json6StringC2ERKS1_(String* self, const String* other) { _ZN3sce4Json6StringC1ERKS1_(self, other); } +void APS5_VABI _ZN3sce4Json6StringC2Ev(String* self) { _ZN3sce4Json6StringC1Ev(self); } +void APS5_VABI _ZN3sce4Json6StringD2Ev(String* self) { _ZN3sce4Json6StringD1Ev(self); } + +String* APS5_VABI _ZN3sce4Json5Value11referStringEv(Value* self) { return self->type == TypeString ? self->string : nullptr; } +bool* APS5_VABI _ZN3sce4Json5Value12referBooleanEv(Value* self) { return self->type == TypeBoolean ? &self->boolean : nullptr; } +std::int64_t* APS5_VABI _ZN3sce4Json5Value12referIntegerEv(Value* self) { return self->type == TypeInteger || self->type == TypeUInteger ? &self->integer : nullptr; } +std::uint64_t* APS5_VABI _ZN3sce4Json5Value13referUIntegerEv(Value* self) { return self->type == TypeInteger || self->type == TypeUInteger ? &self->uinteger : nullptr; } +double* APS5_VABI _ZN3sce4Json5Value9referRealEv(Value* self) { return self->type == TypeReal ? &self->real : nullptr; } +void* APS5_VABI _ZNK3sce4Json14InitParameter211getUserDataEv(const InitParameter2* self) { return self->userData; } +void* APS5_VABI _ZNK3sce4Json14InitParameter212getAllocatorEv(const InitParameter2* self) { return self->allocator; } +std::size_t APS5_VABI _ZNK3sce4Json14InitParameter217getFileBufferSizeEv(const InitParameter2* self) { return self->fileBufferSize; } +std::int32_t APS5_VABI _ZNK3sce4Json14InitParameter225getSpecialFloatFormatTypeEv(const InitParameter2* self) { return self->specialFloatFormatType; } } diff --git a/core/libs/prx/libSceJson2/InventoryExports.cpp b/core/libs/prx/libSceJson2/InventoryExports.cpp index c509808eb3..275a4599b5 100644 --- a/core/libs/prx/libSceJson2/InventoryExports.cpp +++ b/core/libs/prx/libSceJson2/InventoryExports.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceJson2/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,400 +6,9 @@ extern "C" { -int APS5_VABI sceJson2Unknown_001_nid_no_patch() { +int APS5_VABI _ZN3sce4Json12MemAllocatorD0Ev() { 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 index 630d23d193..4a7f077873 100644 --- a/core/libs/prx/libSceJson2/include/Api.hpp +++ b/core/libs/prx/libSceJson2/include/Api.hpp @@ -1,23 +1,8 @@ #pragma once #include "SceTypes.hpp" -#include -#include -#include -#include -#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include namespace SceJson2 { @@ -32,75 +17,42 @@ enum ValueType : std::int32_t { TypeObject = 7, }; -} +struct Value; +struct Text; +struct Auxiliary; +struct ArrayStorage; +struct ObjectStorage; +struct ArrayIteratorState; +struct ObjectIteratorState; -namespace SceJson2 { +struct Text { char* characters; std::uint32_t length; std::uint32_t capacity; std::uint64_t reserved; }; -struct Node; +struct String { Text* text; }; -} +struct Array { ArrayStorage* items; }; -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 Object { ObjectStorage* items; }; 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 Value { + Value* parent = nullptr; + Auxiliary* auxiliary = nullptr; + union { + std::uint64_t payload = 0; + bool boolean; + std::int64_t integer; + std::uint64_t uinteger; + double real; + String* string; + Array* array; + Object* object; + }; + std::uint32_t reserved = 0; + ValueType type = TypeNull; +}; struct Pair { String key; @@ -108,79 +60,12 @@ struct Pair { 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; @@ -189,159 +74,247 @@ struct InitParameter2 { std::uint32_t reserved[3]; }; -} +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*); + +using NullAccessCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*); + +using ParserReadCallback = int (APS5_VABI*)(char&, void*); + +using SerializationCallback = int (APS5_VABI*)(String&, void*); + extern "C" { +extern const bool _ZN3sce4Json5Value10s_nullboolE; +extern const std::int64_t _ZN3sce4Json5Value13s_nullintegerE; +extern const std::uint64_t _ZN3sce4Json5Value14s_nulluintegerE; +extern const double _ZN3sce4Json5Value10s_nullrealE; +extern String* _ZN3sce4Json5Value12s_nullstringE; +extern Array* _ZN3sce4Json5Value11s_nullarrayE; +extern Object* _ZN3sce4Json5Value12s_nullobjectE; -int APS5_VABI _ZN3sce4Json11InitializerC1Ev(void* self); -int APS5_VABI _ZN3sce4Json11InitializerD1Ev(void* self); + +int APS5_VABI _ZN3sce4Json12MemAllocatorD0Ev(void); +extern std::size_t _ZN3sce4Json6String4nposE; + +void APS5_VABI _ZN3sce4Json11InitializerC1Ev(void* self); +void APS5_VABI _ZN3sce4Json11InitializerD1Ev(void* self); int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(void* self, const void* parameter); +void APS5_VABI _ZN3sce4Json19InternalInitializerC1Ev(void* self); +void APS5_VABI _ZN3sce4Json19InternalInitializerC2Ev(void* self); +void APS5_VABI _ZN3sce4Json19InternalInitializerD1Ev(void* self); +void APS5_VABI _ZN3sce4Json19InternalInitializerD2Ev(void* self); +int APS5_VABI _ZN3sce4Json19InternalInitializer19terminate_staticlibEv(void* self); +int APS5_VABI _ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE(void* self, const void* parameter, std::int32_t option); 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); +int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void* self, NullAccessCallback callback, void* context); +int APS5_VABI _ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(void* self, NullAccessCallback callback, void* context); +int APS5_VABI _ZN3sce4Json11Initializer28setGlobalTypeMismatchHandlerEPFRKNS0_5ValueENS0_9ValueTypeEPS3_S6_PvES7_(void* self, TypeMismatchHandler callback, void* context); +int APS5_VABI _ZN3sce4Json11Initializer28setGlobalSpecialFloatHandlerEPFKNS0_5ValueENS0_12FunctionTypeEdPS3_PvPbES6_(void* self, SpecialFloatHandler callback, void* context); +int APS5_VABI _ZN3sce4Json11Initializer36setGlobalElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKNS0_5ValueES9_PvESA_(void* self, ElementAccessFailureHandler 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 _ZN3sce4Json12MemAllocatorD2Ev(void*); +void APS5_VABI _ZN3sce4Json12MemAllocatorD1Ev(void*); +void* APS5_VABI _ZN3sce4Json6MallocEm(std::size_t size); +void APS5_VABI _ZN3sce4Json4FreeEPv(void* memory); +int APS5_VABI _ZN3sce4Json11Initializer24setAllocatorInfoCallBackEPFviNS0_9ValueTypeEPvES3_(void*, void (APS5_VABI*)(int, ValueType, void*), void*); +void APS5_VABI _ZN3sce4Json6StringC1Ev(String* self); +void APS5_VABI _ZN3sce4Json6StringC1EPKc(String* self, const char* text); +void APS5_VABI _ZN3sce4Json6StringC1ERKS1_(String* self, const String* other); +void APS5_VABI _ZN3sce4Json6StringD1Ev(String* self); +String* APS5_VABI _ZN3sce4Json6StringaSERKS1_(String* self, const String* other); +const char* APS5_VABI _ZNK3sce4Json6String5c_strEv(const String* self); +bool APS5_VABI _ZNK3sce4Json6String5emptyEv(const String* self); +std::size_t APS5_VABI _ZNK3sce4Json6String6lengthEv(const String* self); +std::size_t APS5_VABI _ZNK3sce4Json6String4sizeEv(const String* self); +std::size_t APS5_VABI _ZNK3sce4Json6String8capacityEv(const String* self); +bool APS5_VABI _ZNK3sce4Json6StringeqEPKc(const String* self, const char* text); +bool APS5_VABI _ZNK3sce4Json6StringeqERKS1_(const String* self, const String* other); +void APS5_VABI _ZN3sce4Json6String5clearEv(String* self); +void APS5_VABI _ZN3sce4Json6String6resizeEm(String* self, std::size_t count); +void APS5_VABI _ZN3sce4Json6String7reserveEm(String* self, std::size_t capacity); +String* APS5_VABI _ZN3sce4Json6String6appendERKS1_(String* self, const String* other); +String* APS5_VABI _ZN3sce4Json6String6appendEPKc(String* self, const char* text); +String* APS5_VABI _ZN3sce4Json6String6appendEPKcm(String* self, const char* text, std::size_t length); +String* APS5_VABI _ZN3sce4Json6StringpLEPKc(String* self, const char* text); +String* APS5_VABI _ZN3sce4Json6StringpLEh(String* self, unsigned char ch); +String* APS5_VABI _ZNK3sce4Json6String6substrEmm(String* result, const String* self, std::size_t position, std::size_t length); +char APS5_VABI _ZNK3sce4Json6String2atEm(const String* self, std::size_t index); +std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcm(const String* self, const char* needle, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String4findEPKcmm(const String* self, const char* needle, std::size_t position, std::size_t count); +std::size_t APS5_VABI _ZNK3sce4Json6String4findERKS1_m(const String* self, const String* needle, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcm(const String* self, const char* needle, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEPKcmm(const String* self, const char* needle, std::size_t position, std::size_t count); +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindERKS1_m(const String* self, const String* needle, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String4findEcm(const String* self, char character, std::size_t position); +std::size_t APS5_VABI _ZNK3sce4Json6String5rfindEcm(const String* self, char character, std::size_t position); +int APS5_VABI _ZNK3sce4Json6String7compareEPKc(const String* self, const char* text); +int APS5_VABI _ZNK3sce4Json6String7compareERKS1_(const String* self, const String* other); +void APS5_VABI _ZN3sce4Json5ArrayC1Ev(Array* self); +void APS5_VABI _ZN3sce4Json5ArrayC1ERKS1_(Array* self, const Array* other); +void APS5_VABI _ZN3sce4Json5ArrayD1Ev(Array* self); +Array* APS5_VABI _ZN3sce4Json5ArrayaSERKS1_(Array* self, const Array* other); +void APS5_VABI _ZN3sce4Json5Array10push_frontERKNS0_5ValueE(Array* self, const Value* value); +ArrayIterator* APS5_VABI _ZN3sce4Json5Array6insertERKNS1_8iteratorERKNS0_5ValueE(ArrayIterator* result, Array* self, const ArrayIterator* position, const Value* value); +void APS5_VABI _ZN3sce4Json5Array9push_backERKNS0_5ValueE(Array* self, const Value* value); +ArrayIterator* APS5_VABI _ZNK3sce4Json5Array5beginEv(ArrayIterator* result, const Array* self); +ArrayIterator* APS5_VABI _ZNK3sce4Json5Array3endEv(ArrayIterator* result, const Array* self); +bool APS5_VABI _ZNK3sce4Json5Array5emptyEv(const Array* self); +std::size_t APS5_VABI _ZNK3sce4Json5Array4sizeEv(const Array* self); +void APS5_VABI _ZN3sce4Json5Array5clearEv(Array* self); +const Value* APS5_VABI _ZNK3sce4Json5Array5frontEv(const Array* self); +const Value* APS5_VABI _ZNK3sce4Json5Array4backEv(const Array* self); +void APS5_VABI _ZN3sce4Json5Array8pop_backEv(Array* self); +ArrayIterator* APS5_VABI _ZN3sce4Json5Array5eraseERKNS1_8iteratorE(ArrayIterator* result, Array* self, const ArrayIterator* position); +void APS5_VABI _ZN3sce4Json5Array9pop_frontEv(Array* self); +void APS5_VABI _ZN3sce4Json5Array8iteratorD1Ev(ArrayIterator* self); +ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEv(ArrayIterator* self); +Value* APS5_VABI _ZNK3sce4Json5Array8iteratordeEv(const ArrayIterator* self); +Value* APS5_VABI _ZNK3sce4Json5Array8iteratorptEv(const ArrayIterator* self); +bool APS5_VABI _ZNK3sce4Json5Array8iteratorneERKS2_(const ArrayIterator* self, const ArrayIterator* other); +void APS5_VABI _ZN3sce4Json5Array8iteratorC1ERKS2_(ArrayIterator* self, const ArrayIterator* other); +void APS5_VABI _ZN3sce4Json5Array8iterator7advanceEm(ArrayIterator* self, std::size_t count); +void APS5_VABI _ZN3sce4Json6ObjectC1Ev(Object* self); +void APS5_VABI _ZN3sce4Json6ObjectC1ERKS1_(Object* self, const Object* other); +void APS5_VABI _ZN3sce4Json6ObjectD1Ev(Object* self); +Object* APS5_VABI _ZN3sce4Json6ObjectaSERKS1_(Object* self, const Object* other); +void APS5_VABI _ZN3sce4Json6Object5clearEv(Object* self); +Value* APS5_VABI _ZN3sce4Json6ObjectixERKNS0_6StringE(Object* self, const String* key); +ObjectIterator* APS5_VABI _ZNK3sce4Json6Object5beginEv(ObjectIterator* result, const Object* self); +ObjectIterator* APS5_VABI _ZNK3sce4Json6Object3endEv(ObjectIterator* result, const Object* self); +void APS5_VABI _ZN3sce4Json6Object8iteratorD1Ev(ObjectIterator* self); +ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEv(ObjectIterator* self); +Pair* APS5_VABI _ZNK3sce4Json6Object8iteratordeEv(const ObjectIterator* self); +Pair* APS5_VABI _ZNK3sce4Json6Object8iteratorptEv(const ObjectIterator* self); +ObjectIterator* APS5_VABI _ZNK3sce4Json6Object4findERKNS0_6StringE(ObjectIterator* result, const Object* self, const String* key); +bool APS5_VABI _ZNK3sce4Json6Object8iteratoreqERKS2_(const ObjectIterator* self, const ObjectIterator* other); +bool APS5_VABI _ZNK3sce4Json6Object8iteratorneERKS2_(const ObjectIterator* self, const ObjectIterator* other); +void APS5_VABI _ZN3sce4Json6Object8iteratorC1ERKS2_(ObjectIterator* self, const ObjectIterator* other); +void APS5_VABI _ZN3sce4Json6Object8iterator7advanceEm(ObjectIterator* self, std::size_t count); +bool APS5_VABI _ZNK3sce4Json6Object5emptyEv(const Object* self); +std::size_t APS5_VABI _ZNK3sce4Json6Object4sizeEv(const Object* self); +void APS5_VABI _ZN3sce4Json6Object5eraseERKNS0_6StringE(Object* self, const String* key); +void APS5_VABI _ZN3sce4Json5ValueC1Ev(Value* self); +void APS5_VABI _ZN3sce4Json5ValueC1Eb(Value* self, bool value); +void APS5_VABI _ZN3sce4Json5ValueC1El(Value* self, std::int64_t value); +void APS5_VABI _ZN3sce4Json5ValueC1Em(Value* self, std::uint64_t value); +void APS5_VABI _ZN3sce4Json5ValueC1Ed(Value* self, double value); +void APS5_VABI _ZN3sce4Json5ValueC1EPKc(Value* self, const char* text); +void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6StringE(Value* self, const String* text); +void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(Value* self, const Array* array); +void APS5_VABI _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(Value* self, const Object* object); +void APS5_VABI _ZN3sce4Json5ValueC1ERKS1_(Value* self, const Value* other); +void APS5_VABI _ZN3sce4Json5ValueD1Ev(Value* self); +Value* APS5_VABI _ZN3sce4Json5ValueaSERKS1_(Value* self, const Value* other); +void APS5_VABI _ZN3sce4Json5Value4swapERS1_(Value* self, Value* other); +void APS5_VABI _ZN3sce4Json5Value3setENS0_9ValueTypeE(Value* self, ValueType type); +void APS5_VABI _ZN3sce4Json5Value3setEb(Value* self, bool value); +void APS5_VABI _ZN3sce4Json5Value3setEl(Value* self, std::int64_t value); +void APS5_VABI _ZN3sce4Json5Value3setEm(Value* self, std::uint64_t value); +void APS5_VABI _ZN3sce4Json5Value3setEd(Value* self, double value); +void APS5_VABI _ZN3sce4Json5Value3setEPKc(Value* self, const char* text); +void APS5_VABI _ZN3sce4Json5Value3setERKNS0_6StringE(Value* self, const String* text); +void APS5_VABI _ZN3sce4Json5Value3setERKNS0_5ArrayE(Value* self, const Array* array); +void APS5_VABI _ZN3sce4Json5Value3setERKNS0_6ObjectE(Value* self, const Object* object); +void APS5_VABI _ZN3sce4Json5Value3setERKS1_(Value* self, const Value* other); +ValueType APS5_VABI _ZNK3sce4Json5Value7getTypeEv(const Value* self); +const bool* APS5_VABI _ZNK3sce4Json5Value10getBooleanEv(const Value* self); +const std::int64_t* APS5_VABI _ZNK3sce4Json5Value10getIntegerEv(const Value* self); +const std::uint64_t* APS5_VABI _ZNK3sce4Json5Value11getUIntegerEv(const Value* self); +const double* APS5_VABI _ZNK3sce4Json5Value7getRealEv(const Value* self); +const String* APS5_VABI _ZNK3sce4Json5Value9getStringEv(const Value* self); +const Array* APS5_VABI _ZNK3sce4Json5Value8getArrayEv(const Value* self); +const Object* APS5_VABI _ZNK3sce4Json5Value9getObjectEv(const Value* self); +std::size_t APS5_VABI _ZNK3sce4Json5Value5countEv(const Value* self); +const Value* APS5_VABI _ZNK3sce4Json5ValueixERKNS0_6StringE(const Value* self, const String* key); +const Value* APS5_VABI _ZNK3sce4Json5ValueixEPKc(const Value* self, const char* key); +const Value* APS5_VABI _ZNK3sce4Json5ValueixEm(const Value* self, std::size_t index); +void APS5_VABI _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(Value* self, ValueType type); +const Value* APS5_VABI _ZNK3sce4Json5Value8getValueEm(const Value* self, std::size_t index); +const Value* APS5_VABI _ZNK3sce4Json5Value8getValueERKNS0_6StringE(const Value* self, const String* key); +Value* APS5_VABI _ZN3sce4Json5Value10referValueEm(Value* self, std::size_t index); +Value* APS5_VABI _ZN3sce4Json5Value10referValueERKNS0_6StringE(Value* self, const String* key); +Array* APS5_VABI _ZN3sce4Json5Value10referArrayEv(Value* self); +Object* APS5_VABI _ZN3sce4Json5Value11referObjectEv(Value* self); +int APS5_VABI _ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(Value* self, SerializationCallback callback, void* context); +int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_(Value* self, String* output, SerializationCallback callback, void* context); +int APS5_VABI _ZN3sce4Json5Value9serializeERNS0_6StringE(Value* self, String* out); +void APS5_VABI _ZNK3sce4Json5Value8toStringERNS0_6StringE(const Value* self, String* out); +int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(Value* out, ParserReadCallback read, void* context); +int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(Value* out, const char* text, std::size_t size); +int APS5_VABI _ZN3sce4Json6Parser5parseERNS0_5ValueEPKc(Value* out, const char* path); +void APS5_VABI _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize); +void APS5_VABI _ZN3sce4Json14InitParameter2C1Ev(InitParameter2* self); +void APS5_VABI _ZN3sce4Json14InitParameter2C2Ev(InitParameter2* self); +void APS5_VABI _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(InitParameter2* self, void* allocator, void* userData); +void APS5_VABI _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(InitParameter2* self, void* allocParam, void* userData); +void APS5_VABI _ZN3sce4Json14InitParameter217setFileBufferSizeEm(InitParameter2* self, std::size_t size); +void APS5_VABI _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(InitParameter2* self, std::int32_t type); +int APS5_VABI _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(void* self, const InitParameter2* parameter); +void APS5_VABI _ZN3sce4Json5Value5clearEv(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); +int APS5_VABI _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(Value* self, NullAccessCallback callback, void* context); +int APS5_VABI _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(Value* self, ElementAccessFailureHandler handler, void* context); +int APS5_VABI _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(Value* self, SpecialFloatHandler handler, void* context); +int APS5_VABI _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(Value* self, TypeMismatchHandler handler, void* context); +bool APS5_VABI _ZNK3sce4Json5ValuecvbEv(const Value* self); +void APS5_VABI _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(Pair* self, const String* key, const Value* value); +void APS5_VABI _ZN3sce4Json6Object4PairD1Ev(Pair* self); +ObjectIterator* APS5_VABI _ZN3sce4Json6Object6insertERKNS1_4PairE(ObjectIterator* result, Object* self, const Pair* pair); +ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratorppEi(ArrayIterator* self, int); +ArrayIterator* APS5_VABI _ZN3sce4Json5Array8iteratoraSERKS2_(ArrayIterator* self, const ArrayIterator* other); +void APS5_VABI _ZN3sce4Json5Array8iteratorC1Ev(ArrayIterator* self); +ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratorppEi(ObjectIterator* self, int); +ObjectIterator* APS5_VABI _ZN3sce4Json6Object8iteratoraSERKS2_(ObjectIterator* self, const ObjectIterator* other); +void APS5_VABI _ZN3sce4Json6Object8iteratorC1Ev(ObjectIterator* self); +void APS5_VABI _ZN3sce4Json6Object4PairC1Ev(Pair* self); +void APS5_VABI _ZN3sce4Json11InitializerC2Ev(void* self); +void APS5_VABI _ZN3sce4Json11InitializerD2Ev(void* self); +void APS5_VABI _ZN3sce4Json17InitParameterRttiC2EPNS0_14AllocParamRttiEPvm(InitParameterRtti* self, void* allocParam, void* userData, std::size_t fileBufferSize); +void APS5_VABI _ZN3sce4Json5Array8iteratorC2ERKS2_(ArrayIterator* self, const ArrayIterator* other); +void APS5_VABI _ZN3sce4Json5Array8iteratorC2Ev(ArrayIterator* self); +void APS5_VABI _ZN3sce4Json5Array8iteratorD2Ev(ArrayIterator* self); +void APS5_VABI _ZN3sce4Json5ArrayC2ERKS1_(Array* self, const Array* other); +void APS5_VABI _ZN3sce4Json5ArrayC2Ev(Array* self); +void APS5_VABI _ZN3sce4Json5ArrayD2Ev(Array* self); +void APS5_VABI _ZN3sce4Json5ValueC2ENS0_9ValueTypeE(Value* self, ValueType type); +void APS5_VABI _ZN3sce4Json5ValueC2EPKc(Value* self, const char* text); +void APS5_VABI _ZN3sce4Json5ValueC2ERKNS0_5ArrayE(Value* self, const Array* array); +void APS5_VABI _ZN3sce4Json5ValueC2ERKNS0_6ObjectE(Value* self, const Object* object); +void APS5_VABI _ZN3sce4Json5ValueC2ERKNS0_6StringE(Value* self, const String* text); +void APS5_VABI _ZN3sce4Json5ValueC2ERKS1_(Value* self, const Value* other); +void APS5_VABI _ZN3sce4Json5ValueC2Eb(Value* self, bool value); +void APS5_VABI _ZN3sce4Json5ValueC2Ed(Value* self, double value); +void APS5_VABI _ZN3sce4Json5ValueC2El(Value* self, std::int64_t value); +void APS5_VABI _ZN3sce4Json5ValueC2Em(Value* self, std::uint64_t value); +void APS5_VABI _ZN3sce4Json5ValueC2Ev(Value* self); +void APS5_VABI _ZN3sce4Json5ValueD2Ev(Value* self); +void APS5_VABI _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(Pair* self, const String* key, const Value* value); +void APS5_VABI _ZN3sce4Json6Object4PairC2Ev(Pair* self); +void APS5_VABI _ZN3sce4Json6Object4PairD2Ev(Pair* self); +void APS5_VABI _ZN3sce4Json6Object8iteratorC2ERKS2_(ObjectIterator* self, const ObjectIterator* other); +void APS5_VABI _ZN3sce4Json6Object8iteratorC2Ev(ObjectIterator* self); +void APS5_VABI _ZN3sce4Json6Object8iteratorD2Ev(ObjectIterator* self); +void APS5_VABI _ZN3sce4Json6ObjectC2ERKS1_(Object* self, const Object* other); +void APS5_VABI _ZN3sce4Json6ObjectC2Ev(Object* self); +void APS5_VABI _ZN3sce4Json6ObjectD2Ev(Object* self); +void APS5_VABI _ZN3sce4Json6StringC2EPKc(String* self, const char* text); +void APS5_VABI _ZN3sce4Json6StringC2ERKS1_(String* self, const String* other); +void APS5_VABI _ZN3sce4Json6StringC2Ev(String* self); +void APS5_VABI _ZN3sce4Json6StringD2Ev(String* self); +String* APS5_VABI _ZN3sce4Json5Value11referStringEv(Value* self); +bool* APS5_VABI _ZN3sce4Json5Value12referBooleanEv(Value* self); +std::int64_t* APS5_VABI _ZN3sce4Json5Value12referIntegerEv(Value* self); +std::uint64_t* APS5_VABI _ZN3sce4Json5Value13referUIntegerEv(Value* self); +double* APS5_VABI _ZN3sce4Json5Value9referRealEv(Value* self); +void* APS5_VABI _ZNK3sce4Json14InitParameter211getUserDataEv(const InitParameter2* self); +void* APS5_VABI _ZNK3sce4Json14InitParameter212getAllocatorEv(const InitParameter2* self); +std::size_t APS5_VABI _ZNK3sce4Json14InitParameter217getFileBufferSizeEv(const InitParameter2* self); +std::int32_t APS5_VABI _ZNK3sce4Json14InitParameter225getSpecialFloatFormatTypeEv(const InitParameter2* self); + +} } diff --git a/core/libs/prx/libSceLibcInternal/CMakeLists.txt b/core/libs/prx/libSceLibcInternal/CMakeLists.txt index 2655d73c0b..b3bbfa698f 100644 --- a/core/libs/prx/libSceLibcInternal/CMakeLists.txt +++ b/core/libs/prx/libSceLibcInternal/CMakeLists.txt @@ -4,6 +4,19 @@ set_source_files_properties(UserContext.cpp PROPERTIES COMPILE_OPTIONS -fno-omit target_link_libraries(libSceLibcInternal PRIVATE ${CMAKE_DL_LIBS}) target_sources(libSceLibcInternal PRIVATE Directory.cpp) target_sources(libSceLibcInternal PRIVATE InventoryExports.cpp) +target_sources(libSceLibcInternal PRIVATE Pad.cpp) +if(BUILD_TESTING) + add_test_executable(guest_libc_internal_pad_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalPad.cpp) + target_include_directories(guest_libc_internal_pad_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_internal_pad_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_libc_internal_pad_tests) + add_test(NAME guest_libc_internal_pad COMMAND guest_libc_internal_pad_tests) + set_tests_properties(guest_libc_internal_pad PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libc_internal_pad PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() target_sources(libSceLibcInternal PRIVATE InitOnce.cpp) target_sources(libSceLibcInternal PRIVATE Mutex.cpp) target_sources(libSceLibcInternal PRIVATE AlignedNew.cpp) @@ -44,6 +57,20 @@ if(BUILD_TESTING) endif() endif() target_sources(libSceLibcInternal PRIVATE Err.cpp) +target_sources(libSceLibcInternal PRIVATE RecoveredInventory.cpp) +target_sources(libSceLibcInternal PRIVATE ExceptionDestructors.cpp) +if(BUILD_TESTING) + add_test_executable(guest_libc_internal_exception_destructors_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcInternalExceptionDestructors.cpp) + target_include_directories(guest_libc_internal_exception_destructors_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libc_internal_exception_destructors_tests PRIVATE libSceLibcInternal libkernel libc) + configure_windows_unwind(guest_libc_internal_exception_destructors_tests) + add_test(NAME guest_libc_internal_exception_destructors COMMAND guest_libc_internal_exception_destructors_tests) + set_tests_properties(guest_libc_internal_exception_destructors PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libc_internal_exception_destructors PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() configure_windows_unwind(libSceLibcInternal) target_sources(libSceLibcInternal PRIVATE FilesystemStatistics.cpp PathStatistics.cpp) target_sources(libSceLibcInternal PRIVATE Inet.cpp) @@ -109,6 +136,15 @@ if(BUILD_TESTING) set_tests_properties(guest_utime PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + if(NOT WIN32) + add_test_executable(guest_posix_spawn_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSpawnErrors.cpp) + target_include_directories(guest_posix_spawn_errors_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_posix_spawn_errors_tests PRIVATE libSceLibcInternal libc libkernel) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/spawn-errors) + add_test(NAME guest_posix_spawn_errors COMMAND guest_posix_spawn_errors_tests) + set_tests_properties(guest_posix_spawn_errors PROPERTIES TIMEOUT 20 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/spawn-errors) + 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 libkernel) @@ -120,6 +156,19 @@ if(BUILD_TESTING) set_tests_properties(guest_posix_spawn PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_posix_spawn_optional_pid_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixSpawnOptionalPid.cpp) + target_include_directories(guest_posix_spawn_optional_pid_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_posix_spawn_optional_pid_tests PRIVATE libSceLibcInternal libc libkernel) + configure_windows_unwind(guest_posix_spawn_optional_pid_tests) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/spawn-optional-pid) + add_test(NAME guest_posix_spawn_optional_pid COMMAND guest_posix_spawn_optional_pid_tests) + set_tests_properties(guest_posix_spawn_optional_pid PROPERTIES TIMEOUT 20 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/spawn-optional-pid) + add_asan_test_options(guest_posix_spawn_optional_pid handle_sigbus=0) + if(WIN32) + set_tests_properties(guest_posix_spawn_optional_pid PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_program_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestProgramName.cpp) target_include_directories(guest_program_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_program_name_tests PRIVATE libSceLibcInternal libkernel libc) @@ -222,3 +271,40 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +target_sources(libSceLibcInternal PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Sigvec.cpp) + +if(BUILD_TESTING AND (WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")) + add_test_executable(guest_sigvec_modules_tests + ${LIBS_INCLUDE_DIR}/tests/GuestSigvecModules.cpp + ${LIBS_INCLUDE_DIR}/nid/src/NidCompute.cpp + ${LIBS_INCLUDE_DIR}/nid/src/Sha1.cpp) + target_include_directories(guest_sigvec_modules_tests PRIVATE ${LIBS_INCLUDE_DIR} ${LIBS_INCLUDE_DIR}/nid/include) + target_link_libraries(guest_sigvec_modules_tests PRIVATE ${CMAKE_DL_LIBS}) + set_source_files_properties(${LIBS_INCLUDE_DIR}/tests/GuestSigvecModules.cpp PROPERTIES COMPILE_OPTIONS "-fno-exceptions;-fno-asynchronous-unwind-tables") + if(MINGW) + target_compile_options(guest_sigvec_modules_tests PRIVATE -fasynchronous-unwind-tables) + target_link_options(guest_sigvec_modules_tests PRIVATE -static -static-libgcc -static-libstdc++) + endif() + add_library(sigvec_modules_exceptions SHARED ${LIBS_INCLUDE_DIR}/tests/SigvecModulesExceptions.cpp) + target_include_directories(sigvec_modules_exceptions PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(sigvec_modules_exceptions PRIVATE libc) + set_target_properties(sigvec_modules_exceptions PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + configure_windows_unwind(sigvec_modules_exceptions) + add_dependencies(guest_sigvec_modules_tests libc libkernel libSceLibcInternal nid_patcher sigvec_modules_exceptions) + set(sigvecAllocatorArgument) + if(TARGET mimalloc) + set(sigvecAllocatorArgument -DALLOCATOR_DIRECTORY=$) + endif() + add_test(NAME guest_sigvec_modules COMMAND ${CMAKE_COMMAND} + -DEXCEPTION_PROBE=$ + -DPATCHER=$ + -DLIBC=$ + -DKERNEL=$ + -DINTERNAL=$ + ${sigvecAllocatorArgument} + -DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/sigvec-modules + -P ${LIBS_INCLUDE_DIR}/tests/SigvecModules.cmake + -- "$") + set_tests_properties(guest_sigvec_modules PROPERTIES TIMEOUT 20) +endif() diff --git a/core/libs/prx/libSceLibcInternal/Err.cpp b/core/libs/prx/libSceLibcInternal/Err.cpp index 3e67760ef9..d89aa35608 100644 --- a/core/libs/prx/libSceLibcInternal/Err.cpp +++ b/core/libs/prx/libSceLibcInternal/Err.cpp @@ -4,6 +4,7 @@ #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" #include extern "C" { @@ -156,3 +157,899 @@ void APS5_VABI warnx_nid_postfix(const char* format, ...) { } } + +extern "C" { +int APS5_VABI sceLibcInternalPending3_001_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Rqu9OmkKY+M", sceLibcInternalPending3_001_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_002_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ARZszYKvDWg", sceLibcInternalPending3_002_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_003_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("6aFwTNpqTP8", sceLibcInternalPending3_003_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_004_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ckD5sIxo+Co", sceLibcInternalPending3_004_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_005_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("UzmR8lOfyqU", sceLibcInternalPending3_005_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_006_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("zF2GfKzBgqU", sceLibcInternalPending3_006_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_007_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ypq5jFNRIJA", sceLibcInternalPending3_007_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_008_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ij-yZhH9YjY", sceLibcInternalPending3_008_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_009_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("v8P1X84ytFY", sceLibcInternalPending3_009_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_010_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("zkUC74aJxpE", sceLibcInternalPending3_010_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_011_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("PWFePkVdv9w", sceLibcInternalPending3_011_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_012_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("XX+xiPXAN8I", sceLibcInternalPending3_012_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_013_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("c6Lyc6xOp4o", sceLibcInternalPending3_013_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_014_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("DDnr3lDwW8I", sceLibcInternalPending3_014_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_015_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("E5NdzqEmWuY", sceLibcInternalPending3_015_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_016_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("NQ81EZ7CL6w", sceLibcInternalPending3_016_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_017_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("PJ2UDX9Tvwg", sceLibcInternalPending3_017_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_018_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("eoW60zcLT8Q", sceLibcInternalPending3_018_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_019_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("m6rjfL4aMcA", sceLibcInternalPending3_019_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_020_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("RYTHR81Y-Mc", sceLibcInternalPending3_020_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_021_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Mo6K-pUyNhI", sceLibcInternalPending3_021_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_022_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("BvRS0cGTd6I", sceLibcInternalPending3_022_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_023_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("9Vyfb-I-9xw", sceLibcInternalPending3_023_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_024_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("+uPwCGfmJHs", sceLibcInternalPending3_024_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_025_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("0FKwlv9iH1c", sceLibcInternalPending3_025_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_026_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("lynApfiP6Lw", sceLibcInternalPending3_026_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_027_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("iCWgjeqMHvg", sceLibcInternalPending3_027_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_028_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("7tIwDZyyKHo", sceLibcInternalPending3_028_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_029_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("TNexGlwiVEQ", sceLibcInternalPending3_029_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_030_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("14xKj+SV72o", sceLibcInternalPending3_030_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_031_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("P+q1OLiErP0", sceLibcInternalPending3_031_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_032_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Uc+-Sx0UZ3U", sceLibcInternalPending3_032_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_033_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ikBt0lqkxPc", sceLibcInternalPending3_033_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_034_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("N7z+Dnk1cS0", sceLibcInternalPending3_034_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_035_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Xk7IZcfHDD8", sceLibcInternalPending3_035_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_036_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("oDtGxrzLXMU", sceLibcInternalPending3_036_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_037_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("4fPIrH+z+E4", sceLibcInternalPending3_037_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_038_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("5BQIjX7Y5YU", sceLibcInternalPending3_038_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_039_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("KheIhkaSrlA", sceLibcInternalPending3_039_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_040_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("WAPkmrXx2o8", sceLibcInternalPending3_040_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_041_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ABFE75lbcDc", sceLibcInternalPending3_041_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_042_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("G1zcPUEvY7U", sceLibcInternalPending3_042_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_043_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("1eEXfeW6wrI", sceLibcInternalPending3_043_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_044_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("gYlF567r3-A", sceLibcInternalPending3_044_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_045_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("6vYXzFD-mrk", sceLibcInternalPending3_045_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_046_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Q-8lQCGMj4g", sceLibcInternalPending3_046_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_047_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("GSAXi4F1SlM", sceLibcInternalPending3_047_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_049_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("roztnFEs5Es", sceLibcInternalPending3_049_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_050_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Zxe-nQMgxHM", sceLibcInternalPending3_050_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_051_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("entYnoIu+fc", sceLibcInternalPending3_051_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_052_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("n-3HFZvDwBw", sceLibcInternalPending3_052_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_053_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("cWaCDW+Dc9U", sceLibcInternalPending3_053_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_054_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("81uX7PzrtG8", sceLibcInternalPending3_054_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_055_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("my9ujasm6-0", sceLibcInternalPending3_055_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_056_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("OWO5cpNw3NA", sceLibcInternalPending3_056_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_057_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("mAwXCpkWaYc", sceLibcInternalPending3_057_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_058_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("wUCRGap1j0U", sceLibcInternalPending3_058_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_059_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("6RGkooTERsE", sceLibcInternalPending3_059_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_060_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("N1VqUWz2OEI", sceLibcInternalPending3_060_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_061_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("I2UzwkwwEPs", sceLibcInternalPending3_061_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_062_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("2bfL3yIBi5k", sceLibcInternalPending3_062_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_063_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("gozsp4urvq8", sceLibcInternalPending3_063_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_064_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("4hiQK82QuLc", sceLibcInternalPending3_064_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_065_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("eZfFLyWCkvg", sceLibcInternalPending3_065_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_066_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("VKdXFE7ualw", sceLibcInternalPending3_066_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_067_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("SmtBNDda5qU", sceLibcInternalPending3_067_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_068_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("bNQpG-eKogg", sceLibcInternalPending3_068_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_069_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("uukWbYS6Bn4", sceLibcInternalPending3_069_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_070_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("IntAnFb+tw0", sceLibcInternalPending3_070_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_071_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ywJpNe675zo", sceLibcInternalPending3_071_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_072_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ALEXgLx9fqU", sceLibcInternalPending3_072_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_073_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Pq4PkG0x1fk", sceLibcInternalPending3_073_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_074_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("dRu2RLn4SKM", sceLibcInternalPending3_074_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_075_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("F+AmVDFUyqM", sceLibcInternalPending3_075_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_076_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("TtYifKtVkYA", sceLibcInternalPending3_076_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_077_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("4+y8-2NsDw0", sceLibcInternalPending3_077_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_078_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("G9LB1YD5-xc", sceLibcInternalPending3_078_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_079_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("J-0I2PtiZc4", sceLibcInternalPending3_079_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_080_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("zomvAQ5RFdA", sceLibcInternalPending3_080_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_081_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("vW-nnV62ea4", sceLibcInternalPending3_081_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_082_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("+hjXHfvy1Mg", sceLibcInternalPending3_082_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_083_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("xLZr4GJRMLo", sceLibcInternalPending3_083_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_084_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("2mb8FYgER+E", sceLibcInternalPending3_084_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_085_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Y3hBU5FYmhM", sceLibcInternalPending3_085_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_086_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("-m2YPwVCwJQ", sceLibcInternalPending3_086_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_087_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("94ZLp2+AOq0", sceLibcInternalPending3_087_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_088_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("bZ+lKHGvOr8", sceLibcInternalPending3_088_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_089_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("cG5hQhjFGog", sceLibcInternalPending3_089_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_090_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("banNSumaAZ0", sceLibcInternalPending3_090_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_091_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("BNZq-mRvDS8", sceLibcInternalPending3_091_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_092_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("wEU8oFtBXT8", sceLibcInternalPending3_092_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_093_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("t39dKpPEuVA", sceLibcInternalPending3_093_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_094_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("MCtJ9D7B5Cs", sceLibcInternalPending3_094_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_095_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Gy2iRxp3LGk", sceLibcInternalPending3_095_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_096_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("2bUUbbcqHUo", sceLibcInternalPending3_096_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_097_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("QossXdwWltI", sceLibcInternalPending3_097_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_098_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ig6SRr1GCU4", sceLibcInternalPending3_098_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_099_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("kU7PvJJKUng", sceLibcInternalPending3_099_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_100_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Ou7GV51-ng4", sceLibcInternalPending3_100_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_101_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("rYLrGFoqfi4", sceLibcInternalPending3_101_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_102_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("W5VYncHdreo", sceLibcInternalPending3_102_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_103_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("GGqIV4cjzzI", sceLibcInternalPending3_103_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_104_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("bZ0oEGQUKO8", sceLibcInternalPending3_104_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_105_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("8NVUcufbklM", sceLibcInternalPending3_105_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_106_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("nftirmo6hBg", sceLibcInternalPending3_106_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_107_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("w9NzCYAjEpQ", sceLibcInternalPending3_107_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_108_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("VPcTGA-LwSo", sceLibcInternalPending3_108_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_109_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ffnhh0HcxJ4", sceLibcInternalPending3_109_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_110_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("uODuM76vS4U", sceLibcInternalPending3_110_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_111_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("NJtKruu9qOs", sceLibcInternalPending3_111_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_112_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("dep6W2Ix35s", sceLibcInternalPending3_112_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_113_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("k8zgjeBmpVY", sceLibcInternalPending3_113_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_114_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("tCihLs4UJxQ", sceLibcInternalPending3_114_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_115_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("w11G58-u4p8", sceLibcInternalPending3_115_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_116_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ll99KkqO6ig", sceLibcInternalPending3_116_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_117_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("mNk6FfI8T7I", sceLibcInternalPending3_117_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_118_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("xlgA01CQtBo", sceLibcInternalPending3_118_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_119_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("jykT-VWQVBY", sceLibcInternalPending3_119_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_120_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("0xgFRKf0Lc4", sceLibcInternalPending3_120_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_121_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ke36E2bqNmI", sceLibcInternalPending3_121_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_122_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("F+cp2B3cWNU", sceLibcInternalPending3_122_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_123_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("rLiFc4+HyHw", sceLibcInternalPending3_123_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_124_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("I3+xmBWGPGo", sceLibcInternalPending3_124_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_125_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("nlAk46weq1w", sceLibcInternalPending3_125_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_126_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("H2KGT3vA7yQ", sceLibcInternalPending3_126_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_127_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("Vbeoft607aI", sceLibcInternalPending3_127_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_128_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("xfOSCbCiY44", sceLibcInternalPending3_128_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_129_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("mY9FWooxqJY", sceLibcInternalPending3_129_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_130_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("V7aIsVIsIIA", sceLibcInternalPending3_130_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_131_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("vCIFGeI6adI", sceLibcInternalPending3_131_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_132_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("USLhWp7sZoU", sceLibcInternalPending3_132_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_133_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("qtpzdwMMCPc", sceLibcInternalPending3_133_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_134_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("ryykbHJ04Cw", sceLibcInternalPending3_134_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_135_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("lmb3oBpMNPU", sceLibcInternalPending3_135_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_136_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("kRGVhisjgMg", sceLibcInternalPending3_136_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_137_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("-b+Avqa2v9k", sceLibcInternalPending3_137_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_138_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("T07KcAOlIeU", sceLibcInternalPending3_138_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_139_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("IdV-tXejEGQ", sceLibcInternalPending3_139_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_140_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("B6JXVOMDdlw", sceLibcInternalPending3_140_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_141_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("WheFSRlZ9JA", sceLibcInternalPending3_141_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_142_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("4pQ3B1BTMgo", sceLibcInternalPending3_142_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_143_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("BDteGj1gqBY", sceLibcInternalPending3_143_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_144_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("1C2-2WB9NN4", sceLibcInternalPending3_144_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_145_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("sX3o6Zmihw0", sceLibcInternalPending3_145_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_146_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("6OYWLisfrB8", sceLibcInternalPending3_146_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_147_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("VpwhOe58wsM", sceLibcInternalPending3_147_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_148_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("jHo78LGEtmU", sceLibcInternalPending3_148_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_149_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("9SSHrlIamto", sceLibcInternalPending3_149_nid_no_patch); + +int APS5_VABI sceLibcInternalPending3_150_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); + return 0; +} +APS5_EXPORT("uX0nKsUo8gc", sceLibcInternalPending3_150_nid_no_patch); +} diff --git a/core/libs/prx/libSceLibcInternal/ExceptionDestructors.cpp b/core/libs/prx/libSceLibcInternal/ExceptionDestructors.cpp new file mode 100644 index 0000000000..9419eec401 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/ExceptionDestructors.cpp @@ -0,0 +1,23 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include "SceTypes.hpp" + +extern "C" { + +void APS5_VABI sceLibcInternalFutureErrorBaseDestructor_nid_no_patch(void* self) { + _ZNSt12future_errorD1Ev_nid_postfix(static_cast(self)); +} +APS5_EXPORT("XFh0C66aEms", sceLibcInternalFutureErrorBaseDestructor_nid_no_patch); + +void APS5_VABI sceLibcInternalOverflowErrorBaseDestructor_nid_no_patch(void* self) { + _ZNSt14overflow_errorD1Ev_nid_postfix(static_cast(self)); +} +APS5_EXPORT("OypvNf3Uq3c", sceLibcInternalOverflowErrorBaseDestructor_nid_no_patch); + +void APS5_VABI sceLibcInternalUnderflowErrorBaseDestructor_nid_no_patch(void* self) { + _ZNSt15underflow_errorD1Ev_nid_postfix(static_cast(self)); +} +APS5_EXPORT("xiqd+QkuYXc", sceLibcInternalUnderflowErrorBaseDestructor_nid_no_patch); + +} diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index dc14f9dc48..628e648c33 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1,5 +1,6 @@ #include "prx/libkernel/include/Api.hpp" #include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libSceLibcInternal/include/MathInternals.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceLibcInternal/include/RuntimeInternals.hpp" @@ -1088,11 +1089,10 @@ unsigned int APS5_VABI alarm_nid_postfix(unsigned int seconds) { } -int APS5_VABI sceLibcInternalUnknown_FHErahnajkw_nid_no_patch() { +int APS5_VABI _Atan_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("FHErahnajkw", sceLibcInternalUnknown_FHErahnajkw_nid_no_patch); int APS5_VABI _Atqexit_nid_postfix() { NotImplemented_nid_no_patch(__func__); @@ -1144,11 +1144,10 @@ int APS5_VABI _Do_call_nid_postfix() { return 0; } -int APS5_VABI sceLibcInternalUnknown_vCQLavj_3CM_nid_no_patch() { +int APS5_VABI _Dtento_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("vCQLavj-3CM", sceLibcInternalUnknown_vCQLavj_3CM_nid_no_patch); int APS5_VABI sceLibcInternalUnknown_aQURHgjHo_w_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -1156,17 +1155,15 @@ int APS5_VABI sceLibcInternalUnknown_aQURHgjHo_w_nid_no_patch() { } APS5_EXPORT("aQURHgjHo-w", sceLibcInternalUnknown_aQURHgjHo_w_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_us3bDnDzd70_nid_no_patch() { +int APS5_VABI _FAtan_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("us3bDnDzd70", sceLibcInternalUnknown_us3bDnDzd70_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_SoNnx4Ejxw8_nid_no_patch() { +int APS5_VABI _FDtento_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("SoNnx4Ejxw8", sceLibcInternalUnknown_SoNnx4Ejxw8_nid_no_patch); int APS5_VABI sceLibcInternalUnknown_qwR1gtp_WS0_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -1180,11 +1177,10 @@ int APS5_VABI sceLibcInternalUnknown_DS03EjPDtFo_nid_no_patch() { } APS5_EXPORT("DS03EjPDtFo", sceLibcInternalUnknown_DS03EjPDtFo_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_OAE3YU396YQ_nid_no_patch() { +int APS5_VABI _FLogpoly_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("OAE3YU396YQ", sceLibcInternalUnknown_OAE3YU396YQ_nid_no_patch); int APS5_VABI sceLibcInternalUnknown_cfpRP3h9F6o_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -1204,17 +1200,15 @@ int APS5_VABI sceLibcInternalUnknown_A98W3Iad6xE_nid_no_patch() { } APS5_EXPORT("A98W3Iad6xE", sceLibcInternalUnknown_A98W3Iad6xE_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_y9Ur6T0A0p8_nid_no_patch() { +int APS5_VABI _FQuad_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("y9Ur6T0A0p8", sceLibcInternalUnknown_y9Ur6T0A0p8_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_PDQbEFcV4h0_nid_no_patch() { +int APS5_VABI _FQuadph_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("PDQbEFcV4h0", sceLibcInternalUnknown_PDQbEFcV4h0_nid_no_patch); int APS5_VABI sceLibcInternalUnknown_TLvAYmLtdVw_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -1222,95 +1216,80 @@ int APS5_VABI sceLibcInternalUnknown_TLvAYmLtdVw_nid_no_patch() { } APS5_EXPORT("TLvAYmLtdVw", sceLibcInternalUnknown_TLvAYmLtdVw_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_fQ_SWrQUQBg_nid_no_patch() { +int APS5_VABI _FSincos_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("fQ+SWrQUQBg", sceLibcInternalUnknown_fQ_SWrQUQBg_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_iBrTJkDlQv8_nid_no_patch() { +int APS5_VABI _FTan_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("iBrTJkDlQv8", sceLibcInternalUnknown_iBrTJkDlQv8_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_odPHnVL_rFg_nid_no_patch() { +int APS5_VABI _FTgamma_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("odPHnVL-rFg", sceLibcInternalUnknown_odPHnVL_rFg_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_1EyHxzcz6AM_nid_no_patch() { +int APS5_VABI _FXp_addh_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("1EyHxzcz6AM", sceLibcInternalUnknown_1EyHxzcz6AM_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_1b_IhPTX0nk_nid_no_patch() { +int APS5_VABI _FXp_addx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("1b+IhPTX0nk", sceLibcInternalUnknown_1b_IhPTX0nk_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_e1y7KVAySrY_nid_no_patch() { +int APS5_VABI _FXp_getw_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("e1y7KVAySrY", sceLibcInternalUnknown_e1y7KVAySrY_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_OVqW4uElSrc_nid_no_patch() { +int APS5_VABI _FXp_invx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("OVqW4uElSrc", sceLibcInternalUnknown_OVqW4uElSrc_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_7GgGIxmwA6I_nid_no_patch() { +int APS5_VABI _FXp_ldexpx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("7GgGIxmwA6I", sceLibcInternalUnknown_7GgGIxmwA6I_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_DVZmEd0ipSg_nid_no_patch() { +int APS5_VABI _FXp_movx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("DVZmEd0ipSg", sceLibcInternalUnknown_DVZmEd0ipSg_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_W_lrIwAQVUk_nid_no_patch() { +int APS5_VABI _FXp_mulh_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("W+lrIwAQVUk", sceLibcInternalUnknown_W_lrIwAQVUk_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_o1B4dkvesMc_nid_no_patch() { +int APS5_VABI _FXp_mulx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("o1B4dkvesMc", sceLibcInternalUnknown_o1B4dkvesMc_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_ikHTMeh60B0_nid_no_patch() { +int APS5_VABI _FXp_setn_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("ikHTMeh60B0", sceLibcInternalUnknown_ikHTMeh60B0_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_5zWUVRtR8xg_nid_no_patch() { +int APS5_VABI _FXp_setw_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("5zWUVRtR8xg", sceLibcInternalUnknown_5zWUVRtR8xg_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_pNWIpeE5Wv4_nid_no_patch() { +int APS5_VABI _FXp_sqrtx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("pNWIpeE5Wv4", sceLibcInternalUnknown_pNWIpeE5Wv4_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_HD9vSXqj6zI_nid_no_patch() { +int APS5_VABI _FXp_subx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("HD9vSXqj6zI", sceLibcInternalUnknown_HD9vSXqj6zI_nid_no_patch); int APS5_VABI _Fac_tidy_nid_postfix() { NotImplemented_nid_no_patch(__func__); @@ -1458,17 +1437,15 @@ int APS5_VABI _Isdst_nid_postfix() { return 0; } -int APS5_VABI sceLibcInternalUnknown_2Aw366Jn07s_nid_no_patch() { +int APS5_VABI _LAtan_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("2Aw366Jn07s", sceLibcInternalUnknown_2Aw366Jn07s_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_PEU_SAfo5_0_nid_no_patch() { +int APS5_VABI _LDtento_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("PEU-SAfo5+0", sceLibcInternalUnknown_PEU_SAfo5_0_nid_no_patch); int APS5_VABI sceLibcInternalUnknown_PG4HVe4X_Oc_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -1482,11 +1459,10 @@ int APS5_VABI sceLibcInternalUnknown_IRUFZywbTT0_nid_no_patch() { } APS5_EXPORT("IRUFZywbTT0", sceLibcInternalUnknown_IRUFZywbTT0_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_niPixjs0l2w_nid_no_patch() { +int APS5_VABI _LLogpoly_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("niPixjs0l2w", sceLibcInternalUnknown_niPixjs0l2w_nid_no_patch); int APS5_VABI sceLibcInternalUnknown_W_T_amhWrkA_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -1506,17 +1482,15 @@ int APS5_VABI sceLibcInternalUnknown_3Z8jD_HTKr8_nid_no_patch() { } APS5_EXPORT("3Z8jD-HTKr8", sceLibcInternalUnknown_3Z8jD_HTKr8_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_fXCPTDS0tD0_nid_no_patch() { +int APS5_VABI _LQuad_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("fXCPTDS0tD0", sceLibcInternalUnknown_fXCPTDS0tD0_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_CbRhl_RoYEM_nid_no_patch() { +int APS5_VABI _LQuadph_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("CbRhl+RoYEM", sceLibcInternalUnknown_CbRhl_RoYEM_nid_no_patch); int APS5_VABI sceLibcInternalUnknown_fJmOr59K8QY_nid_no_patch() { NotImplemented_nid_no_patch(__func__); @@ -1524,95 +1498,80 @@ int APS5_VABI sceLibcInternalUnknown_fJmOr59K8QY_nid_no_patch() { } APS5_EXPORT("fJmOr59K8QY", sceLibcInternalUnknown_fJmOr59K8QY_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_g3dK2qlzwnM_nid_no_patch() { +int APS5_VABI _LSincos_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("g3dK2qlzwnM", sceLibcInternalUnknown_g3dK2qlzwnM_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_mM4OblD9xWM_nid_no_patch() { +int APS5_VABI _LTan_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("mM4OblD9xWM", sceLibcInternalUnknown_mM4OblD9xWM_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_jq4Srfnz9K8_nid_no_patch() { +int APS5_VABI _LTgamma_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("jq4Srfnz9K8", sceLibcInternalUnknown_jq4Srfnz9K8_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_QEr_PxGUoic_nid_no_patch() { +int APS5_VABI _LXp_addh_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("QEr-PxGUoic", sceLibcInternalUnknown_QEr_PxGUoic_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_BuP7bDPGEcQ_nid_no_patch() { +int APS5_VABI _LXp_addx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("BuP7bDPGEcQ", sceLibcInternalUnknown_BuP7bDPGEcQ_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_TnublTBYXTI_nid_no_patch() { +int APS5_VABI _LXp_getw_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("TnublTBYXTI", sceLibcInternalUnknown_TnublTBYXTI_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_FAreWopdBvE_nid_no_patch() { +int APS5_VABI _LXp_invx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("FAreWopdBvE", sceLibcInternalUnknown_FAreWopdBvE_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_7O_vjsHecbY_nid_no_patch() { +int APS5_VABI _LXp_ldexpx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("7O-vjsHecbY", sceLibcInternalUnknown_7O_vjsHecbY_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_wlEdSSqaz_E_nid_no_patch() { +int APS5_VABI _LXp_movx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("wlEdSSqaz+E", sceLibcInternalUnknown_wlEdSSqaz_E_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_riets0BFHMY_nid_no_patch() { +int APS5_VABI _LXp_mulh_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("riets0BFHMY", sceLibcInternalUnknown_riets0BFHMY_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_LbLiLA2biaI_nid_no_patch() { +int APS5_VABI _LXp_mulx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("LbLiLA2biaI", sceLibcInternalUnknown_LbLiLA2biaI_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_hjDoZ6qbkmQ_nid_no_patch() { +int APS5_VABI _LXp_setn_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("hjDoZ6qbkmQ", sceLibcInternalUnknown_hjDoZ6qbkmQ_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_kHVXc_AWbMU_nid_no_patch() { +int APS5_VABI _LXp_setw_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("kHVXc-AWbMU", sceLibcInternalUnknown_kHVXc_AWbMU_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_IPjwywTNR8w_nid_no_patch() { +int APS5_VABI _LXp_sqrtx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("IPjwywTNR8w", sceLibcInternalUnknown_IPjwywTNR8w_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_YCVxOE0lHOI_nid_no_patch() { +int APS5_VABI _LXp_subx_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("YCVxOE0lHOI", sceLibcInternalUnknown_YCVxOE0lHOI_nid_no_patch); int APS5_VABI _Ldtob_nid_postfix() { NotImplemented_nid_no_patch(__func__); @@ -1644,11 +1603,10 @@ int APS5_VABI _Locvar_nid_postfix() { return 0; } -int APS5_VABI sceLibcInternalUnknown_bqcStLRGIXw_nid_no_patch() { +int APS5_VABI _Logpoly_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("bqcStLRGIXw", sceLibcInternalUnknown_bqcStLRGIXw_nid_no_patch); int APS5_VABI _Makeloc_nid_postfix() { NotImplemented_nid_no_patch(__func__); @@ -1714,17 +1672,15 @@ int APS5_VABI _Puttxt_nid_postfix() { return 0; } -int APS5_VABI sceLibcInternalUnknown_TQPr_IeNIS0_nid_no_patch() { +int APS5_VABI _Quad_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("TQPr-IeNIS0", sceLibcInternalUnknown_TQPr_IeNIS0_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown_VecRKuKdXdo_nid_no_patch() { +int APS5_VABI _Quadph_nid_postfix() { NotImplemented_nid_no_patch(__func__); return 0; } -APS5_EXPORT("VecRKuKdXdo", sceLibcInternalUnknown_VecRKuKdXdo_nid_no_patch); int APS5_VABI _Readloc_nid_postfix() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceLibcInternal/Filesystem.cpp b/core/libs/prx/libSceLibcInternal/Filesystem.cpp index bdf5cbf0f7..3edb28ec34 100644 --- a/core/libs/prx/libSceLibcInternal/Filesystem.cpp +++ b/core/libs/prx/libSceLibcInternal/Filesystem.cpp @@ -1,99 +1,20 @@ -#include "prx/libkernel/include/Api.hpp" -#include "prx/libc/include/Api.hpp" -#include "prx/libc/include/GuestDirectory.hpp" +#include "prx/libc/include/GuestFilesystemPrimitives.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); + return GuestFilesystem::StatType(path, perms, error); } int APS5_VABI _ZNSt10filesystem6_LstatEPKcPNS_5permsEPi_nid_postfix(const char* path, int* perms, int* error) { - return StatType(path, perms, error); + return GuestFilesystem::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(); + if (error) *error = GuestFilesystem::GuestErrno(); return UINT64_MAX; } return static_cast(status.st_size); @@ -101,23 +22,23 @@ std::uint64_t APS5_VABI _ZNSt10filesystem10_File_sizeEPKcPi_nid_postfix(const ch 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(); + if (stat_nid_postfix(path, &status) != 0) return GuestFilesystem::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; + GuestFilesystem::GuestErrno() = 0; + std::uint16_t mode = GuestFilesystem::DefaultDirectoryMode; if (existing) { FileStat status{}; - if (stat_nid_postfix(existing, &status) != 0) return -GuestErrno(); + if (stat_nid_postfix(existing, &status) != 0) return -GuestFilesystem::GuestErrno(); mode = status.st_mode; } if (mkdir_nid_postfix(path, mode) == 0) return 1; - if (GuestErrno() == Exists) return 0; - return -GuestErrno(); + if (GuestFilesystem::GuestErrno() == GuestFilesystem::Exists) return 0; + return -GuestFilesystem::GuestErrno(); } bool APS5_VABI _ZNSt10filesystem11_Remove_dirEPKc_nid_postfix(const char* path) { @@ -125,8 +46,8 @@ bool APS5_VABI _ZNSt10filesystem11_Remove_dirEPKc_nid_postfix(const char* path) } int APS5_VABI _ZNSt10filesystem7_UnlinkEPKc_nid_postfix(const char* path) { - GuestErrno() = 0; - return unlink_nid_postfix(path) == 0 ? 0 : GuestErrno(); + GuestFilesystem::GuestErrno() = 0; + return unlink_nid_postfix(path) == 0 ? 0 : GuestFilesystem::GuestErrno(); } int APS5_VABI _ZNSt10filesystem6_ChmodEPKcNS_5permsE_nid_postfix(const char* path, int perms) { @@ -134,42 +55,19 @@ int APS5_VABI _ZNSt10filesystem6_ChmodEPKcNS_5permsE_nid_postfix(const char* pat } 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; + return GuestFilesystem::CurrentGet(target); } 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; + return GuestFilesystem::ReadDir(target, handle, type); } 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; + return GuestFilesystem::OpenDir(target, path, error, type); } int APS5_VABI _ZNSt10filesystem10_Close_dirEPv_nid_postfix(void* handle) { - return closedir_nid_postfix(handle); + return GuestFilesystem::CloseDir(handle); } } diff --git a/core/libs/prx/libSceLibcInternal/InventoryExports.cpp b/core/libs/prx/libSceLibcInternal/InventoryExports.cpp index 056e657d70..3047b1c7a3 100644 --- a/core/libs/prx/libSceLibcInternal/InventoryExports.cpp +++ b/core/libs/prx/libSceLibcInternal/InventoryExports.cpp @@ -94,11 +94,6 @@ int APS5_VABI libSceLibcInternalInventory_017_nid_no_patch() { } APS5_EXPORT("rNYLEsL7M0k", libSceLibcInternalInventory_017_nid_no_patch); -int APS5_VABI libSceLibcInternalInventory_018_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("XFh0C66aEms", libSceLibcInternalInventory_018_nid_no_patch); - int APS5_VABI libSceLibcInternalInventory_019_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -224,11 +219,6 @@ int APS5_VABI libSceLibcInternalInventory_043_nid_no_patch() { } APS5_EXPORT("EYZJsnX58DE", libSceLibcInternalInventory_043_nid_no_patch); -int APS5_VABI libSceLibcInternalInventory_044_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("OypvNf3Uq3c", libSceLibcInternalInventory_044_nid_no_patch); - int APS5_VABI libSceLibcInternalInventory_045_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -259,11 +249,6 @@ int APS5_VABI libSceLibcInternalInventory_050_nid_no_patch() { } APS5_EXPORT("olsoiZsezkk", libSceLibcInternalInventory_050_nid_no_patch); -int APS5_VABI libSceLibcInternalInventory_051_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("xiqd+QkuYXc", libSceLibcInternalInventory_051_nid_no_patch); - int APS5_VABI libSceLibcInternalInventory_052_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } @@ -1444,16 +1429,6 @@ int APS5_VABI libSceLibcInternalInventory_284_nid_no_patch() { } APS5_EXPORT("n8onIBR4Qdk", libSceLibcInternalInventory_284_nid_no_patch); -int APS5_VABI libSceLibcInternalUnknown_BIALMFTZ75I_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("BIALMFTZ75I", libSceLibcInternalUnknown_BIALMFTZ75I_nid_no_patch); - -int APS5_VABI libSceLibcInternalUnknown_fqLrWSWcGHw_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); -} -APS5_EXPORT("fqLrWSWcGHw", libSceLibcInternalUnknown_fqLrWSWcGHw_nid_no_patch); - int APS5_VABI libSceLibcInternalInventory_285_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceLibcInternal/InventoryExports6.cpp b/core/libs/prx/libSceLibcInternal/InventoryExports6.cpp index a54350a928..25ace47dd5 100644 --- a/core/libs/prx/libSceLibcInternal/InventoryExports6.cpp +++ b/core/libs/prx/libSceLibcInternal/InventoryExports6.cpp @@ -471,12 +471,6 @@ int APS5_VABI sceLibcInternalUnknown6_080_bTQcNwRc8hE_nid_no_patch() { } APS5_EXPORT("bTQcNwRc8hE", sceLibcInternalUnknown6_080_bTQcNwRc8hE_nid_no_patch); -int APS5_VABI sceLibcInternalUnknown6_081_XyJPhPqpzMw_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); - return 0; -} -APS5_EXPORT("XyJPhPqpzMw", sceLibcInternalUnknown6_081_XyJPhPqpzMw_nid_no_patch); - int APS5_VABI sceLibcInternalUnknown6_082_Sopthb9ztZI_nid_no_patch() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceLibcInternal/Pad.cpp b/core/libs/prx/libSceLibcInternal/Pad.cpp new file mode 100644 index 0000000000..76fe427ebe --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/Pad.cpp @@ -0,0 +1,13 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include "SceTypes.hpp" + +extern "C" { + +void APS5_VABI sceLibcInternalPadCompleteDestructor_nid_no_patch(void* self) { + _ZNSt4_PadD2Ev_nid_postfix(static_cast(self)); +} +APS5_EXPORT("XyJPhPqpzMw", sceLibcInternalPadCompleteDestructor_nid_no_patch); + +} diff --git a/core/libs/prx/libSceLibcInternal/RecoveredInventory.cpp b/core/libs/prx/libSceLibcInternal/RecoveredInventory.cpp new file mode 100644 index 0000000000..8aa0952286 --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/RecoveredInventory.cpp @@ -0,0 +1,166 @@ +#include "prx/libSceLibcInternal/include/RecoveredInventory.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI __std_minmax_d_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_element_f_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_bitset_to_string_1_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_max_1i_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_element_d_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_max_2u_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNSt8__sce_v215_Thrd_sleep_forEm_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_atomic_notify_multi_direct_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_min_d_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_max_1u_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_min_2i_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_max_element_d_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_min_1u_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_min_element_f_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_f_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_min_f_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_1u_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __truncdfhf2_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_4i_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_atomic_wait_get_deadline_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_max_2i_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceLibcInternalPrimitiveMallocStatsFast_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_atomic_wait_get_remaining_timeout_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_atomic_wait_direct_8_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_min_1i_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_min_2u_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_1i_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_8i_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_max_d_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_max_element_f_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_4u_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceLibcInternalPrimitiveMallocStats_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNSt8__sce_v236_Mtx_init_with_default_name_overrideEPP13pthread_mutexiPKc_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_2u_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_2i_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_max_f_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_minmax_8u_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_min_element_d_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_atomic_wait_direct_16_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI __std_bitset_to_string_2_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceLibcInternal/Sigvec.cpp b/core/libs/prx/libSceLibcInternal/Sigvec.cpp new file mode 100644 index 0000000000..531b0a0cfb --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/Sigvec.cpp @@ -0,0 +1,5 @@ +#include "prx/libc/include/LegacySignals.hpp" + +extern "C" int APS5_VABI sigvec_nid_postfix(int signal, const GuestSignalVector* replacement, GuestSignalVector* previous) { + return LegacySigvec_nid_no_patch(signal, replacement, previous); +} diff --git a/core/libs/prx/libSceLibcInternal/Spawn.cpp b/core/libs/prx/libSceLibcInternal/Spawn.cpp index edb0f41a8f..c6aadc3af3 100644 --- a/core/libs/prx/libSceLibcInternal/Spawn.cpp +++ b/core/libs/prx/libSceLibcInternal/Spawn.cpp @@ -4,6 +4,7 @@ #include "prx/libc/include/ChildProcesses.hpp" #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/HostErrno.hpp" #ifdef _WIN32 #include "prx/libc/include/WindowsDescriptor.hpp" #endif @@ -382,6 +383,10 @@ int SpawnChild(const char* function, const std::string& guest, PosixSpawnFileAct return 0; } #else +int HostSpawnError(int error) { + return error == 0 ? 0 : GuestErrnoFromHost_nid_no_patch(error); +} + 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); @@ -391,7 +396,7 @@ int SpawnChild(const char* function, const std::string& guest, PosixSpawnFileAct 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; + if (const int error = HostSpawnError(posix_spawnattr_init(&hostAttributes))) return error; usedAttributes = &hostAttributes; short hostFlags = 0; if ((flags & SpawnFlagResetIds) != 0) hostFlags |= POSIX_SPAWN_RESETIDS; @@ -404,9 +409,9 @@ int SpawnChild(const char* function, const std::string& guest, PosixSpawnFileAct 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 (const int error = HostSpawnError(posix_spawn_file_actions_init(&hostActions))) { if (usedAttributes != nullptr) posix_spawnattr_destroy(usedAttributes); - return Fault; + return error; } usedActions = &hostActions; } @@ -415,17 +420,17 @@ int SpawnChild(const char* function, const std::string& guest, PosixSpawnFileAct for (const auto& action : (*actions)->actions) { switch (action.kind) { case PosixSpawnFileActionKind::Close: - result = posix_spawn_file_actions_addclose(usedActions, action.descriptor); + result = HostSpawnError(posix_spawn_file_actions_addclose(usedActions, action.descriptor)); break; case PosixSpawnFileActionKind::Duplicate: - result = posix_spawn_file_actions_adddup2(usedActions, action.descriptor, action.newDescriptor); + result = HostSpawnError(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); + result = HostSpawnError(posix_spawn_file_actions_addopen(usedActions, action.descriptor, native.c_str(), HostOpenFlags(action.openFlags), action.mode)); } catch (const std::invalid_argument&) { result = InvalidArgument; } @@ -443,7 +448,7 @@ int SpawnChild(const char* function, const std::string& guest, PosixSpawnFileAct if (hostEnvironment == nullptr) hostEnvironment = *_NSGetEnviron(); #endif pid_t childPid = -1; - result = posix_spawn(&childPid, target.c_str(), usedActions, usedAttributes, arguments, hostEnvironment); + result = HostSpawnError(posix_spawn(&childPid, target.c_str(), usedActions, usedAttributes, arguments, hostEnvironment)); if (result == 0 && processId != nullptr) *processId = static_cast(childPid); } if (usedActions != nullptr) posix_spawn_file_actions_destroy(usedActions); @@ -590,7 +595,8 @@ int SpawnProgram(const char* function, const char* file, bool searchesPath, Posi std::string found; if (!ProgramFound(file, searchesPath, found)) return NoSuchFile; if (arguments == nullptr) return Fault; - return SpawnChild(function, found, actions, attributes, processId, arguments, environment); + std::int32_t ignoredProcessId = 0; + return SpawnChild(function, found, actions, attributes, processId != nullptr ? processId : &ignoredProcessId, 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) { diff --git a/core/libs/prx/libSceLibcInternal/include/Api.hpp b/core/libs/prx/libSceLibcInternal/include/Api.hpp index a995513d96..d2be46cc9b 100644 --- a/core/libs/prx/libSceLibcInternal/include/Api.hpp +++ b/core/libs/prx/libSceLibcInternal/include/Api.hpp @@ -4,6 +4,7 @@ #include "prx/libSceLibcInternal/include/Spawn.hpp" #include "prx/libkernel/System/include/UserContext.hpp" #include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/LegacySignals.hpp" #include "SceTypes.hpp" #include @@ -93,6 +94,7 @@ std::uint64_t APS5_VABI _ZNSt10filesystem10_File_sizeEPKcPi_nid_postfix(const ch 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); +void APS5_VABI sceLibcInternalPadCompleteDestructor_nid_no_patch(void* self); 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); @@ -108,6 +110,9 @@ int APS5_VABI _Mtxunlock_nid_postfix(PthreadMutex* mutex); int APS5_VABI _Mtxdst_nid_postfix(PthreadMutex* mutex); void* APS5_VABI sceLibcInternalAlignedArrayNewNothrow_nid_no_patch(std::size_t size, std::size_t alignment, const void* tag) noexcept; void APS5_VABI sceLibcInternalAlignedArrayDelete_nid_no_patch(void* pointer, std::size_t alignment); +void APS5_VABI sceLibcInternalFutureErrorBaseDestructor_nid_no_patch(void* self); +void APS5_VABI sceLibcInternalOverflowErrorBaseDestructor_nid_no_patch(void* self); +void APS5_VABI sceLibcInternalUnderflowErrorBaseDestructor_nid_no_patch(void* self); int APS5_VABI sceLibcMspaceSetMallocCallback(); int APS5_VABI sceLibcPafMspaceReportMemoryBlocks(); diff --git a/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp b/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp index f513e0d264..83ea0a2f25 100644 --- a/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp +++ b/core/libs/prx/libSceLibcInternal/include/InventoryExports.hpp @@ -22,7 +22,6 @@ int APS5_VABI libSceLibcInternalInventory_014_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_015_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_016_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_017_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_018_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_019_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_020_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_021_nid_no_patch(); @@ -48,14 +47,12 @@ int APS5_VABI libSceLibcInternalInventory_040_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_041_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_042_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_043_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_044_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_045_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_046_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_047_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_048_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_049_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_050_nid_no_patch(); -int APS5_VABI libSceLibcInternalInventory_051_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_052_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_053_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_054_nid_no_patch(); @@ -292,8 +289,6 @@ int APS5_VABI libSceLibcInternalInventory_281_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_282_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_283_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_284_nid_no_patch(); -int APS5_VABI libSceLibcInternalUnknown_BIALMFTZ75I_nid_no_patch(); -int APS5_VABI libSceLibcInternalUnknown_fqLrWSWcGHw_nid_no_patch(); int APS5_VABI libSceLibcInternalInventory_285_nid_no_patch(); int APS5_VABI libSceLibcInternalUnknown_GG6441JdYkA_nid_no_patch(); int APS5_VABI libSceLibcInternalUnknown_QZ9YgTk_plus_yrE_nid_no_patch(); diff --git a/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp b/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp new file mode 100644 index 0000000000..d706b9c36f --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/include/MathInternals.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI _Atan_nid_postfix(); +int APS5_VABI _Dtento_nid_postfix(); +int APS5_VABI _FAtan_nid_postfix(); +int APS5_VABI _FDtento_nid_postfix(); +int APS5_VABI _FLogpoly_nid_postfix(); +int APS5_VABI _FQuad_nid_postfix(); +int APS5_VABI _FQuadph_nid_postfix(); +int APS5_VABI _FSincos_nid_postfix(); +int APS5_VABI _FTan_nid_postfix(); +int APS5_VABI _FTgamma_nid_postfix(); +int APS5_VABI _FXp_addh_nid_postfix(); +int APS5_VABI _FXp_addx_nid_postfix(); +int APS5_VABI _FXp_getw_nid_postfix(); +int APS5_VABI _FXp_invx_nid_postfix(); +int APS5_VABI _FXp_ldexpx_nid_postfix(); +int APS5_VABI _FXp_movx_nid_postfix(); +int APS5_VABI _FXp_mulh_nid_postfix(); +int APS5_VABI _FXp_mulx_nid_postfix(); +int APS5_VABI _FXp_setn_nid_postfix(); +int APS5_VABI _FXp_setw_nid_postfix(); +int APS5_VABI _FXp_sqrtx_nid_postfix(); +int APS5_VABI _FXp_subx_nid_postfix(); +int APS5_VABI _LAtan_nid_postfix(); +int APS5_VABI _LDtento_nid_postfix(); +int APS5_VABI _LLogpoly_nid_postfix(); +int APS5_VABI _LQuad_nid_postfix(); +int APS5_VABI _LQuadph_nid_postfix(); +int APS5_VABI _LSincos_nid_postfix(); +int APS5_VABI _LTan_nid_postfix(); +int APS5_VABI _LTgamma_nid_postfix(); +int APS5_VABI _LXp_addh_nid_postfix(); +int APS5_VABI _LXp_addx_nid_postfix(); +int APS5_VABI _LXp_getw_nid_postfix(); +int APS5_VABI _LXp_invx_nid_postfix(); +int APS5_VABI _LXp_ldexpx_nid_postfix(); +int APS5_VABI _LXp_movx_nid_postfix(); +int APS5_VABI _LXp_mulh_nid_postfix(); +int APS5_VABI _LXp_mulx_nid_postfix(); +int APS5_VABI _LXp_setn_nid_postfix(); +int APS5_VABI _LXp_setw_nid_postfix(); +int APS5_VABI _LXp_sqrtx_nid_postfix(); +int APS5_VABI _LXp_subx_nid_postfix(); +int APS5_VABI _Logpoly_nid_postfix(); +int APS5_VABI _Quad_nid_postfix(); +int APS5_VABI _Quadph_nid_postfix(); + +} diff --git a/core/libs/prx/libSceLibcInternal/include/RecoveredInventory.hpp b/core/libs/prx/libSceLibcInternal/include/RecoveredInventory.hpp new file mode 100644 index 0000000000..9a381429bf --- /dev/null +++ b/core/libs/prx/libSceLibcInternal/include/RecoveredInventory.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI __std_minmax_d_nid_postfix(); +int APS5_VABI __std_minmax_element_f_nid_postfix(); +int APS5_VABI __std_bitset_to_string_1_nid_postfix(); +int APS5_VABI __std_max_1i_nid_postfix(); +int APS5_VABI __std_minmax_element_d_nid_postfix(); +int APS5_VABI __std_max_2u_nid_postfix(); +int APS5_VABI _ZNSt8__sce_v215_Thrd_sleep_forEm_nid_postfix(); +int APS5_VABI __std_atomic_notify_multi_direct_nid_postfix(); +int APS5_VABI __std_min_d_nid_postfix(); +int APS5_VABI __std_max_1u_nid_postfix(); +int APS5_VABI __std_min_2i_nid_postfix(); +int APS5_VABI __std_max_element_d_nid_postfix(); +int APS5_VABI __std_min_1u_nid_postfix(); +int APS5_VABI __std_min_element_f_nid_postfix(); +int APS5_VABI __std_minmax_f_nid_postfix(); +int APS5_VABI __std_min_f_nid_postfix(); +int APS5_VABI __std_minmax_1u_nid_postfix(); +int APS5_VABI __truncdfhf2_nid_postfix(); +int APS5_VABI __std_minmax_4i_nid_postfix(); +int APS5_VABI __std_atomic_wait_get_deadline_nid_postfix(); +int APS5_VABI __std_max_2i_nid_postfix(); +int APS5_VABI sceLibcInternalPrimitiveMallocStatsFast_nid_postfix(); +int APS5_VABI __std_atomic_wait_get_remaining_timeout_nid_postfix(); +int APS5_VABI __std_atomic_wait_direct_8_nid_postfix(); +int APS5_VABI __std_min_1i_nid_postfix(); +int APS5_VABI __std_min_2u_nid_postfix(); +int APS5_VABI __std_minmax_1i_nid_postfix(); +int APS5_VABI __std_minmax_8i_nid_postfix(); +int APS5_VABI __std_max_d_nid_postfix(); +int APS5_VABI __std_max_element_f_nid_postfix(); +int APS5_VABI __std_minmax_4u_nid_postfix(); +int APS5_VABI sceLibcInternalPrimitiveMallocStats_nid_postfix(); +int APS5_VABI _ZNSt8__sce_v236_Mtx_init_with_default_name_overrideEPP13pthread_mutexiPKc_nid_postfix(); +int APS5_VABI __std_minmax_2u_nid_postfix(); +int APS5_VABI __std_minmax_2i_nid_postfix(); +int APS5_VABI __std_max_f_nid_postfix(); +int APS5_VABI __std_minmax_8u_nid_postfix(); +int APS5_VABI __std_min_element_d_nid_postfix(); +int APS5_VABI __std_atomic_wait_direct_16_nid_postfix(); +int APS5_VABI __std_bitset_to_string_2_nid_postfix(); + +} diff --git a/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp b/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp index c144aac8b7..8fb6498e98 100644 --- a/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp +++ b/core/libs/prx/libSceLibcInternal/libSceLibcInternal.hpp @@ -1,5 +1,7 @@ #pragma once +#include "prx/libSceLibcInternal/include/RecoveredInventory.hpp" #include "prx/libSceLibcInternal/include/RuntimeInternals.hpp" #include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libSceLibcInternal/include/MathInternals.hpp" #include "prx/libSceLibcInternal/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceLibreSsl/CMakeLists.txt b/core/libs/prx/libSceLibreSsl/CMakeLists.txt index e4635639c7..ebaa00d309 100644 --- a/core/libs/prx/libSceLibreSsl/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl/CMakeLists.txt @@ -1,10 +1,21 @@ -add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp) +add_library(libSceLibreSsl SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Bignum.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Digest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Runtime.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Aes.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ocsp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Stack.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Ui.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rsa.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/X509StoreContext.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Pkey.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/EvpCipherCtx.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Evp.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 ${CMAKE_CURRENT_SOURCE_DIR}/src/ExData.cpp) if(BUILD_TESTING) + add_test_executable(guest_libressl_bn_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslBn.cpp) + target_include_directories(guest_libressl_bn_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_bn_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_bn_tests) + add_test(NAME guest_libressl_bn COMMAND guest_libressl_bn_tests) + set_tests_properties(guest_libressl_bn PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_bn PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + 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) @@ -159,6 +170,17 @@ if(BUILD_TESTING) set_tests_properties(guest_libressl_pkey_type PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_libressl_evp_cipher_ctx_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslEvpCipherCtx.cpp) + target_include_directories(guest_libressl_evp_cipher_ctx_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_evp_cipher_ctx_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_evp_cipher_ctx_tests) + add_test(NAME guest_libressl_evp_cipher_ctx COMMAND guest_libressl_evp_cipher_ctx_tests) + set_tests_properties(guest_libressl_evp_cipher_ctx PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_evp_cipher_ctx PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Cipher.cpp) @@ -336,3 +358,29 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_libressl_x509_extension_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslX509Extension.cpp) + target_include_directories(guest_libressl_x509_extension_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_x509_extension_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_x509_extension_tests) + add_test(NAME guest_libressl_x509_extension COMMAND guest_libressl_x509_extension_tests) + set_tests_properties(guest_libressl_x509_extension PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_x509_extension PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_libressl_evp_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslEvp.cpp) + target_include_directories(guest_libressl_evp_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_evp_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_evp_tests) + add_test(NAME guest_libressl_evp COMMAND guest_libressl_evp_tests) + set_tests_properties(guest_libressl_evp PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_evp PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl/Cmac.cmake b/core/libs/prx/libSceLibreSsl/Cmac.cmake new file mode 100644 index 0000000000..e7b8cf205c --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/Cmac.cmake @@ -0,0 +1,32 @@ +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/Cmac.cpp) +target_link_libraries(libSceLibreSsl PRIVATE mbedcrypto) +if(MINGW OR CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(libSceLibreSsl PRIVATE LINKER:--exclude-libs,ALL) +endif() + +if(BUILD_TESTING) + add_test_executable(guest_libressl_cmac_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslCmac.cpp) + target_include_directories(guest_libressl_cmac_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_cmac_tests PRIVATE libSceLibreSsl libc mbedcrypto) + configure_windows_unwind(guest_libressl_cmac_tests) + add_test(NAME guest_libressl_cmac COMMAND guest_libressl_cmac_tests) + + add_test_executable(guest_libressl_cmac_patched_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslCmac.cpp) + target_include_directories(guest_libressl_cmac_patched_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_compile_definitions(guest_libressl_cmac_patched_tests PRIVATE ANYPS5_CMAC_DYNAMIC) + target_link_libraries(guest_libressl_cmac_patched_tests PRIVATE mbedcrypto ${CMAKE_DL_LIBS}) + configure_windows_unwind(guest_libressl_cmac_patched_tests) + if(WIN32) + target_link_libraries(guest_libressl_cmac_patched_tests PRIVATE libc) + endif() + add_custom_target(guest_libressl_cmac_patch DEPENDS ${PATCHED_DIR}/libSceLibreSsl.prx ${PATCHED_DIR}/libc.prx) + add_dependencies(guest_libressl_cmac_patched_tests guest_libressl_cmac_patch) + add_test(NAME guest_libressl_cmac_patched COMMAND guest_libressl_cmac_patched_tests ${PATCHED_DIR}) + set_tests_properties(guest_libressl_cmac guest_libressl_cmac_patched PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_cmac PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + set_tests_properties(guest_libressl_cmac_patched PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:${PATCHED_DIR}") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl/ErrorQueue.cmake b/core/libs/prx/libSceLibreSsl/ErrorQueue.cmake new file mode 100644 index 0000000000..a4cca61645 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/ErrorQueue.cmake @@ -0,0 +1,33 @@ +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/ErrorQueue.cpp) +target_link_libraries(libSceLibreSsl3 PRIVATE libSceLibreSsl) + +if(BUILD_TESTING) + find_package(Python3 REQUIRED COMPONENTS Interpreter) + set(libreSslSource ${PROJECT_SOURCE_DIR}/3rdparty/libressl-openbsd/src/lib/libcrypto) + if(NOT EXISTS ${libreSslSource}/err/err.c) + message(FATAL_ERROR "Initialize 3rdparty/libressl-openbsd with git submodule update --init") + endif() + file(GLOB_RECURSE libreSslStringSources CONFIGURE_DEPENDS ${libreSslSource}/*.h ${libreSslSource}/*.c) + set(libreSslStrings ${CMAKE_CURRENT_BINARY_DIR}/prx/libSceLibreSsl/ErrorStrings.inc) + add_custom_command(OUTPUT ${libreSslStrings} + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/GenerateErrorStrings.py ${libreSslSource} ${libreSslStrings} + DEPENDS ${CMAKE_CURRENT_LIST_DIR}/GenerateErrorStrings.py ${libreSslStringSources} + VERBATIM) + + add_test_executable(guest_libressl_error_string_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslErrors.cpp) + target_include_directories(guest_libressl_error_string_tests PRIVATE ${LIBS_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/prx/libSceLibreSsl) + target_sources(guest_libressl_error_string_tests PRIVATE ${libreSslStrings}) + target_link_libraries(guest_libressl_error_string_tests PRIVATE libSceLibreSsl libSceLibreSsl3) + configure_windows_unwind(guest_libressl_error_string_tests) + add_test(NAME guest_libressl_errors COMMAND guest_libressl_error_string_tests) + add_test(NAME guest_libressl_free_strings COMMAND guest_libressl_error_string_tests free) + add_test(NAME guest_libressl_free_strings_first COMMAND guest_libressl_error_string_tests free-first) + set_tests_properties(guest_libressl_errors guest_libressl_free_strings + guest_libressl_free_strings_first PROPERTIES TIMEOUT 15) + if(WIN32) + set_tests_properties(guest_libressl_errors guest_libressl_free_strings + guest_libressl_free_strings_first PROPERTIES + ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl/ErrorQueue.cpp b/core/libs/prx/libSceLibreSsl/ErrorQueue.cpp new file mode 100644 index 0000000000..7f597f4dab --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/ErrorQueue.cpp @@ -0,0 +1,41 @@ +#include "ErrorQueue.hpp" +#include "prx/libc/include/General.hpp" +#include +#include + +namespace { +char errorBuffer[256]; +} + +extern "C" { +char* APS5_VABI ERR_error_string_nid_postfix(std::uint64_t error, char* buffer) { + if (buffer == nullptr) buffer = errorBuffer; + ERR_error_string_n_nid_postfix(error, buffer, sizeof(errorBuffer)); + return buffer; +} + +void APS5_VABI ERR_error_string_n_nid_postfix(std::uint64_t error, char* buffer, std::size_t length) { + if (buffer == nullptr && length != 0) NotImplemented_nid_no_patch(__func__); + const int library = (error >> 24) & 255; + const int function = (error >> 12) & 4095; + const int reason = error & 4095; + const int written = std::snprintf(buffer, length, "error:%08llX:lib(%d):func(%d):reason(%d)", + static_cast(error), library, function, reason); + if (written >= 0 && static_cast(written) >= length && length > 4) { + char* next = buffer; + for (std::size_t index = 0; index < 4; ++index) { + char* colon = std::strchr(next, ':'); + char* last = buffer + length - 5 + index; + if (colon == nullptr || colon > last) { + colon = last; + *colon = ':'; + } + next = colon + 1; + } + } +} + +void APS5_VABI ERR_free_strings_nid_postfix() { +} + +} diff --git a/core/libs/prx/libSceLibreSsl/ErrorQueue.hpp b/core/libs/prx/libSceLibreSsl/ErrorQueue.hpp new file mode 100644 index 0000000000..2d188a0dcd --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/ErrorQueue.hpp @@ -0,0 +1,15 @@ +#ifndef CORE_LIBS_PRX_LIBSCELIBRESSL_ERRORQUEUE_HPP +#define CORE_LIBS_PRX_LIBSCELIBRESSL_ERRORQUEUE_HPP + +#include +#include +#include "include/Error.hpp" +#include "libSceLibreSsl.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +extern "C" { +std::uint64_t LibreSslPeekLastError_nid_no_patch(); + +} + +#endif diff --git a/core/libs/prx/libSceLibreSsl/Export.cpp b/core/libs/prx/libSceLibreSsl/Export.cpp index fc78f64744..0f80ff0573 100644 --- a/core/libs/prx/libSceLibreSsl/Export.cpp +++ b/core/libs/prx/libSceLibreSsl/Export.cpp @@ -8,6 +8,7 @@ #include #include #include "SceTypes.hpp" +#include "prx/libSceLibreSsl/include/Bio.hpp" #include "prx/libc/include/General.hpp" #include "mbedtls/des.h" @@ -22,7 +23,18 @@ std::atomic lockingCallback{nullptr}; std::atomic addLockCallback{nullptr}; std::atomic threadIdCallback{nullptr}; -} +struct X509Extension { + void* object; + std::int32_t critical; + void* value; +}; + +static_assert(sizeof(X509Extension) == 24); +static_assert(offsetof(X509Extension, object) == 0); +static_assert(offsetof(X509Extension, critical) == 8); +static_assert(offsetof(X509Extension, value) == 16); + +} // namespace extern "C" { @@ -37,36 +49,16 @@ int APS5_VABI ASN1_TIME_print_nid_postfix(void) { return 0; } -int APS5_VABI BIO_ctrl_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI BIO_f_ssl_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI BIO_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BIO_new_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI BIO_new_fp_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI BIO_new_mem_buf_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI BIO_new_socket_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -77,89 +69,13 @@ int APS5_VABI BIO_printf_nid_postfix(void) { return 0; } -int APS5_VABI BIO_puts_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI BIO_s_file_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI BIO_s_mem_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BIO_write_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_CTX_end_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_CTX_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_CTX_new_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_CTX_start_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_add_word_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_bin2bn_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_bn2bin_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_hex2bn_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_mod_inverse_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_mul_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_new_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_num_bits_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI BIO_write_nid_postfix(LibreSsl::Bio* bio, const void* source, int size) { + return LibreSslBioWrite_nid_no_patch(bio, source, size); } int APS5_VABI BN_print_nid_postfix(void) { @@ -167,41 +83,6 @@ int APS5_VABI BN_print_nid_postfix(void) { return 0; } -int APS5_VABI BN_set_word_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI BN_sub_word_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI CMAC_CTX_free_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI CMAC_CTX_new_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI CMAC_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI CMAC_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI CMAC_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI CONF_modules_free_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -449,21 +330,6 @@ int APS5_VABI ENGINE_set_default_nid_postfix(void) { return 0; } -int APS5_VABI ERR_error_string_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ERR_error_string_n_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI ERR_free_strings_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; @@ -479,21 +345,6 @@ int APS5_VABI EVP_CIPHER_CTX_free_nid_postfix(void) { return 0; } -int APS5_VABI EVP_CIPHER_CTX_init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_CIPHER_CTX_new_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_CIPHER_CTX_set_padding_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_CIPHER_block_size_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -529,26 +380,6 @@ int APS5_VABI EVP_DecryptUpdate_nid_postfix(void) { return 0; } -int APS5_VABI EVP_Digest_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_DigestFinal_ex_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_DigestInit_ex_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_DigestUpdate_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_DigestVerifyFinal_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -574,31 +405,6 @@ int APS5_VABI EVP_EncryptUpdate_nid_postfix(void) { return 0; } -int APS5_VABI EVP_MD_CTX_cleanup_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_MD_CTX_create_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_MD_CTX_destroy_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_MD_CTX_init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_MD_size_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_PKEY_copy_parameters_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -634,10 +440,6 @@ int APS5_VABI EVP_PKEY_set1_RSA_nid_postfix(void) { return 0; } -int APS5_VABI EVP_aes_128_cbc_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI EVP_aes_128_cfb8_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); @@ -654,11 +456,6 @@ int APS5_VABI EVP_aes_128_gcm_nid_postfix(void) { return 0; } -int APS5_VABI EVP_aes_192_cbc_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_aes_192_cfb8_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -674,11 +471,6 @@ int APS5_VABI EVP_aes_192_gcm_nid_postfix(void) { return 0; } -int APS5_VABI EVP_aes_256_cbc_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI EVP_aes_256_cfb8_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -699,61 +491,6 @@ int APS5_VABI EVP_cleanup_nid_postfix(void) { return 0; } -int APS5_VABI EVP_md5_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_sha256_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_sha384_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI EVP_sha512_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI HMAC_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI HMAC_CTX_cleanup_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI HMAC_CTX_init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI HMAC_Final_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI HMAC_Init_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI HMAC_Init_ex_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI HMAC_Update_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI OBJ_nid2sn_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1362,19 +1099,25 @@ int APS5_VABI X509V3_EXT_print_nid_postfix(void) { return 0; } -int APS5_VABI X509_EXTENSION_get_critical_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI X509_EXTENSION_get_critical_nid_postfix(const void* extension) { + if (!extension) return 0; + std::int32_t critical; + std::memcpy(&critical, static_cast(extension) + offsetof(X509Extension, critical), sizeof(critical)); + return critical > 0; } -int APS5_VABI X509_EXTENSION_get_data_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI X509_EXTENSION_get_data_nid_postfix(const void* extension) { + if (!extension) return nullptr; + void* value; + std::memcpy(&value, static_cast(extension) + offsetof(X509Extension, value), sizeof(value)); + return value; } -int APS5_VABI X509_EXTENSION_get_object_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI X509_EXTENSION_get_object_nid_postfix(const void* extension) { + if (!extension) return nullptr; + void* object; + std::memcpy(&object, static_cast(extension) + offsetof(X509Extension, object), sizeof(object)); + return object; } int APS5_VABI X509_INFO_free_nid_postfix(void) { diff --git a/core/libs/prx/libSceLibreSsl/GenerateErrorStrings.py b/core/libs/prx/libSceLibreSsl/GenerateErrorStrings.py new file mode 100644 index 0000000000..15f265bef2 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/GenerateErrorStrings.py @@ -0,0 +1,97 @@ +import ast +import json +import operator +from pathlib import Path +import re +import sys + + +source = Path(sys.argv[1]) +destination = Path(sys.argv[2]) +macros = {} +operations = {ast.BitOr: operator.or_, ast.BitAnd: operator.and_, ast.LShift: operator.lshift, + ast.RShift: operator.rshift, ast.Add: operator.add, ast.Sub: operator.sub} + + +def uncomment(text): + return re.sub(r"/\*.*?\*/", "", text, flags=re.S) + + +for header in sorted(source.rglob("*.h")): + for name, value in re.findall(r"^#\s*define\s+(\w+)\s+([^\n]+)", uncomment(header.read_text(encoding="utf-8")), re.M): + if re.fullmatch(r"ERR_(?:LIB|R)_\w+|\w+_[RF]_\w+", name): + if name in macros and macros[name] != value.strip(): + raise ValueError(f"Conflicting definition: {name}") + macros[name] = value.strip() + + +def evaluate(expression): + def visit(node): + if isinstance(node, ast.Constant) and type(node.value) is int: + return node.value + if isinstance(node, ast.Name): + return evaluate(macros[node.id]) + if isinstance(node, ast.BinOp) and type(node.op) in operations: + return operations[type(node.op)](visit(node.left), visit(node.right)) + raise ValueError(f"Unsupported constant: {expression}") + return visit(ast.parse(expression, mode="eval").body) + + +def pack(library, function, reason): + return ((library & 255) << 24) | ((function & 4095) << 12) | (reason & 4095) + + +def code(expression, library): + call = re.fullmatch(r"ERR_(PACK|FUNC|REASON)\((.*)\)", expression.strip()) + if call: + values = [evaluate(value.strip()) for value in call[2].split(",")] + if call[1] == "PACK": + return pack(*values) + if call[1] == "FUNC": + return pack(library, values[0], 0) + return pack(library, 0, values[0]) + return evaluate(expression.strip()) + + +loader = uncomment((source / "err/err_all.c").read_text(encoding="utf-8")) +loaded = set(re.findall(r"\bERR_load_(\w+)_strings\(\);", loader)) - {"ERR"} +strings = {} +licenses = [] +found = set() +for path in sorted(source.rglob("*.c")): + text = path.read_text(encoding="latin-1") + clean = uncomment(text) + library = 0 + if path != source / "err/err.c": + names = set(re.findall(r"\bERR_load_(\w+)_strings\(void\)", clean)) & loaded + if not names: + continue + found.update(names) + library_name = re.search(r"#define ERR_FUNC\(func\)\s+ERR_PACK\((\w+),", clean) + if not library_name: + raise ValueError(f"Missing library: {path}") + library = evaluate(library_name[1]) + licenses.append(text[:text.index("#include")]) + tables = re.findall(r"static ERR_STRING_DATA (\w+)\[\]\s*=\s*\{(.*?)\n\};", clean, re.S) + if not tables: + raise ValueError(f"Missing string tables: {path}") + for name, body in tables: + entries = re.findall(r'\{\s*([^{}]+?),\s*("(?:[^"\\]|\\.)*"|NULL)\s*\}', body, re.S) + remainder = re.sub(r'\{\s*([^{}]+?),\s*("(?:[^"\\]|\\.)*"|NULL)\s*\}', "", body, flags=re.S) + if remainder.strip(" \t\r\n,"): + raise ValueError(f"Unsupported table: {path}: {name}") + for expression, message in entries: + value = code(expression, library) + if value == 0 and message == "NULL": + continue + if message == "NULL": + raise ValueError(f"Missing message: {path}: {expression}") + if name == "ERR_str_functs": + value |= pack(evaluate("ERR_LIB_SYS"), 0, 0) + strings[value] = json.loads(message) +if found != loaded: + raise ValueError(f"Missing libraries: {loaded - found}") +destination.parent.mkdir(parents=True, exist_ok=True) +destination.write_text("\n".join(licenses) + "\nconstexpr ErrorString CryptoStrings[] = {\n" + + "".join(f" {{0x{value:08X}, {json.dumps(message)}}},\n" + for value, message in sorted(strings.items())) + "};\n", encoding="utf-8") diff --git a/core/libs/prx/libSceLibreSsl/MemoryBio.cmake b/core/libs/prx/libSceLibreSsl/MemoryBio.cmake new file mode 100644 index 0000000000..a9ed5d36c8 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/MemoryBio.cmake @@ -0,0 +1,32 @@ +target_sources(libSceLibreSsl PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/Bio.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_libressl_bio_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslBio.cpp) + target_include_directories(guest_libressl_bio_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl_bio_tests PRIVATE libSceLibreSsl libc) + configure_windows_unwind(guest_libressl_bio_tests) + add_test(NAME guest_libressl_bio COMMAND guest_libressl_bio_tests) + set_tests_properties(guest_libressl_bio PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_bio PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_libressl_bio_patched_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibreSslBio.cpp) + target_include_directories(guest_libressl_bio_patched_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_compile_definitions(guest_libressl_bio_patched_tests PRIVATE ANYPS5_BIO_DYNAMIC) + target_link_libraries(guest_libressl_bio_patched_tests PRIVATE ${CMAKE_DL_LIBS}) + configure_windows_unwind(guest_libressl_bio_patched_tests) + if(WIN32) + target_link_libraries(guest_libressl_bio_patched_tests PRIVATE libc) + endif() + add_custom_target(libressl_bio_patched_libraries DEPENDS + ${PATCHED_DIR}/libSceLibreSsl.prx ${PATCHED_DIR}/libc.prx ${PATCHED_DIR}/libkernel.prx) + add_dependencies(guest_libressl_bio_patched_tests libressl_bio_patched_libraries) + add_test(NAME guest_libressl_bio_patched COMMAND guest_libressl_bio_patched_tests ${PATCHED_DIR}) + set_tests_properties(guest_libressl_bio_patched PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_libressl_bio_patched PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:${PATCHED_DIR}") + endif() +endif() diff --git a/core/libs/prx/libSceLibreSsl/include/Api.hpp b/core/libs/prx/libSceLibreSsl/include/Api.hpp index 3e3d9b7601..78ebb73e80 100644 --- a/core/libs/prx/libSceLibreSsl/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Api.hpp @@ -1,6 +1,9 @@ #pragma once +#include #include "SceTypes.hpp" +#include "Bio.hpp" +#include "Cmac.hpp" struct Asn1String { std::int32_t length; @@ -131,6 +134,18 @@ struct Sha512Context { std::uint32_t num, mdLength; }; +struct Bignum { + std::uint64_t* d; + int top; + int dmax; + int neg; + int flags; +}; + +struct BignumContext { + std::vector marks; +}; + using StackCompare = int (APS5_VABI*)(const void*, const void*); using StackElementFree = void (APS5_VABI*)(void*); @@ -250,6 +265,64 @@ struct LibreSslX509StoreContext { void* exData; }; +struct EvpMdContext; + +struct EvpMd { + std::int32_t type; + std::int32_t pkeyType; + std::int32_t mdSize; + std::uint64_t flags; + int (APS5_VABI* init)(EvpMdContext* context); + int (APS5_VABI* update)(EvpMdContext* context, const void* data, std::size_t length); + int (APS5_VABI* finalize)(EvpMdContext* context, unsigned char* digest); + int (APS5_VABI* copy)(EvpMdContext* to, const EvpMdContext* from); + int (APS5_VABI* cleanup)(EvpMdContext* context); + void* sign; + void* verify; + std::int32_t requiredPkeyType[5]; + std::int32_t blockSize; + std::int32_t contextSize; + int (APS5_VABI* control)(EvpMdContext* context, int command, int argument, void* pointer); +}; + +struct EvpMdContext { + const EvpMd* digest; + void* engine; + std::uint64_t flags; + void* mdData; + void* pkeyContext; + int (APS5_VABI* update)(EvpMdContext* context, const void* data, std::size_t length); +}; + +inline constexpr std::size_t HmacMaxBlock = 128; + +struct HmacContext { + const EvpMd* md; + EvpMdContext mdContext; + EvpMdContext innerContext; + EvpMdContext outerContext; + std::uint32_t keyLength; + unsigned char key[HmacMaxBlock]; +}; + +struct LibreSslEvpCipherCtx { + const void* cipher; + void* engine; + int encrypt; + int bufLen; + unsigned char oiv[16]; + unsigned char iv[16]; + unsigned char buf[32]; + int num; + void* appData; + int keyLen; + std::uint64_t flags; + void* cipherData; + int finalUsed; + int blockMask; + unsigned char final[32]; +}; + extern "C" { std::int64_t APS5_VABI ASN1_INTEGER_get_nid_postfix(const Asn1String* value); @@ -259,39 +332,39 @@ 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); +std::int64_t APS5_VABI BIO_ctrl_nid_postfix(LibreSsl::Bio* bio, int command, std::int64_t argument, void* pointer); 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_free_nid_postfix(LibreSsl::Bio* bio); +LibreSsl::Bio* APS5_VABI BIO_new_nid_postfix(const LibreSsl::BioMethod* method); int APS5_VABI BIO_new_fp_nid_postfix(void); -int APS5_VABI BIO_new_mem_buf_nid_postfix(void); +LibreSsl::Bio* APS5_VABI BIO_new_mem_buf_nid_postfix(const void* source, int size); 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_puts_nid_postfix(LibreSsl::Bio* bio, const char* source); 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); +const LibreSsl::BioMethod* APS5_VABI BIO_s_mem_nid_postfix(); +int APS5_VABI BIO_write_nid_postfix(LibreSsl::Bio* bio, const void* source, int size); +void APS5_VABI BN_CTX_end_nid_postfix(BignumContext* ctx); +void APS5_VABI BN_CTX_free_nid_postfix(BignumContext* ctx); +BignumContext* APS5_VABI BN_CTX_new_nid_postfix(void); +void APS5_VABI BN_CTX_start_nid_postfix(BignumContext* ctx); +int APS5_VABI BN_add_word_nid_postfix(Bignum* a, std::uint64_t w); +Bignum* APS5_VABI BN_bin2bn_nid_postfix(const unsigned char* s, int len, Bignum* ret); +int APS5_VABI BN_bn2bin_nid_postfix(const Bignum* a, unsigned char* to); +void APS5_VABI BN_free_nid_postfix(Bignum* a); +int APS5_VABI BN_hex2bn_nid_postfix(Bignum** a, const char* str); +Bignum* APS5_VABI BN_mod_inverse_nid_postfix(Bignum* r, const Bignum* a, const Bignum* n, BignumContext*); +int APS5_VABI BN_mul_nid_postfix(Bignum* r, const Bignum* a, const Bignum* b, BignumContext*); +Bignum* APS5_VABI BN_new_nid_postfix(void); +int APS5_VABI BN_num_bits_nid_postfix(const Bignum* a); 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 BN_set_word_nid_postfix(Bignum* a, std::uint64_t w); +int APS5_VABI BN_sub_word_nid_postfix(Bignum* a, std::uint64_t w); +void APS5_VABI CMAC_CTX_free_nid_postfix(LibreSslCmac* context); +LibreSslCmac* APS5_VABI CMAC_CTX_new_nid_postfix(); +int APS5_VABI CMAC_Final_nid_postfix(LibreSslCmac* context, unsigned char* output, std::size_t* length); +int APS5_VABI CMAC_Init_nid_postfix(LibreSslCmac* context, const void* key, std::size_t keyLength, const LibreSslCipher* cipher, void* engine); +int APS5_VABI CMAC_Update_nid_postfix(LibreSslCmac* context, const void* data, std::size_t length); int APS5_VABI CONF_modules_free_nid_postfix(void); int APS5_VABI CONF_modules_load_file_nid_postfix(void); int APS5_VABI CRYPTO_THREADID_set_callback_nid_postfix(void* callback); @@ -341,19 +414,19 @@ 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); +void APS5_VABI ERR_clear_error_nid_postfix(); +char* APS5_VABI ERR_error_string_nid_postfix(std::uint64_t error, char* buffer); +void APS5_VABI ERR_error_string_n_nid_postfix(std::uint64_t error, char* buffer, std::size_t length); +void APS5_VABI ERR_free_strings_nid_postfix(); +std::uint64_t APS5_VABI ERR_get_error_nid_postfix(); +std::uint64_t APS5_VABI ERR_peek_error_nid_postfix(); +void APS5_VABI ERR_remove_thread_state_nid_postfix(const void* id); 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); +void APS5_VABI EVP_CIPHER_CTX_init_nid_postfix(LibreSslEvpCipherCtx* ctx); +LibreSslEvpCipherCtx* APS5_VABI EVP_CIPHER_CTX_new_nid_postfix(void); +int APS5_VABI EVP_CIPHER_CTX_set_padding_nid_postfix(LibreSslEvpCipherCtx* ctx, int pad); 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); @@ -361,20 +434,20 @@ 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_Digest_nid_postfix(const void* data, std::size_t length, unsigned char* digest, std::uint32_t* size, const EvpMd* type, void* engine); +int APS5_VABI EVP_DigestFinal_ex_nid_postfix(EvpMdContext* context, unsigned char* digest, std::uint32_t* size); +int APS5_VABI EVP_DigestInit_ex_nid_postfix(EvpMdContext* context, const EvpMd* type, void* engine); +int APS5_VABI EVP_DigestUpdate_nid_postfix(EvpMdContext* context, const void* data, std::size_t length); 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_MD_CTX_cleanup_nid_postfix(EvpMdContext* context); +EvpMdContext* APS5_VABI EVP_MD_CTX_create_nid_postfix(void); +void APS5_VABI EVP_MD_CTX_destroy_nid_postfix(EvpMdContext* context); +void APS5_VABI EVP_MD_CTX_init_nid_postfix(EvpMdContext* context); +int APS5_VABI EVP_MD_size_nid_postfix(const EvpMd* md); 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); @@ -383,32 +456,32 @@ 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(int type); -int APS5_VABI EVP_aes_128_cbc_nid_postfix(void); +const LibreSslCipher* APS5_VABI EVP_aes_128_cbc_nid_postfix(); 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); +const LibreSslCipher* APS5_VABI EVP_aes_192_cbc_nid_postfix(); 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); +const LibreSslCipher* APS5_VABI EVP_aes_256_cbc_nid_postfix(); 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); +const EvpMd* APS5_VABI EVP_md5_nid_postfix(void); +const EvpMd* APS5_VABI EVP_sha256_nid_postfix(void); +const EvpMd* APS5_VABI EVP_sha384_nid_postfix(void); +const EvpMd* APS5_VABI EVP_sha512_nid_postfix(void); void APS5_VABI GENERAL_NAMES_free_nid_postfix(LibreSslStack* names); void APS5_VABI GENERAL_NAME_free_nid_postfix(LibreSslGeneralName* name); -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); +unsigned char* APS5_VABI HMAC_nid_postfix(const EvpMd* md, const void* key, int keyLength, const unsigned char* data, std::size_t length, unsigned char* digest, std::uint32_t* digestLength); +void APS5_VABI HMAC_CTX_cleanup_nid_postfix(HmacContext* context); +void APS5_VABI HMAC_CTX_init_nid_postfix(HmacContext* context); +int APS5_VABI HMAC_Final_nid_postfix(HmacContext* context, unsigned char* digest, std::uint32_t* length); +int APS5_VABI HMAC_Init_nid_postfix(HmacContext* context, const void* key, int length, const EvpMd* md); +int APS5_VABI HMAC_Init_ex_nid_postfix(HmacContext* context, const void* key, int length, const EvpMd* md, void* engine); +int APS5_VABI HMAC_Update_nid_postfix(HmacContext* context, const unsigned char* data, std::size_t length); void APS5_VABI OBJ_cleanup_nid_postfix(void); int APS5_VABI OBJ_create_nid_postfix(const char* oid, const char* shortName, const char* longName); int APS5_VABI OBJ_nid2sn_nid_postfix(void); @@ -558,9 +631,9 @@ int APS5_VABI UI_method_set_reader_nid_postfix(LibreSslUiMethod* method, UiStrin int APS5_VABI UI_method_set_writer_nid_postfix(LibreSslUiMethod* method, UiStringFunction writer); int APS5_VABI UI_set_result_nid_postfix(LibreSslUi* ui, LibreSslUiString* string, const char* result); 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_EXTENSION_get_critical_nid_postfix(const void* extension); +void* APS5_VABI X509_EXTENSION_get_data_nid_postfix(const void* extension); +void* APS5_VABI X509_EXTENSION_get_object_nid_postfix(const void* extension); int APS5_VABI X509_INFO_free_nid_postfix(void); LibreSslX509LookupMethod* APS5_VABI X509_LOOKUP_file_nid_postfix(void); int APS5_VABI X509_NAME_ENTRY_get_data_nid_postfix(void); diff --git a/core/libs/prx/libSceLibreSsl/include/Bio.hpp b/core/libs/prx/libSceLibreSsl/include/Bio.hpp new file mode 100644 index 0000000000..63613ce1d7 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/include/Bio.hpp @@ -0,0 +1,72 @@ +#ifndef CORE_LIBS_PRX_LIBSCELIBRESSL_INCLUDE_BIO_HPP +#define CORE_LIBS_PRX_LIBSCELIBRESSL_INCLUDE_BIO_HPP + +#include +#include +#include "prx/libc/include/general/VabiMacros.hpp" + +namespace LibreSsl { + +struct Bio; +using BioCallback = std::int64_t (APS5_VABI *)(Bio*, int, const char*, int, std::int64_t, std::int64_t); +using BioInfoCallback = void (APS5_VABI *)(Bio*, int, const char*, int, std::int64_t, std::int64_t); + +struct BioMethod { + int type; + const char* name; + int (APS5_VABI *write)(Bio*, const char*, int); + int (APS5_VABI *read)(Bio*, char*, int); + int (APS5_VABI *puts)(Bio*, const char*); + int (APS5_VABI *gets)(Bio*, char*, int); + std::int64_t (APS5_VABI *control)(Bio*, int, std::int64_t, void*); + int (APS5_VABI *create)(Bio*); + int (APS5_VABI *destroy)(Bio*); + std::int64_t (APS5_VABI *callbackControl)(Bio*, int, BioInfoCallback); +}; + +struct Bio { + const BioMethod* method; + BioCallback callback; + char* callbackArgument; + int initialized; + int shutdown; + int flags; + int retryReason; + int number; + void* data; + Bio* next; + Bio* previous; + int references; + std::uint64_t bytesRead; + std::uint64_t bytesWritten; + void* extraData; +}; + +struct BioBuffer { + std::size_t length; + char* data; + std::size_t capacity; +}; + +static_assert(sizeof(BioMethod) == 80); +static_assert(offsetof(BioMethod, write) == 16); +static_assert(offsetof(BioMethod, control) == 48); +static_assert(offsetof(BioMethod, callbackControl) == 72); +static_assert(sizeof(Bio) == 104); +static_assert(offsetof(Bio, initialized) == 24); +static_assert(offsetof(Bio, flags) == 32); +static_assert(offsetof(Bio, number) == 40); +static_assert(offsetof(Bio, data) == 48); +static_assert(offsetof(Bio, references) == 72); +static_assert(offsetof(Bio, bytesRead) == 80); +static_assert(offsetof(Bio, bytesWritten) == 88); +static_assert(offsetof(Bio, extraData) == 96); +static_assert(sizeof(BioBuffer) == 24); +static_assert(offsetof(BioBuffer, data) == 8); +static_assert(offsetof(BioBuffer, capacity) == 16); + +} // namespace LibreSsl + +extern "C" int LibreSslBioWrite_nid_no_patch(LibreSsl::Bio* bio, const void* source, int size); + +#endif diff --git a/core/libs/prx/libSceLibreSsl/include/Cmac.hpp b/core/libs/prx/libSceLibreSsl/include/Cmac.hpp new file mode 100644 index 0000000000..caf9698952 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/include/Cmac.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include "prx/libc/include/general/VabiMacros.hpp" + +struct LibreSslCipherContext; + +struct LibreSslCipher { + std::int32_t nid; + std::int32_t blockSize; + std::int32_t keyLength; + std::int32_t ivLength; + std::uint64_t flags; + int (APS5_VABI* init)(LibreSslCipherContext*, const unsigned char*, const unsigned char*, int); + int (APS5_VABI* cipher)(LibreSslCipherContext*, unsigned char*, const unsigned char*, std::size_t); + int (APS5_VABI* cleanup)(LibreSslCipherContext*); + std::int32_t contextSize; + int (APS5_VABI* setAsn1Parameters)(LibreSslCipherContext*, void*); + int (APS5_VABI* getAsn1Parameters)(LibreSslCipherContext*, void*); + int (APS5_VABI* control)(LibreSslCipherContext*, int, int, void*); + void* applicationData; +}; + +struct LibreSslCipherContext { + const LibreSslCipher* cipher; + void* engine; + std::int32_t encrypt; + std::int32_t bufferLength; + unsigned char originalIv[16]; + unsigned char iv[16]; + unsigned char buffer[32]; + std::int32_t position; + void* applicationData; + std::int32_t keyLength; + std::uint64_t flags; + void* cipherData; + std::int32_t finalUsed; + std::int32_t blockMask; + unsigned char final[32]; +}; + +static_assert(sizeof(LibreSslCipher) == 88 && offsetof(LibreSslCipher, init) == 24 && offsetof(LibreSslCipher, contextSize) == 48); +static_assert(sizeof(LibreSslCipherContext) == 168 && offsetof(LibreSslCipherContext, iv) == 40 && offsetof(LibreSslCipherContext, flags) == 112 && offsetof(LibreSslCipherContext, cipherData) == 120); + +struct LibreSslCmac; diff --git a/core/libs/prx/libSceLibreSsl/include/Error.hpp b/core/libs/prx/libSceLibreSsl/include/Error.hpp index 0c7a23712f..95d5c2732d 100644 --- a/core/libs/prx/libSceLibreSsl/include/Error.hpp +++ b/core/libs/prx/libSceLibreSsl/include/Error.hpp @@ -3,7 +3,7 @@ extern "C" { -void LibreSslPutError_nid_no_patch(int library, int function, int reason); +void LibreSslPutError_nid_no_patch(int library, int function, int reason, const char* file = nullptr, int line = 0); } diff --git a/core/libs/prx/libSceLibreSsl/src/Bignum.cpp b/core/libs/prx/libSceLibreSsl/src/Bignum.cpp new file mode 100644 index 0000000000..27ce2ec72e --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Bignum.cpp @@ -0,0 +1,378 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +namespace { + +static_assert(sizeof(Bignum) == 24); +static_assert(offsetof(Bignum, d) == 0); +static_assert(offsetof(Bignum, top) == 8); +static_assert(offsetof(Bignum, dmax) == 12); +static_assert(offsetof(Bignum, neg) == 16); +static_assert(offsetof(Bignum, flags) == 20); + +constexpr int FlagMalloced = 1; +constexpr int FlagStaticData = 2; + +using Mag = std::vector; + +struct Int { + Mag mag; + bool neg = false; +}; + +void Trim(Mag& m) { + while (!m.empty() && m.back() == 0) m.pop_back(); +} + +int Compare(const Mag& a, const Mag& b) { + if (a.size() != b.size()) return a.size() < b.size() ? -1 : 1; + for (std::size_t i = a.size(); i-- > 0;) { + if (a[i] != b[i]) return a[i] < b[i] ? -1 : 1; + } + return 0; +} + +Mag AddMag(const Mag& a, const Mag& b) { + const std::size_t count = a.size() > b.size() ? a.size() : b.size(); + Mag r; + r.reserve(count + 1); + std::uint64_t carry = 0; + for (std::size_t i = 0; i < count; i++) { + const unsigned __int128 sum = static_cast(i < a.size() ? a[i] : 0) + (i < b.size() ? b[i] : 0) + carry; + r.push_back(static_cast(sum)); + carry = static_cast(sum >> 64); + } + if (carry) r.push_back(carry); + return r; +} + +Mag SubMag(const Mag& a, const Mag& b) { + Mag r(a.size()); + std::uint64_t borrow = 0; + for (std::size_t i = 0; i < a.size(); i++) { + const std::uint64_t x = a[i]; + const std::uint64_t y = i < b.size() ? b[i] : 0; + const std::uint64_t diff = x - y; + const std::uint64_t first = x < y; + r[i] = diff - borrow; + borrow = first | (diff < borrow); + } + Trim(r); + return r; +} + +Mag MulMag(const Mag& a, const Mag& b) { + if (a.empty() || b.empty()) return {}; + Mag r(a.size() + b.size(), 0); + for (std::size_t i = 0; i < a.size(); i++) { + std::uint64_t carry = 0; + for (std::size_t j = 0; j < b.size(); j++) { + const unsigned __int128 product = static_cast(a[i]) * b[j] + r[i + j] + carry; + r[i + j] = static_cast(product); + carry = static_cast(product >> 64); + } + r[i + b.size()] = carry; + } + Trim(r); + return r; +} + +std::size_t BitLength(const Mag& m) { + if (m.empty()) return 0; + return (m.size() - 1) * 64 + std::bit_width(m.back()); +} + +Mag ShiftLeft(const Mag& m, std::size_t bits) { + if (m.empty()) return {}; + const std::size_t words = bits / 64; + const unsigned rest = static_cast(bits % 64); + Mag r(m.size() + words + 1, 0); + for (std::size_t i = 0; i < m.size(); i++) { + r[i + words] |= m[i] << rest; + if (rest) r[i + words + 1] |= m[i] >> (64 - rest); + } + Trim(r); + return r; +} + +void ShiftRightOne(Mag& m) { + for (std::size_t i = 0; i < m.size(); i++) { + m[i] >>= 1; + if (i + 1 < m.size()) m[i] |= m[i + 1] << 63; + } + Trim(m); +} + +void DivMod(const Mag& a, const Mag& b, Mag& quotient, Mag& remainder) { + remainder = a; + quotient.clear(); + const std::size_t aBits = BitLength(a); + const std::size_t bBits = BitLength(b); + if (aBits < bBits) return; + const std::size_t shift = aBits - bBits; + Mag divisor = ShiftLeft(b, shift); + quotient.assign(shift / 64 + 1, 0); + for (std::size_t s = shift + 1; s-- > 0;) { + if (Compare(remainder, divisor) >= 0) { + remainder = SubMag(remainder, divisor); + quotient[s / 64] |= std::uint64_t(1) << (s % 64); + } + ShiftRightOne(divisor); + } + Trim(quotient); +} + +Int AddSigned(const Int& x, const Int& y) { + Int r; + if (x.neg == y.neg) { + r.mag = AddMag(x.mag, y.mag); + r.neg = x.neg; + } else { + const int order = Compare(x.mag, y.mag); + if (order > 0) { + r.mag = SubMag(x.mag, y.mag); + r.neg = x.neg; + } else if (order < 0) { + r.mag = SubMag(y.mag, x.mag); + r.neg = y.neg; + } + } + if (r.mag.empty()) r.neg = false; + return r; +} + +bool InverseMod(const Int& a, const Mag& n, Mag& out) { + if (n.size() == 1 && n[0] == 1) { + out.clear(); + return true; + } + Mag quotient; + Mag r1; + DivMod(a.mag, n, quotient, r1); + if (a.neg && !r1.empty()) r1 = SubMag(n, r1); + Mag r0 = n; + Int t0; + Int t1; + t1.mag = {1}; + while (!r1.empty()) { + Mag remainder; + DivMod(r0, r1, quotient, remainder); + Int scaled; + scaled.mag = MulMag(quotient, t1.mag); + scaled.neg = !t1.neg && !scaled.mag.empty(); + Int t2 = AddSigned(t0, scaled); + r0 = std::move(r1); + r1 = std::move(remainder); + t0 = std::move(t1); + t1 = std::move(t2); + } + if (r0.size() != 1 || r0[0] != 1) return false; + out = t0.neg ? SubMag(n, t0.mag) : t0.mag; + return true; +} + +Bignum* Checked(Bignum* value, const char* function) { + if (!value) throw std::invalid_argument(std::string(function) + ": null BIGNUM"); + return value; +} + +const Bignum* Checked(const Bignum* value, const char* function) { + if (!value) throw std::invalid_argument(std::string(function) + ": null BIGNUM"); + return value; +} + +Int Load(const Bignum* bn) { + Int r; + if (bn->top > 0) r.mag.assign(bn->d, bn->d + bn->top); + Trim(r.mag); + r.neg = bn->neg && !r.mag.empty(); + return r; +} + +bool Store(Bignum* bn, const Mag& m, bool neg) { + if (m.empty()) { + bn->top = 0; + bn->neg = 0; + return true; + } + const std::size_t count = m.size(); + if (!bn->d || static_cast(bn->dmax) < count) { + if (bn->flags & FlagStaticData) return false; + if (count > 0x7FFFFFFF) return false; + auto* grown = static_cast(calloc_nid_postfix(count, sizeof(std::uint64_t))); + if (!grown) return false; + if (bn->d) free_nid_postfix(bn->d); + bn->d = grown; + bn->dmax = static_cast(count); + } + for (std::size_t i = 0; i < count; i++) bn->d[i] = m[i]; + for (std::size_t i = count; i < static_cast(bn->dmax); i++) bn->d[i] = 0; + bn->top = static_cast(count); + bn->neg = neg ? 1 : 0; + return true; +} + +Bignum* NewBignum() { + auto* bn = static_cast(calloc_nid_postfix(1, sizeof(Bignum))); + if (bn) bn->flags = FlagMalloced; + return bn; +} + +void FreeBignum(Bignum* bn) { + if (!bn) return; + if (bn->d && !(bn->flags & FlagStaticData)) free_nid_postfix(bn->d); + if (bn->flags & FlagMalloced) { + free_nid_postfix(bn); + } else { + bn->d = nullptr; + } +} + +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; +} + +bool ApplyWord(Bignum* a, std::uint64_t w, bool subtract) { + Int word; + if (w) word.mag = {w}; + word.neg = subtract && w; + const Int sum = AddSigned(Load(a), word); + return Store(a, sum.mag, sum.neg); +} +} + +extern "C" { +Bignum* APS5_VABI BN_new_nid_postfix(void) { + return NewBignum(); +} + +void APS5_VABI BN_free_nid_postfix(Bignum* a) { + FreeBignum(a); +} + +BignumContext* APS5_VABI BN_CTX_new_nid_postfix(void) { + return new (std::nothrow) BignumContext(); +} + +void APS5_VABI BN_CTX_free_nid_postfix(BignumContext* ctx) { + delete ctx; +} + +void APS5_VABI BN_CTX_start_nid_postfix(BignumContext* ctx) { + if (!ctx) throw std::invalid_argument(std::string(__func__) + ": null context"); + ctx->marks.push_back(0); +} + +void APS5_VABI BN_CTX_end_nid_postfix(BignumContext* ctx) { + if (!ctx) throw std::invalid_argument(std::string(__func__) + ": null context"); + if (!ctx->marks.empty()) ctx->marks.pop_back(); +} + +Bignum* APS5_VABI BN_bin2bn_nid_postfix(const unsigned char* s, int len, Bignum* ret) { + if (len < 0) return nullptr; + if (len > 0 && !s) throw std::invalid_argument(std::string(__func__) + ": null input"); + Bignum* target = ret ? ret : NewBignum(); + if (!target) return nullptr; + Mag m((static_cast(len) + 7) / 8, 0); + for (int i = 0; i < len; i++) { + const std::size_t bit = static_cast(len - 1 - i); + m[bit / 8] |= static_cast(s[i]) << (bit % 8 * 8); + } + Trim(m); + if (!Store(target, m, false)) { + if (!ret) FreeBignum(target); + return nullptr; + } + return target; +} + +int APS5_VABI BN_num_bits_nid_postfix(const Bignum* a) { + return static_cast(BitLength(Load(Checked(a, __func__)).mag)); +} + +int APS5_VABI BN_bn2bin_nid_postfix(const Bignum* a, unsigned char* to) { + const Mag m = Load(Checked(a, __func__)).mag; + const std::size_t length = (BitLength(m) + 7) / 8; + if (length && !to) throw std::invalid_argument(std::string(__func__) + ": null output"); + for (std::size_t i = 0; i < length; i++) { + const std::size_t bit = length - 1 - i; + to[i] = static_cast(m[bit / 8] >> (bit % 8 * 8)); + } + return static_cast(length); +} + +int APS5_VABI BN_hex2bn_nid_postfix(Bignum** a, const char* str) { + if (!str || !*str) return 0; + const bool negative = *str == '-'; + const char* digits = str + negative; + std::size_t count = 0; + while (HexValue(digits[count]) >= 0) count++; + if (count == 0 || count > 0x7FFFFFFF / 4) return 0; + const int consumed = static_cast(count + negative); + if (!a) return consumed; + Mag m((count + 15) / 16, 0); + for (std::size_t i = 0; i < count; i++) { + const std::size_t nibble = count - 1 - i; + m[nibble / 16] |= static_cast(HexValue(digits[i])) << (nibble % 16 * 4); + } + Trim(m); + Bignum* target = *a ? *a : NewBignum(); + if (!target) return 0; + if (!Store(target, m, negative)) { + if (!*a) FreeBignum(target); + return 0; + } + *a = target; + return consumed; +} + +int APS5_VABI BN_set_word_nid_postfix(Bignum* a, std::uint64_t w) { + Checked(a, __func__); + Mag m; + if (w) m = {w}; + return Store(a, m, false); +} + +int APS5_VABI BN_add_word_nid_postfix(Bignum* a, std::uint64_t w) { + return ApplyWord(Checked(a, __func__), w, false); +} + +int APS5_VABI BN_sub_word_nid_postfix(Bignum* a, std::uint64_t w) { + return ApplyWord(Checked(a, __func__), w, true); +} + +int APS5_VABI BN_mul_nid_postfix(Bignum* r, const Bignum* a, const Bignum* b, BignumContext*) { + Checked(r, __func__); + const Int x = Load(Checked(a, __func__)); + const Int y = Load(Checked(b, __func__)); + const Mag product = MulMag(x.mag, y.mag); + return Store(r, product, x.neg != y.neg); +} + +Bignum* APS5_VABI BN_mod_inverse_nid_postfix(Bignum* r, const Bignum* a, const Bignum* n, BignumContext*) { + const Int value = Load(Checked(a, __func__)); + const Int modulus = Load(Checked(n, __func__)); + if (modulus.mag.empty()) return nullptr; + Mag inverse; + if (!InverseMod(value, modulus.mag, inverse)) return nullptr; + Bignum* target = r ? r : NewBignum(); + if (!target) return nullptr; + if (!Store(target, inverse, false)) { + if (!r) FreeBignum(target); + return nullptr; + } + return target; +} +} diff --git a/core/libs/prx/libSceLibreSsl/src/Bio.cpp b/core/libs/prx/libSceLibreSsl/src/Bio.cpp new file mode 100644 index 0000000000..98f88e4985 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Bio.cpp @@ -0,0 +1,284 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibreSsl/ErrorQueue.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include + + +namespace { + +using LibreSsl::Bio; +using LibreSsl::BioBuffer; +using LibreSsl::BioMethod; + +constexpr int ReadOnly = 0x200; +constexpr int RetryFlags = 0xf; +constexpr int RetryRead = 0x9; +constexpr std::size_t MaximumGrowth = 0x5ffffffc; +constexpr int BioLibrary = 32; +constexpr int BufferLibrary = 7; +constexpr int AllocationFailure = 65; +constexpr int NullParameter = 115; +constexpr int Uninitialized = 120; +constexpr int UnsupportedMethod = 121; +constexpr int ReadOnlyWrite = 126; + +int queueError(int library, int reason, int result) { + LibreSslPutError_nid_no_patch(library, 0xfff, reason, __FILE__, __LINE__); + return result; +} + +void clearBuffer(BioBuffer& buffer) { + auto* bytes = reinterpret_cast(buffer.data); + for (std::size_t i = 0; i < buffer.capacity; ++i) bytes[i] = 0; +} + +BioBuffer& memoryBuffer(Bio* bio) { + if (!bio || !bio->data) throw std::invalid_argument("BIO: missing memory buffer"); + return *static_cast(bio->data); +} + +int APS5_VABI createMemory(Bio* bio) { + auto* buffer = static_cast(calloc_nid_postfix(1, sizeof(BioBuffer))); + if (!buffer) return queueError(BufferLibrary, AllocationFailure, 0); + bio->data = buffer; + bio->initialized = 1; + bio->shutdown = 1; + bio->number = -1; + return 1; +} + +int APS5_VABI destroyMemory(Bio* bio) { + if (!bio) return 0; + if (bio->shutdown && bio->initialized && bio->data) { + auto* buffer = static_cast(bio->data); + if (!(bio->flags & ReadOnly)) { + clearBuffer(*buffer); + free_nid_postfix(buffer->data); + } + free_nid_postfix(buffer); + bio->data = nullptr; + } + return 1; +} + +int APS5_VABI readMemory(Bio* bio, char* destination, int size) { + auto& buffer = memoryBuffer(bio); + bio->flags &= ~RetryFlags; + const int count = size >= 0 ? static_cast(std::min(size, buffer.length)) : size; + if (destination && count > 0) { + std::memcpy(destination, buffer.data, count); + buffer.length -= count; + if (bio->flags & ReadOnly) buffer.data += count; + else std::memmove(buffer.data, buffer.data + count, buffer.length); + return count; + } + if (buffer.length != 0) return count; + if (bio->number != 0) bio->flags |= RetryRead; + return bio->number; +} + +int APS5_VABI writeMemory(Bio* bio, const char* source, int size) { + auto& buffer = memoryBuffer(bio); + if (!source) return queueError(BioLibrary, NullParameter, -1); + if (bio->flags & ReadOnly) return queueError(BioLibrary, ReadOnlyWrite, -1); + if (size < 0) throw std::invalid_argument("BIO: negative memory write size"); + bio->flags &= ~RetryFlags; + const auto count = static_cast(size); + if (buffer.length > std::numeric_limits::max() - count) NotImplemented_nid_no_patch(__func__); + const std::size_t length = buffer.length + count; + if (length > buffer.capacity) { + if (length > MaximumGrowth) return queueError(BufferLibrary, AllocationFailure, -1); + const std::size_t capacity = (length + 3) / 3 * 4; + auto* data = static_cast(calloc_nid_postfix(capacity, 1)); + if (!data) return queueError(BufferLibrary, AllocationFailure, -1); + if (buffer.length != 0) std::memcpy(data, buffer.data, buffer.length); + if (count != 0) std::memcpy(data + buffer.length, source, count); + clearBuffer(buffer); + free_nid_postfix(buffer.data); + buffer.data = data; + buffer.capacity = capacity; + } else if (count != 0) { + std::memmove(buffer.data + buffer.length, source, count); + } + buffer.length = length; + return size; +} + +int APS5_VABI putsMemory(Bio* bio, const char* source) { + if (!source) throw std::invalid_argument("BIO_puts: null string"); + const std::size_t size = std::strlen(source); + if (size > static_cast(std::numeric_limits::max())) NotImplemented_nid_no_patch(__func__); + return writeMemory(bio, source, static_cast(size)); +} + +int APS5_VABI getsMemory(Bio* bio, char* destination, int size) { + auto& buffer = memoryBuffer(bio); + if (!destination || size == std::numeric_limits::min()) throw std::invalid_argument("BIO: invalid line buffer"); + bio->flags &= ~RetryFlags; + if (size <= 1 || buffer.length == 0) { + *destination = '\0'; + return 0; + } + const auto limit = std::min(size - 1, buffer.length); + const void* newline = std::memchr(buffer.data, '\n', limit); + const std::size_t count = newline ? static_cast(newline) - buffer.data + 1 : limit; + const int result = readMemory(bio, destination, static_cast(count)); + destination[result] = '\0'; + return result; +} + +std::int64_t APS5_VABI controlMemory(Bio* bio, int command, std::int64_t argument, void* pointer) { + if (!bio) throw std::invalid_argument("BIO: null memory BIO"); + switch (command) { + case 1: { + auto& buffer = memoryBuffer(bio); + if (buffer.data) { + if (bio->flags & ReadOnly) { + if (buffer.capacity < buffer.length) throw std::invalid_argument("BIO: invalid read-only buffer length"); + buffer.data -= buffer.capacity - buffer.length; + buffer.length = buffer.capacity; + } else { + clearBuffer(buffer); + buffer.length = 0; + } + } + return 1; + } + case 2: + return memoryBuffer(bio).length == 0; + case 3: + if (pointer) *static_cast(pointer) = memoryBuffer(bio).data; + return static_cast(memoryBuffer(bio).length); + case 8: + return bio->shutdown; + case 9: + bio->shutdown = static_cast(argument); + return 1; + case 10: + return static_cast(memoryBuffer(bio).length); + case 11: + case 12: + return 1; + case 13: + return 0; + case 114: + destroyMemory(bio); + bio->data = pointer; + bio->shutdown = static_cast(argument); + return 1; + case 115: + if (pointer) std::memcpy(pointer, &bio->data, sizeof(bio->data)); + return 1; + case 130: + bio->number = static_cast(argument); + return 1; + default: + return 0; + } +} + +const BioMethod MemoryMethod{0x401, "memory buffer", writeMemory, readMemory, putsMemory, getsMemory, controlMemory, createMemory, destroyMemory, nullptr}; + +} // namespace + +extern "C" { + +const LibreSsl::BioMethod* APS5_VABI BIO_s_mem_nid_postfix() { + return &MemoryMethod; +} + +LibreSsl::Bio* APS5_VABI BIO_new_nid_postfix(const LibreSsl::BioMethod* method) { + if (!method) throw std::invalid_argument("BIO_new: null method"); + auto* bio = static_cast(calloc_nid_postfix(1, sizeof(Bio))); + if (!bio) { + queueError(BioLibrary, AllocationFailure, 0); + return nullptr; + } + bio->method = method; + bio->shutdown = 1; + bio->references = 1; + try { + if (!method->create || method->create(bio)) return bio; + } catch (...) { + free_nid_postfix(bio); + throw; + } + free_nid_postfix(bio); + return nullptr; +} + +LibreSsl::Bio* APS5_VABI BIO_new_mem_buf_nid_postfix(const void* source, int size) { + if (!source) { + queueError(BioLibrary, NullParameter, 0); + return nullptr; + } + auto* bio = BIO_new_nid_postfix(&MemoryMethod); + if (!bio) return nullptr; + auto& buffer = memoryBuffer(bio); + buffer.data = const_cast(static_cast(source)); + buffer.length = size < 0 ? std::strlen(buffer.data) : static_cast(size); + buffer.capacity = buffer.length; + bio->flags |= ReadOnly; + bio->number = 0; + return bio; +} + +int APS5_VABI BIO_free_nid_postfix(LibreSsl::Bio* bio) { + if (!bio) return 0; + if (bio->extraData) NotImplemented_nid_no_patch(__func__); + if (std::atomic_ref(bio->references).fetch_sub(1) > 1) return 1; + if (bio->callback) { + const int result = static_cast(bio->callback(bio, 1, nullptr, 0, 0, 1)); + if (result <= 0) return result; + } + if (bio->extraData) NotImplemented_nid_no_patch(__func__); + if (bio->method && bio->method->destroy) bio->method->destroy(bio); + free_nid_postfix(bio); + return 1; +} + +std::int64_t APS5_VABI BIO_ctrl_nid_postfix(LibreSsl::Bio* bio, int command, std::int64_t argument, void* pointer) { + if (!bio) return 0; + if (!bio->method || !bio->method->control) return queueError(BioLibrary, UnsupportedMethod, -2); + const auto callback = bio->callback; + if (callback) { + const auto result = callback(bio, 6, static_cast(pointer), command, argument, 1); + if (result <= 0) return result; + } + const auto result = bio->method->control(bio, command, argument, pointer); + return callback ? callback(bio, 0x86, static_cast(pointer), command, argument, result) : result; +} + +int LibreSslBioWrite_nid_no_patch(LibreSsl::Bio* bio, const void* source, int size) { + if (!bio || size < 0) return 0; + if (!bio->method || !bio->method->write) return queueError(BioLibrary, UnsupportedMethod, -2); + const auto callback = bio->callback; + if (callback) { + const int result = static_cast(callback(bio, 3, static_cast(source), size, 0, 1)); + if (result <= 0) return result; + } + if (!bio->initialized) return queueError(BioLibrary, Uninitialized, -2); + const int result = bio->method->write(bio, static_cast(source), size); + if (result > 0) bio->bytesWritten += static_cast(result); + return callback ? static_cast(callback(bio, 0x83, static_cast(source), size, 0, result)) : result; +} + +int APS5_VABI BIO_puts_nid_postfix(LibreSsl::Bio* bio, const char* source) { + if (!bio || !bio->method || !bio->method->puts) return queueError(BioLibrary, UnsupportedMethod, -2); + const auto callback = bio->callback; + if (callback) { + const int result = static_cast(callback(bio, 4, source, 0, 0, 1)); + if (result <= 0) return result; + } + if (!bio->initialized) return queueError(BioLibrary, Uninitialized, -2); + const int result = bio->method->puts(bio, source); + if (result > 0) bio->bytesWritten += static_cast(result); + return callback ? static_cast(callback(bio, 0x84, source, 0, 0, result)) : result; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Cmac.cpp b/core/libs/prx/libSceLibreSsl/src/Cmac.cpp new file mode 100644 index 0000000000..8f9967a383 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Cmac.cpp @@ -0,0 +1,271 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibreSsl/include/Cmac.hpp" +#include "prx/libSceLibreSsl/include/Error.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include + +struct LibreSslCmac { + LibreSslCipherContext cipherContext; + unsigned char firstSubkey[32]; + unsigned char secondSubkey[32]; + unsigned char previousBlock[32]; + unsigned char lastBlock[32]; + std::int32_t lastLength; +}; + +static_assert(sizeof(LibreSslCmac) == 304); + +namespace { + +void Cleanse(void* data, std::size_t length) { + auto* bytes = static_cast(data); + while (length-- != 0) *bytes++ = 0; +} + +int Error(int reason) { + LibreSslPutError_nid_no_patch(6, 0xfff, reason, __FILE__, __LINE__); + return 0; +} + +void RequireContext(const LibreSslCmac* context) { + if (context == nullptr) throw std::invalid_argument("CMAC: null context"); +} + +int Cleanup(LibreSslCipherContext& context) { + if (context.engine != nullptr) NotImplemented_nid_no_patch(__func__); + if (context.cipher != nullptr) { + if (context.cipher->cleanup != nullptr && !context.cipher->cleanup(&context)) return 0; + if (context.cipherData != nullptr) Cleanse(context.cipherData, context.cipher->contextSize); + } + free_nid_postfix(context.cipherData); + Cleanse(&context, sizeof(context)); + return 1; +} + +int Control(LibreSslCipherContext& context, int command, int argument) { + if (context.cipher == nullptr) return Error(131); + if (context.cipher->control == nullptr) return Error(132); + const int result = context.cipher->control(&context, command, argument, nullptr); + return result == -1 ? Error(133) : result; +} + +int InitializeCipher(LibreSslCipherContext& context, const LibreSslCipher* cipher, const unsigned char* key, const unsigned char* iv, void* engine) { + context.encrypt = 1; + if (context.engine != nullptr || (cipher != nullptr && engine != nullptr)) NotImplemented_nid_no_patch(__func__); + if (cipher != nullptr) { + if (cipher->contextSize < 0 || cipher->ivLength < 0) throw std::invalid_argument("CMAC: invalid cipher dimensions"); + if (context.cipher != nullptr) { + const auto flags = context.flags; + Cleanup(context); + context.encrypt = 1; + context.flags = flags; + } + context.cipher = cipher; + context.cipherData = nullptr; + if (cipher->contextSize != 0) { + context.cipherData = calloc_nid_postfix(1, cipher->contextSize); + if (context.cipherData == nullptr) return Error(65); + } + context.keyLength = cipher->keyLength; + context.flags &= 1; + if ((cipher->flags & 0x40) != 0 && !Control(context, 0, 0)) return Error(134); + } + if (context.cipher == nullptr) return Error(131); + const auto& method = *context.cipher; + if (method.blockSize != 1 && method.blockSize != 8 && method.blockSize != 16) return Error(136); + const auto mode = method.flags & 0xf0007; + if ((context.flags & 1) == 0 && mode == 0x10002) return Error(170); + if ((method.flags & 0x10) == 0) { + if (mode == 2 || mode == 3 || mode == 4 || mode == 5) { + if (method.ivLength > 16) return Error(102); + if (mode != 2) context.position = 0; + if (mode == 5) { + if (iv != nullptr) std::memcpy(context.iv, iv, method.ivLength); + } else { + if (iv != nullptr) std::memcpy(context.originalIv, iv, method.ivLength); + std::memcpy(context.iv, context.originalIv, method.ivLength); + } + } else if (mode != 0 && mode != 1) { + return 0; + } + } + if (key != nullptr || (method.flags & 0x20) != 0) { + if (method.init == nullptr) NotImplemented_nid_no_patch(__func__); + if (!method.init(&context, key, iv, 1)) return 0; + } + context.bufferLength = 0; + context.finalUsed = 0; + context.blockMask = method.blockSize - 1; + return 1; +} + +int SetKeyLength(LibreSslCipherContext& context, std::size_t length) { + const int narrowed = std::bit_cast(static_cast(length)); + if ((context.cipher->flags & 0x80) != 0) return Control(context, 1, narrowed); + if (context.keyLength == narrowed) return 1; + if (narrowed > 0 && (context.cipher->flags & 8) != 0) { + context.keyLength = narrowed; + return 1; + } + return Error(130); +} + +int EncryptBlock(LibreSslCmac& context, unsigned char* output, const unsigned char* input) { + if (context.cipherContext.cipher->cipher == nullptr) NotImplemented_nid_no_patch(__func__); + return context.cipherContext.cipher->cipher(&context.cipherContext, output, input, context.cipherContext.cipher->blockSize); +} + +void DoubleBlock(unsigned char* output, const unsigned char* input, int length) { + unsigned char carry = 0; + for (int index = length; index-- > 0;) { + const auto value = input[index]; + output[index] = static_cast((value << 1) | carry); + carry = value >> 7; + } + output[length - 1] ^= static_cast((length == 16 ? 0x87 : 0x1b) & -static_cast(carry)); +} + +int APS5_VABI AesInit(LibreSslCipherContext* context, const unsigned char* key, const unsigned char*, int encrypt) { + if (key == nullptr || context->cipherData == nullptr) throw std::invalid_argument("CMAC AES: missing key storage"); + if (context->keyLength != 16 && context->keyLength != 24 && context->keyLength != 32) return Error(143); + auto* aes = static_cast(context->cipherData); + mbedtls_aes_init(aes); + const int result = encrypt ? mbedtls_aes_setkey_enc(aes, key, context->keyLength * 8) : mbedtls_aes_setkey_dec(aes, key, context->keyLength * 8); + if (result != 0) throw std::runtime_error("CMAC AES: backend key setup failed"); + return 1; +} + +int APS5_VABI AesCipher(LibreSslCipherContext* context, unsigned char* output, const unsigned char* input, std::size_t length) { + if (context->cipherData == nullptr || (length != 0 && (output == nullptr || input == nullptr))) throw std::invalid_argument("CMAC AES: null data"); + if (length == 0) return 1; + auto* aes = static_cast(context->cipherData); + const int mode = context->encrypt ? MBEDTLS_AES_ENCRYPT : MBEDTLS_AES_DECRYPT; + const auto complete = length - length % 16; + if (complete != 0 && mbedtls_aes_crypt_cbc(aes, mode, complete, context->iv, input, output) != 0) + throw std::runtime_error("CMAC AES: backend encryption failed"); + const auto remaining = length - complete; + if (remaining == 0) return 1; + input += complete; + output += complete; + unsigned char block[16]; + unsigned char nextIv[16]; + if (context->encrypt) { + std::memcpy(block, context->iv, 16); + for (std::size_t index = 0; index < remaining; ++index) block[index] ^= input[index]; + if (mbedtls_aes_crypt_ecb(aes, mode, block, output) != 0) throw std::runtime_error("CMAC AES: backend encryption failed"); + std::memcpy(context->iv, output, 16); + } else { + std::memcpy(nextIv, input, 16); + if (mbedtls_aes_crypt_ecb(aes, mode, input, block) != 0) throw std::runtime_error("CMAC AES: backend decryption failed"); + for (std::size_t index = 0; index < remaining; ++index) output[index] = block[index] ^ context->iv[index]; + std::memcpy(context->iv, nextIv, 16); + } + Cleanse(block, sizeof(block)); + Cleanse(nextIv, sizeof(nextIv)); + return 1; +} + +const LibreSslCipher aes128{419, 16, 16, 16, 0x5002, AesInit, AesCipher, nullptr, sizeof(mbedtls_aes_context), nullptr, nullptr, nullptr, nullptr}; +const LibreSslCipher aes192{423, 16, 24, 16, 0x5002, AesInit, AesCipher, nullptr, sizeof(mbedtls_aes_context), nullptr, nullptr, nullptr, nullptr}; +const LibreSslCipher aes256{427, 16, 32, 16, 0x5002, AesInit, AesCipher, nullptr, sizeof(mbedtls_aes_context), nullptr, nullptr, nullptr, nullptr}; + +} + +extern "C" { + +LibreSslCmac* APS5_VABI CMAC_CTX_new_nid_postfix() { + auto* context = static_cast(malloc_nid_postfix(sizeof(LibreSslCmac))); + if (context != nullptr) { + context->cipherContext = {}; + context->lastLength = -1; + } + return context; +} + +void APS5_VABI CMAC_CTX_free_nid_postfix(LibreSslCmac* context) { + if (context == nullptr) return; + Cleanup(context->cipherContext); + Cleanse(context, sizeof(*context)); + free_nid_postfix(context); +} + +int APS5_VABI CMAC_Init_nid_postfix(LibreSslCmac* context, const void* key, std::size_t keyLength, const LibreSslCipher* cipher, void* engine) { + RequireContext(context); + const unsigned char zero[32]{}; + if (key == nullptr && keyLength == 0 && cipher == nullptr && engine == nullptr) { + if (context->lastLength == -1) return 0; + if (!InitializeCipher(context->cipherContext, nullptr, nullptr, zero, nullptr)) return 0; + std::memset(context->previousBlock, 0, context->cipherContext.cipher->blockSize); + context->lastLength = 0; + return 1; + } + if (cipher != nullptr && !InitializeCipher(context->cipherContext, cipher, nullptr, nullptr, engine)) return 0; + if (key == nullptr) return 1; + if (context->cipherContext.cipher == nullptr) return 0; + if (!SetKeyLength(context->cipherContext, keyLength)) return 0; + if (!InitializeCipher(context->cipherContext, nullptr, static_cast(key), zero, nullptr)) return 0; + if (!EncryptBlock(*context, context->previousBlock, zero)) return 0; + const auto blockSize = context->cipherContext.cipher->blockSize; + DoubleBlock(context->firstSubkey, context->previousBlock, blockSize); + DoubleBlock(context->secondSubkey, context->firstSubkey, blockSize); + Cleanse(context->previousBlock, blockSize); + if (!InitializeCipher(context->cipherContext, nullptr, nullptr, zero, nullptr)) return 0; + context->lastLength = 0; + return 1; +} + +int APS5_VABI CMAC_Update_nid_postfix(LibreSslCmac* context, const void* data, std::size_t length) { + RequireContext(context); + if (context->lastLength == -1) return 0; + if (length == 0) return 1; + if (data == nullptr) throw std::invalid_argument("CMAC_Update: null input"); + const auto* bytes = static_cast(data); + const auto blockSize = context->cipherContext.cipher->blockSize; + if (context->lastLength != 0) { + const auto take = std::min(length, static_cast(blockSize - context->lastLength)); + std::memcpy(context->lastBlock + context->lastLength, bytes, take); + context->lastLength += static_cast(take); + bytes += take; + length -= take; + if (length == 0) return 1; + if (!EncryptBlock(*context, context->previousBlock, context->lastBlock)) return 0; + } + while (length > static_cast(blockSize)) { + if (!EncryptBlock(*context, context->previousBlock, bytes)) return 0; + bytes += blockSize; + length -= blockSize; + } + std::memcpy(context->lastBlock, bytes, length); + context->lastLength = static_cast(length); + return 1; +} + +int APS5_VABI CMAC_Final_nid_postfix(LibreSslCmac* context, unsigned char* output, std::size_t* length) { + RequireContext(context); + if (context->lastLength == -1) return 0; + if (length == nullptr) throw std::invalid_argument("CMAC_Final: null output length"); + const auto blockSize = context->cipherContext.cipher->blockSize; + *length = blockSize; + if (output == nullptr) return 1; + const bool full = context->lastLength == blockSize; + if (!full) { + std::memset(context->lastBlock + context->lastLength, 0, blockSize - context->lastLength); + context->lastBlock[context->lastLength] = 0x80; + } + const auto* subkey = full ? context->firstSubkey : context->secondSubkey; + for (int index = 0; index < blockSize; ++index) output[index] = context->lastBlock[index] ^ subkey[index]; + if (EncryptBlock(*context, output, output)) return 1; + Cleanse(output, blockSize); + return 0; +} + +const LibreSslCipher* APS5_VABI EVP_aes_128_cbc_nid_postfix() { return &aes128; } +const LibreSslCipher* APS5_VABI EVP_aes_192_cbc_nid_postfix() { return &aes192; } +const LibreSslCipher* APS5_VABI EVP_aes_256_cbc_nid_postfix() { return &aes256; } + +} diff --git a/core/libs/prx/libSceLibreSsl/src/Error.cpp b/core/libs/prx/libSceLibreSsl/src/Error.cpp index bad0374dbb..4e3dff58b8 100644 --- a/core/libs/prx/libSceLibreSsl/src/Error.cpp +++ b/core/libs/prx/libSceLibreSsl/src/Error.cpp @@ -1,7 +1,8 @@ -#include "prx/libSceLibreSsl/include/Api.hpp" #include +#include #include #include +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" #include "prx/libSceLibreSsl/include/Error.hpp" #include "prx/libc/include/General.hpp" @@ -10,6 +11,8 @@ constexpr int QueueSize = 16; struct ErrorQueue { std::array codes; + std::array files; + std::array lines; int top; int bottom; }; @@ -25,6 +28,8 @@ std::uint64_t Oldest(bool remove) { if (remove) { queue.bottom = index; queue.codes[index] = 0; + queue.files[index] = nullptr; + queue.lines[index] = 0; } return code; } @@ -36,12 +41,20 @@ void Clear() { extern "C" { -void LibreSslPutError_nid_no_patch(int library, int function, int reason) { +void LibreSslPutError_nid_no_patch(int library, int function, int reason, const char* file, int line) { + const int savedError = errno; 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); + queue.files[queue.top] = file; + queue.lines[queue.top] = line; + errno = savedError; +} + +std::uint64_t LibreSslPeekLastError_nid_no_patch() { + return queue.top == queue.bottom ? 0 : queue.codes[queue.top]; } std::uint64_t APS5_VABI ERR_get_error_nid_postfix() { @@ -57,7 +70,7 @@ void APS5_VABI ERR_clear_error_nid_postfix() { } 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"); + if (threadId != nullptr) NotImplemented_nid_no_patch(__func__); Clear(); } diff --git a/core/libs/prx/libSceLibreSsl/src/Evp.cpp b/core/libs/prx/libSceLibreSsl/src/Evp.cpp new file mode 100644 index 0000000000..a611047347 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/Evp.cpp @@ -0,0 +1,529 @@ +/* $OpenBSD: digest.c,v 1.27 2017/01/29 17:49:23 beck Exp $ */ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ +/* ==================================================================== + * Copyright (c) 1998-2001 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ +/* $OpenBSD: hmac.c,v 1.24 2017/03/03 10:39:07 inoguchi Exp $ */ +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibreSsl/include/Api.hpp" +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +namespace { +static_assert(sizeof(EvpMd) == 120); +static_assert(offsetof(EvpMd, flags) == 16); +static_assert(offsetof(EvpMd, sign) == 64); +static_assert(offsetof(EvpMd, requiredPkeyType) == 80); +static_assert(offsetof(EvpMd, blockSize) == 100); +static_assert(offsetof(EvpMd, control) == 112); +static_assert(sizeof(EvpMdContext) == 48); +static_assert(offsetof(EvpMdContext, update) == 40); +static_assert(sizeof(HmacContext) == 288); +static_assert(offsetof(HmacContext, keyLength) == 152); +static_assert(offsetof(HmacContext, key) == 156); + +constexpr std::uint64_t FlagOneShot = 0x1; +constexpr std::uint64_t FlagCleaned = 0x2; +constexpr std::uint64_t FlagReuse = 0x4; +constexpr std::uint64_t FlagNoInit = 0x100; +constexpr std::size_t MaxDigestSize = 64; +constexpr std::uint64_t PkeyMethodSignature = 0x4; +constexpr std::uint64_t DigestAlgorithmIdAbsent = 0x8; +constexpr std::int32_t PkeyRsa = 6; +constexpr std::int32_t PkeyRsa2 = 19; +constexpr std::int32_t PointerSize = 8; + +int APS5_VABI Md5Init(EvpMdContext* context) { + return MD5_Init_nid_postfix(static_cast(context->mdData)); +} + +int APS5_VABI Md5Update(EvpMdContext* context, const void* data, std::size_t length) { + return MD5_Update_nid_postfix(static_cast(context->mdData), data, length); +} + +int APS5_VABI Md5Final(EvpMdContext* context, unsigned char* digest) { + return MD5_Final_nid_postfix(digest, static_cast(context->mdData)); +} + +int APS5_VABI Sha256Init(EvpMdContext* context) { + return SHA256_Init_nid_postfix(static_cast(context->mdData)); +} + +int APS5_VABI Sha256Update(EvpMdContext* context, const void* data, std::size_t length) { + return SHA256_Update_nid_postfix(static_cast(context->mdData), data, length); +} + +int APS5_VABI Sha256Final(EvpMdContext* context, unsigned char* digest) { + return SHA256_Final_nid_postfix(digest, static_cast(context->mdData)); +} + +int APS5_VABI Sha384Init(EvpMdContext* context) { + return SHA384_Init_nid_postfix(static_cast(context->mdData)); +} + +int APS5_VABI Sha512Init(EvpMdContext* context) { + return SHA512_Init_nid_postfix(static_cast(context->mdData)); +} + +int APS5_VABI Sha512Update(EvpMdContext* context, const void* data, std::size_t length) { + return SHA512_Update_nid_postfix(static_cast(context->mdData), data, length); +} + +int APS5_VABI Sha512Final(EvpMdContext* context, unsigned char* digest) { + return SHA512_Final_nid_postfix(digest, static_cast(context->mdData)); +} + +constexpr EvpMd Md5 { + 4, 8, 16, 0, Md5Init, Md5Update, Md5Final, nullptr, nullptr, nullptr, nullptr, + {PkeyRsa, PkeyRsa2, 0, 0, 0}, 64, PointerSize + 92, nullptr, +}; + +constexpr EvpMd Sha256 { + 672, 668, 32, PkeyMethodSignature | DigestAlgorithmIdAbsent, Sha256Init, Sha256Update, Sha256Final, nullptr, nullptr, nullptr, nullptr, + {PkeyRsa, PkeyRsa2, 0, 0, 0}, 64, PointerSize + 112, nullptr, +}; + +constexpr EvpMd Sha384 { + 673, 669, 48, PkeyMethodSignature | DigestAlgorithmIdAbsent, Sha384Init, Sha512Update, Sha512Final, nullptr, nullptr, nullptr, nullptr, + {PkeyRsa, PkeyRsa2, 0, 0, 0}, 128, PointerSize + 216, nullptr, +}; + +constexpr EvpMd Sha512 { + 674, 670, 64, PkeyMethodSignature | DigestAlgorithmIdAbsent, Sha512Init, Sha512Update, Sha512Final, nullptr, nullptr, nullptr, nullptr, + {PkeyRsa, PkeyRsa2, 0, 0, 0}, 128, PointerSize + 216, nullptr, +}; + +template TContext& RequireContext(TContext* context, const char* function) { + if (!context) throw std::invalid_argument(std::string(function) + ": null context"); + return *context; +} + +void RequireNoEngineOrKey(const EvpMdContext& context, const char* function) { + if (context.engine) NotImplemented_nid_no_patch((std::string(function) + ": ENGINE").c_str()); + if (context.pkeyContext) NotImplemented_nid_no_patch((std::string(function) + ": EVP_PKEY_CTX").c_str()); +} + +void ReleaseData(EvpMdContext& context) { + if (!context.digest || context.digest->contextSize <= 0 || !context.mdData || (context.flags & FlagReuse)) return; + std::memset(context.mdData, 0, static_cast(context.digest->contextSize)); + free_nid_postfix(context.mdData); + context.mdData = nullptr; +} + +int CleanupContext(EvpMdContext& context, const char* function) { + RequireNoEngineOrKey(context, function); + if (context.digest && context.digest->cleanup && !(context.flags & FlagCleaned)) context.digest->cleanup(&context); + ReleaseData(context); + std::memset(&context, 0, sizeof(context)); + return 1; +} + +int DigestInit(EvpMdContext& context, const EvpMd* type, void* engine, const char* function) { + RequireNoEngineOrKey(context, function); + if (engine) NotImplemented_nid_no_patch((std::string(function) + ": ENGINE").c_str()); + context.flags &= ~FlagCleaned; + if (!type) { + if (!context.digest) return 0; + throw std::invalid_argument(std::string(function) + ": null digest type for a context that has one"); + } + if (context.digest != type) { + ReleaseData(context); + if (!(context.flags & FlagNoInit) && type->contextSize) { + if (type->contextSize < 0) throw std::invalid_argument(std::string(function) + ": negative digest context size"); + context.mdData = malloc_nid_postfix(static_cast(type->contextSize)); + if (!context.mdData) { + context.digest = nullptr; + context.update = nullptr; + return 0; + } + context.update = type->update; + } + context.digest = type; + } + if (context.flags & FlagNoInit) return 1; + if (!context.digest->init) throw std::invalid_argument(std::string(function) + ": digest without an init function"); + return context.digest->init(&context); +} + +int DigestUpdate(EvpMdContext& context, const void* data, std::size_t length, const char* function) { + if (!context.update) throw std::invalid_argument(std::string(function) + ": context without an update function"); + return context.update(&context, data, length); +} + +int DigestFinal(EvpMdContext& context, unsigned char* digest, std::uint32_t* size, const char* function) { + if (!context.digest) throw std::invalid_argument(std::string(function) + ": context without a digest"); + const auto* type = context.digest; + if (static_cast(type->mdSize) > MaxDigestSize) return 0; + if (!type->finalize) throw std::invalid_argument(std::string(function) + ": digest without a final function"); + const int result = type->finalize(&context, digest); + if (size) *size = static_cast(type->mdSize); + if (type->cleanup) { + type->cleanup(&context); + context.flags |= FlagCleaned; + } + if (type->contextSize) { + if (!context.mdData || type->contextSize < 0) throw std::invalid_argument(std::string(function) + ": context without digest data"); + std::memset(context.mdData, 0, static_cast(type->contextSize)); + } + return result; +} + +int CopyContext(EvpMdContext& to, const EvpMdContext& from, const char* function) { + if (!from.digest) return 0; + RequireNoEngineOrKey(from, function); + void* reused = nullptr; + if (to.digest == from.digest) { + reused = to.mdData; + to.flags |= FlagReuse; + } + CleanupContext(to, function); + std::memcpy(&to, &from, sizeof(to)); + if (from.mdData && to.digest->contextSize) { + if (to.digest->contextSize < 0) throw std::invalid_argument(std::string(function) + ": negative digest context size"); + const auto size = static_cast(to.digest->contextSize); + to.mdData = reused ? reused : malloc_nid_postfix(size); + if (!to.mdData) { + to.digest = nullptr; + to.update = nullptr; + return 0; + } + std::memmove(to.mdData, from.mdData, size); + } + to.update = from.update; + if (to.digest->copy) return to.digest->copy(&to, &from); + return 1; +} + +void HmacContextInit(HmacContext& context) { + std::memset(&context.innerContext, 0, sizeof(context.innerContext)); + std::memset(&context.outerContext, 0, sizeof(context.outerContext)); + std::memset(&context.mdContext, 0, sizeof(context.mdContext)); + context.md = nullptr; +} + +void HmacContextCleanup(HmacContext& context, const char* function) { + CleanupContext(context.innerContext, function); + CleanupContext(context.outerContext, function); + CleanupContext(context.mdContext, function); + std::memset(&context, 0, sizeof(context)); +} + +int HmacInit(HmacContext& context, const void* key, int length, const EvpMd* md, void* engine, const char* function) { + if (md && md != context.md && (!key || length < 0)) return 0; + bool reset = false; + if (md) { + reset = true; + context.md = md; + } else if (context.md) { + md = context.md; + } else { + return 0; + } + if (key) { + reset = true; + const int block = md->blockSize; + if (static_cast(block) > sizeof(context.key)) return 0; + if (block < length) { + if (!DigestInit(context.mdContext, md, engine, function)) return 0; + if (!DigestUpdate(context.mdContext, key, static_cast(length), function)) return 0; + if (!DigestFinal(context.mdContext, context.key, &context.keyLength, function)) return 0; + } else { + if (length < 0 || static_cast(length) > sizeof(context.key)) return 0; + std::memcpy(context.key, key, static_cast(length)); + context.keyLength = static_cast(length); + } + if (context.keyLength != HmacMaxBlock) std::memset(context.key + context.keyLength, 0, HmacMaxBlock - context.keyLength); + } + if (reset) { + unsigned char pad[HmacMaxBlock]; + const auto block = static_cast(md->blockSize); + for (std::size_t i = 0; i < HmacMaxBlock; ++i) pad[i] = 0x36 ^ context.key[i]; + if (!DigestInit(context.innerContext, md, engine, function)) return 0; + if (!DigestUpdate(context.innerContext, pad, block, function)) return 0; + for (std::size_t i = 0; i < HmacMaxBlock; ++i) pad[i] = 0x5c ^ context.key[i]; + if (!DigestInit(context.outerContext, md, engine, function)) return 0; + if (!DigestUpdate(context.outerContext, pad, block, function)) return 0; + } + return CopyContext(context.mdContext, context.innerContext, function) ? 1 : 0; +} + +int HmacUpdate(HmacContext& context, const unsigned char* data, std::size_t length, const char* function) { + if (!context.md) return 0; + return DigestUpdate(context.mdContext, data, length, function); +} + +int HmacFinal(HmacContext& context, unsigned char* digest, std::uint32_t* length, const char* function) { + if (!context.md) return 0; + unsigned char inner[MaxDigestSize]; + std::uint32_t innerLength = 0; + if (!DigestFinal(context.mdContext, inner, &innerLength, function)) return 0; + if (!CopyContext(context.mdContext, context.outerContext, function)) return 0; + if (!DigestUpdate(context.mdContext, inner, innerLength, function)) return 0; + if (!DigestFinal(context.mdContext, digest, length, function)) return 0; + return 1; +} +} + +extern "C" { + +const EvpMd* APS5_VABI EVP_md5_nid_postfix(void) { + return &Md5; +} + +const EvpMd* APS5_VABI EVP_sha256_nid_postfix(void) { + return &Sha256; +} + +const EvpMd* APS5_VABI EVP_sha384_nid_postfix(void) { + return &Sha384; +} + +const EvpMd* APS5_VABI EVP_sha512_nid_postfix(void) { + return &Sha512; +} + +int APS5_VABI EVP_MD_size_nid_postfix(const EvpMd* md) { + return md ? md->mdSize : -1; +} + +void APS5_VABI EVP_MD_CTX_init_nid_postfix(EvpMdContext* context) { + std::memset(&RequireContext(context, __func__), 0, sizeof(EvpMdContext)); +} + +EvpMdContext* APS5_VABI EVP_MD_CTX_create_nid_postfix(void) { + return static_cast(calloc_nid_postfix(1, sizeof(EvpMdContext))); +} + +int APS5_VABI EVP_MD_CTX_cleanup_nid_postfix(EvpMdContext* context) { + return CleanupContext(RequireContext(context, __func__), __func__); +} + +void APS5_VABI EVP_MD_CTX_destroy_nid_postfix(EvpMdContext* context) { + if (!context) return; + CleanupContext(*context, __func__); + free_nid_postfix(context); +} + +int APS5_VABI EVP_DigestInit_ex_nid_postfix(EvpMdContext* context, const EvpMd* type, void* engine) { + return DigestInit(RequireContext(context, __func__), type, engine, __func__); +} + +int APS5_VABI EVP_DigestUpdate_nid_postfix(EvpMdContext* context, const void* data, std::size_t length) { + return DigestUpdate(RequireContext(context, __func__), data, length, __func__); +} + +int APS5_VABI EVP_DigestFinal_ex_nid_postfix(EvpMdContext* context, unsigned char* digest, std::uint32_t* size) { + return DigestFinal(RequireContext(context, __func__), digest, size, __func__); +} + +int APS5_VABI EVP_Digest_nid_postfix(const void* data, std::size_t length, unsigned char* digest, std::uint32_t* size, const EvpMd* type, void* engine) { + EvpMdContext context {}; + context.flags = FlagOneShot; + const int result = DigestInit(context, type, engine, __func__) && DigestUpdate(context, data, length, __func__) && DigestFinal(context, digest, size, __func__); + CleanupContext(context, __func__); + return result; +} + +void APS5_VABI HMAC_CTX_init_nid_postfix(HmacContext* context) { + HmacContextInit(RequireContext(context, __func__)); +} + +void APS5_VABI HMAC_CTX_cleanup_nid_postfix(HmacContext* context) { + HmacContextCleanup(RequireContext(context, __func__), __func__); +} + +int APS5_VABI HMAC_Init_ex_nid_postfix(HmacContext* context, const void* key, int length, const EvpMd* md, void* engine) { + return HmacInit(RequireContext(context, __func__), key, length, md, engine, __func__); +} + +int APS5_VABI HMAC_Init_nid_postfix(HmacContext* context, const void* key, int length, const EvpMd* md) { + auto& c = RequireContext(context, __func__); + if (key && md) HmacContextInit(c); + return HmacInit(c, key, length, md, nullptr, __func__); +} + +int APS5_VABI HMAC_Update_nid_postfix(HmacContext* context, const unsigned char* data, std::size_t length) { + return HmacUpdate(RequireContext(context, __func__), data, length, __func__); +} + +int APS5_VABI HMAC_Final_nid_postfix(HmacContext* context, unsigned char* digest, std::uint32_t* length) { + return HmacFinal(RequireContext(context, __func__), digest, length, __func__); +} + +unsigned char* APS5_VABI HMAC_nid_postfix(const EvpMd* md, const void* key, int keyLength, const unsigned char* data, std::size_t length, unsigned char* digest, std::uint32_t* digestLength) { + static unsigned char buffer[MaxDigestSize]; + if (!digest) digest = buffer; + HmacContext context; + HmacContextInit(context); + const bool ok = HMAC_Init_nid_postfix(&context, key, keyLength, md) && HmacUpdate(context, data, length, __func__) && HmacFinal(context, digest, digestLength, __func__); + HmacContextCleanup(context, __func__); + return ok ? digest : nullptr; +} + +} diff --git a/core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp b/core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp new file mode 100644 index 0000000000..53046c487c --- /dev/null +++ b/core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp @@ -0,0 +1,38 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include "prx/libc/include/General.hpp" + +static_assert(sizeof(LibreSslEvpCipherCtx) == 168); +static_assert(offsetof(LibreSslEvpCipherCtx, num) == 88); +static_assert(offsetof(LibreSslEvpCipherCtx, flags) == 112); +static_assert(offsetof(LibreSslEvpCipherCtx, final) == 136); + +namespace { + +constexpr std::uint64_t CipherFlagNoPadding = 0x100; + +} + +extern "C" { +void APS5_VABI EVP_CIPHER_CTX_init_nid_postfix(LibreSslEvpCipherCtx* ctx) { + if (!ctx) throw std::invalid_argument(std::string(__func__) + ": null context"); + std::memset(ctx, 0, sizeof(LibreSslEvpCipherCtx)); +} + +LibreSslEvpCipherCtx* APS5_VABI EVP_CIPHER_CTX_new_nid_postfix(void) { + return static_cast(calloc_nid_postfix(1, sizeof(LibreSslEvpCipherCtx))); +} + +int APS5_VABI EVP_CIPHER_CTX_set_padding_nid_postfix(LibreSslEvpCipherCtx* ctx, int pad) { + if (!ctx) throw std::invalid_argument(std::string(__func__) + ": null context"); + if (pad) ctx->flags &= ~CipherFlagNoPadding; + else ctx->flags |= CipherFlagNoPadding; + return 1; +} + +} diff --git a/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp b/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp index 06c5ffe3a4..df0619e169 100644 --- a/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp +++ b/core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp @@ -685,10 +685,6 @@ int APS5_VABI ASN1_TYPE_free_nid_postfix() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI ASN1_TIME_diff_nid_postfix() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI ECDSA_SIG_set0_nid_postfix() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt index 79c5a1a91d..d6060cd2c0 100644 --- a/core/libs/prx/libSceLibreSsl3/CMakeLists.txt +++ b/core/libs/prx/libSceLibreSsl3/CMakeLists.txt @@ -12,8 +12,19 @@ if(WIN32) target_sources(libSceLibreSsl3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/ParentExports.cpp) endif() target_sources(libSceLibreSsl3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/PkeyGetters.cpp) +target_sources(libSceLibreSsl3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp) if(BUILD_TESTING) + add_test_executable(guest_libressl3_time_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Time.cpp) + target_include_directories(guest_libressl3_time_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_time_tests PRIVATE libSceLibreSsl3) + configure_windows_unwind(guest_libressl3_time_tests) + add_test(NAME guest_libressl3_time COMMAND guest_libressl3_time_tests) + set_tests_properties(guest_libressl3_time PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_libressl3_time PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_libressl3_pkey_getters_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/PkeyGetters.cpp) target_include_directories(guest_libressl3_pkey_getters_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libressl3_pkey_getters_tests PRIVATE libSceLibreSsl3 libc) @@ -87,8 +98,14 @@ if(BUILD_TESTING) configure_windows_unwind(guest_libressl3_key_reference_tests) add_test(NAME guest_libressl3_key_reference COMMAND guest_libressl3_key_reference_tests) set_tests_properties(guest_libressl3_key_reference PROPERTIES TIMEOUT 20) + add_test_executable(guest_libressl3_version_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/OpenSslVersion.cpp) + target_include_directories(guest_libressl3_version_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_libressl3_version_tests PRIVATE libSceLibreSsl3 libc) + configure_windows_unwind(guest_libressl3_version_tests) + add_test(NAME guest_libressl3_version COMMAND guest_libressl3_version_tests) + set_tests_properties(guest_libressl3_version PROPERTIES TIMEOUT 30) if(WIN32) - set_tests_properties(guest_crypto_memcmp guest_libressl3_digest guest_libressl3_asn1 guest_libressl3_key_reference PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_crypto_memcmp guest_libressl3_digest guest_libressl3_asn1 guest_libressl3_key_reference guest_libressl3_version 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 8c74e9e41f..d327cfb245 100644 --- a/core/libs/prx/libSceLibreSsl3/Export.cpp +++ b/core/libs/prx/libSceLibreSsl3/Export.cpp @@ -2,6 +2,7 @@ #include #include #include "SceTypes.hpp" +#include "prx/libSceLibreSsl/ErrorQueue.hpp" #include "prx/libc/include/General.hpp" extern "C" { @@ -61,14 +62,11 @@ int APS5_VABI DSA_get0_pqg_nid_postfix(void) { return 0; } -int APS5_VABI ERR_load_crypto_strings_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI ERR_load_crypto_strings_nid_postfix() { } -int APS5_VABI ERR_peek_last_error_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint64_t APS5_VABI ERR_peek_last_error_nid_postfix() { + return LibreSslPeekLastError_nid_no_patch(); } int APS5_VABI ERR_print_errors_cb_nid_postfix(void) { @@ -221,9 +219,16 @@ int APS5_VABI OCSP_resp_find_status_nid_postfix(void) { return 0; } -int APS5_VABI OpenSSL_version_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +const char* APS5_VABI OpenSSL_version_nid_postfix(int type) { + switch (type) { + case 0: return "LibreSSL 4.0.0"; + case 1: return "compiler: information not available"; + case 2: return "built on: date not available"; + case 3: return "platform: information not available"; + case 4: return "OPENSSLDIR: \"/etc/ssl\""; + case 5: return "ENGINESDIR: N/A"; + default: return "not available"; + } } int APS5_VABI PKCS7_free_nid_postfix(void) { diff --git a/core/libs/prx/libSceLibreSsl3/include/Api.hpp b/core/libs/prx/libSceLibreSsl3/include/Api.hpp index b08676acdc..5aa440134d 100644 --- a/core/libs/prx/libSceLibreSsl3/include/Api.hpp +++ b/core/libs/prx/libSceLibreSsl3/include/Api.hpp @@ -18,8 +18,8 @@ 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(LibreSsl3EcKey* key); -int APS5_VABI ERR_load_crypto_strings_nid_postfix(void); -int APS5_VABI ERR_peek_last_error_nid_postfix(void); +void APS5_VABI ERR_load_crypto_strings_nid_postfix(); +std::uint64_t APS5_VABI ERR_peek_last_error_nid_postfix(); 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); @@ -54,7 +54,7 @@ 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); +const char* APS5_VABI OpenSSL_version_nid_postfix(int type); 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); @@ -270,7 +270,7 @@ int APS5_VABI tls_config_parse_protocols_nid_postfix(); int APS5_VABI SSL_CTX_set_post_handshake_auth_nid_postfix(); int APS5_VABI tls_config_insecure_noverifyname_nid_postfix(); int APS5_VABI ASN1_TYPE_free_nid_postfix(); -int APS5_VABI ASN1_TIME_diff_nid_postfix(); +int APS5_VABI ASN1_TIME_diff_nid_postfix(int* days, int* seconds, const Asn1String* from, const Asn1String* to); int APS5_VABI ECDSA_SIG_set0_nid_postfix(); int APS5_VABI OBJ_obj2txt_nid_postfix(); int APS5_VABI SSL_set_quic_transport_params_nid_postfix(); diff --git a/core/libs/prx/libSceLibreSsl3/src/Time.cpp b/core/libs/prx/libSceLibreSsl3/src/Time.cpp new file mode 100644 index 0000000000..4d6a8e0cf8 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/src/Time.cpp @@ -0,0 +1,51 @@ +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" + +#include +#include +#include + +namespace { + +static_assert(sizeof(Asn1String) == 24); +static_assert(offsetof(Asn1String, data) == 8); + +bool ReadTime(const Asn1String* value, std::int64_t& seconds) { + if (!value) { + seconds = std::time(nullptr); + return seconds >= -62167219200LL && seconds <= 253402300799LL; + } + if (!value->data || (value->length != 13 && value->length != 15)) return false; + const auto* data = value->data; + if (data[value->length - 1] != 'Z') return false; + for (int i = 0; i < value->length - 1; ++i) + if (data[i] < '0' || data[i] > '9') return false; + const int yearDigits = value->length - 11; + int year = 0; + for (int i = 0; i < yearDigits; ++i) year = year * 10 + data[i] - '0'; + if (yearDigits == 2) year += year < 50 ? 2000 : 1900; + unsigned fields[5]; + for (int i = 0; i < 5; ++i) { + const int position = yearDigits + i * 2; + fields[i] = (data[position] - '0') * 10 + data[position + 1] - '0'; + } + const std::chrono::year_month_day date{std::chrono::year{year}, + std::chrono::month{fields[0]}, std::chrono::day{fields[1]}}; + if (!date.ok() || fields[2] > 23 || fields[3] > 59 || fields[4] > 59) return false; + seconds = static_cast(std::chrono::sys_days{date}.time_since_epoch().count()) * 86400 + + fields[2] * 3600 + fields[3] * 60 + fields[4]; + return true; +} + +} + +extern "C" int APS5_VABI ASN1_TIME_diff_nid_postfix(int* days, int* seconds, + const Asn1String* from, const Asn1String* to) { + std::int64_t start; + std::int64_t end; + if (!ReadTime(from, start) || !ReadTime(to, end)) return 0; + if (!days || !seconds) throw std::invalid_argument("ASN1_TIME_diff: null output"); + const auto difference = end - start; + *seconds = static_cast(difference % 86400); + *days = static_cast(difference / 86400); + return 1; +} diff --git a/core/libs/prx/libSceLibreSsl3/tests/OpenSslVersion.cpp b/core/libs/prx/libSceLibreSsl3/tests/OpenSslVersion.cpp new file mode 100644 index 0000000000..721f50eb9c --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/tests/OpenSslVersion.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include "prx/libSceLibreSsl3/include/Api.hpp" + +#ifdef LIBRESSL3_STANDALONE_TEST +extern "C" [[noreturn]] void NotImplemented_nid_no_patch(const char* function) { + throw std::runtime_error(function); +} +#endif + +int main() { + constexpr std::array expected { + "LibreSSL 4.0.0", + "compiler: information not available", + "built on: date not available", + "platform: information not available", + "OPENSSLDIR: \"/etc/ssl\"", + "ENGINESDIR: N/A", + }; + + for (int type = 0; type < static_cast(expected.size()); ++type) { + const char* first = OpenSSL_version_nid_postfix(type); + const char* second = OpenSSL_version_nid_postfix(type); + if (!first || std::strcmp(first, expected[type]) != 0 || first != second) return EXIT_FAILURE; + } + + constexpr std::array unsupported {-1, 6, 42, 0x7fffffff}; + for (int type : unsupported) { + const char* value = OpenSSL_version_nid_postfix(type); + if (!value || std::strcmp(value, "not available") != 0) return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/core/libs/prx/libSceLibreSsl3/tests/Time.cpp b/core/libs/prx/libSceLibreSsl3/tests/Time.cpp new file mode 100644 index 0000000000..4214ffefe9 --- /dev/null +++ b/core/libs/prx/libSceLibreSsl3/tests/Time.cpp @@ -0,0 +1,128 @@ +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL3 time check failed at line %u\n", location.line()); + std::abort(); +} + +Asn1String Time(std::string_view text, int type = 24) { + return {static_cast(text.size()), type, + reinterpret_cast(const_cast(text.data())), 0}; +} + +void Check(std::string_view from, std::string_view to, int days, int seconds) { + auto start = Time(from); + auto end = Time(to); + std::array output{17, 18, 19, 20}; + Require(ASN1_TIME_diff_nid_postfix(&output[1], &output[2], &start, &end) == 1); + Require(output == std::array{17, days, seconds, 20}); + Require(ASN1_TIME_diff_nid_postfix(&output[1], &output[2], &end, &start) == 1); + Require(output == std::array{17, -days, -seconds, 20}); + Require(ASN1_TIME_diff_nid_postfix(&output[1], &output[1], &start, &end) == 1); + Require(output[1] == days); +} + +void Invalid(const Asn1String& invalid) { + auto valid = Time("20000101000000Z"); + int days = 123; + int seconds = 456; + Require(ASN1_TIME_diff_nid_postfix(&days, &seconds, &invalid, &valid) == 0); + Require(days == 123 && seconds == 456); + Require(ASN1_TIME_diff_nid_postfix(&days, &seconds, &valid, &invalid) == 0); + Require(days == 123 && seconds == 456); +} + +void CurrentTime() { + auto epoch = Time("19700101000000Z"); + int days; + int seconds; + auto before = std::time(nullptr); + Require(ASN1_TIME_diff_nid_postfix(&days, &seconds, &epoch, nullptr) == 1); + auto after = std::time(nullptr); + const std::int64_t forward = static_cast(days) * 86400 + seconds; + Require(forward >= before && forward <= after); + before = std::time(nullptr); + Require(ASN1_TIME_diff_nid_postfix(&days, &seconds, nullptr, &epoch) == 1); + after = std::time(nullptr); + const std::int64_t backward = static_cast(days) * 86400 + seconds; + Require(-backward >= before && -backward <= after); + before = std::time(nullptr); + Require(ASN1_TIME_diff_nid_postfix(&days, &seconds, nullptr, nullptr) == 1); + after = std::time(nullptr); + const std::int64_t elapsed = static_cast(days) * 86400 + seconds; + Require(elapsed >= 0 && elapsed <= after - before); +} + +} + +int main() { + Check("19700101000000Z", "19700101000000Z", 0, 0); + Check("19700101235959Z", "19700102000000Z", 0, 1); + Check("19991231235959Z", "20000102000001Z", 1, 2); + Check("19700101000000Z", "19700101235959Z", 0, 86399); + Check("00000101000000Z", "99991231235959Z", 3652424, 86399); + Check("00000228000000Z", "00000301000000Z", 2, 0); + Check("500101000000Z", "19500101000000Z", 0, 0); + Check("491231235959Z", "20500101000000Z", 0, 1); + Check("000229000000Z", "20000301000000Z", 1, 0); + for (int year = 0; year < 10000; ++year) { + char from[32]; + char to[32]; + std::snprintf(from, sizeof(from), "%04d0228000000Z", year); + std::snprintf(to, sizeof(to), "%04d0301000000Z", year); + const bool leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + Check(from, to, leap ? 2 : 1, 0); + } + for (const auto text : {"", "200001010000Z", "20000101000000", "20000101000000z", + "20000101000000+0000", "20000101000000.0Z", "20000101000000ZZ", + "20000001000000Z", "20001301000000Z", "20000100000000Z", "20000132000000Z", + "20000431000000Z", "19000229000000Z", "21000229000000Z", "20010229000000Z", + "20000101240000Z", "20000101006000Z", "20000101000060Z", "200001010000/0Z"}) + Invalid(Time(text)); + std::array bytes{}; + std::memcpy(bytes.data(), "20000101000000Z", bytes.size()); + Asn1String bounded{15, -100, bytes.data(), -1}; + int days; + int seconds; + auto same = Time("000101000000Z", 99); + Require(ASN1_TIME_diff_nid_postfix(&days, &seconds, &bounded, &same) == 1); + Require(days == 0 && seconds == 0); + for (std::size_t i = 0; i < bytes.size(); ++i) { + const auto saved = bytes[i]; + for (const auto replacement : {0, 0x80, 0xff}) { + bytes[i] = static_cast(replacement); + Invalid(bounded); + } + bytes[i] = saved; + } + bounded.length = -1; + Invalid(bounded); + bounded.length = 15; + bounded.data = nullptr; + Invalid(bounded); + for (bool missingDays : {false, true}) { + bool threw = false; + days = 123; + seconds = 456; + try { + ASN1_TIME_diff_nid_postfix(missingDays ? nullptr : &days, + missingDays ? &seconds : nullptr, &same, &same); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw && days == 123 && seconds == 456); + } + CurrentTime(); +} diff --git a/core/libs/prx/libSceLoginService/Export.cpp b/core/libs/prx/libSceLoginService/Export.cpp index e3a24fd796..b37bf2c7fe 100644 --- a/core/libs/prx/libSceLoginService/Export.cpp +++ b/core/libs/prx/libSceLoginService/Export.cpp @@ -1,20 +1,37 @@ #include "prx/libSceLoginService/include/Api.hpp" +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int SCE_LOGIN_SERVICE_ERROR_NOT_INITIALIZED = static_cast(0x813a0001u); +constexpr int SCE_LOGIN_SERVICE_ERROR_INVALID_ARGUMENT = static_cast(0x813a0003u); + +std::atomic g_initialized{false}; + +} + extern "C" { int APS5_VABI sceLoginServiceInitialize(void) { + g_initialized.store(true); return 0; } int APS5_VABI sceLoginServiceTerminate(void) { + g_initialized.store(false); return 0; } int APS5_VABI sceLoginServiceRequestDevices(const LoginServiceDeviceRequest* param) { - (void)param; + if (param == nullptr) { + return SCE_LOGIN_SERVICE_ERROR_INVALID_ARGUMENT; + } + if (!g_initialized.load()) { + return SCE_LOGIN_SERVICE_ERROR_NOT_INITIALIZED; + } NotImplemented_nid_no_patch(__func__); return 0; } diff --git a/core/libs/prx/libSceMat/Export.cpp b/core/libs/prx/libSceMat/Export.cpp index fb34206c21..242ea1c1e5 100644 --- a/core/libs/prx/libSceMat/Export.cpp +++ b/core/libs/prx/libSceMat/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceMat/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,6 +6,264 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceMatMemoryPoolReserve(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatUninitialize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatTagVirtualMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMspaceCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatUnregisterResource(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMprotect(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMapDirectMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatInitialize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatNewFrame(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatRegisterResourceType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatFree(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatSetUserCallstackCapture(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMemoryPoolMove(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatRegisterGroup(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMemoryPoolExpand(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMemoryPoolBatch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatAllocPoolMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatAgcUnregisterOwnerAndResources(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatReleasePhysicalMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMemoryPoolDecommit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatSetPrtAperture(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatFreePoolMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatUnmapMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMspaceDestroy(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatAlloc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatReallocBegin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatReallocEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatAgcRegisterResource(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatWriteBookmark(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatBatchMap(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatTagPool(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatAgcRegisterOwner(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatUnregisterAllResourcesForOwner(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMtypeprotect(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatSetThreadInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatPushMarker(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatUnregisterOwnerAndResources(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatAgcUnregisterResource(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatRegisterOwner(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatAgcUnregisterAllResourcesForOwner(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatPopMarker(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatPushMarkerStatic(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatUpdateModuleList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatResizeCommsBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatReallocPoolMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatMemoryPoolCommit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatRegisterResource(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatAllocPhysicalMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatReserveVirtualRange(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatTagAllocation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatFreeRange(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceMatCalloc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceMat/include/Api.hpp b/core/libs/prx/libSceMat/include/Api.hpp new file mode 100644 index 0000000000..36ccb68e48 --- /dev/null +++ b/core/libs/prx/libSceMat/include/Api.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceMatMemoryPoolReserve(void); +int APS5_VABI sceMatUninitialize(void); +int APS5_VABI sceMatTagVirtualMemory(void); +int APS5_VABI sceMatMspaceCreate(void); +int APS5_VABI sceMatUnregisterResource(void); +int APS5_VABI sceMatMprotect(void); +int APS5_VABI sceMatMapDirectMemory(void); +int APS5_VABI sceMatInitialize(void); +int APS5_VABI sceMatNewFrame(void); +int APS5_VABI sceMatRegisterResourceType(void); +int APS5_VABI sceMatFree(void); +int APS5_VABI sceMatSetUserCallstackCapture(void); +int APS5_VABI sceMatMemoryPoolMove(void); +int APS5_VABI sceMatRegisterGroup(void); +int APS5_VABI sceMatMemoryPoolExpand(void); +int APS5_VABI sceMatMemoryPoolBatch(void); +int APS5_VABI sceMatAllocPoolMemory(void); +int APS5_VABI sceMatAgcUnregisterOwnerAndResources(void); +int APS5_VABI sceMatReleasePhysicalMemory(void); +int APS5_VABI sceMatMemoryPoolDecommit(void); +int APS5_VABI sceMatSetPrtAperture(void); +int APS5_VABI sceMatFreePoolMemory(void); +int APS5_VABI sceMatUnmapMemory(void); +int APS5_VABI sceMatMspaceDestroy(void); +int APS5_VABI sceMatAlloc(void); +int APS5_VABI sceMatReallocBegin(void); +int APS5_VABI sceMatReallocEnd(void); +int APS5_VABI sceMatAgcRegisterResource(void); +int APS5_VABI sceMatWriteBookmark(void); +int APS5_VABI sceMatBatchMap(void); +int APS5_VABI sceMatTagPool(void); +int APS5_VABI sceMatAgcRegisterOwner(void); +int APS5_VABI sceMatUnregisterAllResourcesForOwner(void); +int APS5_VABI sceMatMtypeprotect(void); +int APS5_VABI sceMatSetThreadInfo(void); +int APS5_VABI sceMatPushMarker(void); +int APS5_VABI sceMatUnregisterOwnerAndResources(void); +int APS5_VABI sceMatAgcUnregisterResource(void); +int APS5_VABI sceMatRegisterOwner(void); +int APS5_VABI sceMatAgcUnregisterAllResourcesForOwner(void); +int APS5_VABI sceMatPopMarker(void); +int APS5_VABI sceMatPushMarkerStatic(void); +int APS5_VABI sceMatUpdateModuleList(void); +int APS5_VABI sceMatResizeCommsBuffer(void); +int APS5_VABI sceMatReallocPoolMemory(void); +int APS5_VABI sceMatMemoryPoolCommit(void); +int APS5_VABI sceMatRegisterResource(void); +int APS5_VABI sceMatAllocPhysicalMemory(void); +int APS5_VABI sceMatReserveVirtualRange(void); +int APS5_VABI sceMatTagAllocation(void); +int APS5_VABI sceMatFreeRange(void); +int APS5_VABI sceMatCalloc(void); + +} diff --git a/core/libs/prx/libSceMat/libSceMat.hpp b/core/libs/prx/libSceMat/libSceMat.hpp index 6f70f09bee..7dc1ae9a59 100644 --- a/core/libs/prx/libSceMat/libSceMat.hpp +++ b/core/libs/prx/libSceMat/libSceMat.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libSceMat/include/Api.hpp" diff --git a/core/libs/prx/libSceMbus/PendingExports.cpp b/core/libs/prx/libSceMbus/PendingExports.cpp index c696940b17..f2310b6e67 100644 --- a/core/libs/prx/libSceMbus/PendingExports.cpp +++ b/core/libs/prx/libSceMbus/PendingExports.cpp @@ -335,7 +335,7 @@ int APS5_VABI sceMbusEventBusStatusChangeUnsubscribe(std::uint64_t unusedSlot, s } void APS5_VABI sceMbusEventCallbackFuncsInit_(MbusEventCallbacks* callbacks, std::uint32_t size) { - if (size != 0) std::memset(callbacks, 0, size); + if (callbacks != nullptr) std::memset(callbacks, 0, size); } int APS5_VABI sceMbusEventCreate_(std::uint32_t flags, MbusEvent* buffer, std::uint32_t capacity, const MbusEventCallbacks* callbacks, void* userData) { diff --git a/core/libs/prx/libSceMouse/CMakeLists.txt b/core/libs/prx/libSceMouse/CMakeLists.txt index cf563cd6a7..9d75d16ebd 100644 --- a/core/libs/prx/libSceMouse/CMakeLists.txt +++ b/core/libs/prx/libSceMouse/CMakeLists.txt @@ -7,6 +7,18 @@ target_include_directories(libSceMouse PRIVATE ${LIBS_INCLUDE_DIR}) set_target_properties(libSceMouse PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(libSceMouse) +if(BUILD_TESTING) + add_test_executable(mouse_absolute_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/MouseAbsolute.cpp) + target_include_directories(mouse_absolute_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(mouse_absolute_tests PRIVATE libSceMouse libkernel libc ${CMAKE_DL_LIBS}) + configure_windows_unwind(mouse_absolute_tests) + add_test(NAME mouse_absolute_position COMMAND mouse_absolute_tests) + if(WIN32) + set_tests_properties(mouse_absolute_position PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test_executable(mouse_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Mouse.cpp diff --git a/core/libs/prx/libSceMouse/Export.cpp b/core/libs/prx/libSceMouse/Export.cpp index e7a4202cef..7fc6bae2e4 100644 --- a/core/libs/prx/libSceMouse/Export.cpp +++ b/core/libs/prx/libSceMouse/Export.cpp @@ -82,19 +82,16 @@ int APS5_VABI sceMouseSetProcessPrivilege() { return 0; } -int APS5_VABI sceMouseReadAbsolutePosition() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num) { + return Mouse::ReadAbsolutePosition(handle, data, num); } -int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode) { + return Mouse::SetAbsoluteHighPrecisionMode(handle, mode); } -int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size) { + return Mouse::SetAbsoluteVirtualScreenSize(handle, size); } } diff --git a/core/libs/prx/libSceMouse/include/Api.hpp b/core/libs/prx/libSceMouse/include/Api.hpp index 1e30e9a3fc..6ed1540333 100644 --- a/core/libs/prx/libSceMouse/include/Api.hpp +++ b/core/libs/prx/libSceMouse/include/Api.hpp @@ -1,6 +1,7 @@ #pragma once #include "SceTypes.hpp" +#include "prx/libSceMouse/include/mouse_structs.h" extern "C" { @@ -19,8 +20,8 @@ int APS5_VABI sceMouseMbusInit(); int APS5_VABI sceMouseSetHandType(); int APS5_VABI sceMouseSetPointerSpeed(); int APS5_VABI sceMouseSetProcessPrivilege(); -int APS5_VABI sceMouseReadAbsolutePosition(); -int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode(); -int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize(); +int APS5_VABI sceMouseReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num); +int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode); +int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size); } diff --git a/core/libs/prx/libSceMouse/include/MouseState.hpp b/core/libs/prx/libSceMouse/include/MouseState.hpp index 390cf33968..b6a161f68d 100644 --- a/core/libs/prx/libSceMouse/include/MouseState.hpp +++ b/core/libs/prx/libSceMouse/include/MouseState.hpp @@ -6,6 +6,8 @@ struct MouseInputEvent { std::int32_t x = 0; std::int32_t y = 0; + std::int32_t countsX = 0; + std::int32_t countsY = 0; std::int32_t wheel = 0; std::int32_t tilt = 0; std::uint32_t button = 0; @@ -22,6 +24,9 @@ int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam int Close(std::int32_t handle); int Read(std::int32_t handle, MouseData* data, std::int32_t num); int ResetAbsolutePosition(std::int32_t handle, std::uint16_t x, std::uint16_t y); +int SetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size); +int SetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode); +int ReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num); void Publish(const MouseInputEvent& event); } diff --git a/core/libs/prx/libSceMouse/include/mouse_structs.h b/core/libs/prx/libSceMouse/include/mouse_structs.h index 30c7d3a8f9..a1da9bb48d 100644 --- a/core/libs/prx/libSceMouse/include/mouse_structs.h +++ b/core/libs/prx/libSceMouse/include/mouse_structs.h @@ -10,6 +10,18 @@ struct MouseOpenParam { std::uint8_t reserved[7]; }; +struct MouseAbsoluteData { + std::uint64_t timestamp; + std::uint8_t connected; + std::uint8_t padding[3]; + std::uint32_t buttons; + std::uint16_t x; + std::uint16_t y; + std::int32_t wheel; + std::int32_t tilt; + std::uint8_t reserved[12]; +}; + constexpr int MOUSE_OK = 0; constexpr int MOUSE_ERROR_INVALID_ARG = static_cast(0x80df0001u); constexpr int MOUSE_ERROR_INVALID_HANDLE = static_cast(0x80df0003u); @@ -20,6 +32,13 @@ constexpr int MOUSE_HANDLE = 1; constexpr std::uint8_t MOUSE_OPEN_PARAM_MERGED = 1; static_assert(sizeof(MouseOpenParam) == 8); +static_assert(sizeof(MouseAbsoluteData) == 40); +static_assert(offsetof(MouseAbsoluteData, connected) == 8); +static_assert(offsetof(MouseAbsoluteData, buttons) == 12); +static_assert(offsetof(MouseAbsoluteData, x) == 16); +static_assert(offsetof(MouseAbsoluteData, y) == 18); +static_assert(offsetof(MouseAbsoluteData, wheel) == 20); +static_assert(offsetof(MouseAbsoluteData, tilt) == 24); static_assert(sizeof(MouseData) == 40); static_assert(alignof(MouseData) == 8); static_assert(offsetof(MouseData, timestamp) == 0); diff --git a/core/libs/prx/libSceMouse/src/mouse_impl.cpp b/core/libs/prx/libSceMouse/src/mouse_impl.cpp index b4df78375a..ac1b19e9d8 100644 --- a/core/libs/prx/libSceMouse/src/mouse_impl.cpp +++ b/core/libs/prx/libSceMouse/src/mouse_impl.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include namespace { @@ -14,6 +16,74 @@ bool initialized = false; bool opened = false; bool connected = true; std::uint32_t buttons = 0; +constexpr std::array SCREEN_WIDTHS{1280, 1920, 2560, 3840}; +constexpr std::array SCREEN_HEIGHTS{720, 1080, 1440, 2160}; +constexpr std::array CURVE_SPEEDS{0.0f, 0.4300079345703125f, 1.25f, 3.8600006103515625f, 40.0f}; +constexpr std::array CURVE_MOVES{0.0f, 1.0702667236328125f, 4.140625f, 18.984375f, 443.75f}; +struct AbsoluteSample { + std::uint64_t timestamp; + std::uint32_t buttons; + std::int32_t x; + std::int32_t y; + std::int32_t wheel; + std::int32_t tilt; + bool connected; +}; + +std::array absoluteHistory{}; +int absoluteHead = 0; +int absoluteCount = 0; +std::uint32_t screenSize = 1; +std::int64_t absoluteX = 0; +std::int64_t absoluteY = 0; +std::uint8_t highPrecision = 0; +float fractionX = 0.0f; +float fractionY = 0.0f; + +float curve(float speed) { + std::size_t segment = 1; + while (segment + 1 < CURVE_SPEEDS.size() && speed > CURVE_SPEEDS[segment]) ++segment; + const float lowSpeed = CURVE_SPEEDS[segment - 1]; + const float lowMove = CURVE_MOVES[segment - 1]; + return lowMove + (speed - lowSpeed) * (CURVE_MOVES[segment] - lowMove) / (CURVE_SPEEDS[segment] - lowSpeed); +} + +std::int64_t step(float& fraction, float delta, float scale) { + const float total = delta * scale + fraction; + const float whole = std::trunc(total); + fraction = total - whole; + return static_cast(whole); +} + +void moveAbsolute(std::int32_t dx, std::int32_t dy) { + std::int64_t moveX = dx; + std::int64_t moveY = dy; + if (highPrecision != 0) { + moveX = 0; + moveY = 0; + const float length = std::sqrt(static_cast(dx) * static_cast(dx) + static_cast(dy) * static_cast(dy)); + if (length != 0.0f) { + const float scale = curve(length * 120.0f / 420.0f) * 96.0f / 120.0f / length; + moveX = step(fractionX, static_cast(dx), scale); + moveY = step(fractionY, static_cast(dy), scale); + } + } + absoluteX = std::clamp(absoluteX + moveX, 0, SCREEN_WIDTHS[screenSize] - 1); + absoluteY = std::clamp(absoluteY + moveY, 0, SCREEN_HEIGHTS[screenSize] - 1); +} + +void enqueueAbsolute(const AbsoluteSample& sample) { + if (absoluteCount == MOUSE_MAX_DATA_NUM) { + absoluteHead = (absoluteHead + 1) % MOUSE_MAX_DATA_NUM; + --absoluteCount; + } + absoluteHistory[(absoluteHead + absoluteCount) % MOUSE_MAX_DATA_NUM] = sample; + ++absoluteCount; +} + +bool sharesRecord(const AbsoluteSample& first, const AbsoluteSample& second) { + return first.connected && second.connected && first.buttons == second.buttons; +} void enqueue(const MouseData& data) { if (count == MOUSE_MAX_DATA_NUM) { @@ -23,6 +93,24 @@ void enqueue(const MouseData& data) { history[(head + count) % MOUSE_MAX_DATA_NUM] = data; ++count; } + +bool motionOnly(const MouseData& data) { + return data.wheel == 0 && data.tilt == 0 && (data.x_axis != 0 || data.y_axis != 0); +} + +std::int32_t saturatedSum(std::int32_t a, std::int32_t b) { + return static_cast(std::clamp(static_cast(a) + b, INT32_MIN, INT32_MAX)); +} + +bool mergeMotion(const MouseData& data) { + if (count == 0 || !motionOnly(data)) return false; + MouseData& tail = history[(head + count - 1) % MOUSE_MAX_DATA_NUM]; + if (!motionOnly(tail) || tail.buttons != data.buttons || tail.connected != data.connected) return false; + tail.timestamp = data.timestamp; + tail.x_axis = saturatedSum(tail.x_axis, data.x_axis); + tail.y_axis = saturatedSum(tail.y_axis, data.y_axis); + return true; +} } namespace Mouse { @@ -50,10 +138,19 @@ int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam buttons = 0; head = 0; count = 0; + screenSize = 1; + absoluteX = 0; + absoluteY = 0; + highPrecision = 0; + fractionX = 0.0f; + fractionY = 0.0f; MouseData initial{}; initial.timestamp = sceKernelGetProcessTime(); initial.connected = connected; enqueue(initial); + absoluteHead = 0; + absoluteCount = 0; + enqueueAbsolute(AbsoluteSample{initial.timestamp, 0, 0, 0, 0, 0, connected}); return MOUSE_HANDLE; } @@ -65,6 +162,8 @@ int Close(std::int32_t handle) { buttons = 0; head = 0; count = 0; + absoluteHead = 0; + absoluteCount = 0; return MOUSE_OK; } @@ -82,11 +181,87 @@ int Read(std::int32_t handle, MouseData* data, std::int32_t num) { return available; } +int SetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + const auto next = static_cast(size); + if (next >= SCREEN_WIDTHS.size()) return MOUSE_ERROR_INVALID_ARG; + if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + absoluteX = absoluteX * SCREEN_WIDTHS[next] / SCREEN_WIDTHS[screenSize]; + absoluteY = absoluteY * SCREEN_HEIGHTS[next] / SCREEN_HEIGHTS[screenSize]; + screenSize = next; + return MOUSE_OK; +} + +int SetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + highPrecision = static_cast(mode); + return MOUSE_OK; +} + +int ReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (data == nullptr || num <= 0 || num > MOUSE_MAX_DATA_NUM) return MOUSE_ERROR_INVALID_ARG; + if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + if (!connected) { + data[0] = MouseAbsoluteData{}; + return 1; + } + std::array samples{}; + std::array endsRecord{}; + const int pending = absoluteCount; + for (int i = 0; i < pending; ++i) { + samples[i] = absoluteHistory[(absoluteHead + i) % MOUSE_MAX_DATA_NUM]; + endsRecord[i] = true; + } + absoluteHead = 0; + absoluteCount = 0; + int records = pending; + for (int i = 0; i + 1 < pending && records > num; ++i) { + if (!sharesRecord(samples[i], samples[i + 1])) continue; + endsRecord[i] = false; + --records; + } + int first = pending; + for (int kept = 0; first > 0 && kept < num;) { + --first; + if (endsRecord[first]) ++kept; + while (first > 0 && !endsRecord[first - 1]) --first; + } + int written = 0; + std::int32_t wheel = 0; + std::int32_t tilt = 0; + for (int i = first; i < pending; ++i) { + const AbsoluteSample& sample = samples[i]; + if (!sample.connected) continue; + moveAbsolute(sample.x, sample.y); + wheel += sample.wheel; + tilt += sample.tilt; + if (!endsRecord[i]) continue; + MouseAbsoluteData& record = data[written++]; + record.timestamp = sample.timestamp; + record.connected = 1; + record.buttons = sample.buttons; + record.x = static_cast(absoluteX); + record.y = static_cast(absoluteY); + record.wheel = wheel; + record.tilt = tilt; + wheel = 0; + tilt = 0; + } + return written; +} + 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; + if (x >= SCREEN_WIDTHS[screenSize] || y >= SCREEN_HEIGHTS[screenSize]) return MOUSE_ERROR_INVALID_ARG; + absoluteX = x; + absoluteY = y; return MOUSE_OK; } @@ -100,19 +275,26 @@ void Publish(const MouseInputEvent& event) { return; } if (!opened) return; + const bool countsGiven = event.countsX != 0 || event.countsY != 0; + const bool noReport = event.button == 0 && !event.resetButtons && !event.connectionChange && event.wheel == 0 && event.tilt == 0; + if (noReport && event.x == 0 && event.y == 0 && !countsGiven) return; if (event.resetButtons) buttons = 0; if (event.button != 0) { if (event.pressed) buttons |= event.button; else buttons &= ~event.button; } + const std::uint64_t timestamp = sceKernelGetProcessTime(); + enqueueAbsolute(AbsoluteSample{timestamp, buttons, countsGiven ? event.countsX : event.x, countsGiven ? event.countsY : event.y, event.wheel, event.tilt, connected}); + if (noReport && event.x == 0 && event.y == 0) return; MouseData data{}; - data.timestamp = sceKernelGetProcessTime(); + data.timestamp = timestamp; data.connected = connected; data.buttons = buttons; data.x_axis = event.x; data.y_axis = event.y; data.wheel = event.wheel; data.tilt = event.tilt; + if (event.button == 0 && !event.resetButtons && mergeMotion(data)) return; enqueue(data); } diff --git a/core/libs/prx/libSceMouse/tests/Mouse.cpp b/core/libs/prx/libSceMouse/tests/Mouse.cpp index 27c99cc622..16eb9d3477 100644 --- a/core/libs/prx/libSceMouse/tests/Mouse.cpp +++ b/core/libs/prx/libSceMouse/tests/Mouse.cpp @@ -5,7 +5,9 @@ #include "SDL_events.h" #include "SDL_mouse.h" +#include #include +#include extern "C" { bool MouseIsOpen_nid_no_patch(); @@ -14,6 +16,18 @@ bool MouseIsOpen_nid_no_patch(); static void Require(bool value) { if (!value) std::abort(); } +struct AbsoluteRecord { + std::uint64_t timestamp; + std::uint8_t connected; + std::uint8_t padding[3]; + std::uint32_t buttons; + std::uint16_t x; + std::uint16_t y; + std::int32_t wheel; + std::int32_t tilt; + std::uint8_t reserved[12]; +}; + int main() { MouseData data[64]{}; Require(!MouseIsOpen_nid_no_patch()); @@ -97,13 +111,87 @@ int main() { input.HandleEvent(event, 7); Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && data[0].connected); - for (int i = 0; i < 70; ++i) { + for (int i = 0; i < 300; ++i) { MouseInputEvent motion{}; - motion.x = i; + motion.x = -1; + motion.y = i % 2; MousePublishInput_nid_no_patch(motion); } + Require(sceMouseRead(MOUSE_HANDLE, data, 8) == 1); + Require(data[0].x_axis == -300 && data[0].y_axis == 150 && data[0].buttons == 0); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + + MouseInputEvent still{}; + MousePublishInput_nid_no_patch(still); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + for (int i = 0; i < 300; ++i) { + MouseInputEvent alternating{}; + alternating.x = i % 2; + MousePublishInput_nid_no_patch(alternating); + } + Require(sceMouseRead(MOUSE_HANDLE, data, 8) == 1); + Require(data[0].x_axis == 150 && data[0].y_axis == 0); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + + MouseInputEvent zero{}; + MouseInputEvent two{}; + two.x = 2; + MouseInputEvent three{}; + three.x = 3; + MouseInputEvent down{}; + down.button = 1; + down.pressed = true; + MouseInputEvent up{}; + up.button = 1; + for (const MouseInputEvent& report : {two, zero, down, zero, three, zero, up, zero}) MousePublishInput_nid_no_patch(report); + Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 4); + Require(data[0].x_axis == 2 && data[0].buttons == 0); + Require(data[1].x_axis == 0 && data[1].buttons == 1); + Require(data[2].x_axis == 3 && data[2].buttons == 1); + Require(data[3].x_axis == 0 && data[3].buttons == 0); + + MouseInputEvent step{}; + step.x = 3; + MousePublishInput_nid_no_patch(step); + MousePublishInput_nid_no_patch(step); + MouseInputEvent press{}; + press.button = 1; + press.pressed = true; + MousePublishInput_nid_no_patch(press); + MousePublishInput_nid_no_patch(step); + MousePublishInput_nid_no_patch(step); + MouseInputEvent wheel{}; + wheel.wheel = 1; + MousePublishInput_nid_no_patch(wheel); + MousePublishInput_nid_no_patch(step); + MouseInputEvent release{}; + release.button = 1; + MousePublishInput_nid_no_patch(release); + MousePublishInput_nid_no_patch(step); + Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 7); + Require(data[0].x_axis == 6 && data[0].buttons == 0); + Require(data[1].x_axis == 0 && data[1].buttons == 1); + Require(data[2].x_axis == 6 && data[2].buttons == 1); + Require(data[3].wheel == 1 && data[3].x_axis == 0 && data[3].buttons == 1); + Require(data[4].x_axis == 3 && data[4].wheel == 0 && data[4].buttons == 1); + Require(data[5].x_axis == 0 && data[5].buttons == 0); + Require(data[6].x_axis == 3 && data[6].buttons == 0); + Require(data[2].timestamp >= data[1].timestamp && data[6].timestamp >= data[5].timestamp); + + MouseInputEvent large{}; + large.x = INT32_MAX; + MousePublishInput_nid_no_patch(large); + MousePublishInput_nid_no_patch(large); + Require(sceMouseRead(MOUSE_HANDLE, data, 8) == 1 && data[0].x_axis == INT32_MAX); + + for (int i = 0; i < 70; ++i) { + MouseInputEvent click{}; + click.button = 1; + click.pressed = (i % 2) == 0; + MousePublishInput_nid_no_patch(click); + } Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 64); - Require(data[0].x_axis == 6 && data[63].x_axis == 69); + Require(data[0].buttons == 1 && data[63].buttons == 0 && data[62].buttons == 1); Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK); Require(!MouseIsOpen_nid_no_patch()); @@ -119,4 +207,22 @@ int main() { Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK); Require(!MouseIsOpen_nid_no_patch()); Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 959, 539) == MOUSE_ERROR_INVALID_HANDLE); + + using ReadAbsolute = int (APS5_VABI*)(std::int32_t, AbsoluteRecord*, std::int32_t); + const auto readAbsolute = reinterpret_cast(reinterpret_cast(&sceMouseReadAbsolutePosition)); + AbsoluteRecord absolute[MOUSE_MAX_DATA_NUM]{}; + Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_HANDLE); + Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 1); + Require(readAbsolute(MOUSE_HANDLE, absolute, MOUSE_MAX_DATA_NUM) == 1); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK); + input.SetPresentation(3840, 2160, 1920, 1080); + event = {}; + event.type = SDL_MOUSEMOTION; + event.motion.windowID = 7; + event.motion.xrel = 10; + event.motion.yrel = 5; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && data[0].x_axis == 20 && data[0].y_axis == 10); + Require(readAbsolute(MOUSE_HANDLE, absolute, 1) == 1 && absolute[0].x == 110 && absolute[0].y == 105); + Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK); } diff --git a/core/libs/prx/libSceMouse/tests/MouseAbsolute.cpp b/core/libs/prx/libSceMouse/tests/MouseAbsolute.cpp new file mode 100644 index 0000000000..5659f4bf73 --- /dev/null +++ b/core/libs/prx/libSceMouse/tests/MouseAbsolute.cpp @@ -0,0 +1,193 @@ +#include "prx/libSceMouse/include/Api.hpp" +#include "prx/libSceMouse/include/MouseState.hpp" +#include "prx/libSceMouse/include/mouse_structs.h" + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace { + +struct AbsoluteRecord { + std::uint64_t timestamp; + std::uint8_t connected; + std::uint8_t padding[3]; + std::uint32_t buttons; + std::uint16_t x; + std::uint16_t y; + std::int32_t wheel; + std::int32_t tilt; + std::uint8_t reserved[12]; +}; +static_assert(sizeof(AbsoluteRecord) == 40); + +using SetValueFn = int (APS5_VABI*)(std::int32_t, std::int32_t); +using ReadAbsoluteFn = int (APS5_VABI*)(std::int32_t, AbsoluteRecord*, std::int32_t); + +template +Function Resolve(const char* name) { +#ifdef _WIN32 + void* symbol = reinterpret_cast(GetProcAddress(GetModuleHandleA("libSceMouse.prx"), name)); +#else + void* symbol = dlsym(RTLD_DEFAULT, name); +#endif + if (!symbol) { + std::fprintf(stderr, "%s is not exported by libSceMouse\n", name); + std::abort(); + } + return reinterpret_cast(symbol); +} + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "mouse absolute position: %s\n", what); + std::abort(); +} + +void Motion(std::int32_t x, std::int32_t y) { + MouseInputEvent event{}; + event.x = x; + event.y = y; + MousePublishInput_nid_no_patch(event); +} + +void Button(std::uint32_t button, bool pressed) { + MouseInputEvent event{}; + event.button = button; + event.pressed = pressed; + MousePublishInput_nid_no_patch(event); +} + +} + +int main() { + const auto setSize = Resolve("sceMouseSetAbsoluteVirtualScreenSize"); + const auto setMode = Resolve("sceMouseSetAbsoluteHighPrecisionMode"); + const auto readAbsolute = Resolve("sceMouseReadAbsolutePosition"); + AbsoluteRecord records[64]; + + Require(setSize(MOUSE_HANDLE, 9) == MOUSE_ERROR_NOT_INITIALIZED, "the screen size before initialization did not return NOT_INITIALIZED first"); + Require(setMode(MOUSE_HANDLE, 1) == MOUSE_ERROR_NOT_INITIALIZED, "the precision mode before initialization did not return NOT_INITIALIZED"); + Require(readAbsolute(MOUSE_HANDLE, nullptr, 0) == MOUSE_ERROR_NOT_INITIALIZED, "a read before initialization did not return NOT_INITIALIZED first"); + Require(sceMouseInit() == MOUSE_OK, "initialization failed"); + Require(setSize(MOUSE_HANDLE, 4) == MOUSE_ERROR_INVALID_ARG && setSize(MOUSE_HANDLE, -1) == MOUSE_ERROR_INVALID_ARG, "a size past 3 was not refused before the handle"); + Require(setSize(MOUSE_HANDLE, 1) == MOUSE_ERROR_INVALID_HANDLE, "the screen size of a closed handle was set"); + Require(setMode(MOUSE_HANDLE, 1) == MOUSE_ERROR_INVALID_HANDLE, "the precision mode of a closed handle was set"); + Require(readAbsolute(MOUSE_HANDLE, nullptr, 1) == MOUSE_ERROR_INVALID_ARG && readAbsolute(MOUSE_HANDLE, records, 0) == MOUSE_ERROR_INVALID_ARG && readAbsolute(MOUSE_HANDLE, records, 65) == MOUSE_ERROR_INVALID_ARG, "a bad read was not refused before the handle"); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == MOUSE_ERROR_INVALID_HANDLE, "a closed handle was read"); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 5000, 0) == MOUSE_ERROR_INVALID_HANDLE, "a reset with a bad handle and a bad position did not return INVALID_HANDLE"); + + Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_HANDLE, "open failed"); + std::memset(records, 0xA5, sizeof(records)); + Require(readAbsolute(MOUSE_HANDLE, records, 64) == 1, "the sample queued at open was not read"); + Require(records[0].connected == 1 && records[0].x == 0 && records[0].y == 0, "the position does not start at the top-left corner"); + Require(records[0].padding[0] == 0xA5 && records[0].padding[2] == 0xA5 && records[0].reserved[0] == 0xA5 && records[0].reserved[11] == 0xA5, "bytes the console leaves alone were written"); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 0, "a read with no samples returned a record"); + + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 1920, 0) == MOUSE_ERROR_INVALID_ARG, "x 1920 was taken at 1920x1080"); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 1000, 500) == MOUSE_OK, "the reset failed"); + Require(setSize(MOUSE_HANDLE, 3) == MOUSE_OK, "the size 3840x2160 was refused"); + Button(1, true); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 2000 && records[0].y == 1000 && records[0].buttons == 1, "the position was not scaled to 3840x2160"); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 3839, 2159) == MOUSE_OK, "the corner of 3840x2160 was refused"); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 2000, 1000) == MOUSE_OK, "the reset at 3840x2160 failed"); + Require(setSize(MOUSE_HANDLE, 0) == MOUSE_OK, "the size 1280x720 was refused"); + Button(1, false); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 666 && records[0].y == 333 && records[0].buttons == 0, "the position was not scaled to 1280x720 with truncation"); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 1280, 0) == MOUSE_ERROR_INVALID_ARG, "x 1280 was taken at 1280x720"); + + Require(setSize(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "returning to 1920x1080 failed"); + Motion(5, -3); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 105 && records[0].y == 97, "motion did not move the position one for one"); + Motion(-5000, 5000); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 0 && records[0].y == 1079, "the position was not clamped to the screen"); + MouseInputEvent wheel{}; + wheel.wheel = 2; + wheel.tilt = -1; + MousePublishInput_nid_no_patch(wheel); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].wheel == 2 && records[0].tilt == -1, "the wheel and tilt were not copied"); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "the reset before the merge checks failed"); + Motion(1, 0); + Motion(2, 0); + Motion(4, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 3) == 3 && records[0].x == 101 && records[1].x == 103 && records[2].x == 107, "three samples read with num 3 did not give one record each"); + Motion(1, 0); + Motion(2, 0); + Motion(4, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 114, "three samples read with num 1 did not share one record with all their motion"); + + Require(setMode(MOUSE_HANDLE, 0x100) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "the precision mode was refused"); + Motion(1, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 101, "mode 0x100 did not keep its low byte 0, which is off"); + Require(setMode(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "turning high precision on failed"); + Motion(1, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 100, "a delta of 1 moved a whole pixel in high precision"); + Motion(1, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 101, "the kept fraction was not added to the next delta"); + Motion(10, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 111, "a delta of 10 did not follow the curve"); + Motion(100, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 359 && records[0].y == 100, "a delta of 100 did not follow the curve"); + + MouseData relative[64]; + const auto reopen = [&](const char* what) { + Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK && sceMouseOpen(1, 0, 0, nullptr) == MOUSE_HANDLE, what); + Require(readAbsolute(MOUSE_HANDLE, records, 64) == 1 && sceMouseRead(MOUSE_HANDLE, relative, 64) == 1, what); + }; + reopen("reopening for the queued precision check failed"); + Require(setMode(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "turning high precision on failed"); + for (int i = 0; i < 10; ++i) Motion(1, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 105, "ten queued deltas of 1 did not each go through the curve"); + reopen("reopening for the incremental precision check failed"); + Require(setMode(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "turning high precision on failed"); + for (int i = 0; i < 10; ++i) { + Motion(1, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1, "a delta read on its own gave no record"); + } + Require(records[0].x == 105, "ten deltas of 1 read one at a time did not end where the queued ones did"); + + Require(setMode(MOUSE_HANDLE, 0) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 0, 100) == MOUSE_OK, "the edge reset failed"); + Motion(-5, 0); + Motion(5, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 5, "queued motion at the left edge was not clamped per sample"); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 0, 100) == MOUSE_OK, "the edge reset failed"); + Motion(-5, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 0, "motion past the left edge was not clamped"); + Motion(5, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 5, "motion read one at a time at the edge did not end where the queued motion did"); + + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "the reset before the size check failed"); + Motion(10, 0); + Require(setSize(MOUSE_HANDLE, 3) == MOUSE_OK && readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 210 && records[0].y == 200, "a queued sample was not applied at the size in force when it was read"); + Require(setSize(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "returning to 1920x1080 failed"); + Motion(10, 0); + Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 50, 50) == MOUSE_OK && readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 60 && records[0].y == 50, "a queued sample did not move the position a later reset set"); + + Require(sceMouseRead(MOUSE_HANDLE, relative, 64) >= 0, "draining sceMouseRead failed"); + Motion(7, 0); + Require(sceMouseRead(MOUSE_HANDLE, relative, 1) == 1 && relative[0].x_axis == 7, "sceMouseRead did not get the motion"); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 67, "a relative read took the sample from the absolute reads"); + Motion(3, 0); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 70, "the absolute read missed the motion"); + Require(sceMouseRead(MOUSE_HANDLE, relative, 1) == 1 && relative[0].x_axis == 3, "an absolute read took the sample from sceMouseRead"); + + MouseInputEvent disconnect{}; + disconnect.connectionChange = true; + disconnect.connected = false; + MousePublishInput_nid_no_patch(disconnect); + std::memset(records, 0xA5, sizeof(records)); + Require(readAbsolute(MOUSE_HANDLE, records, 4) == 1, "a disconnected mouse did not return one record"); + const AbsoluteRecord zero{}; + Require(std::memcmp(&records[0], &zero, sizeof(zero)) == 0 && records[1].connected == 0xA5, "a disconnected mouse did not return one zeroed record"); + + Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK, "close failed"); + Require(readAbsolute(MOUSE_HANDLE, records, 1) == MOUSE_ERROR_INVALID_HANDLE, "a closed handle was read"); + std::puts("mouse absolute position tests passed"); + return 0; +} diff --git a/core/libs/prx/libSceMsgDialog.native/Export.cpp b/core/libs/prx/libSceMsgDialog.native/Export.cpp index 71826e1fca..4b015f3115 100644 --- a/core/libs/prx/libSceMsgDialog.native/Export.cpp +++ b/core/libs/prx/libSceMsgDialog.native/Export.cpp @@ -55,9 +55,10 @@ int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result) { if (status == 0) 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; - *result = MsgDialogResult{}; - result->result = COMMON_DIALOG_RESULT_OK; - result->button_id = g_buttonId.load(); + MsgDialogResult value{}; + value.result = COMMON_DIALOG_RESULT_OK; + value.button_id = g_buttonId.load(); + if (!GuestCopyOut_nid_no_patch(result, &value, sizeof(value))) return COMMON_DIALOG_ERROR_ARG_NULL; return 0; } diff --git a/core/libs/prx/libSceNKWebKit/CMakeLists.txt b/core/libs/prx/libSceNKWebKit/CMakeLists.txt index 9bad4ef4d0..bc7cef34db 100644 --- a/core/libs/prx/libSceNKWebKit/CMakeLists.txt +++ b/core/libs/prx/libSceNKWebKit/CMakeLists.txt @@ -55,6 +55,10 @@ if(BUILD_TESTING) target_link_libraries(nkwebkit_${testName}_tests PRIVATE libSceNKWebKit libSceJsc libc) configure_windows_unwind(nkwebkit_${testName}_tests) add_test(NAME nkwebkit_${testName} COMMAND nkwebkit_${testName}_tests) + if(ANYPS5_ENABLE_ASAN AND testName STREQUAL "wtf") + set_tests_properties(nkwebkit_${testName} PROPERTIES ENVIRONMENT_MODIFICATION + "ASAN_OPTIONS=path_list_append:allocator_may_return_null=1") + endif() if(WIN32) set_tests_properties(nkwebkit_${testName} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceNKWebKit/Export.cpp b/core/libs/prx/libSceNKWebKit/Export.cpp index 82827b517b..41b73e0b27 100644 --- a/core/libs/prx/libSceNKWebKit/Export.cpp +++ b/core/libs/prx/libSceNKWebKit/Export.cpp @@ -12116,10 +12116,6 @@ int APS5_VABI _ZN7WebCore8Settings27setLoadsImagesAutomaticallyEb() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKPreferencesGetApplicationChromeModeEnabled() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore18JSHTMLInputElementC2ERKS0_() { NotImplemented_nid_no_patch(__func__); } @@ -12164,10 +12160,6 @@ int APS5_VABI _ZN3JSC30int52AwareSpeculationFromValueENS_7JSValueE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN9Inspector18InjectedScriptHost7subtypeEPN3JSC14JSGlobalObjectENS1_7JSValueE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore13HTTPHeaderMap3addERKN3WTF6StringES4_() { NotImplemented_nid_no_patch(__func__); } @@ -12336,10 +12328,6 @@ int APS5_VABI _ZN7WebCore18TextureMapperLayer18setFixedToViewportEb() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleBackForwardListGetForwardListCount() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore28collectVaryingRequestHeadersEPNS_21NetworkStorageSessionERKNS_15ResourceRequestERKNS_16ResourceResponseE() { NotImplemented_nid_no_patch(__func__); } @@ -12664,10 +12652,6 @@ int APS5_VABI WKAXObjectRole() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKAccessibilityCanUseSecondaryAXThread() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKAccessibilityEnableEnhancedAccessibility() { NotImplemented_nid_no_patch(__func__); } @@ -12796,50 +12780,6 @@ int APS5_VABI WKBundleBackForwardListClear() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleBackForwardListCopyItemAtIndex() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListGetBackListCount() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListItemCopyChildren() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListItemCopyOriginalURL() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListItemCopyTarget() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListItemCopyTitle() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListItemCopyURL() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListItemHasCachedPageExpired() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListItemIsInBackForwardCache() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListItemIsSame() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleBackForwardListItemIsTargetItem() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleClearHistoryForTesting() { NotImplemented_nid_no_patch(__func__); } @@ -12868,10 +12808,6 @@ int APS5_VABI WKBundleExtendClassesForParameterCoder() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleFileHandleCreateWithPath() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleFrameAllowsFollowingLink() { NotImplemented_nid_no_patch(__func__); } @@ -12932,14 +12868,6 @@ int APS5_VABI WKBundleFrameCopyURL() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleFrameCopyWebArchiveFilteringSubframes() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleFrameCopyWebArchive() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleFrameCreateFrameHandle() { NotImplemented_nid_no_patch(__func__); } @@ -12976,10 +12904,6 @@ int APS5_VABI WKBundleFrameGetJavaScriptContext() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleFrameGetJavaScriptWrapperForFileForWorld() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleFrameGetJavaScriptWrapperForNodeForWorld() { NotImplemented_nid_no_patch(__func__); } @@ -13192,26 +13116,14 @@ int APS5_VABI WKBundleNodeHandleCopyDocument() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleNodeHandleCopyHTMLFrameElementContentFrame() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleNodeHandleCopyHTMLIFrameElementContentFrame() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleNodeHandleCopyHTMLTableCellElementCellAbove() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleNodeHandleCopySnapshotWithOptions() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleNodeHandleCopyVisibleRange() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleNodeHandleCreate() { NotImplemented_nid_no_patch(__func__); } @@ -13220,30 +13132,14 @@ int APS5_VABI WKBundleNodeHandleGetElementBounds() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutoFillAvailable() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutoFillButtonBounds() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutoFillButtonEnabled() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutoFillButtonType() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutoFilled() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutofilled() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundleNodeHandleGetHTMLInputElementLastAutoFillButtonType() { NotImplemented_nid_no_patch(__func__); } @@ -13348,14 +13244,6 @@ int APS5_VABI WKBundlePageConfirmCompositionWithText() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundlePageCopyContextMenuAtPointInWindow() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKBundlePageCopyContextMenuItems() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundlePageCopyGroupIdentifier() { NotImplemented_nid_no_patch(__func__); } @@ -13440,10 +13328,6 @@ int APS5_VABI WKBundlePageGetAppCacheUsageForOrigin() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKBundlePageGetBackForwardList() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKBundlePageGetBackingScaleFactor() { NotImplemented_nid_no_patch(__func__); } @@ -13832,10 +13716,6 @@ int APS5_VABI WKContextClearVisitedLinks() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKContextCopyPlugInAutoStartOriginHashes() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKContextCreate() { NotImplemented_nid_no_patch(__func__); } @@ -13852,10 +13732,6 @@ int APS5_VABI WKContextDisableProcessTermination() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKContextDownloadURLRequest() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKContextEnableProcessTermination() { NotImplemented_nid_no_patch(__func__); } @@ -13884,18 +13760,10 @@ int APS5_VABI WKContextGetGlobalStatistics() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKContextGetIconDatabase() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKContextGetKeyValueStorageManager() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKContextGetMaximumNumberOfProcesses() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKContextGetNotificationManager() { NotImplemented_nid_no_patch(__func__); } @@ -13924,50 +13792,6 @@ int APS5_VABI WKContextJavaScriptConfigurationFileEnabled() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKContextMenuCopySubmenuItems() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemCopyTitle() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemCreateAsAction() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemCreateAsCheckableAction() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemCreateAsSubmenu() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemGetChecked() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemGetEnabled() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemGetTag() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemGetType() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemGetUserData() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKContextMenuItemSeparatorItem() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKContextMenuItemSetUserData() { NotImplemented_nid_no_patch(__func__); } @@ -14004,10 +13828,6 @@ int APS5_VABI WKContextRegisterURLSchemeAsSecure() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKContextResumeDownload() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKContextSetAccessibilityEnabled() { NotImplemented_nid_no_patch(__func__); } @@ -14224,10 +14044,6 @@ int APS5_VABI WKFrameCreateFrameHandle() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKFrameCreateFrameInfo() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKFrameGetCertificateInfo() { NotImplemented_nid_no_patch(__func__); } @@ -14268,10 +14084,6 @@ int APS5_VABI WKFrameIsDisplayingStandaloneImageDocument() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKFrameIsFrameSet() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKFrameIsMainFrame() { NotImplemented_nid_no_patch(__func__); } @@ -14384,14 +14196,6 @@ int APS5_VABI WKIconDatabaseClose() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKIconDatabaseCopyIconDataForPageURL() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKIconDatabaseCopyIconURLForPageURL() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKIconDatabaseEnableDatabaseCleanup() { NotImplemented_nid_no_patch(__func__); } @@ -14528,22 +14332,10 @@ int APS5_VABI WKKeyValueStorageManagerDeleteEntriesForOrigin() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKKeyValueStorageManagerGetCreationTimeKey() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKKeyValueStorageManagerGetKeyValueStorageOrigins() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKKeyValueStorageManagerGetModificationTimeKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKKeyValueStorageManagerGetOriginKey() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKKeyValueStorageManagerGetStorageDetailsByOrigin() { NotImplemented_nid_no_patch(__func__); } @@ -14648,10 +14440,6 @@ int APS5_VABI WKOpenPanelParametersCopyAllowedMIMETypes() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKOpenPanelParametersCopyCapture() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKOpenPanelParametersCopySelectedFileNames() { NotImplemented_nid_no_patch(__func__); } @@ -14664,10 +14452,6 @@ int APS5_VABI WKOpenPanelParametersGetAllowsMultipleFiles() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKOpenPanelParametersGetMediaCaptureType() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKOpenPanelResultListenerCancel() { NotImplemented_nid_no_patch(__func__); } @@ -14860,10 +14644,6 @@ int APS5_VABI WKPageGetFocusedFrame() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKPageGetFrameSetLargestFrame() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKPageGetGapBetweenPages() { NotImplemented_nid_no_patch(__func__); } @@ -15016,10 +14796,6 @@ int APS5_VABI WKPageIsContentEditable() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKPageIsMockRealtimeMediaSourceCenterEnabled() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKPageIsPinnedToBottomSide() { NotImplemented_nid_no_patch(__func__); } @@ -15428,62 +15204,6 @@ int APS5_VABI WKPageWillHandleHorizontalScrollEvents() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKPlugInInformationReplacementObscuredKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationBundleIdentifierKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationBundleShortVersionKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationBundleVersionKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationDefaultLoadPolicyKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationDisplayNameKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationFrameURLKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationHasSandboxProfileKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationMIMETypeKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationPageURLKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationPathKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationPluginURLKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationPluginspageAttributeURLKey() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPluginInformationUpdatePastLastBlockedVersionIsKnownAvailableKey() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKPopupMenuItemCopyText() { NotImplemented_nid_no_patch(__func__); } @@ -15500,18 +15220,6 @@ int APS5_VABI WKPopupMenuItemIsSeparator() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKPreferencesGetAccelerated2DCanvasEnabled() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPreferencesGetQTKitEnabled() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI WKPreferencesGetTypeID() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKPreferencesSetAccelerated2DCanvasEnabled() { NotImplemented_nid_no_patch(__func__); } @@ -15740,10 +15448,6 @@ int APS5_VABI WKUserContentExtensionStoreCompile() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI WKUserContentExtensionStoreCreate() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI WKUserContentExtensionStoreLookup() { NotImplemented_nid_no_patch(__func__); } @@ -16628,10 +16332,6 @@ int APS5_VABI _ZN3JSC12addErrorInfoEPNS_14JSGlobalObjectEPNS_8JSObjectEb() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3JSC12isFromJSCodeEPv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3JSC12iteratorStepEPNS_14JSGlobalObjectENS_15IterationRecordE() { NotImplemented_nid_no_patch(__func__); } @@ -17652,14 +17352,6 @@ int APS5_VABI _ZN3JSC22globalMemoryStatisticsEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3JSC22optimizeNextInvocationENS_7JSValueE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3JSC22optimizeNextInvocationEPNS_14JSGlobalObjectEPNS_9CallFrameE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3JSC23AbstractSamplingCounter4dumpEv() { NotImplemented_nid_no_patch(__func__); } @@ -17900,10 +17592,6 @@ int APS5_VABI _ZN3JSC35getSomeBaselineCodeBlockForFunctionENS_7JSValueE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3JSC3DFG18getNumCompilationsEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3JSC41constructFunctionSkippingEvalEnabledCheckEPNS_14JSGlobalObjectERKNS_7ArgListERKNS_10IdentifierERKNS_12SourceOriginERKN3WTF6StringERKNSB_12TextPositionEiNS_24FunctionConstructionModeENS_7JSValueE() { NotImplemented_nid_no_patch(__func__); } @@ -19076,10 +18764,6 @@ int APS5_VABI _ZN3WTF12isMainThreadEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF13FastBitVector10clearRangeEmm() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF13MetaAllocator17addFreshFreeSpaceEPvm() { NotImplemented_nid_no_patch(__func__); } @@ -19388,10 +19072,6 @@ int APS5_VABI _ZN3WTF14FileSystemImpl25pathByAppendingComponentsENS_10StringView NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF14FileSystemImpl27isSafeToUseMemoryMapForPathERKNS_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF14FileSystemImpl29makeSafeToUseMemoryMapForPathERKNS_6StringE() { NotImplemented_nid_no_patch(__func__); } @@ -19464,10 +19144,6 @@ int APS5_VABI _ZN3WTF14isVersion4UUIDENS_10StringViewE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF14setCloseOnExecEi() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF15AtomStringTableD1Ev() { NotImplemented_nid_no_patch(__func__); } @@ -19476,10 +19152,6 @@ int APS5_VABI _ZN3WTF15AtomStringTableD2Ev() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF15AutomaticThread11shouldSleepERKNS_14AbstractLockerE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF15AutomaticThread14threadDidStartEv() { NotImplemented_nid_no_patch(__func__); } @@ -19732,10 +19404,6 @@ int APS5_VABI _ZN3WTF19isCompilationThreadEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF19mimeTypeFromDataURLENS_10StringViewE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF19numGraphemeClustersENS_10StringViewE() { NotImplemented_nid_no_patch(__func__); } @@ -19908,14 +19576,6 @@ int APS5_VABI _ZN3WTF22CrossThreadTaskHandlerD2Ev() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN3WTF22FastBitVectorWordOwner10resizeSlowEm() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN3WTF22FastBitVectorWordOwner13setEqualsSlowERKS0_() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN3WTF22TextBreakIteratorCache9singletonEv() { NotImplemented_nid_no_patch(__func__); } @@ -20872,18 +20532,10 @@ int APS5_VABI _ZN7WebCore10Pasteboard14writePlainTextERKN3WTF6StringENS0_18Smart NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore10Pasteboard15canSmartReplaceEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore10Pasteboard15writeCustomDataERKN3WTF6VectorINS_20PasteboardCustomDataELm0ENS1_15CrashOnOverflowELm16ENS1_10FastMallocEEE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore10Pasteboard16fileContentStateEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore10Pasteboard20typesSafeForBindingsERKN3WTF6StringE() { NotImplemented_nid_no_patch(__func__); } @@ -22096,10 +21748,6 @@ int APS5_VABI _ZN7WebCore11startOfWordERKNS_15VisiblePositionENS_9EWordSideE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore12BlobRegistry16blobRegistryImplEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore12BlobRegistryC2ERKS0_() { NotImplemented_nid_no_patch(__func__); } @@ -22188,10 +21836,6 @@ int APS5_VABI _ZN7WebCore12EventHandler17scrollRecursivelyENS_15ScrollDirectionE NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore12EventHandler18accessKeyModifiersEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore12EventHandler20sendContextMenuEventERKNS_18PlatformMouseEventE() { NotImplemented_nid_no_patch(__func__); } @@ -22932,18 +22576,10 @@ int APS5_VABI _ZN7WebCore13MIMETypeCache17addSupportedTypesERKN3WTF6VectorINS1_6 NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore13MIMETypeCache21canDecodeExtendedTypeERKNS_11ContentTypeE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore13MIMETypeCache21supportsContainerTypeERKN3WTF6StringE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore13MIMETypeCache26isUnsupportedContainerTypeERKN3WTF6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore13MIMETypeCacheC1ERKS0_() { NotImplemented_nid_no_patch(__func__); } @@ -23132,10 +22768,6 @@ int APS5_VABI _ZN7WebCore13endOfSentenceERKNS_15VisiblePositionE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore13getLogChannelERKN3WTF6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore13internalErrorERKN3WTF3URLE() { NotImplemented_nid_no_patch(__func__); } @@ -24940,10 +24572,6 @@ int APS5_VABI _ZN7WebCore16HTMLVideoElement6createERNS_8DocumentE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore16ISOSchemeTypeBox11boxTypeNameEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore16ISOSchemeTypeBox5parseERN3JSC8DataViewERj() { NotImplemented_nid_no_patch(__func__); } @@ -25648,10 +25276,6 @@ int APS5_VABI _ZN7WebCore17JSDOMGlobalObject17addBuiltinGlobalsERN3JSC2VME() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore17JSDOMGlobalObject21offsetOfWorldIsNormalEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore17JSDOMGlobalObject24builtinInternalFunctionsEv() { NotImplemented_nid_no_patch(__func__); } @@ -26272,14 +25896,6 @@ int APS5_VABI _ZN7WebCore18PlatformPasteboard5writeERKN3WTF6StringES4_() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore18PlatformPasteboard5writeERKN3WTF6VectorINS_20PasteboardCustomDataELm0ENS1_15CrashOnOverflowELm16ENS1_10FastMallocEEE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN7WebCore18PlatformPasteboard5writeERKNS_20PasteboardCustomDataE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore18PlatformPasteboard5writeERKNS_20PasteboardWebContentE() { NotImplemented_nid_no_patch(__func__); } @@ -27248,10 +26864,6 @@ int APS5_VABI _ZN7WebCore20HTMLTableCellElement8setScopeERKN3WTF10AtomStringE() NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore20ISOOriginalFormatBox11boxTypeNameEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore20ISOOriginalFormatBox5parseERN3JSC8DataViewERj() { NotImplemented_nid_no_patch(__func__); } @@ -28072,10 +27684,6 @@ int APS5_VABI _ZN7WebCore21HTMLOptionsCollection9setLengthEj() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore21ISOTrackEncryptionBox11boxTypeNameEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore21ISOTrackEncryptionBox5parseERN3JSC8DataViewERj() { NotImplemented_nid_no_patch(__func__); } @@ -28376,10 +27984,6 @@ int APS5_VABI _ZN7WebCore21PageOverlayController45copyAccessibilityAttributeStri NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore21PlatformKeyboardEvent20currentCapsLockStateEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore21PlatformKeyboardEvent23getCurrentModifierStateERbS1_S1_S1_() { NotImplemented_nid_no_patch(__func__); } @@ -28588,10 +28192,6 @@ int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient17dispatchWillCloseEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient17objectContentTypeERKN3WTF3URLERKNS1_6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient17setCopiesOnScrollEv() { NotImplemented_nid_no_patch(__func__); } @@ -28696,10 +28296,6 @@ int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient29dispatchDidPopStateWithinPage NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient29hasFrameSpecificStorageAccessEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient29savePlatformDataToCachedFrameEPNS_11CachedFrameE() { NotImplemented_nid_no_patch(__func__); } @@ -28764,10 +28360,6 @@ int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient37dispatchDidReachVisuallyNonEm NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient38dispatchDidLoadResourceFromMemoryCacheEPNS_14DocumentLoaderERKNS_15ResourceRequestERKNS_16ResourceResponseEi() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient50dispatchDidReceiveServerRedirectForProvisionalLoadEv() { NotImplemented_nid_no_patch(__func__); } @@ -28944,10 +28536,6 @@ int APS5_VABI _ZN7WebCore23HTMLTableSectionElement9insertRowEi() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore23ISOSchemeInformationBox11boxTypeNameEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore23ISOSchemeInformationBox5parseERN3JSC8DataViewERj() { NotImplemented_nid_no_patch(__func__); } @@ -29600,10 +29188,6 @@ int APS5_VABI _ZN7WebCore26HTMLTextFormControlElement6selectENS_19SelectionRevea NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore26ISOProtectionSchemeInfoBox11boxTypeNameEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore26ISOProtectionSchemeInfoBox5parseERN3JSC8DataViewERj() { NotImplemented_nid_no_patch(__func__); } @@ -29856,10 +29440,6 @@ int APS5_VABI _ZN7WebCore27parseHTMLNonNegativeIntegerEN3WTF10StringViewE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore27screenSupportsExtendedColorEPNS_6WidgetE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore27withinTextUnitOfGranularityERKNS_15VisiblePositionENS_15TextGranularityENS_18SelectionDirectionE() { NotImplemented_nid_no_patch(__func__); } @@ -30308,10 +29888,6 @@ int APS5_VABI _ZN7WebCore36ContentSecurityPolicyResponseHeadersC2ERKNS_16Resourc NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN7WebCore36ISOProtectionSystemSpecificHeaderBox11boxTypeNameEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN7WebCore36ISOProtectionSystemSpecificHeaderBox5parseERN3JSC8DataViewERj() { NotImplemented_nid_no_patch(__func__); } @@ -33864,14 +33440,6 @@ int APS5_VABI _ZN9Inspector18InjectedScriptHost16clearAllWrappersEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZN9Inspector18InjectedScriptHost19isHTMLAllCollectionERN3JSC2VMENS1_7JSValueE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZN9Inspector18InjectedScriptHost21getInternalPropertiesERN3JSC2VMEPNS1_14JSGlobalObjectENS1_7JSValueE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZN9Inspector18InjectedScriptHost6createEv() { NotImplemented_nid_no_patch(__func__); } @@ -36900,10 +36468,6 @@ int APS5_VABI _ZNK3JSC9ClassInfo4dumpERN3WTF11PrintStreamE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK3JSC9CodeBlock26reoptimizationRetryCounterEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK3JSC9CodeBlock4dumpERN3WTF11PrintStreamE() { NotImplemented_nid_no_patch(__func__); } @@ -37004,10 +36568,6 @@ int APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK3WTF3URL30stringCenterEllipsizedToLengthEj() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK3WTF3URL4dumpERNS_11PrintStreamE() { NotImplemented_nid_no_patch(__func__); } @@ -38332,10 +37892,6 @@ int APS5_VABI _ZNK7WebCore13KeyboardEvent8charCodeEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore13MIMETypeCache11isAvailableEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore13MIMETypeCache7isEmptyEv() { NotImplemented_nid_no_patch(__func__); } @@ -38448,22 +38004,10 @@ int APS5_VABI _ZNK7WebCore14JSWebAnimation7wrappedEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore14LoaderStrategy11usePingLoadEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore14LoaderStrategy12ongoingLoadsEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore14LoaderStrategy27havePerformedSecurityChecksERKNS_16ResourceResponseE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZNK7WebCore14LoaderStrategy27shouldPerformSecurityChecksEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore14LoggedInStatus10hasExpiredEv() { NotImplemented_nid_no_patch(__func__); } @@ -38628,10 +38172,6 @@ int APS5_VABI _ZNK7WebCore15ActiveDOMObject20isAllowedToRunScriptEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore15ActiveDOMObject25virtualHasPendingActivityEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore15ActiveDOMObject30assertSuspendIfNeededWasCalledEv() { NotImplemented_nid_no_patch(__func__); } @@ -39108,10 +38648,6 @@ int APS5_VABI _ZNK7WebCore16JSXMLHttpRequest8responseERN3JSC14JSGlobalObjectE() NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore16TrackPrivateBase10trackIndexEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore16TrackPrivateBase17startTimeVarianceEv() { NotImplemented_nid_no_patch(__func__); } @@ -39508,18 +39044,10 @@ int APS5_VABI _ZNK7WebCore18TextureMapperLayer9transformEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore19BlurFilterOperation11movesPixelsEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore19BlurFilterOperation12stdDeviationEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore19BlurFilterOperation14affectsOpacityEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore19BlurFilterOperation5cloneEv() { NotImplemented_nid_no_patch(__func__); } @@ -39728,10 +39256,6 @@ int APS5_VABI _ZNK7WebCore20ISOOriginalFormatBox10dataFormatEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore20LowPowerModeNotifier21isLowPowerModeEnabledEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore20PasteboardCustomData18createSharedBufferEv() { NotImplemented_nid_no_patch(__func__); } @@ -39744,10 +39268,6 @@ int APS5_VABI _ZNK7WebCore20RenderBoxModelObject18inlineContinuationEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore20ResourceResponseBase11httpVersionEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore20ResourceResponseBase12isAttachmentEv() { NotImplemented_nid_no_patch(__func__); } @@ -39764,34 +39284,14 @@ int APS5_VABI _ZNK7WebCore20ResourceResponseBase14httpStatusCodeEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore20ResourceResponseBase14httpStatusTextEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore20ResourceResponseBase15httpHeaderFieldENS_14HTTPHeaderNameE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore20ResourceResponseBase16httpHeaderFieldsEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZNK7WebCore20ResourceResponseBase16textEncodingNameEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore20ResourceResponseBase17suggestedFilenameEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore20ResourceResponseBase21expectedContentLengthEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore20ResourceResponseBase23hasCacheValidatorFieldsEv() { NotImplemented_nid_no_patch(__func__); } @@ -39816,10 +39316,6 @@ int APS5_VABI _ZNK7WebCore20ResourceResponseBase34cacheControlContainsMustRevali NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore20ResourceResponseBase3urlEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore20ResourceResponseBase6sourceEv() { NotImplemented_nid_no_patch(__func__); } @@ -39832,14 +39328,6 @@ int APS5_VABI _ZNK7WebCore20ResourceResponseBase8lazyInitENS0_9InitLevelE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore20ResourceResponseBase8mimeTypeEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZNK7WebCore20ScrollingCoordinator25scrollableContainerNodeIDERKNS_12RenderObjectE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore20ScrollingCoordinator36coordinatesScrollingForOverflowLayerERKNS_11RenderLayerE() { NotImplemented_nid_no_patch(__func__); } @@ -39964,34 +39452,14 @@ int APS5_VABI _ZNK7WebCore22DefaultFilterOperationeqERKNS_15FilterOperationE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient10hasWebViewEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient12blockedErrorERKNS_15ResourceRequestE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient12canCachePageEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient14cancelledErrorERKNS_15ResourceRequestE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient14shouldFallBackERKNS_13ResourceErrorE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient15canShowMIMETypeERKN3WTF6StringE() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient16canHandleRequestERKNS_15ResourceRequestE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient17overrideMediaTypeEv() { NotImplemented_nid_no_patch(__func__); } @@ -40000,26 +39468,14 @@ int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient18cannotShowURLErrorERKNS_15Re NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient21canShowMIMETypeAsHTMLERKN3WTF6StringE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient21fileDoesNotExistErrorERKNS_16ResourceResponseE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient21shouldGoToHistoryItemERNS_11HistoryItemE() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient23cannotShowMIMETypeErrorERKNS_16ResourceResponseE() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient24isEmptyFrameLoaderClientEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient25pluginWillHandleLoadErrorERKNS_16ResourceResponseE() { NotImplemented_nid_no_patch(__func__); } @@ -40128,10 +39584,6 @@ int APS5_VABI _ZNK7WebCore24CoordinatedGraphicsLayer22shouldHaveBackingStoreEv() NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore24CoordinatedGraphicsLayer26isCoordinatedGraphicsLayerEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore24CoordinatedGraphicsLayer28shouldDirectlyCompositeImageEPNS_5ImageE() { NotImplemented_nid_no_patch(__func__); } @@ -40144,18 +39596,10 @@ int APS5_VABI _ZNK7WebCore24CoordinatedGraphicsLayer41selfOrAncestorHasActiveTra NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore25DropShadowFilterOperation11movesPixelsEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore25DropShadowFilterOperation12stdDeviationEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore25DropShadowFilterOperation14affectsOpacityEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore25DropShadowFilterOperation1xEv() { NotImplemented_nid_no_patch(__func__); } @@ -40452,10 +39896,6 @@ int APS5_VABI _ZNK7WebCore4Page16hasSeenAnyPluginEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore4Page17useDarkAppearanceEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore4Page17viewportArgumentsEv() { NotImplemented_nid_no_patch(__func__); } @@ -40684,18 +40124,6 @@ int APS5_VABI _ZNK7WebCore6Path2D4pathEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore6Quirks25needsYouTubeMouseOutQuirkEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZNK7WebCore6Quirks29shouldAvoidUsingIOS13ForGmailEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZNK7WebCore6Quirks32isNeverRichlyEditableForTouchBarEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore6Quirks44shouldAvoidResizingWhenInputViewBoundsChangeEv() { NotImplemented_nid_no_patch(__func__); } @@ -40704,14 +40132,6 @@ int APS5_VABI _ZNK7WebCore6Quirks47shouldIgnoreContentObservationForSyntheticCli NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore6Quirks49isTouchBarUpdateSupressedForHiddenContentEditableEv() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI _ZNK7WebCore6Quirks50shouldIgnoreAriaForFastPathContentObservationCheckEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore6Quirks56shouldDispatchSyntheticMouseEventsWhenModifyingSelectionEv() { NotImplemented_nid_no_patch(__func__); } @@ -40948,10 +40368,6 @@ int APS5_VABI _ZNK7WebCore8Document21haveStylesheetsLoadedEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7WebCore8Document22touchEventHandlerCountEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK7WebCore8Document22wheelEventHandlerCountEv() { NotImplemented_nid_no_patch(__func__); } @@ -41244,10 +40660,6 @@ int APS5_VABI _ZNK7bmalloc11IsoTLSEntry6offsetEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK7bmalloc11IsoTLSEntry9alignmentEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK9Inspector14ConsoleMessage12globalObjectEv() { NotImplemented_nid_no_patch(__func__); } @@ -41352,10 +40764,6 @@ int APS5_VABI _ZNK9Inspector15AsyncStackTrace9isPendingEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK9Inspector15InspectorTarget13isProvisionalEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK9Inspector15InspectorTarget8isPausedEv() { NotImplemented_nid_no_patch(__func__); } @@ -41544,10 +40952,6 @@ int APS5_VABI _ZNK9Inspector22InspectorDebuggerAgent8isPausedEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK9Inspector22RemoteAutomationTarget4typeEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK9Inspector22RemoteAutomationTarget8isPairedEv() { NotImplemented_nid_no_patch(__func__); } @@ -41556,10 +40960,6 @@ int APS5_VABI _ZNK9Inspector22RemoteInspectionTarget20remoteControlAllowedEv() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI _ZNK9Inspector22RemoteInspectionTarget26automaticInspectionAllowedEv() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI _ZNK9Inspector22RemoteInspectionTarget3urlEv() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp index 91b4326336..b80ce7d366 100644 --- a/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/InventoryExports.hpp @@ -1647,7 +1647,6 @@ int APS5_VABI _ZN3WTF11setNonBlockEi(); int APS5_VABI _ZN3WTF12CountingLock10unlockSlowEv(); int APS5_VABI _ZN3WTF12CountingLock8lockSlowEv(); int APS5_VABI _ZN3WTF12isMainThreadEv(); -int APS5_VABI _ZN3WTF13FastBitVector10clearRangeEmm(); int APS5_VABI _ZN3WTF13MetaAllocator17addFreshFreeSpaceEPvm(); int APS5_VABI _ZN3WTF13MetaAllocator17currentStatisticsERKNS_6LockerINS_4LockEEE(); int APS5_VABI _ZN3WTF13MetaAllocator17freeFreeSpaceNodeEPNS0_13FreeSpaceNodeE(); @@ -1821,7 +1820,6 @@ int APS5_VABI _ZN3WTF19ParallelEnvironment7executeEPv(); int APS5_VABI _ZN3WTF19ParallelEnvironmentC1EPFvPvEmi(); int APS5_VABI _ZN3WTF19ParallelEnvironmentC2EPFvPvEmi(); int APS5_VABI _ZN3WTF19isCompilationThreadEv(); -int APS5_VABI _ZN3WTF19mimeTypeFromDataURLENS_10StringViewE(); int APS5_VABI _ZN3WTF19numGraphemeClustersENS_10StringViewE(); int APS5_VABI _ZN3WTF20ConcurrentPtrHashSet15deleteOldTablesEv(); int APS5_VABI _ZN3WTF20ConcurrentPtrHashSet5clearEv(); @@ -1865,8 +1863,6 @@ int APS5_VABI _ZN3WTF22CrossThreadTaskHandlerC2EPKcNS0_25AutodrainedPoolForRunLo int APS5_VABI _ZN3WTF22CrossThreadTaskHandlerD0Ev(); int APS5_VABI _ZN3WTF22CrossThreadTaskHandlerD1Ev(); int APS5_VABI _ZN3WTF22CrossThreadTaskHandlerD2Ev(); -int APS5_VABI _ZN3WTF22FastBitVectorWordOwner10resizeSlowEm(); -int APS5_VABI _ZN3WTF22FastBitVectorWordOwner13setEqualsSlowERKS0_(); int APS5_VABI _ZN3WTF22TextBreakIteratorCache9singletonEv(); int APS5_VABI _ZN3WTF22isMainThreadOrGCThreadEv(); int APS5_VABI _ZN3WTF23callOnMainThreadAndWaitEONS_8FunctionIFvvEEE(); @@ -6140,7 +6136,6 @@ int APS5_VABI _ZNK3WTF3URL17lastPathComponentEv(); int APS5_VABI _ZNK3WTF3URL19protocolHostAndPortEv(); int APS5_VABI _ZNK3WTF3URL21truncatedForUseAsBaseEv(); int APS5_VABI _ZNK3WTF3URL24strippedForUseAsReferrerEv(); -int APS5_VABI _ZNK3WTF3URL30stringCenterEllipsizedToLengthEj(); int APS5_VABI _ZNK3WTF3URL4dumpERNS_11PrintStreamE(); int APS5_VABI _ZNK3WTF3URL4userEv(); int APS5_VABI _ZNK3WTF3URL8passwordEv(); @@ -6825,29 +6820,21 @@ int APS5_VABI _ZNK7WebCore20LowPowerModeNotifier21isLowPowerModeEnabledEv(); int APS5_VABI _ZNK7WebCore20PasteboardCustomData18createSharedBufferEv(); int APS5_VABI _ZNK7WebCore20PasteboardWriterData7isEmptyEv(); int APS5_VABI _ZNK7WebCore20RenderBoxModelObject18inlineContinuationEv(); -int APS5_VABI _ZNK7WebCore20ResourceResponseBase11httpVersionEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase12isAttachmentEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase12isSuccessfulEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase13isRedirectionEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase14httpStatusCodeEv(); -int APS5_VABI _ZNK7WebCore20ResourceResponseBase14httpStatusTextEv(); -int APS5_VABI _ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase15httpHeaderFieldENS_14HTTPHeaderNameE(); -int APS5_VABI _ZNK7WebCore20ResourceResponseBase16httpHeaderFieldsEv(); -int APS5_VABI _ZNK7WebCore20ResourceResponseBase16textEncodingNameEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase17suggestedFilenameEv(); -int APS5_VABI _ZNK7WebCore20ResourceResponseBase21expectedContentLengthEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase23hasCacheValidatorFieldsEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase24isAttachmentWithFilenameEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase27cacheControlContainsNoCacheEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase27cacheControlContainsNoStoreEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase29cacheControlContainsImmutableEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase34cacheControlContainsMustRevalidateEv(); -int APS5_VABI _ZNK7WebCore20ResourceResponseBase3urlEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase6sourceEv(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase8isHTTP09Ev(); int APS5_VABI _ZNK7WebCore20ResourceResponseBase8lazyInitENS0_9InitLevelE(); -int APS5_VABI _ZNK7WebCore20ResourceResponseBase8mimeTypeEv(); int APS5_VABI _ZNK7WebCore20ScrollingCoordinator25scrollableContainerNodeIDERKNS_12RenderObjectE(); int APS5_VABI _ZNK7WebCore20ScrollingCoordinator36coordinatesScrollingForOverflowLayerERKNS_11RenderLayerE(); int APS5_VABI _ZNK7WebCore21BackForwardController12forwardCountEv(); diff --git a/core/libs/prx/libSceNKWebKit/include/ResourceResponseExports.hpp b/core/libs/prx/libSceNKWebKit/include/ResourceResponseExports.hpp new file mode 100644 index 0000000000..2356592e09 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/include/ResourceResponseExports.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "SceTypes.hpp" +#include "prx/libSceNKWebKit/src/ResourceResponseAbi.hpp" + +extern "C" { + +const NKWebKit::WtfAbi::URL& APS5_VABI _ZNK7WebCore20ResourceResponseBase3urlEv(const NKWebKit::WebCoreAbi::ResourceResponseBase* self); +const NKWebKit::WebCoreAbi::StringSlot& APS5_VABI _ZNK7WebCore20ResourceResponseBase8mimeTypeEv(const NKWebKit::WebCoreAbi::ResourceResponseBase* self); +long long APS5_VABI _ZNK7WebCore20ResourceResponseBase21expectedContentLengthEv(const NKWebKit::WebCoreAbi::ResourceResponseBase* self); +const NKWebKit::WebCoreAbi::StringSlot& APS5_VABI _ZNK7WebCore20ResourceResponseBase16textEncodingNameEv(const NKWebKit::WebCoreAbi::ResourceResponseBase* self); +const NKWebKit::WebCoreAbi::StringSlot& APS5_VABI _ZNK7WebCore20ResourceResponseBase14httpStatusTextEv(const NKWebKit::WebCoreAbi::ResourceResponseBase* self); +const NKWebKit::WebCoreAbi::StringSlot& APS5_VABI _ZNK7WebCore20ResourceResponseBase11httpVersionEv(const NKWebKit::WebCoreAbi::ResourceResponseBase* self); +const NKWebKit::WebCoreAbi::ResponseHeaderStorage& APS5_VABI _ZNK7WebCore20ResourceResponseBase16httpHeaderFieldsEv(const NKWebKit::WebCoreAbi::ResourceResponseBase* self); +bool APS5_VABI _ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv(const NKWebKit::WebCoreAbi::ResourceResponseBase* self); + +} diff --git a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp index 148aaddf81..63c9dd727e 100644 --- a/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp +++ b/core/libs/prx/libSceNKWebKit/include/WtfExports.hpp @@ -208,6 +208,8 @@ bool APS5_VABI _ZNK3WTF24TimeWithDynamicClockTypegeERKS0_(const WTF::TimeWithDyn bool APS5_VABI _ZNK3WTF24TimeWithDynamicClockTypegtERKS0_(const WTF::TimeWithDynamicClockType* self, const WTF::TimeWithDynamicClockType& other); bool APS5_VABI _ZNK3WTF24TimeWithDynamicClockTypeleERKS0_(const WTF::TimeWithDynamicClockType* self, const WTF::TimeWithDynamicClockType& other); WTF::Seconds APS5_VABI _ZNK3WTF24TimeWithDynamicClockTypemiERKS0_(const WTF::TimeWithDynamicClockType* self, const WTF::TimeWithDynamicClockType& other); +NKWebKit::WtfAbi::String APS5_VABI _ZN3WTF19mimeTypeFromDataURLENS_10StringViewE(NKWebKit::WtfAbi::StringView dataURL); +NKWebKit::WtfAbi::String APS5_VABI _ZNK3WTF3URL30stringCenterEllipsizedToLengthEj(const NKWebKit::WtfAbi::URL* self, unsigned length); bool APS5_VABI _ZNK3WTF3URL10protocolIsENS_10StringViewE(const NKWebKit::WtfAbi::URL* self, NKWebKit::WtfAbi::StringView protocol); NKWebKit::WtfAbi::StringView APS5_VABI _ZNK3WTF3URL11encodedUserEv(const NKWebKit::WtfAbi::URL* self); bool APS5_VABI _ZNK3WTF3URL12isAboutBlankEv(const NKWebKit::WtfAbi::URL* self); diff --git a/core/libs/prx/libSceNKWebKit/include/WtfFastBitVector.hpp b/core/libs/prx/libSceNKWebKit/include/WtfFastBitVector.hpp new file mode 100644 index 0000000000..53852bc137 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/include/WtfFastBitVector.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include + +namespace WTF { + +struct FastBitVectorWordOwner { + std::uint32_t* words = nullptr; + std::size_t numBits = 0; +}; + +struct FastBitVector { + FastBitVectorWordOwner storage; +}; + +static_assert(std::is_standard_layout_v); +static_assert(sizeof(FastBitVectorWordOwner) == 16); +static_assert(alignof(FastBitVectorWordOwner) == 8); +static_assert(offsetof(FastBitVectorWordOwner, words) == 0); +static_assert(offsetof(FastBitVectorWordOwner, numBits) == 8); +static_assert(std::is_standard_layout_v); +static_assert(sizeof(FastBitVector) == 16); +static_assert(alignof(FastBitVector) == 8); +static_assert(offsetof(FastBitVector, storage) == 0); + +} + +extern "C" { + +void APS5_VABI _ZN3WTF13FastBitVector10clearRangeEmm(WTF::FastBitVector* vector, std::size_t begin, std::size_t end); +void APS5_VABI _ZN3WTF22FastBitVectorWordOwner10resizeSlowEm(WTF::FastBitVectorWordOwner* owner, std::size_t numBits); +void APS5_VABI _ZN3WTF22FastBitVectorWordOwner13setEqualsSlowERKS0_(WTF::FastBitVectorWordOwner* owner, + const WTF::FastBitVectorWordOwner* other); + +} diff --git a/core/libs/prx/libSceNKWebKit/libSceNKWebKit.hpp b/core/libs/prx/libSceNKWebKit/libSceNKWebKit.hpp index 2f12efa557..6a7a41629d 100644 --- a/core/libs/prx/libSceNKWebKit/libSceNKWebKit.hpp +++ b/core/libs/prx/libSceNKWebKit/libSceNKWebKit.hpp @@ -4,8 +4,10 @@ #include "prx/libSceNKWebKit/include/Api.hpp" #include "prx/libSceNKWebKit/include/InventoryExports.hpp" #include "prx/libSceNKWebKit/include/JscExports.hpp" +#include "prx/libSceNKWebKit/include/ResourceResponseExports.hpp" #include "prx/libSceNKWebKit/include/WebCoreExports.hpp" #include "prx/libSceNKWebKit/include/WebKitConfigExports.hpp" #include "prx/libSceNKWebKit/include/WebKitValueExports.hpp" #include "prx/libSceNKWebKit/include/WtfExports.hpp" +#include "prx/libSceNKWebKit/include/WtfFastBitVector.hpp" #include "prx/libSceNKWebKit/src/Preferences.hpp" diff --git a/core/libs/prx/libSceNKWebKit/src/ConstantExports.cpp b/core/libs/prx/libSceNKWebKit/src/ConstantExports.cpp new file mode 100644 index 0000000000..a57ae1631b --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/ConstantExports.cpp @@ -0,0 +1,553 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +extern "C" { + +int APS5_VABI WKAccessibilityCanUseSecondaryAXThread() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListCopyItemAtIndex() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListGetBackListCount() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListGetForwardListCount() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListItemCopyChildren() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListItemCopyOriginalURL() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListItemCopyTarget() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListItemCopyTitle() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListItemCopyURL() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListItemHasCachedPageExpired() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListItemIsInBackForwardCache() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListItemIsSame() { + return 0; +} + +int APS5_VABI WKBundleBackForwardListItemIsTargetItem() { + return 0; +} + +int APS5_VABI WKBundleFileHandleCreateWithPath() { + return 0; +} + +int APS5_VABI WKBundleFrameCopyWebArchive() { + return 0; +} + +int APS5_VABI WKBundleFrameCopyWebArchiveFilteringSubframes() { + return 0; +} + +int APS5_VABI WKBundleFrameGetJavaScriptWrapperForFileForWorld() { + return 0; +} + +int APS5_VABI WKBundleNodeHandleCopyHTMLFrameElementContentFrame() { + return 0; +} + +int APS5_VABI WKBundleNodeHandleCopyHTMLTableCellElementCellAbove() { + return 0; +} + +int APS5_VABI WKBundleNodeHandleCopyVisibleRange() { + return 0; +} + +int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutoFillAvailable() { + return 0; +} + +int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutoFillButtonEnabled() { + return 0; +} + +int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutoFilled() { + return 0; +} + +int APS5_VABI WKBundleNodeHandleGetHTMLInputElementAutofilled() { + return 0; +} + +int APS5_VABI WKBundlePageCopyContextMenuAtPointInWindow() { + return 0; +} + +int APS5_VABI WKBundlePageCopyContextMenuItems() { + return 0; +} + +int APS5_VABI WKBundlePageGetBackForwardList() { + return 0; +} + +int APS5_VABI WKContextCopyPlugInAutoStartOriginHashes() { + return 0; +} + +int APS5_VABI WKContextDownloadURLRequest() { + return 0; +} + +int APS5_VABI WKContextGetIconDatabase() { + return 0; +} + +int APS5_VABI WKContextGetMaximumNumberOfProcesses() { + return static_cast(0xffffffffu); +} + +int APS5_VABI WKContextMenuCopySubmenuItems() { + return 0; +} + +int APS5_VABI WKContextMenuItemCopyTitle() { + return 0; +} + +int APS5_VABI WKContextMenuItemCreateAsAction() { + return 0; +} + +int APS5_VABI WKContextMenuItemCreateAsCheckableAction() { + return 0; +} + +int APS5_VABI WKContextMenuItemCreateAsSubmenu() { + return 0; +} + +int APS5_VABI WKContextMenuItemGetChecked() { + return 0; +} + +int APS5_VABI WKContextMenuItemGetEnabled() { + return 0; +} + +int APS5_VABI WKContextMenuItemGetTag() { + return 0; +} + +int APS5_VABI WKContextMenuItemGetType() { + return 0; +} + +int APS5_VABI WKContextMenuItemGetUserData() { + return 0; +} + +int APS5_VABI WKContextMenuItemSeparatorItem() { + return 0; +} + +int APS5_VABI WKContextResumeDownload() { + return 0; +} + +int APS5_VABI WKFrameCreateFrameInfo() { + return 0; +} + +int APS5_VABI WKFrameIsFrameSet() { + return 0; +} + +int APS5_VABI WKIconDatabaseCopyIconDataForPageURL() { + return 0; +} + +int APS5_VABI WKIconDatabaseCopyIconURLForPageURL() { + return 0; +} + +int APS5_VABI WKKeyValueStorageManagerGetCreationTimeKey() { + return 0; +} + +int APS5_VABI WKKeyValueStorageManagerGetModificationTimeKey() { + return 0; +} + +int APS5_VABI WKKeyValueStorageManagerGetOriginKey() { + return 0; +} + +int APS5_VABI WKOpenPanelParametersCopyCapture() { + return 0; +} + +int APS5_VABI WKOpenPanelParametersGetMediaCaptureType() { + return 0; +} + +int APS5_VABI WKPageGetFrameSetLargestFrame() { + return 0; +} + +int APS5_VABI WKPageIsMockRealtimeMediaSourceCenterEnabled() { + return 0; +} + +int APS5_VABI WKPlugInInformationReplacementObscuredKey() { + return 0; +} + +int APS5_VABI WKPluginInformationBundleIdentifierKey() { + return 0; +} + +int APS5_VABI WKPluginInformationBundleShortVersionKey() { + return 0; +} + +int APS5_VABI WKPluginInformationBundleVersionKey() { + return 0; +} + +int APS5_VABI WKPluginInformationDefaultLoadPolicyKey() { + return 0; +} + +int APS5_VABI WKPluginInformationDisplayNameKey() { + return 0; +} + +int APS5_VABI WKPluginInformationFrameURLKey() { + return 0; +} + +int APS5_VABI WKPluginInformationHasSandboxProfileKey() { + return 0; +} + +int APS5_VABI WKPluginInformationMIMETypeKey() { + return 0; +} + +int APS5_VABI WKPluginInformationPageURLKey() { + return 0; +} + +int APS5_VABI WKPluginInformationPathKey() { + return 0; +} + +int APS5_VABI WKPluginInformationPluginURLKey() { + return 0; +} + +int APS5_VABI WKPluginInformationPluginspageAttributeURLKey() { + return 0; +} + +int APS5_VABI WKPluginInformationUpdatePastLastBlockedVersionIsKnownAvailableKey() { + return 0; +} + +int APS5_VABI WKPreferencesGetAccelerated2DCanvasEnabled() { + return 0; +} + +int APS5_VABI WKPreferencesGetApplicationChromeModeEnabled() { + return 0; +} + +int APS5_VABI WKPreferencesGetQTKitEnabled() { + return 0; +} + +int APS5_VABI WKPreferencesGetTypeID() { + return static_cast(0x00000058u); +} + +int APS5_VABI WKUserContentExtensionStoreCreate() { + return 0; +} + +int APS5_VABI _ZN3JSC12isFromJSCodeEPv() { + return 0; +} + +int APS5_VABI _ZN3JSC22optimizeNextInvocationENS_7JSValueE() { + return static_cast(0x0000000au); +} + +int APS5_VABI _ZN3JSC22optimizeNextInvocationEPNS_14JSGlobalObjectEPNS_9CallFrameE() { + return static_cast(0x0000000au); +} + +int APS5_VABI _ZN3JSC3DFG18getNumCompilationsEv() { + return 0; +} + +int APS5_VABI _ZN3WTF14FileSystemImpl27isSafeToUseMemoryMapForPathERKNS_6StringE() { + return 1; +} + +int APS5_VABI _ZN3WTF14setCloseOnExecEi() { + return 1; +} + +int APS5_VABI _ZN3WTF15AutomaticThread11shouldSleepERKNS_14AbstractLockerE() { + return 1; +} + +int APS5_VABI _ZN7WebCore10Pasteboard15canSmartReplaceEv() { + return 0; +} + +int APS5_VABI _ZN7WebCore10Pasteboard16fileContentStateEv() { + return 0; +} + +int APS5_VABI _ZN7WebCore12BlobRegistry16blobRegistryImplEv() { + return 0; +} + +int APS5_VABI _ZN7WebCore12EventHandler18accessKeyModifiersEv() { + return 1; +} + +int APS5_VABI _ZN7WebCore13MIMETypeCache21canDecodeExtendedTypeERKNS_11ContentTypeE() { + return 0; +} + +int APS5_VABI _ZN7WebCore13MIMETypeCache26isUnsupportedContainerTypeERKN3WTF6StringE() { + return 0; +} + +int APS5_VABI _ZN7WebCore13getLogChannelERKN3WTF6StringE() { + return 0; +} + +int APS5_VABI _ZN7WebCore16ISOSchemeTypeBox11boxTypeNameEv() { + return static_cast(0x7363686du); +} + +int APS5_VABI _ZN7WebCore17JSDOMGlobalObject21offsetOfWorldIsNormalEv() { + return static_cast(0x00000930u); +} + +int APS5_VABI _ZN7WebCore18PlatformPasteboard5writeERKN3WTF6VectorINS_20PasteboardCustomDataELm0ENS1_15CrashOnOverflowELm16ENS1_10FastMallocEEE() { + return 0; +} + +int APS5_VABI _ZN7WebCore18PlatformPasteboard5writeERKNS_20PasteboardCustomDataE() { + return 0; +} + +int APS5_VABI _ZN7WebCore20ISOOriginalFormatBox11boxTypeNameEv() { + return static_cast(0x66726d61u); +} + +int APS5_VABI _ZN7WebCore21ISOTrackEncryptionBox11boxTypeNameEv() { + return static_cast(0x74656e63u); +} + +int APS5_VABI _ZN7WebCore21PlatformKeyboardEvent20currentCapsLockStateEv() { + return 0; +} + +int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient17objectContentTypeERKN3WTF3URLERKNS1_6StringE() { + return 0; +} + +int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient29hasFrameSpecificStorageAccessEv() { + return 0; +} + +int APS5_VABI _ZN7WebCore22EmptyFrameLoaderClient38dispatchDidLoadResourceFromMemoryCacheEPNS_14DocumentLoaderERKNS_15ResourceRequestERKNS_16ResourceResponseEi() { + return 0; +} + +int APS5_VABI _ZN7WebCore23ISOSchemeInformationBox11boxTypeNameEv() { + return static_cast(0x73636869u); +} + +int APS5_VABI _ZN7WebCore26ISOProtectionSchemeInfoBox11boxTypeNameEv() { + return static_cast(0x73696e66u); +} + +int APS5_VABI _ZN7WebCore27screenSupportsExtendedColorEPNS_6WidgetE() { + return 0; +} + +int APS5_VABI _ZN7WebCore36ISOProtectionSystemSpecificHeaderBox11boxTypeNameEv() { + return static_cast(0x70737368u); +} + +int APS5_VABI _ZN9Inspector18InjectedScriptHost19isHTMLAllCollectionERN3JSC2VMENS1_7JSValueE() { + return 0; +} + +int APS5_VABI _ZN9Inspector18InjectedScriptHost21getInternalPropertiesERN3JSC2VMEPNS1_14JSGlobalObjectENS1_7JSValueE() { + return 0; +} + +int APS5_VABI _ZN9Inspector18InjectedScriptHost7subtypeEPN3JSC14JSGlobalObjectENS1_7JSValueE() { + return static_cast(0x0000000au); +} + +int APS5_VABI _ZNK3JSC9CodeBlock26reoptimizationRetryCounterEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore13MIMETypeCache11isAvailableEv() { + return 1; +} + +int APS5_VABI _ZNK7WebCore14LoaderStrategy11usePingLoadEv() { + return 1; +} + +int APS5_VABI _ZNK7WebCore14LoaderStrategy27havePerformedSecurityChecksERKNS_16ResourceResponseE() { + return 0; +} + +int APS5_VABI _ZNK7WebCore14LoaderStrategy27shouldPerformSecurityChecksEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore15ActiveDOMObject25virtualHasPendingActivityEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore16TrackPrivateBase10trackIndexEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore19BlurFilterOperation11movesPixelsEv() { + return 1; +} + +int APS5_VABI _ZNK7WebCore19BlurFilterOperation14affectsOpacityEv() { + return 1; +} + +int APS5_VABI _ZNK7WebCore20LowPowerModeNotifier21isLowPowerModeEnabledEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore20ScrollingCoordinator25scrollableContainerNodeIDERKNS_12RenderObjectE() { + return 0; +} + +int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient10hasWebViewEv() { + return 1; +} + +int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient12canCachePageEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient14shouldFallBackERKNS_13ResourceErrorE() { + return 0; +} + +int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient15canShowMIMETypeERKN3WTF6StringE() { + return 0; +} + +int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient16canHandleRequestERKNS_15ResourceRequestE() { + return 0; +} + +int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient21canShowMIMETypeAsHTMLERKN3WTF6StringE() { + return 0; +} + +int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient21shouldGoToHistoryItemERNS_11HistoryItemE() { + return 0; +} + +int APS5_VABI _ZNK7WebCore22EmptyFrameLoaderClient24isEmptyFrameLoaderClientEv() { + return 1; +} + +int APS5_VABI _ZNK7WebCore24CoordinatedGraphicsLayer26isCoordinatedGraphicsLayerEv() { + return 1; +} + +int APS5_VABI _ZNK7WebCore25DropShadowFilterOperation11movesPixelsEv() { + return 1; +} + +int APS5_VABI _ZNK7WebCore25DropShadowFilterOperation14affectsOpacityEv() { + return 1; +} + +int APS5_VABI _ZNK7WebCore4Page17useDarkAppearanceEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore6Quirks25needsYouTubeMouseOutQuirkEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore6Quirks29shouldAvoidUsingIOS13ForGmailEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore6Quirks32isNeverRichlyEditableForTouchBarEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore6Quirks49isTouchBarUpdateSupressedForHiddenContentEditableEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore6Quirks50shouldIgnoreAriaForFastPathContentObservationCheckEv() { + return 0; +} + +int APS5_VABI _ZNK7WebCore8Document22touchEventHandlerCountEv() { + return 0; +} + +int APS5_VABI _ZNK7bmalloc11IsoTLSEntry9alignmentEv() { + return static_cast(0x00000008u); +} + +int APS5_VABI _ZNK9Inspector15InspectorTarget13isProvisionalEv() { + return 0; +} + +int APS5_VABI _ZNK9Inspector22RemoteAutomationTarget4typeEv() { + return 0; +} + +int APS5_VABI _ZNK9Inspector22RemoteInspectionTarget26automaticInspectionAllowedEv() { + return 0; +} + +} diff --git a/core/libs/prx/libSceNKWebKit/src/ResourceResponse.cpp b/core/libs/prx/libSceNKWebKit/src/ResourceResponse.cpp new file mode 100644 index 0000000000..12d22b0058 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/ResourceResponse.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2006, 2008, 2016 Apple Inc. All rights reserved. + * Copyright (C) 2009 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libSceNKWebKit/include/ResourceResponseExports.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include + +namespace { + +using NKWebKit::WebCoreAbi::ResourceResponseBase; + +const ResourceResponseBase& Checked(const ResourceResponseBase* self) { + if (!self) throw std::invalid_argument("null WebCore::ResourceResponseBase"); + return *self; +} + +} + +extern "C" { + +const NKWebKit::WtfAbi::URL& APS5_VABI _ZNK7WebCore20ResourceResponseBase3urlEv(const ResourceResponseBase* self) { + return Checked(self).url; +} + +const NKWebKit::WebCoreAbi::StringSlot& APS5_VABI _ZNK7WebCore20ResourceResponseBase8mimeTypeEv(const ResourceResponseBase* self) { + return Checked(self).mimeType; +} + +long long APS5_VABI _ZNK7WebCore20ResourceResponseBase21expectedContentLengthEv(const ResourceResponseBase* self) { + return Checked(self).expectedContentLength; +} + +const NKWebKit::WebCoreAbi::StringSlot& APS5_VABI _ZNK7WebCore20ResourceResponseBase16textEncodingNameEv(const ResourceResponseBase* self) { + return Checked(self).textEncodingName; +} + +const NKWebKit::WebCoreAbi::StringSlot& APS5_VABI _ZNK7WebCore20ResourceResponseBase14httpStatusTextEv(const ResourceResponseBase* self) { + return Checked(self).httpStatusText; +} + +const NKWebKit::WebCoreAbi::StringSlot& APS5_VABI _ZNK7WebCore20ResourceResponseBase11httpVersionEv(const ResourceResponseBase* self) { + return Checked(self).httpVersion; +} + +const NKWebKit::WebCoreAbi::ResponseHeaderStorage& APS5_VABI _ZNK7WebCore20ResourceResponseBase16httpHeaderFieldsEv(const ResourceResponseBase* self) { + return Checked(self).httpHeaderFields; +} + +bool APS5_VABI _ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv(const ResourceResponseBase* self) { + const auto& url = Checked(self).url; + if (!url.impl || !(url.bits & 1)) return false; + NKWebKit::WtfAbi::StringView view{url.impl->data, url.impl->length, !!(url.impl->hashAndFlags & 4)}; + return _ZN3WTF22protocolIsInHTTPFamilyENS_10StringViewE(view); +} + +} diff --git a/core/libs/prx/libSceNKWebKit/src/ResourceResponseAbi.hpp b/core/libs/prx/libSceNKWebKit/src/ResourceResponseAbi.hpp new file mode 100644 index 0000000000..3a67cf5c78 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/ResourceResponseAbi.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "prx/libSceNKWebKit/src/WtfStringAbi.hpp" + +#include + +namespace NKWebKit::WebCoreAbi { + +struct StringSlot { + WtfAbi::StringImpl* impl; +}; + +struct ResponseHeaderStorage { + alignas(8) unsigned char storage[32]; +}; + +struct ResourceResponseBase { + WtfAbi::URL url; + StringSlot mimeType; + long long expectedContentLength; + StringSlot textEncodingName; + StringSlot httpStatusText; + StringSlot httpVersion; + ResponseHeaderStorage httpHeaderFields; +}; + +static_assert(sizeof(StringSlot) == 8); +static_assert(sizeof(ResponseHeaderStorage) == 32); +static_assert(offsetof(ResourceResponseBase, url) == 0); +static_assert(offsetof(ResourceResponseBase, mimeType) == 40); +static_assert(offsetof(ResourceResponseBase, expectedContentLength) == 48); +static_assert(offsetof(ResourceResponseBase, textEncodingName) == 56); +static_assert(offsetof(ResourceResponseBase, httpStatusText) == 64); +static_assert(offsetof(ResourceResponseBase, httpVersion) == 72); +static_assert(offsetof(ResourceResponseBase, httpHeaderFields) == 80); + +} diff --git a/core/libs/prx/libSceNKWebKit/src/WtfFastBitVector.cpp b/core/libs/prx/libSceNKWebKit/src/WtfFastBitVector.cpp new file mode 100644 index 0000000000..a3eb1e1b61 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/src/WtfFastBitVector.cpp @@ -0,0 +1,70 @@ +#include "prx/libSceNKWebKit/include/WtfFastBitVector.hpp" +#include "prx/libSceNKWebKit/include/WtfExports.hpp" + +#include +#include +#include +#include + +namespace { + +std::size_t WordCount(std::size_t bits) { + if (bits > std::numeric_limits::max() - 31) + throw std::length_error("FastBitVector: bit count overflows word rounding"); + return (bits + 31) / 32; +} + +std::size_t CheckedWordCount(const WTF::FastBitVectorWordOwner* owner) { + if (!owner) throw std::invalid_argument("FastBitVector: null word owner"); + const auto count = WordCount(owner->numBits); + if (count && !owner->words) throw std::invalid_argument("FastBitVector: null nonempty word storage"); + return count; +} + +} + +extern "C" void APS5_VABI _ZN3WTF13FastBitVector10clearRangeEmm(WTF::FastBitVector* vector, + std::size_t begin, std::size_t end) { + if (!vector) throw std::invalid_argument("FastBitVector::clearRange: null vector"); + auto& storage = vector->storage; + if (begin > end || end > storage.numBits) + throw std::out_of_range("FastBitVector::clearRange: invalid bit range"); + if (begin == end) return; + WordCount(storage.numBits); + if (!storage.words) throw std::invalid_argument("FastBitVector::clearRange: null words"); + const auto firstWord = begin / 32; + const auto lastWord = (end - 1) / 32; + const auto firstMask = std::numeric_limits::max() << (begin % 32); + const auto lastBits = end % 32; + const auto lastMask = lastBits ? (std::uint32_t{1} << lastBits) - 1 : std::numeric_limits::max(); + if (firstWord == lastWord) { + storage.words[firstWord] &= ~(firstMask & lastMask); + return; + } + storage.words[firstWord] &= ~firstMask; + std::fill_n(storage.words + firstWord + 1, lastWord - firstWord - 1, std::uint32_t{0}); + storage.words[lastWord] &= ~lastMask; +} + +extern "C" void APS5_VABI _ZN3WTF22FastBitVectorWordOwner10resizeSlowEm(WTF::FastBitVectorWordOwner* owner, + std::size_t numBits) { + const auto oldCount = CheckedWordCount(owner); + const auto newCount = WordCount(numBits); + if (newCount < oldCount) throw std::invalid_argument("FastBitVector::resizeSlow: shrinking word storage"); + auto* words = static_cast(_ZN3WTF10fastMallocEm(newCount * sizeof(std::uint32_t))); + if (oldCount) std::memcpy(words, owner->words, oldCount * sizeof(std::uint32_t)); + std::fill_n(words + oldCount, newCount - oldCount, std::uint32_t{0}); + _ZN3WTF8fastFreeEPv(owner->words); + owner->words = words; +} + +extern "C" void APS5_VABI _ZN3WTF22FastBitVectorWordOwner13setEqualsSlowERKS0_(WTF::FastBitVectorWordOwner* owner, + const WTF::FastBitVectorWordOwner* other) { + CheckedWordCount(owner); + const auto count = CheckedWordCount(other); + auto* words = static_cast(_ZN3WTF10fastMallocEm(count * sizeof(std::uint32_t))); + if (count) std::memcpy(words, other->words, count * sizeof(std::uint32_t)); + _ZN3WTF8fastFreeEPv(owner->words); + owner->words = words; + owner->numBits = other->numBits; +} diff --git a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp index 11d3aad8c7..0ec203f32c 100644 --- a/core/libs/prx/libSceNKWebKit/src/WtfString.cpp +++ b/core/libs/prx/libSceNKWebKit/src/WtfString.cpp @@ -916,6 +916,41 @@ StringView ViewWithoutFragment(const URL& url) { return Slice(view, 0, std::min(url.queryEnd, view.length)); } +std::size_t FindUnit(StringView view, char16_t value, unsigned start) { + for (unsigned i = start; i < view.length; ++i) { + if (Unit(view, i) == value) return i; + } + return NotFound; +} + +StringView LeftView(StringView view, unsigned length) { + return Slice(view, 0, length); +} + +StringView RightView(StringView view, unsigned length) { + return Slice(view, view.length - length, length); +} + +String ConcatViews(const StringView* parts, unsigned count) { + std::size_t total = 0; + bool is8Bit = true; + for (unsigned i = 0; i < count; ++i) { + total += parts[i].length; + is8Bit &= parts[i].is8Bit; + } + if (total > MaxStringLength) throw std::length_error("WTF string exceeds maximum length"); + void* data; + auto* impl = NewImpl(static_cast(total), is8Bit, data); + unsigned offset = 0; + for (unsigned i = 0; i < count; ++i) { + for (unsigned j = 0; j < parts[i].length; ++j, ++offset) { + if (is8Bit) static_cast(data)[offset] = static_cast(Unit(parts[i], j)); + else static_cast(data)[offset] = Unit(parts[i], j); + } + } + return String(impl); +} + } // namespace extern "C" { @@ -1022,4 +1057,20 @@ bool APS5_VABI _ZN3WTF22protocolIsInHTTPFamilyENS_10StringViewE(StringView view) return Unit(view, 4) == ':' || ((Unit(view, 4) | 32) == 's' && view.length >= 6 && Unit(view, 5) == ':'); } +String APS5_VABI _ZN3WTF19mimeTypeFromDataURLENS_10StringViewE(StringView dataURL) { + auto index = FindUnit(dataURL, u';', 5); + if (index == NotFound) index = FindUnit(dataURL, u',', 5); + if (index == NotFound) return String(EmptyImpl()); + if (index == 5) return String(CopyImpl({"text/plain", 10, true})); + return String(ConvertCase(Slice(dataURL, 5, static_cast(index - 5)), true)); +} + +String APS5_VABI _ZNK3WTF3URL30stringCenterEllipsizedToLengthEj(const URL* self, unsigned length) { + const auto& url = Checked(self); + auto view = View(url.impl); + if (view.length <= length) return String(Retain(url.impl)); + const StringView parts[] = {LeftView(view, length / 2 - 1), {"...", 3, true}, RightView(view, length / 2 - 2)}; + return ConcatViews(parts, 3); +} + } diff --git a/core/libs/prx/libSceNKWebKit/tests/Constants.cpp b/core/libs/prx/libSceNKWebKit/tests/Constants.cpp new file mode 100644 index 0000000000..7f778e0529 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/Constants.cpp @@ -0,0 +1,155 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/libSceNKWebKit.hpp" + +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "libSceNKWebKit constant check failed at line %d\n", line); + std::abort(); + } +} + +#define Require(value) Check((value), __LINE__) + +int main() { + Require(WKAccessibilityCanUseSecondaryAXThread() == 0); + Require(WKBundleBackForwardListCopyItemAtIndex() == 0); + Require(WKBundleBackForwardListGetBackListCount() == 0); + Require(WKBundleBackForwardListGetForwardListCount() == 0); + Require(WKBundleBackForwardListItemCopyChildren() == 0); + Require(WKBundleBackForwardListItemCopyOriginalURL() == 0); + Require(WKBundleBackForwardListItemCopyTarget() == 0); + Require(WKBundleBackForwardListItemCopyTitle() == 0); + Require(WKBundleBackForwardListItemCopyURL() == 0); + Require(WKBundleBackForwardListItemHasCachedPageExpired() == 0); + Require(WKBundleBackForwardListItemIsInBackForwardCache() == 0); + Require(WKBundleBackForwardListItemIsSame() == 0); + Require(WKBundleBackForwardListItemIsTargetItem() == 0); + Require(WKBundleFileHandleCreateWithPath() == 0); + Require(WKBundleFrameCopyWebArchive() == 0); + Require(WKBundleFrameCopyWebArchiveFilteringSubframes() == 0); + Require(WKBundleFrameGetJavaScriptWrapperForFileForWorld() == 0); + Require(WKBundleNodeHandleCopyHTMLFrameElementContentFrame() == 0); + Require(WKBundleNodeHandleCopyHTMLTableCellElementCellAbove() == 0); + Require(WKBundleNodeHandleCopyVisibleRange() == 0); + Require(WKBundleNodeHandleGetHTMLInputElementAutoFillAvailable() == 0); + Require(WKBundleNodeHandleGetHTMLInputElementAutoFillButtonEnabled() == 0); + Require(WKBundleNodeHandleGetHTMLInputElementAutoFilled() == 0); + Require(WKBundleNodeHandleGetHTMLInputElementAutofilled() == 0); + Require(WKBundlePageCopyContextMenuAtPointInWindow() == 0); + Require(WKBundlePageCopyContextMenuItems() == 0); + Require(WKBundlePageGetBackForwardList() == 0); + Require(WKContextCopyPlugInAutoStartOriginHashes() == 0); + Require(WKContextDownloadURLRequest() == 0); + Require(WKContextGetIconDatabase() == 0); + Require(WKContextGetMaximumNumberOfProcesses() == static_cast(0xffffffffu)); + Require(WKContextMenuCopySubmenuItems() == 0); + Require(WKContextMenuItemCopyTitle() == 0); + Require(WKContextMenuItemCreateAsAction() == 0); + Require(WKContextMenuItemCreateAsCheckableAction() == 0); + Require(WKContextMenuItemCreateAsSubmenu() == 0); + Require(WKContextMenuItemGetChecked() == 0); + Require(WKContextMenuItemGetEnabled() == 0); + Require(WKContextMenuItemGetTag() == 0); + Require(WKContextMenuItemGetType() == 0); + Require(WKContextMenuItemGetUserData() == 0); + Require(WKContextMenuItemSeparatorItem() == 0); + Require(WKContextResumeDownload() == 0); + Require(WKFrameCreateFrameInfo() == 0); + Require(WKFrameIsFrameSet() == 0); + Require(WKIconDatabaseCopyIconDataForPageURL() == 0); + Require(WKIconDatabaseCopyIconURLForPageURL() == 0); + Require(WKKeyValueStorageManagerGetCreationTimeKey() == 0); + Require(WKKeyValueStorageManagerGetModificationTimeKey() == 0); + Require(WKKeyValueStorageManagerGetOriginKey() == 0); + Require(WKOpenPanelParametersCopyCapture() == 0); + Require(WKOpenPanelParametersGetMediaCaptureType() == 0); + Require(WKPageGetFrameSetLargestFrame() == 0); + Require(WKPageIsMockRealtimeMediaSourceCenterEnabled() == 0); + Require(WKPlugInInformationReplacementObscuredKey() == 0); + Require(WKPluginInformationBundleIdentifierKey() == 0); + Require(WKPluginInformationBundleShortVersionKey() == 0); + Require(WKPluginInformationBundleVersionKey() == 0); + Require(WKPluginInformationDefaultLoadPolicyKey() == 0); + Require(WKPluginInformationDisplayNameKey() == 0); + Require(WKPluginInformationFrameURLKey() == 0); + Require(WKPluginInformationHasSandboxProfileKey() == 0); + Require(WKPluginInformationMIMETypeKey() == 0); + Require(WKPluginInformationPageURLKey() == 0); + Require(WKPluginInformationPathKey() == 0); + Require(WKPluginInformationPluginURLKey() == 0); + Require(WKPluginInformationPluginspageAttributeURLKey() == 0); + Require(WKPluginInformationUpdatePastLastBlockedVersionIsKnownAvailableKey() == 0); + Require(WKPreferencesGetAccelerated2DCanvasEnabled() == 0); + Require(WKPreferencesGetApplicationChromeModeEnabled() == 0); + Require(WKPreferencesGetQTKitEnabled() == 0); + Require(WKPreferencesGetTypeID() == static_cast(0x00000058u)); + Require(WKUserContentExtensionStoreCreate() == 0); + Require(_ZN3JSC12isFromJSCodeEPv() == 0); + Require(_ZN3JSC22optimizeNextInvocationENS_7JSValueE() == static_cast(0x0000000au)); + Require(_ZN3JSC22optimizeNextInvocationEPNS_14JSGlobalObjectEPNS_9CallFrameE() == static_cast(0x0000000au)); + Require(_ZN3JSC3DFG18getNumCompilationsEv() == 0); + Require((_ZN3WTF14FileSystemImpl27isSafeToUseMemoryMapForPathERKNS_6StringE() & 0xff) == 1); + Require((_ZN3WTF14setCloseOnExecEi() & 0xff) == 1); + Require((_ZN3WTF15AutomaticThread11shouldSleepERKNS_14AbstractLockerE() & 0xff) == 1); + Require(_ZN7WebCore10Pasteboard15canSmartReplaceEv() == 0); + Require(_ZN7WebCore10Pasteboard16fileContentStateEv() == 0); + Require(_ZN7WebCore12BlobRegistry16blobRegistryImplEv() == 0); + Require((_ZN7WebCore12EventHandler18accessKeyModifiersEv() & 0xff) == 1); + Require(_ZN7WebCore13MIMETypeCache21canDecodeExtendedTypeERKNS_11ContentTypeE() == 0); + Require(_ZN7WebCore13MIMETypeCache26isUnsupportedContainerTypeERKN3WTF6StringE() == 0); + Require(_ZN7WebCore13getLogChannelERKN3WTF6StringE() == 0); + Require(_ZN7WebCore16ISOSchemeTypeBox11boxTypeNameEv() == static_cast(0x7363686du)); + Require(_ZN7WebCore17JSDOMGlobalObject21offsetOfWorldIsNormalEv() == static_cast(0x00000930u)); + Require(_ZN7WebCore18PlatformPasteboard5writeERKN3WTF6VectorINS_20PasteboardCustomDataELm0ENS1_15CrashOnOverflowELm16ENS1_10FastMallocEEE() == 0); + Require(_ZN7WebCore18PlatformPasteboard5writeERKNS_20PasteboardCustomDataE() == 0); + Require(_ZN7WebCore20ISOOriginalFormatBox11boxTypeNameEv() == static_cast(0x66726d61u)); + Require(_ZN7WebCore21ISOTrackEncryptionBox11boxTypeNameEv() == static_cast(0x74656e63u)); + Require(_ZN7WebCore21PlatformKeyboardEvent20currentCapsLockStateEv() == 0); + Require(_ZN7WebCore22EmptyFrameLoaderClient17objectContentTypeERKN3WTF3URLERKNS1_6StringE() == 0); + Require(_ZN7WebCore22EmptyFrameLoaderClient29hasFrameSpecificStorageAccessEv() == 0); + Require(_ZN7WebCore22EmptyFrameLoaderClient38dispatchDidLoadResourceFromMemoryCacheEPNS_14DocumentLoaderERKNS_15ResourceRequestERKNS_16ResourceResponseEi() == 0); + Require(_ZN7WebCore23ISOSchemeInformationBox11boxTypeNameEv() == static_cast(0x73636869u)); + Require(_ZN7WebCore26ISOProtectionSchemeInfoBox11boxTypeNameEv() == static_cast(0x73696e66u)); + Require(_ZN7WebCore27screenSupportsExtendedColorEPNS_6WidgetE() == 0); + Require(_ZN7WebCore36ISOProtectionSystemSpecificHeaderBox11boxTypeNameEv() == static_cast(0x70737368u)); + Require(_ZN9Inspector18InjectedScriptHost19isHTMLAllCollectionERN3JSC2VMENS1_7JSValueE() == 0); + Require(_ZN9Inspector18InjectedScriptHost21getInternalPropertiesERN3JSC2VMEPNS1_14JSGlobalObjectENS1_7JSValueE() == 0); + Require(_ZN9Inspector18InjectedScriptHost7subtypeEPN3JSC14JSGlobalObjectENS1_7JSValueE() == static_cast(0x0000000au)); + Require(_ZNK3JSC9CodeBlock26reoptimizationRetryCounterEv() == 0); + Require((_ZNK7WebCore13MIMETypeCache11isAvailableEv() & 0xff) == 1); + Require((_ZNK7WebCore14LoaderStrategy11usePingLoadEv() & 0xff) == 1); + Require(_ZNK7WebCore14LoaderStrategy27havePerformedSecurityChecksERKNS_16ResourceResponseE() == 0); + Require(_ZNK7WebCore14LoaderStrategy27shouldPerformSecurityChecksEv() == 0); + Require(_ZNK7WebCore15ActiveDOMObject25virtualHasPendingActivityEv() == 0); + Require(_ZNK7WebCore16TrackPrivateBase10trackIndexEv() == 0); + Require((_ZNK7WebCore19BlurFilterOperation11movesPixelsEv() & 0xff) == 1); + Require((_ZNK7WebCore19BlurFilterOperation14affectsOpacityEv() & 0xff) == 1); + Require(_ZNK7WebCore20LowPowerModeNotifier21isLowPowerModeEnabledEv() == 0); + Require(_ZNK7WebCore20ScrollingCoordinator25scrollableContainerNodeIDERKNS_12RenderObjectE() == 0); + Require((_ZNK7WebCore22EmptyFrameLoaderClient10hasWebViewEv() & 0xff) == 1); + Require(_ZNK7WebCore22EmptyFrameLoaderClient12canCachePageEv() == 0); + Require(_ZNK7WebCore22EmptyFrameLoaderClient14shouldFallBackERKNS_13ResourceErrorE() == 0); + Require(_ZNK7WebCore22EmptyFrameLoaderClient15canShowMIMETypeERKN3WTF6StringE() == 0); + Require(_ZNK7WebCore22EmptyFrameLoaderClient16canHandleRequestERKNS_15ResourceRequestE() == 0); + Require(_ZNK7WebCore22EmptyFrameLoaderClient21canShowMIMETypeAsHTMLERKN3WTF6StringE() == 0); + Require(_ZNK7WebCore22EmptyFrameLoaderClient21shouldGoToHistoryItemERNS_11HistoryItemE() == 0); + Require((_ZNK7WebCore22EmptyFrameLoaderClient24isEmptyFrameLoaderClientEv() & 0xff) == 1); + Require((_ZNK7WebCore24CoordinatedGraphicsLayer26isCoordinatedGraphicsLayerEv() & 0xff) == 1); + Require((_ZNK7WebCore25DropShadowFilterOperation11movesPixelsEv() & 0xff) == 1); + Require((_ZNK7WebCore25DropShadowFilterOperation14affectsOpacityEv() & 0xff) == 1); + Require(_ZNK7WebCore4Page17useDarkAppearanceEv() == 0); + Require(_ZNK7WebCore6Quirks25needsYouTubeMouseOutQuirkEv() == 0); + Require(_ZNK7WebCore6Quirks29shouldAvoidUsingIOS13ForGmailEv() == 0); + Require(_ZNK7WebCore6Quirks32isNeverRichlyEditableForTouchBarEv() == 0); + Require(_ZNK7WebCore6Quirks49isTouchBarUpdateSupressedForHiddenContentEditableEv() == 0); + Require(_ZNK7WebCore6Quirks50shouldIgnoreAriaForFastPathContentObservationCheckEv() == 0); + Require(_ZNK7WebCore8Document22touchEventHandlerCountEv() == 0); + Require(_ZNK7bmalloc11IsoTLSEntry9alignmentEv() == static_cast(0x00000008u)); + Require(_ZNK9Inspector15InspectorTarget13isProvisionalEv() == 0); + Require(_ZNK9Inspector22RemoteAutomationTarget4typeEv() == 0); + Require(_ZNK9Inspector22RemoteInspectionTarget26automaticInspectionAllowedEv() == 0); + return 0; +} diff --git a/core/libs/prx/libSceNKWebKit/tests/FastBitVector.cpp b/core/libs/prx/libSceNKWebKit/tests/FastBitVector.cpp new file mode 100644 index 0000000000..9fb4588aeb --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/FastBitVector.cpp @@ -0,0 +1,198 @@ +#include "prx/libSceNKWebKit/libSceNKWebKit.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "FastBitVector check failed at line %u\n", location.line()); + std::abort(); + } +} + +using ClearRange = void (APS5_VABI *)(WTF::FastBitVector*, std::size_t, std::size_t); +static_assert(std::is_same_v); +using Resize = void (APS5_VABI *)(WTF::FastBitVectorWordOwner*, std::size_t); +using Copy = void (APS5_VABI *)(WTF::FastBitVectorWordOwner*, const WTF::FastBitVectorWordOwner*); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +struct GuardedOwner { + std::uint64_t before = 0x123456789ABCDEF0; + WTF::FastBitVectorWordOwner owner; + std::uint64_t after = 0xFEDCBA9876543210; + + GuardedOwner(std::size_t bits) { + owner.numBits = bits; + const auto count = (bits + 31) / 32; + if (count) { + owner.words = static_cast(_ZN3WTF10fastMallocEm(count * sizeof(std::uint32_t))); + for (std::size_t i = 0; i < count; ++i) owner.words[i] = 0xA5A50000u + static_cast(i); + } + } + + ~GuardedOwner() { + _ZN3WTF8fastFreeEPv(owner.words); + } + + void CheckGuards() const { + Require(before == 0x123456789ABCDEF0 && after == 0xFEDCBA9876543210); + } +}; + +template +void RequireThrows(Function function) { + bool threw = false; + try { + function(); + } catch (const std::exception&) { + threw = true; + } + Require(threw); +} + +void TestClearRange() { + ClearRange clear = &_ZN3WTF13FastBitVector10clearRangeEmm; + std::mt19937 random(13117); + std::size_t cases = 0; + for (std::size_t bits = 0; bits <= 130; ++bits) { + const auto words = (bits + 31) / 32; + for (std::size_t begin = 0; begin <= bits; ++begin) { + for (std::size_t end = begin; end <= bits; ++end) { + std::vector guarded(words + 2); + for (auto& word : guarded) word = random(); + const auto before = guarded; + WTF::FastBitVector vector{{guarded.data() + 1, bits}}; + const auto original = vector; + clear(&vector, begin, end); + Require(vector.storage.words == original.storage.words && vector.storage.numBits == bits); + Require(guarded.front() == before.front() && guarded.back() == before.back()); + for (std::size_t bit = 0; bit < words * 32; ++bit) { + const auto mask = std::uint32_t{1} << (bit % 32); + const bool expected = bit >= begin && bit < end ? false : (before[bit / 32 + 1] & mask) != 0; + Require(((guarded[bit / 32 + 1] & mask) != 0) == expected); + } + ++cases; + } + } + } + WTF::FastBitVector empty; + clear(&empty, 0, 0); + RequireThrows([&] { clear(nullptr, 0, 0); }); + RequireThrows([&] { clear(&empty, 0, 1); }); + RequireThrows([&] { clear(&empty, 1, 0); }); + WTF::FastBitVector missing{{nullptr, 64}}; + RequireThrows([&] { clear(&missing, 0, 64); }); + std::array words{0x12345678, 0xABCDEF01}; + const auto before = words; + WTF::FastBitVector guarded{{words.data(), 63}}; + RequireThrows([&] { clear(&guarded, 0, 64); }); + RequireThrows([&] { clear(&guarded, 32, 31); }); + WTF::FastBitVector overflow{{words.data(), std::numeric_limits::max()}}; + RequireThrows([&] { clear(&overflow, 0, 1); }); + Require(words == before); + std::printf("FastBitVector clearRange: %zu exhaustive interval cases passed\n", cases); +} + +void TestResize() { + Resize resize = &_ZN3WTF22FastBitVectorWordOwner10resizeSlowEm; + std::size_t cases = 0; + for (std::size_t bits = 0; bits <= 130; ++bits) { + const auto oldCount = (bits + 31) / 32; + for (std::size_t newBits = 0; newBits <= 260; ++newBits) { + const auto newCount = (newBits + 31) / 32; + if (newCount < oldCount) continue; + GuardedOwner value(bits); + const auto oldWords = value.owner.words; + resize(&value.owner, newBits); + value.CheckGuards(); + Require(value.owner.numBits == bits && value.owner.words != nullptr); + Require(value.owner.words != oldWords); + Require(_ZN3WTF14fastMallocSizeEPKv(value.owner.words) == newCount * sizeof(std::uint32_t)); + for (std::size_t i = 0; i < oldCount; ++i) + Require(value.owner.words[i] == 0xA5A50000u + static_cast(i)); + for (std::size_t i = oldCount; i < newCount; ++i) Require(value.owner.words[i] == 0); + value.owner.numBits = newBits; + ++cases; + } + } + GuardedOwner value(65); + const auto original = value.owner; + const std::array contents{value.owner.words[0], value.owner.words[1], value.owner.words[2]}; + RequireThrows([&] { resize(nullptr, 0); }); + RequireThrows([&] { resize(&value.owner, 32); }); + RequireThrows([&] { resize(&value.owner, std::numeric_limits::max()); }); + Require(value.owner.words == original.words && value.owner.numBits == original.numBits); + Require(std::memcmp(value.owner.words, contents.data(), sizeof(contents)) == 0); + value.CheckGuards(); + WTF::FastBitVectorWordOwner missing{nullptr, 1}; + RequireThrows([&] { resize(&missing, 64); }); + Require(missing.words == nullptr && missing.numBits == 1); + std::printf("FastBitVector resizeSlow: %zu boundary and growth cases passed\n", cases); +} + +void TestCopy() { + Copy copy = &_ZN3WTF22FastBitVectorWordOwner13setEqualsSlowERKS0_; + std::size_t cases = 0; + for (std::size_t bits = 0; bits <= 130; ++bits) { + for (std::size_t oldBits = 0; oldBits <= 130; ++oldBits) { + GuardedOwner source(bits); + GuardedOwner target(oldBits); + const auto sourcePointer = source.owner.words; + copy(&target.owner, &source.owner); + source.CheckGuards(); + target.CheckGuards(); + Require(target.owner.numBits == bits && target.owner.words != nullptr); + Require(source.owner.words == sourcePointer && source.owner.numBits == bits); + Require(target.owner.words != source.owner.words); + const auto count = (bits + 31) / 32; + Require(_ZN3WTF14fastMallocSizeEPKv(target.owner.words) == count * sizeof(std::uint32_t)); + for (std::size_t i = 0; i < count; ++i) Require(target.owner.words[i] == source.owner.words[i]); + if (count) { + target.owner.words[count - 1] ^= 1; + Require(target.owner.words[count - 1] != source.owner.words[count - 1]); + } + ++cases; + } + GuardedOwner self(bits); + copy(&self.owner, &self.owner); + self.CheckGuards(); + Require(self.owner.numBits == bits); + for (std::size_t i = 0; i < (bits + 31) / 32; ++i) + Require(self.owner.words[i] == 0xA5A50000u + static_cast(i)); + } + GuardedOwner source(33); + GuardedOwner target(65); + const auto original = target.owner; + RequireThrows([&] { copy(nullptr, &source.owner); }); + RequireThrows([&] { copy(&target.owner, nullptr); }); + WTF::FastBitVectorWordOwner missing{nullptr, 1}; + RequireThrows([&] { copy(&target.owner, &missing); }); + WTF::FastBitVectorWordOwner overflow{nullptr, std::numeric_limits::max()}; + RequireThrows([&] { copy(&target.owner, &overflow); }); + Require(target.owner.words == original.words && target.owner.numBits == original.numBits); + for (std::size_t i = 0; i < 3; ++i) + Require(target.owner.words[i] == 0xA5A50000u + static_cast(i)); + target.CheckGuards(); + std::printf("FastBitVector setEqualsSlow: %zu copy cases and 131 self-copies passed\n", cases); +} + +} + +int main() { + TestClearRange(); + TestResize(); + TestCopy(); +} diff --git a/core/libs/prx/libSceNKWebKit/tests/ResourceResponse.cpp b/core/libs/prx/libSceNKWebKit/tests/ResourceResponse.cpp new file mode 100644 index 0000000000..7ba192daf9 --- /dev/null +++ b/core/libs/prx/libSceNKWebKit/tests/ResourceResponse.cpp @@ -0,0 +1,89 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceNKWebKit/libSceNKWebKit.hpp" + +#include +#include +#include +#include + +using NKWebKit::WebCoreAbi::ResourceResponseBase; +using NKWebKit::WtfAbi::StringImpl; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "ResourceResponseBase check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static StringImpl Make(const char* text) { + return StringImpl{2, static_cast(std::strlen(text)), text, 4}; +} + +static void TestFields() { + auto urlText = Make("http://a/"); + auto mime = Make("text/html"); + auto encoding = Make("utf-8"); + auto status = Make("OK"); + auto version = Make("HTTP/1.1"); + ResourceResponseBase response{}; + response.url.impl = &urlText; + response.url.bits = 1 | (4 << 6); + response.mimeType.impl = &mime; + response.expectedContentLength = 1234567890123LL; + response.textEncodingName.impl = &encoding; + response.httpStatusText.impl = &status; + response.httpVersion.impl = &version; + response.httpHeaderFields.storage[3] = 9; + + Require(&_ZNK7WebCore20ResourceResponseBase3urlEv(&response) == &response.url); + Require(&_ZNK7WebCore20ResourceResponseBase8mimeTypeEv(&response) == &response.mimeType); + Require(_ZNK7WebCore20ResourceResponseBase21expectedContentLengthEv(&response) == 1234567890123LL); + Require(&_ZNK7WebCore20ResourceResponseBase16textEncodingNameEv(&response) == &response.textEncodingName); + Require(&_ZNK7WebCore20ResourceResponseBase14httpStatusTextEv(&response) == &response.httpStatusText); + Require(&_ZNK7WebCore20ResourceResponseBase11httpVersionEv(&response) == &response.httpVersion); + Require(&_ZNK7WebCore20ResourceResponseBase16httpHeaderFieldsEv(&response) == &response.httpHeaderFields); + Require(_ZNK7WebCore20ResourceResponseBase16httpHeaderFieldsEv(&response).storage[3] == 9); + Require(urlText.refCount == 2 && mime.refCount == 2); + response.expectedContentLength = -1; + Require(_ZNK7WebCore20ResourceResponseBase21expectedContentLengthEv(&response) == -1); +} + +static void TestHTTPFamily() { + auto http = Make("http://a/"); + auto https = Make("HTTPS://a/"); + auto file = Make("file:///a"); + auto ftp = Make("ftp://a/"); + ResourceResponseBase response{}; + Require(!_ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv(&response)); + response.url.impl = &http; + response.url.bits = 1 | (4 << 6); + Require(_ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv(&response)); + response.url.impl = &https; + Require(_ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv(&response)); + response.url.impl = &file; + Require(!_ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv(&response)); + response.url.impl = &ftp; + Require(!_ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv(&response)); + response.url.impl = &http; + response.url.bits = 0; + Require(!_ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv(&response)); +} + +static void TestNull() { + bool thrown = false; + try { + _ZNK7WebCore20ResourceResponseBase3urlEv(nullptr); + } catch (const std::invalid_argument&) { + thrown = true; + } + Require(thrown); +} + +int main() { + TestFields(); + TestHTTPFamily(); + TestNull(); + return 0; +} diff --git a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp index a3b14714dd..8508298479 100644 --- a/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp +++ b/core/libs/prx/libSceNKWebKit/tests/Wtf.cpp @@ -809,6 +809,80 @@ const auto JSRealloc = &_ZN3WTF14jsValueReallocEPvm; std::string ViewText(StringView view) { return Bytes(ViewUTF8(&view, 1)); } +const auto URLEllipsize = &_ZNK3WTF3URL30stringCenterEllipsizedToLengthEj; +const auto DataURLMime = &_ZN3WTF19mimeTypeFromDataURLENS_10StringViewE; + +bool Is8Bit(const String& value) { return value.impl && (value.impl->hashAndFlags & 4); } + +StringView ViewOf(const String& value) { + return {value.impl->data, value.impl->length, !!(value.impl->hashAndFlags & 4)}; +} + +StringView Literal(const char* text) { return {text, static_cast(std::strlen(text)), true}; } + +void TestURLStringHelpers() { + auto narrow = FromUTF8Z(U8("http://example.com/abcdefghij")); + URL narrowURL{narrow.impl, 0, 0, 0, 0, 0, 0, 0, 0}; + for (unsigned length : {29U, 30U, 40U, 0xFFFFFFFFU}) { + auto same = URLEllipsize(&narrowURL, length); + Check(same.impl == narrow.impl && narrow.impl->refCount == 4); + } + Check(narrow.impl->refCount == 2); + Text(URLEllipsize(&narrowURL, 28), "http://exampl...m/abcdefghij"); + Text(URLEllipsize(&narrowURL, 10), "http...hij"); + Text(URLEllipsize(&narrowURL, 11), "http...hij"); + Text(URLEllipsize(&narrowURL, 6), "ht...j"); + Text(URLEllipsize(&narrowURL, 5), "h..."); + Text(URLEllipsize(&narrowURL, 4), "h..."); + Text(URLEllipsize(&narrowURL, 3), "..."); + Text(URLEllipsize(&narrowURL, 2), "..."); + Text(URLEllipsize(&narrowURL, 1), "http://example.com/abcdefghij..."); + Text(URLEllipsize(&narrowURL, 0), "http://example.com/abcdefghij..."); + Check(Is8Bit(URLEllipsize(&narrowURL, 10)) && Is8Bit(URLEllipsize(&narrowURL, 3))); + Check(URLEllipsize(&narrowURL, 10).impl != narrow.impl); + + auto wide = Create16(u"http://wide.test/path", 21); + URL wideURL{wide.impl, 0, 0, 0, 0, 0, 0, 0, 0}; + Check(URLEllipsize(&wideURL, 21).impl == wide.impl); + auto wideShort = URLEllipsize(&wideURL, 10); + Text(wideShort, "http...ath"); + Check(!Is8Bit(wideShort)); + auto wideDots = URLEllipsize(&wideURL, 3); + Text(wideDots, "..."); + Check(!Is8Bit(wideDots)); + auto wideLong = URLEllipsize(&wideURL, 1); + Text(wideLong, "http://wide.test/path..."); + Check(!Is8Bit(wideLong)); + + URL absent{}; + Check(URLEllipsize(&absent, 0).impl == nullptr && URLEllipsize(&absent, 10).impl == nullptr); + auto empty = Create8(U8(""), 0); + URL emptyURL{empty.impl, 0, 0, 0, 0, 0, 0, 0, 0}; + Check(URLEllipsize(&emptyURL, 0).impl == empty.impl && URLEllipsize(&emptyURL, 5).impl == empty.impl); + Throws([&] { URLEllipsize(nullptr, 10); }); + + Text(DataURLMime(Literal("data:text/HTML;base64,x")), "text/html"); + Text(DataURLMime(Literal("data:Text/X,y")), "text/x"); + Text(DataURLMime(Literal("data:,x")), "text/plain"); + Text(DataURLMime(Literal("data:;base64,x")), "text/plain"); + Check(Is8Bit(DataURLMime(Literal("data:,x")))); + Text(DataURLMime(Literal("data:a,b;c")), "a,b"); + Text(DataURLMime(Literal("data:text/plain")), ""); + Text(DataURLMime(Literal("data:")), ""); + Text(DataURLMime(Literal("data")), ""); + Text(DataURLMime(Literal("")), ""); + Text(DataURLMime(StringView{nullptr, 0, true}), ""); + Text(DataURLMime(Literal("http://a;b")), "//a"); + auto wideData = Create16(u"data:TEXT/Plain;x", 17); + auto wideMime = DataURLMime(ViewOf(wideData)); + Text(wideMime, "text/plain"); + Check(!Is8Bit(wideMime)); + auto accented = Create16(u"data:É/X,", 9); + Text(DataURLMime(ViewOf(accented)), "\xC3\x89/x"); + auto widePlain = Create16(u"data:,x", 7); + Text(DataURLMime(ViewOf(widePlain)), "text/plain"); +} + void TestURLsAndReallocation() { struct PinnedBits { unsigned valid : 1; @@ -967,6 +1041,7 @@ void TestURLsAndReallocation() { memory = static_cast(JSRealloc(memory, 2)); Check(memory[0] == 'h' && memory[1] == 'e'); Check(!JSRealloc(memory, 0)); + TestURLStringHelpers(); } } // namespace Wtf2Tests diff --git a/core/libs/prx/libSceNet/CMakeLists.txt b/core/libs/prx/libSceNet/CMakeLists.txt index f5a380e277..6076a98df3 100644 --- a/core/libs/prx/libSceNet/CMakeLists.txt +++ b/core/libs/prx/libSceNet/CMakeLists.txt @@ -67,6 +67,18 @@ if(BUILD_TESTING) set_tests_properties(guest_net_buffer_validation PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_net_sendmsg_fault_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetSendmsgFaults.cpp) + target_include_directories(guest_net_sendmsg_fault_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_sendmsg_fault_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_sendmsg_fault_tests) + foreach(fault IN ITEMS payload iovec header name) + add_test(NAME guest_net_sendmsg_fault_${fault} COMMAND guest_net_sendmsg_fault_tests ${fault}) + set_tests_properties(guest_net_sendmsg_fault_${fault} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_net_sendmsg_fault_${fault} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() 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) @@ -91,7 +103,7 @@ if(BUILD_TESTING) target_link_libraries(net_blocking_send_tests PRIVATE libkernel libc libSceNetCtl) configure_windows_unwind(net_blocking_send_tests) if(WIN32) - target_link_libraries(net_blocking_send_tests PRIVATE ws2_32) + target_link_libraries(net_blocking_send_tests PRIVATE ws2_32 iphlpapi) endif() foreach(api IN ITEMS send sendto sendmsg) add_test(NAME net_dontwait_blocking_${api} COMMAND net_blocking_send_tests ${api}) @@ -112,6 +124,17 @@ if(BUILD_TESTING) set_tests_properties(guest_sce_net_keepalive PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_net_kernel_forwards_tests ${LIBS_INCLUDE_DIR}/tests/GuestNetKernelForwards.cpp) + target_include_directories(guest_net_kernel_forwards_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_net_kernel_forwards_tests PRIVATE libSceNet libkernel libc) + configure_windows_unwind(guest_net_kernel_forwards_tests) + add_test(NAME guest_sce_net_kernel_forwards COMMAND guest_net_kernel_forwards_tests) + set_tests_properties(guest_sce_net_kernel_forwards PROPERTIES TIMEOUT 20) + if(WIN32) + target_link_libraries(guest_net_kernel_forwards_tests PRIVATE ws2_32) + set_tests_properties(guest_sce_net_kernel_forwards 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) @@ -214,7 +237,7 @@ if(BUILD_TESTING) 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) + target_link_libraries(net_epoll_lifetime_tests PRIVATE ws2_32 iphlpapi) 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}) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index eb96f8b6fc..a7f0cd1ed3 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -6,9 +6,12 @@ #include #include #include +#include #else #include #include +#include +#include #include #include #include @@ -34,11 +37,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -129,6 +134,7 @@ 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_CONFIG_ERROR_NULL_FLAGS = static_cast(0x8041130Eu); +constexpr int NET_CONFIG_ERROR_NO_ROUTE = static_cast(0x8041130Cu); constexpr int NET_RESOLVER_ABORT_FLAG_NTOA_PRESERVATION = 0x1; constexpr int NET_RESOLVER_ABORT_FLAG_ATON_PRESERVATION = 0x2; constexpr std::uint32_t NET_IFF_UP = 0x1; @@ -136,6 +142,8 @@ constexpr std::uint32_t NET_IFF_LOOPBACK = 0x8; constexpr std::uint32_t NET_IFF_RUNNING = 0x40; constexpr std::uint32_t NET_IFF_MULTICAST = 0x8000; constexpr std::uint32_t NET_IF_LO0 = 0; +constexpr std::array NET_INTERFACE_NAMES = { + "lo0", "eth0", "eth1", "dbg0", "wlan0", "wlan1", "gbe0", "bt0", "phone0", "pppoe0"}; constexpr int NET_AF_INET = 2; constexpr int NET_AF_INET6 = 28; @@ -377,6 +385,7 @@ int* errno_slot() { extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, std::size_t capacity); extern "C" bool NetCtlHostIpv4Address_nid_no_patch(std::uint32_t* address, std::uint32_t* netmask, std::uint32_t* mtu); +extern "C" bool NetCtlHostIpv4Gateway_nid_no_patch(std::uint32_t* gateway); std::mutex g_dnsLock; bool g_dnsOverridden = false; @@ -397,6 +406,56 @@ int fail(int err) { return NET_ERROR_BASE | err; } +std::optional HostInterfaceByRole(bool loopback) { +#ifdef _WIN32 + const ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; + ULONG size = 0; + if (GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, nullptr, &size) != ERROR_BUFFER_OVERFLOW) return std::nullopt; + std::vector storage(size); + auto* adapters = reinterpret_cast(storage.data()); + if (GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, adapters, &size) != NO_ERROR) return std::nullopt; + std::optional primary; + for (auto* adapter = adapters; adapter != nullptr; adapter = adapter->Next) { + if (adapter->IfIndex == 0) continue; + if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) { + if (loopback) return static_cast(adapter->IfIndex); + continue; + } + if (!primary && adapter->OperStatus == IfOperStatusUp && adapter->FirstUnicastAddress != nullptr) + primary = static_cast(adapter->IfIndex); + } + return loopback ? std::nullopt : primary; +#else + ifaddrs* list = nullptr; + if (getifaddrs(&list) != 0) return std::nullopt; + std::uint32_t loop = 0; + std::optional primary; + for (auto* item = list; item != nullptr; item = item->ifa_next) { + if (item->ifa_name == nullptr || !(item->ifa_flags & IFF_UP)) continue; + const unsigned int index = if_nametoindex(item->ifa_name); + if (index == 0) continue; + if ((item->ifa_flags & IFF_LOOPBACK) != 0) { + if (loop == 0) loop = index; + continue; + } + if (!primary && item->ifa_addr != nullptr && + (item->ifa_addr->sa_family == AF_INET || item->ifa_addr->sa_family == AF_INET6)) + primary = static_cast(index); + } + freeifaddrs(list); + if (loopback) return loop == 0 ? std::nullopt : std::optional(loop); + return primary; +#endif +} + +std::optional HostInterfaceIndex(std::uint32_t interfaceId) { + if (interfaceId >= NET_INTERFACE_NAMES.size()) return std::nullopt; + if (interfaceId == 0) return HostInterfaceByRole(true); + if (interfaceId == 1) return HostInterfaceByRole(false); + const unsigned int index = if_nametoindex(NET_INTERFACE_NAMES[interfaceId]); + return index == 0 ? std::nullopt : std::optional(index); +} + void* APS5_VABI RunNetThread(void* param) { const auto* thread = static_cast(param); thread->entry(thread->arg); @@ -734,6 +793,20 @@ bool parse_ipv4_parts(const char* text, std::uint32_t& address) { return true; } +std::optional InterfaceIdOfName(std::string_view name) { + for (std::uint32_t interfaceId = 0; interfaceId < NET_INTERFACE_NAMES.size(); ++interfaceId) { + if (name == NET_INTERFACE_NAMES[interfaceId]) return interfaceId; + } + return std::nullopt; +} + +std::optional InterfaceIdOfHostIndex(std::uint32_t index) { + for (std::uint32_t interfaceId = 0; interfaceId < NET_INTERFACE_NAMES.size(); ++interfaceId) { + if (HostInterfaceIndex(interfaceId) == index) return interfaceId; + } + return std::nullopt; +} + } // namespace void GuestSceNetTest::SetResolverLookupForTests_nid_no_patch(ResolverLookup lookup) noexcept { @@ -855,6 +928,63 @@ bool GetSocketOptionForKernel(int descriptor, int level, int option, void* value const bool g_socketOptionGetterRegistered = (KernelSetSocketOptionGetter_nid_no_patch(&GetSocketOptionForKernel), true); +int FamilyOfSocket(int descriptor) { + std::lock_guard lk(g_mutex); + const auto it = g_socks.find(descriptor); + return it == g_socks.end() ? 0 : it->second.family; +} + +bool IsStreamSocket(int descriptor) { + std::lock_guard lk(g_mutex); + const auto it = g_socks.find(descriptor); + return it != g_socks.end() && it->second.type == NET_SOCK_STREAM; +} + +int ErrorOf(std::int64_t result) { + return result < 0 ? *errno_slot() : 0; +} + +constexpr int KernelWaitAll = 0x40; + +std::int64_t ReceiveForKernel(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength) { + const int netFlags = flags & ~KernelWaitAll; + const auto size = static_cast(length); + if (length == 0 && !IsStreamSocket(descriptor)) { + KernelSocketPoll::Entry entry{descriptor, KernelSocketPoll::Readable, 0}; + if (PollSockets(&entry, 1, 0) == 0) return 0; + } + if (netFlags == flags || (flags & NET_MSG_PEEK) != 0 || !IsStreamSocket(descriptor)) return sceNetRecvfrom(descriptor, buffer, size, netFlags, address, addressLength); + size_t received = 0; + for (;;) { + const auto result = sceNetRecvfrom(descriptor, buffer == nullptr ? nullptr : static_cast(buffer) + received, size - received, netFlags, address, addressLength); + if (result < 0) return received > 0 ? static_cast(received) : result; + received += static_cast(result); + if (result == 0 || received == size) return static_cast(received); + } +} + +const KernelSocketPoll::Forwards g_socketForwards{ + FamilyOfSocket, + [](int descriptor, const void* address, std::uint32_t length) { + return ErrorOf(sceNetBind_nid_postfix(descriptor, address, length)); + }, + [](int descriptor, void* address, std::uint32_t* length) { + return ErrorOf(sceNetGetsockname(descriptor, address, length)); + }, + [](int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength, int* error) { + const std::int64_t result = sceNetSendto(descriptor, buffer, static_cast(length), flags, address, addressLength); + *error = ErrorOf(result); + return result; + }, + [](int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength, int* error) { + const std::int64_t result = ReceiveForKernel(descriptor, buffer, length, flags, address, addressLength); + *error = ErrorOf(result); + return result; + }, +}; + +const bool g_socketForwardsRegistered = (KernelSetSocketForwards_nid_no_patch(&g_socketForwards), true); + constexpr int GuestFcntlGetStatus = 3; constexpr int GuestFcntlSetStatus = 4; constexpr int GuestReadWrite = 2; @@ -1342,9 +1472,12 @@ int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, co }, true, static_cast(len)); #endif } else { + std::array address{}; + const auto address_length = std::min(tolen, address.size()); + if (address_length && !GuestSocketReadGuest_nid_no_patch(address.data(), to, address_length)) return fail(NET_EFAULT); sockaddr_storage destination{}; NativeLength destination_length = 0; - if (!guest_to_native_address(to, tolen, destination, destination_length)) return fail(NET_EINVAL); + if (!guest_to_native_address(address.data(), address_length, destination, destination_length)) return fail(NET_EINVAL); if (destination.ss_family != (socket.family == NET_AF_INET ? AF_INET : AF_INET6)) return fail(NET_EAFNOSUPPORT); #ifdef __APPLE__ result = send_stream_in_chunks(*socket.native, flags, static_cast(buf), len, [&](const char* data, std::size_t size) { @@ -1366,9 +1499,18 @@ int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, co } int64_t APS5_VABI sceNetSendmsg(int s, const NetMsghdr* msg, int flags) { - const auto total = message_length(msg); + if (!msg) return fail(NET_EFAULT); + NetMsghdr message{}; + if (!GuestSocketReadGuest_nid_no_patch(&message, msg, sizeof(message))) return fail(NET_EFAULT); + std::vector vectors; + if (message.iov && message.iov_length > 0 && message.iov_length <= NET_UIO_MAXIOV) { + vectors.resize(static_cast(message.iov_length)); + if (!GuestSocketReadGuest_nid_no_patch(vectors.data(), message.iov, vectors.size() * sizeof(NetIovec))) return fail(NET_EFAULT); + message.iov = vectors.data(); + } + const auto total = message_length(&message); if (total < 0) return total; - if (msg->control && msg->control_length) throw std::runtime_error("sceNetSendmsg: control data is not supported"); + if (message.control && message.control_length) throw std::runtime_error("sceNetSendmsg: control data is not supported"); std::vector buffer; try { buffer.resize(static_cast(total)); @@ -1376,11 +1518,11 @@ int64_t APS5_VABI sceNetSendmsg(int s, const NetMsghdr* msg, int flags) { return fail(55); } std::size_t offset = 0; - for (int i = 0; i < msg->iov_length; ++i) { - if (msg->iov[i].length) std::memcpy(buffer.data() + offset, msg->iov[i].base, msg->iov[i].length); - offset += msg->iov[i].length; + for (const auto& entry : vectors) { + if (entry.length && !GuestSocketReadGuest_nid_no_patch(buffer.data() + offset, entry.base, entry.length)) return fail(NET_EFAULT); + offset += entry.length; } - return sceNetSendto(s, buffer.data(), buffer.size(), flags, msg->name, msg->name ? msg->name_length : 0u); + return sceNetSendto(s, buffer.data(), buffer.size(), flags, message.name, message.name ? message.name_length : 0u); } int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags) { @@ -1825,6 +1967,13 @@ static int fill_sock_info(int s, NetSockInfo& record) { record.local_port = local.sin_port; record.remote_port = remote.sin_port; record.flags = NET_SOCKINFO_F_SELF | (socket.native->nonblock ? NET_SOCKINFO_F_NONBLOCK : 0); +#ifdef _WIN32 + u_long pending = 0; + if (::ioctlsocket(socket.native->value, FIONREAD, &pending) == 0) record.recv_queue_length = static_cast(pending); +#else + int pending = 0; + if (::ioctl(socket.native->value, FIONREAD, &pending) == 0) record.recv_queue_length = pending; +#endif return 0; } @@ -2328,7 +2477,7 @@ int APS5_VABI sceNetGetMacAddress(NetEtherAddr* addr, int flags) { } // Keep guest identity stable without exposing the host adapter address. const std::uint8_t mac[6] = {0x02, 0x50, 0x53, 0x35, 0x00, 0x01}; - std::memcpy(addr->data, mac, 6); + if (!GuestCopyOut_nid_no_patch(addr, mac, sizeof(mac))) return fail(NET_EFAULT); return 0; } @@ -2492,7 +2641,7 @@ int APS5_VABI sceNetResolverGetError(int rid, int* status) { std::lock_guard lk(g_mutex); const auto resolver = g_resolvers.find(rid); if (resolver == g_resolvers.end()) return fail(NET_EBADF); - *status = resolver->second.error; + if (!GuestCopyOut_nid_no_patch(status, &resolver->second.error, sizeof(*status))) return fail(NET_EFAULT); return 0; } @@ -2735,13 +2884,11 @@ int APS5_VABI sceNetGetIfListOnce(std::int32_t interfaceId, NetInterfaceStatisti } const char* APS5_VABI sceNetGetIfName(std::uint32_t index) { - static constexpr std::array names = { - "lo0", "eth0", "eth1", "dbg0", "wlan0", "wlan1", "gbe0", "bt0", "phone0", "pppoe0"}; - if (index >= names.size()) { + if (index >= NET_INTERFACE_NAMES.size()) { set_errno(NET_EINVAL); return ""; } - return names[index]; + return NET_INTERFACE_NAMES[index]; } int APS5_VABI sceNetGetIfnameNumList(std::uint32_t* interfaceIds, std::int32_t capacity, std::uint32_t flags) { @@ -2770,8 +2917,7 @@ int APS5_VABI sceNetGetRouteInfo(std::int32_t family, NetRouteInfo* routes, std: } int APS5_VABI sceNetGetsockoptEx(int socketId, int level, int option, void* value, std::uint32_t* valueSize) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceNetGetsockopt(socketId, level, option, value, valueSize); } void APS5_VABI sceNetGetSystemTime(std::uint64_t* output) { @@ -2829,7 +2975,9 @@ int APS5_VABI sceNetIoctl(int socketId, std::uint32_t command, void* argument) { 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; + int enable = 0; + if (!GuestCopyIn_nid_no_patch(&enable, argument, sizeof(enable))) return fail(NET_EFAULT); + const int enabled = enable != 0; return sceNetSetsockopt(socketId, NET_SOL_SOCKET, NET_SO_NBIO, &enabled, sizeof(enabled)); } #ifdef _WIN32 @@ -2845,7 +2993,8 @@ int APS5_VABI sceNetIoctl(int socketId, std::uint32_t command, void* argument) { int available = 0; if (::ioctl(native->value, FIONREAD, &available) != 0) return fail(native_error()); #endif - *static_cast(argument) = static_cast(available); + const int readable = static_cast(available); + if (!GuestCopyOut_nid_no_patch(argument, &readable, sizeof(readable))) return fail(NET_EFAULT); return 0; } @@ -2875,13 +3024,11 @@ int APS5_VABI sceNetSetDnsInfoToKernel(const NetDnsInfo* info, std::uint32_t fla } int APS5_VABI sceNetSetsockoptEx(int socketId, int level, int option, const void* value, std::uint32_t valueSize) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceNetSetsockopt(socketId, level, option, value, valueSize); } int APS5_VABI sceNetSocketInternal(const char* name, int domain, int type, int protocol) { - NotImplemented_nid_no_patch(__func__); - return 0; + return sceNetSocket(name, domain, type, protocol); } int APS5_VABI sceNetSyncCreate(NetSyncObject* sync, std::uint32_t signals, const char* name) { @@ -2910,8 +3057,8 @@ 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) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (sysctl_nid_postfix(name, nameLength, oldValue, oldSize, newValue, newSize) == 0) return 0; + return fail(*__error_nid_postfix()); } int APS5_VABI sceNetThreadCreate(Pthread* thread, const NetThreadParameter* parameter, const char* name) { @@ -2969,13 +3116,51 @@ int APS5_VABI sceNetShowNetstatExForBuffer(char* buffer, std::int32_t bufferSize } 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; + if (address == nullptr || text == nullptr) { + set_errno(NET_ENOSPC); + return nullptr; + } + if (family != NET_AF_INET6) { + set_errno(NET_EAFNOSUPPORT); + return nullptr; + } + std::array unscoped{}; + if (sceNetInetNtop(family, address, unscoped.data(), static_cast(unscoped.size())) == nullptr) return nullptr; + std::string scoped(unscoped.data()); + if (scopeId != 0) { + const auto interfaceId = InterfaceIdOfHostIndex(scopeId); + if (!interfaceId) throw std::runtime_error("sceNetInetNtopWithScopeId: scope " + std::to_string(scopeId) + " matches no interface, and the module's answer for it is not known"); + scoped += '%'; + scoped += NET_INTERFACE_NAMES[*interfaceId]; + } + if (scoped.size() >= size) { + set_errno(NET_ENOSPC); + return nullptr; + } + std::memcpy(text, scoped.c_str(), scoped.size() + 1); + return text; } int APS5_VABI sceNetInetPtonWithScopeId(std::int32_t family, const char* text, void* address, std::uint32_t* scopeId) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (family != NET_AF_INET6) return fail(NET_EAFNOSUPPORT); + if (text == nullptr) return fail(NET_EINVAL); + const char* percent = scopeId != nullptr ? std::strchr(text, '%') : nullptr; + std::uint32_t zone = 0; + if (percent != nullptr) { + const auto interfaceId = InterfaceIdOfName(percent + 1); + if (!interfaceId) return 0; + const auto index = HostInterfaceIndex(*interfaceId); + if (!index) return 0; + zone = *index; + } + const std::string unscoped = percent != nullptr ? std::string(text, percent) : std::string(text); + std::array parsed{}; + const int converted = sceNetInetPton(family, unscoped.c_str(), parsed.data()); + if (converted != 1) return converted; + if (address == nullptr) throw std::invalid_argument("sceNetInetPtonWithScopeId: null address"); + std::memcpy(address, parsed.data(), parsed.size()); + if (scopeId != nullptr) *scopeId = zone; + return 1; } int APS5_VABI sceNetGetDns6Info(NetDns6Info* info, std::uint32_t flags) { @@ -3141,8 +3326,12 @@ int APS5_VABI sceNetConfigDelRoute(const std::uint32_t* destination, const std:: } int APS5_VABI sceNetConfigGetDefaultRoute(std::uint32_t* gateway) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (!g_net_inited) return fail(NET_ENOTINIT); + std::uint32_t found = 0; + if (!NetCtlHostIpv4Gateway_nid_no_patch(&found)) return NET_CONFIG_ERROR_NO_ROUTE; + if (gateway == nullptr) throw std::invalid_argument("sceNetConfigGetDefaultRoute: null gateway"); + *gateway = found; + return 1; } int APS5_VABI sceNetDhcpGetAutoipInfo(std::int32_t interfaceId, NetAutoIpInfo* parameter) { diff --git a/core/libs/prx/libSceNetCtl/Export.cpp b/core/libs/prx/libSceNetCtl/Export.cpp index fd2218367a..7fa120170d 100644 --- a/core/libs/prx/libSceNetCtl/Export.cpp +++ b/core/libs/prx/libSceNetCtl/Export.cpp @@ -485,6 +485,13 @@ extern "C" std::size_t NetCtlHostDnsServers_nid_no_patch(std::uint32_t* servers, return host.dns.size(); } +extern "C" bool NetCtlHostIpv4Gateway_nid_no_patch(std::uint32_t* gateway) { + HostIpv4Interface host; + if (!FindHostIpv4Interface(host) || host.gateway.s_addr == 0) return false; + *gateway = host.gateway.s_addr; + return true; +} + #ifdef _WIN32 using StunSocket = SOCKET; static constexpr StunSocket INVALID_STUN_SOCKET = INVALID_SOCKET; @@ -1034,7 +1041,8 @@ 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; - throw std::runtime_error("sceNetCtlGetInfoV6: info code " + std::to_string(code) + " is not implemented"); + NotImplemented_nid_no_patch(__func__); + return 0; } void APS5_VABI sceNetCtlTerm(void) { diff --git a/core/libs/prx/libSceNgs2.native/CMakeLists.txt b/core/libs/prx/libSceNgs2.native/CMakeLists.txt index f83a826a8d..61220b5529 100644 --- a/core/libs/prx/libSceNgs2.native/CMakeLists.txt +++ b/core/libs/prx/libSceNgs2.native/CMakeLists.txt @@ -16,18 +16,23 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() - foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom Gain Routing PitchShift JobControl JobBuffer JobFx Activation Threads Detached Sampler Parse Stream UserFx Report Geom VoiceIndex HandleErrors VoiceHandles VoiceEvents) + foreach(ngs2Test IN ITEMS Render Atrac9 Filter Custom CustomReverb Gain Routing PitchShift JobControl JobBuffer JobFx Activation Threads Detached Sampler 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) target_link_libraries(ngs2_${testName}_tests PRIVATE libSceNgs2.native atrac9 libkernel libc) configure_windows_unwind(ngs2_${testName}_tests) - add_test(NAME ngs2_${testName} COMMAND ngs2_${testName}_tests) + set(ngs2Arguments) + if(ngs2Test STREQUAL "HandleErrors") + set(ngs2Arguments $ $) + endif() + add_test(NAME ngs2_${testName} COMMAND ngs2_${testName}_tests ${ngs2Arguments}) if(WIN32) set_tests_properties(ngs2_${testName} PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endforeach() + target_link_libraries(ngs2_handleerrors_tests PRIVATE libSceNgs2) add_sce_ngs2_test(libSceNgs2.native ngs2_pcm Ngs2Pcm.cpp) add_test(NAME ngs2_detached_pending COMMAND ngs2_threads_tests detached-pending) set_tests_properties(ngs2_detached_pending PROPERTIES TIMEOUT 20) diff --git a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp index 4e58d7a329..631a3c47f3 100644 --- a/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp +++ b/core/libs/prx/libSceNgs2.native/include/Ngs2Types.hpp @@ -118,6 +118,7 @@ static constexpr std::uint32_t SCE_NGS2_CUSTOM_MASTERING_VOICE_PARAM_SETUP = 0x4 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_REVERB = 0x40001b00; 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; diff --git a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp index 71c988fb9b..8b82aab270 100644 --- a/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Atrac9.cpp @@ -377,6 +377,29 @@ static int ParseAtrac9(const RiffChunks& chunks, Ngs2WaveformInfo& info) { return ParseAtrac9Loop(chunks, info); } +static void AddPcmBlock(Ngs2WaveformInfo& info, std::uint32_t begin, std::uint32_t samples, std::uint32_t repeats) { + auto& block = info.block[info.num_blocks++]; + block = {info.data_offset + static_cast(begin) * info.audio_frame_size, static_cast(samples) * info.audio_frame_size, repeats, 0, samples, 0, 0}; +} + +static int ParsePcmLoop(const RiffChunks& chunks, Ngs2WaveformInfo& info) { + if (chunks.sampler == nullptr) return SCE_NGS2_OK; + 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"); + const std::uint32_t begin = ReadLe32(chunks.sampler + 44); + const std::uint32_t end = ReadLe32(chunks.sampler + 48) + 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; + if (begin != 0) AddPcmBlock(info, 0, begin, 0); + AddPcmBlock(info, begin, end - begin, UINT32_MAX); + if (end != info.num_samples) AddPcmBlock(info, end, info.num_samples - end, 0); + return SCE_NGS2_OK; +} + 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; @@ -394,7 +417,6 @@ static int ParsePcm(const RiffChunks& chunks, std::size_t size, Ngs2WaveformInfo 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 = {type, channels, sampleRate, 0, 0, 0}; @@ -410,7 +432,7 @@ static int ParsePcm(const RiffChunks& chunks, std::size_t size, Ngs2WaveformInfo info.block[0].data_offset = chunks.dataOffset; info.block[0].data_size = dataSize; info.block[0].num_samples = info.num_samples; - return SCE_NGS2_OK; + return ParsePcmLoop(chunks, info); } static int ParseWaveform(const RiffChunks& chunks, std::size_t size, Ngs2WaveformInfo& info) { diff --git a/core/libs/prx/libSceNgs2.native/src/Custom.cpp b/core/libs/prx/libSceNgs2.native/src/Custom.cpp index 675c7e7101..7bbc9b46e8 100644 --- a/core/libs/prx/libSceNgs2.native/src/Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Custom.cpp @@ -7,6 +7,7 @@ #include #include +#include "prx/libc/include/Api.hpp" #include "prx/libc/include/General.hpp" #include "Ngs2Internal.hpp" @@ -99,6 +100,7 @@ int Ngs2CheckCustomRack(std::uint32_t rackId, const Ngs2CustomRackOption& option 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_REVERB) continue; if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT) continue; if (moduleId == SCE_NGS2_CUSTOM_MODULE_ID_GAIN) continue; const char* name = BuiltInModuleName(moduleId); @@ -132,6 +134,7 @@ int Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option) { voice.userFx.resize(option.num_modules); voice.customUserFx.resize(option.num_modules); voice.customGain.assign(option.num_modules, {}); + voice.customReverb.resize(option.num_modules); if (hasPitchShift) voice.customPitchShift.resize(option.num_modules); } for (std::uint32_t m = 0; m < option.num_modules; m++) { @@ -142,6 +145,11 @@ int Ngs2SetupUserFx(Ngs2Rack& rack, const Ngs2CustomRackOption& option) { rack.customModuleOptions[m].assign(data, data + info.option->size); } 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 (info.module_id == SCE_NGS2_CUSTOM_MODULE_ID_REVERB) { + const auto size = info.option == nullptr ? 1u : OptionWord(*info.option); + for (auto& voice : rack.voices) voice.customReverb[m] = Ngs2CreateCustomReverb(size); + continue; + } if (option.module[m].module_id == SCE_NGS2_CUSTOM_MODULE_ID_FILTER) continue; if (option.module[m].module_id == SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT) { for (auto& voice : rack.voices) voice.customPitchShift[m].channels.resize(rack.maxChannels); @@ -212,7 +220,10 @@ static void SetupCustomSubmixer(Ngs2Voice& voice, const Ngs2CustomSubmixerVoiceS if (setup.num_input_channels != setup.num_output_channels) throw std::runtime_error("NGS2: custom submixer channel conversion is not implemented"); if (setup.num_input_channels == 0 || setup.num_input_channels > voice.rack->maxChannels || setup.num_input_channels > NGS2_MAX_CHANNELS) APS5_INVALID_ARG_EX; if (Ngs2HasPitchShift(voice)) voice.ResetSetup(); - else Ngs2ResetUserFx2(voice); + else { + Ngs2ResetUserFx2(voice); + Ngs2ResetCustomReverb(voice); + } for (auto& gain : voice.customGain) if (gain.configured) gain.lifecycleUnknown = true; ++voice.waveformRevision; voice.channels = setup.num_input_channels; @@ -298,7 +309,7 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { if (index >= voice.customPitchShift.size() || voice.rack->customModuleIds[index] != SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT) APS5_INVALID_ARG_EX; const auto& pitch = ParamAs(param); auto& state = voice.customPitchShift[index]; - state.ratio = std::exp2(static_cast(std::clamp(pitch.cents, -1200, 1200)) / 1200.0f); + state.ratio = exp2f_nid_postfix(static_cast(std::clamp(pitch.cents, -1200, 1200)) / 1200.0f); state.bypassMask = pitch.bypass_mask; state.changed = true; return; @@ -366,6 +377,15 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { Ngs2SetFilter(voice, filter); return; } + if ((param.id & ~0xffu) == SCE_NGS2_CUSTOM_VOICE_PARAM_REVERB) { + 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"); + } + const auto index = param.id & 0x1f; + if (index >= voice.customReverb.size() || !voice.customReverb[index]) APS5_INVALID_ARG_EX; + Ngs2SetCustomReverbParams(voice.customReverb[index], ParamAs(param).i3dl2); + 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"); @@ -395,6 +415,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_REVERB) { + voice.hasSamples = Ngs2ProcessCustomReverb(voice, m, grain, sampleRate); + continue; + } if (voice.rack->customModuleIds[m] == SCE_NGS2_CUSTOM_MODULE_ID_DELAY) continue; if (voice.rack->customModuleIds[m] == SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT) { Ngs2ProcessPitchShift(voice, m, grain); diff --git a/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp b/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp index e0076ca298..ed15d02e9e 100644 --- a/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp +++ b/core/libs/prx/libSceNgs2.native/src/Ngs2Internal.hpp @@ -66,6 +66,8 @@ struct Ngs2ReverbDeleter { void operator()(Ngs2ReverbState* reverb) const; }; +using Ngs2Reverb = std::unique_ptr; + struct Ngs2Voice; struct Ngs2ReturnCode { @@ -167,7 +169,8 @@ struct Ngs2Voice { std::uint32_t sampleRate = 0; std::uint32_t waveformType = 0; Ngs2Atrac9 atrac9; - std::unique_ptr reverb; + Ngs2Reverb reverb; + std::vector customReverb; float pitch = 1.0f; std::uint64_t phase = 0; std::array, NGS2_MAX_CHANNELS> samplerHistory{}; @@ -376,7 +379,12 @@ void Ngs2ApplyCustomParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param); void Ngs2ProcessUserFx(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate); void Ngs2SetReverbParams(Ngs2Voice& voice, const Ngs2ReverbI3DL2Param& params); void Ngs2SetupReverb(Ngs2Voice& voice); +Ngs2Reverb Ngs2CreateCustomReverb(std::uint32_t size); +void Ngs2SetCustomReverbParams(Ngs2Reverb& reverb, const Ngs2ReverbI3DL2Param& params); +bool Ngs2ProcessCustomReverb(Ngs2Voice& voice, std::size_t module, std::uint32_t grain, std::uint32_t sampleRate); void Ngs2ClearReverb(Ngs2Voice& voice); +void Ngs2ClearCustomReverb(Ngs2Voice& voice); +void Ngs2ResetCustomReverb(Ngs2Voice& voice); bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate); void Ngs2ProcessLegacyUserFx(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate); void Ngs2PublishSamplerState(Ngs2Voice& voice); diff --git a/core/libs/prx/libSceNgs2.native/src/Reverb.cpp b/core/libs/prx/libSceNgs2.native/src/Reverb.cpp index 4946f8de9a..e042717d37 100644 --- a/core/libs/prx/libSceNgs2.native/src/Reverb.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Reverb.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -72,6 +73,9 @@ struct Ngs2ReverbChannel { struct Ngs2ReverbState { Ngs2ReverbI3DL2Param params{}; bool reset = true; + bool custom = false; + bool active = false; + std::uint32_t ringSamples = TAP_RING_SAMPLES; bool changed = true; std::uint32_t sampleRate = 0; std::vector ring; @@ -82,6 +86,15 @@ struct Ngs2ReverbState { float density = NAN; std::array combs; std::array channels; + std::vector input; + std::vector mapped; + std::vector mono; + std::vector early; + std::vector late; + std::vector oldEarly; + std::vector oldLate; + std::vector combSamples; + std::vector tail; bool roomFilter = false; float roomB0 = 0.0f; float roomA1 = 0.0f; @@ -190,7 +203,7 @@ static void SetupComb(Ngs2Comb& comb, std::uint32_t length, const Ngs2ReverbI3DL } static void Reset(Ngs2ReverbState& reverb) { - reverb.ring.assign(TAP_RING_SAMPLES, 0.0f); + reverb.ring.assign(reverb.ringSamples, 0.0f); reverb.ringCursor = 0; reverb.taps = {}; reverb.earlyGain = 1.0f; @@ -239,6 +252,46 @@ static void UpdateRoomFilter(Ngs2ReverbState& reverb, float rate) { reverb.roomA2 = pole * -pole; } +static void RunCustomRoomFilter(const Ngs2ReverbState& reverb, Ngs2ReverbChannel& state, float* samples, std::uint32_t grain) { + const float b = reverb.roomB0; + const float a1 = reverb.roomA1; + const float a2 = reverb.roomA2; + const float h1 = b * a1; + const float h2 = b * a2 + h1 * a1; + const float h3 = h1 * a2 + h2 * a1; + const std::array x0{b, h1, h2, h3}; + const std::array x1{0, b, h1, h2}; + const std::array x2{0, 0, b, h1}; + const std::array x3{0, 0, 0, b}; + const float feedback1 = a2 + a1 * a1; + const float feedback2 = a1 * a2 + feedback1 * a1; + const float feedback3 = feedback1 * a2 + feedback2 * a1; + const float previous1 = a2 * a1; + const float previous2 = a2 * a2 + previous1 * a1; + const float previous3 = previous1 * a2 + previous2 * a1; + const std::array y1{a1, feedback1, feedback2, feedback3}; + const std::array y2{a2, previous1, previous2, previous3}; + std::uint32_t i = 0; + for (; i + 4 <= grain; i += 4) { + const std::array input{samples[i], samples[i + 1], samples[i + 2], samples[i + 3]}; + for (std::uint32_t lane = 0; lane < 4; lane++) { + float odd = std::fma(x1[lane], input[1], x3[lane] * input[3]); + odd = std::fma(y1[lane], state.y1, odd); + float even = std::fma(x0[lane], input[0], x2[lane] * input[2]); + even = std::fma(y2[lane], state.y2, even); + samples[i + lane] = even + odd; + } + state.y2 = samples[i + 2]; + state.y1 = samples[i + 3]; + } + for (; i < grain; i++) { + const float y = std::fma(a2, state.y2, std::fma(a1, state.y1, b * samples[i])); + state.y2 = state.y1; + state.y1 = y; + samples[i] = y; + } +} + static void RunComb(Ngs2Comb& comb, const float* input, float* output, std::uint32_t grain) { const auto length = static_cast(comb.buffer.size()); for (std::uint32_t i = 0; i < grain; i++) { @@ -264,7 +317,20 @@ static void RunAllpass(Ngs2Allpass& allpass, float gain, float* samples, std::ui } } -static void MixDown(const float* input, std::uint32_t channels, std::uint32_t grain, float* mono) { +static void MixDown(const float* input, std::uint32_t channels, std::uint32_t grain, float* mono, const Ngs2ReverbState& reverb) { + if (reverb.custom) { + const auto mask = reverb.params.reserved[0]; + const auto count = std::popcount(((1u << channels) - 1) & ~mask); + const float gain = count == 0 ? 0.0f : (count <= 2 ? 1.0f : 0.707106769f) / std::sqrt(static_cast(count)); + bool first = true; + for (std::uint32_t c = 0; c < channels; c++) { + if ((mask & (1u << c)) != 0) continue; + const float* channel = input + static_cast(c) * grain; + for (std::uint32_t i = 0; i < grain; i++) mono[i] = first ? channel[i] * gain : mono[i] + channel[i] * gain; + first = false; + } + return; + } const float* channel[8]; for (std::uint32_t c = 0; c < channels; c++) channel[c] = input + static_cast(c) * grain; if (channels == 1) { @@ -279,8 +345,7 @@ static void MixDown(const float* input, std::uint32_t channels, std::uint32_t gr } } -bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate) { - auto& reverb = *voice.reverb; +static bool ProcessReverb(Ngs2Voice& voice, Ngs2ReverbState& reverb, std::uint32_t grain, std::uint32_t sampleRate) { const float rate = static_cast(sampleRate); const std::uint32_t channels = voice.channels; if (reverb.sampleRate != sampleRate) { @@ -293,10 +358,16 @@ bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp 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(), inputChannels, grain, mono.data()); - std::vector mapped; + reverb.input = voice.samples; + const auto& input = reverb.input; + auto& mono = reverb.mono; + auto& early = reverb.early; + auto& late = reverb.late; + mono.assign(grain, 0.0f); + early.resize(grain); + late.resize(grain); + MixDown(input.data(), inputChannels, grain, mono.data(), reverb); + auto& mapped = reverb.mapped; if (inputChannels != channels) { mapped.assign(static_cast(grain) * channels, 0.0f); for (std::uint32_t dst = 0; dst < channels; dst++) { @@ -310,7 +381,10 @@ bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp } const std::vector& direct = inputChannels != channels ? mapped : input; if (reverb.changed) { - std::vector oldEarly(grain), oldLate(grain); + auto& oldEarly = reverb.oldEarly; + auto& oldLate = reverb.oldLate; + oldEarly.resize(grain); + oldLate.resize(grain); RunTaps(reverb, reverb.taps, mono.data(), grain, oldEarly.data(), oldLate.data()); ApplyParams(reverb, rate); RunTaps(reverb, reverb.taps, mono.data(), grain, early.data(), late.data()); @@ -325,7 +399,8 @@ bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp reverb.ring[reverb.ringCursor] = mono[i]; reverb.ringCursor = reverb.ringCursor + 1 == reverb.ring.size() ? 0 : reverb.ringCursor + 1; } - std::vector combs(static_cast(COMBS) * grain); + auto& combs = reverb.combSamples; + combs.resize(static_cast(COMBS) * grain); for (std::uint32_t i = 0; i < COMBS; i++) RunComb(reverb.combs[i], late.data(), combs.data() + static_cast(i) * grain, grain); UpdateRoomFilter(reverb, rate); const auto& params = reverb.params; @@ -334,10 +409,12 @@ bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp auto& state = reverb.channels[channel]; float* out = voice.samples.data() + static_cast(channel) * grain; const float* in = direct.data() + static_cast(channel) * grain; - if (channel == LFE_CHANNEL) { + const bool excluded = reverb.custom ? (params.reserved[0] & (1u << channel)) != 0 : channel == LFE_CHANNEL; + if (excluded) { std::copy(in, in + grain, out); } else { - std::vector tail(grain, 0.0f); + auto& tail = reverb.tail; + tail.assign(grain, 0.0f); std::uint32_t selected = 0; for (std::uint32_t i = 0; i < COMBS; i++) { if ((CHANNEL_COMBS[channel] >> i & 1) == 0) continue; @@ -348,14 +425,18 @@ bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp } for (auto& allpass : state.allpasses) RunAllpass(allpass, params.diffusion * 0.005f, tail.data(), grain); for (std::uint32_t n = 0; n < grain; n++) out[n] = early[n] * reverb.earlyGain + tail[n] * reverb.lateGain; - for (std::uint32_t n = 0; n < grain; n++) { - const float y = reverb.roomFilter ? reverb.roomB0 * out[n] + reverb.roomA1 * state.y1 + reverb.roomA2 * state.y2 : out[n]; - state.y2 = state.y1; - state.y1 = y; - out[n] = y; + if (reverb.custom && reverb.roomFilter) { + RunCustomRoomFilter(reverb, state, out, grain); + } else { + for (std::uint32_t n = 0; n < grain; n++) { + const float y = reverb.roomFilter ? reverb.roomB0 * out[n] + reverb.roomA1 * state.y1 + reverb.roomA2 * state.y2 : out[n]; + state.y2 = state.y1; + state.y1 = y; + out[n] = y; + } } } - const float wet = channel == LFE_CHANNEL ? 0.0f : params.wet; + const float wet = excluded ? 0.0f : params.wet; Ramp(out, reset ? wet : state.wet, wet, grain, out, false); Ramp(out, reset ? params.dry : state.dry, params.dry, grain, in, true); state.wet = wet; @@ -367,16 +448,16 @@ bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t samp return audible; } -void Ngs2SetReverbParams(Ngs2Voice& voice, const Ngs2ReverbI3DL2Param& params) { +static void SetReverbParams(Ngs2Reverb& reverb, const Ngs2ReverbI3DL2Param& params) { if (std::isnan(params.wet) || std::isnan(params.dry) || std::isnan(params.decay_time) || std::isnan(params.decay_hf_ratio) || std::isnan(params.reflections_delay) || std::isnan(params.reverb_delay) || std::isnan(params.diffusion) || std::isnan(params.density) || std::isnan(params.hf_reference)) { APS5_INVALID_ARG_EX; } - if (!voice.reverb) voice.reverb.reset(new Ngs2ReverbState{}); - auto& stored = voice.reverb->params; + if (!reverb) reverb.reset(new Ngs2ReverbState{}); + auto& stored = reverb->params; stored = params; - stored.wet = Clamp(params.wet, 0.0f, 32.0f); - stored.dry = Clamp(params.dry, 0.0f, 32.0f); + stored.wet = reverb->custom ? params.wet : Clamp(params.wet, 0.0f, 32.0f); + stored.dry = reverb->custom ? params.dry : Clamp(params.dry, 0.0f, 32.0f); stored.reflection_pattern = params.reflection_pattern < 16 ? params.reflection_pattern : 0; stored.decay_time = Clamp(params.decay_time, 0.1f, 20.0f); stored.decay_hf_ratio = Clamp(params.decay_hf_ratio, 0.00999999978f, 2.0f); @@ -385,7 +466,32 @@ void Ngs2SetReverbParams(Ngs2Voice& voice, const Ngs2ReverbI3DL2Param& params) { stored.diffusion = Clamp(params.diffusion, 0.0f, 100.0f); stored.density = Clamp(params.density, 0.0f, 100.0f); stored.hf_reference = Clamp(params.hf_reference, 20.0f, 20000.0f); - voice.reverb->changed = true; + reverb->changed = true; +} + +bool Ngs2ProcessReverb(Ngs2Voice& voice, std::uint32_t grain, std::uint32_t sampleRate) { + return ProcessReverb(voice, *voice.reverb, grain, sampleRate); +} + +void Ngs2SetReverbParams(Ngs2Voice& voice, const Ngs2ReverbI3DL2Param& params) { + SetReverbParams(voice.reverb, params); +} + +Ngs2Reverb Ngs2CreateCustomReverb(std::uint32_t size) { + Ngs2Reverb reverb(new Ngs2ReverbState{}); + reverb->custom = true; + reverb->ringSamples = size == 0 ? 4800 : 14400; + return reverb; +} + +void Ngs2SetCustomReverbParams(Ngs2Reverb& reverb, const Ngs2ReverbI3DL2Param& params) { + SetReverbParams(reverb, params); + reverb->active = true; +} + +bool Ngs2ProcessCustomReverb(Ngs2Voice& voice, std::size_t module, std::uint32_t grain, std::uint32_t sampleRate) { + auto& reverb = *voice.customReverb[module]; + return reverb.active ? ProcessReverb(voice, reverb, grain, sampleRate) : voice.hasSamples; } void Ngs2SetupReverb(Ngs2Voice& voice) { @@ -393,6 +499,22 @@ void Ngs2SetupReverb(Ngs2Voice& voice) { Ngs2SetReverbParams(voice, SETUP_PARAMS); } +void Ngs2ClearCustomReverb(Ngs2Voice& voice) { + for (auto& reverb : voice.customReverb) { + if (reverb) reverb->reset = true; + } +} + +void Ngs2ResetCustomReverb(Ngs2Voice& voice) { + for (auto& reverb : voice.customReverb) { + if (reverb) { + reverb->active = false; + reverb->reset = true; + } + } +} + void Ngs2ClearReverb(Ngs2Voice& voice) { if (voice.reverb) voice.reverb->reset = true; + Ngs2ClearCustomReverb(voice); } diff --git a/core/libs/prx/libSceNgs2.native/src/Voice.cpp b/core/libs/prx/libSceNgs2.native/src/Voice.cpp index d3c633f2e3..9724006f00 100644 --- a/core/libs/prx/libSceNgs2.native/src/Voice.cpp +++ b/core/libs/prx/libSceNgs2.native/src/Voice.cpp @@ -1,4 +1,4 @@ -#include "prx/libSceNgs2.native/tests/Ngs2Test.hpp" +#include "prx/libSceNgs2.native/include/Api.hpp" #include #include #include @@ -17,6 +17,7 @@ static constexpr std::uint32_t COMMAND_MATRIX_LEVELS = 5; static constexpr std::uint32_t COMMAND_PORT_VOLUME = 6; static constexpr std::uint32_t COMMAND_PORT_MATRIX = 7; static constexpr float MAX_PORT_VOLUME = 32.0f; +static constexpr float MAX_PS4_PORT_VOLUME = 4.0f; static constexpr std::size_t DEFAULT_MAX_PORTS = 8; static constexpr std::uint64_t PS4_MAX_FILTER_LOCATIONS = 18; static constexpr std::uint32_t PS4_FIRST_UNFILTERED_TYPE = 11; @@ -32,6 +33,10 @@ static constexpr bool PS4_LIBRARY = false; #endif static constexpr int FULL_WAVEFORM_BLOCKS = PS4_LIBRARY ? SCE_NGS2_ERROR_PS4_INVALID_NUM_WAVEFORM_BLOCKS : SCE_NGS2_ERROR_FULL_WAVEFORM_BLOCKS; +static float ClampPortVolume(float level) { + return PS4_LIBRARY ? std::clamp(level, -MAX_PS4_PORT_VOLUME, MAX_PS4_PORT_VOLUME) : std::clamp(level, 0.0f, MAX_PORT_VOLUME); +} + 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); @@ -134,6 +139,7 @@ void Ngs2Voice::SetEvent(std::uint32_t eventId) { stateFlags = SCE_NGS2_VOICE_STATE_FLAG_INUSE; } if (state == Ngs2PlayState::Empty || state == Ngs2PlayState::Stopped) { + Ngs2ClearCustomReverb(*this); if (IsSampler()) { Ngs2ReplaySampler(*this); Ngs2ResetFilterHistory(*this); @@ -226,6 +232,7 @@ void Ngs2Voice::ResetSetup() { for (auto& gain : customGain) if (gain.configured) gain.lifecycleUnknown = true; ++waveformRevision; SetEvent(SCE_NGS2_VOICE_EVENT_STOP_IMM); + Ngs2ResetCustomReverb(*this); Ngs2ResetPitchShift(*this); std::fill(ports.begin(), ports.end(), Ngs2Port{}); for (auto& matrix : matrices) matrix.clear(); @@ -535,6 +542,36 @@ static int ControlUserFx2(Ngs2Voice& voice, std::uint32_t index, const Ngs2Custo static Ngs2Voice* PatchDest(const Ngs2Voice& voice, const Ngs2VoicePatchParam& patch); +static void CheckFilter(const Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param) { + if (param.header.size < sizeof(Ngs2SamplerVoiceFilterParam)) throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_VOICE_CONTROL_SIZE}; + if (param.index >= voice.rack->maxFilters) throw Ngs2ReturnCode{PS4_LIBRARY ? SCE_NGS2_ERROR_PS4_INVALID_FILTER_INDEX : SCE_NGS2_ERROR_INVALID_PARAM_ID}; + if (!PS4_LIBRARY && param.type >= 12) throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_FILTER_TYPE}; + if (PS4_LIBRARY ? param.location >= PS4_MAX_FILTER_LOCATIONS : param.location >= std::uint64_t{2} + voice.ports.size()) { + throw Ngs2ReturnCode{PS4_LIBRARY ? SCE_NGS2_ERROR_PS4_INVALID_FILTER_LOCATION : SCE_NGS2_ERROR_INVALID_MODULE_LOCATION}; + } +} + +static Ngs2SamplerVoiceFilterParam SamplerFilterParam(const Ngs2VoiceParamHeader& param) { + if (!PS4_LIBRARY) { + if (param.size < sizeof(Ngs2SamplerVoiceFilterParam)) throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_VOICE_CONTROL_SIZE}; + return reinterpret_cast(param); + } + if (param.size != sizeof(Ngs2Ps4SamplerVoiceFilterParam)) throw Ngs2ReturnCode{SCE_NGS2_ERROR_PS4_INVALID_VOICE_CONTROL_SIZE}; + const auto& ps4 = reinterpret_cast(param); + Ngs2SamplerVoiceFilterParam filter{}; + filter.header = {sizeof(filter), 0, param.id}; + filter.index = ps4.index; + filter.location = ps4.location; + filter.channel_mask = ps4.channel_mask; + filter.type = ps4.type; + filter.frequency = ps4.fcq.frequency; + filter.q = ps4.fcq.q; + filter.level = ps4.fcq.level; + filter.reserved[0] = ps4.fcq.reserved; + filter.reserved[1] = ps4.fcq.reserved2; + return filter; +} + static Ngs2PendingControl CaptureParamControl(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param) { if (param.id == SCE_NGS2_VOICE_PARAM_EVENT) { const auto event = ParamAs(param).event_id; @@ -544,7 +581,7 @@ static Ngs2PendingControl CaptureParamControl(Ngs2Voice& voice, const Ngs2VoiceP if (param.id == SCE_NGS2_VOICE_PARAM_PORT_VOLUME) { auto value = ParamAs(param); PortAt(voice, value.port); - value.level = std::clamp(value.level, 0.0f, MAX_PORT_VOLUME); + value.level = ClampPortVolume(value.level); return value; } if (param.id == SCE_NGS2_VOICE_PARAM_PORT_MATRIX) { @@ -607,6 +644,14 @@ static Ngs2PendingControl CaptureParamControl(Ngs2Voice& voice, const Ngs2VoiceP ParamAs(param); return CopyParam(param); } + if (param.id == SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER) { + if (voice.rack->rackId == SCE_NGS2_RACK_ID_CUSTOM_SAMPLER) { + throw Ngs2ReturnCode{PS4_LIBRARY ? SCE_NGS2_ERROR_PS4_INVALID_VOICE_CONTROL_ID : SCE_NGS2_ERROR_INVALID_PARAM_ID}; + } + if (voice.rack->rackId != SCE_NGS2_RACK_ID_SAMPLER) throw std::invalid_argument("NGS2: voice param " + Ngs2Hex(param.id) + " belongs to another rack"); + CheckFilter(voice, SamplerFilterParam(param)); + return CopyParam(param); + } if (voice.rack->rackId == SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER && (param.id & ~0x1fu) == SCE_NGS2_CUSTOM_VOICE_PARAM_USER_FX) { const auto index = param.id & 0x1fu; if (index >= voice.customUserFx.size() || voice.rack->customModuleIds[index] != SCE_NGS2_CUSTOM_MODULE_ID_USER_FX) APS5_INVALID_ARG_EX; @@ -639,7 +684,7 @@ static Ngs2PendingControl CaptureCommandControl(Ngs2Voice& voice, const Ngs2Voic } if (id == COMMAND_PORT_VOLUME) { PortAt(voice, index); - return Ngs2VoicePortVolumeParam{{}, index, std::clamp(command.value.f, 0.0f, MAX_PORT_VOLUME)}; + return Ngs2VoicePortVolumeParam{{}, index, ClampPortVolume(command.value.f)}; } if (id == COMMAND_PORT_MATRIX && command.type == COMMAND_TYPE_INT) { PortAt(voice, index); @@ -775,7 +820,7 @@ static void ApplyCommonParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& param } case SCE_NGS2_VOICE_PARAM_PORT_VOLUME: { const auto& volume = ParamAs(param); - PortAt(voice, volume.port).volume = std::clamp(volume.level, 0.0f, MAX_PORT_VOLUME); + PortAt(voice, volume.port).volume = ClampPortVolume(volume.level); break; } case SCE_NGS2_VOICE_PARAM_PORT_MATRIX: { @@ -883,12 +928,7 @@ static void AddWaveformBlocks(Ngs2Voice& voice, const Ngs2SamplerVoiceWaveformBl } void Ngs2SetFilter(Ngs2Voice& voice, const Ngs2SamplerVoiceFilterParam& param) { - if (param.header.size < sizeof(Ngs2SamplerVoiceFilterParam)) throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_VOICE_CONTROL_SIZE}; - if (param.index >= voice.rack->maxFilters) throw Ngs2ReturnCode{PS4_LIBRARY ? SCE_NGS2_ERROR_PS4_INVALID_FILTER_INDEX : SCE_NGS2_ERROR_INVALID_PARAM_ID}; - if (!PS4_LIBRARY && param.type >= 12) throw Ngs2ReturnCode{SCE_NGS2_ERROR_INVALID_FILTER_TYPE}; - if (PS4_LIBRARY ? param.location >= PS4_MAX_FILTER_LOCATIONS : param.location >= std::uint64_t{2} + voice.ports.size()) { - throw Ngs2ReturnCode{PS4_LIBRARY ? SCE_NGS2_ERROR_PS4_INVALID_FILTER_LOCATION : SCE_NGS2_ERROR_INVALID_MODULE_LOCATION}; - } + CheckFilter(voice, param); if (voice.filters.size() <= param.index) voice.filters.resize(param.index + 1); auto& filter = voice.filters[param.index]; if (param.type == 0 || (PS4_LIBRARY && param.type >= PS4_FIRST_UNFILTERED_TYPE)) { @@ -954,26 +994,7 @@ static void ApplySamplerParam(Ngs2Voice& voice, const Ngs2VoiceParamHeader& para voice.pitch = std::clamp(ratio, 0.0f, 4.0f); break; } - case SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER: - if (PS4_LIBRARY) { - if (param.size != sizeof(Ngs2Ps4SamplerVoiceFilterParam)) throw Ngs2ReturnCode{SCE_NGS2_ERROR_PS4_INVALID_VOICE_CONTROL_SIZE}; - const auto& ps4 = reinterpret_cast(param); - Ngs2SamplerVoiceFilterParam filter{}; - filter.header = {sizeof(filter), 0, param.id}; - filter.index = ps4.index; - filter.location = ps4.location; - filter.channel_mask = ps4.channel_mask; - filter.type = ps4.type; - filter.frequency = ps4.fcq.frequency; - filter.q = ps4.fcq.q; - filter.level = ps4.fcq.level; - filter.reserved[0] = ps4.fcq.reserved; - filter.reserved[1] = ps4.fcq.reserved2; - Ngs2SetFilter(voice, filter); - } else { - Ngs2SetFilter(voice, reinterpret_cast(param)); - } - break; + case SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER: Ngs2SetFilter(voice, SamplerFilterParam(param)); break; case SCE_NGS2_SAMPLER_VOICE_PARAM_PEAK_METER: SetPeakMeter(voice, ParamAs(param).detect_type); break; default: throw std::runtime_error("NGS2: sampler voice param " + Ngs2Hex(param.id) + " is not implemented"); } @@ -1143,7 +1164,7 @@ int APS5_VABI sceNgs2VoiceRunCommands(uintptr_t voice_handle, const Ngs2VoiceCom if (expectedType == 0) throw std::runtime_error("NGS2: voice command " + Ngs2Hex(command.id) + " is not implemented"); if (id != COMMAND_PORT_VOLUME && command.type != expectedType) throw std::invalid_argument("NGS2: voice command " + Ngs2Hex(command.id) + " has value type " + Ngs2Hex(command.type)); if (id == COMMAND_MATRIX_LEVELS) SetMatrixLevels(voice, index, command.value.levels, command.count); - else if (id == COMMAND_PORT_VOLUME) PortAt(voice, index).volume = std::clamp(command.value.f, 0.0f, MAX_PORT_VOLUME); + else if (id == COMMAND_PORT_VOLUME) PortAt(voice, index).volume = ClampPortVolume(command.value.f); else SetPortMatrix(voice, index, command.value.i); } } catch (const Ngs2ReturnCode& result) { diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp index af4804eebb..234dbafe66 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Custom.cpp @@ -578,8 +578,8 @@ static void TestCustomRackChecks() { 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(!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); diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2CustomReverb.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2CustomReverb.cpp new file mode 100644 index 0000000000..56f748cccf --- /dev/null +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2CustomReverb.cpp @@ -0,0 +1,213 @@ +#include "Ngs2Test.hpp" + +#include +#include +#include + +static constexpr std::uint32_t RenderGrain = 256; + +static int APS5_VABI Impulse(Ngs2UserFxProcessContext* context) { + auto& calls = *reinterpret_cast(context->user_data0); + Require(context->num_channels == 2 && context->num_grain_samples == RenderGrain); + for (std::uint32_t channel = 0; channel < 2; channel++) { + std::fill_n(context->channel_data[channel], RenderGrain, 0.0f); + if (calls == 0) context->channel_data[channel][3] = channel == 0 ? .5f : .25f; + } + calls++; + return SCE_NGS2_OK; +} + +static void TestReverb(std::uint32_t size, bool strongHf = false) { + const auto firstBuffer = usedBuffers; + const auto system = CreateSystem(); + Require(sceNgs2SystemSetGrainSamples(system, RenderGrain) == SCE_NGS2_OK); + const auto master = Mastering(system, 2); + const std::uint32_t moduleOption[]{12, size, 0}; + Ngs2CustomSubmixerRackOption 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.state_size = 16; + option.custom_rack_option.num_buffers = 1; + option.custom_rack_option.num_modules = 2; + option.max_channels = 2; + option.max_inputs = 1; + for (auto index : {0u, 1u}) { + auto& module = option.custom_rack_option.module[index]; + module.module_id = SCE_NGS2_CUSTOM_MODULE_ID_REVERB; + module.option = reinterpret_cast(moduleOption); + } + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &common, &query) == SCE_NGS2_OK); + const auto storage = Buffer(query); + uintptr_t rack = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &common, &storage, &rack) == SCE_NGS2_OK); + std::array voices{}; + for (auto index : {0u, 1u}) { + Require(sceNgs2RackGetVoiceHandle(rack, index, &voices[index]) == SCE_NGS2_OK); + Control(voices[index], SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 2, 0, 0}); + Patch(voices[index], master); + Event(voices[index], SCE_NGS2_VOICE_EVENT_PLAY); + } + Ngs2ReverbI3DL2Param cave{1, 0, -1000, 0, 0, 2.91f, 1.3f, -602, .25f, -302, .022f, 100, 100, 5000, {}}; + if (strongHf) { + cave.reflections_delay = .015f; + cave.room_hf = -12000; + } + const auto source = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SUBMIXER)); + Control(source, SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP, Ngs2SubmixerVoiceSetupParam{{}, 2, 0}); + std::uint32_t calls = 0; + Control(source, SCE_NGS2_SUBMIXER_VOICE_PARAM_USER_FX, + Ngs2SubmixerVoiceUserFxParam{{}, Impulse, reinterpret_cast(&calls), 0, 0}); + Patch(source, voices[0]); + Event(source, SCE_NGS2_VOICE_EVENT_PLAY); + std::vector out(RenderGrain * 2); + const Ngs2RenderBufferInfo output{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2}; + Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK); + for (std::uint32_t i = 0; i < out.size(); i++) Require(out[i] == (i == 6 ? .5f : i == 7 ? .25f : 0.0f)); + calls = 0; + Control(voices[0], SCE_NGS2_CUSTOM_VOICE_PARAM_REVERB | 0x20, Ngs2ReverbVoiceI3DL2Param{{}, cave}); + auto dry = cave; + dry.wet = 0; + dry.dry = 1; + Control(voices[0], SCE_NGS2_CUSTOM_VOICE_PARAM_REVERB | 1, Ngs2ReverbVoiceI3DL2Param{{}, dry}); + for (std::uint32_t grain = 0; grain < 80; grain++) { + Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK); + for (const auto frame : {6000u, 8192u, 12000u, 16000u}) { + if (frame / RenderGrain != grain) continue; + const float expected[3][4][2]{ + {{5.78283021e-9f, 2.58373547e-7f}, {-8.08179466e-5f, 8.2077866e-5f}, {-7.49083818e-4f, -3.76510725e-4f}, {3.18058097e-4f, 6.42870436e-4f}}, + {{0, 0}, {0, 0}, {0, 0}, {8.11674454e-6f, -8.95900448e-5f}}, + {{-1.07959904e-5f, -1.03797774e-5f}, {2.38732318e-5f, 2.75960247e-5f}, {3.50958362e-5f, 3.04597361e-5f}, {2.78984935e-5f, 2.99852363e-5f}}, + }; + const auto sample = frame % RenderGrain; + const auto check = frame == 6000 ? 0 : frame == 8192 ? 1 : frame == 12000 ? 2 : 3; + Require(std::abs(out[sample * 2] - expected[strongHf ? 2 : size][check][0]) < 1e-7f); + Require(std::abs(out[sample * 2 + 1] - expected[strongHf ? 2 : size][check][1]) < 1e-7f); + } + } + Control(voices[0], SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 2, 0, 0}); + Patch(voices[0], master); + calls = 0; + Event(voices[0], SCE_NGS2_VOICE_EVENT_PLAY); + Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK); + for (std::uint32_t i = 0; i < out.size(); i++) Require(out[i] == (i == 6 ? .5f : i == 7 ? .25f : 0.0f)); + Event(voices[0], SCE_NGS2_VOICE_EVENT_STOP_IMM); + Event(voices[0], SCE_NGS2_VOICE_EVENT_PLAY); + Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK); + for (float sample : out) Require(sample == 0); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + usedBuffers = firstBuffer; +} + +static void TestSamplerRestart() { + const auto firstBuffer = usedBuffers; + const auto system = CreateSystem(); + Require(sceNgs2SystemSetGrainSamples(system, RenderGrain) == SCE_NGS2_OK); + const auto master = Mastering(system, 2); + Ngs2CustomSamplerRackOption option{}; + auto& common = option.custom_rack_option.rack_option; + common.size = sizeof(option); + common.max_grain_samples = 512; + common.max_voices = 1; + common.max_matrices = 1; + common.max_ports = 1; + option.custom_rack_option.state_size = 48; + option.custom_rack_option.num_buffers = 1; + option.custom_rack_option.num_modules = 1; + option.custom_rack_option.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_REVERB; + option.max_channel_works = 2; + option.max_waveform_blocks = 4; + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &common, &query) == SCE_NGS2_OK); + const auto storage = Buffer(query); + uintptr_t rack = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SAMPLER, &common, &storage, &rack) == SCE_NGS2_OK); + const auto voice = Voice(rack); + struct SamplerSetup { + Ngs2VoiceParamHeader header; + Ngs2WaveformFormat format; + std::uint32_t reserved[2]; + }; + Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_SETUP, + SamplerSetup{{}, {SCE_NGS2_WAVEFORM_TYPE_PCM_I16L, 2, 48000, 0, 0, 0}, {}}); + const Ngs2ReverbI3DL2Param cave{1, 0, -1000, 0, 0, 2.91f, 1.3f, -602, .015f, -302, .022f, 100, 100, 5000, {}}; + Control(voice, SCE_NGS2_CUSTOM_VOICE_PARAM_REVERB, Ngs2ReverbVoiceI3DL2Param{{}, cave}); + Patch(voice, master); + std::vector pcm(RenderGrain * 2); + pcm[6] = 16384; + pcm[7] = 8192; + Ngs2WaveformBlock block{0, pcm.size() * sizeof(std::int16_t), 0, 0, RenderGrain, 0, 0}; + Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, Ngs2SamplerVoiceWaveformBlocksParam{{}, pcm.data(), 0, 1, &block}); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + std::vector out(RenderGrain * 2); + const Ngs2RenderBufferInfo output{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2}; + for (std::uint32_t i = 0; i < 8; i++) Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK); + Require(Flags(voice) == 0); + std::vector silence(RenderGrain * 2 * 20); + block.data_size = silence.size() * sizeof(std::int16_t); + block.num_samples = silence.size() / 2; + Control(voice, SCE_NGS2_CUSTOM_SAMPLER_VOICE_PARAM_ADD_WAVEFORM_BLOCKS, + Ngs2SamplerVoiceWaveformBlocksParam{{}, silence.data(), SCE_NGS2_WAVEFORM_BLOCKS_FLAG_RESET, 1, &block}); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + for (std::uint32_t i = 0; i < 20; i++) { + Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK); + for (float sample : out) Require(sample == 0); + } + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + usedBuffers = firstBuffer; +} + +static void TestPitchShiftAndReverbSetup() { + const auto firstBuffer = usedBuffers; + const auto system = CreateSystem(); + Require(sceNgs2SystemSetGrainSamples(system, RenderGrain) == SCE_NGS2_OK); + const auto master = Mastering(system, 2); + const std::uint32_t reverbOption[]{12, 1, 0}; + Ngs2CustomSubmixerRackOption option{}; + auto& custom = option.custom_rack_option; + auto& common = custom.rack_option; + common.size = sizeof(option); + common.max_grain_samples = 512; + common.max_voices = 1; + common.max_matrices = 1; + common.max_ports = 1; + custom.state_size = 32; + custom.num_buffers = 1; + custom.num_modules = 2; + option.max_channels = 2; + option.max_inputs = 1; + custom.module[0].module_id = SCE_NGS2_CUSTOM_MODULE_ID_PITCH_SHIFT; + custom.module[1].module_id = SCE_NGS2_CUSTOM_MODULE_ID_REVERB; + custom.module[1].option = reinterpret_cast(reverbOption); + Ngs2ContextBufferInfo query{}; + Require(sceNgs2RackQueryBufferSize(SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &common, &query) == SCE_NGS2_OK); + const auto storage = Buffer(query); + std::uintptr_t rack = 0; + Require(sceNgs2RackCreate(system, SCE_NGS2_RACK_ID_CUSTOM_SUBMIXER, &common, &storage, &rack) == SCE_NGS2_OK); + const auto voice = Voice(rack); + for (int i = 0; i < 2; ++i) { + Control(voice, SCE_NGS2_CUSTOM_SUBMIXER_VOICE_PARAM_SETUP, Ngs2CustomSubmixerVoiceSetupParam{{}, 2, 2, 0, 0}); + Patch(voice, master); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + std::array samples{}; + const Ngs2RenderBufferInfo output{samples.data(), samples.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 2}; + Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK); + Require(std::all_of(samples.begin(), samples.end(), [](float value) { return value == 0.0f; })); + Event(voice, SCE_NGS2_VOICE_EVENT_STOP_IMM); + } + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + usedBuffers = firstBuffer; +} + +int main() { + TestReverb(0); + TestReverb(1); + TestReverb(1, true); + TestSamplerRestart(); + TestPitchShiftAndReverbSetup(); + return 0; +} diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp index e0000ba149..8d94523fa5 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2HandleErrors.cpp @@ -1,5 +1,7 @@ #include "Ngs2Test.hpp" #include "prx/libSceNgs2.native/include/Api.hpp" +#include "prx/libSceNgs2/include/Api.hpp" +#include "tests/ExportLookup.hpp" #include #include @@ -20,6 +22,45 @@ static void CheckExportLinked() { Require(linked != nullptr); } +static void CheckPendingExports(const char* path) { + struct Export { + int (APS5_VABI* function)(void); + const char* name; + }; + const Export exports[] = { + {sceNgs2ModuleArrayEnumItems, "sceNgs2ModuleArrayEnumItems"}, + {sceNgs2ModuleEnumConfigs, "sceNgs2ModuleEnumConfigs"}, + {sceNgs2ModuleQueueEnumItems, "sceNgs2ModuleQueueEnumItems"}, + {sceNgs2RackRunCommands, "sceNgs2RackRunCommands"}, + {sceNgs2StreamCreate, "sceNgs2StreamCreate"}, + {sceNgs2StreamCreateWithAllocator, "sceNgs2StreamCreateWithAllocator"}, + {sceNgs2StreamDestroy, "sceNgs2StreamDestroy"}, + {sceNgs2StreamQueryBufferSize, "sceNgs2StreamQueryBufferSize"}, + {sceNgs2StreamQueryInfo, "sceNgs2StreamQueryInfo"}, + {sceNgs2StreamResetOption, "sceNgs2StreamResetOption"}, + {sceNgs2StreamRunCommands, "sceNgs2StreamRunCommands"}, + {sceNgs2SystemQueryInfo, "sceNgs2SystemQueryInfo"}, + {sceNgs2SystemRunCommands, "sceNgs2SystemRunCommands"}, + }; + if (!ExportsResolveByName(path, exports, "sceNgs2NotExported")) { + throw std::runtime_error("the pending libSceNgs2.native exports do not all resolve by name"); + } +} + +static void CheckCorePendingExports(const char* path) { + struct Export { + void (*function)(void); + const char* name; + }; + const Export exports[] = { + {reinterpret_cast(&sceNgs2FftInit), "sceNgs2FftInit"}, + {reinterpret_cast(&sceNgs2FftProcess), "sceNgs2FftProcess"}, + }; + if (!ExportsResolveByName(path, exports, "sceNgs2NotExported")) { + throw std::runtime_error("the pending libSceNgs2 exports do not all resolve by name"); + } +} + 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; @@ -433,7 +474,7 @@ static void CheckEnumeration() { Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } -int main() { +int main(int argc, char** argv) { try { constexpr int systemError = static_cast(0x804A8201u); constexpr int rackError = static_cast(0x804A8202u); @@ -476,6 +517,8 @@ int main() { CheckFftQuerySize(); CheckEnumeration(); CheckExportLinked(); + CheckPendingExports(argc > 1 ? argv[1] : nullptr); + CheckCorePendingExports(argc > 2 ? argv[2] : nullptr); 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/Ngs2JobBuffer.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2JobBuffer.cpp index 7c2ca69869..75b1e1ab6f 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2JobBuffer.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2JobBuffer.cpp @@ -9,6 +9,12 @@ #include #include +#ifdef APS5_NGS2_PS4_LIBRARY +static constexpr bool Ps4Library = true; +#else +static constexpr bool Ps4Library = false; +#endif + enum class Update { Play, Start, Pause, Kill, Volume, Matrix, Combined, Pitch, Gain, PlayStop, StopPlay, PauseResume, KillPlay, PlayKill, PlayPause, StopResume, LongPause, Mute, SelectMatrix, Over, Shift, ExternalShift, UnchangedShift }; struct BufferedState { std::uintptr_t voice; @@ -95,13 +101,17 @@ static int APS5_VABI UpdatePeer(Ngs2UserFxProcessContext* context) { Require(volume() == 2.0f); } send(-1.0f, 1); - Require(std::bit_cast(volume()) == 0); + Require(std::bit_cast(volume()) == (Ps4Library ? std::bit_cast(-1.0f) : 0)); send(std::bit_cast(0x7fc00000u), 1); Require(std::bit_cast(volume()) == 0x7fc00000u); send(-0.0f, 1); Require(std::bit_cast(volume()) == 0x80000000u); + if (Ps4Library) { + send(-16.0f, 1); + Require(volume() == -4.0f); + } send(33.0f, 1); - Require(volume() == 32.0f); + Require(volume() == (Ps4Library ? 4.0f : 32.0f)); break; } case Update::Shift: @@ -237,7 +247,7 @@ static void TestBuffer(Update update, bool custom, bool commands, unsigned order if (captured) captured->insert(captured->end(), samples.begin(), samples.begin() + grainFrames); for (unsigned i = 0; !shift && i < grainFrames; ++i) { if (update == Update::Over) { - if (grain != 1) Require(samples[i] == (grain == 0 && i < 3 ? 0.0f : 1000.0f / 32768.0f) * (grain == 0 ? 1.0f : 32.0f)); + if (grain != 1) Require(samples[i] == (grain == 0 && i < 3 ? 0.0f : 1000.0f / 32768.0f) * (grain == 0 ? 1.0f : Ps4Library ? 4.0f : 32.0f)); continue; } float expected = 1000.0f / 32768.0f; @@ -562,6 +572,69 @@ static void TestBufferedWaveformCodes() { ResetBuffers(firstBuffer); } +static Ngs2SamplerVoiceFilterParam LowPass(std::uint32_t index, std::uint32_t location, std::uint32_t type) { + Ngs2SamplerVoiceFilterParam param{}; + param.header = {sizeof(param), 0, SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER}; + param.index = index; + param.location = location; + param.type = type; + param.frequency = 1000.0f; + param.q = 0.707f; + param.level = 1.0f; + return param; +} + +static float RenderEnergy(std::uintptr_t system) { + std::array samples{}; + const Ngs2RenderBufferInfo output{samples.data(), sizeof(samples), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1}; + Require(sceNgs2SystemRender(system, &output, 1) == SCE_NGS2_OK); + float energy = 0.0f; + for (const float sample : samples) energy += sample * sample; + return energy; +} + +static void TestBufferedSamplerFilter() { + const auto firstBuffer = usedBuffers; + std::array pcm{}; + for (std::size_t i = 0; i < pcm.size(); ++i) pcm[i] = i % 2 == 0 ? 8000 : -8000; + PeerAction peer; + const auto setup = CreatePeerSystem(peer); + const auto voice = MonoSampler(setup.system); + Require(AddBlocks(voice, pcm.data(), 0, {0, static_cast(sizeof(pcm)), 0, 0, static_cast(pcm.size()), 0, 0}) == SCE_NGS2_OK); + Patch(voice, setup.master); + Event(voice, SCE_NGS2_VOICE_EVENT_PLAY); + const auto direct = MonoSampler(setup.system); + const auto custom = Sampler(setup.system, true); + const auto badType = LowPass(0, 1, 12); + const auto badIndex = LowPass(8, 1, 1); + const auto badLocation = LowPass(0, 1000, 1); + const Ngs2VoiceParamHeader truncated{sizeof(Ngs2VoiceParamHeader), 0, SCE_NGS2_SAMPLER_VOICE_PARAM_FILTER}; + const auto filter = LowPass(0, 1, 1); + const std::array rejected{&badType.header, &badIndex.header, &badLocation.header, &truncated}; + std::array immediate{}; + for (std::size_t i = 0; i < rejected.size(); ++i) { + immediate[i] = sceNgs2VoiceControl(direct, rejected[i]); + Require(immediate[i] != SCE_NGS2_OK); + } + const int customImmediate = sceNgs2VoiceControl(custom, &filter.header); + Require(customImmediate != SCE_NGS2_OK); + unsigned checked = 0; + peer.action = [&](unsigned tick) { + if (tick != 0) return; + for (std::size_t i = 0; i < rejected.size(); ++i) Require(sceNgs2VoiceControl(voice, rejected[i]) == immediate[i]); + Require(sceNgs2VoiceControl(custom, &filter.header) == customImmediate); + Require(sceNgs2VoiceControl(voice, &filter.header) == SCE_NGS2_OK); + ++checked; + }; + const float unfiltered = RenderEnergy(setup.system); + Require(unfiltered > 1.0f); + float filtered = unfiltered; + for (unsigned grain = 0; grain < 6; ++grain) filtered = RenderEnergy(setup.system); + Require(checked == 1 && filtered < unfiltered * 1.0e-3f); + Require(sceNgs2SystemDestroy(setup.system, nullptr) == SCE_NGS2_OK); + ResetBuffers(firstBuffer); +} + static void TestBufferedAppendAfterDrain() { const auto firstBuffer = usedBuffers; std::array pcm{}; @@ -732,9 +805,13 @@ static void TestBlockQueueRace() { ResetBuffers(firstBuffer); } +static void TestPortVolumeOver() { + for (bool custom : {false, true}) for (bool commands : {false, true}) for (unsigned order = 0; order < 3; ++order) TestBuffer(Update::Over, custom, commands, order, 64); +} + int main(int argc, char** argv) { if (argc > 1) { - const std::pair scenarios[] = {{"validation", TestCaptureValidation}, {"drained", TestBufferedAppendAfterDrain}, + const std::pair scenarios[] = {{"over", TestPortVolumeOver}, {"validation", TestCaptureValidation}, {"filter", TestBufferedSamplerFilter}, {"drained", TestBufferedAppendAfterDrain}, {"emptysetup", TestEmptySetupPort}, {"destroyed", TestPendingPatchDestroyedDest}, {"race", TestBlockQueueRace}}; for (const auto& [name, run] : scenarios) { if (std::strcmp(argv[1], name) != 0) continue; @@ -760,10 +837,8 @@ int main(int argc, char** argv) { } } TestBuffer(Update::Combined, true, true, 2, 256); - for (bool custom : {false, true}) for (bool commands : {false, true}) for (unsigned order = 0; order < 3; ++order) { - TestBuffer(Update::Over, custom, commands, order, 64); - cases++; - } + TestPortVolumeOver(); + cases += 12; std::vector worker, external, unchanged; TestBuffer(Update::Shift, true, false, 2, 256, &worker); TestBuffer(Update::ExternalShift, true, false, 2, 256, &external); @@ -785,11 +860,12 @@ int main(int argc, char** argv) { ++cases; TestCaptureValidation(); TestBufferedWaveformCodes(); + TestBufferedSamplerFilter(); TestBufferedAppendAfterDrain(); TestEmptySetupPort(); TestPendingPatchDestroyedDest(); TestBlockQueueRace(); - cases += 6; + cases += 7; std::printf("NGS2 worker buffering: %u scenarios passed\n", cases + 1); return 0; } diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp index 6e7e2d2694..53cba7e77e 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Parse.cpp @@ -4,8 +4,12 @@ #include #include +#include +#include +#include #include #include +#include #include static void Put16(std::vector& out, std::uint32_t value) { @@ -108,6 +112,99 @@ static void TestParsePcm() { Require(sceNgs2ParseWaveformData(noChannels.data(), noChannels.size(), &info) == SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT); } +static void AppendLoops(std::vector& file, std::uint32_t loops, std::uint32_t type, std::uint32_t begin, std::uint32_t lastSample, std::uint32_t plays, + std::uint32_t fraction = 0) { + PutTag(file, "smpl"); + Put32(file, 36 + 24 * loops); + for (int i = 0; i < 7; ++i) Put32(file, 0); + Put32(file, loops); + Put32(file, 0); + for (std::uint32_t loop = 0; loop < loops; ++loop) { + Put32(file, loop); + Put32(file, type); + Put32(file, begin); + Put32(file, lastSample); + Put32(file, fraction); + Put32(file, plays); + } + const auto riffSize = static_cast(file.size() - 8); + std::memcpy(file.data() + 4, &riffSize, sizeof(riffSize)); +} + +static void RequireBlock(const Ngs2WaveformBlock& block, std::uint64_t offset, std::uint64_t size, std::uint32_t repeats, std::uint32_t samples) { + Require(block.data_offset == offset && block.data_size == size && block.num_repeats == repeats && block.num_skip_samples == 0 && block.num_samples == samples); +} + +static void AppendShortSampler(std::vector& file, std::uint32_t size, std::uint32_t loops) { + PutTag(file, "smpl"); + Put32(file, size); + for (std::uint32_t offset = 0; offset < size; offset += 4) Put32(file, offset == 28 ? loops : 0); + const auto riffSize = static_cast(file.size() - 8); + std::memcpy(file.data() + 4, &riffSize, sizeof(riffSize)); +} + +static Ngs2WaveformInfo ParseLooped(std::uint32_t loops, std::uint32_t type, std::uint32_t begin, std::uint32_t lastSample, std::uint32_t plays, std::uint32_t fraction = 0) { + auto file = PcmFile(1, 44100, 16, 100); + AppendLoops(file, loops, type, begin, lastSample, plays, fraction); + Ngs2WaveformInfo info{}; + Require(sceNgs2ParseWaveformData(file.data(), file.size(), &info) == SCE_NGS2_OK); + return info; +} + +static void TestParsePcmLoops() { + for (const std::uint32_t bits : {16u, 24u}) { + const std::uint32_t frame = 2 * bits / 8; + auto looped = PcmFile(2, 48000, bits, 100); + AppendLoops(looped, 1, 0, 10, 59, 0); + Ngs2WaveformInfo info{}; + Require(sceNgs2ParseWaveformData(looped.data(), looped.size(), &info) == SCE_NGS2_OK); + Require(info.num_samples == 100 && info.loop_begin_position == 10 && info.loop_end_position == 60 && info.num_blocks == 3); + RequireBlock(info.block[0], 44, 10 * frame, 0, 10); + RequireBlock(info.block[1], 44 + 10 * frame, 50 * frame, UINT32_MAX, 50); + RequireBlock(info.block[2], 44 + 60 * frame, 40 * frame, 0, 40); + } + + for (const auto& info : {ParseLooped(1, 0, 10, 59, 3), ParseLooped(2, 1, 10, 59, 0), ParseLooped(3, 2, 10, 59, 7, 0x80000000u)}) { + Require(info.loop_begin_position == 10 && info.loop_end_position == 60 && info.num_blocks == 3); + RequireBlock(info.block[0], 44, 20, 0, 10); + RequireBlock(info.block[1], 64, 100, UINT32_MAX, 50); + RequireBlock(info.block[2], 164, 80, 0, 40); + } + + auto info = ParseLooped(1, 0, 0, 99, 3); + Require(info.loop_begin_position == 0 && info.loop_end_position == 100 && info.num_blocks == 1); + RequireBlock(info.block[0], 44, 200, UINT32_MAX, 100); + + info = ParseLooped(1, 0, 0, 49, 0); + Require(info.loop_begin_position == 0 && info.loop_end_position == 50 && info.num_blocks == 2); + RequireBlock(info.block[0], 44, 100, UINT32_MAX, 50); + RequireBlock(info.block[1], 144, 100, 0, 50); + + info = ParseLooped(1, 0, 30, 99, 0); + Require(info.loop_begin_position == 30 && info.loop_end_position == 100 && info.num_blocks == 2); + RequireBlock(info.block[0], 44, 60, 0, 30); + RequireBlock(info.block[1], 104, 140, UINT32_MAX, 70); + + info = ParseLooped(1, 0, 50, 50, 0); + Require(info.loop_begin_position == 50 && info.loop_end_position == 51 && info.num_blocks == 3); + RequireBlock(info.block[1], 144, 2, UINT32_MAX, 1); + RequireBlock(info.block[2], 146, 98, 0, 49); + + for (const auto [begin, lastSample, end] : {std::tuple{50u, 49u, 50u}, {50u, 10u, 11u}, {20u, 0xffffffffu, 0u}}) { + info = ParseLooped(1, 0, begin, lastSample, 0); + Require(info.loop_begin_position == begin && info.loop_end_position == end && info.num_blocks == 1); + RequireBlock(info.block[0], 44, 200, 0, 100); + } + + info = ParseLooped(0, 0, 10, 59, 0); + Require(info.num_blocks == 1 && info.loop_begin_position == 0 && info.loop_end_position == 0); + RequireBlock(info.block[0], 44, 200, 0, 100); + + auto emptySampler = PcmFile(1, 44100, 16, 100); + AppendShortSampler(emptySampler, 36, 0); + Require(sceNgs2ParseWaveformData(emptySampler.data(), emptySampler.size(), &info) == SCE_NGS2_OK && info.num_blocks == 1); +} + static void TestParseUnknownFormats() { const std::uint8_t pcmGuid[16] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}; const std::uint8_t unknownGuid[16] = {0xaa, 0xbb, 0xcc, 0xdd, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc}; @@ -270,6 +367,7 @@ static void TestParseUser() { int main() { TestParsePcm(); + TestParsePcmLoops(); TestParseUser(); TestParseUnknownFormats(); TestParseFile(); diff --git a/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp b/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp index 7e68529007..3cac4f1406 100644 --- a/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp +++ b/core/libs/prx/libSceNgs2.native/tests/Ngs2Render.cpp @@ -1561,18 +1561,6 @@ static void TestStereoIntoSurround() { Require(outSeven[i * 8] == (i < 3 ? 0.0f : 0.5f) && outSeven[i * 8 + 1] == (i < 3 ? 0.0f : 0.25f)); for (std::uint32_t channel = 2; channel < 8; channel++) Require(outSeven[i * 8 + channel] == 0.0f); } - - const auto rejectedSource = RackVoice(samplerRack, 2); - StereoSource(rejectedSource, pcm, masters[2]); - Event(rejectedSource, SCE_NGS2_VOICE_EVENT_PLAY); - bool threw = false; - try { - sceNgs2SystemRender(system, info, 3); - - } catch (const std::runtime_error&) { - threw = true; - } - Require(threw); Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); } @@ -1984,6 +1972,112 @@ static std::vector MixFrame(std::uint32_t sourceChannels, std::uint32_t s return std::vector(out.begin() + 3 * destChannels, out.begin() + 4 * destChannels); } +struct PortVolumeFx { + uintptr_t sampler = 0; + std::vector> cases; + bool command = false; + std::uint32_t calls = 0; +}; + +static int APS5_VABI SetPortVolumes(Ngs2UserFxProcessContext* context) { + auto& fx = *reinterpret_cast(context->user_data0); + if (fx.calls++ != 0) return SCE_NGS2_OK; + for (const auto& [sent, stored] : fx.cases) { + if (fx.command) { + const auto set = PortVolumeCommand(0, sent); + Require(sceNgs2VoiceRunCommands(fx.sampler, &set, 1) == SCE_NGS2_OK); + } else { + Control(fx.sampler, SCE_NGS2_VOICE_PARAM_PORT_VOLUME, Ngs2VoicePortVolumeParam{{}, 0, sent}); + } + Require(SameBits(PortVolume(fx.sampler, 0), stored)); + } + return SCE_NGS2_OK; +} + +static void TestPortVolumeRangeLikePs4() { + const std::vector pcm(Grain, 4096); + const float inf = INFINITY; + const float nan = std::bit_cast(0x7fc00000u); + const float negativeNan = std::bit_cast(0xffc00000u); + const float tiny = std::bit_cast(0x000116c2u); + const float negativeTiny = std::bit_cast(0x800116c2u); + const float aboveFour = 4.000001f; + const float belowFour = 3.999999f; + const auto check = [&](bool command, float sent, float stored, bool rendered) { + const auto system = CreateSystem(); + const auto sampler = Sampler(system, pcm, 0); + Patch(sampler, Mastering(system, 1)); + if (command) { + const auto set = PortVolumeCommand(0, sent); + Require(sceNgs2VoiceRunCommands(sampler, &set, 1) == SCE_NGS2_OK); + } else { + Control(sampler, SCE_NGS2_VOICE_PARAM_PORT_VOLUME, Ngs2VoicePortVolumeParam{{}, 0, sent}); + } + Require(SameBits(PortVolume(sampler, 0), stored)); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + std::vector out(Grain, -1.0f); + const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1}; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + for (std::size_t i = 0; rendered && i < out.size(); ++i) Require(std::isnan(stored) ? std::isnan(out[i]) : out[i] == (i < 3 ? 0.0f : 0.125f * stored)); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + }; + const std::vector> cases{ + {0.0f, 0.0f}, {-0.0f, -0.0f}, {0.5f, 0.5f}, {1.0f, 1.0f}, {2.5f, 2.5f}, {belowFour, belowFour}, {4.0f, 4.0f}, + {aboveFour, 4.0f}, {4.01f, 4.0f}, {5.0f, 4.0f}, {8.0f, 4.0f}, {32.0f, 4.0f}, {33.0f, 4.0f}, {1e30f, 4.0f}, {inf, 4.0f}, + {-0.5f, -0.5f}, {-1.0f, -1.0f}, {-3.9f, -3.9f}, {-belowFour, -belowFour}, {-4.0f, -4.0f}, + {-aboveFour, -4.0f}, {-5.0f, -4.0f}, {-16.0f, -4.0f}, {-1e30f, -4.0f}, {-inf, -4.0f}, + {nan, nan}, {negativeNan, negativeNan}}; + for (const bool command : {false, true}) { + for (const auto& [sent, stored] : cases) check(command, sent, stored, true); + check(command, tiny, tiny, false); + check(command, negativeTiny, negativeTiny, false); + } + + for (const bool command : {false, true}) { + const auto system = CreateSystem(); + const auto master = Mastering(system, 1); + const auto fxVoice = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SUBMIXER)); + Control(fxVoice, SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP, Ngs2SubmixerVoiceSetupParam{{}, 1, 0}); + const auto sampler = Sampler(system, pcm, 100); + PortVolumeFx fx{sampler, {{5.0f, 4.0f}, {-5.0f, -4.0f}, {aboveFour, 4.0f}, {33.0f, 4.0f}, {-0.5f, -0.5f}, {-16.0f, -4.0f}}, command}; + Control(fxVoice, SCE_NGS2_SUBMIXER_VOICE_PARAM_USER_FX, Ngs2SubmixerVoiceUserFxParam{{}, SetPortVolumes, reinterpret_cast(&fx), 0, 0}); + Patch(fxVoice, master); + Patch(sampler, fxVoice); + Event(fxVoice, SCE_NGS2_VOICE_EVENT_PLAY); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + std::vector out(Grain, -1.0f); + const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1}; + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + Require(fx.calls == 1 && SameBits(PortVolume(sampler, 0), -4.0f)); + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + Require(fx.calls == 3 && SameBits(PortVolume(sampler, 0), -4.0f)); + for (std::size_t i = 0; i < out.size(); ++i) Require(out[i] == 0.125f * -4.0f); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + } + + for (const auto& [sent, stored] : std::vector>{ + {1.5f, 1.5f}, {belowFour, belowFour}, {4.0f, 4.0f}, {aboveFour, 4.0f}, {5.0f, 4.0f}, {32.0f, 4.0f}, {1e30f, 4.0f}, {inf, 4.0f}, + {-1.0f, -1.0f}, {-belowFour, -belowFour}, {-4.0f, -4.0f}, {-aboveFour, -4.0f}, {-5.0f, -4.0f}, {-16.0f, -4.0f}, {-inf, -4.0f}, {nan, nan}, {-0.0f, -0.0f}}) { + const auto system = CreateSystem(); + const auto master = Mastering(system, 1); + const auto submixer = Voice(CreateRack(system, SCE_NGS2_RACK_ID_SUBMIXER)); + Control(submixer, SCE_NGS2_SUBMIXER_VOICE_PARAM_SETUP, Ngs2SubmixerVoiceSetupParam{{}, 1, 0}); + Patch(submixer, master); + Event(submixer, SCE_NGS2_VOICE_EVENT_PLAY); + const auto sampler = Sampler(system, pcm, 100); + Patch(sampler, submixer); + Event(sampler, SCE_NGS2_VOICE_EVENT_PLAY); + Control(submixer, SCE_NGS2_VOICE_PARAM_PORT_VOLUME, Ngs2VoicePortVolumeParam{{}, 0, sent}); + Require(SameBits(PortVolume(submixer, 0), stored)); + std::vector out(Grain, -1.0f); + const Ngs2RenderBufferInfo info{out.data(), out.size() * sizeof(float), SCE_NGS2_WAVEFORM_TYPE_PCM_F32L, 1}; + for (int render = 0; render < 3; ++render) Require(sceNgs2SystemRender(system, &info, 1) == SCE_NGS2_OK); + for (std::size_t i = 0; i < out.size(); ++i) Require(std::isnan(stored) ? std::isnan(out[i]) : out[i] == 0.125f * stored); + Require(sceNgs2SystemDestroy(system, nullptr) == SCE_NGS2_OK); + } +} + static void TestDefaultChannelMap() { const float half = 0.5f * HalfPower; const float quarter = 0.5f * (std::numbers::sqrt2_v / 4.0f); @@ -2865,7 +2959,8 @@ int main() { TestMasteringGain(); TestMatrixLevelClamp(); - TestPortVolumeClampedLikePs5(); + if (!Ps4Library) TestPortVolumeClampedLikePs5(); + else TestPortVolumeRangeLikePs4(); TestStereoIntoSurround(); TestLock(); diff --git a/core/libs/prx/libSceNgs2/CMakeLists.txt b/core/libs/prx/libSceNgs2/CMakeLists.txt index 7fcf07b212..ce8456ad84 100644 --- a/core/libs/prx/libSceNgs2/CMakeLists.txt +++ b/core/libs/prx/libSceNgs2/CMakeLists.txt @@ -43,5 +43,15 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() add_sce_ngs2_test(libSceNgs2 ngs2_core_pcm Ngs2Pcm.cpp) + add_test_executable(ngs2_core_jobbuffer_tests ${CMAKE_CURRENT_SOURCE_DIR}/../libSceNgs2.native/tests/Ngs2JobBuffer.cpp) + target_include_directories(ngs2_core_jobbuffer_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/LibAtrac9/C/src) + target_link_libraries(ngs2_core_jobbuffer_tests PRIVATE libSceNgs2 atrac9 libkernel libc) + target_compile_definitions(ngs2_core_jobbuffer_tests PRIVATE APS5_NGS2_PS4_LIBRARY) + configure_windows_unwind(ngs2_core_jobbuffer_tests) + add_test(NAME ngs2_core_jobbuffer COMMAND ngs2_core_jobbuffer_tests over) + if(WIN32) + set_tests_properties(ngs2_core_jobbuffer PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_sce_ngs2_test(libSceNgs2 ngs2_core_ps4_voice_state Ngs2Ps4VoiceState.cpp) endif() diff --git a/core/libs/prx/libSceNpCommon/CMakeLists.txt b/core/libs/prx/libSceNpCommon/CMakeLists.txt index b5896f2365..00aaab61bb 100644 --- a/core/libs/prx/libSceNpCommon/CMakeLists.txt +++ b/core/libs/prx/libSceNpCommon/CMakeLists.txt @@ -3,6 +3,19 @@ if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp") target_sources(libSceNpCommon PRIVATE Export.cpp) endif() target_sources(libSceNpCommon PRIVATE Json.cpp Runtime.cpp Strings.cpp) +if(BUILD_TESTING) + add_test_executable(guest_np_integer_radices_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpIntegerRadices.cpp) + target_include_directories(guest_np_integer_radices_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_integer_radices_tests PRIVATE libSceNpCommon libc) + configure_windows_unwind(guest_np_integer_radices_tests) + add_test(NAME guest_np_integer_radices COMMAND guest_np_integer_radices_tests) + set_tests_properties(guest_np_integer_radices PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_integer_radices PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + 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 index d32ce8ed7c..d26cc3bec1 100644 --- a/core/libs/prx/libSceNpCommon/Export.cpp +++ b/core/libs/prx/libSceNpCommon/Export.cpp @@ -165,6 +165,7 @@ int APS5_VABI sceNpGetSystemClockUsec(std::int64_t* usec) { } void APS5_VABI sceNpGetSdkVersion(char* version) { + std::memset(version, 0, 8); SceKernelSystemSwVersion swVersion{}; swVersion.size = sizeof(swVersion); sceKernelGetSystemSwVersion_nid_postfix(&swVersion); diff --git a/core/libs/prx/libSceNpCommon/PendingExports.cpp b/core/libs/prx/libSceNpCommon/PendingExports.cpp index 718ddf19b7..bcdb755c1f 100644 --- a/core/libs/prx/libSceNpCommon/PendingExports.cpp +++ b/core/libs/prx/libSceNpCommon/PendingExports.cpp @@ -124,9 +124,4 @@ int APS5_VABI sceNpUtilGetWebTraceSetting(void) { return 0; } -int APS5_VABI sceNpUtilJidToNpId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp b/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp index 28e23ac137..84627b0063 100644 --- a/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp +++ b/core/libs/prx/libSceNpCommon/PendingExportsRound7.cpp @@ -11,21 +11,11 @@ int APS5_VABI _ZN3sce2np10CancelableC2Ev(void) { return 0; } -int APS5_VABI sceNpUtilXmlEscape(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np4CondD0Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _sceNpAllocatorExRealloc(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np8WorkItem6CancelEi(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -81,11 +71,6 @@ int APS5_VABI _ZN3sce2np10CancelLockC2Ev(void) { return 0; } -int APS5_VABI _ZN3sce2np13NpTitleSecretC2ERK16SceNpTitleSecret(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np8WorkItem10SetRunningEv(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -211,21 +196,11 @@ int APS5_VABI _ZN3sce2np12HttpTemplate19SetAuthInfoCallbackEPFii15SceHttpAuthTyp return 0; } -int APS5_VABI sceNpUtilParseJid(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np9Semaphore6CreateEiiPKc(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceNpUtilXmlUnescape(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceNpXmlParse(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -411,11 +386,6 @@ int APS5_VABI _ZThn16_N3sce2np10MemoryFileD1Ev(void) { return 0; } -int APS5_VABI _sceNpAllocatorExFree(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient7ConnectEPKvm(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -436,11 +406,6 @@ int APS5_VABI _ZN3sce2np9HttpTrans21GetResponseStatusCodeEPNS0_6HandleEPi(void) return 0; } -int APS5_VABI _ZN3sce2np13NpTitleSecretC2ERKS1_(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3sce2np14CalloutContextD1Ev(void) { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceNpCommon/Runtime.cpp b/core/libs/prx/libSceNpCommon/Runtime.cpp index 669acaed60..b3f6a2ff8c 100644 --- a/core/libs/prx/libSceNpCommon/Runtime.cpp +++ b/core/libs/prx/libSceNpCommon/Runtime.cpp @@ -134,6 +134,21 @@ bool IsTitleIdDigit(char c) { return c >= '0' && c <= '9'; } +constexpr std::size_t NP_TITLE_SECRET_SIZE = sizeof(NpTitleSecret); + +void* const NpTitleSecretVtable[] = { + nullptr, nullptr, + reinterpret_cast(&_ZN3sce2np13NpTitleSecretD1Ev), reinterpret_cast(&_ZN3sce2np13NpTitleSecretD0Ev), +}; + +bool SameTitleSecret(const NpTitleSecret* lhs, const void* rhs) { + return std::memcmp(lhs->data, rhs, NP_TITLE_SECRET_SIZE) == 0; +} + +int OnlyFailure(int result) { + return result < 0 ? result : 0; +} + int StoreTitleId(NpTitleIdObject* out, const char* text, std::size_t length, std::size_t expected) { if (length != expected) return NP_ERROR_TITLE_ID_INVALID; if (!std::all_of(text, text + 4, IsTitleIdLetter) || !std::all_of(text + 4, text + NP_TITLE_ID_CODE_LENGTH, IsTitleIdDigit)) @@ -719,6 +734,38 @@ NpAllocatorEx* APS5_VABI sceNpGlobalHeapGetAllocatorExPtr(void) { return &g_globalAllocatorEx; } +NpAllocator* APS5_VABI _sceNpAllocatorExConvertAllocator(NpAllocatorEx* allocator) { + return &allocator->allocator; +} + +void* APS5_VABI _sceNpAllocatorExMalloc(NpAllocatorEx* allocator, std::size_t size) { + return allocator->allocator.allocate(size, allocator->allocator.context); +} + +void* APS5_VABI _sceNpAllocatorExRealloc(NpAllocatorEx* allocator, void* pointer, std::size_t size) { + return allocator->allocator.reallocate(pointer, size, allocator->allocator.context); +} + +void APS5_VABI _sceNpAllocatorExFree(NpAllocatorEx* allocator, void* pointer) { + allocator->allocator.release(pointer, allocator->allocator.context); +} + +char* APS5_VABI _sceNpAllocatorExStrdup(NpAllocatorEx* allocator, const char* text) { + const std::size_t size = std::strlen(text) + 1; + auto* copy = static_cast(_sceNpAllocatorExMalloc(allocator, size)); + if (copy != nullptr) std::memcpy(copy, text, size); + return copy; +} + +char* APS5_VABI _sceNpAllocatorExStrndup(NpAllocatorEx* allocator, const char* text, std::size_t maxLength) { + const std::size_t length = strnlen(text, maxLength); + auto* copy = static_cast(_sceNpAllocatorExMalloc(allocator, length + 1)); + if (copy == nullptr) return nullptr; + std::memcpy(copy, text, length); + copy[length] = '\0'; + return copy; +} + void* APS5_VABI _ZN3sce2np6ObjectnwEmR14SceNpAllocator(std::size_t size, NpAllocator* allocator) { return AllocateObject(size, allocator); } @@ -1046,6 +1093,8 @@ int APS5_VABI sceNpCalloutInitCtx(NpCalloutContext* context, const char* name, s void APS5_VABI sceNpCalloutTermCtx(NpCalloutContext* context) { if (context->initialized == 0) return; + if (scePthreadEqual(scePthreadSelf(), context->thread) != 0) + throw std::runtime_error("sceNpCalloutTermCtx: terminating a callout context from one of its own callouts is not implemented"); sceNpMutexLock(&context->mutex); context->stopping = 1; @@ -1359,6 +1408,107 @@ bool APS5_VABI _ZN3sce2npneERKNS0_9NpTitleIdERK12SceNpTitleId(const NpTitleIdObj return !SameTitleId(lhs->titleId.data, rhs->data); } +void APS5_VABI _ZN3sce2np13NpTitleSecretC1Ev(NpTitleSecretObject* self) { + self->vtable = &NpTitleSecretVtable[2]; + self->secret = {}; +} + +void APS5_VABI _ZN3sce2np13NpTitleSecretC2Ev(NpTitleSecretObject* self) { + _ZN3sce2np13NpTitleSecretC1Ev(self); +} + +void APS5_VABI _ZN3sce2np13NpTitleSecretC1ERKS1_(NpTitleSecretObject* self, const NpTitleSecretObject* other) { + self->vtable = &NpTitleSecretVtable[2]; + self->secret = other->secret; +} + +void APS5_VABI _ZN3sce2np13NpTitleSecretC2ERKS1_(NpTitleSecretObject* self, const NpTitleSecretObject* other) { + _ZN3sce2np13NpTitleSecretC1ERKS1_(self, other); +} + +void APS5_VABI _ZN3sce2np13NpTitleSecretC1ERK16SceNpTitleSecret(NpTitleSecretObject* self, const NpTitleSecret* secret) { + self->vtable = &NpTitleSecretVtable[2]; + self->secret = *secret; +} + +void APS5_VABI _ZN3sce2np13NpTitleSecretC2ERK16SceNpTitleSecret(NpTitleSecretObject* self, const NpTitleSecret* secret) { + _ZN3sce2np13NpTitleSecretC1ERK16SceNpTitleSecret(self, secret); +} + +void APS5_VABI _ZN3sce2np13NpTitleSecretC1EPKvm(NpTitleSecretObject* self, const void* data, std::size_t size) { + self->vtable = &NpTitleSecretVtable[2]; + if (size != NP_TITLE_SECRET_SIZE) throw std::invalid_argument("sce::np::NpTitleSecret: the size is not 128"); + std::memcpy(self->secret.data, data, NP_TITLE_SECRET_SIZE); +} + +void APS5_VABI _ZN3sce2np13NpTitleSecretC2EPKvm(NpTitleSecretObject* self, const void* data, std::size_t size) { + _ZN3sce2np13NpTitleSecretC1EPKvm(self, data, size); +} + +void APS5_VABI _ZN3sce2np13NpTitleSecretD1Ev(NpTitleSecretObject*) {} + +void APS5_VABI _ZN3sce2np13NpTitleSecretD2Ev(NpTitleSecretObject*) {} + +void APS5_VABI _ZN3sce2np13NpTitleSecretD0Ev(NpTitleSecretObject* self) { + _ZN3sce2np6ObjectdlEPv(self); +} + +NpTitleSecretObject* APS5_VABI _ZN3sce2np13NpTitleSecretaSERKS1_(NpTitleSecretObject* self, const NpTitleSecretObject* other) { + self->secret = other->secret; + return self; +} + +int APS5_VABI _ZN3sce2np13NpTitleSecret13SetSecretDataEPvm(NpTitleSecretObject* self, const void* data, std::size_t size) { + if (size != NP_TITLE_SECRET_SIZE) return NP_ERROR_TITLE_ID_INVALID; + std::memcpy(self->secret.data, data, NP_TITLE_SECRET_SIZE); + return 0; +} + +bool APS5_VABI _ZNK3sce2np13NpTitleSecret7IsEmptyEv(const NpTitleSecretObject* self) { + const NpTitleSecret empty {}; + return SameTitleSecret(&self->secret, empty.data); +} + +bool APS5_VABI _ZNK3sce2np13NpTitleSecret6EqualsEPvm(const NpTitleSecretObject* self, const void* data, std::size_t size) { + return size == NP_TITLE_SECRET_SIZE && SameTitleSecret(&self->secret, data); +} + +bool APS5_VABI _ZNK3sce2np13NpTitleSecret6EqualsERKS1_(const NpTitleSecretObject* self, const NpTitleSecretObject* other) { + return SameTitleSecret(&self->secret, other->secret.data); +} + +bool APS5_VABI _ZNK3sce2np13NpTitleSecret6EqualsERK16SceNpTitleSecret(const NpTitleSecretObject* self, const NpTitleSecret* secret) { + return SameTitleSecret(&self->secret, secret->data); +} + +int APS5_VABI _ZN3sce2np13NpTitleSecret8ParseHexEPS1_PKcm(NpTitleSecretObject* out, const char* hex, std::size_t length) { + return OnlyFailure(sceNpStrnParseHex(hex, length, out->secret.data, NP_TITLE_SECRET_SIZE)); +} + +int APS5_VABI _ZN3sce2np13NpTitleSecret8BuildHexERKS1_Pcmb(const NpTitleSecretObject* secret, char* hex, std::size_t size, bool uppercase) { + return OnlyFailure(sceNpStrBuildHex(hex, size, secret->secret.data, NP_TITLE_SECRET_SIZE, uppercase)); +} + +bool APS5_VABI _ZN3sce2npeqERKNS0_13NpTitleSecretES3_(const NpTitleSecretObject* lhs, const NpTitleSecretObject* rhs) { + return SameTitleSecret(&lhs->secret, rhs->secret.data); +} + +bool APS5_VABI _ZN3sce2npeqERK16SceNpTitleSecretRKNS0_13NpTitleSecretE(const NpTitleSecret* lhs, const NpTitleSecretObject* rhs) { + return SameTitleSecret(lhs, rhs->secret.data); +} + +bool APS5_VABI _ZN3sce2npeqERKNS0_13NpTitleSecretERK16SceNpTitleSecret(const NpTitleSecretObject* lhs, const NpTitleSecret* rhs) { + return SameTitleSecret(&lhs->secret, rhs->data); +} + +bool APS5_VABI _ZN3sce2npneERKNS0_13NpTitleSecretES3_(const NpTitleSecretObject* lhs, const NpTitleSecretObject* rhs) { + return !SameTitleSecret(&lhs->secret, rhs->secret.data); +} + +bool APS5_VABI _ZN3sce2npneERK16SceNpTitleSecretRKNS0_13NpTitleSecretE(const NpTitleSecret* lhs, const NpTitleSecretObject* rhs) { + return !SameTitleSecret(lhs, rhs->secret.data); +} + int APS5_VABI _ZN3sce2np12StreamReader4ReadEPNS0_6HandleEPNS0_9StreamCtxEPvmPm(NpStreamReader* self, void* handle, NpStreamCtx* ctx, void* out, std::size_t size, std::size_t* read) { int result = StreamRead(self, handle, out, size, read); if (result < 0) return result; diff --git a/core/libs/prx/libSceNpCommon/Strings.cpp b/core/libs/prx/libSceNpCommon/Strings.cpp index 79d4b8641e..4f865d4438 100644 --- a/core/libs/prx/libSceNpCommon/Strings.cpp +++ b/core/libs/prx/libSceNpCommon/Strings.cpp @@ -1,4 +1,5 @@ #include "prx/libSceNpCommon/include/Api.hpp" +#include #include #include #include @@ -6,6 +7,7 @@ #include #include #include +#include #include "SceTypes.hpp" namespace { @@ -114,6 +116,44 @@ int Base64Encode(const std::uint8_t* src, char* dst, std::size_t length, const c return static_cast(out - dst); } +bool IsAsciiAlnum(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); +} + +bool IsJidUserByte(char c) { + return IsAsciiAlnum(c) || c == '+' || c == '-' || c == '.' || c == '_'; +} + +bool IsJidDomainByte(char c) { + return IsAsciiAlnum(c) || c == '-' || c == '.' || c == '_'; +} + +struct JidParts { + std::string_view user; + std::string_view domain; + std::string_view resource; +}; + +JidParts SplitJid(std::string_view text) { + JidParts parts; + std::size_t user = 0; + while (user < text.size() && IsJidUserByte(text[user])) ++user; + std::size_t position = 0; + if (user != 0 && user < text.size() && text[user] == '@') { + parts.user = text.substr(0, user); + position = user; + } + if (position < text.size() && text[position] == '@') ++position; + std::size_t domain = 0; + while (position + domain < text.size() && IsJidDomainByte(text[position + domain])) ++domain; + while (domain != 0 && text[position + domain - 1] == '.') --domain; + parts.domain = text.substr(position, domain); + position += domain; + if (position < text.size()) ++position; + parts.resource = text.substr(position); + return parts; +} + int HexValue(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; @@ -190,14 +230,17 @@ int ParseInteger(const char* str, std::size_t* parsedLength, Parse parse, bool* 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'; + std::memcpy(dst, src, length); + std::memset(dst + length, 0, size - length); 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 int NP_UTIL_ERROR_INVALID_JID = static_cast(0x80550604); +constexpr std::size_t NP_ONLINE_ID_MAX_LENGTH = 16; +constexpr std::size_t NP_ID_RESOURCE_MAX_LENGTH = 4; 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; @@ -296,6 +339,27 @@ bool IsAllowedTextByte(unsigned char c) { return (c >= 0x20 && c != 0x7F) || c == '\t' || c == '\n' || c == '\r'; } +struct XmlEntity { + char character; + const char* text; +}; + +constexpr XmlEntity XML_ENTITIES[] = {{'"', """}, {'&', "&"}, {'\'', "'"}, {'<', "<"}, {'>', ">"}}; + +const char* XmlEntityOf(char c) { + for (const XmlEntity& entity : XML_ENTITIES) { + if (entity.character == c) return entity.text; + } + return nullptr; +} + +const XmlEntity* XmlEntityAt(const char* text) { + for (const XmlEntity& entity : XML_ENTITIES) { + if (std::strncmp(text, entity.text, std::strlen(entity.text)) == 0) return &entity; + } + return nullptr; +} + bool IsUrlSafe(unsigned char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '*'; @@ -585,6 +649,66 @@ int APS5_VABI sceNpUtilJsonGetOneChar(char* dst, std::size_t dstSize, const char return status; } +int APS5_VABI sceNpUtilXmlEscape(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, std::size_t* required) { + if (src == nullptr || srcLength == 0 || (dst == nullptr && required == nullptr)) return NP_UTIL_ERROR_INVALID_ARGUMENT; + const char* const end = src + srcLength; + std::size_t length = 0; + do { + const auto lead = static_cast(*src); + const char* piece = src; + std::size_t pieceLength = 1; + std::size_t consumed = 1; + if (lead < 0x80) { + if (!IsAllowedTextByte(lead)) return NP_UTIL_ERROR_INVALID_CHARACTER; + if (const char* entity = XmlEntityOf(*src)) { + piece = entity; + pieceLength = std::strlen(entity); + } + } else { + consumed = pieceLength = Utf8SequenceLength(lead); + if (pieceLength == 0 || !IsValidSequence(src, pieceLength)) return NP_UTIL_ERROR_INVALID_CHARACTER; + } + if (dst != nullptr) { + if (length + pieceLength > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + std::memcpy(dst + length, piece, pieceLength); + } + length += pieceLength; + src += consumed; + } while (src < end); + if (dst != nullptr) { + if (length + 1 > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + dst[length] = '\0'; + } + if (required != nullptr) *required = length + 1; + return 0; +} + +int APS5_VABI sceNpUtilXmlUnescape(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, std::size_t* required) { + if (src == nullptr || srcLength == 0 || (dst == nullptr && required == nullptr)) return NP_UTIL_ERROR_INVALID_ARGUMENT; + const char* const end = src + srcLength; + std::size_t length = 0; + do { + if (dst != nullptr && length + 1 > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + char character = *src; + std::size_t consumed = 1; + if (character == '&') { + if (const XmlEntity* entity = XmlEntityAt(src)) { + character = entity->character; + consumed = std::strlen(entity->text); + } + } + if (dst != nullptr) dst[length] = character; + ++length; + src += consumed; + } while (src < end); + if (dst != nullptr) { + if (length + 1 > dstSize) return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + dst[length] = '\0'; + } + if (required != nullptr) *required = length + 1; + return 0; +} + 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; @@ -603,6 +727,72 @@ int APS5_VABI sceNpUtilGetNthChar(std::size_t index, const char* text, const cha return 0; } +int APS5_VABI sceNpUtilParseJid(NpJid* jid, const char* text, char* buffer, std::size_t bufferSize, std::size_t* required) { + if (text == nullptr) return NP_UTIL_ERROR_INVALID_ARGUMENT; + const bool fill = jid != nullptr && buffer != nullptr; + if (fill) { + *jid = {}; + } else if (required == nullptr) { + return NP_UTIL_ERROR_INVALID_ARGUMENT; + } + const std::string_view whole(text); + const std::size_t start = whole.find_first_not_of(' '); + if (start == std::string_view::npos) return NP_UTIL_ERROR_INVALID_JID; + const JidParts parts = SplitJid(whole.substr(start)); + const std::string_view pieces[] = {parts.user, parts.domain, parts.resource}; + const char* copies[3] = {}; + std::size_t used = 0; + for (std::size_t i = 0; i < 3; ++i) { + const std::size_t size = pieces[i].size() + 1; + if (fill) { + if (bufferSize - used < size) { + *jid = {}; + return NP_UTIL_ERROR_BUFFER_TOO_SMALL; + } + std::copy(pieces[i].begin(), pieces[i].end(), buffer + used); + buffer[used + pieces[i].size()] = '\0'; + copies[i] = buffer + used; + } + used += size; + } + if (fill) { + jid->user = copies[0]; + jid->domain = copies[1]; + jid->resource = copies[2]; + } + if (required != nullptr) *required = used; + return 0; +} + +int APS5_VABI sceNpUtilJidToNpId(NpIdFull* npId, const NpJid* jid) { + if (npId == nullptr || jid == nullptr) return NP_UTIL_ERROR_INVALID_ARGUMENT; + if (jid->user == nullptr || jid->domain == nullptr) return NP_UTIL_ERROR_INVALID_JID; + const std::size_t userLength = std::strlen(jid->user); + if (userLength == 0 || userLength > NP_ONLINE_ID_MAX_LENGTH) return NP_UTIL_ERROR_INVALID_JID; + const char* domain = jid->domain; + if (!IsAsciiAlnum(domain[0]) || !IsAsciiAlnum(domain[1]) || domain[2] != '.' || !IsAsciiAlnum(domain[3]) || !IsAsciiAlnum(domain[4]) + || domain[5] != '.') { + return NP_UTIL_ERROR_INVALID_JID; + } + const char* label = domain + 6; + while (*label != '.') { + if (*label == '\0') return NP_UTIL_ERROR_INVALID_JID; + ++label; + } + if (std::strcmp(label + 1, "playstation.net") != 0) return NP_UTIL_ERROR_INVALID_JID; + const std::size_t resourceLength = jid->resource != nullptr ? std::strlen(jid->resource) : 0; + if (resourceLength > NP_ID_RESOURCE_MAX_LENGTH) return NP_UTIL_ERROR_INVALID_JID; + *npId = {}; + std::memcpy(npId->handle.data, jid->user, userLength); + npId->opt[0] = static_cast(domain[0]); + npId->opt[1] = static_cast(domain[1]); + npId->opt[2] = static_cast(domain[3]); + npId->opt[3] = static_cast(domain[4]); + if (resourceLength != 0) std::memcpy(npId->opt + 4, jid->resource, resourceLength); + npId->reserved[0] = 1; + 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; diff --git a/core/libs/prx/libSceNpCommon/include/Api.hpp b/core/libs/prx/libSceNpCommon/include/Api.hpp index b47cd199b7..243af24f3c 100644 --- a/core/libs/prx/libSceNpCommon/include/Api.hpp +++ b/core/libs/prx/libSceNpCommon/include/Api.hpp @@ -47,6 +47,13 @@ struct NpIdFull { std::uint8_t reserved[8]; }; +struct NpJid { + const char* user; + const char* domain; + const char* resource; + std::uint64_t reserved; +}; + struct NpJsonRule { std::uint64_t kind; const char* key; @@ -134,6 +141,11 @@ struct NpTitleIdObject { NpTitleId titleId; }; +struct NpTitleSecretObject { + const void* const* vtable; + NpTitleSecret secret; +}; + extern "C" { int APS5_VABI sceNpGetSandboxMode(); @@ -196,7 +208,7 @@ 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 sceNpUtilJidToNpId(NpIdFull* npId, const NpJid* jid); 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); @@ -242,6 +254,12 @@ void APS5_VABI sceNpGlobalHeapGetAllocator(NpAllocator* allocator); void APS5_VABI sceNpGlobalHeapGetAllocatorEx(NpAllocatorEx* allocator); NpAllocator* APS5_VABI sceNpGlobalHeapGetAllocatorPtr(void); NpAllocatorEx* APS5_VABI sceNpGlobalHeapGetAllocatorExPtr(void); +NpAllocator* APS5_VABI _sceNpAllocatorExConvertAllocator(NpAllocatorEx* allocator); +void* APS5_VABI _sceNpAllocatorExMalloc(NpAllocatorEx* allocator, std::size_t size); +void* APS5_VABI _sceNpAllocatorExRealloc(NpAllocatorEx* allocator, void* pointer, std::size_t size); +void APS5_VABI _sceNpAllocatorExFree(NpAllocatorEx* allocator, void* pointer); +char* APS5_VABI _sceNpAllocatorExStrdup(NpAllocatorEx* allocator, const char* text); +char* APS5_VABI _sceNpAllocatorExStrndup(NpAllocatorEx* allocator, const char* text, std::size_t maxLength); void* APS5_VABI _ZN3sce2np6ObjectnwEmR14SceNpAllocator(std::size_t size, NpAllocator* allocator); void* APS5_VABI _ZN3sce2np6ObjectnaEmR14SceNpAllocator(std::size_t size, NpAllocator* allocator); void APS5_VABI _ZN3sce2np6ObjectdlEPvR14SceNpAllocator(void* object, NpAllocator* allocator); @@ -339,9 +357,8 @@ int APS5_VABI sceNpUtilGetNthChar(std::size_t index, const char* text, const cha int APS5_VABI sceNpUtilHttpUrlEncode(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, std::size_t* dstLength); int APS5_VABI _ZN3sce2np10CancelableC2Ev(void); -int APS5_VABI sceNpUtilXmlEscape(void); +int APS5_VABI sceNpUtilXmlEscape(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, std::size_t* required); int APS5_VABI _ZN3sce2np4CondD0Ev(void); -int APS5_VABI _sceNpAllocatorExRealloc(void); int APS5_VABI _ZN3sce2np8WorkItem6CancelEi(void); int APS5_VABI _ZN3sce2np9WorkQueue11GetItemByIdEi(void); int APS5_VABI _ZN3sce2np14JsonDocBuilderC2ERKNS0_9JsonValueE(void); @@ -353,7 +370,6 @@ int APS5_VABI _ZN3sce2np4Cond4ctorEv(void); int APS5_VABI _ZN3sce2np16StreamReadBufferD1Ev(void); int APS5_VABI _ZNK3sce2np9EventFlag6IsInitEv(void); int APS5_VABI _ZN3sce2np10CancelLockC2Ev(void); -int APS5_VABI _ZN3sce2np13NpTitleSecretC2ERK16SceNpTitleSecret(void); int APS5_VABI _ZN3sce2np8WorkItem10SetRunningEv(void); int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient21CreateRequestForAsyncEPiiPKvm(void); int APS5_VABI _ZN3sce2np18HttpConnectionPool13InvalidateAllEv(void); @@ -379,9 +395,9 @@ int APS5_VABI _ZN3sce2np4Cond4InitEPKcPNS0_5MutexE(void); int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient10DisconnectEv(void); int APS5_VABI _ZN3sce2np3ipc10IpmiClient11IsConnectedEv(void); int APS5_VABI _ZN3sce2np12HttpTemplate19SetAuthInfoCallbackEPFii15SceHttpAuthTypePKcPcS5_iPPhPmPiPvESA_(void); -int APS5_VABI sceNpUtilParseJid(void); +int APS5_VABI sceNpUtilParseJid(NpJid* jid, const char* text, char* buffer, std::size_t bufferSize, std::size_t* required); int APS5_VABI _ZN3sce2np9Semaphore6CreateEiiPKc(void); -int APS5_VABI sceNpUtilXmlUnescape(void); +int APS5_VABI sceNpUtilXmlUnescape(char* dst, std::size_t dstSize, const char* src, std::size_t srcLength, std::size_t* required); int APS5_VABI sceNpXmlParse(void); int APS5_VABI _ZN3sce2np9WorkQueue15GetFinishedItemENS0_14WorkItemStatusE(void); int APS5_VABI _ZN3sce2np9EventFlag4PollEmjPm(void); @@ -419,12 +435,10 @@ int APS5_VABI _ZN3sce2np7Callout11CalloutFuncEPv(void); int APS5_VABI _ZThn8_N3sce2np6HandleD1Ev(void); int APS5_VABI _ZN3sce2np6Handle4InitEv(void); int APS5_VABI _ZThn16_N3sce2np10MemoryFileD1Ev(void); -int APS5_VABI _sceNpAllocatorExFree(void); int APS5_VABI _ZN3sce2np3ipc17ServiceIpmiClient7ConnectEPKvm(void); int APS5_VABI _ZN3sce2np7RingBufC1Ev(void); int APS5_VABI _ZN3sce2np10MemoryFileD1Ev(void); int APS5_VABI _ZN3sce2np9HttpTrans21GetResponseStatusCodeEPNS0_6HandleEPi(void); -int APS5_VABI _ZN3sce2np13NpTitleSecretC2ERKS1_(void); int APS5_VABI _ZN3sce2np14CalloutContextD1Ev(void); int APS5_VABI _ZN3sce2np5Mutex7DestroyEv(void); int APS5_VABI _ZN3sce2np14JsonDocBuilderC1ERKNS0_9JsonValueE(void); @@ -500,6 +514,30 @@ bool APS5_VABI _ZN3sce2npneERKNS0_9NpTitleIdES3_(const NpTitleIdObject* lhs, con bool APS5_VABI _ZN3sce2npneERK12SceNpTitleIdRKNS0_9NpTitleIdE(const NpTitleId* lhs, const NpTitleIdObject* rhs); bool APS5_VABI _ZN3sce2npneERKNS0_9NpTitleIdERK12SceNpTitleId(const NpTitleIdObject* lhs, const NpTitleId* rhs); +void APS5_VABI _ZN3sce2np13NpTitleSecretC1Ev(NpTitleSecretObject* self); +void APS5_VABI _ZN3sce2np13NpTitleSecretC2Ev(NpTitleSecretObject* self); +void APS5_VABI _ZN3sce2np13NpTitleSecretC1ERKS1_(NpTitleSecretObject* self, const NpTitleSecretObject* other); +void APS5_VABI _ZN3sce2np13NpTitleSecretC2ERKS1_(NpTitleSecretObject* self, const NpTitleSecretObject* other); +void APS5_VABI _ZN3sce2np13NpTitleSecretC1ERK16SceNpTitleSecret(NpTitleSecretObject* self, const NpTitleSecret* secret); +void APS5_VABI _ZN3sce2np13NpTitleSecretC2ERK16SceNpTitleSecret(NpTitleSecretObject* self, const NpTitleSecret* secret); +void APS5_VABI _ZN3sce2np13NpTitleSecretC1EPKvm(NpTitleSecretObject* self, const void* data, std::size_t size); +void APS5_VABI _ZN3sce2np13NpTitleSecretC2EPKvm(NpTitleSecretObject* self, const void* data, std::size_t size); +void APS5_VABI _ZN3sce2np13NpTitleSecretD1Ev(NpTitleSecretObject* self); +void APS5_VABI _ZN3sce2np13NpTitleSecretD2Ev(NpTitleSecretObject* self); +void APS5_VABI _ZN3sce2np13NpTitleSecretD0Ev(NpTitleSecretObject* self); +NpTitleSecretObject* APS5_VABI _ZN3sce2np13NpTitleSecretaSERKS1_(NpTitleSecretObject* self, const NpTitleSecretObject* other); +int APS5_VABI _ZN3sce2np13NpTitleSecret13SetSecretDataEPvm(NpTitleSecretObject* self, const void* data, std::size_t size); +bool APS5_VABI _ZNK3sce2np13NpTitleSecret7IsEmptyEv(const NpTitleSecretObject* self); +bool APS5_VABI _ZNK3sce2np13NpTitleSecret6EqualsEPvm(const NpTitleSecretObject* self, const void* data, std::size_t size); +bool APS5_VABI _ZNK3sce2np13NpTitleSecret6EqualsERKS1_(const NpTitleSecretObject* self, const NpTitleSecretObject* other); +bool APS5_VABI _ZNK3sce2np13NpTitleSecret6EqualsERK16SceNpTitleSecret(const NpTitleSecretObject* self, const NpTitleSecret* secret); +int APS5_VABI _ZN3sce2np13NpTitleSecret8ParseHexEPS1_PKcm(NpTitleSecretObject* out, const char* hex, std::size_t length); +int APS5_VABI _ZN3sce2np13NpTitleSecret8BuildHexERKS1_Pcmb(const NpTitleSecretObject* secret, char* hex, std::size_t size, bool uppercase); +bool APS5_VABI _ZN3sce2npeqERKNS0_13NpTitleSecretES3_(const NpTitleSecretObject* lhs, const NpTitleSecretObject* rhs); +bool APS5_VABI _ZN3sce2npeqERK16SceNpTitleSecretRKNS0_13NpTitleSecretE(const NpTitleSecret* lhs, const NpTitleSecretObject* rhs); +bool APS5_VABI _ZN3sce2npeqERKNS0_13NpTitleSecretERK16SceNpTitleSecret(const NpTitleSecretObject* lhs, const NpTitleSecret* rhs); +bool APS5_VABI _ZN3sce2npneERKNS0_13NpTitleSecretES3_(const NpTitleSecretObject* lhs, const NpTitleSecretObject* rhs); +bool APS5_VABI _ZN3sce2npneERK16SceNpTitleSecretRKNS0_13NpTitleSecretE(const NpTitleSecret* lhs, const NpTitleSecretObject* rhs); int APS5_VABI _ZN3sce2np12StreamReader4ReadEPNS0_6HandleEPNS0_9StreamCtxEPvmPm(NpStreamReader* self, void* handle, NpStreamCtx* ctx, void* out, std::size_t size, std::size_t* read); int APS5_VABI _ZN3sce2np12StreamReader7ReadAllEPNS0_6HandleEPvmPm(NpStreamReader* self, void* handle, void* out, std::size_t size, std::size_t* read); int APS5_VABI _ZN3sce2np12StreamReader7ReadAllEPNS0_6HandleEPNS0_9StreamCtxEPvmPm(NpStreamReader* self, void* handle, NpStreamCtx* ctx, void* out, std::size_t size, std::size_t* read); diff --git a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt index c6c7e4e41d..7079b95d84 100644 --- a/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt +++ b/core/libs/prx/libSceNpCppWebApi/CMakeLists.txt @@ -46,3 +46,15 @@ if(BUILD_TESTING) PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_cpp_web_api_activities_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpCppWebApiActivities.cpp) + target_include_directories(guest_np_cpp_web_api_activities_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_cpp_web_api_activities_tests PRIVATE libSceNpCppWebApi libc) + configure_windows_unwind(guest_np_cpp_web_api_activities_tests) + add_test(NAME guest_np_cpp_web_api_activities COMMAND guest_np_cpp_web_api_activities_tests) + if(WIN32) + set_tests_properties(guest_np_cpp_web_api_activities + PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceNpCppWebApi/Export.cpp b/core/libs/prx/libSceNpCppWebApi/Export.cpp index 33b677ea4b..16b152f9b1 100644 --- a/core/libs/prx/libSceNpCppWebApi/Export.cpp +++ b/core/libs/prx/libSceNpCppWebApi/Export.cpp @@ -21,6 +21,137 @@ static int InitializeParameter(void* self, const std::uint64_t* context, bool ar static int InitializeParameterWithBody(void* self, const std::uint64_t* context, bool textsPresent, const IntrusivePtr* body); static int TerminateParameter(void* self); +static constexpr int ActivityInvalidArgument = static_cast(0x80553503); +static constexpr int ActivityOutOfMemory = static_cast(0x80553504); +static constexpr int ActivityAlreadyInitialized = static_cast(0x8055350f); +static constexpr int ActivityNotInitialized = static_cast(0x80553510); + +struct ActivityParameter { + void* base; + void* state; +}; + +struct ActivityParameterState { + int limit; + int npServiceLabel; +}; + +static_assert(sizeof(ActivityParameter) == 0x10 && offsetof(ActivityParameter, state) == 0x08); + +static void DestroyActivityParameter(ActivityParameter* self) { + std::free(self->state); + self->state = nullptr; + self->base = nullptr; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesC1Ev(void* rawSelf) { + auto* self = static_cast(rawSelf); + self->base = nullptr; + self->state = nullptr; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesC2Ev(void* rawSelf) { + return _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesC1Ev(rawSelf); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesD1Ev(void* rawSelf) { + DestroyActivityParameter(static_cast(rawSelf)); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesD2Ev(void* rawSelf) { + return _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesD1Ev(rawSelf); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities10initializeEPNS1_6Common10LibContextENS7_12IntrusivePtrINS7_6VectorINS7_6StringEEEEE(void* rawSelf, void* libContext, const IntrusivePtr* accountIds) { + (void)accountIds; + auto* self = static_cast(rawSelf); + if (libContext == nullptr) return ActivityInvalidArgument; + if (self->base != nullptr || self->state != nullptr) return ActivityAlreadyInitialized; + auto* state = static_cast(std::malloc(sizeof(ActivityParameterState))); + if (state == nullptr) return ActivityOutOfMemory; + state->limit = 0; + state->npServiceLabel = 0; + self->base = libContext; + self->state = state; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities9terminateEv(void* rawSelf) { + auto* self = static_cast(rawSelf); + if (self->base == nullptr) return ActivityNotInitialized; + DestroyActivityParameter(self); + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities8setlimitEi(void* rawSelf, int limit) { + auto* self = static_cast(rawSelf); + auto* state = static_cast(self->state); + if (state != nullptr) state->limit = limit; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities17setnpServiceLabelEi(void* rawSelf, int label) { + auto* self = static_cast(rawSelf); + auto* state = static_cast(self->state); + if (state != nullptr) state->npServiceLabel = label; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi17getUserActivitiesEiRKNS5_28ParameterToGetUserActivitiesERNS1_6Common11TransactionINS9_12IntrusivePtrINS4_26GetUsersActivitiesResponseEEENSB_INS9_18ResponseHeaderBaseEEEEE(int user, const void* parameter, void* transaction) { + (void)user; + (void)parameter; + (void)transaction; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +const char* APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity13getActivityIdEv(const void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return nullptr; +} + +const char* APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity7getNameEv(const void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return nullptr; +} + +bool APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity9getHiddenEv(const void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return false; +} + +bool APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity12matchIdIsSetEv(const void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return false; +} + +const char* APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity10getMatchIdEv(const void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return nullptr; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities26GetUsersActivitiesResponse8getUsersEv(const void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +const char* APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities14UserActivities12getAccountIdEv(const void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return nullptr; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities14UserActivities13getActivitiesEv(const void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V111UserFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_4UserEEE() { return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } @@ -1016,6 +1147,8 @@ static_assert(sizeof(IntrusivePtr) == 0x18); static constexpr std::size_t ReferenceCountOffset = 0; static constexpr std::size_t VectorReferenceCountOffset = 0x30; +static constexpr std::size_t StringVectorReferenceCountOffset = 0x38; +static constexpr std::size_t ScalarVectorReferenceCountOffset = 0x20; static std::atomic_ref ReferenceCount(void* object, const std::size_t offset) { return std::atomic_ref(*reinterpret_cast(static_cast(object) + offset)); @@ -1629,6 +1762,16 @@ static int VectorPushBackString(IntrusivePtr* self, const CommonString* element) body->size += 1; return 0; } + +static_assert(sizeof(CommonIterator) == 0x10 && offsetof(CommonIterator, element) == 0x08); + +static CommonString* StringVectorElements(const IntrusivePtr* self, const bool end, const char* function) { + const auto* body = static_cast(self->object); + if (body == nullptr) return nullptr; + if (body->magic != VectorBodyMagic || body->elementKind != 2) NotImplemented_nid_no_patch(function); + auto* elements = static_cast(body->elements); + return end && body->size != 0 ? elements + body->size : elements; +} // -------------------------------------------------------------------------- // SessionManager2V1 and extra Common symbols, from a title dump whose modules lack // libSceNpCppWebApi.prx. @@ -1731,6 +1874,7 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibCont self->object = nullptr; self->deleter = nullptr; self->libContext = libContext; + ReferenceCount(self, StringVectorReferenceCountOffset).store(0); } int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEE8pushBackERKS8_(IntrusivePtr* self, const void* element) { @@ -1990,7 +2134,6 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 ReleaseIntrusivePtr(self, 0x00, __func__); } - void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_14SessionManager2V136UsersPlayerSessionsInvitationForReadEEEEdeEv(const void* self) { (void)self; NotImplemented_nid_no_patch(__func__); @@ -2006,7 +2149,6 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common15DefaultResponseD1Ev(void* self) { return 0; } - void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V120RequestPlayerSessionEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { self->object = nullptr; self->deleter = nullptr; @@ -2084,7 +2226,6 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V1 ReleaseIntrusivePtr(self, 0x00, __func__); } - void* APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V154GetUsersAccountIdPlayerSessionsInvitationsResponseBody14getInvitationsEv(void* self) { (void)self; NotImplemented_nid_no_patch(__func__); @@ -2622,24 +2763,25 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession14set return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body) { + return InitializeParameterWithBody(self, context, sessionId != nullptr, body); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_39PatchPlayerSessionsSessionIdRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body) { + return InitializeParameterWithBody(self, context, sessionId != nullptr, body); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader9terminateEv(void* self) { + return TerminateParameter(self); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesC1Ev(void* self) { + ConstructParameter(self); 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 _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers9terminateEv(void* self) { + return TerminateParameter(self); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V120RequestPlayerSession14setCustomData1EPKvm() { @@ -2656,12 +2798,13 @@ int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V124GetBlockingUsersResponse9get return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesD1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesD1Ev(void* self) { + (void)self; return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body) { + return InitializeParameterWithBody(self, context, sessionId != nullptr, body); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V146PatchPlayerSessionsSessionIdRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_39PatchPlayerSessionsSessionIdRequestBodyEEE() { @@ -2690,15 +2833,16 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28Parame return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9terminateEv() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9terminateEv(void* self) { + return TerminateParameter(self); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv(void* self) { return TerminateParameter(self); } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsD1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsD1Ev(void* self) { + (void)self; return 0; } @@ -2742,11 +2886,13 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead9get return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsC1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsC1Ev(void* self) { + ConstructParameter(self); return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsD1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsD1Ev(void* self) { + (void)self; return 0; } @@ -2771,7 +2917,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V119JoinableUserFactory6creat return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderD1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderD1Ev(void* self) { + (void)self; return 0; } @@ -2779,15 +2926,16 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V156PostPlayerSessionsSession return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations9terminateEv() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations9terminateEv(void* self) { + return TerminateParameter(self); } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions10initializeEPNS1_6Common10LibContextEPKc() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* sessionIds) { + return InitializeParameter(self, context, sessionIds != nullptr); } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsC1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsC1Ev(void* self) { + ConstructParameter(self); return 0; } @@ -2809,24 +2957,26 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V19ToFactory6createEPNS1_6Com return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersD1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersD1Ev(void* self) { + (void)self; return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body) { + return InitializeParameterWithBody(self, context, sessionId != nullptr, body); } int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V167PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_12JoinableUserEEEEEPNS9_INS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE() { return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderC1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderC1Ev(void* self) { + ConstructParameter(self); return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties9terminateEv() { - return 0; +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties9terminateEv(void* self) { + return TerminateParameter(self); } int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V118LeaderWithOnlineId12getAccountIdEv() { @@ -2843,7 +2993,8 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V129GetPlayerSessionsResponse return 0; } -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersC1Ev() { +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersC1Ev(void* self) { + ConstructParameter(self); return 0; } @@ -3627,6 +3778,592 @@ int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10labelIsSetEv() { return 0; } +struct MatchesParameter { + void* base; + void* implementation; +}; + +static constexpr int MatchesOutOfMemory = static_cast(0x80553504); + +static CommonString* MatchesStringAt(void* implementation, const std::size_t offset) { + return reinterpret_cast(static_cast(implementation) + offset); +} + +static void ConstructMatchesString(CommonString* self, void* libContext) { + self->referenceCount = 0; + self->buffer = nullptr; + self->bufferSize = 0; + self->libContext = libContext; +} + +static void DestroyMatchesString(CommonString* self) { + std::free(self->buffer); + self->buffer = nullptr; + self->bufferSize = 0; +} + +static int AssignMatchesString(CommonString* self, const char* text) { + if (text == nullptr) return ParameterInvalidArgument; + const std::size_t length = std::strlen(text); + char* const oldBuffer = self->buffer; + char* const buffer = static_cast(std::malloc(length + 1)); + if (buffer == nullptr) { + self->buffer = oldBuffer; + return MatchesOutOfMemory; + } + std::memcpy(buffer, text, length + 1); + self->buffer = buffer; + self->bufferSize = length + 1; + std::free(oldBuffer); + return 0; +} + +static int InitializeMatchesParameter(void* self, const std::uint64_t* context, const bool hasText, const char* text, const std::size_t implementationSize, const std::size_t bodyOffset, const std::size_t contextOffset, const IntrusivePtr* body) { + if (context == nullptr) return ParameterInvalidArgument; + if (*context == 0) return ParameterNotInitialized; + auto* parameter = static_cast(self); + if (parameter->implementation != nullptr) return ParameterAlreadyInitialized; + void* implementation = std::calloc(1, implementationSize); + if (implementation == nullptr) return MatchesOutOfMemory; + void* libContext = const_cast(context); + if (hasText) ConstructMatchesString(MatchesStringAt(implementation, 0), libContext); + *reinterpret_cast(static_cast(implementation) + contextOffset) = libContext; + if (hasText) { + const int result = AssignMatchesString(MatchesStringAt(implementation, 0), text); + if (result < 0) { + DestroyMatchesString(MatchesStringAt(implementation, 0)); + std::free(implementation); + return result; + } + } + if (body == nullptr || body->object == nullptr) { + if (hasText) DestroyMatchesString(MatchesStringAt(implementation, 0)); + std::free(implementation); + return ParameterBodyMissing; + } + auto* destination = reinterpret_cast(static_cast(implementation) + bodyOffset); + *destination = *body; + ReferenceCount(destination->object, ReferenceCountOffset).fetch_add(1); + parameter->implementation = implementation; + return 0; +} + +static int TerminateMatchesParameter(void* self, const bool hasText, const std::size_t bodyOffset) { + auto* parameter = static_cast(self); + if (parameter->implementation == nullptr) return ParameterTerminatedUninitialized; + void* implementation = parameter->implementation; + if (hasText) DestroyMatchesString(MatchesStringAt(implementation, 0)); + ReleaseIntrusivePtr(reinterpret_cast(static_cast(implementation) + bodyOffset), ReferenceCountOffset, __func__); + std::free(implementation); + parameter->implementation = nullptr; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer12setAccountIdEPKc(void* self, const char* value) { + const int result = AssignMatchesString(MatchesStringAt(self, 96), value); + if (result < 0) return result; + *reinterpret_cast(static_cast(self) + 128) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer13setPlayerNameEPKc(void* self, const char* value) { + const int result = AssignMatchesString(MatchesStringAt(self, 56), value); + if (result < 0) return result; + *reinterpret_cast(static_cast(self) + 88) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer9setTeamIdEPKc(void* self, const char* value) { + const int result = AssignMatchesString(MatchesStringAt(self, 136), value); + if (result < 0) return result; + *reinterpret_cast(static_cast(self) + 168) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V113RemovedPlayer9setReasonERKNS3_11LeaveReasonE(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 48) = *value; + *reinterpret_cast(static_cast(self) + 52) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116JoinMatchRequest17setNpServiceLabelERKi(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 16) = *value; + *reinterpret_cast(static_cast(self) + 20) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam10setMembersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_13RequestMemberEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 88), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 112) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam11setTeamNameEPKc(void* self, const char* value) { + const int result = AssignMatchesString(MatchesStringAt(self, 48), value); + if (result < 0) return result; + *reinterpret_cast(static_cast(self) + 80) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V117LeaveMatchRequest17setNpServiceLabelERKi(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 16) = *value; + *reinterpret_cast(static_cast(self) + 20) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr || value->object == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 152), value, ReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 176) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setExpirationTimeERKi(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 92) = *value; + *reinterpret_cast(static_cast(self) + 96) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setNpServiceLabelERKi(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 16) = *value; + *reinterpret_cast(static_cast(self) + 20) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest9setZoneIdEPKc(void* self, const char* value) { + const int result = AssignMatchesString(MatchesStringAt(self, 56), value); + if (result < 0) return result; + *reinterpret_cast(static_cast(self) + 88) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118RequestMatchPlayer12setAccountIdEPKc(void* self, const char* value) { + const int result = AssignMatchesString(MatchesStringAt(self, 96), value); + if (result < 0) return result; + *reinterpret_cast(static_cast(self) + 128) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118RequestMatchPlayer13setPlayerNameEPKc(void* self, const char* value) { + const int result = AssignMatchesString(MatchesStringAt(self, 56), value); + if (result < 0) return result; + *reinterpret_cast(static_cast(self) + 88) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118RequestTeamResults20setTeamMemberResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_23RequestTeamMemberResultEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 72), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 96) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118RequestTeamResults8setScoreERKd(void* self, const double* value) { + *reinterpret_cast(static_cast(self) + 56) = *value; + *reinterpret_cast(static_cast(self) + 64) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10setPlayersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestMatchPlayerEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 16), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 40) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster8setTeamsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_16RequestMatchTeamEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 48), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 72) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCompetitiveResultERKNS1_6Common12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr || value->object == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 32), value, ReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 56) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCooperativeResultERKNS3_24RequestCooperativeResultE(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 20) = *value; + *reinterpret_cast(static_cast(self) + 24) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest17setNpServiceLabelERKi(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 16) = *value; + *reinterpret_cast(static_cast(self) + 20) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest18setMatchStatisticsERKNS1_6Common12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr || value->object == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 48), value, ReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 72) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestPlayerResults8setScoreERKd(void* self, const double* value) { + *reinterpret_cast(static_cast(self) + 56) = *value; + *reinterpret_cast(static_cast(self) + 64) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic23setTeamMemberStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_26RequestTeamMemberStatisticEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 80), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 104) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic8setStatsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 48), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 72) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics17setTeamStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestTeamStatisticEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 48), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 72) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics19setPlayerStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_22RequestPlayerStatisticEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 16), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 40) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123JoinMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_11AddedPlayerEEEEEPNS9_INS3_16JoinMatchRequestEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124LeaveMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_13RemovedPlayerEEEEEPNS9_INS3_17LeaveMatchRequestEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult14setTeamResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestTeamResultsEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 48), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 72) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult16setPlayerResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestPlayerResultsEEEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 16), value, VectorReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 40) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, const IntrusivePtr* value) { + if (value == nullptr || value->object == nullptr) return ParameterBodyMissing; + AssignIntrusivePtr(reinterpret_cast(static_cast(self) + 112), value, ReferenceCountOffset, __func__); + *reinterpret_cast(static_cast(self) + 136) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setExpirationTimeERKi(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 60) = *value; + *reinterpret_cast(static_cast(self) + 64) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setNpServiceLabelERKi(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 16) = *value; + *reinterpret_cast(static_cast(self) + 20) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest9setZoneIdEPKc(void* self, const char* value) { + const int result = AssignMatchesString(MatchesStringAt(self, 24), value); + if (result < 0) return result; + *reinterpret_cast(static_cast(self) + 56) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchStatusRequest17setNpServiceLabelERKi(void* self, const int* value) { + *reinterpret_cast(static_cast(self) + 16) = *value; + *reinterpret_cast(static_cast(self) + 20) = 1; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestMatchPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_18RequestMatchPlayerEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestTeamResultsFactory6createEPNS1_6Common10LibContextEPKciPNS5_12IntrusivePtrINS3_18RequestTeamResultsEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126AdditionalStatisticFactory6createEPNS1_6Common10LibContextEPKcS9_PNS5_12IntrusivePtrINS3_19AdditionalStatisticEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127ReportResultsRequestFactory6createEPNS1_6Common10LibContextENS5_12IntrusivePtrINS3_19RequestMatchResultsEEEPNS8_INS3_20ReportResultsRequestEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestPlayerResultsFactory6createEPNS1_6Common10LibContextEPKciPNS5_12IntrusivePtrINS3_20RequestPlayerResultsEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestPlayerStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_22RequestPlayerStatisticEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V130RequestTeamMemberResultFactory6createEPNS1_6Common10LibContextEPKcdPNS5_12IntrusivePtrINS3_23RequestTeamMemberResultEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V133RequestTeamMemberStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_26RequestTeamMemberStatisticEEE(void* self) { + (void)self; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi10leaveMatchEiRKNS4_21ParameterToLeaveMatchERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi11createMatchEiRKNS4_22ParameterToCreateMatchERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_19CreateMatchResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi13reportResultsEiRKNS4_24ParameterToReportResultsERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi17updateMatchDetailEiRKNS4_28ParameterToUpdateMatchDetailERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi17updateMatchStatusEiRKNS4_28ParameterToUpdateMatchStatusERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_16JoinMatchRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body) { + return InitializeMatchesParameter(self, context, true, text, 0x40, 0x20, 0x38, body); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch9terminateEv(void* self) { + return TerminateMatchesParameter(self, true, 0x20); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatchC1Ev(void* self) { + *static_cast(self) = {}; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatchD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi21ParameterToLeaveMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_17LeaveMatchRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body) { + return InitializeMatchesParameter(self, context, true, text, 0x40, 0x20, 0x38, body); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi21ParameterToLeaveMatch9terminateEv(void* self) { + return TerminateMatchesParameter(self, true, 0x20); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi21ParameterToLeaveMatchC1Ev(void* self) { + *static_cast(self) = {}; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi21ParameterToLeaveMatchD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self, const std::uint64_t* context, const IntrusivePtr* body) { + return InitializeMatchesParameter(self, context, false, nullptr, 0x20, 0x00, 0x18, body); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch9terminateEv(void* self) { + return TerminateMatchesParameter(self, false, 0x00); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatchC1Ev(void* self) { + *static_cast(self) = {}; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatchD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi24ParameterToReportResults10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_20ReportResultsRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body) { + return InitializeMatchesParameter(self, context, true, text, 0x40, 0x20, 0x38, body); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi24ParameterToReportResults9terminateEv(void* self) { + return TerminateMatchesParameter(self, true, 0x20); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi24ParameterToReportResultsC1Ev(void* self) { + *static_cast(self) = {}; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi24ParameterToReportResultsD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchDetail10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body) { + return InitializeMatchesParameter(self, context, true, text, 0x40, 0x20, 0x38, body); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchDetailC1Ev(void* self) { + *static_cast(self) = {}; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchDetailD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchStatus10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body) { + return InitializeMatchesParameter(self, context, true, text, 0x40, 0x20, 0x38, body); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchStatus9terminateEv(void* self) { + return TerminateMatchesParameter(self, true, 0x20); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchStatusC1Ev(void* self) { + *static_cast(self) = {}; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchStatusD1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi9joinMatchEiRKNS4_20ParameterToJoinMatchERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self) { + (void)self; + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +const void* APS5_VABI _ZNK3sce2Np9CppWebApi7Matches2V119CreateMatchResponse10getMatchIdEv(const void* self) { + return static_cast(self) + 0x30; +} + +bool APS5_VABI _ZNK3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10teamsIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x48) != 0; +} + +bool APS5_VABI _ZNK3sce2Np9CppWebApi7Matches2V119RequestInGameRoster12playersIsSetEv(const void* self) { + return *reinterpret_cast(static_cast(self) + 0x28) != 0; +} + + int APS5_VABI _ZNK3sce2Np9CppWebApi13InGameCatalog2V53Sku10priceIsSetEv() { NotImplemented_nid_no_patch(__func__); return 0; @@ -3827,16 +4564,15 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13 AssignIntrusivePtr(self, source, 0x30, __func__); } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEppEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEppEv(CommonIterator* self) { + self->element = static_cast(self->element) + 1; + return self; } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEC1Ev(IntrusivePtr* self) { *self = {}; } - int APS5_VABI _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_6StringEED2Ev(void* self) { (void)self; return 0; @@ -3851,9 +4587,8 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEE3getE return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEneERKS5_() { - NotImplemented_nid_no_patch(__func__); - return 0; +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEneERKS5_(const CommonIterator* self, const CommonIterator* other) { + return self->element != other->element; } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE11hasResponseEv() { @@ -3864,9 +4599,10 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6Stri return self->object; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE3endEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE3endEv(CommonIterator* iterator, const IntrusivePtr* self) { + iterator->referenceCount = 0; + iterator->element = StringVectorElements(self, true, __func__); + return iterator; } int APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringeqEPKc(const CommonString* self, const char* other) { @@ -3877,14 +4613,14 @@ std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_15Defau return 0; } - int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5abortEv() { return TransactionNotStarted; } -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE5beginEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE5beginEv(CommonIterator* iterator, const IntrusivePtr* self) { + iterator->referenceCount = 0; + iterator->element = StringVectorElements(self, false, __func__); + return iterator; } @@ -3892,9 +4628,8 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6Stri return self->object; } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEdeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +CommonString* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEdeEv(const CommonIterator* self) { + return static_cast(self->element); } // -------------------------------------------------------------------------- @@ -5883,6 +6618,27 @@ std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1 } +static constexpr int StringInvalidArgument = static_cast(0x80553503); +static constexpr int StringOutOfMemory = static_cast(0x80553504); + +static void ResetString(CommonString* self) { + std::free(self->buffer); + self->buffer = nullptr; + self->bufferSize = 0; +} + +static int AssignString(CommonString* self, const char* text) { + if (text == nullptr) return StringInvalidArgument; + const std::size_t length = std::strlen(text) + 1; + char* buffer = static_cast(std::malloc(length)); + if (buffer == nullptr) return StringOutOfMemory; + std::memcpy(buffer, text, length); + std::free(self->buffer); + self->buffer = buffer; + self->bufferSize = length; + return 0; +} + void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(CommonString* self, void* libContext) { self->referenceCount = 0; self->buffer = nullptr; @@ -5890,10 +6646,62 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(CommonStr self->libContext = libContext; } +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1Ev(CommonString* self) { + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(self, nullptr); +} + void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD1Ev(CommonString* self) { - std::free(self->buffer); - self->buffer = nullptr; - self->bufferSize = 0; + ResetString(self); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD2Ev(CommonString* self) { + ResetString(self); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String8copyFromEPKc(CommonString* self, const char* text) { + return AssignString(self, text); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String8copyFromERKS3_(CommonString* self, const CommonString* source) { + const bool hadContext = self->libContext != nullptr; + if (!hadContext) self->libContext = source->libContext; + if (source->bufferSize == 0) { + ResetString(self); + return 0; + } + const int result = AssignString(self, source->buffer != nullptr ? source->buffer : ""); + if (!hadContext && result != 0) self->libContext = nullptr; + return result; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6String5clearEv(CommonString* self) { + ResetString(self); +} + +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String4sizeEv(const CommonString* self) { + return self->buffer != nullptr ? static_cast(std::strlen(self->buffer)) : 0; +} + +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String6lengthEv(const CommonString* self) { + return self->buffer != nullptr ? static_cast(std::strlen(self->buffer)) : 0; +} + +bool APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringeqERKS3_(const CommonString* self, const CommonString* other) { + const char* left = self->buffer != nullptr ? self->buffer : ""; + const char* right = other->buffer != nullptr ? other->buffer : ""; + return std::strncmp(left, right, std::strlen(left) + 1) == 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6String10setContextEPNS2_10LibContextE(CommonString* self, void* libContext) { + self->libContext = libContext; +} + +std::int32_t APS5_VABI _ZN3sce2Np9CppWebApi6Common6String21intrusive_ptr_add_refEPS3_(CommonString* self) { + return std::atomic_ref(self->referenceCount).fetch_add(1) + 1; +} + +std::int32_t APS5_VABI _ZN3sce2Np9CppWebApi6Common6String21intrusive_ptr_sub_refEPS3_(CommonString* self) { + return std::atomic_ref(self->referenceCount).fetch_sub(1); } const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const CommonString* self) { @@ -5901,26 +6709,26 @@ const char* APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(const CommonSt } int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendEPKc(CommonString* self, const char* text) { - if (text == nullptr) return 0; + if (text == nullptr) return StringInvalidArgument; + if (*text == '\0') return 0; + const std::size_t oldLength = self->buffer != nullptr && self->bufferSize != 0 + ? static_cast(self->bufferSize - 1) : 0; const std::size_t textLength = std::strlen(text); - const std::size_t oldLength = self->buffer != nullptr ? std::strlen(self->buffer) : 0; - const std::size_t needed = oldLength + textLength + 1; - if (needed > self->bufferSize) { - std::size_t capacity = self->bufferSize != 0 ? static_cast(self->bufferSize) : 16; - while (capacity < needed) capacity *= 2; - char* buffer = static_cast(std::malloc(capacity)); - if (buffer == nullptr) return -1; - if (oldLength != 0) std::memcpy(buffer, self->buffer, oldLength); - std::memmove(buffer + oldLength, text, textLength + 1); - std::free(self->buffer); - self->buffer = buffer; - self->bufferSize = capacity; - return 0; - } - std::memmove(self->buffer + oldLength, text, textLength + 1); + const std::size_t newSize = oldLength + textLength + 1; + char* buffer = static_cast(std::malloc(newSize)); + if (buffer == nullptr) return StringOutOfMemory; + if (oldLength != 0) std::memcpy(buffer, self->buffer, oldLength); + std::memcpy(buffer + oldLength, text, textLength + 1); + std::free(self->buffer); + self->buffer = buffer; + self->bufferSize = newSize; return 0; } +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendERKS3_(CommonString* self, const CommonString* source) { + return _ZN3sce2Np9CppWebApi6Common6String6appendEPKc(self, source->buffer != nullptr ? source->buffer : ""); +} + void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(IntrusivePtr* self) { ReleaseIntrusivePtr(self, 0x30, __func__); } @@ -5930,13 +6738,14 @@ int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14Sessio } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1ERS7_(IntrusivePtr* self, const IntrusivePtr* source) { - CopyIntrusivePtr(self, source, 0x30); + CopyIntrusivePtr(self, source, StringVectorReferenceCountOffset); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(IntrusivePtr* self, void* body, void (APS5_VABI* deleter)(void*), void* libContext) { self->object = body; self->deleter = deleter; self->libContext = libContext; + if (body) ReferenceCount(body, StringVectorReferenceCountOffset).fetch_add(1); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext) { @@ -5946,24 +6755,108 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14Sessi } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(IntrusivePtr* self) { - ReleaseIntrusivePtr(self, 0x30, __func__); + ReleaseIntrusivePtr(self, StringVectorReferenceCountOffset, __func__); } -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE4sizeEv() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE4sizeEv(const void* self) { + return HostVectorSize(self, __func__); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEED1Ev(IntrusivePtr* self) { - ReleaseIntrusivePtr(self, 0x30, __func__); + ReleaseIntrusivePtr(self, ScalarVectorReferenceCountOffset, __func__); } void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEaSERS6_(IntrusivePtr* self, const IntrusivePtr* source) { - ReleaseIntrusivePtr(self, 0x30, __func__); - CopyIntrusivePtr(self, source, 0x30); + AssignIntrusivePtr(self, source, ScalarVectorReferenceCountOffset, __func__); } int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5emptyEv(const CommonString* self) { return self->buffer == nullptr || self->buffer[0] == '\0'; } + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi16getPublicProfileEiRKNS4_27ParameterToGetPublicProfileERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetPublicProfileResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfile10initializeEPNS1_6Common10LibContextEPKc() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfile9terminateEv() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfileC1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfileD1Ev() { + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self) { + (void)self; + return 0; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self) { + (void)self; + return 0; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V114PersonalDetailEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEC1Ev(IntrusivePtr* self) { + *self = {}; +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x00, __func__); +} + +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V16AvatarEEEEEED1Ev(IntrusivePtr* self) { + ReleaseIntrusivePtr(self, 0x30, __func__); +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() { + return 0; +} + +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() { + return SCE_NP_WEBAPI_ERROR_NOT_SIGNED_IN; +} + +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEcvbEv(const IntrusivePtr* self) { + return self->object != nullptr; +} + +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEcvbEv(const IntrusivePtr* self) { + return self->object != nullptr; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V16AvatarEEptEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V16AvatarEEEEEEdeEv(const IntrusivePtr* self) { + return self->object; +} + +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE4sizeEv(const void* self) { + return HostVectorSize(self, __func__); +} } diff --git a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp index c5844039e4..a648f82777 100644 --- a/core/libs/prx/libSceNpCppWebApi/include/Api.hpp +++ b/core/libs/prx/libSceNpCppWebApi/include/Api.hpp @@ -15,7 +15,29 @@ struct IntrusivePtr { void* libContext; }; +struct CommonIterator { + std::int32_t referenceCount; + void* element; +}; + extern "C" { +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesC2Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesD2Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities10initializeEPNS1_6Common10LibContextENS7_12IntrusivePtrINS7_6VectorINS7_6StringEEEEE(void* self, void* libContext, const IntrusivePtr* accountIds); +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities9terminateEv(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities8setlimitEi(void* self, int limit); +void APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities17setnpServiceLabelEi(void* self, int label); +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi17getUserActivitiesEiRKNS5_28ParameterToGetUserActivitiesERNS1_6Common11TransactionINS9_12IntrusivePtrINS4_26GetUsersActivitiesResponseEEENSB_INS9_18ResponseHeaderBaseEEEEE(int user, const void* parameter, void* transaction); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity13getActivityIdEv(const void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity7getNameEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity9getHiddenEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity12matchIdIsSetEv(const void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities8Activity10getMatchIdEv(const void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities26GetUsersActivitiesResponse8getUsersEv(const void* self); +const char* APS5_VABI _ZNK3sce2Np9CppWebApi10Activities2V114UserActivities14UserActivities12getAccountIdEv(const void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi10Activities2V114UserActivities14UserActivities13getActivitiesEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi12Leaderboards2V111UserFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_4UserEEE(); @@ -509,25 +531,25 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V149PostPlayerSessionsSession 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 _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_39PatchPlayerSessionsSessionIdRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers9terminateEv(void* self); 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 _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body); 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 _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9terminateEv(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi29ParameterToLeavePlayerSession9terminateEv(void* self); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsD1Ev(void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData1Ev(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi16getBlockingUsersEiRKNS4_27ParameterToGetBlockingUsersERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetBlockingUsersResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V19BlocksApi27ParameterToGetBlockingUsers10initializeEPNS1_6Common10LibContextE(void* self, const std::uint64_t* context); @@ -537,31 +559,31 @@ int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi31Parame 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 _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsD1Ev(void* self); 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 _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderD1Ev(void* self); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V156PostPlayerSessionsSessionIdInvitationsRequestBodyFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations9terminateEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions10initializeEPNS1_6Common10LibContextEPKc(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions10initializeEPNS1_6Common10LibContextEPKc(void* self, const std::uint64_t* context, const char* sessionIds); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsC1Ev(void* self); 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 _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE(void* self, const std::uint64_t* context, const char* sessionId, const IntrusivePtr* body); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V167PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_12JoinableUserEEEEEPNS9_INS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderC1Ev(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties9terminateEv(void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V118LeaderWithOnlineId12getAccountIdEv(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V139PatchPlayerSessionsSessionIdRequestBody20setInvitableUserTypeERKNS3_23CustomInvitableUserTypeE(); int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V129GetPlayerSessionsResponseBody17getPlayerSessionsEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersC1Ev(void* self); int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V120PlayerSessionForRead14getCustomData2Ev(); int APS5_VABI _ZNK3sce2Np9CppWebApi14SessionManager2V127ResponsePlayerSessionPlayer12getAccountIdEv(); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_14SessionManager2V130PostPlayerSessionsResponseBodyEEENS4_INS2_18ResponseHeaderBaseEEEE11hasResponseEv(); @@ -779,21 +801,21 @@ void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_ 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(); +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEppEv(CommonIterator* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEC1Ev(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_(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEneERKS5_(const CommonIterator* self, const CommonIterator* other); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE11hasResponseEv(); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEptEv(const IntrusivePtr* self); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE3endEv(); +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE3endEv(CommonIterator* iterator, const IntrusivePtr* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringeqEPKc(const CommonString* self, const char* other); std::int64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5getIdEv(); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_15DefaultResponseENS2_12IntrusivePtrINS2_18ResponseHeaderBaseEEEE5abortEv(); -int APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE5beginEv(); +CommonIterator* APS5_VABI _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE5beginEv(CommonIterator* iterator, const IntrusivePtr* self); void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEE3getEv(const IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEdeEv(); +CommonString* APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEdeEv(const CommonIterator* self); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi28ParameterToGetPublicProfilesD1Ev(); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEEdeEv(); @@ -812,6 +834,95 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11 void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetBasicPresencesResponseEED1Ev(IntrusivePtr* self); int APS5_VABI _ZNK3sce2Np9CppWebApi11UserProfile2V112BasicProfile17getPersonalDetailEv(); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetBlockingUsersResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); + +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer12setAccountIdEPKc(void* self, const char* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer13setPlayerNameEPKc(void* self, const char* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer9setTeamIdEPKc(void* self, const char* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V113RemovedPlayer9setReasonERKNS3_11LeaveReasonE(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116JoinMatchRequest17setNpServiceLabelERKi(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam10setMembersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_13RequestMemberEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam11setTeamNameEPKc(void* self, const char* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V117LeaveMatchRequest17setNpServiceLabelERKi(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118AddedPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_11AddedPlayerEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setExpirationTimeERKi(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setNpServiceLabelERKi(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest9setZoneIdEPKc(void* self, const char* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118RequestMatchPlayer12setAccountIdEPKc(void* self, const char* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118RequestMatchPlayer13setPlayerNameEPKc(void* self, const char* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118RequestTeamResults20setTeamMemberResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_23RequestTeamMemberResultEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V118RequestTeamResults8setScoreERKd(void* self, const double* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10setPlayersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestMatchPlayerEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster8setTeamsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_16RequestMatchTeamEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCompetitiveResultERKNS1_6Common12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCooperativeResultERKNS3_24RequestCooperativeResultE(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RemovedPlayerFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RemovedPlayerEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest17setNpServiceLabelERKi(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest18setMatchStatisticsERKNS1_6Common12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestMemberFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_13RequestMemberEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestPlayerResults8setScoreERKd(void* self, const double* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic23setTeamMemberStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_26RequestTeamMemberStatisticEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic8setStatsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics17setTeamStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestTeamStatisticEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics19setPlayerStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_22RequestPlayerStatisticEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123JoinMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_11AddedPlayerEEEEEPNS9_INS3_16JoinMatchRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V123RequestMatchTeamFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_16RequestMatchTeamEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124LeaveMatchRequestFactory6createEPNS1_6Common10LibContextERKNS5_6VectorINS5_12IntrusivePtrINS3_13RemovedPlayerEEEEEPNS9_INS3_17LeaveMatchRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult14setTeamResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestTeamResultsEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult16setPlayerResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestPlayerResultsEEEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self, const IntrusivePtr* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setExpirationTimeERKi(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setNpServiceLabelERKi(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest9setZoneIdEPKc(void* self, const char* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchStatusRequest17setNpServiceLabelERKi(void* self, const int* value); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125CreateMatchRequestFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestMatchPlayerFactory6createEPNS1_6Common10LibContextEPKcNS3_10PlayerTypeEPNS5_12IntrusivePtrINS3_18RequestMatchPlayerEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V125RequestTeamResultsFactory6createEPNS1_6Common10LibContextEPKciPNS5_12IntrusivePtrINS3_18RequestTeamResultsEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126AdditionalStatisticFactory6createEPNS1_6Common10LibContextEPKcS9_PNS5_12IntrusivePtrINS3_19AdditionalStatisticEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestInGameRosterFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_19RequestInGameRosterEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V126RequestMatchResultsFactory6createEPNS1_6Common10LibContextENS3_14ResultsVersionEPNS5_12IntrusivePtrINS3_19RequestMatchResultsEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127ReportResultsRequestFactory6createEPNS1_6Common10LibContextENS5_12IntrusivePtrINS3_19RequestMatchResultsEEEPNS8_INS3_20ReportResultsRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestPlayerResultsFactory6createEPNS1_6Common10LibContextEPKciPNS5_12IntrusivePtrINS3_20RequestPlayerResultsEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V127RequestTeamStatisticFactory6createEPNS1_6Common10LibContextEPKcPNS5_12IntrusivePtrINS3_20RequestTeamStatisticEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestMatchStatisticsFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_22RequestMatchStatisticsEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V129RequestPlayerStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_22RequestPlayerStatisticEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V130RequestTeamMemberResultFactory6createEPNS1_6Common10LibContextEPKcdPNS5_12IntrusivePtrINS3_23RequestTeamMemberResultEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131RequestCompetitiveResultFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24RequestCompetitiveResultEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchDetailRequestFactory6createEPNS1_6Common10LibContextEPNS5_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V131UpdateMatchStatusRequestFactory6createEPNS1_6Common10LibContextENS3_12UpdateStatusEPNS5_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V133RequestTeamMemberStatisticFactory6createEPNS1_6Common10LibContextEPKcRKNS5_6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEEPNSB_INS3_26RequestTeamMemberStatisticEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi10leaveMatchEiRKNS4_21ParameterToLeaveMatchERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi11createMatchEiRKNS4_22ParameterToCreateMatchERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_19CreateMatchResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi13reportResultsEiRKNS4_24ParameterToReportResultsERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi17updateMatchDetailEiRKNS4_28ParameterToUpdateMatchDetailERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi17updateMatchStatusEiRKNS4_28ParameterToUpdateMatchStatusERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_16JoinMatchRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatchC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatchD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi21ParameterToLeaveMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_17LeaveMatchRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi21ParameterToLeaveMatch9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi21ParameterToLeaveMatchC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi21ParameterToLeaveMatchD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_18CreateMatchRequestEEE(void* self, const std::uint64_t* context, const IntrusivePtr* body); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatchC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatchD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi24ParameterToReportResults10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_20ReportResultsRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi24ParameterToReportResults9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi24ParameterToReportResultsC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi24ParameterToReportResultsD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchDetail10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_24UpdateMatchDetailRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchDetailC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchDetailD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchStatus10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_24UpdateMatchStatusRequestEEE(void* self, const std::uint64_t* context, const char* text, const IntrusivePtr* body); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchStatus9terminateEv(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchStatusC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi28ParameterToUpdateMatchStatusD1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi7Matches2V18MatchApi9joinMatchEiRKNS4_20ParameterToJoinMatchERNS1_6Common11TransactionINS8_15DefaultResponseENS8_12IntrusivePtrINS8_18ResponseHeaderBaseEEEEE(void* self); +const void* APS5_VABI _ZNK3sce2Np9CppWebApi7Matches2V119CreateMatchResponse10getMatchIdEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10teamsIsSetEv(const void* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi7Matches2V119RequestInGameRoster12playersIsSetEv(const void* self); int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi17getPublicProfilesEiRKNS4_28ParameterToGetPublicProfilesERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_25GetPublicProfilesResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V113BasicPresence12setInContextERKb(); @@ -1240,8 +1351,41 @@ void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6String 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_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(IntrusivePtr* self, void* libContext); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(IntrusivePtr* self); -int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE4sizeEv(); +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE4sizeEv(const void* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEED1Ev(IntrusivePtr* self); void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEaSERS6_(IntrusivePtr* self, const IntrusivePtr* source); int APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String5emptyEv(const CommonString* self); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi16getPublicProfileEiRKNS4_27ParameterToGetPublicProfileERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetPublicProfileResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfile10initializeEPNS1_6Common10LibContextEPKc(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfile9terminateEv(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfileC1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfileD1Ev(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(void* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V114PersonalDetailEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEC1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEED1Ev(IntrusivePtr* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V16AvatarEEEEEED1Ev(IntrusivePtr* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE(); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption(); +int APS5_VABI _ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_(); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEcvbEv(const IntrusivePtr* self); +bool APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEcvbEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V16AvatarEEptEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEEdeEv(const IntrusivePtr* self); +void* APS5_VABI _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V16AvatarEEEEEEdeEv(const IntrusivePtr* self); +std::size_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE4sizeEv(const void* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringC1Ev(CommonString* self); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringD2Ev(CommonString* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String8copyFromEPKc(CommonString* self, const char* text); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String8copyFromERKS3_(CommonString* self, const CommonString* source); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6String5clearEv(CommonString* self); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String4sizeEv(const CommonString* self); +std::uint64_t APS5_VABI _ZNK3sce2Np9CppWebApi6Common6String6lengthEv(const CommonString* self); +bool APS5_VABI _ZN3sce2Np9CppWebApi6Common6StringeqERKS3_(const CommonString* self, const CommonString* other); +void APS5_VABI _ZN3sce2Np9CppWebApi6Common6String10setContextEPNS2_10LibContextE(CommonString* self, void* libContext); +std::int32_t APS5_VABI _ZN3sce2Np9CppWebApi6Common6String21intrusive_ptr_add_refEPS3_(CommonString* self); +std::int32_t APS5_VABI _ZN3sce2Np9CppWebApi6Common6String21intrusive_ptr_sub_refEPS3_(CommonString* self); +int APS5_VABI _ZN3sce2Np9CppWebApi6Common6String6appendERKS3_(CommonString* self, const CommonString* source); } diff --git a/core/libs/prx/libSceNpEntitlementAccess/Export.cpp b/core/libs/prx/libSceNpEntitlementAccess/Export.cpp index 3df65ec49d..5d6b2ecf93 100644 --- a/core/libs/prx/libSceNpEntitlementAccess/Export.cpp +++ b/core/libs/prx/libSceNpEntitlementAccess/Export.cpp @@ -4,10 +4,13 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include +#include #include #include #include #include +#include +#include #include #include #include @@ -79,6 +82,41 @@ const std::vector& OwnedAddons() { return owned; } +std::string PrintableLabel(const std::string& label) { + std::string printable; + for (const unsigned char byte : label) { + if (byte >= 0x20 && byte < 0x7f && byte != '\\') { + printable += static_cast(byte); + continue; + } + char escaped[5]; + std::snprintf(escaped, sizeof(escaped), "\\x%02x", byte); + printable += escaped; + } + return printable; +} + +std::string EntitlementsName() { + if (const auto* configured = ConfiguredEntitlements(); configured != nullptr && configured[0] != 0) return EntitlementsPath().string(); + return "anyps5-entitlements.ini"; +} + +void ReportUnlisted(const std::string& label) { + constexpr std::size_t reportLimit = 64; + static std::mutex reportMutex; + static std::set reported; + static bool capped = false; + std::lock_guard lock(reportMutex); + if (reported.contains(label)) return; + if (reported.size() < reportLimit) { + reported.insert(label); + std::fprintf(stderr, "[entitlements] the title asked for the add-on %s, which %s does not list, so it is reported as not installed\n", PrintableLabel(label).c_str(), EntitlementsName().c_str()); + } else if (!capped) { + capped = true; + std::fprintf(stderr, "[entitlements] the title asked for more than %zu add-ons that %s does not list; further ones are reported as not installed without a line\n", reportLimit, EntitlementsName().c_str()); + } +} + } extern "C" { @@ -92,6 +130,7 @@ int APS5_VABI sceNpEntitlementAccessGetAddcontEntitlementInfo(uint32_t service_l return 0; } } + ReportUnlisted(std::string(entitlement_label->data, strnlen(entitlement_label->data, sizeof(entitlement_label->data)))); return SCE_NP_ENTITLEMENT_ACCESS_ERROR_NOT_FOUND; } @@ -204,19 +243,32 @@ int APS5_VABI sceNpEntitlementAccessGetAddcontEntitlementInfoIndividual(std::int return sceNpEntitlementAccessGetAddcontEntitlementInfo(service_label, entitlement_label, info); } -int APS5_VABI sceNpEntitlementAccessPollConsumableEntitlementInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpEntitlementAccessPollConsumableEntitlementInfo(int64_t requestId, int32_t* result, void* info) { + (void)requestId; + (void)result; + (void)info; + return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessRequestConsumableEntitlementInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpEntitlementAccessRequestConsumableEntitlementInfo( + int32_t userId, uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, int64_t* requestId) { + (void)userId; + (void)serviceLabel; + (void)entitlementLabel; + (void)requestId; + return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpEntitlementAccessRequestConsumeEntitlement() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpEntitlementAccessRequestConsumeEntitlement( + int32_t userId, uint32_t serviceLabel, const void* entitlementLabel, const void* transactionId, int32_t useCount, + int64_t* requestId) { + (void)userId; + (void)serviceLabel; + (void)entitlementLabel; + (void)transactionId; + (void)useCount; + (void)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 index f65a2a2cb3..3f99d276b7 100644 --- a/core/libs/prx/libSceNpEntitlementAccess/include/Api.hpp +++ b/core/libs/prx/libSceNpEntitlementAccess/include/Api.hpp @@ -26,8 +26,8 @@ int APS5_VABI sceNpEntitlementAccessPollUnifiedEntitlementInfoList(std::int64_t 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(std::int32_t userId, uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, NpEntitlementAccessAddcontEntitlementInfo* info); -int APS5_VABI sceNpEntitlementAccessPollConsumableEntitlementInfo(); -int APS5_VABI sceNpEntitlementAccessRequestConsumableEntitlementInfo(); -int APS5_VABI sceNpEntitlementAccessRequestConsumeEntitlement(); +int APS5_VABI sceNpEntitlementAccessPollConsumableEntitlementInfo(std::int64_t requestId, std::int32_t* result, void* info); +int APS5_VABI sceNpEntitlementAccessRequestConsumableEntitlementInfo(std::int32_t userId, std::uint32_t serviceLabel, const NpUnifiedEntitlementLabel* entitlementLabel, std::int64_t* requestId); +int APS5_VABI sceNpEntitlementAccessRequestConsumeEntitlement(std::int32_t userId, std::uint32_t serviceLabel, const void* entitlementLabel, const void* transactionId, std::int32_t useCount, std::int64_t* requestId); } diff --git a/core/libs/prx/libSceNpGameIntent/Export.cpp b/core/libs/prx/libSceNpGameIntent/Export.cpp index 7d0ac8f3be..187a1cc748 100644 --- a/core/libs/prx/libSceNpGameIntent/Export.cpp +++ b/core/libs/prx/libSceNpGameIntent/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpGameIntent/include/InventoryExports.hpp" #include "prx/libSceNpGameIntent/include/Api.hpp" #include #include @@ -31,6 +32,27 @@ std::atomic_bool& AppLauncherInitialized() { extern "C" { +int APS5_VABI sceNpGameIntentGetPermittedIntentTypes() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpGameIntentLaunchAppForPinvoke() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpGameIntentLaunchAppRequestToGkp() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpGameIntentRemoveLaunchCallback() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpGameIntentRequest() { + NotImplemented_nid_no_patch(__func__); +} + + int APS5_VABI sceNpGameIntentGetPropertyValueString(const NpGameIntentData* intentData, const char* key, char* valueBuf, size_t bufSize) { if (!NpGameIntentInitialized()) { return NpGameIntentErrorNotInitialized; diff --git a/core/libs/prx/libSceNpGameIntent/include/InventoryExports.hpp b/core/libs/prx/libSceNpGameIntent/include/InventoryExports.hpp new file mode 100644 index 0000000000..9dd3fc1288 --- /dev/null +++ b/core/libs/prx/libSceNpGameIntent/include/InventoryExports.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpGameIntentGetPermittedIntentTypes(); +int APS5_VABI sceNpGameIntentLaunchAppForPinvoke(); +int APS5_VABI sceNpGameIntentLaunchAppRequestToGkp(); +int APS5_VABI sceNpGameIntentRemoveLaunchCallback(); +int APS5_VABI sceNpGameIntentRequest(); + +} diff --git a/core/libs/prx/libSceNpGameIntent/libSceNpGameIntent.hpp b/core/libs/prx/libSceNpGameIntent/libSceNpGameIntent.hpp index 7c0f3b8b2e..591063b314 100644 --- a/core/libs/prx/libSceNpGameIntent/libSceNpGameIntent.hpp +++ b/core/libs/prx/libSceNpGameIntent/libSceNpGameIntent.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceNpGameIntent/include/Api.hpp" +#include "prx/libSceNpGameIntent/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceNpManager/CMakeLists.txt b/core/libs/prx/libSceNpManager/CMakeLists.txt index ff2213e55b..d7449703cf 100644 --- a/core/libs/prx/libSceNpManager/CMakeLists.txt +++ b/core/libs/prx/libSceNpManager/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(libSceNpManager SHARED EXCLUDE_FROM_ALL Export.cpp) +add_library(libSceNpManager SHARED EXCLUDE_FROM_ALL Export.cpp PendingExports.cpp) target_include_directories(libSceNpManager PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceNpManager) target_sources(libSceNpManager PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/AdditionalExports.cpp) diff --git a/core/libs/prx/libSceNpManager/Export.cpp b/core/libs/prx/libSceNpManager/Export.cpp index 9d38ebc1c8..5611a62f1a 100644 --- a/core/libs/prx/libSceNpManager/Export.cpp +++ b/core/libs/prx/libSceNpManager/Export.cpp @@ -1,6 +1,7 @@ #include "prx/libSceNpManager/include/LegacyExports.hpp" #include "prx/libc/include/general/ExportMacros.hpp" #include "prx/libSceNpManager/include/Api.hpp" +#include "prx/libSceNpManager/include/InGameMessageIds.hpp" #include #include #include "SceTypes.hpp" @@ -10,6 +11,8 @@ #include #include #include +#include +#include #include // PSN is not emulated: the user is reported as signed out and online queries fail. @@ -36,6 +39,25 @@ static constexpr uint32_t NP_GAME_PRESENCE_STATUS_OFFLINE = 0; static std::atomic g_nextRequest{1}; +static constexpr int NP_IN_GAME_MESSAGE_ERROR_LIB_CONTEXT_NOT_FOUND = static_cast(0x80552B03); +static constexpr int NP_IN_GAME_MESSAGE_ERROR_HANDLE_NOT_FOUND = static_cast(0x80552B05); + +namespace { +struct InGameMessageContext { + std::set handles; +}; + +std::mutex inGameMessageMutex; +std::map inGameMessageContexts; +std::int32_t nextInGameMessageContext = 1; +std::int32_t nextInGameMessageHandle = 1; + +InGameMessageContext* FindInGameMessageContext(std::int32_t libCtxId) { + const auto found = inGameMessageContexts.find(libCtxId); + return found == inGameMessageContexts.end() ? nullptr : &found->second; +} +} + namespace { std::mutex reachabilityMutex; void* reachabilityCallback = nullptr; @@ -341,9 +363,11 @@ int APS5_VABI sceNpIdMapperOnlineIdToAccountId(int reqId, int userId, const void if (userId == USER_SERVICE_USER_ID_INVALID || !input || !output || value < 1 || value > 1000) return NP_ID_MAPPER_REFUSED_ARGUMENT; return SCE_NP_ERROR_SIGNED_OUT; } -int APS5_VABI sceNpInGameMessageAbortHandle(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpInGameMessageAbortHandle(std::int32_t libCtxId, std::int32_t handleId) { + std::lock_guard lock(inGameMessageMutex); + InGameMessageContext* context = FindInGameMessageContext(libCtxId); + if (!context) return NP_IN_GAME_MESSAGE_ERROR_LIB_CONTEXT_NOT_FOUND; + return context->handles.count(handleId) == 1 ? 0 : NP_IN_GAME_MESSAGE_ERROR_HANDLE_NOT_FOUND; } int APS5_VABI sceNpInGameMessageCheckCallback(void) { NotImplemented_nid_no_patch(__func__); @@ -835,6 +859,12 @@ int APS5_VABI sceNpSetGamePresenceOnline(int req_id, const NpOnlineId* online_id return SCE_NP_ERROR_SIGNED_OUT; } +int APS5_VABI sceNpSetGamePresenceOnlineA(int reqId, int userId, uint32_t presence) { + (void)presence; + if (userId == USER_SERVICE_USER_ID_INVALID) return SCE_NP_ERROR_INVALID_ARGUMENT; + return SignedOutRequest(reqId); +} + int APS5_VABI sceNpGetAccountCountryA(int user_id, void* country_code) { (void)user_id; if (!country_code) return SCE_NP_ERROR_INVALID_ARGUMENT; @@ -1060,19 +1090,29 @@ int APS5_VABI sceNpCheckCallbackForLib() { extern "C" { -int APS5_VABI sceNpInGameMessageCreateHandle() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpInGameMessageCreateHandle(std::int32_t libCtxId) { + std::lock_guard lock(inGameMessageMutex); + InGameMessageContext* context = FindInGameMessageContext(libCtxId); + if (!context) return NP_IN_GAME_MESSAGE_ERROR_LIB_CONTEXT_NOT_FOUND; + const std::int32_t handleId = TakeInGameMessageId(nextInGameMessageHandle, __func__); + context->handles.insert(handleId); + return handleId; } -int APS5_VABI sceNpInGameMessageDeleteHandle() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpInGameMessageDeleteHandle(std::int32_t libCtxId, std::int32_t handleId) { + std::lock_guard lock(inGameMessageMutex); + InGameMessageContext* context = FindInGameMessageContext(libCtxId); + if (!context) return NP_IN_GAME_MESSAGE_ERROR_LIB_CONTEXT_NOT_FOUND; + return context->handles.erase(handleId) == 1 ? 0 : NP_IN_GAME_MESSAGE_ERROR_HANDLE_NOT_FOUND; } -int APS5_VABI sceNpInGameMessageInitialize() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpInGameMessageInitialize(std::size_t poolSize, void* option) { + (void)poolSize; + (void)option; + std::lock_guard lock(inGameMessageMutex); + const std::int32_t libCtxId = TakeInGameMessageId(nextInGameMessageContext, __func__); + inGameMessageContexts.emplace(libCtxId, InGameMessageContext{}); + return libCtxId; } int APS5_VABI sceNpInGameMessagePrepare() { @@ -1085,9 +1125,9 @@ int APS5_VABI sceNpInGameMessageSendData() { return 0; } -int APS5_VABI sceNpInGameMessageTerminate() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpInGameMessageTerminate(std::int32_t libCtxId) { + std::lock_guard lock(inGameMessageMutex); + return inGameMessageContexts.erase(libCtxId) == 1 ? 0 : NP_IN_GAME_MESSAGE_ERROR_LIB_CONTEXT_NOT_FOUND; } int APS5_VABI sceNpSnsFacebookCreateRequest() { diff --git a/core/libs/prx/libSceNpManager/PendingExports.cpp b/core/libs/prx/libSceNpManager/PendingExports.cpp new file mode 100644 index 0000000000..29368447c5 --- /dev/null +++ b/core/libs/prx/libSceNpManager/PendingExports.cpp @@ -0,0 +1,762 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libSceNpManager/include/Api.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI _ZN3sce2np10NpOnlineId13GetNpOnlineIdERKNS0_4UserEP13SceNpOnlineId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineId13GetNpOnlineIdERKNS0_4UserEPS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineId16SetNpOnlineIdStrEPKc(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineId5ClearEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineIdC1ERK13SceNpOnlineId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineIdC1ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineIdC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineIdC2ERK13SceNpOnlineId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineIdC2ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineIdC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineIdD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineIdD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np10NpOnlineIdD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np11NpHttpTransC2EP16SceNpAllocatorEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np11NpHttpTransD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np11NpHttpTransD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np11NpHttpTransD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpHttpClient4InitEii(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpHttpClientC1EP16SceNpAllocatorEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpHttpClientC2EP16SceNpAllocatorEx(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpHttpClientD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpHttpClientD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpHttpClientD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpTitleToken5ClearEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpTitleTokenC1ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpTitleTokenC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpTitleTokenC2ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpTitleTokenC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpTitleTokenD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpTitleTokenD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np12NpTitleTokenD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessToken5ClearEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessTokenC1ERK16SceNpAccessToken(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessTokenC1ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessTokenC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessTokenC2ERK16SceNpAccessToken(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessTokenC2ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessTokenC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessTokenD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessTokenD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np13NpAccessTokenD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client10EndRequestEii(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client11InitServiceEi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client11TermServiceEi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client11WaitRequestEiij(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client12AbortRequestEii(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client12BeginRequestEii(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client13CreateRequestEimPKvPi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client13DeleteRequestEii(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client13GetIpmiClientEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client13PollEventFlagEijmjPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np3ipc14service_client14PollEventQueueEiPvm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpId5ClearEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpIdC1ERK7SceNpId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpIdC1ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpIdC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpIdC2ERK7SceNpId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpIdC2ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpIdC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpIdD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpIdD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4NpIdD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4User5ClearEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4User7GetUserEiPS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4UserC1ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4UserC1Ei(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4UserC1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4UserC2ERKS1_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4UserC2Ei(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4UserC2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4UserD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4UserD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np4UserD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np8NpTicket5ClearEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np8NpTicketD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np8NpTicketD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2np8NpTicketD2Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npeqERK13SceNpOnlineIdRKNS0_10NpOnlineIdE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npeqERKNS0_10NpOnlineIdERK13SceNpOnlineId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npeqERKNS0_10NpOnlineIdES3_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npeqERKNS0_4UserERKi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npeqERKNS0_4UserES3_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npneERK13SceNpOnlineIdRKNS0_10NpOnlineIdE(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npneERKNS0_10NpOnlineIdERK13SceNpOnlineId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npneERKNS0_10NpOnlineIdES3_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npneERKNS0_4UserERKi(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZN3sce2npneERKNS0_4UserES3_(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np10NpOnlineId7IsEmptyEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np12NpTitleToken6GetStrEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np13NpAccessToken7IsEmptyEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np4User10IsLoggedInEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np4User12GetAccountIdEPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np4User12HasAccountIdEPb(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np4User25GetAccountIdFromRegistoryEPm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np4User7IsEmptyEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np4User7IsGuestEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np4User9GetUserIdEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np8NpTicket13GetTicketDataEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZNK3sce2np8NpTicket13GetTicketSizeEv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZThn16_N3sce2np11NpHttpTransD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZThn16_N3sce2np11NpHttpTransD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZThn8_N3sce2np11NpHttpTransD0Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _ZThn8_N3sce2np11NpHttpTransD1Ev(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpIpcCreateMemoryFromKernel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpIpcCreateMemoryFromPool(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpIpcDestroyMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpIpcFreeImpl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpIpcGetNpMemAllocator(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpIpcMallocImpl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpIpcReallocImpl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpManagerCreateMemoryFromKernel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpManagerCreateMemoryFromPool(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpManagerDestroyMemory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpManagerFreeImpl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpManagerGetNpMemAllocator(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpManagerMallocImpl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI _sceNpManagerReallocImpl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntGetOnlineIdByAccountId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginGetAccessTokenViaImplicitFlow(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginGetAccountId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginGetAuthenticateResponse(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginGetDeviceCodeInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginGetOnlineId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginGetUserId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginResetSsoToken(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginRevalidatePassword(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginSetUserId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginSignin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginValidateKratosAuthCode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntLoginVerifyDeviceCode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntPfAuth(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntRemoveActiveSigninStateCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntRemoveOnlineIdChangeCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntRemovePlusMemberTypeCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntRemoveSigninStateCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntRevalidatePassword(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntSetPlusMemberTypeNB(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntSetRequestHeader(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntSetTimeout(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntSignout(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntSubmitUserCode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntTemporarySignout(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntUpdateBcAccessToken(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerIntUpdateVshAccessToken(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerPrxStartVsh(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpManagerPrxStopVsh(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2CreateUserContext(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2DeleteUserContext(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2Init(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2IsInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2OptionalCheckCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2RegisterDataType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2RegisterExtendedDataFilter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2RegisterNotificationExCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2SendPushStatisticsDataSystemTelemetry(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2SetGlobalMutex(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2SetNpCommunicationId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2Term(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2TriggerEmptyUserEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2UnregisterDataType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2UnregisterExtendedDataFilter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2UnregisterNotificationExCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2UnsetNpCommunicationId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPush2WaitCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushCheckCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushIntBeginInactive(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushIntEndInactive(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushIntGetBindUserState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushIntGetConnectionState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushIntRegisterNotificationPacketCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushIntUnregisterNotificationPacketCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushRegisterExtendedDataFilter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushRegisterNotificationExCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushStartNotification(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushStartNotificationA(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushStopNotification(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushStopNotificationA(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushUnregisterExtendedDataFilter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpPushUnregisterNotificationCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpRegisterStateCallbackForToolkit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceClientInit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpServiceClientTerm(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpSetAdditionalScope(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpSetNpTitleIdVsh(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUnregisterGamePresenceCallbackA(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} +int APS5_VABI sceNpUnregisterStateCallbackForToolkit(void) { + 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 index 3ab6ee31a7..9090cdaf11 100644 --- a/core/libs/prx/libSceNpManager/include/Api.hpp +++ b/core/libs/prx/libSceNpManager/include/Api.hpp @@ -69,7 +69,7 @@ int APS5_VABI sceNpIdMapperCreateRequest(void); int APS5_VABI sceNpIdMapperDeleteRequest(int reqId); int APS5_VABI sceNpIdMapperNpIdToAccountId(int reqId, int userId, const void* input, void* output, std::uint32_t value, std::uint8_t flag, std::uint64_t extra); int APS5_VABI sceNpIdMapperOnlineIdToAccountId(int reqId, int userId, const void* input, void* output, std::uint32_t value, std::uint8_t flag, std::uint64_t extra); -int APS5_VABI sceNpInGameMessageAbortHandle(void); +int APS5_VABI sceNpInGameMessageAbortHandle(std::int32_t libCtxId, std::int32_t handleId); int APS5_VABI sceNpInGameMessageCheckCallback(void); int APS5_VABI sceNpInGameMessageGetMemoryPoolStatistics(void); int APS5_VABI sceNpInGameMessagePrepareA(void); @@ -176,6 +176,7 @@ 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 sceNpSetGamePresenceOnlineA(int reqId, int userId, 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); @@ -206,14 +207,203 @@ 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 sceNpInGameMessageCreateHandle(std::int32_t libCtxId); +int APS5_VABI sceNpInGameMessageDeleteHandle(std::int32_t libCtxId, std::int32_t handleId); +int APS5_VABI sceNpInGameMessageInitialize(std::size_t poolSize, void* option); int APS5_VABI sceNpInGameMessagePrepare(); int APS5_VABI sceNpInGameMessageSendData(); -int APS5_VABI sceNpInGameMessageTerminate(); +int APS5_VABI sceNpInGameMessageTerminate(std::int32_t libCtxId); int APS5_VABI sceNpSnsFacebookCreateRequest(); int APS5_VABI sceNpSnsFacebookDeleteRequest(); int APS5_VABI sceNpSnsFacebookGetAccessToken(); +int APS5_VABI _ZN3sce2np10NpOnlineId13GetNpOnlineIdERKNS0_4UserEP13SceNpOnlineId(void); +int APS5_VABI _ZN3sce2np10NpOnlineId13GetNpOnlineIdERKNS0_4UserEPS1_(void); +int APS5_VABI _ZN3sce2np10NpOnlineId16SetNpOnlineIdStrEPKc(void); +int APS5_VABI _ZN3sce2np10NpOnlineId5ClearEv(void); +int APS5_VABI _ZN3sce2np10NpOnlineIdC1ERK13SceNpOnlineId(void); +int APS5_VABI _ZN3sce2np10NpOnlineIdC1ERKS1_(void); +int APS5_VABI _ZN3sce2np10NpOnlineIdC1Ev(void); +int APS5_VABI _ZN3sce2np10NpOnlineIdC2ERK13SceNpOnlineId(void); +int APS5_VABI _ZN3sce2np10NpOnlineIdC2ERKS1_(void); +int APS5_VABI _ZN3sce2np10NpOnlineIdC2Ev(void); +int APS5_VABI _ZN3sce2np10NpOnlineIdD0Ev(void); +int APS5_VABI _ZN3sce2np10NpOnlineIdD1Ev(void); +int APS5_VABI _ZN3sce2np10NpOnlineIdD2Ev(void); +int APS5_VABI _ZN3sce2np11NpHttpTransC2EP16SceNpAllocatorEx(void); +int APS5_VABI _ZN3sce2np11NpHttpTransD0Ev(void); +int APS5_VABI _ZN3sce2np11NpHttpTransD1Ev(void); +int APS5_VABI _ZN3sce2np11NpHttpTransD2Ev(void); +int APS5_VABI _ZN3sce2np12NpHttpClient4InitEii(void); +int APS5_VABI _ZN3sce2np12NpHttpClientC1EP16SceNpAllocatorEx(void); +int APS5_VABI _ZN3sce2np12NpHttpClientC2EP16SceNpAllocatorEx(void); +int APS5_VABI _ZN3sce2np12NpHttpClientD0Ev(void); +int APS5_VABI _ZN3sce2np12NpHttpClientD1Ev(void); +int APS5_VABI _ZN3sce2np12NpHttpClientD2Ev(void); +int APS5_VABI _ZN3sce2np12NpTitleToken5ClearEv(void); +int APS5_VABI _ZN3sce2np12NpTitleTokenC1ERKS1_(void); +int APS5_VABI _ZN3sce2np12NpTitleTokenC1Ev(void); +int APS5_VABI _ZN3sce2np12NpTitleTokenC2ERKS1_(void); +int APS5_VABI _ZN3sce2np12NpTitleTokenC2Ev(void); +int APS5_VABI _ZN3sce2np12NpTitleTokenD0Ev(void); +int APS5_VABI _ZN3sce2np12NpTitleTokenD1Ev(void); +int APS5_VABI _ZN3sce2np12NpTitleTokenD2Ev(void); +int APS5_VABI _ZN3sce2np13NpAccessToken5ClearEv(void); +int APS5_VABI _ZN3sce2np13NpAccessTokenC1ERK16SceNpAccessToken(void); +int APS5_VABI _ZN3sce2np13NpAccessTokenC1ERKS1_(void); +int APS5_VABI _ZN3sce2np13NpAccessTokenC1Ev(void); +int APS5_VABI _ZN3sce2np13NpAccessTokenC2ERK16SceNpAccessToken(void); +int APS5_VABI _ZN3sce2np13NpAccessTokenC2ERKS1_(void); +int APS5_VABI _ZN3sce2np13NpAccessTokenC2Ev(void); +int APS5_VABI _ZN3sce2np13NpAccessTokenD0Ev(void); +int APS5_VABI _ZN3sce2np13NpAccessTokenD1Ev(void); +int APS5_VABI _ZN3sce2np13NpAccessTokenD2Ev(void); +int APS5_VABI _ZN3sce2np3ipc14service_client10EndRequestEii(void); +int APS5_VABI _ZN3sce2np3ipc14service_client11InitServiceEi(void); +int APS5_VABI _ZN3sce2np3ipc14service_client11TermServiceEi(void); +int APS5_VABI _ZN3sce2np3ipc14service_client11WaitRequestEiij(void); +int APS5_VABI _ZN3sce2np3ipc14service_client12AbortRequestEii(void); +int APS5_VABI _ZN3sce2np3ipc14service_client12BeginRequestEii(void); +int APS5_VABI _ZN3sce2np3ipc14service_client13CreateRequestEimPKvPi(void); +int APS5_VABI _ZN3sce2np3ipc14service_client13DeleteRequestEii(void); +int APS5_VABI _ZN3sce2np3ipc14service_client13GetIpmiClientEv(void); +int APS5_VABI _ZN3sce2np3ipc14service_client13PollEventFlagEijmjPm(void); +int APS5_VABI _ZN3sce2np3ipc14service_client14PollEventQueueEiPvm(void); +int APS5_VABI _ZN3sce2np4NpId5ClearEv(void); +int APS5_VABI _ZN3sce2np4NpIdC1ERK7SceNpId(void); +int APS5_VABI _ZN3sce2np4NpIdC1ERKS1_(void); +int APS5_VABI _ZN3sce2np4NpIdC1Ev(void); +int APS5_VABI _ZN3sce2np4NpIdC2ERK7SceNpId(void); +int APS5_VABI _ZN3sce2np4NpIdC2ERKS1_(void); +int APS5_VABI _ZN3sce2np4NpIdC2Ev(void); +int APS5_VABI _ZN3sce2np4NpIdD0Ev(void); +int APS5_VABI _ZN3sce2np4NpIdD1Ev(void); +int APS5_VABI _ZN3sce2np4NpIdD2Ev(void); +int APS5_VABI _ZN3sce2np4User5ClearEv(void); +int APS5_VABI _ZN3sce2np4User7GetUserEiPS1_(void); +int APS5_VABI _ZN3sce2np4UserC1ERKS1_(void); +int APS5_VABI _ZN3sce2np4UserC1Ei(void); +int APS5_VABI _ZN3sce2np4UserC1Ev(void); +int APS5_VABI _ZN3sce2np4UserC2ERKS1_(void); +int APS5_VABI _ZN3sce2np4UserC2Ei(void); +int APS5_VABI _ZN3sce2np4UserC2Ev(void); +int APS5_VABI _ZN3sce2np4UserD0Ev(void); +int APS5_VABI _ZN3sce2np4UserD1Ev(void); +int APS5_VABI _ZN3sce2np4UserD2Ev(void); +int APS5_VABI _ZN3sce2np8NpTicket5ClearEv(void); +int APS5_VABI _ZN3sce2np8NpTicketD0Ev(void); +int APS5_VABI _ZN3sce2np8NpTicketD1Ev(void); +int APS5_VABI _ZN3sce2np8NpTicketD2Ev(void); +int APS5_VABI _ZN3sce2npeqERK13SceNpOnlineIdRKNS0_10NpOnlineIdE(void); +int APS5_VABI _ZN3sce2npeqERKNS0_10NpOnlineIdERK13SceNpOnlineId(void); +int APS5_VABI _ZN3sce2npeqERKNS0_10NpOnlineIdES3_(void); +int APS5_VABI _ZN3sce2npeqERKNS0_4UserERKi(void); +int APS5_VABI _ZN3sce2npeqERKNS0_4UserES3_(void); +int APS5_VABI _ZN3sce2npneERK13SceNpOnlineIdRKNS0_10NpOnlineIdE(void); +int APS5_VABI _ZN3sce2npneERKNS0_10NpOnlineIdERK13SceNpOnlineId(void); +int APS5_VABI _ZN3sce2npneERKNS0_10NpOnlineIdES3_(void); +int APS5_VABI _ZN3sce2npneERKNS0_4UserERKi(void); +int APS5_VABI _ZN3sce2npneERKNS0_4UserES3_(void); +int APS5_VABI _ZNK3sce2np10NpOnlineId7IsEmptyEv(void); +int APS5_VABI _ZNK3sce2np12NpTitleToken6GetStrEv(void); +int APS5_VABI _ZNK3sce2np13NpAccessToken7IsEmptyEv(void); +int APS5_VABI _ZNK3sce2np4User10IsLoggedInEv(void); +int APS5_VABI _ZNK3sce2np4User12GetAccountIdEPm(void); +int APS5_VABI _ZNK3sce2np4User12HasAccountIdEPb(void); +int APS5_VABI _ZNK3sce2np4User25GetAccountIdFromRegistoryEPm(void); +int APS5_VABI _ZNK3sce2np4User7IsEmptyEv(void); +int APS5_VABI _ZNK3sce2np4User7IsGuestEv(void); +int APS5_VABI _ZNK3sce2np4User9GetUserIdEv(void); +int APS5_VABI _ZNK3sce2np8NpTicket13GetTicketDataEv(void); +int APS5_VABI _ZNK3sce2np8NpTicket13GetTicketSizeEv(void); +int APS5_VABI _ZThn16_N3sce2np11NpHttpTransD0Ev(void); +int APS5_VABI _ZThn16_N3sce2np11NpHttpTransD1Ev(void); +int APS5_VABI _ZThn8_N3sce2np11NpHttpTransD0Ev(void); +int APS5_VABI _ZThn8_N3sce2np11NpHttpTransD1Ev(void); +int APS5_VABI _sceNpIpcCreateMemoryFromKernel(void); +int APS5_VABI _sceNpIpcCreateMemoryFromPool(void); +int APS5_VABI _sceNpIpcDestroyMemory(void); +int APS5_VABI _sceNpIpcFreeImpl(void); +int APS5_VABI _sceNpIpcGetNpMemAllocator(void); +int APS5_VABI _sceNpIpcMallocImpl(void); +int APS5_VABI _sceNpIpcReallocImpl(void); +int APS5_VABI _sceNpManagerCreateMemoryFromKernel(void); +int APS5_VABI _sceNpManagerCreateMemoryFromPool(void); +int APS5_VABI _sceNpManagerDestroyMemory(void); +int APS5_VABI _sceNpManagerFreeImpl(void); +int APS5_VABI _sceNpManagerGetNpMemAllocator(void); +int APS5_VABI _sceNpManagerMallocImpl(void); +int APS5_VABI _sceNpManagerReallocImpl(void); +int APS5_VABI sceNpManagerIntGetOnlineIdByAccountId(void); +int APS5_VABI sceNpManagerIntLoginGetAccessTokenViaImplicitFlow(void); +int APS5_VABI sceNpManagerIntLoginGetAccountId(void); +int APS5_VABI sceNpManagerIntLoginGetAuthenticateResponse(void); +int APS5_VABI sceNpManagerIntLoginGetDeviceCodeInfo(void); +int APS5_VABI sceNpManagerIntLoginGetOnlineId(void); +int APS5_VABI sceNpManagerIntLoginGetUserId(void); +int APS5_VABI sceNpManagerIntLoginResetSsoToken(void); +int APS5_VABI sceNpManagerIntLoginRevalidatePassword(void); +int APS5_VABI sceNpManagerIntLoginSetUserId(void); +int APS5_VABI sceNpManagerIntLoginSignin(void); +int APS5_VABI sceNpManagerIntLoginValidateKratosAuthCode(void); +int APS5_VABI sceNpManagerIntLoginVerifyDeviceCode(void); +int APS5_VABI sceNpManagerIntPfAuth(void); +int APS5_VABI sceNpManagerIntRemoveActiveSigninStateCallback(void); +int APS5_VABI sceNpManagerIntRemoveOnlineIdChangeCallback(void); +int APS5_VABI sceNpManagerIntRemovePlusMemberTypeCallback(void); +int APS5_VABI sceNpManagerIntRemoveSigninStateCallback(void); +int APS5_VABI sceNpManagerIntRevalidatePassword(void); +int APS5_VABI sceNpManagerIntSetPlusMemberTypeNB(void); +int APS5_VABI sceNpManagerIntSetRequestHeader(void); +int APS5_VABI sceNpManagerIntSetTimeout(void); +int APS5_VABI sceNpManagerIntSignout(void); +int APS5_VABI sceNpManagerIntSubmitUserCode(void); +int APS5_VABI sceNpManagerIntTemporarySignout(void); +int APS5_VABI sceNpManagerIntUpdateBcAccessToken(void); +int APS5_VABI sceNpManagerIntUpdateVshAccessToken(void); +int APS5_VABI sceNpManagerPrxStartVsh(void); +int APS5_VABI sceNpManagerPrxStopVsh(void); +int APS5_VABI sceNpPush2CreateUserContext(void); +int APS5_VABI sceNpPush2DeleteUserContext(void); +int APS5_VABI sceNpPush2Init(void); +int APS5_VABI sceNpPush2IsInit(void); +int APS5_VABI sceNpPush2OptionalCheckCallback(void); +int APS5_VABI sceNpPush2RegisterDataType(void); +int APS5_VABI sceNpPush2RegisterExtendedDataFilter(void); +int APS5_VABI sceNpPush2RegisterNotificationExCallback(void); +int APS5_VABI sceNpPush2SendPushStatisticsDataSystemTelemetry(void); +int APS5_VABI sceNpPush2SetGlobalMutex(void); +int APS5_VABI sceNpPush2SetNpCommunicationId(void); +int APS5_VABI sceNpPush2Term(void); +int APS5_VABI sceNpPush2TriggerEmptyUserEvent(void); +int APS5_VABI sceNpPush2UnregisterDataType(void); +int APS5_VABI sceNpPush2UnregisterExtendedDataFilter(void); +int APS5_VABI sceNpPush2UnregisterNotificationExCallback(void); +int APS5_VABI sceNpPush2UnsetNpCommunicationId(void); +int APS5_VABI sceNpPush2WaitCallback(void); +int APS5_VABI sceNpPushCheckCallback(void); +int APS5_VABI sceNpPushInit(void); +int APS5_VABI sceNpPushIntBeginInactive(void); +int APS5_VABI sceNpPushIntEndInactive(void); +int APS5_VABI sceNpPushIntGetBindUserState(void); +int APS5_VABI sceNpPushIntGetConnectionState(void); +int APS5_VABI sceNpPushIntRegisterNotificationPacketCallback(void); +int APS5_VABI sceNpPushIntUnregisterNotificationPacketCallback(void); +int APS5_VABI sceNpPushRegisterExtendedDataFilter(void); +int APS5_VABI sceNpPushRegisterNotificationExCallback(void); +int APS5_VABI sceNpPushStartNotification(void); +int APS5_VABI sceNpPushStartNotificationA(void); +int APS5_VABI sceNpPushStopNotification(void); +int APS5_VABI sceNpPushStopNotificationA(void); +int APS5_VABI sceNpPushTerm(void); +int APS5_VABI sceNpPushUnregisterExtendedDataFilter(void); +int APS5_VABI sceNpPushUnregisterNotificationCallback(void); +int APS5_VABI sceNpRegisterStateCallbackForToolkit(void); +int APS5_VABI sceNpServiceClientInit(void); +int APS5_VABI sceNpServiceClientTerm(void); +int APS5_VABI sceNpSetAdditionalScope(void); +int APS5_VABI sceNpSetNpTitleIdVsh(void); +int APS5_VABI sceNpUnregisterGamePresenceCallbackA(void); +int APS5_VABI sceNpUnregisterStateCallbackForToolkit(void); + } diff --git a/core/libs/prx/libSceNpManager/include/InGameMessageIds.hpp b/core/libs/prx/libSceNpManager/include/InGameMessageIds.hpp new file mode 100644 index 0000000000..a87e664318 --- /dev/null +++ b/core/libs/prx/libSceNpManager/include/InGameMessageIds.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include +#include + +inline std::int32_t TakeInGameMessageId(std::int32_t& next, const char* function) { + if (next == std::numeric_limits::max()) throw std::runtime_error(std::string(function) + ": the in-game message ids are exhausted"); + return next++; +} diff --git a/core/libs/prx/libSceNpMatching2/CMakeLists.txt b/core/libs/prx/libSceNpMatching2/CMakeLists.txt index 06978af7ad..843d50f494 100644 --- a/core/libs/prx/libSceNpMatching2/CMakeLists.txt +++ b/core/libs/prx/libSceNpMatching2/CMakeLists.txt @@ -5,7 +5,7 @@ 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) + target_link_libraries(guest_np_matching2_offline_tests PRIVATE libSceNpMatching2 ${CMAKE_DL_LIBS}) 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) diff --git a/core/libs/prx/libSceNpMatching2/Export.cpp b/core/libs/prx/libSceNpMatching2/Export.cpp index c6a28b47b5..d6f0266d23 100644 --- a/core/libs/prx/libSceNpMatching2/Export.cpp +++ b/core/libs/prx/libSceNpMatching2/Export.cpp @@ -1,6 +1,7 @@ #include "prx/libSceNpMatching2/include/Api.hpp" #include #include +#include #include #include #include "SceTypes.hpp" @@ -12,10 +13,20 @@ static constexpr int SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT = static_cast( 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_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND = static_cast(0x80550C06); +static constexpr int SCE_NP_MATCHING2_ERROR_INVALID_SERVER_ID = static_cast(0x80550C0C); +static constexpr int SCE_NP_MATCHING2_ERROR_INVALID_ROOM_ID = static_cast(0x80550C0F); +static constexpr int SCE_NP_MATCHING2_ERROR_SERVER_NOT_AVAILABLE = static_cast(0x80550C28); +static constexpr int SCE_NP_MATCHING2_ERROR_SESSION_NOT_FOUND = static_cast(0x80550C3A); +static constexpr int SCE_NP_MATCHING2_SIGNALING_ERROR_CTX_NOT_FOUND = static_cast(0x80550E05); +static constexpr int SCE_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT = static_cast(0x80550E15); +static constexpr int SCE_NP_MATCHING2_SIGNALING_ERROR_PEER_NOT_FOUND = static_cast(0x80550E19); static constexpr int SCE_NET_ERROR_RESOLVER_ETIMEDOUT = static_cast(0x804101E2); static constexpr std::int32_t SCE_NP_SIGNALING_CONN_STATUS_INACTIVE = 0; static constexpr int MAX_CONTEXTS = 10; +static constexpr std::uint16_t CONTEXT_A_FLAG = 0x100; static constexpr std::uint64_t CREATE_CONTEXT_PARAMETER_SIZE = 0x28; +static constexpr std::uint64_t CREATE_CONTEXT_PARAMETER_A_SIZE = 0x10; namespace { @@ -23,17 +34,64 @@ static_assert(offsetof(NpMatching2InitializeParameter, size) == 0x20); static_assert(sizeof(NpMatching2InitializeParameter) == 0x30); static_assert(sizeof(NpMatching2CreateContextParameter) == CREATE_CONTEXT_PARAMETER_SIZE); +static_assert(sizeof(NpMatching2CreateContextParameterA) == CREATE_CONTEXT_PARAMETER_A_SIZE); std::mutex matching2Mutex; bool matching2Initialized = false; std::unordered_set contexts; int nextContextId = 1; +std::uint16_t signalingPort = 0; + +enum class Leading { ServerId, RoomId }; 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; } +int AllocateContext(std::uint16_t* ctxId, std::uint16_t flag) { + for (int tried = 0; tried < MAX_CONTEXTS; ++tried) { + const auto candidate = static_cast((nextContextId - 1 + tried) % MAX_CONTEXTS + 1); + if (contexts.contains(candidate) || contexts.contains(candidate | CONTEXT_A_FLAG)) continue; + contexts.insert(candidate | flag); + nextContextId = candidate % MAX_CONTEXTS + 1; + *ctxId = candidate | flag; + return 0; + } + return SCE_NP_MATCHING2_ERROR_CONTEXT_MAX; +} + +int CheckRequest(const void* request, const std::uint32_t* requestId, Leading leading) { + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + if (!request || !requestId) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + if (leading == Leading::ServerId) { + std::uint16_t serverId; + std::memcpy(&serverId, request, sizeof(serverId)); + return serverId == 0 ? SCE_NP_MATCHING2_ERROR_INVALID_SERVER_ID : 0; + } + std::uint64_t roomId; + std::memcpy(&roomId, request, sizeof(roomId)); + return roomId == 0 ? SCE_NP_MATCHING2_ERROR_INVALID_ROOM_ID : 0; +} + +int StartRequest(const char* name, std::uint16_t ctxId, const void* request, const std::uint32_t* requestId, Leading leading) { + std::lock_guard lock(matching2Mutex); + if (const int error = CheckRequest(request, requestId, leading)) return error; + if (!contexts.contains(ctxId)) return SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND; + NotImplemented_nid_no_patch(name); + return 0; +} + +int CheckLocal(std::uint16_t ctxId) { + return contexts.contains(ctxId) ? SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED : SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND; +} + +int ConnectionInfo(std::uint16_t ctxId, const void* info) { + std::lock_guard lock(matching2Mutex); + if (!info) return SCE_NP_MATCHING2_SIGNALING_ERROR_INVALID_ARGUMENT; + return contexts.contains(ctxId) ? SCE_NP_MATCHING2_SIGNALING_ERROR_PEER_NOT_FOUND : SCE_NP_MATCHING2_SIGNALING_ERROR_CTX_NOT_FOUND; +} + } extern "C" { @@ -56,15 +114,7 @@ int APS5_VABI sceNpMatching2CreateContext(const NpMatching2CreateContextParamete 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; + return AllocateContext(ctxId, 0); } int APS5_VABI sceNpMatching2CreateJoinRoom() { @@ -84,9 +134,9 @@ int APS5_VABI sceNpMatching2GetRoomDataExternalList() { return 0; } -int APS5_VABI sceNpMatching2GetRoomDataInternal() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2GetRoomDataInternal(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId) { + (void)requestOpt; + return StartRequest(__func__, ctxId, request, requestId, Leading::RoomId); } int APS5_VABI sceNpMatching2GetRoomMemberDataInternal() { @@ -94,9 +144,11 @@ int APS5_VABI sceNpMatching2GetRoomMemberDataInternal() { return 0; } -int APS5_VABI sceNpMatching2GetServerId() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2GetServerId(std::uint16_t ctxId, std::uint16_t* serverId) { + std::lock_guard lock(matching2Mutex); + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + if (!serverId) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + return CheckLocal(ctxId); } int APS5_VABI sceNpMatching2GetUserInfoList() { @@ -104,9 +156,9 @@ int APS5_VABI sceNpMatching2GetUserInfoList() { return 0; } -int APS5_VABI sceNpMatching2GetWorldInfoList() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2GetWorldInfoList(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId) { + (void)requestOpt; + return StartRequest(__func__, ctxId, request, requestId, Leading::ServerId); } int APS5_VABI sceNpMatching2Initialize(const NpMatching2InitializeParameter* param) { @@ -127,9 +179,11 @@ int APS5_VABI sceNpMatching2KickoutRoomMember() { return 0; } -int APS5_VABI sceNpMatching2LeaveRoom() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2LeaveRoom(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId) { + (void)requestOpt; + std::lock_guard lock(matching2Mutex); + if (const int error = CheckRequest(request, requestId, Leading::RoomId)) return error; + return contexts.contains(ctxId) ? SCE_NP_MATCHING2_ERROR_SESSION_NOT_FOUND : SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND; } int APS5_VABI sceNpMatching2RegisterContextCallback(void* callback, void* userdata) { @@ -182,14 +236,14 @@ int APS5_VABI sceNpMatching2SetDefaultRequestOptParam(std::uint16_t ctxId, const return CheckContext(ctxId); } -int APS5_VABI sceNpMatching2SetRoomDataExternal() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2SetRoomDataExternal(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId) { + (void)requestOpt; + return StartRequest(__func__, ctxId, request, requestId, Leading::RoomId); } -int APS5_VABI sceNpMatching2SetRoomDataInternal() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2SetRoomDataInternal(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId) { + (void)requestOpt; + return StartRequest(__func__, ctxId, request, requestId, Leading::RoomId); } int APS5_VABI sceNpMatching2SetRoomMemberDataInternal() { @@ -197,14 +251,16 @@ int APS5_VABI sceNpMatching2SetRoomMemberDataInternal() { return 0; } -int APS5_VABI sceNpMatching2SetUserInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2SetUserInfo(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId) { + (void)requestOpt; + return StartRequest(__func__, ctxId, request, requestId, Leading::ServerId); } -int APS5_VABI sceNpMatching2SignalingGetConnectionInfo() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2SignalingGetConnectionInfo(std::uint16_t ctxId, std::uint64_t roomId, std::uint16_t memberId, std::int32_t infoType, void* info) { + (void)roomId; + (void)memberId; + (void)infoType; + return ConnectionInfo(ctxId, info); } int APS5_VABI sceNpMatching2Terminate(void) { @@ -222,9 +278,11 @@ int APS5_VABI sceNpMatching2AbortContextStart(std::uint16_t ctxId) { return matching2Initialized ? 0 : SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; } -int APS5_VABI sceNpMatching2CreateContextA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2CreateContextA(const NpMatching2CreateContextParameterA* 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_A_SIZE || !ctxId) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + return AllocateContext(ctxId, CONTEXT_A_FLAG); } int APS5_VABI sceNpMatching2CreateContextInternal(void) { @@ -257,9 +315,12 @@ int APS5_VABI sceNpMatching2GetMemoryInfo(void) { return 0; } -int APS5_VABI sceNpMatching2GetRoomJoinedSlotMaskLocal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2GetRoomJoinedSlotMaskLocal(std::uint16_t ctxId, std::uint64_t roomId, std::uint64_t* joinedSlotMask) { + std::lock_guard lock(matching2Mutex); + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + if (!joinedSlotMask) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + if (!contexts.contains(ctxId)) return SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND; + return roomId == 0 ? SCE_NP_MATCHING2_ERROR_INVALID_SERVER_ID : SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_STARTED; } int APS5_VABI sceNpMatching2GetRoomMemberDataExternalList(void) { @@ -292,9 +353,13 @@ int APS5_VABI sceNpMatching2GetUserInfoListA(void) { return 0; } -int APS5_VABI sceNpMatching2GetWorldIdArrayForAllServers(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2GetWorldIdArrayForAllServers(std::uint16_t ctxId, void* worldIds, std::uint64_t worldIdsNum, void* resultNum) { + (void)worldIdsNum; + std::lock_guard lock(matching2Mutex); + if (!matching2Initialized) return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; + if (!contexts.contains(ctxId)) return SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND; + if (!worldIds || !resultNum) return SCE_NP_MATCHING2_ERROR_INVALID_ARGUMENT; + return SCE_NP_MATCHING2_ERROR_SERVER_NOT_AVAILABLE; } int APS5_VABI sceNpMatching2GrantRoomOwner(void) { @@ -317,14 +382,18 @@ int APS5_VABI sceNpMatching2LeaveLobby(void) { return 0; } -int APS5_VABI sceNpMatching2RegisterLobbyEventCallback(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2RegisterLobbyEventCallback(std::uint16_t ctxId, void* callback, void* userdata) { + (void)callback; + (void)userdata; + std::lock_guard lock(matching2Mutex); + return CheckContext(ctxId); } -int APS5_VABI sceNpMatching2RegisterLobbyMessageCallback(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2RegisterLobbyMessageCallback(std::uint16_t ctxId, void* callback, void* userdata) { + (void)callback; + (void)userdata; + std::lock_guard lock(matching2Mutex); + return CheckContext(ctxId); } int APS5_VABI sceNpMatching2RegisterManualUdpSignalingCallback(void) { @@ -377,9 +446,11 @@ int APS5_VABI sceNpMatching2SignalingEstablishConnection(void) { return 0; } -int APS5_VABI sceNpMatching2SignalingGetConnectionInfoA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpMatching2SignalingGetConnectionInfoA(std::uint16_t ctxId, std::uint64_t roomId, std::uint16_t memberId, std::int32_t infoType, void* info) { + (void)roomId; + (void)memberId; + (void)infoType; + return ConnectionInfo(ctxId, info); } int APS5_VABI sceNpMatching2SignalingGetConnectionStatus(std::uint16_t ctxId, std::uint64_t roomId, std::uint16_t memberId, std::int32_t* connStatus, void* peerAddr, std::uint16_t* peerPort) { @@ -410,18 +481,20 @@ int APS5_VABI sceNpMatching2SignalingGetPeerNetInfoResult(void) { return 0; } -int APS5_VABI sceNpMatching2SignalingGetPingInfo(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpMatching2SignalingGetPingInfo(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId) { + (void)requestOpt; + return StartRequest(__func__, ctxId, request, requestId, Leading::RoomId); +} + +int APS5_VABI sceNpMatching2SignalingGetPort(std::uint16_t* port) { + std::lock_guard lock(matching2Mutex); + *port = signalingPort; return 0; } -int APS5_VABI sceNpMatching2SignalingGetPort(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpMatching2SignalingSetPort(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpMatching2SignalingSetPort(std::uint16_t port) { + std::lock_guard lock(matching2Mutex); + signalingPort = port; return 0; } diff --git a/core/libs/prx/libSceNpMatching2/include/Api.hpp b/core/libs/prx/libSceNpMatching2/include/Api.hpp index e83dba6539..af9f3bf46f 100644 --- a/core/libs/prx/libSceNpMatching2/include/Api.hpp +++ b/core/libs/prx/libSceNpMatching2/include/Api.hpp @@ -20,6 +20,12 @@ struct NpMatching2CreateContextParameter { std::uint64_t size; }; +struct NpMatching2CreateContextParameterA { + std::int32_t userId; + std::uint32_t serviceLabel; + std::uint64_t size; +}; + extern "C" { int APS5_VABI sceNpMatching2ContextStart(std::uint16_t ctxId, std::uint64_t timeout); @@ -28,15 +34,15 @@ int APS5_VABI sceNpMatching2CreateContext(const NpMatching2CreateContextParamete int APS5_VABI sceNpMatching2CreateJoinRoom(); int APS5_VABI sceNpMatching2DestroyContext(std::uint16_t ctxId); int APS5_VABI sceNpMatching2GetRoomDataExternalList(); -int APS5_VABI sceNpMatching2GetRoomDataInternal(); +int APS5_VABI sceNpMatching2GetRoomDataInternal(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId); int APS5_VABI sceNpMatching2GetRoomMemberDataInternal(); -int APS5_VABI sceNpMatching2GetServerId(); +int APS5_VABI sceNpMatching2GetServerId(std::uint16_t ctxId, std::uint16_t* serverId); int APS5_VABI sceNpMatching2GetUserInfoList(); -int APS5_VABI sceNpMatching2GetWorldInfoList(); +int APS5_VABI sceNpMatching2GetWorldInfoList(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId); int APS5_VABI sceNpMatching2Initialize(const NpMatching2InitializeParameter* param); int APS5_VABI sceNpMatching2JoinRoom(); int APS5_VABI sceNpMatching2KickoutRoomMember(); -int APS5_VABI sceNpMatching2LeaveRoom(); +int APS5_VABI sceNpMatching2LeaveRoom(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId); 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); @@ -45,35 +51,35 @@ 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 sceNpMatching2SetRoomDataExternal(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId); +int APS5_VABI sceNpMatching2SetRoomDataInternal(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId); int APS5_VABI sceNpMatching2SetRoomMemberDataInternal(); -int APS5_VABI sceNpMatching2SetUserInfo(); -int APS5_VABI sceNpMatching2SignalingGetConnectionInfo(); +int APS5_VABI sceNpMatching2SetUserInfo(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId); +int APS5_VABI sceNpMatching2SignalingGetConnectionInfo(std::uint16_t ctxId, std::uint64_t roomId, std::uint16_t memberId, std::int32_t infoType, void* info); int APS5_VABI sceNpMatching2Terminate(void); int APS5_VABI sceNpMatching2AbortContextStart(std::uint16_t ctxId); -int APS5_VABI sceNpMatching2CreateContextA(); +int APS5_VABI sceNpMatching2CreateContextA(const NpMatching2CreateContextParameterA* param, std::uint16_t* ctxId); int APS5_VABI sceNpMatching2CreateContextInternal(); int APS5_VABI sceNpMatching2CreateJoinRoomA(); int APS5_VABI sceNpMatching2GetLobbyInfoList(); int APS5_VABI sceNpMatching2GetLobbyMemberDataInternal(); int APS5_VABI sceNpMatching2GetLobbyMemberDataInternalList(); int APS5_VABI sceNpMatching2GetMemoryInfo(); -int APS5_VABI sceNpMatching2GetRoomJoinedSlotMaskLocal(); +int APS5_VABI sceNpMatching2GetRoomJoinedSlotMaskLocal(std::uint16_t ctxId, std::uint64_t roomId, std::uint64_t* joinedSlotMask); int APS5_VABI sceNpMatching2GetRoomMemberDataExternalList(); int APS5_VABI sceNpMatching2GetRoomMemberIdListLocal(); int APS5_VABI sceNpMatching2GetRoomPasswordLocal(); int APS5_VABI sceNpMatching2GetSignalingOptParamLocal(); int APS5_VABI sceNpMatching2GetSslMemoryInfo(); int APS5_VABI sceNpMatching2GetUserInfoListA(); -int APS5_VABI sceNpMatching2GetWorldIdArrayForAllServers(); +int APS5_VABI sceNpMatching2GetWorldIdArrayForAllServers(std::uint16_t ctxId, void* worldIds, std::uint64_t worldIdsNum, void* resultNum); int APS5_VABI sceNpMatching2GrantRoomOwner(); int APS5_VABI sceNpMatching2JoinLobby(); int APS5_VABI sceNpMatching2JoinRoomA(); int APS5_VABI sceNpMatching2LeaveLobby(); -int APS5_VABI sceNpMatching2RegisterLobbyEventCallback(); -int APS5_VABI sceNpMatching2RegisterLobbyMessageCallback(); +int APS5_VABI sceNpMatching2RegisterLobbyEventCallback(std::uint16_t ctxId, void* callback, void* userdata); +int APS5_VABI sceNpMatching2RegisterLobbyMessageCallback(std::uint16_t ctxId, void* callback, void* userdata); int APS5_VABI sceNpMatching2RegisterManualUdpSignalingCallback(); int APS5_VABI sceNpMatching2SendLobbyChatMessage(); int APS5_VABI sceNpMatching2SetExtraInitParam(); @@ -84,13 +90,13 @@ int APS5_VABI sceNpMatching2SignalingAbortConnection(); int APS5_VABI sceNpMatching2SignalingCancelPeerNetInfo(); int APS5_VABI sceNpMatching2SignalingEnableManualUdpMode(); int APS5_VABI sceNpMatching2SignalingEstablishConnection(); -int APS5_VABI sceNpMatching2SignalingGetConnectionInfoA(); +int APS5_VABI sceNpMatching2SignalingGetConnectionInfoA(std::uint16_t ctxId, std::uint64_t roomId, std::uint16_t memberId, std::int32_t infoType, void* info); int APS5_VABI sceNpMatching2SignalingGetConnectionStatus(std::uint16_t ctxId, std::uint64_t roomId, std::uint16_t memberId, std::int32_t* connStatus, void* peerAddr, std::uint16_t* peerPort); int APS5_VABI sceNpMatching2SignalingGetLocalNetInfo(); int APS5_VABI sceNpMatching2SignalingGetPeerNetInfo(); int APS5_VABI sceNpMatching2SignalingGetPeerNetInfoResult(); -int APS5_VABI sceNpMatching2SignalingGetPingInfo(); -int APS5_VABI sceNpMatching2SignalingGetPort(); -int APS5_VABI sceNpMatching2SignalingSetPort(); +int APS5_VABI sceNpMatching2SignalingGetPingInfo(std::uint16_t ctxId, const void* request, const void* requestOpt, std::uint32_t* requestId); +int APS5_VABI sceNpMatching2SignalingGetPort(std::uint16_t* port); +int APS5_VABI sceNpMatching2SignalingSetPort(std::uint16_t port); } diff --git a/core/libs/prx/libSceNpProfileDialog/Export.cpp b/core/libs/prx/libSceNpProfileDialog/Export.cpp index 0cb54f9a1a..523114eaf9 100644 --- a/core/libs/prx/libSceNpProfileDialog/Export.cpp +++ b/core/libs/prx/libSceNpProfileDialog/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libSceNpProfileDialog/include/Api.hpp" +#include #include #include #include @@ -16,6 +17,7 @@ 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_INVALID_ARGUMENT = static_cast(0x80B8000Au); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); constexpr int COMMON_DIALOG_RESULT_USER_CANCELED = 1; @@ -137,10 +139,14 @@ int APS5_VABI sceNpProfileDialogGetResult(SceNpProfileDialogResult* result) { 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; + SceNpProfileDialogResult out{}; + out.result = COMMON_DIALOG_OK; + out.userAction = COMMON_DIALOG_RESULT_USER_CANCELED; + out.userData = dialog.userData; + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(result, &out, sizeof(out)); + errno = savedErrno; + if (!copied) return COMMON_DIALOG_ERROR_INVALID_ARGUMENT; return COMMON_DIALOG_OK; } diff --git a/core/libs/prx/libSceNpScoreRanking/CMakeLists.txt b/core/libs/prx/libSceNpScoreRanking/CMakeLists.txt index d73264a441..22e5f023a1 100644 --- a/core/libs/prx/libSceNpScoreRanking/CMakeLists.txt +++ b/core/libs/prx/libSceNpScoreRanking/CMakeLists.txt @@ -5,7 +5,7 @@ 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) + target_link_libraries(guest_np_score_offline_tests PRIVATE libSceNpScoreRanking ${CMAKE_DL_LIBS}) 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) diff --git a/core/libs/prx/libSceNpScoreRanking/Export.cpp b/core/libs/prx/libSceNpScoreRanking/Export.cpp index c52736cc7a..c8caaa6c50 100644 --- a/core/libs/prx/libSceNpScoreRanking/Export.cpp +++ b/core/libs/prx/libSceNpScoreRanking/Export.cpp @@ -1,6 +1,8 @@ #include "prx/libSceNpScoreRanking/include/Api.hpp" #include #include +#include +#include #include #include #include @@ -19,13 +21,22 @@ static constexpr int SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT = static_cast 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 int RESOLVE_TIMEOUT_MIN = 1000000; +static constexpr int TRANSFER_TIMEOUT_MIN = 10000000; 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 RANK_DATA_A_SIZE = 0x90; +static constexpr std::uint64_t PLAYER_RANK_DATA_A_SIZE = 0x98; +static constexpr std::uint64_t ACCOUNT_ID_SIZE = 8; +static constexpr std::uint64_t NP_ID_PC_ID_SIZE = 44; +static constexpr std::uint64_t ACCOUNT_ID_PC_ID_SIZE = 16; static constexpr std::uint64_t COMMENT_SIZE = 64; +static constexpr std::size_t COMMENT_MAX_LENGTH = 255; +static constexpr std::uint32_t SAVE_DATA_OWNER_MODE_MAX = 3; 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; @@ -53,13 +64,45 @@ int CheckArrays(std::uint64_t arrayNum, const void* commentArray, std::uint64_t return 0; } -int Offline(int reqId) { +int AddTitleContext() { + 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 Finish(int reqId, int error) { 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; + if (!request->second.result) request->second.result = error; + return error; +} + +int Offline(int reqId) { + return Finish(reqId, SCE_NP_ERROR_SIGNED_OUT); +} + +bool KnownRequest(int reqId) { + std::lock_guard lock(scoreMutex); + return requests.contains(reqId); +} + +int CheckComment(int reqId, const char* comment, void* option) { + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!comment) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (!KnownRequest(reqId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + if (strnlen(comment, COMMENT_MAX_LENGTH + 1) > COMMENT_MAX_LENGTH) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return Offline(reqId); +} + +int RecordGameData(int reqId, const void* data, void* option) { + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (!data) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (!KnownRequest(reqId)) return SCE_NP_COMMUNITY_ERROR_INVALID_ID; + return Offline(reqId); } int RecordScore(int reqId, const NpScoreGameInfo* gameInfo, void* option) { @@ -68,12 +111,12 @@ int RecordScore(int reqId, const NpScoreGameInfo* gameInfo, void* option) { 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) { +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, std::uint64_t rankDataSize = RANK_DATA_SIZE, std::uint64_t otherRankDataSize = RANK_DATA_A_SIZE) { 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 (rankArraySize != arrayNum * rankDataSize && rankArraySize != arrayNum * otherRankDataSize) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; if (const int error = CheckArrays(arrayNum, commentArray, commentArraySize, infoArray, infoArraySize)) return error; return Offline(reqId); } @@ -87,9 +130,9 @@ int GetRankingByNpId(int reqId, const void* npIdArray, std::uint64_t npIdArraySi 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) { +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, std::uint64_t rankDataSize = RANK_DATA_SIZE) { if (!rankArray) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; - if (rankArraySize != arrayNum * RANK_DATA_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + if (rankArraySize != arrayNum * rankDataSize) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; std::uint32_t startSerialRank = 1; if (includeSelf == 0) { if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; @@ -103,6 +146,38 @@ int GetFriendsRanking(int reqId, int includeSelf, void* rankArray, std::uint64_t if (const int error = CheckArrays(arrayNum, commentArray, commentArraySize, infoArray, infoArraySize)) return error; return Offline(reqId); } + +int GetRankingByAccountId(int reqId, const void* accountIdArray, std::uint64_t accountIdArraySize, void* rankArray, std::uint64_t rankArraySize, void* commentArray, std::uint64_t commentArraySize, void* infoArray, std::uint64_t infoArraySize, std::uint64_t arrayNum, void* option, std::uint64_t idSize) { + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (arrayNum > MAX_NPID_PER_REQUEST) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + if (arrayNum == 0 || !rankArray || !accountIdArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (accountIdArraySize != arrayNum * idSize) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + if (rankArraySize != arrayNum * PLAYER_RANK_DATA_A_SIZE) return SCE_NP_COMMUNITY_ERROR_INVALID_ALIGNMENT; + if (const int error = CheckArrays(arrayNum, commentArray, commentArraySize, infoArray, infoArraySize)) return error; + return Offline(reqId); +} + +int GetRankingByNpIdPcId(int reqId, const void* idArray, std::uint64_t idArraySize, 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 (!idArray || arrayNum == 0 || !rankArray) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (arrayNum > MAX_NPID_PER_REQUEST) return SCE_NP_COMMUNITY_ERROR_TOO_MANY_NPID; + if (idArraySize != arrayNum * NP_ID_PC_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 GetGameData(int reqId, const void* npId, void* option) { + if (!npId) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (*static_cast(npId) == '\0') return Finish(reqId, SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT); + return Offline(reqId); +} + +int GetGameDataByAccountId(int reqId, std::uint64_t accountId, void* option) { + if (option) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (accountId == 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + return Offline(reqId); +} } extern "C" { @@ -118,11 +193,7 @@ int APS5_VABI sceNpScoreAbortRequest(int reqId) { 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; + return AddTitleContext(); } int APS5_VABI sceNpScoreCreateRequest(int titleCtxId) { @@ -222,183 +293,251 @@ int APS5_VABI sceNpScoreRecordScoreAsync(int reqId, std::uint32_t boardId, std:: return RecordScore(reqId, gameInfo, option); } -int APS5_VABI sceNpScoreCensorComment(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpScoreCensorComment(int reqId, const char* comment, void* option) { + return CheckComment(reqId, comment, option); +} + +int APS5_VABI sceNpScoreCensorCommentAsync(int reqId, const char* comment, void* option) { + return CheckComment(reqId, comment, option); +} + +int APS5_VABI sceNpScoreChangeModeForOtherSaveDataOwners(int id, std::uint32_t mode) { + if (mode > SAVE_DATA_OWNER_MODE_MAX) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(scoreMutex); + return titleContexts.contains(id) || requests.contains(id) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID; +} + +int APS5_VABI sceNpScoreCreateNpTitleCtxA(std::uint32_t serviceLabel, int selfId) { + (void)serviceLabel; + (void)selfId; + return SCE_NP_ERROR_SIGNED_OUT; +} + +int APS5_VABI sceNpScoreCreateTitleCtx(const void* titleId, const void* titleSecret, const void* selfNpId) { + std::fputs("sceNpScoreCreateTitleCtx can use only for sp-int, so you can't use this at product.\nPlease use sceNpScoreCreateNpTitleCtx().\nNP Service label is published at NP title's devnet thread.\n", stderr); + if (!titleId || !titleSecret || !selfNpId) return SCE_NP_COMMUNITY_ERROR_INSUFFICIENT_ARGUMENT; + return AddTitleContext(); +} + +int APS5_VABI sceNpScoreGetBoardInfoAsync(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("sceNpScoreGetBoardInfoAsync: the answer for a null board info depends on the title's SDK version"); + return Offline(reqId); +} + +int APS5_VABI sceNpScoreGetFriendsRankingA(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, RANK_DATA_A_SIZE); +} + +int APS5_VABI sceNpScoreGetFriendsRankingAAsync(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, RANK_DATA_A_SIZE); +} + +int APS5_VABI sceNpScoreGetFriendsRankingForCrossSave(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 sceNpScoreGetFriendsRankingForCrossSaveAsync(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 sceNpScoreGetGameData(int reqId, std::uint32_t boardId, const void* npId, std::uint64_t* totalSize, std::uint64_t recvSize, void* data, void* option) { + (void)boardId; + (void)totalSize; + (void)recvSize; + (void)data; + return GetGameData(reqId, npId, option); +} + +int APS5_VABI sceNpScoreGetGameDataAsync(int reqId, std::uint32_t boardId, const void* npId, std::uint64_t* totalSize, std::uint64_t recvSize, void* data, void* option) { + (void)boardId; + (void)totalSize; + (void)recvSize; + (void)data; + return GetGameData(reqId, npId, option); +} + +int APS5_VABI sceNpScoreGetGameDataByAccountId(int reqId, std::uint32_t boardId, std::uint64_t accountId, std::uint64_t* totalSize, std::uint64_t recvSize, void* data, void* option) { + (void)boardId; + (void)totalSize; + (void)recvSize; + (void)data; + return GetGameDataByAccountId(reqId, accountId, option); +} + +int APS5_VABI sceNpScoreGetGameDataByAccountIdAsync(int reqId, std::uint32_t boardId, std::uint64_t accountId, std::uint64_t* totalSize, std::uint64_t recvSize, void* data, void* option) { + (void)boardId; + (void)totalSize; + (void)recvSize; + (void)data; + return GetGameDataByAccountId(reqId, accountId, option); +} + +int APS5_VABI sceNpScoreGetRankingByAccountId(int reqId, std::uint32_t boardId, const std::uint64_t* accountIdArray, std::uint64_t accountIdArraySize, 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 GetRankingByAccountId(reqId, accountIdArray, accountIdArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option, ACCOUNT_ID_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByAccountIdAsync(int reqId, std::uint32_t boardId, const std::uint64_t* accountIdArray, std::uint64_t accountIdArraySize, 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 GetRankingByAccountId(reqId, accountIdArray, accountIdArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option, ACCOUNT_ID_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByAccountIdForCrossSave(int reqId, std::uint32_t boardId, const std::uint64_t* accountIdArray, std::uint64_t accountIdArraySize, 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 GetRankingByAccountId(reqId, accountIdArray, accountIdArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option, ACCOUNT_ID_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByAccountIdForCrossSaveAsync(int reqId, std::uint32_t boardId, const std::uint64_t* accountIdArray, std::uint64_t accountIdArraySize, 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 GetRankingByAccountId(reqId, accountIdArray, accountIdArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option, ACCOUNT_ID_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByAccountIdPcId(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 GetRankingByAccountId(reqId, idArray, idArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option, ACCOUNT_ID_PC_ID_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByAccountIdPcIdAsync(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 GetRankingByAccountId(reqId, idArray, idArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option, ACCOUNT_ID_PC_ID_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByAccountIdPcIdForCrossSave(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 GetRankingByAccountId(reqId, idArray, idArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option, ACCOUNT_ID_PC_ID_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 GetRankingByAccountId(reqId, idArray, idArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option, ACCOUNT_ID_PC_ID_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByNpIdPcId(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 GetRankingByNpIdPcId(reqId, idArray, idArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option); +} + +int APS5_VABI sceNpScoreGetRankingByNpIdPcIdAsync(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 GetRankingByNpIdPcId(reqId, idArray, idArraySize, rankArray, rankArraySize, commentArray, commentArraySize, infoArray, infoArraySize, arrayNum, option); +} + +int APS5_VABI sceNpScoreGetRankingByRangeA(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, RANK_DATA_A_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByRangeAAsync(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, RANK_DATA_A_SIZE); +} + +int APS5_VABI sceNpScoreGetRankingByRangeForCrossSave(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 sceNpScoreGetRankingByRangeForCrossSaveAsync(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 sceNpScoreRecordGameData(int reqId, std::uint32_t boardId, std::int64_t score, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, void* option) { + (void)boardId; + (void)score; + (void)totalSize; + (void)sendSize; + return RecordGameData(reqId, data, option); +} + +int APS5_VABI sceNpScoreRecordGameDataAsync(int reqId, std::uint32_t boardId, std::int64_t score, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, void* option) { + (void)boardId; + (void)score; + (void)totalSize; + (void)sendSize; + return RecordGameData(reqId, data, option); +} + +int APS5_VABI sceNpScoreSanitizeComment(int reqId, const char* comment, char* sanitizedComment, void* option) { + (void)sanitizedComment; + return CheckComment(reqId, comment, option); +} + +int APS5_VABI sceNpScoreSanitizeCommentAsync(int reqId, const char* comment, char* sanitizedComment, void* option) { + (void)sanitizedComment; + return CheckComment(reqId, comment, option); +} + +int APS5_VABI sceNpScoreSetPlayerCharacterId(int ctxId, int pcId) { + if (pcId < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(scoreMutex); + return titleContexts.contains(ctxId) || requests.contains(ctxId) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID; +} + +int APS5_VABI sceNpScoreSetThreadParam(int threadPriority, std::uint64_t cpuAffinityMask) { + (void)threadPriority; + (void)cpuAffinityMask; return 0; } -int APS5_VABI sceNpScoreCensorCommentAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpScoreSetTimeout(int id, int resolveRetry, int resolveTimeout, int connTimeout, int sendTimeout, int recvTimeout) { + if (resolveRetry < 0) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (resolveTimeout != 0 && resolveTimeout < RESOLVE_TIMEOUT_MIN) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (connTimeout != 0 && connTimeout < TRANSFER_TIMEOUT_MIN) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (recvTimeout != 0 && recvTimeout < TRANSFER_TIMEOUT_MIN) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + if (sendTimeout != 0 && sendTimeout < TRANSFER_TIMEOUT_MIN) return SCE_NP_COMMUNITY_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(scoreMutex); + return titleContexts.contains(id) || requests.contains(id) ? 0 : SCE_NP_COMMUNITY_ERROR_INVALID_ID; } -int APS5_VABI sceNpScoreChangeModeForOtherSaveDataOwners(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreCreateNpTitleCtxA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreCreateTitleCtx(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetBoardInfoAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetFriendsRankingA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetFriendsRankingAAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetFriendsRankingForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetFriendsRankingForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetGameData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetGameDataAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetGameDataByAccountId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetGameDataByAccountIdAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByAccountId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByAccountIdAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByAccountIdForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByAccountIdForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByAccountIdPcId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByAccountIdPcIdAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByAccountIdPcIdForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByNpIdPcId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByNpIdPcIdAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByRangeA(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByRangeAAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByRangeForCrossSave(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreGetRankingByRangeForCrossSaveAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreRecordGameData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreRecordGameDataAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreSanitizeComment(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreSanitizeCommentAsync(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreSetPlayerCharacterId(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreSetThreadParam(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreSetTimeout(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNpScoreWaitAsync(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceNpScoreWaitAsync(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) NotImplemented_nid_no_patch("sceNpScoreWaitAsync (a request with no operation started)"); + if (result) *result = *request->second.result; return 0; } diff --git a/core/libs/prx/libSceNpScoreRanking/include/Api.hpp b/core/libs/prx/libSceNpScoreRanking/include/Api.hpp index 91e525eab3..074f26ade3 100644 --- a/core/libs/prx/libSceNpScoreRanking/include/Api.hpp +++ b/core/libs/prx/libSceNpScoreRanking/include/Api.hpp @@ -32,41 +32,41 @@ 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); -int APS5_VABI sceNpScoreCensorComment(); -int APS5_VABI sceNpScoreCensorCommentAsync(); -int APS5_VABI sceNpScoreChangeModeForOtherSaveDataOwners(); -int APS5_VABI sceNpScoreCreateNpTitleCtxA(); -int APS5_VABI sceNpScoreCreateTitleCtx(); -int APS5_VABI sceNpScoreGetBoardInfoAsync(); -int APS5_VABI sceNpScoreGetFriendsRankingA(); -int APS5_VABI sceNpScoreGetFriendsRankingAAsync(); -int APS5_VABI sceNpScoreGetFriendsRankingForCrossSave(); -int APS5_VABI sceNpScoreGetFriendsRankingForCrossSaveAsync(); -int APS5_VABI sceNpScoreGetGameData(); -int APS5_VABI sceNpScoreGetGameDataAsync(); -int APS5_VABI sceNpScoreGetGameDataByAccountId(); -int APS5_VABI sceNpScoreGetGameDataByAccountIdAsync(); -int APS5_VABI sceNpScoreGetRankingByAccountId(); -int APS5_VABI sceNpScoreGetRankingByAccountIdAsync(); -int APS5_VABI sceNpScoreGetRankingByAccountIdForCrossSave(); -int APS5_VABI sceNpScoreGetRankingByAccountIdForCrossSaveAsync(); -int APS5_VABI sceNpScoreGetRankingByAccountIdPcId(); -int APS5_VABI sceNpScoreGetRankingByAccountIdPcIdAsync(); -int APS5_VABI sceNpScoreGetRankingByAccountIdPcIdForCrossSave(); -int APS5_VABI sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync(); -int APS5_VABI sceNpScoreGetRankingByNpIdPcId(); -int APS5_VABI sceNpScoreGetRankingByNpIdPcIdAsync(); -int APS5_VABI sceNpScoreGetRankingByRangeA(); -int APS5_VABI sceNpScoreGetRankingByRangeAAsync(); -int APS5_VABI sceNpScoreGetRankingByRangeForCrossSave(); -int APS5_VABI sceNpScoreGetRankingByRangeForCrossSaveAsync(); -int APS5_VABI sceNpScoreRecordGameData(); -int APS5_VABI sceNpScoreRecordGameDataAsync(); -int APS5_VABI sceNpScoreSanitizeComment(); -int APS5_VABI sceNpScoreSanitizeCommentAsync(); -int APS5_VABI sceNpScoreSetPlayerCharacterId(); -int APS5_VABI sceNpScoreSetThreadParam(); -int APS5_VABI sceNpScoreSetTimeout(); -int APS5_VABI sceNpScoreWaitAsync(); +int APS5_VABI sceNpScoreCensorComment(int reqId, const char* comment, void* option); +int APS5_VABI sceNpScoreCensorCommentAsync(int reqId, const char* comment, void* option); +int APS5_VABI sceNpScoreChangeModeForOtherSaveDataOwners(int id, std::uint32_t mode); +int APS5_VABI sceNpScoreCreateNpTitleCtxA(std::uint32_t serviceLabel, int selfId); +int APS5_VABI sceNpScoreCreateTitleCtx(const void* titleId, const void* titleSecret, const void* selfNpId); +int APS5_VABI sceNpScoreGetBoardInfoAsync(int reqId, std::uint32_t boardId, void* boardInfo, void* option); +int APS5_VABI sceNpScoreGetFriendsRankingA(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 sceNpScoreGetFriendsRankingAAsync(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 sceNpScoreGetFriendsRankingForCrossSave(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 sceNpScoreGetFriendsRankingForCrossSaveAsync(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 sceNpScoreGetGameData(int reqId, std::uint32_t boardId, const void* npId, std::uint64_t* totalSize, std::uint64_t recvSize, void* data, void* option); +int APS5_VABI sceNpScoreGetGameDataAsync(int reqId, std::uint32_t boardId, const void* npId, std::uint64_t* totalSize, std::uint64_t recvSize, void* data, void* option); +int APS5_VABI sceNpScoreGetGameDataByAccountId(int reqId, std::uint32_t boardId, std::uint64_t accountId, std::uint64_t* totalSize, std::uint64_t recvSize, void* data, void* option); +int APS5_VABI sceNpScoreGetGameDataByAccountIdAsync(int reqId, std::uint32_t boardId, std::uint64_t accountId, std::uint64_t* totalSize, std::uint64_t recvSize, void* data, void* option); +int APS5_VABI sceNpScoreGetRankingByAccountId(int reqId, std::uint32_t boardId, const std::uint64_t* accountIdArray, std::uint64_t accountIdArraySize, 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 sceNpScoreGetRankingByAccountIdAsync(int reqId, std::uint32_t boardId, const std::uint64_t* accountIdArray, std::uint64_t accountIdArraySize, 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 sceNpScoreGetRankingByAccountIdForCrossSave(int reqId, std::uint32_t boardId, const std::uint64_t* accountIdArray, std::uint64_t accountIdArraySize, 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 sceNpScoreGetRankingByAccountIdForCrossSaveAsync(int reqId, std::uint32_t boardId, const std::uint64_t* accountIdArray, std::uint64_t accountIdArraySize, 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 sceNpScoreGetRankingByAccountIdPcId(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 sceNpScoreGetRankingByAccountIdPcIdAsync(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 sceNpScoreGetRankingByAccountIdPcIdForCrossSave(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 sceNpScoreGetRankingByNpIdPcId(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 sceNpScoreGetRankingByNpIdPcIdAsync(int reqId, std::uint32_t boardId, const void* idArray, std::uint64_t idArraySize, 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 sceNpScoreGetRankingByRangeA(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 sceNpScoreGetRankingByRangeAAsync(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 sceNpScoreGetRankingByRangeForCrossSave(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 sceNpScoreGetRankingByRangeForCrossSaveAsync(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 sceNpScoreRecordGameData(int reqId, std::uint32_t boardId, std::int64_t score, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, void* option); +int APS5_VABI sceNpScoreRecordGameDataAsync(int reqId, std::uint32_t boardId, std::int64_t score, std::uint64_t totalSize, std::uint64_t sendSize, const void* data, void* option); +int APS5_VABI sceNpScoreSanitizeComment(int reqId, const char* comment, char* sanitizedComment, void* option); +int APS5_VABI sceNpScoreSanitizeCommentAsync(int reqId, const char* comment, char* sanitizedComment, void* option); +int APS5_VABI sceNpScoreSetPlayerCharacterId(int ctxId, int pcId); +int APS5_VABI sceNpScoreSetThreadParam(int threadPriority, std::uint64_t cpuAffinityMask); +int APS5_VABI sceNpScoreSetTimeout(int id, int resolveRetry, int resolveTimeout, int connTimeout, int sendTimeout, int recvTimeout); +int APS5_VABI sceNpScoreWaitAsync(int reqId, int* result); } diff --git a/core/libs/prx/libSceNpTcs/Export.cpp b/core/libs/prx/libSceNpTcs/Export.cpp index 78dee5e9ae..42025891f3 100644 --- a/core/libs/prx/libSceNpTcs/Export.cpp +++ b/core/libs/prx/libSceNpTcs/Export.cpp @@ -27,44 +27,36 @@ int APS5_VABI sceNpTcsIntUploadDataToNewSlotBegin() { extern "C" { -int APS5_VABI npTcsLegacyUnknown__minus_Bbx0u2xR4Q_nid_no_patch() { +int APS5_VABI sceNpTcsIntUploadDataToNewSlot() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("-Bbx0u2xR4Q", npTcsLegacyUnknown__minus_Bbx0u2xR4Q_nid_no_patch); -int APS5_VABI npTcsLegacyUnknown_3ruFwEv4KQg_nid_no_patch() { +int APS5_VABI sceNpTcsIntCreateContext() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("3ruFwEv4KQg", npTcsLegacyUnknown_3ruFwEv4KQg_nid_no_patch); -int APS5_VABI npTcsLegacyUnknown_Mlt9z_minus_KrFc4_nid_no_patch() { +int APS5_VABI sceNpTcsIntUploadDataToNewSlotEnd() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Mlt9z-KrFc4", npTcsLegacyUnknown_Mlt9z_minus_KrFc4_nid_no_patch); -int APS5_VABI npTcsLegacyUnknown_ORvFvy4FTvs_nid_no_patch() { +int APS5_VABI sceNpTcsIntDestroyContext() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("ORvFvy4FTvs", npTcsLegacyUnknown_ORvFvy4FTvs_nid_no_patch); -int APS5_VABI npTcsLegacyUnknown_Qx4CWBw5quY_nid_no_patch() { +int APS5_VABI sceNpTcsIntAbortRequest() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Qx4CWBw5quY", npTcsLegacyUnknown_Qx4CWBw5quY_nid_no_patch); -int APS5_VABI npTcsLegacyUnknown_YTUqdMJrU6Q_nid_no_patch() { +int APS5_VABI sceNpTcsIntDestroyRequest() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("YTUqdMJrU6Q", npTcsLegacyUnknown_YTUqdMJrU6Q_nid_no_patch); -int APS5_VABI npTcsLegacyUnknown_ik_minus_uhY6YvWk_nid_no_patch() { +int APS5_VABI sceNpTcsIntUploadDataToNewSlotWrite() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("ik-uhY6YvWk", npTcsLegacyUnknown_ik_minus_uhY6YvWk_nid_no_patch); -int APS5_VABI npTcsLegacyUnknown_xSse4lLrSNA_nid_no_patch() { +int APS5_VABI sceNpTcsIntCreateRequest() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("xSse4lLrSNA", npTcsLegacyUnknown_xSse4lLrSNA_nid_no_patch); } diff --git a/core/libs/prx/libSceNpTcs/include/LegacyExports.hpp b/core/libs/prx/libSceNpTcs/include/LegacyExports.hpp index 0238e33d45..6f50dce0f6 100644 --- a/core/libs/prx/libSceNpTcs/include/LegacyExports.hpp +++ b/core/libs/prx/libSceNpTcs/include/LegacyExports.hpp @@ -4,13 +4,13 @@ extern "C" { -int APS5_VABI npTcsLegacyUnknown__minus_Bbx0u2xR4Q_nid_no_patch(); -int APS5_VABI npTcsLegacyUnknown_3ruFwEv4KQg_nid_no_patch(); -int APS5_VABI npTcsLegacyUnknown_Mlt9z_minus_KrFc4_nid_no_patch(); -int APS5_VABI npTcsLegacyUnknown_ORvFvy4FTvs_nid_no_patch(); -int APS5_VABI npTcsLegacyUnknown_Qx4CWBw5quY_nid_no_patch(); -int APS5_VABI npTcsLegacyUnknown_YTUqdMJrU6Q_nid_no_patch(); -int APS5_VABI npTcsLegacyUnknown_ik_minus_uhY6YvWk_nid_no_patch(); -int APS5_VABI npTcsLegacyUnknown_xSse4lLrSNA_nid_no_patch(); +int APS5_VABI sceNpTcsIntUploadDataToNewSlot(); +int APS5_VABI sceNpTcsIntCreateContext(); +int APS5_VABI sceNpTcsIntUploadDataToNewSlotEnd(); +int APS5_VABI sceNpTcsIntDestroyContext(); +int APS5_VABI sceNpTcsIntAbortRequest(); +int APS5_VABI sceNpTcsIntDestroyRequest(); +int APS5_VABI sceNpTcsIntUploadDataToNewSlotWrite(); +int APS5_VABI sceNpTcsIntCreateRequest(); } diff --git a/core/libs/prx/libSceNpTrophy/CMakeLists.txt b/core/libs/prx/libSceNpTrophy/CMakeLists.txt index 279849ac16..896b55a5a7 100644 --- a/core/libs/prx/libSceNpTrophy/CMakeLists.txt +++ b/core/libs/prx/libSceNpTrophy/CMakeLists.txt @@ -34,3 +34,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_np_trophy_helpers_tests ${LIBS_INCLUDE_DIR}/tests/GuestNpTrophyHelpers.cpp) + target_include_directories(guest_np_trophy_helpers_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_np_trophy_helpers_tests PRIVATE libSceNpTrophy ${CMAKE_DL_LIBS}) + configure_windows_unwind(guest_np_trophy_helpers_tests) + add_test(NAME guest_np_trophy_helpers COMMAND guest_np_trophy_helpers_tests) + set_tests_properties(guest_np_trophy_helpers PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_np_trophy_helpers 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 index 3322573f4d..06adee50e3 100644 --- a/core/libs/prx/libSceNpTrophy/Export.cpp +++ b/core/libs/prx/libSceNpTrophy/Export.cpp @@ -3,6 +3,7 @@ #include #include "prx/libSceNpTrophy/include/Api.hpp" #include +#include #include #include #include @@ -87,18 +88,25 @@ std::uint64_t BigEndian64(const std::uint8_t* bytes) { } std::optional> ReadPackage(const std::filesystem::path& path) { - std::ifstream file(path, std::ios::binary); + std::ifstream file(path, std::ios::binary | std::ios::ate); if (!file) return std::nullopt; + const auto end = file.tellg(); + if (end < static_cast(TrpHeaderBytes)) return std::map{}; + const auto fileSize = static_cast(end); + file.seekg(0); 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{}; + if (count > (fileSize - TrpHeaderBytes) / 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)}; + const TrpEntry contents{BigEndian64(entry.data() + 32), BigEndian64(entry.data() + 40)}; + if (contents.offset > fileSize || contents.size > fileSize - contents.offset) return std::map{}; + entries[name] = contents; } return entries; } @@ -419,14 +427,13 @@ int APS5_VABI sceNpTrophyConfigGetTrophyTitleDetails(void) { return 0; } -int APS5_VABI sceNpTrophyConfigHasGroupFeature(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTrophyConfigHasGroupFeature(const std::uint8_t* config) { + if (!config) return 0; + return config[0x90] & 1; } -int APS5_VABI sceNpTrophyGroupArrayGetNum(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTrophyGroupArrayGetNum(const std::uint32_t* groupArray) { + return std::popcount(groupArray[0]); } int APS5_VABI sceNpTrophyIntAbortHandle(void) { @@ -484,19 +491,23 @@ int APS5_VABI sceNpTrophyIntNetSyncTitles(void) { return 0; } -int APS5_VABI sceNpTrophyNumInfoGetTotal(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTrophyNumInfoGetTotal(const std::uint32_t* numInfo) { + return static_cast(numInfo[0] + numInfo[1] + numInfo[2] + numInfo[3]); } -int APS5_VABI sceNpTrophySetInfoGetTrophyFlagArray(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTrophySetInfoGetTrophyFlagArray(const std::uint8_t* setInfo, std::uint8_t* flagArray) { + int count = 0; + for (std::size_t index = 0; index < 16; ++index) { + flagArray[index] = setInfo[index] | setInfo[16 + index] | setInfo[32 + index] | setInfo[48 + index]; + count += std::popcount(flagArray[index]); + } + return count; } -int APS5_VABI sceNpTrophySetInfoGetTrophyNum(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpTrophySetInfoGetTrophyNum(const std::uint8_t* setInfo) { + int count = 0; + for (std::size_t index = 0; index < 64; ++index) count += std::popcount(setInfo[index]); + return count; } int APS5_VABI sceNpTrophyShowTrophyList(void) { diff --git a/core/libs/prx/libSceNpTrophy/include/Api.hpp b/core/libs/prx/libSceNpTrophy/include/Api.hpp index 698d7d98db..487ce31811 100644 --- a/core/libs/prx/libSceNpTrophy/include/Api.hpp +++ b/core/libs/prx/libSceNpTrophy/include/Api.hpp @@ -99,8 +99,8 @@ int APS5_VABI sceNpTrophyConfigGetTrophySetInfo(); int APS5_VABI sceNpTrophyConfigGetTrophySetInfoInGroup(); int APS5_VABI sceNpTrophyConfigGetTrophySetVersion(); int APS5_VABI sceNpTrophyConfigGetTrophyTitleDetails(); -int APS5_VABI sceNpTrophyConfigHasGroupFeature(); -int APS5_VABI sceNpTrophyGroupArrayGetNum(); +int APS5_VABI sceNpTrophyConfigHasGroupFeature(const std::uint8_t* config); +int APS5_VABI sceNpTrophyGroupArrayGetNum(const std::uint32_t* groupArray); int APS5_VABI sceNpTrophyIntAbortHandle(); int APS5_VABI sceNpTrophyIntCheckNetSyncTitles(); int APS5_VABI sceNpTrophyIntCreateHandle(); @@ -112,9 +112,9 @@ int APS5_VABI sceNpTrophyIntGetRunningTitles(); int APS5_VABI sceNpTrophyIntGetTrpIconByUri(); int APS5_VABI sceNpTrophyIntNetSyncTitle(); int APS5_VABI sceNpTrophyIntNetSyncTitles(); -int APS5_VABI sceNpTrophyNumInfoGetTotal(); -int APS5_VABI sceNpTrophySetInfoGetTrophyFlagArray(); -int APS5_VABI sceNpTrophySetInfoGetTrophyNum(); +int APS5_VABI sceNpTrophyNumInfoGetTotal(const std::uint32_t* numInfo); +int APS5_VABI sceNpTrophySetInfoGetTrophyFlagArray(const std::uint8_t* setInfo, std::uint8_t* flagArray); +int APS5_VABI sceNpTrophySetInfoGetTrophyNum(const std::uint8_t* setInfo); int APS5_VABI sceNpTrophyShowTrophyList(); int APS5_VABI sceNpTrophySystemAbortHandle(); int APS5_VABI sceNpTrophySystemBuildGroupIconUri(); diff --git a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt index 7f9d3efe3a..d4ea301145 100644 --- a/core/libs/prx/libSceNpTrophy2/CMakeLists.txt +++ b/core/libs/prx/libSceNpTrophy2/CMakeLists.txt @@ -13,7 +13,7 @@ add_library(libSceNpTrophy2 SHARED EXCLUDE_FROM_ALL target_include_directories(libSceNpTrophy2 PRIVATE ${LIBS_INCLUDE_DIR} ) -target_link_libraries(libSceNpTrophy2 PRIVATE libc) +target_link_libraries(libSceNpTrophy2 PRIVATE libc system_service_host_locale) configure_windows_unwind(libSceNpTrophy2) if(BUILD_TESTING) @@ -21,7 +21,7 @@ if(BUILD_TESTING) target_include_directories(guest_nptrophy2_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_nptrophy2_tests PRIVATE libSceNpTrophy2 libc) configure_windows_unwind(guest_nptrophy2_tests) - add_test(NAME guest_nptrophy2 COMMAND guest_nptrophy2_tests) + add_test(NAME guest_nptrophy2 COMMAND guest_nptrophy2_tests $) if(WIN32) set_tests_properties(guest_nptrophy2 PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceNpTrophy2/include/TrophyPackage.hpp b/core/libs/prx/libSceNpTrophy2/include/TrophyPackage.hpp index f493e67ea1..d2e011b28d 100644 --- a/core/libs/prx/libSceNpTrophy2/include/TrophyPackage.hpp +++ b/core/libs/prx/libSceNpTrophy2/include/TrophyPackage.hpp @@ -25,6 +25,8 @@ using UcpFiles = std::map>; UcpFiles ReadUcp(std::string_view data); +struct JsonMember; + class Json { public: enum class Kind { Null, Bool, Number, String, Array, Object }; @@ -33,7 +35,7 @@ public: bool boolean = false; std::string text; std::vector items; - std::vector> members; + std::vector members; const Json* Find(std::string_view key) const; bool IsTrue() const; @@ -41,6 +43,11 @@ public: std::optional Id() const; }; +struct JsonMember { + std::string first; + Json second; +}; + Json ParseJson(std::string_view text); inline constexpr std::int32_t GradePlatinum = 1; diff --git a/core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp b/core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp index 5091aad105..f3876bd2a5 100644 --- a/core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp +++ b/core/libs/prx/libSceNpTrophy2/src/TrophyStore.cpp @@ -243,6 +243,10 @@ bool RecordUnlock(std::int32_t userId, std::uint32_t serviceLabel, std::int32_t } extern "C" bool NpTrophy2RecordUnlock_nid_no_patch(std::int32_t userId, std::uint32_t serviceLabel, std::int32_t trophyId) { + if (trophyId < 0) { + std::fprintf(stderr, "[trophy] ignored the unlock of trophy %d for user %d: trophy ids are not negative\n", trophyId, userId); + return false; + } if (const auto* package = NpTrophy2::Package(serviceLabel)) { if (const auto refusal = NpTrophy2::PostedUnlockRefusal(*package, trophyId)) { std::fprintf(stderr, "[trophy] ignored the posted unlock of trophy %d for user %d: %s\n", trophyId, userId, refusal->c_str()); @@ -267,6 +271,5 @@ extern "C" void NpTrophy2RemoveUnlockListener_nid_no_patch(std::uint64_t id) { auto& registry = NpTrophy2::listeners(); std::lock_guard dispatching(registry.dispatch); std::lock_guard lock(registry.mutex); - const auto removed = std::erase_if(registry.entries, [id](const NpTrophy2::Listener& entry) { return entry.id == id; }); - if (removed == 0) throw std::invalid_argument("no trophy unlock listener has id " + std::to_string(id)); + std::erase_if(registry.entries, [id](const NpTrophy2::Listener& entry) { return entry.id == id; }); } diff --git a/core/libs/prx/libSceNpTrophy2/tests/TrophyPackage.cpp b/core/libs/prx/libSceNpTrophy2/tests/TrophyPackage.cpp index 0e7b0e4364..122c2d7195 100644 --- a/core/libs/prx/libSceNpTrophy2/tests/TrophyPackage.cpp +++ b/core/libs/prx/libSceNpTrophy2/tests/TrophyPackage.cpp @@ -1,6 +1,7 @@ #include "prx/libSceNpTrophy2/include/TitleTrophies.hpp" #include "prx/libSceNpTrophy2/include/TrophyPackage.hpp" #include "prx/libSceNpTrophy2/include/TrophyStore.hpp" +#include "prx/libSceSystemService/LanguageLocale.hpp" #include #include @@ -272,6 +273,10 @@ void testPackage() { "a tropconf.json of more than 1048576 values was read"); check(contains(failure([] { NpTrophy2::ParseTrophyPackage("not a package", "en-US"); }), "fewer than the 64-byte header"), "a package that is not a container was read"); check(NpTrophy2::LanguageTag(1) == "en-US" && NpTrophy2::LanguageTag(0) == "ja-JP" && NpTrophy2::LanguageTag(2) == "fr-FR" && NpTrophy2::LanguageTag(29) == "id-ID", "language tags are wrong"); + for (int language = 0; language <= 30; ++language) { + const auto match = SystemServiceLocale::MatchLocale(NpTrophy2::LanguageTag(language)); + check(match.recognised && match.id == language, "the trophy tag of a console language does not map back to it"); + } bool thrown = false; try { NpTrophy2::LanguageTag(31); diff --git a/core/libs/prx/libSceNpTrophy2/tests/TrophyStore.cpp b/core/libs/prx/libSceNpTrophy2/tests/TrophyStore.cpp index 67651be3da..7e4ff8b3d8 100644 --- a/core/libs/prx/libSceNpTrophy2/tests/TrophyStore.cpp +++ b/core/libs/prx/libSceNpTrophy2/tests/TrophyStore.cpp @@ -217,6 +217,8 @@ void testQueries() { check(NpTrophy2RecordUnlock_nid_no_patch(InitialUser, 0, 12), "trophy 12 was not recorded"); check(!NpTrophy2RecordUnlock_nid_no_patch(InitialUser, 0, 12), "trophy 12 was recorded twice"); check(NpTrophy2RecordUnlock_nid_no_patch(InitialUser, 0, 0), "trophy 0 was not recorded"); + check(!throws([] { NpTrophy2RecordUnlock_nid_no_patch(InitialUser, 0, -1); }), "a negative trophy id threw across the C ABI"); + check(!NpTrophy2RecordUnlock_nid_no_patch(InitialUser, 0, -1), "a negative trophy id was recorded"); const auto after = NpTrophy2::CurrentTick(); NpTrophy2RemoveUnlockListener_nid_no_patch(listener); check(NpTrophy2RecordUnlock_nid_no_patch(InitialUser, 0, 5) && seen.unlocks.size() == 2, "the listener did not see each new unlock once"); @@ -236,7 +238,7 @@ void testQueries() { check(!trophyData(other, 12).unlocked && unlockedTrophies(other) == 0, "another user sees the unlocks"); check(read("_trophy/268435456_0.txt").find("12 ") != std::string::npos, "the unlock was not stored in the title folder"); check(throws([] { NpTrophy2AddUnlockListener_nid_no_patch(nullptr, nullptr); }), "a null listener was added"); - check(throws([listener] { NpTrophy2RemoveUnlockListener_nid_no_patch(listener); }), "a removed listener was removed again"); + check(!throws([listener] { NpTrophy2RemoveUnlockListener_nid_no_patch(listener); }), "removing a listener twice threw across the C ABI"); check(sceNpTrophy2DestroyContext(context) == 0 && sceNpTrophy2DestroyContext(other) == 0, "contexts were not destroyed"); } diff --git a/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt b/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt index b74a51652b..a0408bae0b 100644 --- a/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt +++ b/core/libs/prx/libSceNpUniversalDataSystem/CMakeLists.txt @@ -50,7 +50,7 @@ if(BUILD_TESTING) target_include_directories(guest_np_trophy_data_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_np_trophy_data_tests PRIVATE libSceNpUniversalDataSystem libSceNpTrophy2 libc) configure_windows_unwind(guest_np_trophy_data_tests) - add_test(NAME guest_np_trophy_data COMMAND guest_np_trophy_data_tests) + add_test(NAME guest_np_trophy_data COMMAND guest_np_trophy_data_tests $) if(WIN32) set_tests_properties(guest_np_trophy_data PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") diff --git a/core/libs/prx/libSceNpWebApi/Export.cpp b/core/libs/prx/libSceNpWebApi/Export.cpp index 4f9b31e73c..2d3f323977 100644 --- a/core/libs/prx/libSceNpWebApi/Export.cpp +++ b/core/libs/prx/libSceNpWebApi/Export.cpp @@ -29,6 +29,9 @@ static constexpr int SCE_NP_WEBAPI_ERROR_SERVICE_PUSH_EVENT_CALLBACK_NOT_FOUND = 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_HTTP_ERROR_INVALID_ID = static_cast(0x80431100); +static constexpr int SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE = static_cast(0x80432060); + static constexpr int SCE_NP_WEBAPI_HTTP_METHOD_DELETE = 3; static constexpr int SCE_NP_WEBAPI_HTTP_METHOD_PATCH = 4; static constexpr std::uint32_t SCE_NP_INVALID_SERVICE_LABEL = 0xFFFFFFFF; @@ -240,14 +243,20 @@ int APS5_VABI sceNpWebApiDeleteServicePushEventFilter(int libCtxId, int filterId 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 sceNpWebApiGetHttpResponseHeaderValue(std::int64_t requestId, const char* fieldName, char* value, std::size_t valueSize) { + if (!fieldName || !value || valueSize == 0) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(contextsMutex); + const auto lookup = findRequest(requestId); + if (lookup.error != 0) return lookup.error; + return SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE; } -int APS5_VABI sceNpWebApiGetHttpResponseHeaderValueLength() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceNpWebApiGetHttpResponseHeaderValueLength(std::int64_t requestId, const char* fieldName, std::size_t* valueLength) { + if (!fieldName || !valueLength) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + std::lock_guard lock(contextsMutex); + const auto lookup = findRequest(requestId); + if (lookup.error != 0) return lookup.error; + return SCE_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE; } int APS5_VABI sceNpWebApiGetHttpStatusCode(std::int64_t requestId, int* statusCode) { @@ -267,8 +276,12 @@ int APS5_VABI sceNpWebApiInitialize(int libHttpCtxId, std::size_t poolSize) { } int APS5_VABI sceNpWebApiReadData(std::int64_t requestId, void* data, std::size_t size) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (!data || size == 0) return SCE_NP_WEBAPI_ERROR_INVALID_ARGUMENT; + 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_HTTP_ERROR_INVALID_ID; } int APS5_VABI sceNpWebApiRegisterPushEventCallback(int titleUserCtxId, int filterId, void* callback, void* userArg) { diff --git a/core/libs/prx/libSceNpWebApi/include/Api.hpp b/core/libs/prx/libSceNpWebApi/include/Api.hpp index e8b44ee222..c58f217e65 100644 --- a/core/libs/prx/libSceNpWebApi/include/Api.hpp +++ b/core/libs/prx/libSceNpWebApi/include/Api.hpp @@ -41,8 +41,8 @@ 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 sceNpWebApiGetHttpResponseHeaderValue(std::int64_t requestId, const char* fieldName, char* value, std::size_t valueSize); +int APS5_VABI sceNpWebApiGetHttpResponseHeaderValueLength(std::int64_t requestId, const char* fieldName, std::size_t* valueLength); int APS5_VABI sceNpWebApiGetHttpStatusCode(std::int64_t requestId, int* statusCode); int APS5_VABI sceNpWebApiInitialize(int libHttpCtxId, std::size_t poolSize); int APS5_VABI sceNpWebApiReadData(std::int64_t requestId, void* data, std::size_t size); diff --git a/core/libs/prx/libSceNpWebApi2/Export.cpp b/core/libs/prx/libSceNpWebApi2/Export.cpp index 1ede183689..3f8d5528e1 100644 --- a/core/libs/prx/libSceNpWebApi2/Export.cpp +++ b/core/libs/prx/libSceNpWebApi2/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceNpWebApi2/include/InventoryExports.hpp" #include "prx/libSceNpWebApi2/include/Api.hpp" #include #include @@ -42,6 +43,15 @@ static std::unordered_map pushHandles; extern "C" { +int APS5_VABI sceNpWebApi2IntGlobalPush2Initialize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceNpWebApi2IntGlobalPush2Terminate() { + NotImplemented_nid_no_patch(__func__); +} + + int APS5_VABI sceNpWebApi2AbortRequest(int64_t request_id) { (void)request_id; return 0; diff --git a/core/libs/prx/libSceNpWebApi2/include/InventoryExports.hpp b/core/libs/prx/libSceNpWebApi2/include/InventoryExports.hpp new file mode 100644 index 0000000000..e6e12dc193 --- /dev/null +++ b/core/libs/prx/libSceNpWebApi2/include/InventoryExports.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceNpWebApi2IntGlobalPush2Initialize(); +int APS5_VABI sceNpWebApi2IntGlobalPush2Terminate(); + +} diff --git a/core/libs/prx/libSceNpWebApi2/libSceNpWebApi2.hpp b/core/libs/prx/libSceNpWebApi2/libSceNpWebApi2.hpp index 6462dce53f..773cf36480 100644 --- a/core/libs/prx/libSceNpWebApi2/libSceNpWebApi2.hpp +++ b/core/libs/prx/libSceNpWebApi2/libSceNpWebApi2.hpp @@ -3,3 +3,4 @@ #include "prx/libSceNpWebApi2/include/Firmware320Exports.hpp" #include "prx/libSceNpWebApi2/include/Api.hpp" +#include "prx/libSceNpWebApi2/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceOpusCeltDec/Export.cpp b/core/libs/prx/libSceOpusCeltDec/Export.cpp index 664483dd5d..79bc92a955 100644 --- a/core/libs/prx/libSceOpusCeltDec/Export.cpp +++ b/core/libs/prx/libSceOpusCeltDec/Export.cpp @@ -2,6 +2,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceOpusDec/include/OpusDecoder.hpp" #include "prx/libSceOpusDec/include/OpusDecodeVariants.hpp" +#include #include #include "prx/libc/include/General.hpp" @@ -36,9 +37,19 @@ int APS5_VABI sceOpusCeltDecCreate() { return 0; } -int APS5_VABI sceOpusCeltDecCtl() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceOpusCeltDecCtl(void* state, int request, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list list; + __builtin_sysv_va_start(list, request); + const auto arguments = OpusDecoderLibrary::ReadCtlArguments(request, [&] { return __builtin_va_arg(list, int); }, [&] { return __builtin_va_arg(list, std::int32_t*); }); + __builtin_sysv_va_end(list); +#else + std::va_list list; + va_start(list, request); + const auto arguments = OpusDecoderLibrary::ReadCtlArguments(request, [&] { return va_arg(list, int); }, [&] { return va_arg(list, std::int32_t*); }); + va_end(list); +#endif + return library.Ctl(state, request, arguments); } int APS5_VABI sceOpusCeltDecDecodeFloat(void* state, const std::uint8_t* data, int bytes, float* pcm, int capacity) { diff --git a/core/libs/prx/libSceOpusCeltDec/include/Api.hpp b/core/libs/prx/libSceOpusCeltDec/include/Api.hpp index 1ba7cb6be1..85c150243a 100644 --- a/core/libs/prx/libSceOpusCeltDec/include/Api.hpp +++ b/core/libs/prx/libSceOpusCeltDec/include/Api.hpp @@ -11,7 +11,7 @@ int APS5_VABI sceOpusCeltDecCreateEx(const std::uint32_t* context, void* state, 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 sceOpusCeltDecCtl(void* state, int request, ...); int APS5_VABI sceOpusCeltDecDecodeFloat(void* state, const std::uint8_t* data, int bytes, float* pcm, int capacity); int APS5_VABI sceOpusCeltDecDecodeFloatWithPriority(void* state, const std::uint8_t* data, int bytes, float* pcm, int capacity, int priority); int APS5_VABI sceOpusCeltDecDecodeWithPriority(void* state, const std::uint8_t* data, int bytes, std::int16_t* pcm, int capacity, int priority); diff --git a/core/libs/prx/libSceOpusDec/CMakeLists.txt b/core/libs/prx/libSceOpusDec/CMakeLists.txt index 4356e19cab..ffa1801955 100644 --- a/core/libs/prx/libSceOpusDec/CMakeLists.txt +++ b/core/libs/prx/libSceOpusDec/CMakeLists.txt @@ -26,3 +26,15 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$;PATH=path_list_prepend:$") endif() endif() + +if(BUILD_TESTING) + add_test_executable(guest_opus_dec_ctl_tests ${LIBS_INCLUDE_DIR}/tests/GuestOpusDecCtl.cpp) + target_include_directories(guest_opus_dec_ctl_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_opus_dec_ctl_tests PRIVATE libSceOpusDec libSceOpusCeltDec libc opus) + configure_windows_unwind(guest_opus_dec_ctl_tests) + add_test(NAME guest_opus_dec_ctl COMMAND guest_opus_dec_ctl_tests) + if(WIN32) + set_tests_properties(guest_opus_dec_ctl PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$;PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceOpusDec/Export.cpp b/core/libs/prx/libSceOpusDec/Export.cpp index dc487b2049..4f3eee9ce9 100644 --- a/core/libs/prx/libSceOpusDec/Export.cpp +++ b/core/libs/prx/libSceOpusDec/Export.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceOpusDec/include/OpusDecoder.hpp" #include "prx/libSceOpusDec/include/OpusDecodeVariants.hpp" +#include #include #include "prx/libc/include/General.hpp" @@ -42,9 +43,19 @@ int APS5_VABI sceOpusDecCreate() { return 0; } -int APS5_VABI sceOpusDecCtl() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceOpusDecCtl(void* state, int request, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list list; + __builtin_sysv_va_start(list, request); + const auto arguments = OpusDecoderLibrary::ReadCtlArguments(request, [&] { return __builtin_va_arg(list, int); }, [&] { return __builtin_va_arg(list, std::int32_t*); }); + __builtin_sysv_va_end(list); +#else + std::va_list list; + va_start(list, request); + const auto arguments = OpusDecoderLibrary::ReadCtlArguments(request, [&] { return va_arg(list, int); }, [&] { return va_arg(list, std::int32_t*); }); + va_end(list); +#endif + return library.Ctl(state, request, arguments); } int APS5_VABI sceOpusDecDecodeFloat(void* state, const std::uint8_t* data, int bytes, float* pcm, int capacity) { diff --git a/core/libs/prx/libSceOpusDec/include/Api.hpp b/core/libs/prx/libSceOpusDec/include/Api.hpp index 492ffce7c0..91426b9e35 100644 --- a/core/libs/prx/libSceOpusDec/include/Api.hpp +++ b/core/libs/prx/libSceOpusDec/include/Api.hpp @@ -12,7 +12,7 @@ int APS5_VABI sceOpusDecDecode(void* state, const std::uint8_t* data, int bytes, int APS5_VABI sceOpusDecDestroy(void* state); int APS5_VABI sceOpusDecCancel(); int APS5_VABI sceOpusDecCreate(); -int APS5_VABI sceOpusDecCtl(); +int APS5_VABI sceOpusDecCtl(void* state, int request, ...); int APS5_VABI sceOpusDecDecodeFloat(void* state, const std::uint8_t* data, int bytes, float* pcm, int capacity); int APS5_VABI sceOpusDecDecodeFloatWithPriority(void* state, const std::uint8_t* data, int bytes, float* pcm, int capacity, int priority); int APS5_VABI sceOpusDecDecodeFloatWithPriorityAndTimeout(void* state, const std::uint8_t* data, int bytes, float* pcm, int capacity, int priority, diff --git a/core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp b/core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp index fa9cf66b31..4b431ebda0 100644 --- a/core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp +++ b/core/libs/prx/libSceOpusDec/include/OpusDecoder.hpp @@ -78,6 +78,7 @@ public: if (found == decoders.end()) return error(1); if (bytes == 0) fail("packet loss concealment is not implemented"); auto& decoder = *found->second; + if (decoder.unknownFlag != 0) fail("decoding with request 4050 set is not implemented"); if (celtOnly && (data[0] >> 3) < 16) fail("SILK and hybrid packets are not implemented"); const int samples = opus_packet_get_nb_samples(data, bytes, decoder.sampleRate); if (samples <= 0) fail("invalid packet duration"); @@ -87,10 +88,104 @@ public: 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"); + opus_uint32 range = 0; + if (opus_decoder_ctl(decoder.codec.get(), OPUS_GET_FINAL_RANGE(&range)) != OPUS_OK) fail("libopus did not report the final range"); + decoder.finalRange = range; + decoder.decoded = true; std::memcpy(pcm, decoded.data(), static_cast(outputBytes)); return outputBytes; } + struct CtlArguments { + std::int32_t value = 0; + std::int32_t* output = nullptr; + std::int32_t* second = nullptr; + }; + + template + static CtlArguments ReadCtlArguments(int request, TReadValue readValue, TReadPointer readPointer) { + CtlArguments arguments; + switch (request) { + case OPUS_SET_GAIN_REQUEST: + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + case OPUS_SET_INBAND_FEC_REQUEST: + case unknownFlagSet: arguments.value = readValue(); break; + case OPUS_GET_SAMPLE_RATE_REQUEST: + case OPUS_GET_FINAL_RANGE_REQUEST: + case OPUS_GET_GAIN_REQUEST: + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + case OPUS_GET_INBAND_FEC_REQUEST: + case unknownFlagGet: arguments.output = readPointer(); break; + case -1: + arguments.output = readPointer(); + arguments.second = readPointer(); + break; + } + return arguments; + } + + int Ctl(void* state, int request, const CtlArguments& arguments) { + if (!state) return error(0); + std::lock_guard lock(mutex); + const auto found = decoders.find(state); + if (found == decoders.end()) return error(1); + auto& decoder = *found->second; + const auto value = arguments.value; + auto* output = arguments.output; + const auto get = [&](std::int32_t current) { + if (!output) return error(13); + *output = current; + return 0; + }; + switch (request) { + case OPUS_RESET_STATE: + decoder.inbandFec = 0; + decoder.gain = 0; + decoder.unknownFlag = 0; + decoder.phaseInversionDisabled = 0; + if (opus_decoder_ctl(decoder.codec.get(), OPUS_RESET_STATE) != OPUS_OK) fail("libopus did not reset"); + apply(decoder); + return 0; + case OPUS_GET_SAMPLE_RATE_REQUEST: return get(decoder.sampleRate); + case OPUS_GET_FINAL_RANGE_REQUEST: return get(static_cast(decoder.finalRange)); + case OPUS_SET_GAIN_REQUEST: + if (value < -32768 || value > 32767) return error(12); + decoder.gain = value; + apply(decoder); + return 0; + case OPUS_GET_GAIN_REQUEST: return get(decoder.gain); + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + if (static_cast(value) > 1) return error(celtOnly ? 17 : 27); + decoder.phaseInversionDisabled = value; + apply(decoder); + return 0; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: return get(decoder.phaseInversionDisabled); + case -1: { + auto* second = arguments.second; + if (!output || !second) return error(13); + if (decoder.decoded) fail("the AJM result words of the last batch are not modelled"); + *output = 0; + *second = 0; + return 0; + } + } + if (!celtOnly) { + switch (request) { + case OPUS_SET_INBAND_FEC_REQUEST: + if (static_cast(value) > 1) return error(16); + decoder.inbandFec = value; + return 0; + case OPUS_GET_INBAND_FEC_REQUEST: return get(decoder.inbandFec); + case unknownFlagSet: + if (static_cast(value) > 1) return error(17); + decoder.unknownFlag = value; + return 0; + case unknownFlagGet: return get(decoder.unknownFlag); + } + } + return error(14); + } + int Destroy(void* state) { if (!state) return error(0); std::lock_guard lock(mutex); @@ -104,8 +199,17 @@ private: int channels; int sampleRate; std::unique_ptr codec{nullptr, opus_decoder_destroy}; + std::uint32_t finalRange = 0; + bool decoded = false; + std::int32_t inbandFec = 0; + std::int32_t gain = 0; + std::int32_t unknownFlag = 0; + std::int32_t phaseInversionDisabled = 0; }; + static constexpr int unknownFlagSet = 4050; + static constexpr int unknownFlagGet = 4051; + const char* name; bool celtOnly; std::mutex mutex; @@ -117,6 +221,12 @@ private: return static_cast((celtOnly ? 0x81400000u : 0x81740000u) + offset); } + void apply(Decoder& decoder) const { + if (opus_decoder_ctl(decoder.codec.get(), OPUS_SET_GAIN(decoder.gain)) != OPUS_OK || + opus_decoder_ctl(decoder.codec.get(), OPUS_SET_PHASE_INVERSION_DISABLED(decoder.phaseInversionDisabled)) != OPUS_OK) + fail("libopus rejected a decoder setting"); + } + [[noreturn]] void fail(const char* message) const { throw std::runtime_error(std::string(name) + ": " + message); } diff --git a/core/libs/prx/libScePad/CMakeLists.txt b/core/libs/prx/libScePad/CMakeLists.txt index 54630c0ffa..f979b4eff4 100644 --- a/core/libs/prx/libScePad/CMakeLists.txt +++ b/core/libs/prx/libScePad/CMakeLists.txt @@ -32,6 +32,39 @@ if(BUILD_TESTING) endif() endif() +if(BUILD_TESTING) + add_test_executable(guest_pad_controller_queries_tests ${LIBS_INCLUDE_DIR}/tests/GuestPadControllerQueries.cpp) + target_include_directories(guest_pad_controller_queries_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pad_controller_queries_tests PRIVATE libScePad libc) + configure_windows_unwind(guest_pad_controller_queries_tests) + add_test(NAME guest_pad_controller_queries COMMAND guest_pad_controller_queries_tests) + if(WIN32) + set_tests_properties(guest_pad_controller_queries PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_pad_firmware_checks_tests ${LIBS_INCLUDE_DIR}/tests/GuestPadFirmwareChecks.cpp) + target_include_directories(guest_pad_firmware_checks_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pad_firmware_checks_tests PRIVATE libScePad libc) + configure_windows_unwind(guest_pad_firmware_checks_tests) + add_test(NAME guest_pad_firmware_checks COMMAND guest_pad_firmware_checks_tests) + if(WIN32) + set_tests_properties(guest_pad_firmware_checks PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + +if(BUILD_TESTING) + add_test_executable(guest_pad_audio_route_tests ${LIBS_INCLUDE_DIR}/tests/GuestPadAudioRoute.cpp) + target_include_directories(guest_pad_audio_route_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pad_audio_route_tests PRIVATE libScePad libc) + configure_windows_unwind(guest_pad_audio_route_tests) + add_test(NAME guest_pad_audio_route COMMAND guest_pad_audio_route_tests) + if(WIN32) + set_tests_properties(guest_pad_audio_route PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + if(BUILD_TESTING) add_test_executable(pad_rumble_intensity_tests tests/PadRumbleIntensity.cpp) target_include_directories(pad_rumble_intensity_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libScePad/Export.cpp b/core/libs/prx/libScePad/Export.cpp index e0c2fec1f7..d105933e65 100644 --- a/core/libs/prx/libScePad/Export.cpp +++ b/core/libs/prx/libScePad/Export.cpp @@ -26,6 +26,9 @@ static_assert(offsetof(PadVirtualTriggerInternalState, mode) == 8); static_assert(offsetof(PadVirtualTriggerInternalState, flags) == 9); static_assert(sizeof(PadLightBarBlinkingParameter) == 3); +static_assert(sizeof(PadContainerIdInformation) == 0x2004); +static_assert(sizeof(PadVolumeGain) == 4); +static_assert(offsetof(PadVolumeGain, micGain) == 3); static_assert(sizeof(PadButtonRemappingInfo) == 16); static_assert(offsetof(PadButtonRemappingInfo, mode) == 15); @@ -40,11 +43,12 @@ constexpr int PAD_ERROR_INVALID_PORT_INDEX = static_cast(0x809b0003); constexpr int PAD_ERROR_NOT_PERMITTED = static_cast(0x80920101); constexpr int PAD_USER_ID_INVALID = -1; -std::array g_opened{false}; -std::array g_everOpened{false}; -bool g_remoteOpened = false; -std::array g_specialUsers{}; +std::array g_opened{}; +std::array g_everOpened{}; +std::atomic_bool g_remoteOpened{false}; +std::array, PAD_SPECIAL_HANDLE_COUNT> g_specialUsers{}; std::atomic_bool g_initialized{false}; +std::atomic_bool g_particularMode{false}; bool SpecialHandle(int handle) { const int slot = handle - PAD_SPECIAL_HANDLE; @@ -68,12 +72,20 @@ bool KnownHandle(int handle) { return PortOf(handle) >= 0 || SpecialHandle(handle); } +int DeviceOf(int handle) { + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + if (SpecialHandle(handle)) return PAD_ERROR_DEVICE_NOT_CONNECTED; + return Pad::IsConnected(PortOf(handle)) ? PAD_OK : PAD_ERROR_DEVICE_NOT_CONNECTED; +} + bool OpenHandle(int handle) { const int port = PortOf(handle); return (port >= 0 && g_opened[port]) || SpecialHandle(handle); } constexpr std::size_t PAD_LEONARDO_UNIQUE_DATA_SIZE = 0x18; +constexpr std::uint32_t PAD_HISTORY_BUFFER_SIZE = 0x960; +constexpr std::int32_t PAD_OPEN_EXT2_PARAM_SIZE = 0x10; PadData SpecialData() { PadData data{}; @@ -246,7 +258,45 @@ int APS5_VABI scePadSetProcessPrivilege(int privilege) { } int APS5_VABI scePadSetParticularMode(bool mode) { - (void)mode; + g_particularMode.store(mode, std::memory_order_release); + return PAD_OK; +} + +int APS5_VABI scePadGetParticularMode(void) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + return g_particularMode.load(std::memory_order_acquire) ? 1 : 0; +} + +int APS5_VABI scePadGetControllerBusType(int handle, int* busType) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (busType == nullptr) return PAD_ERROR_INVALID_ARG; + const int device = DeviceOf(handle); + if (device != PAD_OK) return device; + *busType = PAD_CONNECT_TYPE_BLUETOOTH; + return PAD_OK; +} + +int APS5_VABI scePadGetJackState(int handle, int* state) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (state == nullptr) return PAD_ERROR_INVALID_ARG; + const int device = DeviceOf(handle); + if (device != PAD_OK) return device; + *state = 0; + return PAD_OK; +} + +int APS5_VABI scePadIsControllerUpdateRequired(int handle) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + return DeviceOf(handle); +} + +int APS5_VABI scePadGetContainerIdInformation(int handle, PadContainerIdInformation* info) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (info == nullptr) return PAD_ERROR_INVALID_ARG; + const int device = DeviceOf(handle); + if (device != PAD_OK) return device; + info->size = 0; + info->id[0] = '\0'; return PAD_OK; } @@ -262,15 +312,13 @@ int APS5_VABI scePadOpen_nid_postfix(int userId, int type, int index, const void 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 (unknownType) return PAD_ERROR_INVALID_ARG; 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; - } + int free = 0; + if (g_specialUsers[slot].compare_exchange_strong(free, userId)) return PAD_SPECIAL_HANDLE + slot; } return PAD_ERROR_UNKNOWN_USER; } @@ -408,6 +456,31 @@ int APS5_VABI scePadSetVibrationMode(int handle, int mode) { return PAD_OK; } +int APS5_VABI scePadIsSupportedAudioFunction(int handle) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + const int device = DeviceOf(handle); + if (device != PAD_OK) return device; + return 1; +} + +int APS5_VABI scePadSetAudioOutPath(int handle, int path) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (static_cast(path) > 4u) return PAD_ERROR_INVALID_ARG; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + if (SpecialHandle(handle)) return PAD_OK; + Pad::SetAudioOutPath(PortOf(handle), path); + return PAD_OK; +} + +int APS5_VABI scePadSetVolumeGain(int handle, const PadVolumeGain* gain) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (gain == nullptr) return PAD_ERROR_INVALID_ARG; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + if (SpecialHandle(handle)) return PAD_OK; + Pad::SetVolumeGain(PortOf(handle), gain->speakerVolume, gain->headsetVolume, gain->micGain); + return PAD_OK; +} + int APS5_VABI scePadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enabled) { (void)enabled; return PAD_OK; @@ -487,8 +560,9 @@ int APS5_VABI scePadLastLotusButtonData(std::int32_t handle, std::uint32_t param } int APS5_VABI scePadReadStateExt(std::int32_t handle, PadDataExtended* data) { + if (!KnownHandle(handle) && !RemoteHandle(handle)) return PAD_ERROR_INVALID_HANDLE; + if (data == nullptr) return PAD_ERROR_INVALID_ARG; NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadSetExtensionReport(std::int32_t handle, const PadExtensionReport* report) { @@ -497,8 +571,10 @@ int APS5_VABI scePadSetExtensionReport(std::int32_t handle, const PadExtensionRe } int APS5_VABI scePadGetExtensionUnitInfo(std::int32_t handle, PadExtensionUnitInfo* info) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (info == nullptr) return PAD_ERROR_INVALID_ARG; + static_cast(handle); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadEnableExtensionPort(bool enabled) { @@ -510,9 +586,11 @@ int APS5_VABI scePadEnableSpecificDeviceClass(std::int32_t deviceClass) { return 0; } -int APS5_VABI scePadOpenExt2(std::uint64_t deviceId, std::int32_t type, const void* reserved) { +int APS5_VABI scePadOpenExt2(std::int32_t userId, std::int32_t paramSize, const void* param) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (paramSize != PAD_OPEN_EXT2_PARAM_SIZE || param == nullptr) return PAD_ERROR_INVALID_ARG; + static_cast(userId); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadDeviceOpen(std::uint64_t deviceId, std::int32_t type, std::int32_t index) { @@ -556,13 +634,15 @@ int APS5_VABI scePadSetLightBarBlinking(std::int32_t handle, const PadLightBarBl } std::uint64_t APS5_VABI scePadGetIdleCount(std::int32_t userId) { + if (!Pad::IsInitialized()) return 0; + static_cast(userId); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadGetControllerIdleCount(std::int32_t handle, std::uint64_t* count) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (handle == 0 || count == nullptr) return PAD_ERROR_INVALID_ARG; NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadSetPowerSavingState(std::int32_t handle, std::uint8_t state) { @@ -601,8 +681,10 @@ int APS5_VABI scePadSetLoginUserNumber(std::int32_t number) { } int APS5_VABI scePadGetCapability(std::int32_t deviceId, std::uint32_t* capability) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (capability == nullptr) return PAD_ERROR_INVALID_ARG; + static_cast(deviceId); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadShareOutputData(std::int32_t source, std::int32_t destination) { @@ -626,8 +708,10 @@ int APS5_VABI scePadSetVibrationForce(std::int32_t handle, const PadVibrationPar } int APS5_VABI scePadGetVersionInfo(std::int32_t handle, PadVersionInfo* info) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (info == nullptr) return PAD_ERROR_INVALID_ARG; + static_cast(handle); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadGetControllerType(std::int32_t handle, std::int32_t* type) { @@ -911,18 +995,27 @@ 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) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (activeTime == nullptr) return PAD_ERROR_INVALID_ARG; + static_cast(handle); + static_cast(type); + *activeTime = 0; NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadGetDeviceId(std::int32_t handle, std::int32_t* deviceId) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (deviceId == nullptr) return PAD_ERROR_INVALID_ARG; + *deviceId = -1; + if (!KnownHandle(handle)) return PAD_ERROR_INVALID_HANDLE; NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadGetInfoByPortType(std::int32_t type, PadPortInfo* info) { + if (info == nullptr) return PAD_ERROR_INVALID_ARG; + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if ((static_cast(type) & ~2u) != 0) return PAD_ERROR_INVALID_ARG; NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadResetLightBarAll(void) { @@ -1053,8 +1146,10 @@ int APS5_VABI scePadDisconnectDevice(std::int32_t deviceId) { } int APS5_VABI scePadGetDeviceInfo(std::int32_t deviceId, PadDeviceInfo* info) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (info == nullptr) return PAD_ERROR_INVALID_ARG; + static_cast(deviceId); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadSetProcessFocus(const PadProcessFocusParameter* parameter) { @@ -1111,8 +1206,9 @@ int APS5_VABI scePadSetShareButtonMaskForRemotePlay(std::uint8_t mask) { } int APS5_VABI scePadSetPS4BcVibrationMode(std::uint8_t mode) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + static_cast(mode); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadSetVirtualDeviceComponentInformation(std::int32_t deviceId, const PadVirtualDeviceComponentInformation* info) { @@ -1130,9 +1226,12 @@ int APS5_VABI scePadGetLicenseControllerInformation(std::int32_t handle, PadLice return 0; } -int APS5_VABI scePadReadHistory(std::int32_t handle, PadHistoryData* data, std::int32_t count) { +int APS5_VABI scePadReadHistory(std::int32_t handle, PadHistoryData* data, std::uint32_t size) { + if (!Pad::IsInitialized()) return PAD_ERROR_NOT_INITIALIZED; + if (size != PAD_HISTORY_BUFFER_SIZE) return PAD_ERROR_INVALID_ARG; + static_cast(handle); + static_cast(data); NotImplemented_nid_no_patch(__func__); - return 0; } int APS5_VABI scePadStartRecording(void) { diff --git a/core/libs/prx/libScePad/include/Pad.hpp b/core/libs/prx/libScePad/include/Pad.hpp index 49154aa264..e45b06fcb1 100644 --- a/core/libs/prx/libScePad/include/Pad.hpp +++ b/core/libs/prx/libScePad/include/Pad.hpp @@ -113,6 +113,18 @@ struct PadVirtualDeviceParameter; struct PadVirtualDeviceId; struct PadVirtualDeviceComponentInformation; +struct PadContainerIdInformation { + std::uint32_t size; + char id[0x2000]; +}; + +struct PadVolumeGain { + std::uint8_t speakerVolume; + std::uint8_t headsetVolume; + std::uint8_t padding; + std::uint8_t micGain; +}; + struct PadVirtualTriggerInternalState { std::uint32_t states[2]; std::uint8_t mode; @@ -155,6 +167,14 @@ int APS5_VABI scePadGetTriggerEffectState(int handle, PadTriggerEffectStateInfor int APS5_VABI scePadGetExtControllerInformation(int handle, void* info); int APS5_VABI scePadSetProcessPrivilege(int privilege); int APS5_VABI scePadSetParticularMode(bool mode); +int APS5_VABI scePadGetParticularMode(void); +int APS5_VABI scePadGetControllerBusType(int handle, int* busType); +int APS5_VABI scePadGetJackState(int handle, int* state); +int APS5_VABI scePadIsControllerUpdateRequired(int handle); +int APS5_VABI scePadGetContainerIdInformation(int handle, PadContainerIdInformation* info); +int APS5_VABI scePadIsSupportedAudioFunction(int handle); +int APS5_VABI scePadSetAudioOutPath(int handle, int path); +int APS5_VABI scePadSetVolumeGain(int handle, const PadVolumeGain* gain); 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); @@ -192,7 +212,7 @@ int APS5_VABI scePadSetExtensionReport(std::int32_t handle, const PadExtensionRe 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 scePadOpenExt2(std::int32_t userId, std::int32_t paramSize, const void* param); 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); @@ -309,7 +329,7 @@ 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 scePadReadHistory(std::int32_t handle, PadHistoryData* data, std::uint32_t size); int APS5_VABI scePadStartRecording(void); int APS5_VABI scePadStopRecording(void); int APS5_VABI scePadRemoteControlInsertData(const std::uint8_t* data); diff --git a/core/libs/prx/libScePad/include/PadState.hpp b/core/libs/prx/libScePad/include/PadState.hpp index 1dfc108fcc..79b1dc6988 100644 --- a/core/libs/prx/libScePad/include/PadState.hpp +++ b/core/libs/prx/libScePad/include/PadState.hpp @@ -52,6 +52,11 @@ struct PadOutputState { bool triggerTouched = false; bool motionEnabled = true; int vibrationMode = 0; + int audioOutPath = 0; + bool volumeGainValid = false; + std::uint8_t speakerVolume = 0; + std::uint8_t headsetVolume = 0; + std::uint8_t micGain = 0; }; namespace Pad { @@ -63,6 +68,8 @@ 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 SetAudioOutPath(int port, int path); +void SetVolumeGain(int port, std::uint8_t speaker, std::uint8_t headset, std::uint8_t mic); 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); diff --git a/core/libs/prx/libScePad/src/PadState.cpp b/core/libs/prx/libScePad/src/PadState.cpp index ee47ae1d03..2844e77320 100644 --- a/core/libs/prx/libScePad/src/PadState.cpp +++ b/core/libs/prx/libScePad/src/PadState.cpp @@ -374,6 +374,27 @@ void Pad::SetVibrationMode(int mode) { SetVibrationMode(0, mode); } +void Pad::SetAudioOutPath(int port, int path) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; + std::lock_guard lock(stateMutex); + auto& output = slots[port].output; + if (output.audioOutPath == path) return; + output.audioOutPath = path; + ++output.sequence; +} + +void Pad::SetVolumeGain(int port, std::uint8_t speaker, std::uint8_t headset, std::uint8_t mic) { + if (port < 0 || port >= PAD_MAX_CONTROLLERS) return; + std::lock_guard lock(stateMutex); + auto& output = slots[port].output; + if (output.volumeGainValid && output.speakerVolume == speaker && output.headsetVolume == headset && output.micGain == mic) return; + output.volumeGainValid = true; + output.speakerVolume = speaker; + output.headsetVolume = headset; + output.micGain = mic; + ++output.sequence; +} + 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); diff --git a/core/libs/prx/libScePerf/Export.cpp b/core/libs/prx/libScePerf/Export.cpp index fb34206c21..9d6857a511 100644 --- a/core/libs/prx/libScePerf/Export.cpp +++ b/core/libs/prx/libScePerf/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libScePerf/include/Api.hpp" #include #include #include "SceTypes.hpp" @@ -5,6 +6,424 @@ extern "C" { -APS5_DUMMY_FUN +int APS5_VABI scePerfTraceIoControllerEnable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTracePmStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcUmcStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcSdfGetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcSdfStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAmmDelete(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTracePmGetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceIoControllerStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcSelectEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTracePmEnable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcClose(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceDelete(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfGetStreamingTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfDelete(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTracePmDeleteBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcL3SelectEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceIoControllerCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceIoControllerDeleteBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceEnable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceGetMemoryStats(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTracePmUpdate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAmmDeleteBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcL3GetCounterSelf(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAprNameCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcSetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcUmcSetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAmmEnable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTracePmCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceGetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcReset(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTracePmStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAprNameStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcSdfSetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfReadBuffer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAprNameAddBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcSdfStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcL3Start(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcSdfSelectEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAmmAddBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfEnable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceDeleteBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceIoControllerDelete(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfAddBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcGetCounterSelf(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfDeleteBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAddBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAmmStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAmmGetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcL3Stop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcMperfGetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfMarker(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAprNameUpdate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAprNameDeleteBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcGetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcL3GetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAprNameStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTracePmDelete(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcUmcStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAmmCreate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcL3SetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceIoControllerUpdate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcAperfGetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfSetWait(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTracePmAddBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAmmUpdate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAprNameEnable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAprNameDelete(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceIoControllerAddBufferNotifyEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAmmStop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceIoControllerGetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceAprNameGetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcUmcGetCounter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceSpmSdfGetInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceIoControllerStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfTraceUpdate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI scePerfPmcOpen(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libScePerf/include/Api.hpp b/core/libs/prx/libScePerf/include/Api.hpp new file mode 100644 index 0000000000..c4c3966edf --- /dev/null +++ b/core/libs/prx/libScePerf/include/Api.hpp @@ -0,0 +1,92 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePerfTraceIoControllerEnable(void); +int APS5_VABI scePerfTraceCreate(void); +int APS5_VABI scePerfTracePmStop(void); +int APS5_VABI scePerfPmcUmcStart(void); +int APS5_VABI scePerfPmcSdfGetCounter(void); +int APS5_VABI scePerfPmcSdfStart(void); +int APS5_VABI scePerfTraceAmmDelete(void); +int APS5_VABI scePerfTracePmGetInfo(void); +int APS5_VABI scePerfTraceIoControllerStop(void); +int APS5_VABI scePerfPmcSelectEvent(void); +int APS5_VABI scePerfTracePmEnable(void); +int APS5_VABI scePerfPmcClose(void); +int APS5_VABI scePerfTraceDelete(void); +int APS5_VABI scePerfTraceSpmSdfStart(void); +int APS5_VABI scePerfTraceSpmSdfGetStreamingTime(void); +int APS5_VABI scePerfTraceSpmSdfDelete(void); +int APS5_VABI scePerfPmcStop(void); +int APS5_VABI scePerfTracePmDeleteBufferNotifyEvent(void); +int APS5_VABI scePerfPmcL3SelectEvent(void); +int APS5_VABI scePerfTraceIoControllerCreate(void); +int APS5_VABI scePerfTraceIoControllerDeleteBufferNotifyEvent(void); +int APS5_VABI scePerfTraceEnable(void); +int APS5_VABI scePerfTraceGetMemoryStats(void); +int APS5_VABI scePerfTracePmUpdate(void); +int APS5_VABI scePerfTraceAmmDeleteBufferNotifyEvent(void); +int APS5_VABI scePerfPmcL3GetCounterSelf(void); +int APS5_VABI scePerfTraceAprNameCreate(void); +int APS5_VABI scePerfPmcSetCounter(void); +int APS5_VABI scePerfPmcUmcSetCounter(void); +int APS5_VABI scePerfTraceAmmEnable(void); +int APS5_VABI scePerfTracePmCreate(void); +int APS5_VABI scePerfTraceGetInfo(void); +int APS5_VABI scePerfPmcReset(void); +int APS5_VABI scePerfTracePmStart(void); +int APS5_VABI scePerfTraceAprNameStart(void); +int APS5_VABI scePerfPmcSdfSetCounter(void); +int APS5_VABI scePerfTraceSpmSdfReadBuffer(void); +int APS5_VABI scePerfTraceAprNameAddBufferNotifyEvent(void); +int APS5_VABI scePerfPmcSdfStop(void); +int APS5_VABI scePerfPmcL3Start(void); +int APS5_VABI scePerfPmcSdfSelectEvent(void); +int APS5_VABI scePerfTraceAmmAddBufferNotifyEvent(void); +int APS5_VABI scePerfTraceSpmSdfEnable(void); +int APS5_VABI scePerfTraceDeleteBufferNotifyEvent(void); +int APS5_VABI scePerfTraceIoControllerDelete(void); +int APS5_VABI scePerfTraceSpmSdfCreate(void); +int APS5_VABI scePerfTraceSpmSdfAddBufferNotifyEvent(void); +int APS5_VABI scePerfPmcGetCounterSelf(void); +int APS5_VABI scePerfTraceSpmSdfDeleteBufferNotifyEvent(void); +int APS5_VABI scePerfTraceAddBufferNotifyEvent(void); +int APS5_VABI scePerfTraceAmmStart(void); +int APS5_VABI scePerfTraceAmmGetInfo(void); +int APS5_VABI scePerfPmcL3Stop(void); +int APS5_VABI scePerfPmcMperfGetCounter(void); +int APS5_VABI scePerfTraceSpmSdfMarker(void); +int APS5_VABI scePerfTraceAprNameUpdate(void); +int APS5_VABI scePerfTraceAprNameDeleteBufferNotifyEvent(void); +int APS5_VABI scePerfPmcGetCounter(void); +int APS5_VABI scePerfPmcL3GetCounter(void); +int APS5_VABI scePerfPmcStart(void); +int APS5_VABI scePerfTraceAprNameStop(void); +int APS5_VABI scePerfTracePmDelete(void); +int APS5_VABI scePerfPmcUmcStop(void); +int APS5_VABI scePerfTraceAmmCreate(void); +int APS5_VABI scePerfPmcL3SetCounter(void); +int APS5_VABI scePerfTraceIoControllerUpdate(void); +int APS5_VABI scePerfPmcAperfGetCounter(void); +int APS5_VABI scePerfTraceSpmSdfSetWait(void); +int APS5_VABI scePerfTracePmAddBufferNotifyEvent(void); +int APS5_VABI scePerfTraceAmmUpdate(void); +int APS5_VABI scePerfTraceStart(void); +int APS5_VABI scePerfTraceStop(void); +int APS5_VABI scePerfTraceAprNameEnable(void); +int APS5_VABI scePerfTraceAprNameDelete(void); +int APS5_VABI scePerfTraceIoControllerAddBufferNotifyEvent(void); +int APS5_VABI scePerfTraceSpmSdfStop(void); +int APS5_VABI scePerfTraceAmmStop(void); +int APS5_VABI scePerfTraceIoControllerGetInfo(void); +int APS5_VABI scePerfTraceAprNameGetInfo(void); +int APS5_VABI scePerfPmcUmcGetCounter(void); +int APS5_VABI scePerfTraceSpmSdfGetInfo(void); +int APS5_VABI scePerfTraceIoControllerStart(void); +int APS5_VABI scePerfTraceUpdate(void); +int APS5_VABI scePerfPmcOpen(void); + +} diff --git a/core/libs/prx/libScePerf/libScePerf.hpp b/core/libs/prx/libScePerf/libScePerf.hpp index 6f70f09bee..3521098647 100644 --- a/core/libs/prx/libScePerf/libScePerf.hpp +++ b/core/libs/prx/libScePerf/libScePerf.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libScePerf/include/Api.hpp" diff --git a/core/libs/prx/libScePlayGo/Export.cpp b/core/libs/prx/libScePlayGo/Export.cpp index 574f2938ad..038a6005c6 100644 --- a/core/libs/prx/libScePlayGo/Export.cpp +++ b/core/libs/prx/libScePlayGo/Export.cpp @@ -1,6 +1,7 @@ #include "prx/libScePlayGo/include/Api.hpp" #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include @@ -75,6 +76,28 @@ static void RejectUnreadablePlayGoFiles() { } } +static bool ReadExplicitChunks(ChunkTable& table) { + const char* text = std::getenv("APS5_PLAYGO_UNLISTED_CHUNKS"); + if (text == nullptr || text[0] == '\0') return false; + const std::string value(text); + if (value == "installed" || value == "absent") return false; + std::size_t start = 0; + while (start <= value.size()) { + const auto end = value.find(',', start); + const auto token = value.substr(start, end == std::string::npos ? std::string::npos : end - start); + std::uint32_t parsed = 0; + const auto result = std::from_chars(token.data(), token.data() + token.size(), parsed); + if (token.empty() || result.ec != std::errc{} || result.ptr != token.data() + token.size() || parsed > 0xFFFF) { + throw std::runtime_error("scePlayGo: APS5_PLAYGO_UNLISTED_CHUNKS must be installed, absent, or a comma-separated list of chunk ids from 0 to 65535, not \"" + value + "\""); + } + table.ids.insert(static_cast(parsed)); + if (end == std::string::npos) break; + start = end + 1; + } + table.defined = true; + return true; +} + static const ChunkTable& Chunks() { static const ChunkTable chunks = [] { ChunkTable table; @@ -82,8 +105,12 @@ static const ChunkTable& Chunks() { if (!file) { if (ReadPackageChunks(table)) return table; table.scenario = std::ifstream(ResolvePath_nid_no_patch("/app0/sce_sys/playgo-scenario.json")).good(); - if (!table.scenario) RejectUnreadablePlayGoFiles(); - else for (uint32_t id = 1; id < PLAYGO_SCENARIO_CHUNK_COUNT; ++id) table.ids.insert(static_cast(id)); + if (table.scenario) { + for (uint32_t id = 1; id < PLAYGO_SCENARIO_CHUNK_COUNT; ++id) table.ids.insert(static_cast(id)); + return table; + } + RejectUnreadablePlayGoFiles(); + ReadExplicitChunks(table); return table; } table.defined = true; @@ -112,7 +139,9 @@ static UnlistedChunkAnswer UnlistedChunks() { const std::string value(text); if (value == "installed") return UnlistedChunkAnswer::Installed; if (value == "absent") return UnlistedChunkAnswer::Absent; - throw std::runtime_error("scePlayGo: APS5_PLAYGO_UNLISTED_CHUNKS must be installed or absent, not \"" + value + "\""); + ChunkTable explicitChunks; + ReadExplicitChunks(explicitChunks); + return UnlistedChunkAnswer::Stop; }(); return answer; } @@ -128,7 +157,7 @@ static bool IsKnownChunk(uint16_t id) { } 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"); + 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, =absent, or a comma-separated list of chunk ids to answer for this dump"); } static int ValidateChunks(const uint16_t* chunk_ids, uint32_t number_of_entries) { diff --git a/core/libs/prx/libScePlayReady/Export.cpp b/core/libs/prx/libScePlayReady/Export.cpp index c09e68fee3..e9ba888948 100644 --- a/core/libs/prx/libScePlayReady/Export.cpp +++ b/core/libs/prx/libScePlayReady/Export.cpp @@ -1,4 +1,6 @@ #include "prx/libScePlayReady/include/Api.hpp" +#include +#include #include #include #include @@ -75,7 +77,12 @@ int APS5_VABI scePlayReadyApiStop() { int APS5_VABI scePlayReadyApiVersion(uint32_t index, uint64_t* version) { if (index >= std::size(ApiVersions) || version == nullptr) return PlayReadyInvalidArgument; - std::memcpy(version, ApiVersions[index], sizeof(ApiVersions[index])); + const auto savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(version, ApiVersions[index], sizeof(ApiVersions[index]))) { + errno = savedErrno; + return PlayReadyInvalidArgument; + } + errno = savedErrno; return 0; } @@ -102,7 +109,13 @@ int APS5_VABI scePlayReadyBufferFree() { int APS5_VABI scePlayReadyBufferInit(void* buffer) { if (buffer == nullptr) return PlayReadyInvalidArgument; - std::memset(buffer, 0, BufferSize); + const std::array zeros{}; + const auto savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(buffer, zeros.data(), zeros.size())) { + errno = savedErrno; + return PlayReadyInvalidArgument; + } + errno = savedErrno; return 0; } diff --git a/core/libs/prx/libScePlayerInvitationDialog/Export.cpp b/core/libs/prx/libScePlayerInvitationDialog/Export.cpp deleted file mode 100644 index 0f78112135..0000000000 --- a/core/libs/prx/libScePlayerInvitationDialog/Export.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.h" -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" - -extern "C" { - -int APS5_VABI scePlayerInvitationDialogTerminate(void) { - return 0; -} - -int APS5_VABI scePlayerInvitationDialogUpdateStatus(void) { - return 0; -} - -} diff --git a/core/libs/prx/libScePlayerSelectionDialog/Export.cpp b/core/libs/prx/libScePlayerSelectionDialog/Export.cpp index d70f74b93b..8172d5144f 100644 --- a/core/libs/prx/libScePlayerSelectionDialog/Export.cpp +++ b/core/libs/prx/libScePlayerSelectionDialog/Export.cpp @@ -1,6 +1,7 @@ #include "prx/libScePlayerSelectionDialog/include/Api.hpp" #include "prx/libSceUserService/UserService.hpp" #include +#include #include #include #include @@ -75,16 +76,24 @@ int APS5_VABI scePlayerSelectionDialogGetResult(PlayerSelectionDialogResult* res 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; + const std::int32_t canceled = COMMON_DIALOG_RESULT_USER_CANCELED; + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(&result->result, &canceled, sizeof(canceled)); + errno = savedErrno; + if (!copied) return COMMON_DIALOG_ERROR_ARG_NULL; return COMMON_DIALOG_RESULT_USER_CANCELED; } int APS5_VABI scePlayerSelectionDialogParamInitialize(PlayerSelectionDialogParam* param) { if (param == nullptr) return COMMON_DIALOG_ERROR_ARG_NULL; - std::memset(param, 0, sizeof(*param)); - param->base.size = sizeof(param->base); - param->base.magic = static_cast(reinterpret_cast(¶m->base)) - 1060003575u; - param->size = sizeof(*param); + PlayerSelectionDialogParam local{}; + local.base.size = sizeof(local.base); + local.base.magic = static_cast(reinterpret_cast(¶m->base)) - 1060003575u; + local.size = sizeof(local); + const int savedErrno = errno; + const bool copied = GuestCopyOut_nid_no_patch(param, &local, sizeof(local)); + errno = savedErrno; + if (!copied) return COMMON_DIALOG_ERROR_ARG_NULL; return COMMON_DIALOG_OK; } diff --git a/core/libs/prx/libScePosixForWebKit/CMakeLists.txt b/core/libs/prx/libScePosixForWebKit/CMakeLists.txt index 9f28aee996..f7265ca9ec 100644 --- a/core/libs/prx/libScePosixForWebKit/CMakeLists.txt +++ b/core/libs/prx/libScePosixForWebKit/CMakeLists.txt @@ -74,8 +74,16 @@ if(BUILD_TESTING) configure_windows_unwind(guest_raw_resolver_tests) add_test(NAME guest_raw_resolver COMMAND guest_raw_resolver_tests) set_tests_properties(guest_raw_resolver PROPERTIES TIMEOUT 30) + add_test_executable(guest_resolver_socket_types_tests ${LIBS_INCLUDE_DIR}/tests/ResolverSocketTypes.cpp) + target_include_directories(guest_resolver_socket_types_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_resolver_socket_types_tests PRIVATE libScePosixForWebKit libc) + configure_windows_unwind(guest_resolver_socket_types_tests) + add_test(NAME guest_resolver_socket_types COMMAND guest_resolver_socket_types_tests) + set_tests_properties(guest_resolver_socket_types PROPERTIES TIMEOUT 30) if(WIN32) set_tests_properties(guest_raw_resolver PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + set_tests_properties(guest_resolver_socket_types PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") endif() endif() diff --git a/core/libs/prx/libScePosixForWebKit/Resolver.cpp b/core/libs/prx/libScePosixForWebKit/Resolver.cpp index f8e439dc79..1ce651d7fe 100644 --- a/core/libs/prx/libScePosixForWebKit/Resolver.cpp +++ b/core/libs/prx/libScePosixForWebKit/Resolver.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #endif #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/HostThreadLocal.hpp" @@ -281,48 +282,91 @@ int APS5_VABI getaddrinfo_nid_postfix(const char* node, const char* service, } if (rawQuery && service) return 9; if (!Ready()) return 4; - addrinfo* native = nullptr; - const auto error = ::getaddrinfo(node, service, &nativeHints, &native); - if (error) return GuestError(error); + const char* numericName = nullptr; + if (node && hints && (hints->flags & 2)) { + std::array v4{}; + std::array v6{}; + const char* scope = std::strchr(node, '%'); + const std::size_t length = scope ? static_cast(scope - node) : std::strlen(node); + std::array address{}; + bool numericV6 = false; + if (length < address.size()) { + std::memcpy(address.data(), node, length); + numericV6 = inet_pton(AF_INET6, address.data(), v6.data()) == 1; + } + if ((hints->flags & 4) || NumericAddress(node, v4) || numericV6) numericName = node; + } + std::array queries{nativeHints, nativeHints}; + std::size_t queryCount = 1; + if (!nativeHints.ai_socktype && !nativeHints.ai_protocol) { + queries[0].ai_socktype = SOCK_STREAM; + queries[0].ai_protocol = IPPROTO_TCP; + queries[1].ai_socktype = SOCK_DGRAM; + queries[1].ai_protocol = IPPROTO_UDP; + queryCount = 2; + } else if (!rawQuery) { + if (!queries[0].ai_socktype) { + if (nativeHints.ai_protocol == IPPROTO_TCP) queries[0].ai_socktype = SOCK_STREAM; + if (nativeHints.ai_protocol == IPPROTO_UDP) queries[0].ai_socktype = SOCK_DGRAM; + } + if (!queries[0].ai_protocol) { + if (nativeHints.ai_socktype == SOCK_STREAM) queries[0].ai_protocol = IPPROTO_TCP; + if (nativeHints.ai_socktype == SOCK_DGRAM) queries[0].ai_protocol = IPPROTO_UDP; + } + } GuestResolver::AddressInfo* first = nullptr; auto** tail = &first; int failure = 0; - for (auto* item = native; item; item = item->ai_next) { - if (item->ai_family != AF_INET && item->ai_family != AF_INET6) { failure = 5; break; } - auto* converted = static_cast(std::calloc(1, sizeof(GuestResolver::AddressInfo))); - if (!converted) { failure = 6; break; } - *tail = converted; - tail = &converted->next; - converted->flags = hints ? hints->flags : 0; - converted->family = item->ai_family == AF_INET ? 2 : 28; - converted->socketType = rawQuery ? 3 : item->ai_socktype == SOCK_STREAM ? 1 : item->ai_socktype == SOCK_DGRAM ? 2 : 0; - converted->protocol = rawQuery ? hints->protocol : item->ai_protocol; - converted->addressLength = item->ai_family == AF_INET ? 16 : 28; - auto* bytes = static_cast(std::calloc(1, converted->addressLength)); - converted->address = bytes; - if (!bytes) { failure = 6; break; } - bytes[0] = static_cast(converted->addressLength); - bytes[1] = static_cast(converted->family); - if (item->ai_family == AF_INET) { - const auto* v4 = reinterpret_cast(item->ai_addr); - std::memcpy(bytes + 2, &v4->sin_port, 2); - std::memcpy(bytes + 4, &v4->sin_addr, 4); - } else { - const auto* v6 = reinterpret_cast(item->ai_addr); - std::memcpy(bytes + 2, &v6->sin6_port, 2); - std::memcpy(bytes + 4, &v6->sin6_flowinfo, 4); - std::memcpy(bytes + 8, &v6->sin6_addr, 16); - std::memcpy(bytes + 24, &v6->sin6_scope_id, 4); + int lookupFailure = 0; + for (std::size_t query = 0; query < queryCount; ++query) { + addrinfo* native = nullptr; + const auto error = ::getaddrinfo(node, service, &queries[query], &native); + if (error) { + if (error == EAI_SERVICE) { lookupFailure = 9; continue; } + failure = GuestError(error); + break; } - if (item->ai_canonname) { - const auto size = std::strlen(item->ai_canonname) + 1; - converted->canonicalName = static_cast(std::malloc(size)); - if (!converted->canonicalName) { failure = 6; break; } - std::memcpy(converted->canonicalName, item->ai_canonname, size); + for (auto* item = native; item; item = item->ai_next) { + if (item->ai_family != AF_INET && item->ai_family != AF_INET6) { failure = 5; break; } + const int socketType = rawQuery ? 3 : item->ai_socktype == SOCK_STREAM ? 1 : item->ai_socktype == SOCK_DGRAM ? 2 : 0; + if (!socketType) continue; + auto* converted = static_cast(std::calloc(1, sizeof(GuestResolver::AddressInfo))); + if (!converted) { failure = 6; break; } + *tail = converted; + tail = &converted->next; + converted->flags = hints ? hints->flags : 0; + converted->family = item->ai_family == AF_INET ? 2 : 28; + converted->socketType = socketType; + converted->protocol = rawQuery ? hints->protocol : item->ai_protocol; + converted->addressLength = item->ai_family == AF_INET ? 16 : 28; + auto* bytes = static_cast(std::calloc(1, converted->addressLength)); + converted->address = bytes; + if (!bytes) { failure = 6; break; } + bytes[0] = static_cast(converted->addressLength); + bytes[1] = static_cast(converted->family); + if (item->ai_family == AF_INET) { + const auto* v4 = reinterpret_cast(item->ai_addr); + std::memcpy(bytes + 2, &v4->sin_port, 2); + std::memcpy(bytes + 4, &v4->sin_addr, 4); + } else { + const auto* v6 = reinterpret_cast(item->ai_addr); + std::memcpy(bytes + 2, &v6->sin6_port, 2); + std::memcpy(bytes + 4, &v6->sin6_flowinfo, 4); + std::memcpy(bytes + 8, &v6->sin6_addr, 16); + std::memcpy(bytes + 24, &v6->sin6_scope_id, 4); + } + if (const char* canonical = item->ai_canonname ? item->ai_canonname : numericName) { + const auto size = std::strlen(canonical) + 1; + converted->canonicalName = static_cast(std::malloc(size)); + if (!converted->canonicalName) { failure = 6; break; } + std::memcpy(converted->canonicalName, canonical, size); + } } + ::freeaddrinfo(native); + if (failure) break; } - ::freeaddrinfo(native); if (failure) { freeaddrinfo_nid_postfix(first); return failure; } + if (!first) return lookupFailure ? lookupFailure : 4; *output = first; return 0; } diff --git a/core/libs/prx/libScePsm/Export.cpp b/core/libs/prx/libScePsm/Export.cpp index d31d96f41f..f1f80e0be1 100644 --- a/core/libs/prx/libScePsm/Export.cpp +++ b/core/libs/prx/libScePsm/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libScePsm/include/InventoryExports.hpp" #include "prx/libScePsm/include/Api.hpp" #include #include @@ -332,3 +334,272 @@ int APS5_VABI scePsmRegisterPInvokeCallTable() { } } + +extern "C" { + +int APS5_VABI libScePsmUnknown__minus_ndbidpphoc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("-ndbidpphoc", libScePsmUnknown__minus_ndbidpphoc_nid_no_patch); + +int APS5_VABI libScePsmUnknown_0PIOM2UIUpQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0PIOM2UIUpQ", libScePsmUnknown_0PIOM2UIUpQ_nid_no_patch); + +int APS5_VABI libScePsmUnknown_0Tit25nFAP4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("0Tit25nFAP4", libScePsmUnknown_0Tit25nFAP4_nid_no_patch); + +int APS5_VABI libScePsmUnknown_12ZHf6Z7WLg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("12ZHf6Z7WLg", libScePsmUnknown_12ZHf6Z7WLg_nid_no_patch); + +int APS5_VABI libScePsmUnknown_1Z9IoxZ508g_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1Z9IoxZ508g", libScePsmUnknown_1Z9IoxZ508g_nid_no_patch); + +int APS5_VABI libScePsmUnknown_5DwG5treG0o_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5DwG5treG0o", libScePsmUnknown_5DwG5treG0o_nid_no_patch); + +int APS5_VABI libScePsmUnknown_5dDsI2vg6dc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("5dDsI2vg6dc", libScePsmUnknown_5dDsI2vg6dc_nid_no_patch); + +int APS5_VABI libScePsmUnknown_6FPQyIqTJi0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("6FPQyIqTJi0", libScePsmUnknown_6FPQyIqTJi0_nid_no_patch); + +int APS5_VABI libScePsmUnknown_74_plus_2xQDzd_minus_c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("74+2xQDzd-c", libScePsmUnknown_74_plus_2xQDzd_minus_c_nid_no_patch); + +int APS5_VABI libScePsmUnknown_8wWLk0PaYGQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("8wWLk0PaYGQ", libScePsmUnknown_8wWLk0PaYGQ_nid_no_patch); + +int APS5_VABI libScePsmUnknown_BaQ59CqSB34_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("BaQ59CqSB34", libScePsmUnknown_BaQ59CqSB34_nid_no_patch); + +int APS5_VABI libScePsmUnknown_C08zEeeiTBI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("C08zEeeiTBI", libScePsmUnknown_C08zEeeiTBI_nid_no_patch); + +int APS5_VABI libScePsmUnknown_CN90Qhgm32U_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("CN90Qhgm32U", libScePsmUnknown_CN90Qhgm32U_nid_no_patch); + +int APS5_VABI libScePsmUnknown_Ia3_minus_a5X02RA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Ia3-a5X02RA", libScePsmUnknown_Ia3_minus_a5X02RA_nid_no_patch); + +int APS5_VABI libScePsmUnknown_IkQNwXle96Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("IkQNwXle96Q", libScePsmUnknown_IkQNwXle96Q_nid_no_patch); + +int APS5_VABI libScePsmUnknown_IyRVF5CAG48_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("IyRVF5CAG48", libScePsmUnknown_IyRVF5CAG48_nid_no_patch); + +int APS5_VABI libScePsmUnknown_KR9fL93LcnU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KR9fL93LcnU", libScePsmUnknown_KR9fL93LcnU_nid_no_patch); + +int APS5_VABI libScePsmUnknown_L4SntCPTllw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("L4SntCPTllw", libScePsmUnknown_L4SntCPTllw_nid_no_patch); + +int APS5_VABI libScePsmUnknown_N0_plus_F2yO3hRc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("N0+F2yO3hRc", libScePsmUnknown_N0_plus_F2yO3hRc_nid_no_patch); + +int APS5_VABI libScePsmUnknown_OJ_plus_3wLrf7Yk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OJ+3wLrf7Yk", libScePsmUnknown_OJ_plus_3wLrf7Yk_nid_no_patch); + +int APS5_VABI libScePsmUnknown_OVqKOEWE_minus_yc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OVqKOEWE-yc", libScePsmUnknown_OVqKOEWE_minus_yc_nid_no_patch); + +int APS5_VABI libScePsmUnknown_PGLOWFhZTtM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("PGLOWFhZTtM", libScePsmUnknown_PGLOWFhZTtM_nid_no_patch); + +int APS5_VABI libScePsmUnknown_PN1KQl3wnoU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("PN1KQl3wnoU", libScePsmUnknown_PN1KQl3wnoU_nid_no_patch); + +int APS5_VABI libScePsmUnknown_QXGdhj5nfpM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("QXGdhj5nfpM", libScePsmUnknown_QXGdhj5nfpM_nid_no_patch); + +int APS5_VABI libScePsmUnknown_SpdSLM7ONAk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("SpdSLM7ONAk", libScePsmUnknown_SpdSLM7ONAk_nid_no_patch); + +int APS5_VABI libScePsmUnknown_UDGJZZ9jrCE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UDGJZZ9jrCE", libScePsmUnknown_UDGJZZ9jrCE_nid_no_patch); + +int APS5_VABI libScePsmUnknown_UFkg2lqxKPQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("UFkg2lqxKPQ", libScePsmUnknown_UFkg2lqxKPQ_nid_no_patch); + +int APS5_VABI libScePsmUnknown_W8PzsJgkCGU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("W8PzsJgkCGU", libScePsmUnknown_W8PzsJgkCGU_nid_no_patch); + +int APS5_VABI libScePsmUnknown_WAKyoyoA8Dw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("WAKyoyoA8Dw", libScePsmUnknown_WAKyoyoA8Dw_nid_no_patch); + +int APS5_VABI libScePsmUnknown_XmlFvKZlm7k_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("XmlFvKZlm7k", libScePsmUnknown_XmlFvKZlm7k_nid_no_patch); + +int APS5_VABI libScePsmUnknown_YTcYAXVAW_plus_A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("YTcYAXVAW+A", libScePsmUnknown_YTcYAXVAW_plus_A_nid_no_patch); + +int APS5_VABI libScePsmUnknown_YnZFBPDyB2A_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("YnZFBPDyB2A", libScePsmUnknown_YnZFBPDyB2A_nid_no_patch); + +int APS5_VABI libScePsmUnknown_bGtKORAU2eE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bGtKORAU2eE", libScePsmUnknown_bGtKORAU2eE_nid_no_patch); + +int APS5_VABI libScePsmUnknown_c_minus_fS4SiII1Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("c-fS4SiII1Q", libScePsmUnknown_c_minus_fS4SiII1Q_nid_no_patch); + +int APS5_VABI libScePsmUnknown_cb_minus_Q6dnF1Uk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("cb-Q6dnF1Uk", libScePsmUnknown_cb_minus_Q6dnF1Uk_nid_no_patch); + +int APS5_VABI libScePsmUnknown_dsbtKMQDhjM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("dsbtKMQDhjM", libScePsmUnknown_dsbtKMQDhjM_nid_no_patch); + +int APS5_VABI libScePsmUnknown_e0WJestaOok_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("e0WJestaOok", libScePsmUnknown_e0WJestaOok_nid_no_patch); + +int APS5_VABI libScePsmUnknown_gW4wqd1GV8E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("gW4wqd1GV8E", libScePsmUnknown_gW4wqd1GV8E_nid_no_patch); + +int APS5_VABI libScePsmUnknown_iKBWRnPccBs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("iKBWRnPccBs", libScePsmUnknown_iKBWRnPccBs_nid_no_patch); + +int APS5_VABI libScePsmUnknown_j0tmOKH875I_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("j0tmOKH875I", libScePsmUnknown_j0tmOKH875I_nid_no_patch); + +int APS5_VABI libScePsmUnknown_jU_plus_doPzvPiQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jU+doPzvPiQ", libScePsmUnknown_jU_plus_doPzvPiQ_nid_no_patch); + +int APS5_VABI libScePsmUnknown_jcyBwXYskmc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("jcyBwXYskmc", libScePsmUnknown_jcyBwXYskmc_nid_no_patch); + +int APS5_VABI libScePsmUnknown_m7QCwDRxED4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("m7QCwDRxED4", libScePsmUnknown_m7QCwDRxED4_nid_no_patch); + +int APS5_VABI libScePsmUnknown_ni7QHsx30nw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ni7QHsx30nw", libScePsmUnknown_ni7QHsx30nw_nid_no_patch); + +int APS5_VABI libScePsmUnknown_owrMSr1b45c_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("owrMSr1b45c", libScePsmUnknown_owrMSr1b45c_nid_no_patch); + +int APS5_VABI libScePsmUnknown_t3a4oAqrvrI_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("t3a4oAqrvrI", libScePsmUnknown_t3a4oAqrvrI_nid_no_patch); + +int APS5_VABI libScePsmUnknown_tFmZ8R28vsE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("tFmZ8R28vsE", libScePsmUnknown_tFmZ8R28vsE_nid_no_patch); + +int APS5_VABI libScePsmUnknown_th1_minus_yYnLsw0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("th1-yYnLsw0", libScePsmUnknown_th1_minus_yYnLsw0_nid_no_patch); + +int APS5_VABI libScePsmUnknown_udthK0YXar0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("udthK0YXar0", libScePsmUnknown_udthK0YXar0_nid_no_patch); + +int APS5_VABI libScePsmUnknown_vClC0x57Nqs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("vClC0x57Nqs", libScePsmUnknown_vClC0x57Nqs_nid_no_patch); + +int APS5_VABI libScePsmUnknown_wDEygcO128w_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("wDEygcO128w", libScePsmUnknown_wDEygcO128w_nid_no_patch); + +int APS5_VABI libScePsmUnknown_xzd24XYJpb0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xzd24XYJpb0", libScePsmUnknown_xzd24XYJpb0_nid_no_patch); + +int APS5_VABI libScePsmUnknown_yemgMfc2oW8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("yemgMfc2oW8", libScePsmUnknown_yemgMfc2oW8_nid_no_patch); + +} diff --git a/core/libs/prx/libScePsm/include/InventoryExports.hpp b/core/libs/prx/libScePsm/include/InventoryExports.hpp new file mode 100644 index 0000000000..ea789cdc1b --- /dev/null +++ b/core/libs/prx/libScePsm/include/InventoryExports.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI libScePsmUnknown__minus_ndbidpphoc_nid_no_patch(); +int APS5_VABI libScePsmUnknown_0PIOM2UIUpQ_nid_no_patch(); +int APS5_VABI libScePsmUnknown_0Tit25nFAP4_nid_no_patch(); +int APS5_VABI libScePsmUnknown_12ZHf6Z7WLg_nid_no_patch(); +int APS5_VABI libScePsmUnknown_1Z9IoxZ508g_nid_no_patch(); +int APS5_VABI libScePsmUnknown_5DwG5treG0o_nid_no_patch(); +int APS5_VABI libScePsmUnknown_5dDsI2vg6dc_nid_no_patch(); +int APS5_VABI libScePsmUnknown_6FPQyIqTJi0_nid_no_patch(); +int APS5_VABI libScePsmUnknown_74_plus_2xQDzd_minus_c_nid_no_patch(); +int APS5_VABI libScePsmUnknown_8wWLk0PaYGQ_nid_no_patch(); +int APS5_VABI libScePsmUnknown_BaQ59CqSB34_nid_no_patch(); +int APS5_VABI libScePsmUnknown_C08zEeeiTBI_nid_no_patch(); +int APS5_VABI libScePsmUnknown_CN90Qhgm32U_nid_no_patch(); +int APS5_VABI libScePsmUnknown_Ia3_minus_a5X02RA_nid_no_patch(); +int APS5_VABI libScePsmUnknown_IkQNwXle96Q_nid_no_patch(); +int APS5_VABI libScePsmUnknown_IyRVF5CAG48_nid_no_patch(); +int APS5_VABI libScePsmUnknown_KR9fL93LcnU_nid_no_patch(); +int APS5_VABI libScePsmUnknown_L4SntCPTllw_nid_no_patch(); +int APS5_VABI libScePsmUnknown_N0_plus_F2yO3hRc_nid_no_patch(); +int APS5_VABI libScePsmUnknown_OJ_plus_3wLrf7Yk_nid_no_patch(); +int APS5_VABI libScePsmUnknown_OVqKOEWE_minus_yc_nid_no_patch(); +int APS5_VABI libScePsmUnknown_PGLOWFhZTtM_nid_no_patch(); +int APS5_VABI libScePsmUnknown_PN1KQl3wnoU_nid_no_patch(); +int APS5_VABI libScePsmUnknown_QXGdhj5nfpM_nid_no_patch(); +int APS5_VABI libScePsmUnknown_SpdSLM7ONAk_nid_no_patch(); +int APS5_VABI libScePsmUnknown_UDGJZZ9jrCE_nid_no_patch(); +int APS5_VABI libScePsmUnknown_UFkg2lqxKPQ_nid_no_patch(); +int APS5_VABI libScePsmUnknown_W8PzsJgkCGU_nid_no_patch(); +int APS5_VABI libScePsmUnknown_WAKyoyoA8Dw_nid_no_patch(); +int APS5_VABI libScePsmUnknown_XmlFvKZlm7k_nid_no_patch(); +int APS5_VABI libScePsmUnknown_YTcYAXVAW_plus_A_nid_no_patch(); +int APS5_VABI libScePsmUnknown_YnZFBPDyB2A_nid_no_patch(); +int APS5_VABI libScePsmUnknown_bGtKORAU2eE_nid_no_patch(); +int APS5_VABI libScePsmUnknown_c_minus_fS4SiII1Q_nid_no_patch(); +int APS5_VABI libScePsmUnknown_cb_minus_Q6dnF1Uk_nid_no_patch(); +int APS5_VABI libScePsmUnknown_dsbtKMQDhjM_nid_no_patch(); +int APS5_VABI libScePsmUnknown_e0WJestaOok_nid_no_patch(); +int APS5_VABI libScePsmUnknown_gW4wqd1GV8E_nid_no_patch(); +int APS5_VABI libScePsmUnknown_iKBWRnPccBs_nid_no_patch(); +int APS5_VABI libScePsmUnknown_j0tmOKH875I_nid_no_patch(); +int APS5_VABI libScePsmUnknown_jU_plus_doPzvPiQ_nid_no_patch(); +int APS5_VABI libScePsmUnknown_jcyBwXYskmc_nid_no_patch(); +int APS5_VABI libScePsmUnknown_m7QCwDRxED4_nid_no_patch(); +int APS5_VABI libScePsmUnknown_ni7QHsx30nw_nid_no_patch(); +int APS5_VABI libScePsmUnknown_owrMSr1b45c_nid_no_patch(); +int APS5_VABI libScePsmUnknown_t3a4oAqrvrI_nid_no_patch(); +int APS5_VABI libScePsmUnknown_tFmZ8R28vsE_nid_no_patch(); +int APS5_VABI libScePsmUnknown_th1_minus_yYnLsw0_nid_no_patch(); +int APS5_VABI libScePsmUnknown_udthK0YXar0_nid_no_patch(); +int APS5_VABI libScePsmUnknown_vClC0x57Nqs_nid_no_patch(); +int APS5_VABI libScePsmUnknown_wDEygcO128w_nid_no_patch(); +int APS5_VABI libScePsmUnknown_xzd24XYJpb0_nid_no_patch(); +int APS5_VABI libScePsmUnknown_yemgMfc2oW8_nid_no_patch(); + +} diff --git a/core/libs/prx/libScePsm/libScePsm.hpp b/core/libs/prx/libScePsm/libScePsm.hpp index 8ba5c40198..60c94a8516 100644 --- a/core/libs/prx/libScePsm/libScePsm.hpp +++ b/core/libs/prx/libScePsm/libScePsm.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libScePsm/include/Api.hpp" +#include "prx/libScePsm/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceRazorCpu_debug/Export.cpp b/core/libs/prx/libSceRazorCpu_debug/Export.cpp index d81f4a5e32..f0a2e8d852 100644 --- a/core/libs/prx/libSceRazorCpu_debug/Export.cpp +++ b/core/libs/prx/libSceRazorCpu_debug/Export.cpp @@ -1,10 +1,24 @@ -#include +#include "prx/libSceRazorCpu_debug/include/Api.hpp" #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI sceRazorCpuStartCapture(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRazorCpuStopCapture(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceRazorCpuSetLiveServerThreadSettings(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceRazorCpu_debug/include/Api.hpp b/core/libs/prx/libSceRazorCpu_debug/include/Api.hpp new file mode 100644 index 0000000000..b000681917 --- /dev/null +++ b/core/libs/prx/libSceRazorCpu_debug/include/Api.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceRazorCpuStartCapture(void); +int APS5_VABI sceRazorCpuStopCapture(void); +int APS5_VABI sceRazorCpuSetLiveServerThreadSettings(void); + +} diff --git a/core/libs/prx/libSceRazorCpu_debug/libSceRazorCpu_debug.hpp b/core/libs/prx/libSceRazorCpu_debug/libSceRazorCpu_debug.hpp index 6f70f09bee..eb2f0609ae 100644 --- a/core/libs/prx/libSceRazorCpu_debug/libSceRazorCpu_debug.hpp +++ b/core/libs/prx/libSceRazorCpu_debug/libSceRazorCpu_debug.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libSceRazorCpu_debug/include/Api.hpp" diff --git a/core/libs/prx/libSceRnpsBundle/Export.cpp b/core/libs/prx/libSceRnpsBundle/Export.cpp new file mode 100644 index 0000000000..eca7dbffdc --- /dev/null +++ b/core/libs/prx/libSceRnpsBundle/Export.cpp @@ -0,0 +1,26 @@ +#include "prx/libSceRnpsBundle/include/InventoryExports.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceRnpsBundleInit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRnpsBundleInitEx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRnpsBundleTerm() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRnpsBundleVerifyAndDecrypt() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceRnpsBundleVerifyChain() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceRnpsBundle/include/InventoryExports.hpp b/core/libs/prx/libSceRnpsBundle/include/InventoryExports.hpp new file mode 100644 index 0000000000..3f988a4d4d --- /dev/null +++ b/core/libs/prx/libSceRnpsBundle/include/InventoryExports.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceRnpsBundleInit(); +int APS5_VABI sceRnpsBundleInitEx(); +int APS5_VABI sceRnpsBundleTerm(); +int APS5_VABI sceRnpsBundleVerifyAndDecrypt(); +int APS5_VABI sceRnpsBundleVerifyChain(); + +} diff --git a/core/libs/prx/libSceRnpsBundle/libSceRnpsBundle.hpp b/core/libs/prx/libSceRnpsBundle/libSceRnpsBundle.hpp new file mode 100644 index 0000000000..d8dfc47233 --- /dev/null +++ b/core/libs/prx/libSceRnpsBundle/libSceRnpsBundle.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceRnpsBundle/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceRtc/Export.cpp b/core/libs/prx/libSceRtc/Export.cpp index 4f794fddb8..249c9a8961 100644 --- a/core/libs/prx/libSceRtc/Export.cpp +++ b/core/libs/prx/libSceRtc/Export.cpp @@ -145,9 +145,11 @@ std::uint64_t currentTick(std::size_t clock = 0) { int setCurrentTick(std::size_t clock, const RtcTick* tick) { if (!tick) return SCE_RTC_ERROR_INVALID_POINTER; - if (tick->tick < UNIX_EPOCH_TICK) return SCE_RTC_ERROR_INVALID_VALUE; + RtcTick source{}; + if (!GuestCopyIn_nid_no_patch(&source, tick, sizeof(source))) return SCE_RTC_ERROR_INVALID_POINTER; + if (source.tick < UNIX_EPOCH_TICK) return SCE_RTC_ERROR_INVALID_VALUE; std::lock_guard lock(clockMutex); - clocks[clock] = ClockState{tick->tick, std::chrono::steady_clock::now(), true}; + clocks[clock] = ClockState{source.tick, std::chrono::steady_clock::now(), true}; return 0; } @@ -447,13 +449,15 @@ int APS5_VABI sceRtcConvertTickToDateTime(const RtcTick* tick, RtcDateTime* time int APS5_VABI sceRtcGetCurrentTick(RtcTick* tick) { if (!tick) return SCE_RTC_ERROR_INVALID_POINTER; - tick->tick = currentTick(); + const RtcTick value{currentTick()}; + if (!GuestCopyOut_nid_no_patch(tick, &value, sizeof(value))) return SCE_RTC_ERROR_INVALID_POINTER; return 0; } int APS5_VABI sceRtcGetCurrentNetworkTick(RtcTick* tick) { if (!tick) return SCE_RTC_ERROR_INVALID_POINTER; - tick->tick = currentTick(1); + const RtcTick value{currentTick(1)}; + if (!GuestCopyOut_nid_no_patch(tick, &value, sizeof(value))) return SCE_RTC_ERROR_INVALID_POINTER; return 0; } @@ -461,7 +465,8 @@ int APS5_VABI sceRtcGetCurrentClock(RtcDateTime* time, int time_zone_minutes) { if (!time) return SCE_RTC_ERROR_INVALID_POINTER; RtcTick tick{currentTick()}; if (const int result = addTicks(&tick, &tick, time_zone_minutes, TICKS_PER_MINUTE); result != 0) return result; - *time = fromTick(tick.tick); + const RtcDateTime converted = fromTick(tick.tick); + if (!GuestCopyOut_nid_no_patch(time, &converted, sizeof(converted))) return SCE_RTC_ERROR_INVALID_POINTER; return 0; } @@ -471,7 +476,8 @@ int APS5_VABI sceRtcGetCurrentClockLocalTime(RtcDateTime* time) { const RtcTick utc{currentTick()}; if (utc.tick > MAX_TICK) return SCE_RTC_ERROR_INVALID_VALUE; if (const int result = addTicks(&local, &utc, localOffsetSeconds(utc.tick), TICKS_PER_SECOND); result != 0) return result; - *time = fromTick(local.tick); + const RtcDateTime converted = fromTick(local.tick); + if (!GuestCopyOut_nid_no_patch(time, &converted, sizeof(converted))) return SCE_RTC_ERROR_INVALID_POINTER; return 0; } @@ -580,8 +586,8 @@ int APS5_VABI sceRtcFormatRFC3339(char* dateTime, const RtcTick* utc, int timeZo 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); + if (time.year < 1 || time.year > 9999) return SCE_RTC_ERROR_INVALID_YEAR; 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"); @@ -612,11 +618,11 @@ int APS5_VABI sceRtcFormatRFC2822(char* dateTime, const RtcTick* utc, int timeZo } 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); + if (static_cast(local.tick) >= 0 && (time.year < 1 || time.year > 9999)) return SCE_RTC_ERROR_INVALID_YEAR; + if (static_cast(utc->tick) < 0 || static_cast(local.tick) < 0) return SCE_RTC_ERROR_INVALID_VALUE; 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, + std::snprintf(dateTime, 32, "%s, %02u %s %04u %02u:%02u:%02u %c%02d%02d", WEEKDAY_NAMES[(daysFromCivil(time.year, time.month, time.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; } @@ -703,19 +709,22 @@ int APS5_VABI sceRtcCompareTick(const RtcTick* first, const RtcTick* second) { int APS5_VABI sceRtcGetCurrentRawNetworkTick(RtcTick* tick) { if (!tick) return SCE_RTC_ERROR_INVALID_POINTER; - tick->tick = currentTick(4); + const RtcTick value{currentTick(4)}; + if (!GuestCopyOut_nid_no_patch(tick, &value, sizeof(value))) return SCE_RTC_ERROR_INVALID_POINTER; return 0; } int APS5_VABI sceRtcGetCurrentAdNetworkTick(RtcTick* tick) { if (!tick) return SCE_RTC_ERROR_INVALID_POINTER; - tick->tick = currentTick(2); + const RtcTick value{currentTick(2)}; + if (!GuestCopyOut_nid_no_patch(tick, &value, sizeof(value))) return SCE_RTC_ERROR_INVALID_POINTER; return 0; } int APS5_VABI sceRtcGetCurrentDebugNetworkTick(RtcTick* tick) { if (!tick) return SCE_RTC_ERROR_INVALID_POINTER; - tick->tick = currentTick(3); + const RtcTick value{currentTick(3)}; + if (!GuestCopyOut_nid_no_patch(tick, &value, sizeof(value))) return SCE_RTC_ERROR_INVALID_POINTER; return 0; } @@ -758,11 +767,11 @@ int APS5_VABI sceRtcFormatRFC3339Precise(char* date_time, const RtcTick* utc, in if (utc->tick < shift) return SCE_RTC_ERROR_INVALID_VALUE; localTick = utc->tick - shift; } else { - if (utc->tick > MAX_TICK || MAX_TICK - utc->tick < shift) return SCE_RTC_ERROR_INVALID_YEAR; + if (utc->tick > std::numeric_limits::max() - shift) return SCE_RTC_ERROR_INVALID_VALUE; localTick = utc->tick + shift; } - if (localTick > MAX_TICK) return SCE_RTC_ERROR_INVALID_YEAR; const RtcDateTime time = fromTick(localTick); + if (time.year < 1 || time.year > 9999) return SCE_RTC_ERROR_INVALID_YEAR; int written = std::snprintf(date_time, 20, "%04u-%02u-%02uT%02u:%02u:%02u", time.year, time.month, time.day, time.hour, time.minute, time.second); if (precision != 0) { std::uint32_t divisor = 1; diff --git a/core/libs/prx/libSceRudp/Export.cpp b/core/libs/prx/libSceRudp/Export.cpp index 864ddbc007..a1f0d0a17f 100644 --- a/core/libs/prx/libSceRudp/Export.cpp +++ b/core/libs/prx/libSceRudp/Export.cpp @@ -195,7 +195,8 @@ int APS5_VABI sceRudpActivate(std::int32_t contextId, const void* address, std:: 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__); + NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceRudpGetStatus(void* status, std::size_t size) { diff --git a/core/libs/prx/libSceSaveData.native/CMakeLists.txt b/core/libs/prx/libSceSaveData.native/CMakeLists.txt index 1d30e2d536..59a990a35d 100644 --- a/core/libs/prx/libSceSaveData.native/CMakeLists.txt +++ b/core/libs/prx/libSceSaveData.native/CMakeLists.txt @@ -41,9 +41,20 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_savedata_event_callback_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataEventCallback.cpp) + target_include_directories(guest_savedata_event_callback_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_savedata_event_callback_tests PRIVATE libSceSaveData.native libkernel libc) + configure_windows_unwind(guest_savedata_event_callback_tests) + add_test(NAME guest_savedata_event_callback COMMAND guest_savedata_event_callback_tests) + set_tests_properties(guest_savedata_event_callback PROPERTIES TIMEOUT 60) + if(WIN32) + set_tests_properties(guest_savedata_event_callback PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_savedata_ps4_tests ${LIBS_INCLUDE_DIR}/tests/GuestSaveDataPs4.cpp) target_include_directories(guest_savedata_ps4_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_savedata_ps4_tests PRIVATE libSceSaveData.native libc) + target_link_libraries(guest_savedata_ps4_tests PRIVATE libSceSaveData.native libc ${CMAKE_DL_LIBS}) configure_windows_unwind(guest_savedata_ps4_tests) add_test(NAME guest_savedata_ps4 COMMAND guest_savedata_ps4_tests) if(WIN32) diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index 644b421f49..c3bc7d2071 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -2,10 +2,13 @@ #include "prx/libSceSaveData/include/Api.hpp" #include #include +#include +#include #include #include #include #include +#include #include #include #include @@ -18,11 +21,19 @@ #include #include #include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#endif #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/RemoveTree.hpp" #include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" +#include "prx/libkernel/include/Api.hpp" #include "SaveData.hpp" #include "prx/libSceSaveData.native/include/SaveDataHostSpace.hpp" #include "prx/libSceSaveData/SaveDataFile.hpp" @@ -49,9 +60,110 @@ static std::string save_root() { // Events the title polls with sceSaveDataGetEventResult; a backup completes at once here. static std::mutex g_state_mutex; static std::deque g_events; +constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP = 1; constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_BACKUP = 2; constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_CONVERT = 5; +namespace { + +struct EventDelivery { + SaveDataEvent event; + SaveDataEventCallback callback; + void* userdata; +}; + +struct EventWorker { + Pthread thread = nullptr; + bool stopping = false; +}; + +struct EventCallbacks { + std::recursive_mutex calling; + std::mutex mutex; + std::condition_variable wake; + SaveDataEventCallback callback = nullptr; + void* userdata = nullptr; + std::deque pending; + EventWorker* worker = nullptr; + Pthread delivering = nullptr; +}; + +EventCallbacks& Callbacks() { + static auto* callbacks = new EventCallbacks; + return *callbacks; +} + +void* APS5_VABI DeliverEvents(void* argument) { + auto& callbacks = Callbacks(); + auto* worker = static_cast(argument); + for (;;) { + { + std::unique_lock lock(callbacks.mutex); + callbacks.wake.wait(lock, [&] { return worker->stopping || !callbacks.pending.empty(); }); + if (worker->stopping) return nullptr; + } + std::lock_guard calling(callbacks.calling); + EventDelivery delivery{}; + { + std::lock_guard lock(callbacks.mutex); + if (worker->stopping) return nullptr; + if (callbacks.pending.empty()) continue; + delivery = callbacks.pending.front(); + callbacks.pending.pop_front(); + callbacks.delivering = scePthreadSelf(); + } + delivery.callback(&delivery.event, delivery.userdata); + std::lock_guard lock(callbacks.mutex); + callbacks.delivering = nullptr; + } +} + +void StartEventDelivery() { + auto& callbacks = Callbacks(); + auto worker = std::make_unique(); + std::lock_guard lock(callbacks.mutex); + if (callbacks.worker != nullptr) return; + if (scePthreadCreate(&worker->thread, nullptr, DeliverEvents, worker.get(), "SceSaveDataEventCallback") != 0) { + throw std::runtime_error("sceSaveDataRegisterEventCallback: scePthreadCreate failed for the event callback thread"); + } + callbacks.worker = worker.release(); +} + +EventWorker* DetachEventDelivery() { + auto& callbacks = Callbacks(); + std::lock_guard lock(callbacks.mutex); + callbacks.callback = nullptr; + callbacks.userdata = nullptr; + callbacks.pending.clear(); + auto* worker = std::exchange(callbacks.worker, nullptr); + if (worker != nullptr) worker->stopping = true; + return worker; +} + +void JoinEventDelivery(EventWorker* worker) { + std::unique_ptr owned(worker); + Callbacks().wake.notify_all(); + if (scePthreadJoin(owned->thread, nullptr) != 0) throw std::runtime_error("sceSaveDataTerminate: scePthreadJoin failed for the event callback thread"); +} + +bool InEventCallback() { + auto& callbacks = Callbacks(); + std::lock_guard lock(callbacks.mutex); + return callbacks.delivering != nullptr && scePthreadEqual(scePthreadSelf(), callbacks.delivering) != 0; +} + +void DeliverEvent(const SaveDataEvent& event) { + auto& callbacks = Callbacks(); + { + std::lock_guard lock(callbacks.mutex); + if (callbacks.callback == nullptr || callbacks.worker == nullptr) return; + callbacks.pending.push_back({event, callbacks.callback, callbacks.userdata}); + } + callbacks.wake.notify_all(); +} + +} + static bool dir_name_match(const char* str, const char* pattern) { if (pattern == nullptr || pattern[0] == '\0') { return true; @@ -313,8 +425,11 @@ int APS5_VABI sceSaveDataBackup(const SaveDataBackup* backup) { event.user_id = backup->user_id; if (backup->title_id != nullptr) event.title_id = *backup->title_id; event.dir_name = *backup->dir_name; - std::lock_guard lock(g_state_mutex); - g_events.push_back(event); + { + std::lock_guard lock(g_state_mutex); + g_events.push_back(event); + } + DeliverEvent(event); return SAVE_DATA_OK; } @@ -333,6 +448,10 @@ static std::string blocks_path(const std::string& real_path) { return real_path + ".blocks"; } +static bool isValidMountPoint(const SaveDataMountPoint* mount_point) { + return mount_point != nullptr && std::memchr(mount_point->data, '\0', sizeof(mount_point->data)) != nullptr; +} + static bool isMountedDirectory(const std::string& path, std::error_code& error) { for (const auto& slot : g_slots) { if (!slot.used) continue; @@ -351,16 +470,301 @@ static bool isMountedDirectory(const std::string& path, std::error_code& error) return false; } +static constexpr char BACKUP_DIR[] = "_sd_backup"; +static constexpr char BACKUP_STAGING_DIR[] = "_sd_backup_tmp"; +static constexpr char BACKUP_OLD_DIR[] = "_sd_backup_old"; +static constexpr char RESTORE_STAGING_DIR[] = "_sd_restore_tmp"; +static constexpr char RESTORE_OLD_DIR[] = "_sd_restore_old"; + +static std::atomic g_file_step_hook{nullptr}; + +void SaveDataSetFileStepHook_nid_no_patch(bool (*hook)(const char* step, const char* path)) { + g_file_step_hook.store(hook, std::memory_order_release); +} + +static void file_step(const char* step, const std::string& path) { + const auto hook = g_file_step_hook.load(std::memory_order_acquire); + if (hook != nullptr && !hook(step, path.c_str())) { + throw std::filesystem::filesystem_error(std::string("SaveData: ") + step + " failed", path, std::make_error_code(std::errc::io_error)); + } +} + +static bool is_link(const std::filesystem::path& path, const std::filesystem::file_status& status) { +#ifdef _WIN32 + const DWORD attributes = GetFileAttributesW(path.c_str()); + if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + return true; + } +#endif + return std::filesystem::is_symlink(status); +} + +static std::filesystem::file_type plain_type(const std::string& path) { + std::error_code error; + const auto status = std::filesystem::symlink_status(path, error); + if (status.type() == std::filesystem::file_type::not_found) { + return std::filesystem::file_type::not_found; + } + if (error) { + throw std::filesystem::filesystem_error("SaveData: cannot read the type of", path, error); + } + if (is_link(path, status) || (status.type() != std::filesystem::file_type::regular && status.type() != std::filesystem::file_type::directory)) { + throw std::filesystem::filesystem_error("SaveData: not a plain file or directory", path, std::make_error_code(std::errc::operation_not_permitted)); + } + return status.type(); +} + +static void require_plain_tree(const std::string& path) { + if (plain_type(path) != std::filesystem::file_type::directory) { + return; + } + for (const auto& entry : std::filesystem::directory_iterator(path)) { + require_plain_tree(entry.path().string()); + } +} + +static void require_inside(const std::string& root, const std::string& path) { + const auto base = std::filesystem::weakly_canonical(std::filesystem::absolute(root)); + const auto target = std::filesystem::weakly_canonical(std::filesystem::absolute(path)); + const auto [inBase, inTarget] = std::mismatch(base.begin(), base.end(), target.begin(), target.end()); + if (inBase != base.end() || inTarget == target.end()) { + throw std::filesystem::filesystem_error("SaveData: a save path leaves its root", path, root, std::make_error_code(std::errc::operation_not_permitted)); + } +} + +static void copy_file_step(const char* step, const std::string& from, const std::string& to) { + if (plain_type(from) != std::filesystem::file_type::regular) { + throw std::filesystem::filesystem_error("SaveData: not a plain file", from, std::make_error_code(std::errc::operation_not_permitted)); + } + file_step(step, to); + std::filesystem::copy_file(from, to); +} + +static void copy_tree(const char* step, const std::string& from, const std::string& to) { + file_step(step, to); + std::filesystem::create_directory(to); + for (const auto& entry : std::filesystem::directory_iterator(from)) { + const std::string source = entry.path().string(); + const std::string target = to + "/" + entry.path().filename().string(); + if (plain_type(source) == std::filesystem::file_type::directory) { + copy_tree(step, source, target); + } else { + copy_file_step(step, source, target); + } + } +} + +static std::map tree_sizes(const std::string& root) { + std::map sizes; + for (const auto& entry : std::filesystem::recursive_directory_iterator(root)) { + sizes[entry.path().lexically_relative(root).generic_string()] = entry.is_directory() ? std::numeric_limits::max() : entry.file_size(); + } + return sizes; +} + +static void flush_path(const std::string& path, bool directory) { +#ifdef _WIN32 + if (directory) { + return; + } + const HANDLE handle = CreateFileW(std::filesystem::path(path).c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle == INVALID_HANDLE_VALUE) { + throw std::filesystem::filesystem_error("SaveData: cannot open to flush", path, std::error_code(static_cast(GetLastError()), std::system_category())); + } + const bool flushed = FlushFileBuffers(handle) != 0; + const DWORD error = GetLastError(); + CloseHandle(handle); + if (!flushed) { + throw std::filesystem::filesystem_error("SaveData: cannot flush", path, std::error_code(static_cast(error), std::system_category())); + } +#else + const int descriptor = ::open(path.c_str(), O_RDONLY | O_CLOEXEC | (directory ? O_DIRECTORY : 0)); + if (descriptor < 0) { + throw std::filesystem::filesystem_error("SaveData: cannot open to flush", path, std::error_code(errno, std::generic_category())); + } + const int result = ::fsync(descriptor); + const int error = errno; + ::close(descriptor); + if (result != 0 && error != EINVAL) { + throw std::filesystem::filesystem_error("SaveData: cannot flush", path, std::error_code(error, std::generic_category())); + } +#endif +} + +static void flush_tree(const std::string& root) { + for (const auto& entry : std::filesystem::recursive_directory_iterator(root)) { + flush_path(entry.path().string(), entry.is_directory()); + } + flush_path(root, true); +} + +struct FileSwap { + std::vector> moved; + + void move(const char* step, const std::string& from, const std::string& to) { + file_step(step, to); + std::filesystem::rename(from, to); + moved.emplace_back(from, to); + flush_path(std::filesystem::path(from).parent_path().string(), true); + flush_path(std::filesystem::path(to).parent_path().string(), true); + } + + void undo(const char* step) { + while (!moved.empty()) { + const auto [from, to] = moved.back(); + file_step(step, from); + std::filesystem::rename(to, from); + moved.pop_back(); + flush_path(std::filesystem::path(from).parent_path().string(), true); + flush_path(std::filesystem::path(to).parent_path().string(), true); + } + } +}; + +static std::string backup_entry(const char* root, const std::string& dirName) { + return std::string(root) + "/" + dirName; +} + +static std::string entry_save(const std::string& entry) { + return entry + "/save"; +} + +static void require_plain_paths(const std::string& dirName, std::initializer_list roots) { + for (const char* root : roots) { + if (plain_type(root) == std::filesystem::file_type::regular) { + throw std::filesystem::filesystem_error("SaveData: a save root is not a directory", root, std::make_error_code(std::errc::not_a_directory)); + } + const std::string entry = backup_entry(root, dirName); + for (const std::string& path : {entry, param_path(entry), blocks_path(entry)}) { + require_inside(root, path); + require_plain_tree(path); + } + } +} + +static bool parse_dir_name(const SceSaveDataDirName* name, std::string* dirName) { + const auto* nameEnd = static_cast(std::memchr(name->data, '\0', sizeof(name->data))); + if (nameEnd == nullptr) { + return false; + } + dirName->assign(name->data, static_cast(nameEnd - name->data)); + return !dirName->empty() && *dirName != "." && *dirName != ".." && dirName->find_first_of("/\\:") == std::string::npos; +} + +static bool running_title(const SceSaveDataTitleId* titleId) { + return titleId == nullptr || std::strncmp(titleId->data, GetAppTitleId_nid_postfix().value, sizeof(titleId->data)) == 0; +} + +static int read_backup(const std::string& dirName, SaveDataParam* param) { + const std::string entry = backup_entry(BACKUP_DIR, dirName); + std::error_code error; + if (!std::filesystem::exists(entry, error)) { + return error ? SAVE_DATA_ERROR_INTERNAL : SAVE_DATA_ERROR_NOT_FOUND; + } + const std::string backup = entry_save(entry); + std::vector bytes; + if (!std::filesystem::is_directory(backup, error) || !std::filesystem::is_regular_file(param_path(backup), error) || !read_file_all(param_path(backup), bytes) || bytes.size() != sizeof(SaveDataParam)) { + return SAVE_DATA_ERROR_INTERNAL; + } + const auto blocks = std::filesystem::status(blocks_path(backup), error).type(); + if (blocks != std::filesystem::file_type::not_found && blocks != std::filesystem::file_type::regular) { + return SAVE_DATA_ERROR_INTERNAL; + } + if (param != nullptr) { + std::memcpy(param, bytes.data(), sizeof(SaveDataParam)); + } + return SAVE_DATA_OK; +} + +static void backupSave(const std::string& dirName) { + const std::string source = save_root() + "/" + dirName; + const std::string entry = backup_entry(BACKUP_DIR, dirName); + const std::string staged = backup_entry(BACKUP_STAGING_DIR, dirName); + const std::string old = backup_entry(BACKUP_OLD_DIR, dirName); + require_plain_paths(dirName, {SAVE_DIR, BACKUP_DIR, BACKUP_STAGING_DIR, BACKUP_OLD_DIR}); + RemoveTree(staged); + RemoveTree(old); + std::filesystem::create_directories(staged); + FileSwap swap; + try { + copy_tree("backup.copy", source, entry_save(staged)); + file_step("backup.copy", param_path(entry_save(staged))); + if (!store_param(entry_save(staged), load_param(source))) { + throw std::filesystem::filesystem_error("SaveData: cannot write the backup's param file", param_path(entry_save(staged)), std::make_error_code(std::errc::io_error)); + } + if (std::filesystem::exists(blocks_path(source))) { + copy_file_step("backup.copy", blocks_path(source), blocks_path(entry_save(staged))); + } + flush_tree(staged); + std::filesystem::create_directories(BACKUP_DIR); + if (std::filesystem::exists(entry)) { + std::filesystem::create_directories(BACKUP_OLD_DIR); + swap.move("backup.aside", entry, old); + } + swap.move("backup.place", staged, entry); + } catch (...) { + swap.undo("backup.rollback"); + std::error_code ignored; + RemoveTree(staged, ignored); + throw; + } + std::error_code ignored; + RemoveTree(old, ignored); +} + +static void restoreSave(const std::string& dirName) { + const std::string live = save_root() + "/" + dirName; + const std::string backupEntry = backup_entry(BACKUP_DIR, dirName); + const std::string backup = entry_save(backupEntry); + const std::string stagedEntry = backup_entry(RESTORE_STAGING_DIR, dirName); + const std::string oldEntry = backup_entry(RESTORE_OLD_DIR, dirName); + const std::string staged = entry_save(stagedEntry); + const std::string old = entry_save(oldEntry); + require_plain_paths(dirName, {SAVE_DIR, BACKUP_DIR, RESTORE_STAGING_DIR, RESTORE_OLD_DIR}); + RemoveTree(stagedEntry); + RemoveTree(oldEntry); + std::filesystem::create_directories(stagedEntry); + std::filesystem::create_directories(oldEntry); + FileSwap swap; + try { + copy_tree("restore.copy", backup, staged); + copy_file_step("restore.copy", param_path(backup), param_path(staged)); + if (std::filesystem::exists(blocks_path(backup))) { + copy_file_step("restore.copy", blocks_path(backup), blocks_path(staged)); + } + if (tree_sizes(stagedEntry) != tree_sizes(backupEntry)) { + throw std::filesystem::filesystem_error("SaveData: the staged copy of the backup is incomplete", stagedEntry, std::make_error_code(std::errc::io_error)); + } + flush_tree(stagedEntry); + swap.move("restore.aside", live, old); + for (const auto& [from, to] : {std::pair{param_path(live), param_path(old)}, std::pair{blocks_path(live), blocks_path(old)}}) { + if (std::filesystem::exists(from)) { + swap.move("restore.aside", from, to); + } + } + swap.move("restore.place", staged, live); + swap.move("restore.place", param_path(staged), param_path(live)); + if (std::filesystem::exists(blocks_path(staged))) { + swap.move("restore.place", blocks_path(staged), blocks_path(live)); + } + } catch (...) { + swap.undo("restore.rollback"); + std::error_code ignored; + RemoveTree(stagedEntry, ignored); + RemoveTree(oldEntry, ignored); + throw; + } + std::error_code ignored; + RemoveTree(oldEntry, ignored); + RemoveTree(stagedEntry, ignored); +} + static int deleteSave(const SaveDataDelete* del) { if (del == nullptr || del->dir_name == nullptr) { 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) { - return SAVE_DATA_ERROR_PARAMETER; - } - const std::string dirName(del->dir_name->data, static_cast(nameEnd - del->dir_name->data)); - if (dirName.empty() || dirName == "." || dirName == ".." || dirName.find_first_of("/\\:") != std::string::npos) { + std::string dirName; + if (!parse_dir_name(del->dir_name, &dirName)) { return SAVE_DATA_ERROR_PARAMETER; } const std::string path = save_root() + "/" + dirName; @@ -374,6 +778,7 @@ static int deleteSave(const SaveDataDelete* del) { std::error_code ec; std::filesystem::remove(param_path(path), ec); std::filesystem::remove(blocks_path(path), ec); + RemoveTree(backup_entry(BACKUP_DIR, dirName)); return SAVE_DATA_OK; } @@ -554,7 +959,7 @@ int APS5_VABI sceSaveDataGetEventResult(const void* event_param, SaveDataEvent* } static int getMountInfo(const SaveDataMountPoint* mount_point, SaveDataMountInfo* info) { - if (mount_point == nullptr || info == nullptr) { + if (!isValidMountPoint(mount_point) || info == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } std::lock_guard lock(g_slots_mutex); @@ -571,12 +976,12 @@ static int getMountInfo(const SaveDataMountPoint* mount_point, SaveDataMountInfo int APS5_VABI sceSaveDataGetMountInfo(const SaveDataMountPoint* mount_point, SaveDataMountInfo* info) { const int rc = getMountInfo(mount_point, info); - SAVEDATA_TRACE("getMountInfo point=%s -> 0x%08x", mount_point != nullptr ? mount_point->data : "(null)", static_cast(rc)); + SAVEDATA_TRACE("getMountInfo point=%.*s -> 0x%08x", static_cast(sizeof(SaveDataMountPoint::data)), mount_point != nullptr ? mount_point->data : "(null)", static_cast(rc)); return rc; } static int getParam(const SaveDataMountPoint* mount_point, uint32_t param_type, void* param_buf, size_t param_buf_size, size_t* got_size) { - if (mount_point == nullptr || param_buf == nullptr) { + if (!isValidMountPoint(mount_point) || param_buf == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } std::lock_guard lock(g_slots_mutex); @@ -598,7 +1003,7 @@ static int getParam(const SaveDataMountPoint* mount_point, uint32_t param_type, int APS5_VABI sceSaveDataGetParam(const SaveDataMountPoint* mount_point, uint32_t param_type, void* param_buf, size_t param_buf_size, size_t* got_size) { const int rc = getParam(mount_point, param_type, param_buf, param_buf_size, got_size); - SAVEDATA_TRACE("getParam point=%s type=%u size=%zu -> 0x%08x got=%zu", mount_point != nullptr ? mount_point->data : "(null)", param_type, param_buf_size, static_cast(rc), got_size != nullptr ? *got_size : static_cast(0)); + SAVEDATA_TRACE("getParam point=%.*s type=%u size=%zu -> 0x%08x got=%zu", static_cast(sizeof(SaveDataMountPoint::data)), mount_point != nullptr ? mount_point->data : "(null)", param_type, param_buf_size, static_cast(rc), got_size != nullptr ? *got_size : static_cast(0)); return rc; } @@ -653,13 +1058,14 @@ int APS5_VABI sceSaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) { int APS5_VABI sceSaveDataInitialize3(const void* init) { (void)init; + std::lock_guard lock(g_slots_mutex); ++g_initializations; return SAVE_DATA_OK; } int APS5_VABI sceSaveDataLoadIcon(const SaveDataMountPoint* mount_point, SaveDataIcon* icon) { (void)icon; - if (mount_point == nullptr) { + if (!isValidMountPoint(mount_point)) { return SAVE_DATA_ERROR_PARAMETER; } std::lock_guard lock(g_slots_mutex); @@ -685,12 +1091,8 @@ static int mount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result if (!create && !create2 && !open) { throw std::runtime_error("sceSaveDataMount3: unknown mount_mode"); } - const auto* nameEnd = static_cast(std::memchr(mount->dir_name->data, '\0', sizeof(mount->dir_name->data))); - if (nameEnd == nullptr) { - return SAVE_DATA_ERROR_PARAMETER; - } - const std::string dirName(mount->dir_name->data, static_cast(nameEnd - mount->dir_name->data)); - if (dirName.empty() || dirName == "." || dirName == ".." || dirName.find_first_of("/\\:") != std::string::npos) { + std::string dirName; + if (!parse_dir_name(mount->dir_name, &dirName)) { return SAVE_DATA_ERROR_PARAMETER; } const std::string real_path = save_root() + "/" + dirName; @@ -743,6 +1145,7 @@ static int mount3(const SaveDataMount3* mount, SaveDataMountResult* mount_result AddPathAlias_nid_no_patch(mountPoint.c_str(), absolutePath.c_str()); std::memset(mount_result, 0, sizeof(*mount_result)); g_slots[slot].used = true; + g_slots[slot].user_id = mount->user_id; g_slots[slot].mount_point = mountPoint; g_slots[slot].real_path = real_path; std::memcpy(mount_result->mount_point.data, mountPoint.c_str(), mountPoint.size() + 1); @@ -757,6 +1160,40 @@ int APS5_VABI sceSaveDataMount3(const SaveDataMount3* mount, SaveDataMountResult return rc; } +static int mount2(const SaveDataMount2* mount, SaveDataMountResult* mount_result) { + if (mount == nullptr || mount_result == nullptr) { + return SAVE_DATA_ERROR_PARAMETER; + } + if (g_initializations == 0) { + return SAVE_DATA_ERROR_NOT_INITIALIZED; + } + if (mount->user_id == -1 || mount->user_id == 0) { + return SAVE_DATA_ERROR_INVALID_LOGIN_USER; + } + const bool creates = (mount->mount_mode & SAVE_DATA_MOUNT_MODE_CREATE) != 0; + if (mount->mount_mode == 0 || (creates && mount->blocks < SAVE_DATA_BLOCKS_MIN2)) { + return SAVE_DATA_ERROR_PARAMETER; + } + if (mount->dir_name != nullptr) { + const std::string_view name(mount->dir_name->data, sizeof(mount->dir_name->data)); + if (name.substr(0, name.find('\0')).find('_') != std::string_view::npos) { + return SAVE_DATA_ERROR_PARAMETER; + } + } + 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 mount3(&converted, mount_result); +} + +int APS5_VABI sceSaveDataMount2(const SaveDataMount2* mount, SaveDataMountResult* mount_result) { + const int rc = mount2(mount, mount_result); + SAVEDATA_TRACE("mount2 user=%d dir=%.*s mode=0x%x blocks=%llu -> 0x%08x point=%.*s status=%u", mount != nullptr ? mount->user_id : -1, static_cast(sizeof(SceSaveDataDirName::data)), mount != nullptr && mount->dir_name != nullptr ? mount->dir_name->data : "(null)", mount != nullptr ? mount->mount_mode : 0u, mount != nullptr ? static_cast(mount->blocks) : 0ull, static_cast(rc), static_cast(sizeof(SaveDataMountPoint::data)), mount_result != nullptr ? mount_result->mount_point.data : "", mount_result != nullptr ? mount_result->mount_status : 0u); + return rc; +} + int APS5_VABI sceSaveDataPrepare(const SaveDataMountPoint* mount_point, const SaveDataPrepareParam* param) { (void)mount_point; (void)param; @@ -765,7 +1202,7 @@ int APS5_VABI sceSaveDataPrepare(const SaveDataMountPoint* mount_point, const Sa int APS5_VABI sceSaveDataSaveIcon(const SaveDataMountPoint* mount_point, const SaveDataIcon* icon) { (void)icon; - if (mount_point == nullptr) { + if (!isValidMountPoint(mount_point)) { return SAVE_DATA_ERROR_PARAMETER; } std::lock_guard lock(g_slots_mutex); @@ -776,13 +1213,15 @@ int APS5_VABI sceSaveDataSaveIcon(const SaveDataMountPoint* mount_point, const S } int APS5_VABI sceSaveDataSaveIconByPath(const SaveDataMountPoint* mount_point, const char* path) { - (void)mount_point; (void)path; + if (!isValidMountPoint(mount_point)) { + return SAVE_DATA_ERROR_PARAMETER; + } return SAVE_DATA_OK; } static int setParam(const SaveDataMountPoint* mount_point, uint32_t param_type, const void* param_buf, size_t param_buf_size) { - if (mount_point == nullptr || param_buf == nullptr) { + if (!isValidMountPoint(mount_point) || param_buf == nullptr) { return SAVE_DATA_ERROR_PARAMETER; } std::lock_guard lock(g_slots_mutex); @@ -813,7 +1252,7 @@ static int setParam(const SaveDataMountPoint* mount_point, uint32_t param_type, int APS5_VABI sceSaveDataSetParam(const SaveDataMountPoint* mount_point, uint32_t param_type, const void* param_buf, size_t param_buf_size) { const int rc = setParam(mount_point, param_type, param_buf, param_buf_size); - SAVEDATA_TRACE("setParam point=%s type=%u size=%zu -> 0x%08x", mount_point != nullptr ? mount_point->data : "(null)", param_type, param_buf_size, static_cast(rc)); + SAVEDATA_TRACE("setParam point=%.*s type=%u size=%zu -> 0x%08x", static_cast(sizeof(SaveDataMountPoint::data)), mount_point != nullptr ? mount_point->data : "(null)", param_type, param_buf_size, static_cast(rc)); return rc; } @@ -957,17 +1396,25 @@ int APS5_VABI sceSaveDataSyncSaveDataMemory(const void* sync_param) { } int APS5_VABI sceSaveDataTerminate(void) { - std::lock_guard lock(g_slots_mutex); - if (g_initializations == 0) { - return SAVE_DATA_ERROR_NOT_INITIALIZED; - } - if (g_initializations == 1 && any_slot_used()) { + if (InEventCallback()) { return SAVE_DATA_ERROR_BUSY; } - --g_initializations; - if (g_initializations == 0) { - g_convertProgress = 0.0f; + EventWorker* stopped = nullptr; + { + std::lock_guard lock(g_slots_mutex); + if (g_initializations == 0) { + return SAVE_DATA_ERROR_NOT_INITIALIZED; + } + if (g_initializations == 1 && any_slot_used()) { + return SAVE_DATA_ERROR_BUSY; + } + --g_initializations; + if (g_initializations == 0) { + g_convertProgress = 0.0f; + stopped = DetachEventDelivery(); + } } + if (stopped != nullptr) JoinEventDelivery(stopped); return SAVE_DATA_OK; } @@ -988,7 +1435,7 @@ int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* moun static int umount2(uint32_t mode, const SaveDataMountPoint* mount_point) { (void)mode; - if (mount_point == nullptr) { + if (!isValidMountPoint(mount_point)) { return SAVE_DATA_ERROR_PARAMETER; } std::lock_guard lock(g_slots_mutex); @@ -1003,7 +1450,7 @@ static int umount2(uint32_t mode, const SaveDataMountPoint* mount_point) { int APS5_VABI sceSaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_point) { const int rc = umount2(mode, mount_point); - SAVEDATA_TRACE("umount2 mode=0x%x point=%s -> 0x%08x", mode, mount_point != nullptr ? mount_point->data : "(null)", static_cast(rc)); + SAVEDATA_TRACE("umount2 mode=0x%x point=%.*s -> 0x%08x", mode, static_cast(sizeof(SaveDataMountPoint::data)), mount_point != nullptr ? mount_point->data : "(null)", static_cast(rc)); return rc; } @@ -1033,12 +1480,8 @@ static int convert(const SaveDataConvert* param) { return SAVE_DATA_ERROR_PARAMETER; } g_convertProgress = 0.0f; - const auto* nameEnd = static_cast(std::memchr(param->dir_name->data, '\0', sizeof(param->dir_name->data))); - if (nameEnd == nullptr) { - return SAVE_DATA_ERROR_PARAMETER; - } - const std::string dirName(param->dir_name->data, static_cast(nameEnd - param->dir_name->data)); - if (dirName.empty() || dirName == "." || dirName == ".." || dirName.find_first_of("/\\:") != std::string::npos) { + std::string dirName; + if (!parse_dir_name(param->dir_name, &dirName)) { return SAVE_DATA_ERROR_PARAMETER; } if (!std::filesystem::is_directory(save_root() + "/" + dirName)) { @@ -1160,9 +1603,38 @@ int APS5_VABI sceSaveDataChangeInternal(void) { return 0; } -int APS5_VABI sceSaveDataCheckBackupData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +static int checkBackupData(const SaveDataCheckBackupData* check) { + if (g_initializations == 0) { + return SAVE_DATA_ERROR_NOT_INITIALIZED; + } + if (check == nullptr || check->dir_name == nullptr) { + return SAVE_DATA_ERROR_PARAMETER; + } + std::string dirName; + if (!parse_dir_name(check->dir_name, &dirName)) { + return SAVE_DATA_ERROR_PARAMETER; + } + if (!running_title(check->title_id)) { + return SAVE_DATA_ERROR_NOT_FOUND; + } + std::lock_guard lock(g_slots_mutex); + std::error_code error; + if (isMountedDirectory(save_root() + "/" + dirName, error)) { + return SAVE_DATA_ERROR_BUSY; + } + if (error) return SAVE_DATA_ERROR_INTERNAL; + try { + require_plain_paths(dirName, {BACKUP_DIR}); + } catch (const std::filesystem::filesystem_error&) { + return SAVE_DATA_ERROR_INTERNAL; + } + return read_backup(dirName, check->param); +} + +int APS5_VABI sceSaveDataCheckBackupData(const SaveDataCheckBackupData* check) { + const int rc = checkBackupData(check); + SAVEDATA_TRACE("checkBackupData dir=%.*s -> 0x%08x", static_cast(sizeof(SceSaveDataDirName::data)), check != nullptr && check->dir_name != nullptr ? check->dir_name->data : "(null)", static_cast(rc)); + return rc; } int APS5_VABI sceSaveDataCheckBackupDataForCdlg(void) { @@ -1307,7 +1779,7 @@ int APS5_VABI sceSaveDataGetMountedSaveDataCount() { return 0; } -int APS5_VABI sceSaveDataGetProgress() { +int APS5_VABI sceSaveDataGetProgress(float* progress) { NotImplemented_nid_no_patch(__func__); return 0; } @@ -1342,9 +1814,8 @@ int APS5_VABI sceSaveDataGetUpdatedDataCount(void) { return 0; } -int APS5_VABI sceSaveDataInitialize2() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSaveDataInitialize2(const void* init) { + return sceSaveDataInitialize3(init); } int APS5_VABI sceSaveDataInitializeForCdlg(void) { @@ -1397,14 +1868,63 @@ int APS5_VABI sceSaveDataRebuildDatabase(void) { return 0; } -int APS5_VABI sceSaveDataRegisterEventCallback() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSaveDataRegisterEventCallback(SaveDataEventCallback callback, void* userdata) { + auto& callbacks = Callbacks(); + std::lock_guard calling(callbacks.calling); + std::lock_guard slots(g_slots_mutex); + if (g_initializations == 0) { + return SAVE_DATA_ERROR_NOT_INITIALIZED; + } + if (callback == nullptr) { + return SAVE_DATA_ERROR_PARAMETER; + } + StartEventDelivery(); + std::lock_guard lock(callbacks.mutex); + callbacks.callback = callback; + callbacks.userdata = userdata; + return SAVE_DATA_OK; } -int APS5_VABI sceSaveDataRestoreBackupData(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +static int restoreBackupData(const SaveDataRestoreBackupData* restore) { + if (g_initializations == 0) { + return SAVE_DATA_ERROR_NOT_INITIALIZED; + } + if (restore == nullptr || restore->dir_name == nullptr) { + return SAVE_DATA_ERROR_PARAMETER; + } + std::string dirName; + if (!parse_dir_name(restore->dir_name, &dirName)) { + return SAVE_DATA_ERROR_PARAMETER; + } + if (!running_title(restore->title_id)) { + return SAVE_DATA_ERROR_NOT_FOUND; + } + const std::string target = save_root() + "/" + dirName; + std::lock_guard lock(g_slots_mutex); + std::error_code error; + if (isMountedDirectory(target, error)) { + return SAVE_DATA_ERROR_BUSY; + } + if (error) return SAVE_DATA_ERROR_INTERNAL; + const int backup = read_backup(dirName, nullptr); + if (backup != SAVE_DATA_OK) { + return backup; + } + if (!std::filesystem::is_directory(target)) { + return SAVE_DATA_ERROR_INTERNAL; + } + try { + restoreSave(dirName); + } catch (const std::filesystem::filesystem_error&) { + return SAVE_DATA_ERROR_INTERNAL; + } + return SAVE_DATA_OK; +} + +int APS5_VABI sceSaveDataRestoreBackupData(const SaveDataRestoreBackupData* restore) { + const int rc = restoreBackupData(restore); + SAVEDATA_TRACE("restoreBackupData dir=%.*s -> 0x%08x", static_cast(sizeof(SceSaveDataDirName::data)), restore != nullptr && restore->dir_name != nullptr ? restore->dir_name->data : "(null)", static_cast(rc)); + return rc; } int APS5_VABI sceSaveDataRestoreBackupDataForCdlg(void) { @@ -1452,14 +1972,58 @@ int APS5_VABI sceSaveDataUmountSys(void) { return 0; } -int APS5_VABI sceSaveDataUmountWithBackup(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +static int umountWithBackup(const SaveDataMountPoint* mount_point) { + if (g_initializations == 0) { + return SAVE_DATA_ERROR_NOT_INITIALIZED; + } + if (mount_point == nullptr) { + return SAVE_DATA_ERROR_PARAMETER; + } + SaveDataEvent event{}; + event.type = SAVE_DATA_EVENT_TYPE_UMOUNT_BACKUP; + event.error_code = SAVE_DATA_OK; + { + std::lock_guard lock(g_slots_mutex); + const int slot = find_slot_by_mount_point(mount_point->data); + if (slot == -1) { + return SAVE_DATA_ERROR_NOT_MOUNTED; + } + const AppTitleId title = GetAppTitleId_nid_postfix(); + const MountSlot mounted = g_slots[slot]; + RemovePathAlias_nid_no_patch(mounted.mount_point.c_str()); + g_slots[slot] = MountSlot{}; + const std::string dirName = std::filesystem::path(mounted.real_path).filename().string(); + event.user_id = mounted.user_id; + std::memcpy(event.title_id.data, title.value, strnlen(title.value, sizeof(event.title_id.data) - 1)); + std::memcpy(event.dir_name.data, dirName.data(), std::min(dirName.size(), sizeof(event.dir_name.data) - 1)); + try { + backupSave(dirName); + } catch (const std::exception&) { + event.error_code = SAVE_DATA_ERROR_INTERNAL; + } + } + { + std::lock_guard lock(g_state_mutex); + g_events.push_back(event); + } + DeliverEvent(event); + return SAVE_DATA_OK; } -int APS5_VABI sceSaveDataUnregisterEventCallback() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSaveDataUmountWithBackup(const SaveDataMountPoint* mount_point) { + const int rc = umountWithBackup(mount_point); + SAVEDATA_TRACE("umountWithBackup point=%s -> 0x%08x", mount_point != nullptr ? mount_point->data : "(null)", static_cast(rc)); + return rc; +} + +int APS5_VABI sceSaveDataUnregisterEventCallback(void) { + auto& callbacks = Callbacks(); + std::lock_guard calling(callbacks.calling); + std::lock_guard lock(callbacks.mutex); + callbacks.callback = nullptr; + callbacks.userdata = nullptr; + callbacks.pending.clear(); + return SAVE_DATA_OK; } int APS5_VABI sceSaveDataUpload(void) { diff --git a/core/libs/prx/libSceSaveData.native/SaveData.hpp b/core/libs/prx/libSceSaveData.native/SaveData.hpp index b8d2b8a1f9..d2a5c59a31 100644 --- a/core/libs/prx/libSceSaveData.native/SaveData.hpp +++ b/core/libs/prx/libSceSaveData.native/SaveData.hpp @@ -19,6 +19,7 @@ constexpr int SAVE_DATA_ERROR_EXISTS = -2137063417; constexpr int SAVE_DATA_ERROR_NOT_FOUND = -2137063416; constexpr int SAVE_DATA_ERROR_NO_SPACE_FS = -2137063414; constexpr int SAVE_DATA_ERROR_INTERNAL = -2137063413; // 0x809F000B +constexpr int SAVE_DATA_ERROR_INVALID_LOGIN_USER = -2137063407; // 0x809F0011 constexpr int SAVE_DATA_ERROR_MEMORY_NOT_READY = -2137063406; // 0x809F0012 constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_RDONLY = 1; @@ -26,6 +27,7 @@ constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_RDWR = 2; constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_CREATE = 4; constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_CREATE2 = 32; +constexpr std::uint64_t SAVE_DATA_BLOCKS_MIN2 = 96; constexpr std::uint64_t SAVE_DATA_BLOCKS_MAX = 16384; constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_ALL = 0; constexpr std::uint32_t SAVE_DATA_PARAM_TYPE_TITLE = 1; @@ -46,6 +48,7 @@ constexpr std::size_t SAVE_DATA_MOUNT_SLOTS = 16; struct MountSlot { bool used = false; + std::int32_t user_id = 0; std::string mount_point; std::string real_path; }; @@ -54,6 +57,9 @@ inline std::array g_slots; inline std::mutex g_slots_mutex; inline int find_slot_by_mount_point(const char* mount_point) { + if (mount_point == nullptr) { + return -1; + } for (int i = 0; i < static_cast(SAVE_DATA_MOUNT_SLOTS); i++) { if (g_slots[i].used && g_slots[i].mount_point == mount_point) { return i; diff --git a/core/libs/prx/libSceSaveData.native/include/Api.hpp b/core/libs/prx/libSceSaveData.native/include/Api.hpp index 00283d8bfd..aea123fc11 100644 --- a/core/libs/prx/libSceSaveData.native/include/Api.hpp +++ b/core/libs/prx/libSceSaveData.native/include/Api.hpp @@ -24,6 +24,7 @@ int APS5_VABI sceSaveDataGetMountInfo(const SaveDataMountPoint* mountPoint, Save 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 sceSaveDataMount2(const SaveDataMount2* 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); @@ -51,5 +52,6 @@ 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); +void SaveDataSetFileStepHook_nid_no_patch(bool (*hook)(const char* step, const char* path)); } diff --git a/core/libs/prx/libSceSaveData/include/Api.hpp b/core/libs/prx/libSceSaveData/include/Api.hpp index 60fe67e664..2e971f807b 100644 --- a/core/libs/prx/libSceSaveData/include/Api.hpp +++ b/core/libs/prx/libSceSaveData/include/Api.hpp @@ -18,7 +18,7 @@ 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 sceSaveDataCheckBackupData(const SaveDataCheckBackupData* check); int APS5_VABI sceSaveDataCheckBackupDataForCdlg(void); int APS5_VABI sceSaveDataCheckBackupDataInternal(void); int APS5_VABI sceSaveDataCheckBackupDataLight(void); @@ -47,14 +47,14 @@ 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 sceSaveDataGetProgress(float* progress); 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 sceSaveDataInitialize2(const void* init); int APS5_VABI sceSaveDataInitializeForCdlg(void); int APS5_VABI sceSaveDataIsDeletingUsbDb(void); int APS5_VABI sceSaveDataIsMounted(); @@ -65,8 +65,8 @@ 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 sceSaveDataRegisterEventCallback(SaveDataEventCallback callback, void* userdata); +int APS5_VABI sceSaveDataRestoreBackupData(const SaveDataRestoreBackupData* restore); int APS5_VABI sceSaveDataRestoreBackupDataForCdlg(void); int APS5_VABI sceSaveDataRestoreLoadSaveDataMemory(void); int APS5_VABI sceSaveDataSetAutoUploadSetting(void); @@ -76,8 +76,8 @@ 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 sceSaveDataUmountWithBackup(const SaveDataMountPoint* mount_point); +int APS5_VABI sceSaveDataUnregisterEventCallback(void); int APS5_VABI sceSaveDataUpload(void); int APS5_VABI sceSaveDataCheckPremiumMember(); int APS5_VABI sceSaveDataCheckSyncStatus(); diff --git a/core/libs/prx/libSceScreenShot/Export.cpp b/core/libs/prx/libSceScreenShot/Export.cpp index b9335af525..9da588f32e 100644 --- a/core/libs/prx/libSceScreenShot/Export.cpp +++ b/core/libs/prx/libSceScreenShot/Export.cpp @@ -72,7 +72,8 @@ int APS5_VABI sceScreenShotEnable() { int APS5_VABI sceScreenShotIsDisabled(int id, int* isDisabled) { if (id == -1 || !isDisabled) return ErrorArgument; - *isDisabled = disabled ? 1 : 0; + const int value = disabled ? 1 : 0; + if (!GuestCopyOut_nid_no_patch(isDisabled, &value, sizeof(value))) return ErrorArgument; return 0; } diff --git a/core/libs/prx/libSceSdma/Export.cpp b/core/libs/prx/libSceSdma/Export.cpp index 6c47112fd0..09f6cc4867 100644 --- a/core/libs/prx/libSceSdma/Export.cpp +++ b/core/libs/prx/libSceSdma/Export.cpp @@ -1,51 +1,221 @@ #include "prx/libSceSdma/include/Api.hpp" #include #include +#include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int SdmaErrorNotQueueMode = static_cast(0x80990002); +constexpr int SdmaErrorInvalidArgument = static_cast(0x80990005); +constexpr int SdmaErrorBusy = static_cast(0x80990006); +constexpr int SdmaErrorRingFull = static_cast(0x80990064); +constexpr int SdmaErrorFencePending = static_cast(0x80990065); + +constexpr std::uint32_t MinimumWorkingBuffer = 0x8000; +constexpr std::uint32_t FenceSlots = 0x1000; +constexpr std::uint32_t RingMargin = 0x10; +constexpr std::uint32_t LinearPacket = 7; +constexpr std::uint32_t FillPacket = 5; +constexpr std::uint32_t WindowPacket = 13; +constexpr std::uint32_t FencePacket = 4; +constexpr std::uint32_t ChunkBytes = 0x400000; + +static_assert(offsetof(SdmaOpenParam, fenceAddress) == 8); +static_assert(offsetof(SdmaOpenParam, writeBackAddress) == 0x10); +static_assert(offsetof(SdmaOpenParam, flags) == 0x20); + +struct SdmaSlot { + bool open = false; + bool queueMode = false; + std::uint32_t ringDwords = 0; + std::uint32_t writeIndex = 0; + std::uint32_t pendingDwords = 0; + std::uint32_t fenceIndex = 0; + std::uint64_t* writeBack = nullptr; + std::vector> deferred; + std::uint64_t fencesEmitted = 0; + std::uint64_t fencesExecuted = 0; + std::uint64_t watchedFence = 0; +}; + +std::mutex g_mutex; +SdmaSlot g_slots[20]; + +void Emit(SdmaSlot& slot, std::uint32_t dwords, std::function operation) { + slot.writeIndex = static_cast((static_cast(slot.writeIndex) + dwords) % slot.ringDwords); + slot.pendingDwords += dwords; + slot.deferred.push_back(std::move(operation)); +} + +std::uint64_t EmitFence(SdmaSlot& slot) { + const std::uint64_t fence = ++slot.fencesEmitted; + Emit(slot, FencePacket, [&slot, fence] { slot.fencesExecuted = fence; }); + return fence; +} + +void Kick(SdmaSlot& slot) { + for (auto& operation : slot.deferred) operation(); + slot.deferred.clear(); + slot.pendingDwords = 0; + if (slot.writeBack) *slot.writeBack = static_cast(slot.writeIndex) * 4; +} + +void FlushAll(SdmaSlot& slot) { + if (slot.writeIndex == 0 && slot.pendingDwords == 0) return; + EmitFence(slot); + Kick(slot); + slot.watchedFence = 0; + slot.fenceIndex = 0; +} + +std::uint32_t RingPending(const SdmaSlot& slot) { return static_cast(slot.pendingDwords); } + +bool Fits(const SdmaSlot& slot, std::uint32_t dwords) { + return static_cast(RingPending(slot)) + dwords + RingMargin <= slot.ringDwords; +} + +int Submit(void* handle, std::uint32_t dwords, bool blocking, const std::function& emit) { + std::lock_guard lock(g_mutex); + SdmaSlot& slot = *static_cast(handle); + if (!Fits(slot, dwords)) { + if (!blocking) return SdmaErrorRingFull; + FlushAll(slot); + if (!Fits(slot, dwords)) NotImplemented_nid_no_patch("sceSdma: a command larger than the ring"); + } + emit(slot); + if (blocking) { + EmitFence(slot); + Kick(slot); + return 0; + } + if (!slot.queueMode) Kick(slot); + return 0; +} + +std::uint32_t LinearDwords(std::uint32_t bytes, std::uint32_t packet) { + return (bytes / ChunkBytes) * packet + packet; +} + +void Chunks(SdmaSlot& slot, std::uint32_t bytes, std::uint32_t packet, const std::function& operation) { + std::uint64_t offset = 0; + for (std::uint32_t chunk = bytes / ChunkBytes; chunk != 0; --chunk, offset += ChunkBytes) { + Emit(slot, packet, [operation, offset] { operation(offset, ChunkBytes); }); + } + if (const std::uint32_t rest = bytes % ChunkBytes; rest != 0) { + Emit(slot, packet, [operation, offset, rest] { operation(offset, rest); }); + } +} + +int CopyLinear(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bytes, bool blocking) { + return Submit(handle, LinearDwords(bytes, LinearPacket), blocking, [=](SdmaSlot& slot) { + Chunks(slot, bytes, LinearPacket, [=](std::uint64_t offset, std::uint32_t size) { + std::memmove(reinterpret_cast(destination + offset), reinterpret_cast(source + offset), size); + }); + }); +} + +int ConstantFill(void* handle, std::uint32_t value, std::uint64_t destination, std::uint32_t bytes, std::uint32_t mode, bool blocking) { + const auto low = static_cast(value); + const bool uniform = value == low * 0x01010101u; + if (!uniform && mode == 0) NotImplemented_nid_no_patch("sceSdmaConstantFill: the pattern of the default fill size"); + if (!uniform && ((destination | bytes) & 3) != 0) NotImplemented_nid_no_patch("sceSdmaConstantFill: a 32-bit pattern on an unaligned range"); + return Submit(handle, LinearDwords(bytes, FillPacket), blocking, [=](SdmaSlot& slot) { + Chunks(slot, bytes, FillPacket, [=](std::uint64_t offset, std::uint32_t size) { + auto* target = reinterpret_cast(destination + offset); + if (uniform) { + std::memset(target, low, size); + return; + } + for (std::uint32_t index = 0; index < size; index += 4) std::memcpy(target + index, &value, 4); + }); + }); +} + +std::uint32_t ElementBytes(std::uint32_t bitsPerElement) { + switch (bitsPerElement) { + case 16: return 2; + case 32: return 4; + case 64: return 8; + case 128: return 16; + default: return 1; + } +} + +int CopyWindow(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bitsPerElement, std::uint32_t width, std::uint32_t height, + std::uint32_t sourcePitch, std::uint32_t sourceX, std::uint32_t sourceY, std::uint32_t destinationPitch, std::uint32_t destinationX, + std::uint32_t destinationY, bool blocking) { + const bool inRange = width - 1 < 0x4000 && height - 1 < 0x4000 && sourceX < 0x4000 && sourceY < 0x4000 && destinationX < 0x4000 && + destinationY < 0x4000 && sourcePitch - 1 < 0x80000 && destinationPitch - 1 < 0x80000; + if (!inRange) NotImplemented_nid_no_patch("sceSdmaCopyWindowL2L: a window outside the packet field ranges"); + const std::uint64_t element = ElementBytes(bitsPerElement); + return Submit(handle, WindowPacket, blocking, [=](SdmaSlot& slot) { + Emit(slot, WindowPacket, [=] { + for (std::uint64_t row = 0; row < height; ++row) { + const std::uint64_t from = source + ((sourceY + row) * sourcePitch + sourceX) * element; + const std::uint64_t to = destination + ((destinationY + row) * destinationPitch + destinationX) * element; + std::memmove(reinterpret_cast(to), reinterpret_cast(from), width * element); + } + }); + }); +} + +} + extern "C" { APS5_DUMMY_FUN - int APS5_VABI sceSdmaPending_001_nid_no_patch(void) { NotImplemented_nid_no_patch(__func__); return 0; } APS5_EXPORT("vPP6vKHY8Tg", sceSdmaPending_001_nid_no_patch); -int APS5_VABI sceSdmaBeginQueueMode(void) { - NotImplemented_nid_no_patch(__func__); + +int APS5_VABI sceSdmaBeginQueueMode(void* handle) { + std::lock_guard lock(g_mutex); + auto& slot = *static_cast(handle); + FlushAll(slot); + slot.queueMode = true; return 0; } -int APS5_VABI sceSdmaCancelQueue(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSdmaCancelQueue(void* handle) { + std::lock_guard lock(g_mutex); + auto& slot = *static_cast(handle); + if (!slot.queueMode) return SdmaErrorNotQueueMode; + if (slot.pendingDwords != RingPending(slot)) NotImplemented_nid_no_patch("sceSdmaCancelQueue: a queue of more than 65535 dwords, which the module rewinds only in part"); + slot.writeIndex = static_cast((static_cast(slot.writeIndex) + slot.ringDwords - slot.pendingDwords % slot.ringDwords) % slot.ringDwords); + slot.pendingDwords = 0; + slot.deferred.clear(); + slot.watchedFence = 0; + slot.fenceIndex = 0; return 0; } -int APS5_VABI sceSdmaClose(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSdmaClose(void* handle) { + std::lock_guard lock(g_mutex); + *static_cast(handle) = SdmaSlot{}; return 0; } -int APS5_VABI sceSdmaConstantFill(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSdmaConstantFill(void* handle, std::uint32_t value, std::uint64_t destination, std::uint32_t bytes, std::uint32_t mode) { + return ConstantFill(handle, value, destination, bytes, mode, true); } -int APS5_VABI sceSdmaConstantFillNonBlocking(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSdmaConstantFillNonBlocking(void* handle, std::uint32_t value, std::uint64_t destination, std::uint32_t bytes, std::uint32_t mode) { + return ConstantFill(handle, value, destination, bytes, mode, false); } -int APS5_VABI sceSdmaCopyLinear(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSdmaCopyLinear(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bytes) { + return CopyLinear(handle, source, destination, bytes, true); } -int APS5_VABI sceSdmaCopyLinearNonBlocking(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSdmaCopyLinearNonBlocking(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bytes) { + return CopyLinear(handle, source, destination, bytes, false); } int APS5_VABI sceSdmaCopyTiled(void) { @@ -58,14 +228,21 @@ int APS5_VABI sceSdmaCopyTiledNonBlocking(void) { return 0; } -int APS5_VABI sceSdmaCopyWindowL2L(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSdmaCopyWindowL2L(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bitsPerElement, std::uint32_t width, + std::uint32_t height, std::uint32_t sourcePitch, std::uint32_t sourceHeight, std::uint32_t sourceX, std::uint32_t sourceY, + std::uint32_t destinationPitch, std::uint32_t destinationHeight, std::uint32_t destinationX, std::uint32_t destinationY) { + (void)sourceHeight; + (void)destinationHeight; + return CopyWindow(handle, source, destination, bitsPerElement, width, height, sourcePitch, sourceX, sourceY, destinationPitch, destinationX, destinationY, true); } -int APS5_VABI sceSdmaCopyWindowL2LNonBlocking(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSdmaCopyWindowL2LNonBlocking(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bitsPerElement, std::uint32_t width, + std::uint32_t height, std::uint32_t sourcePitch, std::uint32_t sourceHeight, std::uint32_t sourceX, + std::uint32_t sourceY, std::uint32_t destinationPitch, std::uint32_t destinationHeight, std::uint32_t destinationX, + std::uint32_t destinationY) { + (void)sourceHeight; + (void)destinationHeight; + return CopyWindow(handle, source, destination, bitsPerElement, width, height, sourcePitch, sourceX, sourceY, destinationPitch, destinationX, destinationY, false); } int APS5_VABI sceSdmaCopyWindowT2T(void) { @@ -88,13 +265,22 @@ int APS5_VABI sceSdmaCopyWindowTiledNonBlocking(void) { return 0; } -int APS5_VABI sceSdmaEndQueueMode(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSdmaEndQueueMode(void* handle) { + std::lock_guard lock(g_mutex); + auto& slot = *static_cast(handle); + FlushAll(slot); + slot.queueMode = false; return 0; } -int APS5_VABI sceSdmaFenceNonBlocking(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSdmaFenceNonBlocking(void* handle) { + std::lock_guard lock(g_mutex); + auto& slot = *static_cast(handle); + if (static_cast(RingPending(slot)) + FencePacket + 8 > slot.ringDwords) return SdmaErrorRingFull; + if (slot.fenceIndex >= FenceSlots - 1) return SdmaErrorRingFull; + slot.watchedFence = EmitFence(slot); + ++slot.fenceIndex; + if (!slot.queueMode) Kick(slot); return 0; } @@ -102,8 +288,9 @@ int APS5_VABI sceSdmaFinalize(void) { return 0; } -int APS5_VABI sceSdmaFlushAll(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSdmaFlushAll(void* handle) { + std::lock_guard lock(g_mutex); + FlushAll(*static_cast(handle)); return 0; } @@ -115,13 +302,22 @@ int APS5_VABI sceSdmaInitialize(void) { return 0; } -int APS5_VABI sceSdmaKickQueue(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSdmaKickQueue(void* handle) { + std::lock_guard lock(g_mutex); + Kick(*static_cast(handle)); return 0; } -int APS5_VABI sceSdmaOpen(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSdmaOpen(void** handle, void* buffer, std::uint32_t size, const SdmaOpenParam* param) { + if (!param || size < MinimumWorkingBuffer || !buffer || param->engine - 12 >= 8) return SdmaErrorInvalidArgument; + std::lock_guard lock(g_mutex); + SdmaSlot& slot = g_slots[param->engine]; + if (slot.open) return SdmaErrorBusy; + slot = SdmaSlot{}; + slot.open = true; + slot.ringDwords = size / 4 - FenceSlots; + if (param->flags & 0x10) slot.writeBack = reinterpret_cast(param->writeBackAddress); + *handle = &slot; return 0; } @@ -129,8 +325,13 @@ int APS5_VABI sceSdmaQueryWorkingBufferSize(void) { return 0x8000; } -int APS5_VABI sceSdmaTryFlush(void) { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSdmaTryFlush(void* handle) { + std::lock_guard lock(g_mutex); + auto& slot = *static_cast(handle); + if (slot.watchedFence == 0) return 0; + if (slot.fencesExecuted < slot.watchedFence) return SdmaErrorFencePending; + slot.watchedFence = 0; + slot.fenceIndex = 0; return 0; } diff --git a/core/libs/prx/libSceSdma/include/Api.hpp b/core/libs/prx/libSceSdma/include/Api.hpp index 1245319f43..67d060a627 100644 --- a/core/libs/prx/libSceSdma/include/Api.hpp +++ b/core/libs/prx/libSceSdma/include/Api.hpp @@ -2,33 +2,48 @@ #include "SceTypes.hpp" +struct SdmaOpenParam { + std::uint32_t engine; + std::uint32_t reserved; + std::uint64_t fenceAddress; + std::uint64_t writeBackAddress; + std::uint64_t writeBackExtra; + std::uint32_t flags; +}; + 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 sceSdmaBeginQueueMode(void* handle); +int APS5_VABI sceSdmaCancelQueue(void* handle); +int APS5_VABI sceSdmaClose(void* handle); +int APS5_VABI sceSdmaConstantFill(void* handle, std::uint32_t value, std::uint64_t destination, std::uint32_t bytes, std::uint32_t mode); +int APS5_VABI sceSdmaConstantFillNonBlocking(void* handle, std::uint32_t value, std::uint64_t destination, std::uint32_t bytes, std::uint32_t mode); +int APS5_VABI sceSdmaCopyLinear(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bytes); +int APS5_VABI sceSdmaCopyLinearNonBlocking(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bytes); int APS5_VABI sceSdmaCopyTiled(void); int APS5_VABI sceSdmaCopyTiledNonBlocking(void); -int APS5_VABI sceSdmaCopyWindowL2L(void); -int APS5_VABI sceSdmaCopyWindowL2LNonBlocking(void); +int APS5_VABI sceSdmaCopyWindowL2L(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bitsPerElement, std::uint32_t width, + std::uint32_t height, std::uint32_t sourcePitch, std::uint32_t sourceHeight, std::uint32_t sourceX, + std::uint32_t sourceY, std::uint32_t destinationPitch, std::uint32_t destinationHeight, + std::uint32_t destinationX, std::uint32_t destinationY); +int APS5_VABI sceSdmaCopyWindowL2LNonBlocking(void* handle, std::uint64_t source, std::uint64_t destination, std::uint32_t bitsPerElement, + std::uint32_t width, std::uint32_t height, std::uint32_t sourcePitch, std::uint32_t sourceHeight, + std::uint32_t sourceX, std::uint32_t sourceY, std::uint32_t destinationPitch, + std::uint32_t destinationHeight, std::uint32_t destinationX, std::uint32_t destinationY); 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 sceSdmaEndQueueMode(void* handle); +int APS5_VABI sceSdmaFenceNonBlocking(void* handle); int APS5_VABI sceSdmaFinalize(void); -int APS5_VABI sceSdmaFlushAll(void); +int APS5_VABI sceSdmaFlushAll(void* handle); int APS5_VABI sceSdmaFlushAllWithEvent(void); int APS5_VABI sceSdmaInitialize(void); -int APS5_VABI sceSdmaKickQueue(void); -int APS5_VABI sceSdmaOpen(void); +int APS5_VABI sceSdmaKickQueue(void* handle); +int APS5_VABI sceSdmaOpen(void** handle, void* buffer, std::uint32_t size, const SdmaOpenParam* param); int APS5_VABI sceSdmaQueryWorkingBufferSize(void); -int APS5_VABI sceSdmaTryFlush(void); +int APS5_VABI sceSdmaTryFlush(void* handle); int APS5_VABI sceSdmaWaitForEvent(void); } diff --git a/core/libs/prx/libSceShare/Export.cpp b/core/libs/prx/libSceShare/Export.cpp index 3076aa4355..310e648421 100644 --- a/core/libs/prx/libSceShare/Export.cpp +++ b/core/libs/prx/libSceShare/Export.cpp @@ -2,6 +2,7 @@ #include "prx/libSceShare/include/InventoryExports.hpp" #include "prx/libSceShare/include/Api.hpp" #include +#include #include #include #include "SceTypes.hpp" @@ -55,7 +56,13 @@ int APS5_VABI sceShareGetCurrentStatus(uint32_t feature_flag, ShareCurrentStatus if (feature_flag == 0 || status == nullptr) { return ERROR_INVALID_PARAM; } - std::memset(status, 0, sizeof(*status)); + const ShareCurrentStatus zeroStatus{}; + const auto savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(status, &zeroStatus, sizeof(zeroStatus))) { + errno = savedErrno; + return ERROR_INVALID_PARAM; + } + errno = savedErrno; return 0; } @@ -122,7 +129,13 @@ int APS5_VABI sceShareGetRunningStatus(uint32_t* status) { if (status == nullptr) { return ERROR_INVALID_PARAM; } - *status = 0; + const std::uint32_t zeroStatus = 0; + const auto savedErrno = errno; + if (!GuestCopyOut_nid_no_patch(status, &zeroStatus, sizeof(zeroStatus))) { + errno = savedErrno; + return ERROR_INVALID_PARAM; + } + errno = savedErrno; return 0; } diff --git a/core/libs/prx/libSceSisrSys/Export.cpp b/core/libs/prx/libSceSisrSys/Export.cpp new file mode 100644 index 0000000000..71d49ab34b --- /dev/null +++ b/core/libs/prx/libSceSisrSys/Export.cpp @@ -0,0 +1,14 @@ +#include "prx/libSceSisrSys/include/Api.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +int APS5_VABI sceSisrSysProcessUmdEvent() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceSisrSysSetup() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceSisrSys/include/Api.hpp b/core/libs/prx/libSceSisrSys/include/Api.hpp new file mode 100644 index 0000000000..cdab06ee86 --- /dev/null +++ b/core/libs/prx/libSceSisrSys/include/Api.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceSisrSysProcessUmdEvent(); +int APS5_VABI sceSisrSysSetup(); + +} diff --git a/core/libs/prx/libSceSisrSys/libSceSisrSys.hpp b/core/libs/prx/libSceSisrSys/libSceSisrSys.hpp new file mode 100644 index 0000000000..ea09624d1d --- /dev/null +++ b/core/libs/prx/libSceSisrSys/libSceSisrSys.hpp @@ -0,0 +1,3 @@ +#pragma once + +#include "prx/libSceSisrSys/include/Api.hpp" diff --git a/core/libs/prx/libSceSsl/Export.cpp b/core/libs/prx/libSceSsl/Export.cpp index bae126e949..9db9ac2943 100644 --- a/core/libs/prx/libSceSsl/Export.cpp +++ b/core/libs/prx/libSceSsl/Export.cpp @@ -1311,8 +1311,9 @@ void* APS5_VABI sceSslGetSubjectName(int sslCtxId, void* sslCert) { return ExtractCertName(__func__, sslCtxId, sslCert, true); } -int APS5_VABI CA_MGMT_extractKeyBlobEx() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI CA_MGMT_extractKeyBlobEx(int, const void* keyBlob, std::uint32_t, void* key) { + if (key == nullptr || keyBlob == nullptr) return ErrorInvalidArg; + std::memcpy(static_cast(key) + 8, &keyBlob, sizeof(keyBlob)); return 0; } @@ -1326,8 +1327,9 @@ int APS5_VABI CA_MGMT_freeKeyBlob() { return 0; } -int APS5_VABI CRYPTO_initAsymmetricKey() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI CRYPTO_initAsymmetricKey(void* key) { + const std::uint32_t initialized = 1; + std::memcpy(key, &initialized, sizeof(initialized)); return 0; } @@ -1384,8 +1386,8 @@ int APS5_VABI sceSslEnableOptionInternal() { return 0; } -int APS5_VABI sceSslGetNanoSSLModuleId() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceSslGetNanoSSLModuleId(int sslCtxId, int* moduleId) { + if (moduleId != nullptr) *moduleId = sslCtxId; return 0; } diff --git a/core/libs/prx/libSceSsl/include/Ssl.hpp b/core/libs/prx/libSceSsl/include/Ssl.hpp index 5de6ba4c60..6b5d2fcf69 100644 --- a/core/libs/prx/libSceSsl/include/Ssl.hpp +++ b/core/libs/prx/libSceSsl/include/Ssl.hpp @@ -38,10 +38,10 @@ int APS5_VABI sceSslGetNotAfter(int sslCtxId, void* sslCert, SceSsl::RtcTick* li 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_extractKeyBlobEx(int sslCtxId, const void* keyBlob, std::uint32_t keyBlobLength, void* key); int APS5_VABI CA_MGMT_extractPublicKeyInfo(); int APS5_VABI CA_MGMT_freeKeyBlob(); -int APS5_VABI CRYPTO_initAsymmetricKey(); +int APS5_VABI CRYPTO_initAsymmetricKey(void* key); int APS5_VABI CRYPTO_uninitAsymmetricKey(); int APS5_VABI RSA_verifySignature(); int APS5_VABI VLONG_freeVlongQueue(); @@ -53,7 +53,7 @@ int APS5_VABI sceSslDisableOptionInternal(); int APS5_VABI sceSslDisableOptionInternalInsecure(); int APS5_VABI sceSslEnableOption(); int APS5_VABI sceSslEnableOptionInternal(); -int APS5_VABI sceSslGetNanoSSLModuleId(); +int APS5_VABI sceSslGetNanoSSLModuleId(int sslCtxId, int* moduleId); int APS5_VABI sceSslGetPeerCert(); int APS5_VABI sceSslGetSslError(); int APS5_VABI sceSslLoadRootCACert(); diff --git a/core/libs/prx/libSceSysUtil/Export.cpp b/core/libs/prx/libSceSysUtil/Export.cpp index a053ac386f..8e5a63dafc 100644 --- a/core/libs/prx/libSceSysUtil/Export.cpp +++ b/core/libs/prx/libSceSysUtil/Export.cpp @@ -5,6 +5,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/include/Api.hpp" namespace { @@ -25,8 +26,15 @@ int APS5_VABI sceSysUtilSendSystemNotificationWithText(int32_t type, const char* std::memcpy(request.data() + TypeOffset, &type, sizeof(type)); if (message != nullptr) { std::size_t length = 0; - while (length < TextLimit && message[length] != '\0') ++length; - std::memcpy(request.data() + TextOffset, message, length); + char c = 0; + while (length < TextLimit) { + if (!GuestCopyIn_nid_no_patch(&c, message + length, sizeof(c))) { + return SCE_KERNEL_ERROR_EFAULT; + } + if (c == '\0') break; + request[TextOffset + length] = static_cast(c); + ++length; + } } return sceKernelSendNotificationRequest(0, request.data(), request.size(), 0); } diff --git a/core/libs/prx/libSceSystemService/CMakeLists.txt b/core/libs/prx/libSceSystemService/CMakeLists.txt index 1311faaac2..5885fd39d5 100644 --- a/core/libs/prx/libSceSystemService/CMakeLists.txt +++ b/core/libs/prx/libSceSystemService/CMakeLists.txt @@ -1,6 +1,11 @@ +add_library(system_service_host_locale INTERFACE) +if(APPLE) + target_link_libraries(system_service_host_locale INTERFACE "-framework CoreFoundation") +endif() + add_library(libSceSystemService SHARED EXCLUDE_FROM_ALL Export.cpp PendingExports.cpp LoadExec.cpp) target_include_directories(libSceSystemService PRIVATE ${LIBS_INCLUDE_DIR}) -target_link_libraries(libSceSystemService PRIVATE libSceAgcDriver libSceUsbStorage) +target_link_libraries(libSceSystemService PRIVATE libSceAgcDriver libSceUsbStorage system_service_host_locale) configure_windows_unwind(libSceSystemService) if(BUILD_TESTING) @@ -81,6 +86,17 @@ if(BUILD_TESTING) set_tests_properties(guest_system_service PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_system_service_status_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemServiceStatus.cpp) + target_include_directories(guest_system_service_status_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_system_service_status_tests PRIVATE libSceSystemService libc) + configure_windows_unwind(guest_system_service_status_tests) + foreach(statusMode IN ITEMS suspend sessions shell devices) + add_test(NAME guest_system_service_status_${statusMode} COMMAND guest_system_service_status_tests ${statusMode}) + if(WIN32) + set_tests_properties(guest_system_service_status_${statusMode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() add_test_executable(guest_system_service_language_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemServiceLanguage.cpp) target_include_directories(guest_system_service_language_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_service_language_tests PRIVATE libSceSystemService libc) @@ -90,6 +106,38 @@ if(BUILD_TESTING) set_tests_properties(guest_system_service_language PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(system_service_language_locale_tests ${LIBS_INCLUDE_DIR}/tests/SystemServiceLanguageLocale.cpp) + target_include_directories(system_service_language_locale_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(system_service_language_locale_tests PRIVATE system_service_host_locale) + configure_host_test_executable(system_service_language_locale_tests) + add_test(NAME system_service_language_locale COMMAND system_service_language_locale_tests) + set_tests_properties(system_service_language_locale PROPERTIES TIMEOUT 20) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_test_executable(guest_system_service_language_auto_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemServiceLanguageAuto.cpp) + target_include_directories(guest_system_service_language_auto_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_system_service_language_auto_tests PRIVATE libSceSystemService libc system_service_host_locale) + configure_windows_unwind(guest_system_service_language_auto_tests) + set(languageAutoIndex 0) + foreach(languageAutoCase IN ITEMS + "4,-,-,de_DE.UTF-8,-,auto,0" + "17,-,-,pt_BR.UTF-8,-,AUTO,0" + "10,-,-,zh_TW.UTF-8,-,Auto,0" + "11,-,-,zh_CN.UTF-8,-,auto,0" + "20,es_MX.UTF-8,fr_FR.UTF-8,de_DE.UTF-8,-,auto,0" + "2,-,fr_FR.UTF-8,de_DE.UTF-8,-,auto,0" + "3,-,-,en_US.UTF-8,es:fr,auto,0" + "18,-,-,en_GB.UTF-8@euro,-,auto,0" + "0,-,-,ja_JP.UTF-8,-,auto,0" + "1,-,-,C,es,auto,1" + "1,-,-,-,-,auto,1" + "1,-,-,lt_LT.UTF-8,-,auto,1" + "30,-,-,lt_LT.UTF-8,lt:uk:fr,auto,0") + string(REPLACE "," ";" languageAutoArguments "${languageAutoCase}") + add_test(NAME guest_system_service_language_auto_${languageAutoIndex} COMMAND guest_system_service_language_auto_tests ${languageAutoArguments}) + set_tests_properties(guest_system_service_language_auto_${languageAutoIndex} PROPERTIES TIMEOUT 20) + math(EXPR languageAutoIndex "${languageAutoIndex} + 1") + endforeach() + endif() add_test_executable(guest_system_service_time_zone_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemServiceTimeZone.cpp) target_include_directories(guest_system_service_time_zone_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_service_time_zone_tests PRIVATE libSceSystemService libc) @@ -167,3 +215,5 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + +target_sources(libSceSystemService PRIVATE InventoryExports.cpp) diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index 20afae7aaa..dc56656c98 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -1,5 +1,6 @@ #include "prx/libSceSystemService/include/SystemService.hpp" #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +76,21 @@ ConsoleTimeZone HostTimeZone() { return {static_cast(offset.count()) - summerMinutes, summer}; } +std::mutex appSuspendBlockMutex; +int appSuspendBlocks = 0; + +int WriteStatus(bool* status, bool value, const char* function) { + if (status == nullptr) NotImplemented_nid_no_patch((std::string(function) + " with a null output").c_str()); + *status = value; + return SYSTEM_SERVICE_OK; +} + +int WriteStatusOrRefuse(bool* status, bool value, int nullOutputError) { + if (status == nullptr) return nullOutputError; + *status = value; + return SYSTEM_SERVICE_OK; +} + constexpr char MainExecutable[] = "/app0/eboot.bin"; constexpr char ApplicationRoot[] = "/app0/"; constexpr char MainExecutableVariable[] = "APS5_EBOOT"; @@ -289,10 +306,70 @@ int ListRunningBigApp(const std::string& caller, std::int32_t* appIds, std::uint return SYSTEM_SERVICE_OK; } +constexpr std::uint32_t GpuLoadEmulationModeOff = 0; +constexpr std::uint32_t GpuLoadEmulationModeNormal = 1; + +std::atomic GpuLoadEmulationMode{GpuLoadEmulationModeNormal}; + +int SetGpuLoadEmulationMode(const char* caller, std::uint32_t mode) { + if (mode != GpuLoadEmulationModeOff && mode != GpuLoadEmulationModeNormal) { + NotImplemented_nid_no_patch((std::string(caller) + ": a mode other than 0 and 1").c_str()); + } + GpuLoadEmulationMode.store(mode, std::memory_order_relaxed); + return SYSTEM_SERVICE_OK; +} + +int GetGpuLoadEmulationModeOfApp(const char* caller, std::int32_t appId) { + if (appId != SYSTEM_SERVICE_RUNNING_APP_ID) NotImplemented_nid_no_patch((std::string(caller) + ": application other than the running title").c_str()); + return static_cast(GpuLoadEmulationMode.load(std::memory_order_relaxed)); +} + +std::atomic SuspendConfirmationDialogEnabled{true}; + } extern "C" { +int APS5_VABI sceSystemServiceEnableSuspendConfirmationDialog() { + SuspendConfirmationDialogEnabled.store(true, std::memory_order_relaxed); + return SYSTEM_SERVICE_OK; +} + +int APS5_VABI sceSystemServiceDisableSuspendConfirmationDialog() { + SuspendConfirmationDialogEnabled.store(false, std::memory_order_relaxed); + return SYSTEM_SERVICE_OK; +} + +bool APS5_VABI sceShellCoreUtilGetSuspendConfirmationDialogFlag(std::int32_t appId) { + if (appId != SYSTEM_SERVICE_RUNNING_APP_ID) NotImplemented_nid_no_patch("sceShellCoreUtilGetSuspendConfirmationDialogFlag: application other than the running title"); + return SuspendConfirmationDialogEnabled.load(std::memory_order_relaxed); +} + +int APS5_VABI sceSystemServiceSetGpuLoadEmulationMode(std::uint32_t mode) { + return SetGpuLoadEmulationMode("sceSystemServiceSetGpuLoadEmulationMode", mode); +} + +int APS5_VABI sceSystemServiceGetGpuLoadEmulationMode() { + return static_cast(GpuLoadEmulationMode.load(std::memory_order_relaxed)); +} + +int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationMode(std::uint32_t mode) { + return SetGpuLoadEmulationMode("sceShellCoreUtilSetGpuLoadEmulationMode", mode); +} + +int APS5_VABI sceShellCoreUtilGetGpuLoadEmulationMode() { + return static_cast(GpuLoadEmulationMode.load(std::memory_order_relaxed)); +} + +int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationModeByAppId(std::int32_t appId, std::uint32_t mode) { + if (appId != SYSTEM_SERVICE_RUNNING_APP_ID) NotImplemented_nid_no_patch("sceShellCoreUtilSetGpuLoadEmulationModeByAppId: application other than the running title"); + return SetGpuLoadEmulationMode("sceShellCoreUtilSetGpuLoadEmulationModeByAppId", mode); +} + +int APS5_VABI sceShellCoreUtilGetGpuLoadEmulationModeByAppId(std::int32_t appId) { + return GetGpuLoadEmulationModeOfApp("sceShellCoreUtilGetGpuLoadEmulationModeByAppId", appId); +} + int APS5_VABI sceSystemServiceLoadExec(const char* path, const char* const* arguments) { if (!path || !*path) return SYSTEM_SERVICE_ERROR_PARAMETER; if (std::strcmp(path, "exit") == 0) LibcExitWithoutHandlers_nid_no_patch(0); @@ -352,6 +429,119 @@ int APS5_VABI sceSystemServiceGetStatus(SystemServiceStatus* status) { return SYSTEM_SERVICE_OK; } +int APS5_VABI sceLncUtilBlockAppSuspend(const char* reason) { + if (reason == nullptr) NotImplemented_nid_no_patch("sceLncUtilBlockAppSuspend with a null reason"); + std::lock_guard lock(appSuspendBlockMutex); + ++appSuspendBlocks; + return SYSTEM_SERVICE_OK; +} + +int APS5_VABI sceLncUtilUnblockAppSuspend(const char* reason) { + std::lock_guard lock(appSuspendBlockMutex); + if (appSuspendBlocks == 0) return SCE_KERNEL_ERROR_EBUSY; + if (reason == nullptr) NotImplemented_nid_no_patch("sceLncUtilUnblockAppSuspend with a null reason while a block is held"); + --appSuspendBlocks; + return SYSTEM_SERVICE_OK; +} + +int APS5_VABI sceLncUtilEnableSuspendNotification() { + return SYSTEM_SERVICE_OK; +} + +int APS5_VABI sceLncUtilDisableSuspendNotification() { + return SYSTEM_SERVICE_OK; +} + +int APS5_VABI sceSystemServiceEnableSuspendNotification() { + return sceLncUtilEnableSuspendNotification(); +} + +int APS5_VABI sceSystemServiceDisableSuspendNotification() { + return sceLncUtilDisableSuspendNotification(); +} + +int APS5_VABI sceShellCoreUtilGetRemotePlayStatus(bool* active) { + return WriteStatusOrRefuse(active, false, SHELL_CORE_UTIL_ERROR_INVALID_ARGUMENT); +} + +int APS5_VABI sceSystemServiceGetRemoteplayStatus(bool* active) { + return sceShellCoreUtilGetRemotePlayStatus(active); +} + +int APS5_VABI sceShellCoreUtilGetSharePlayStatus(bool* active) { + return WriteStatusOrRefuse(active, false, SHELL_CORE_UTIL_ERROR_INVALID_ARGUMENT); +} + +bool APS5_VABI sceShellCoreUtilIsGameLiveStreamingOnAir() { + return false; +} + +bool APS5_VABI sceSystemServiceIsGameLiveStreamingOnAir() { + return sceShellCoreUtilIsGameLiveStreamingOnAir(); +} + +int APS5_VABI sceShellCoreUtilGetGameLiveStreamingStatus(bool* active) { + return WriteStatusOrRefuse(active, sceShellCoreUtilIsGameLiveStreamingOnAir(), SHELL_CORE_UTIL_ERROR_INVALID_ARGUMENT); +} + +bool APS5_VABI sceLncUtilIsShellUiFgAndGameBgCpuMode() { + return false; +} + +bool APS5_VABI sceSystemServiceIsShellUiFgAndGameBgCpuMode() { + return sceLncUtilIsShellUiFgAndGameBgCpuMode(); +} + +bool APS5_VABI sceShellCoreUtilIsImposeScreenOverlaid() { + return false; +} + +bool APS5_VABI sceShellCoreUtilIsScreenSaverOn() { + return false; +} + +bool APS5_VABI sceSystemServiceIsScreenSaverOn() { + return sceShellCoreUtilIsScreenSaverOn(); +} + +bool APS5_VABI sceSystemStateMgrIsShellUIShutdownInProgress() { + return false; +} + +int APS5_VABI sceShellCoreUtilGetSocialScreenStatus(bool* active) { + return WriteStatus(active, false, __func__); +} + +int APS5_VABI sceShellCoreUtilIsEyeToEyeDistanceAdjusted(std::uint32_t userId, std::uint8_t* adjusted) { + if (adjusted == nullptr) NotImplemented_nid_no_patch("sceShellCoreUtilIsEyeToEyeDistanceAdjusted with a null output"); + if (userId != static_cast(USER_SERVICE_INITIAL_USER_ID)) { + NotImplemented_nid_no_patch("sceShellCoreUtilIsEyeToEyeDistanceAdjusted for a user other than the logged-in user"); + } + *adjusted = 0; + return SYSTEM_SERVICE_OK; +} + +int APS5_VABI sceSystemServiceIsEyeToEyeDistanceAdjusted(std::uint32_t userId, std::uint8_t* adjusted) { + if (adjusted == nullptr) return SYSTEM_SERVICE_ERROR_PARAMETER; + return sceShellCoreUtilIsEyeToEyeDistanceAdjusted(userId, adjusted); +} + +bool APS5_VABI sceSystemStateMgrIsBdDriveReady() { + return false; +} + +bool APS5_VABI sceShellCoreUtilIsUsbMassStorageMounted(std::uint32_t) { + return false; +} + +bool APS5_VABI sceShellCoreUtilIsExternalStorageAppMoveInProgress() { + return false; +} + +int APS5_VABI sceLncUtilIsPs2Emu(bool* emulated) { + return WriteStatusOrRefuse(emulated, false, LNC_UTIL_ERROR_INVALID_ARGUMENT); +} + int APS5_VABI sceSystemServiceHideSplashScreen(void) { return SYSTEM_SERVICE_OK; } @@ -971,4 +1161,10 @@ int APS5_VABI sceShellCoreUtilIsBgmPlaying(void) { return 0; } +APS5_EXPORT("M+KwxF0amiw", sceSystemServiceUnknown_M_KwxF0amiw); +int APS5_VABI sceSystemServiceUnknown_M_KwxF0amiw(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libSceSystemService/InventoryExports.cpp b/core/libs/prx/libSceSystemService/InventoryExports.cpp new file mode 100644 index 0000000000..86a1ebb382 --- /dev/null +++ b/core/libs/prx/libSceSystemService/InventoryExports.cpp @@ -0,0 +1,167 @@ +#include "prx/libSceSystemService/include/InventoryExports.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/general/ExportMacros.hpp" + +extern "C" { + +int APS5_VABI libSceSystemServiceUnknown_16waHD4tFBo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("16waHD4tFBo", libSceSystemServiceUnknown_16waHD4tFBo_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_1m_minus_E1Xh8COo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("1m-E1Xh8COo", libSceSystemServiceUnknown_1m_minus_E1Xh8COo_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_7eu2qjQGigw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("7eu2qjQGigw", libSceSystemServiceUnknown_7eu2qjQGigw_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_90unWbnI0qE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("90unWbnI0qE", libSceSystemServiceUnknown_90unWbnI0qE_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_E_minus_bXVJqwGZw_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("E-bXVJqwGZw", libSceSystemServiceUnknown_E_minus_bXVJqwGZw_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_G3oPdw3lkps_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("G3oPdw3lkps", libSceSystemServiceUnknown_G3oPdw3lkps_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_KgiwSrROsIQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("KgiwSrROsIQ", libSceSystemServiceUnknown_KgiwSrROsIQ_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_LxGOxroprpg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("LxGOxroprpg", libSceSystemServiceUnknown_LxGOxroprpg_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_NXFtO8BfVeo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("NXFtO8BfVeo", libSceSystemServiceUnknown_NXFtO8BfVeo_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_Oxh0CSsTZ78_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("Oxh0CSsTZ78", libSceSystemServiceUnknown_Oxh0CSsTZ78_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_OyTwhCKWXi4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("OyTwhCKWXi4", libSceSystemServiceUnknown_OyTwhCKWXi4_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_U0HutHWnMBM_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("U0HutHWnMBM", libSceSystemServiceUnknown_U0HutHWnMBM_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_VDs9VSjSJh0_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("VDs9VSjSJh0", libSceSystemServiceUnknown_VDs9VSjSJh0_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_ZWNP_plus__minus_dWr8E_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ZWNP+-dWr8E", libSceSystemServiceUnknown_ZWNP_plus__minus_dWr8E_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_axzblV8OvWU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("axzblV8OvWU", libSceSystemServiceUnknown_axzblV8OvWU_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_bJyWctnSXyU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bJyWctnSXyU", libSceSystemServiceUnknown_bJyWctnSXyU_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_bUNkT3XDg0Y_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("bUNkT3XDg0Y", libSceSystemServiceUnknown_bUNkT3XDg0Y_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_c5_plus_4Scso9EU_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("c5+4Scso9EU", libSceSystemServiceUnknown_c5_plus_4Scso9EU_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_fBGD_minus_HNimSk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("fBGD-HNimSk", libSceSystemServiceUnknown_fBGD_minus_HNimSk_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_fPAZ5DBsDxo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("fPAZ5DBsDxo", libSceSystemServiceUnknown_fPAZ5DBsDxo_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_ifJiF5witJ4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("ifJiF5witJ4", libSceSystemServiceUnknown_ifJiF5witJ4_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_kDGjRMtUDxo_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("kDGjRMtUDxo", libSceSystemServiceUnknown_kDGjRMtUDxo_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_lgpIdZ8T0xg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("lgpIdZ8T0xg", libSceSystemServiceUnknown_lgpIdZ8T0xg_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_pj1SzIAxPlk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("pj1SzIAxPlk", libSceSystemServiceUnknown_pj1SzIAxPlk_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_qdTPJWjquDc_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("qdTPJWjquDc", libSceSystemServiceUnknown_qdTPJWjquDc_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_rIqPq0oWlrg_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("rIqPq0oWlrg", libSceSystemServiceUnknown_rIqPq0oWlrg_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_sgYo_minus_zXHQRE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("sgYo-zXHQRE", libSceSystemServiceUnknown_sgYo_minus_zXHQRE_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_tPMOYbbviYs_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("tPMOYbbviYs", libSceSystemServiceUnknown_tPMOYbbviYs_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_uElcdmhh_minus_c8_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("uElcdmhh-c8", libSceSystemServiceUnknown_uElcdmhh_minus_c8_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_xn_minus_HgPW29x4_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("xn-HgPW29x4", libSceSystemServiceUnknown_xn_minus_HgPW29x4_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_y16IXiJfafA_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("y16IXiJfafA", libSceSystemServiceUnknown_y16IXiJfafA_nid_no_patch); + +int APS5_VABI libSceSystemServiceUnknown_yOiZq_plus_9_minus_ZMQ_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("yOiZq+9-ZMQ", libSceSystemServiceUnknown_yOiZq_plus_9_minus_ZMQ_nid_no_patch); + +} diff --git a/core/libs/prx/libSceSystemService/LanguageLocale.hpp b/core/libs/prx/libSceSystemService/LanguageLocale.hpp new file mode 100644 index 0000000000..286573b07a --- /dev/null +++ b/core/libs/prx/libSceSystemService/LanguageLocale.hpp @@ -0,0 +1,378 @@ +#ifndef CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_LANGUAGELOCALE_HPP +#define CORE_LIBS_PRX_LIBSCESYSTEMSERVICE_LANGUAGELOCALE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#elif defined(__APPLE__) +#include +#endif + +namespace SystemServiceLocale { + +struct Tag { + std::string language; + std::string script; + std::string region; +}; + +enum ConsoleLanguage : int { + Japanese = 0, + EnglishUs = 1, + French = 2, + Spanish = 3, + German = 4, + Italian = 5, + Dutch = 6, + PortuguesePortugal = 7, + Russian = 8, + Korean = 9, + ChineseTraditional = 10, + ChineseSimplified = 11, + Finnish = 12, + Swedish = 13, + Danish = 14, + Norwegian = 15, + Polish = 16, + PortugueseBrazil = 17, + EnglishUk = 18, + Turkish = 19, + SpanishLatinAmerica = 20, + Arabic = 21, + FrenchCanada = 22, + Czech = 23, + Hungarian = 24, + Greek = 25, + Romanian = 26, + Thai = 27, + Vietnamese = 28, + Indonesian = 29, + Ukrainian = 30, +}; + +constexpr int NoConsoleLanguage = -1; + +struct Row { + std::string_view language; + std::string_view script; + std::string_view region; + int id; +}; + +namespace Detail { + +constexpr bool IsAlpha(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } +constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; } +constexpr char Lower(char c) { return c >= 'A' && c <= 'Z' ? static_cast(c - 'A' + 'a') : c; } +constexpr char Upper(char c) { return c >= 'a' && c <= 'z' ? static_cast(c - 'a' + 'A') : c; } + +constexpr bool AllOf(std::string_view text, bool (*predicate)(char)) { + for (char c : text) { + if (!predicate(c)) return false; + } + return true; +} + +constexpr bool IsAlphanumeric(char c) { return IsAlpha(c) || IsDigit(c); } + +constexpr bool EqualsIgnoringCase(std::string_view left, std::string_view right) { + if (left.size() != right.size()) return false; + for (std::size_t i = 0; i < left.size(); ++i) { + if (Lower(left[i]) != Lower(right[i])) return false; + } + return true; +} + +inline constexpr Row LatinEuropeanRows[] = { + {"en", "", "", EnglishUs}, + {"en", "", "GB", EnglishUk}, + {"en", "", "IE", EnglishUk}, + {"en", "", "AU", EnglishUk}, + {"en", "", "NZ", EnglishUk}, + {"fr", "", "", French}, + {"fr", "", "CA", FrenchCanada}, + {"de", "", "", German}, + {"it", "", "", Italian}, + {"nl", "", "", Dutch}, + {"ro", "", "", Romanian}, + {"mo", "", "", Romanian}, +}; + +inline constexpr Row IberianRows[] = { + {"es", "", "", Spanish}, + {"es", "", "419", SpanishLatinAmerica}, + {"es", "", "MX", SpanishLatinAmerica}, + {"es", "", "AR", SpanishLatinAmerica}, + {"es", "", "BO", SpanishLatinAmerica}, + {"es", "", "CL", SpanishLatinAmerica}, + {"es", "", "CO", SpanishLatinAmerica}, + {"es", "", "CR", SpanishLatinAmerica}, + {"es", "", "CU", SpanishLatinAmerica}, + {"es", "", "DO", SpanishLatinAmerica}, + {"es", "", "EC", SpanishLatinAmerica}, + {"es", "", "GT", SpanishLatinAmerica}, + {"es", "", "HN", SpanishLatinAmerica}, + {"es", "", "NI", SpanishLatinAmerica}, + {"es", "", "PA", SpanishLatinAmerica}, + {"es", "", "PE", SpanishLatinAmerica}, + {"es", "", "PR", SpanishLatinAmerica}, + {"es", "", "PY", SpanishLatinAmerica}, + {"es", "", "SV", SpanishLatinAmerica}, + {"es", "", "US", SpanishLatinAmerica}, + {"es", "", "UY", SpanishLatinAmerica}, + {"es", "", "VE", SpanishLatinAmerica}, + {"pt", "", "", PortugueseBrazil}, + {"pt", "", "PT", PortuguesePortugal}, + {"pt", "", "AO", PortuguesePortugal}, + {"pt", "", "MZ", PortuguesePortugal}, + {"pt", "", "CV", PortuguesePortugal}, + {"pt", "", "GW", PortuguesePortugal}, + {"pt", "", "ST", PortuguesePortugal}, + {"pt", "", "TL", PortuguesePortugal}, + {"pt", "", "MO", PortuguesePortugal}, +}; + +inline constexpr Row ChineseRows[] = { + {"zh", "", "", ChineseSimplified}, + {"zh", "Hans", "", ChineseSimplified}, + {"zh", "Hant", "", ChineseTraditional}, + {"zh", "", "TW", ChineseTraditional}, + {"zh", "", "HK", ChineseTraditional}, + {"zh", "", "MO", ChineseTraditional}, + {"cmn", "", "", ChineseSimplified}, + {"cmn", "Hant", "", ChineseTraditional}, + {"cmn", "", "TW", ChineseTraditional}, + {"yue", "", "", ChineseTraditional}, + {"yue", "Hans", "", ChineseSimplified}, +}; + +inline constexpr Row JapaneseKoreanRows[] = { + {"ja", "", "", Japanese}, + {"ko", "", "", Korean}, +}; + +inline constexpr Row SlavicNordicRows[] = { + {"ru", "", "", Russian}, + {"uk", "", "", Ukrainian}, + {"pl", "", "", Polish}, + {"cs", "", "", Czech}, + {"fi", "", "", Finnish}, + {"sv", "", "", Swedish}, + {"da", "", "", Danish}, + {"no", "", "", Norwegian}, + {"nb", "", "", Norwegian}, + {"nn", "", "", Norwegian}, +}; + +inline constexpr Row OtherRows[] = { + {"ar", "", "", Arabic}, + {"th", "", "", Thai}, + {"vi", "", "", Vietnamese}, + {"id", "", "", Indonesian}, + {"in", "", "", Indonesian}, + {"tr", "", "", Turkish}, + {"el", "", "", Greek}, + {"hu", "", "", Hungarian}, +}; + +inline constexpr std::span RowGroups[] = { + LatinEuropeanRows, + IberianRows, + ChineseRows, + JapaneseKoreanRows, + SlavicNordicRows, + OtherRows, +}; + +} + +inline bool ParseTag(std::string_view text, Tag& tag) { + Tag parsed; + std::size_t index = 0; + bool seenScript = false; + bool seenRegion = false; + bool tail = false; + std::size_t position = 0; + while (true) { + const std::size_t end = text.find('-', position); + const std::string_view subtag = text.substr(position, end == std::string_view::npos ? std::string_view::npos : end - position); + if (subtag.empty() || subtag.size() > 8 || !Detail::AllOf(subtag, Detail::IsAlphanumeric)) return false; + if (index == 0) { + if ((subtag.size() != 2 && subtag.size() != 3) || !Detail::AllOf(subtag, Detail::IsAlpha)) return false; + for (char c : subtag) parsed.language += Detail::Lower(c); + } else if (!tail) { + if (subtag.size() == 1) { + tail = true; + } else if (!seenScript && !seenRegion && subtag.size() == 4 && Detail::AllOf(subtag, Detail::IsAlpha)) { + seenScript = true; + parsed.script += Detail::Upper(subtag[0]); + for (std::size_t i = 1; i < subtag.size(); ++i) parsed.script += Detail::Lower(subtag[i]); + } else if (!seenRegion && ((subtag.size() == 2 && Detail::AllOf(subtag, Detail::IsAlpha)) || (subtag.size() == 3 && Detail::AllOf(subtag, Detail::IsDigit)))) { + seenRegion = true; + for (char c : subtag) parsed.region += Detail::Upper(c); + } else { + tail = true; + } + } + ++index; + if (end == std::string_view::npos) break; + position = end + 1; + } + tag = std::move(parsed); + return true; +} + +inline std::string NormalizePosixLocale(std::string_view text) { + std::string_view modifier; + if (const std::size_t at = text.find('@'); at != std::string_view::npos) { + modifier = text.substr(at + 1); + text = text.substr(0, at); + } + text = text.substr(0, text.find('.')); + std::string name; + for (char c : text) name += c == '_' ? '-' : c; + if (name.empty() || Detail::EqualsIgnoringCase(name, "C") || Detail::EqualsIgnoringCase(name, "POSIX")) return {}; + std::string_view script; + if (Detail::EqualsIgnoringCase(modifier, "latin")) script = "Latn"; + if (Detail::EqualsIgnoringCase(modifier, "cyrillic")) script = "Cyrl"; + if (!script.empty()) { + const std::size_t dash = name.find('-'); + const std::string_view second = dash == std::string::npos ? std::string_view{} : std::string_view(name).substr(dash + 1, 4); + const bool hasScript = second.size() == 4 && Detail::AllOf(second, Detail::IsAlpha) && (dash + 5 == name.size() || name[dash + 5] == '-'); + if (!hasScript) name.insert(dash == std::string::npos ? name.size() : dash, std::string("-") + std::string(script)); + } + return name; +} + +inline bool ParseLocale(std::string_view text, Tag& tag) { + const std::string normalised = NormalizePosixLocale(text); + return !normalised.empty() && ParseTag(normalised, tag); +} + +inline int ConsoleLanguageForTag(const Tag& tag) { + int best = NoConsoleLanguage; + int bestScore = -1; + for (const std::span group : Detail::RowGroups) { + for (const Row& row : group) { + if (row.language != tag.language) continue; + if (!row.script.empty() && row.script != tag.script) continue; + if (!row.region.empty() && row.region != tag.region) continue; + const int score = (row.script.empty() ? 0 : 2) + (row.region.empty() ? 0 : 1); + if (score > bestScore) { + best = row.id; + bestScore = score; + } + } + } + return best; +} + +struct Match { + int id; + bool recognised; +}; + +inline Match MatchLocale(std::string_view locale) { + Tag tag; + const int id = ParseLocale(locale, tag) ? ConsoleLanguageForTag(tag) : NoConsoleLanguage; + if (id == NoConsoleLanguage) return {EnglishUs, false}; + return {id, true}; +} + +using Environment = std::function; + +inline std::vector PosixHostLocales(const Environment& environment) { + const auto read = [&environment](const char* name) { + const char* value = environment(name); + return std::string_view(value != nullptr ? value : ""); + }; + std::string_view effective; + for (const char* name : {"LC_ALL", "LC_MESSAGES", "LANG"}) { + effective = read(name); + if (!effective.empty()) break; + } + std::vector locales; + if (!NormalizePosixLocale(effective).empty()) { + std::string_view preferred = read("LANGUAGE"); + while (!preferred.empty()) { + const std::size_t colon = preferred.find(':'); + const std::string_view entry = preferred.substr(0, colon); + if (!entry.empty()) locales.emplace_back(entry); + preferred = colon == std::string_view::npos ? std::string_view{} : preferred.substr(colon + 1); + } + } + if (!effective.empty()) locales.emplace_back(effective); + return locales; +} + +#if defined(_WIN32) +inline std::vector WindowsHostLocales() { + wchar_t name[LOCALE_NAME_MAX_LENGTH]; + const int length = GetUserDefaultLocaleName(name, LOCALE_NAME_MAX_LENGTH); + if (length <= 1) return {}; + std::string locale; + for (int i = 0; i < length - 1; ++i) { + if (name[i] <= 0 || name[i] > 127) return {}; + locale += static_cast(name[i]); + } + return {locale}; +} +#endif + +#if defined(__APPLE__) +inline std::vector MacHostLocales() { + std::vector locales; + CFArrayRef languages = CFLocaleCopyPreferredLanguages(); + if (languages == nullptr) return locales; + const CFIndex count = CFArrayGetCount(languages); + for (CFIndex i = 0; i < count; ++i) { + const auto name = static_cast(CFArrayGetValueAtIndex(languages, i)); + char buffer[128]; + if (CFStringGetCString(name, buffer, sizeof buffer, kCFStringEncodingUTF8)) locales.emplace_back(buffer); + } + CFRelease(languages); + return locales; +} +#endif + +inline std::vector HostLocales() { +#if defined(_WIN32) + return WindowsHostLocales(); +#elif defined(__APPLE__) + return MacHostLocales(); +#else + return PosixHostLocales([](const char* name) { return std::getenv(name); }); +#endif +} + +using HostLocaleSource = std::function()>; +using HostLocaleNotice = std::function; + +inline int ResolveHostLanguage(const HostLocaleSource& source, const HostLocaleNotice& notice) { + const std::vector locales = source(); + for (const std::string& locale : locales) { + const Match match = MatchLocale(locale); + if (match.recognised) return match.id; + } + const std::string detected = locales.empty() ? "the host reports no locale" : "the host locale \"" + locales.front() + "\" has no console language"; + notice("ANYPS5_LANGUAGE=auto: " + detected + ", using English (United States), language number " + std::to_string(EnglishUs)); + return EnglishUs; +} + +} + +#endif diff --git a/core/libs/prx/libSceSystemService/PendingExports.cpp b/core/libs/prx/libSceSystemService/PendingExports.cpp index f8bbee17c8..925e860a40 100644 --- a/core/libs/prx/libSceSystemService/PendingExports.cpp +++ b/core/libs/prx/libSceSystemService/PendingExports.cpp @@ -185,11 +185,6 @@ int APS5_VABI sceLncUtilAddLocalProcess(std::int32_t appId, const char* path, co return 0; } -int APS5_VABI sceLncUtilBlockAppSuspend(const char* reason) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceLncUtilBlockingGetEventForDaemon(LncDaemonEvent* event, std::uint64_t receiveArgument) { NotImplemented_nid_no_patch(__func__); return 0; @@ -215,16 +210,6 @@ int APS5_VABI sceLncUtilDeclareReadyForSuspend() { return 0; } -int APS5_VABI sceLncUtilDisableSuspendNotification() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceLncUtilEnableSuspendNotification() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceLncUtilFinishSpecialResume() { NotImplemented_nid_no_patch(__func__); return 0; @@ -360,16 +345,6 @@ int APS5_VABI sceLncUtilIsHostBoot(std::int32_t appId, bool* hostBoot) { return 0; } -int APS5_VABI sceLncUtilIsPs2Emu(bool* emulated) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -bool APS5_VABI sceLncUtilIsShellUiFgAndGameBgCpuMode() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceLncUtilIsSpeculativeLaunch(std::int32_t appId, bool* speculative) { if (speculative == nullptr) return LNC_UTIL_ERROR_INVALID_ARGUMENT; if (appId != sceSystemServiceGetAppIdOfRunningBigApp()) throw std::runtime_error("sceLncUtilIsSpeculativeLaunch: application other than the running title"); @@ -512,11 +487,6 @@ int APS5_VABI sceLncUtilTryBlockAppSuspend(const char* reason) { return 0; } -int APS5_VABI sceLncUtilUnblockAppSuspend(const char* reason) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceLncUtilUnregisterCdlgSharedMemoryName(const char* name) { NotImplemented_nid_no_patch(__func__); return 0; @@ -866,26 +836,11 @@ int APS5_VABI sceShellCoreUtilGetFsckProgress(const char* titleId, std::uint32_t return 0; } -int APS5_VABI sceShellCoreUtilGetGameLiveStreamingStatus(bool* active) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - bool APS5_VABI sceShellCoreUtilGetGnmCompositorOnScreenProfilerFlag() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetGpuLoadEmulationMode() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceShellCoreUtilGetGpuLoadEmulationModeByAppId(std::int32_t appId) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceShellCoreUtilGetHidConfigFileInfoString(char* value, std::size_t capacity, std::int32_t configIndex) { NotImplemented_nid_no_patch(__func__); return 0; @@ -986,26 +941,11 @@ int APS5_VABI sceShellCoreUtilGetPsnAccountInfo(ShellCorePsnAccountInfoFirst* fi return 0; } -int APS5_VABI sceShellCoreUtilGetRemotePlayStatus(bool* active) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceShellCoreUtilGetRunLevel() { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI sceShellCoreUtilGetSharePlayStatus(bool* active) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceShellCoreUtilGetSocialScreenStatus(bool* active) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceShellCoreUtilGetSplashScreenState(std::int32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1016,11 +956,6 @@ int APS5_VABI sceShellCoreUtilGetSupportSiteURL(std::uint32_t first, std::uint32 return 0; } -bool APS5_VABI sceShellCoreUtilGetSuspendConfirmationDialogFlag(std::int32_t appId) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceShellCoreUtilGetSystemBGState(std::int32_t appId, std::uint32_t* state) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1106,31 +1041,11 @@ bool APS5_VABI sceShellCoreUtilIsBgmCpuBudgetAvailable() { return 0; } -bool APS5_VABI sceShellCoreUtilIsExternalStorageAppMoveInProgress() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceShellCoreUtilIsEyeToEyeDistanceAdjusted(std::uint32_t userId, std::uint8_t* adjusted) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -bool APS5_VABI sceShellCoreUtilIsGameLiveStreamingOnAir() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceShellCoreUtilIsIDUState() { NotImplemented_nid_no_patch(__func__); return 0; } -bool APS5_VABI sceShellCoreUtilIsImposeScreenOverlaid() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - bool APS5_VABI sceShellCoreUtilIsInSystemSuspendBlackList(std::uint32_t appId) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1161,11 +1076,6 @@ bool APS5_VABI sceShellCoreUtilIsRemotePlayCpuBudgetAcquired() { return 0; } -bool APS5_VABI sceShellCoreUtilIsScreenSaverOn() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - bool APS5_VABI sceShellCoreUtilIsSharePlayCpuBudgetAcquired() { NotImplemented_nid_no_patch(__func__); return 0; @@ -1186,11 +1096,6 @@ int APS5_VABI sceShellCoreUtilIsTitleWorkaroundEnabled(std::int32_t selector, co return 0; } -bool APS5_VABI sceShellCoreUtilIsUsbMassStorageMounted(std::uint32_t deviceIndex) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceShellCoreUtilLaunchByUri(const char* uri, const ShellCoreLaunchByUriParam* param) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1401,16 +1306,6 @@ int APS5_VABI sceShellCoreUtilSetGnmCompositorOnScreenProfilerFlag(std::int32_t return 0; } -int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationMode(std::uint32_t mode) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceShellCoreUtilSetGpuLoadEmulationModeByAppId(std::int32_t appId, std::uint32_t mode) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceShellCoreUtilSetIDUMode(std::uint8_t mode) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1780,16 +1675,6 @@ int APS5_VABI sceSystemServiceDisablePersonalEyeToEyeDistanceSetting() { return 0; } -int APS5_VABI sceSystemServiceDisableSuspendConfirmationDialog() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSystemServiceDisableSuspendNotification() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceSystemServiceDisableVoiceRecognition() { return SystemServiceUnavailable; } @@ -1799,16 +1684,6 @@ int APS5_VABI sceSystemServiceEnablePersonalEyeToEyeDistanceSetting() { return 0; } -int APS5_VABI sceSystemServiceEnableSuspendConfirmationDialog() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceSystemServiceEnableSuspendNotification() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceSystemServiceGetAppCategoryType(std::int32_t appId, std::uint32_t* categoryType) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1839,11 +1714,6 @@ int APS5_VABI sceSystemServiceGetEventForDaemon(SystemServiceDaemonEvent* event) return 0; } -int APS5_VABI sceSystemServiceGetGpuLoadEmulationMode() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceSystemServiceGetLocalProcessStatusList(SystemServiceLocalProcessStatus* statuses, std::uint32_t capacity, std::uint32_t* count) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1906,11 +1776,6 @@ int APS5_VABI sceSystemServiceGetRecentLotusPushedButton(std::uint32_t timeout, return 0; } -int APS5_VABI sceSystemServiceGetRemoteplayStatus(bool* active) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceSystemServiceGetRenderingMode() { NotImplemented_nid_no_patch(__func__); return 0; @@ -1956,26 +1821,6 @@ bool APS5_VABI sceSystemServiceIsBgmCpuBudgetAvailable() { return 0; } -int APS5_VABI sceSystemServiceIsEyeToEyeDistanceAdjusted(std::uint32_t userId, std::uint8_t* adjusted) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -bool APS5_VABI sceSystemServiceIsGameLiveStreamingOnAir() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -bool APS5_VABI sceSystemServiceIsScreenSaverOn() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -bool APS5_VABI sceSystemServiceIsShellUiFgAndGameBgCpuMode() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceSystemServiceKillLocalProcess(std::int32_t appId, std::int32_t processId) { return sceLncUtilKillLocalProcess(appId, processId); } @@ -2107,11 +1952,6 @@ int APS5_VABI sceSystemServiceSetControllerFocusPermission(std::uint8_t permissi return 0; } -int APS5_VABI sceSystemServiceSetGpuLoadEmulationMode(std::uint32_t mode) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceSystemServiceSetOutOfVrPlayAreaFlag(std::uint8_t flag) { NotImplemented_nid_no_patch(__func__); return 0; @@ -2283,21 +2123,11 @@ int APS5_VABI sceSystemStateMgrGetTriggerCode() { return 0; } -bool APS5_VABI sceSystemStateMgrIsBdDriveReady() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceSystemStateMgrIsGpuPerformanceNormal() { NotImplemented_nid_no_patch(__func__); return 0; } -bool APS5_VABI sceSystemStateMgrIsShellUIShutdownInProgress() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - bool APS5_VABI sceSystemStateMgrIsStandbyModeEnabled() { NotImplemented_nid_no_patch(__func__); return 0; diff --git a/core/libs/prx/libSceSystemService/SystemService.hpp b/core/libs/prx/libSceSystemService/SystemService.hpp index dc9d45e4d9..af106ea7dc 100644 --- a/core/libs/prx/libSceSystemService/SystemService.hpp +++ b/core/libs/prx/libSceSystemService/SystemService.hpp @@ -4,12 +4,15 @@ #include #include #include +#include #include #include #include #include #include +#include "prx/libSceSystemService/LanguageLocale.hpp" + constexpr int SYSTEM_SERVICE_OK = 0; constexpr int SYSTEM_SERVICE_ERROR_PARAMETER = -2136932349; constexpr int SYSTEM_SERVICE_ERROR_NO_EVENT = -2136932348; @@ -41,16 +44,23 @@ 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; +inline int SystemServiceHostLanguage() { + static const int language = SystemServiceLocale::ResolveHostLanguage( + SystemServiceLocale::HostLocales, [](const std::string& line) { std::fprintf(stderr, "%s\n", line.c_str()); }); + return language; +} + inline int SystemServiceConsoleLanguage(std::string_view caller) { const char* configured = std::getenv("ANYPS5_LANGUAGE"); if (configured == nullptr || configured[0] == '\0') return SYSTEM_SERVICE_PARAM_LANG_ENGLISH_US; const std::string_view text(configured); + if (SystemServiceLocale::Detail::EqualsIgnoringCase(text, "auto")) return SystemServiceHostLanguage(); int language = -1; const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), language); if (error != std::errc{} || end != text.data() + text.size() || language < SYSTEM_SERVICE_PARAM_LANG_JAPANESE || language > SYSTEM_SERVICE_PARAM_LANG_UKRAINIAN) { throw std::runtime_error(std::string(caller) + ": ANYPS5_LANGUAGE must be a console language number from " + std::to_string(SYSTEM_SERVICE_PARAM_LANG_JAPANESE) + " to " + std::to_string(SYSTEM_SERVICE_PARAM_LANG_UKRAINIAN) + - ", not \"" + std::string(text) + "\""); + " or auto, not \"" + std::string(text) + "\""); } return language; } diff --git a/core/libs/prx/libSceSystemService/include/InventoryExports.hpp b/core/libs/prx/libSceSystemService/include/InventoryExports.hpp new file mode 100644 index 0000000000..52c56ee1f1 --- /dev/null +++ b/core/libs/prx/libSceSystemService/include/InventoryExports.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI libSceSystemServiceUnknown_16waHD4tFBo_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_1m_minus_E1Xh8COo_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_7eu2qjQGigw_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_90unWbnI0qE_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_E_minus_bXVJqwGZw_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_G3oPdw3lkps_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_KgiwSrROsIQ_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_LxGOxroprpg_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_NXFtO8BfVeo_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_Oxh0CSsTZ78_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_OyTwhCKWXi4_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_U0HutHWnMBM_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_VDs9VSjSJh0_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_ZWNP_plus__minus_dWr8E_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_axzblV8OvWU_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_bJyWctnSXyU_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_bUNkT3XDg0Y_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_c5_plus_4Scso9EU_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_fBGD_minus_HNimSk_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_fPAZ5DBsDxo_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_ifJiF5witJ4_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_kDGjRMtUDxo_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_lgpIdZ8T0xg_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_pj1SzIAxPlk_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_qdTPJWjquDc_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_rIqPq0oWlrg_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_sgYo_minus_zXHQRE_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_tPMOYbbviYs_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_uElcdmhh_minus_c8_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_xn_minus_HgPW29x4_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_y16IXiJfafA_nid_no_patch(); +int APS5_VABI libSceSystemServiceUnknown_yOiZq_plus_9_minus_ZMQ_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceSystemService/include/SystemService.hpp b/core/libs/prx/libSceSystemService/include/SystemService.hpp index 47b2c0e0db..a44ed286ce 100644 --- a/core/libs/prx/libSceSystemService/include/SystemService.hpp +++ b/core/libs/prx/libSceSystemService/include/SystemService.hpp @@ -259,6 +259,7 @@ int APS5_VABI sceCoredumpSequenceHandlerGetCoredumpStatusByAppId(void); int APS5_VABI sceCoredumpSequenceHandlerCancelCoredump(void); int APS5_VABI sceSystemServiceIsBgmPlaying(void); int APS5_VABI sceShellCoreUtilIsBgmPlaying(void); +int APS5_VABI sceSystemServiceUnknown_M_KwxF0amiw(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); diff --git a/core/libs/prx/libSceSystemService/libSceSystemService.hpp b/core/libs/prx/libSceSystemService/libSceSystemService.hpp index e3ff176277..1268c62468 100644 --- a/core/libs/prx/libSceSystemService/libSceSystemService.hpp +++ b/core/libs/prx/libSceSystemService/libSceSystemService.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceSystemService/include/SystemService.hpp" +#include "prx/libSceSystemService/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceTEEClient/Export.cpp b/core/libs/prx/libSceTEEClient/Export.cpp index f550aa63e3..d3d2404fc4 100644 --- a/core/libs/prx/libSceTEEClient/Export.cpp +++ b/core/libs/prx/libSceTEEClient/Export.cpp @@ -78,10 +78,9 @@ int APS5_VABI libSceTEEClientUnknown_Xn5kfzvPht4_nid_no_patch() { } APS5_EXPORT("Xn5kfzvPht4", libSceTEEClientUnknown_Xn5kfzvPht4_nid_no_patch); -int APS5_VABI libSceTEEClientUnknown_ZAJdq_minus_IONZ4_nid_no_patch() { +int APS5_VABI AMDTEE_DLM_StartTADebug() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("ZAJdq-IONZ4", libSceTEEClientUnknown_ZAJdq_minus_IONZ4_nid_no_patch); int APS5_VABI libSceTEEClientUnknown_d_minus_OLk3nvFIA_nid_no_patch() { NotImplemented_nid_no_patch(__func__); diff --git a/core/libs/prx/libSceTEEClient/include/InventoryExports.hpp b/core/libs/prx/libSceTEEClient/include/InventoryExports.hpp index fa490466d4..7dcaeddb10 100644 --- a/core/libs/prx/libSceTEEClient/include/InventoryExports.hpp +++ b/core/libs/prx/libSceTEEClient/include/InventoryExports.hpp @@ -7,7 +7,7 @@ extern "C" { int APS5_VABI libSceTEEClientUnknown_H9IE7wXy3qY_nid_no_patch(); int APS5_VABI libSceTEEClientUnknown_VaBqpbmaWpc_nid_no_patch(); int APS5_VABI libSceTEEClientUnknown_Xn5kfzvPht4_nid_no_patch(); -int APS5_VABI libSceTEEClientUnknown_ZAJdq_minus_IONZ4_nid_no_patch(); +int APS5_VABI AMDTEE_DLM_StartTADebug(); int APS5_VABI libSceTEEClientUnknown_d_minus_OLk3nvFIA_nid_no_patch(); } diff --git a/core/libs/prx/libSceUlt/CMakeLists.txt b/core/libs/prx/libSceUlt/CMakeLists.txt index 5736205384..564a05fa3e 100644 --- a/core/libs/prx/libSceUlt/CMakeLists.txt +++ b/core/libs/prx/libSceUlt/CMakeLists.txt @@ -3,6 +3,26 @@ target_include_directories(libSceUlt PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libSceUlt) if(BUILD_TESTING) + add_test_executable(guest_ult_thread_lifecycle_tests ${LIBS_INCLUDE_DIR}/tests/UltThreadLifecycle.cpp src/ReaderWriterLock.cpp) + target_include_directories(guest_ult_thread_lifecycle_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_ult_thread_lifecycle_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_ult_thread_lifecycle_tests) + set(ultLifecycleCases create-error create-exception published-exception join-owner tryjoin-owner join-error join-exception join-cleanup-exception) + if(NOT APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + target_compile_definitions(guest_ult_thread_lifecycle_tests PRIVATE ULT_LIFECYCLE_INTERPOSE=1) + target_link_options(guest_ult_thread_lifecycle_tests PRIVATE + LINKER:--wrap=pthread_mutex_lock LINKER:--wrap=pthread_mutex_unlock LINKER:--wrap=__pthread_cleanup_push_imp_nid_postfix) + list(APPEND ultLifecycleCases publication generation cleanup-setup) + endif() + foreach(testCase IN LISTS ultLifecycleCases) + add_test(NAME guest_ult_thread_lifecycle_${testCase} COMMAND guest_ult_thread_lifecycle_tests ${testCase}) + set_tests_properties(guest_ult_thread_lifecycle_${testCase} PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_ult_thread_lifecycle_${testCase} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + add_test_executable(guest_ult_condition_tests ${LIBS_INCLUDE_DIR}/tests/GuestUltCondition.cpp) target_include_directories(guest_ult_condition_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_ult_condition_tests PRIVATE libSceUlt libc) @@ -31,9 +51,9 @@ if(BUILD_TESTING) add_test_executable(guest_ult_thread_tests ${LIBS_INCLUDE_DIR}/tests/GuestUltThread.cpp) target_include_directories(guest_ult_thread_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_ult_thread_tests PRIVATE libSceUlt libc) + target_link_libraries(guest_ult_thread_tests PRIVATE libSceUlt libkernel libc) configure_windows_unwind(guest_ult_thread_tests) - foreach(testCase IN ITEMS outstanding validation tryjoin destroy underscoredcreate) + foreach(testCase IN ITEMS outstanding validation tryjoin destroy underscoredcreate cancel-blocked cancel-pending self-join) add_test(NAME guest_ult_thread_${testCase} COMMAND guest_ult_thread_tests ${testCase}) set_tests_properties(guest_ult_thread_${testCase} PROPERTIES TIMEOUT 20) if(WIN32) @@ -56,7 +76,7 @@ if(BUILD_TESTING) target_include_directories(guest_ult_sync_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_ult_sync_tests PRIVATE libSceUlt libc libkernel) configure_windows_unwind(guest_ult_sync_tests) - foreach(testCase IN ITEMS mutex-trylock opt-params rwlock-validation rwlock-readers rwlock-writer rwlock-pool rwlock-finalize ulthreads get-info queue-waiters info-waiters) + foreach(testCase IN ITEMS mutex-trylock opt-params rwlock-validation rwlock-readers rwlock-writer rwlock-pool rwlock-finalize rwlock-underscored ulthreads get-info queue-waiters info-waiters) add_test(NAME guest_ult_sync_${testCase} COMMAND guest_ult_sync_tests ${testCase}) set_tests_properties(guest_ult_sync_${testCase} PROPERTIES TIMEOUT 20) if(WIN32) diff --git a/core/libs/prx/libSceUlt/Export.cpp b/core/libs/prx/libSceUlt/Export.cpp index 3b55ed03e2..4c8b2ee861 100644 --- a/core/libs/prx/libSceUlt/Export.cpp +++ b/core/libs/prx/libSceUlt/Export.cpp @@ -1,4 +1,5 @@ #include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" #include "prx/libkernel/include/Api.hpp" #include "prx/libSceUlt/include/Api.hpp" #include @@ -131,6 +132,55 @@ void* APS5_VABI ulthreadRunner(void* arg) { return reinterpret_cast(static_cast(result)); } +void APS5_VABI releaseUlthreadJoin(void* argument) { + auto& state = *static_cast*>(argument); + if (!state) return; + { + std::lock_guard lock(gMutex); + state->_joining = false; + } + state.reset(); +} + +int joinUlthread(void* ulthread, std::int32_t* status, bool blocking) { + if (ulthread == nullptr) return ULT_ERROR_NULL; + std::shared_ptr state; + { + std::lock_guard lock(gMutex); + const auto it = gUlthreads.find(ulthread); + if (it == gUlthreads.end()) return ULT_ERROR_STATE; + state = it->second; + if (!blocking && !state->_exited.load(std::memory_order_acquire)) return ULT_ERROR_AGAIN; + if (state->_joining || state.get() == gCurrentUlthread) return ULT_ERROR_STATE; + state->_joining = true; + } + CleanupHandler cleanup{}; + bool cleanupRegistered = false; + void* result = nullptr; + int joined; + try { + __pthread_cleanup_push_imp_nid_postfix(releaseUlthreadJoin, &state, &cleanup); + cleanupRegistered = true; + joined = scePthreadJoin(state->_thread, &result); + } catch (...) { + if (state) { + if (cleanupRegistered) __pthread_cleanup_pop_imp_nid_postfix(1); + else releaseUlthreadJoin(&state); + } + throw; + } + __pthread_cleanup_pop_imp_nid_postfix(0); + if (joined != 0) { + releaseUlthreadJoin(&state); + return ULT_ERROR_STATE; + } + if (status != nullptr) *status = static_cast(reinterpret_cast(result)); + std::lock_guard lock(gMutex); + const auto it = gUlthreads.find(ulthread); + if (it != gUlthreads.end() && it->second == state) gUlthreads.erase(it); + return ULT_OK; +} + } UltThreadIdentity UltCurrentThreadIdentity_nid_no_patch() { @@ -844,67 +894,29 @@ int APS5_VABI sceUltUlthreadCreate(void* ulthread, const char* name, UltUlthread if (name != nullptr) { std::strncpy(state->_name, name, sizeof(state->_name) - 1); } - { - std::lock_guard lock(gMutex); - if (gRuntimes.find(runtime) == gRuntimes.end()) { - return ULT_ERROR_STATE; - } - if (gUlthreads.find(ulthread) != gUlthreads.end()) { - return ULT_ERROR_STATE; - } - std::memset(ulthread, 0, 512); - gUlthreads[ulthread] = state; - } + std::lock_guard lock(gMutex); + if (!gRuntimes.contains(runtime) || gUlthreads.contains(ulthread)) return ULT_ERROR_STATE; 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); - return ULT_ERROR_AGAIN; + gUlthreads.emplace(ulthread, state); + std::memset(ulthread, 0, 512); + try { + if (scePthreadCreate(&state->_thread, nullptr, ulthreadRunner, state.get(), threadName.c_str()) != 0) { + gUlthreads.erase(ulthread); + return ULT_ERROR_AGAIN; + } + } catch (...) { + if (state->_thread == nullptr) gUlthreads.erase(ulthread); + throw; } return ULT_OK; } int APS5_VABI sceUltUlthreadJoin(void* ulthread, std::int32_t* status) { - if (ulthread == nullptr) { - return ULT_ERROR_NULL; - } - std::shared_ptr state; - { - std::lock_guard lock(gMutex); - auto it = gUlthreads.find(ulthread); - if (it == gUlthreads.end()) { - return ULT_ERROR_STATE; - } - state = it->second; - } - void* result = nullptr; - if (scePthreadJoin(state->_thread, &result) != 0) { - return ULT_ERROR_STATE; - } - if (status != nullptr) { - *status = static_cast(reinterpret_cast(result)); - } - std::lock_guard lock(gMutex); - gUlthreads.erase(ulthread); - return ULT_OK; + return joinUlthread(ulthread, status, true); } int APS5_VABI sceUltUlthreadTryJoin(void* ulthread, std::int32_t* status) { - if (ulthread == nullptr) { - return ULT_ERROR_NULL; - } - { - std::lock_guard lock(gMutex); - auto it = gUlthreads.find(ulthread); - if (it == gUlthreads.end()) { - return ULT_ERROR_STATE; - } - if (!it->second->_exited.load(std::memory_order_acquire)) { - return ULT_ERROR_AGAIN; - } - } - return sceUltUlthreadJoin(ulthread, status); + return joinUlthread(ulthread, status, false); } int APS5_VABI sceUltUlthreadGetSelf(void** ulthread) { @@ -955,6 +967,10 @@ int APS5_VABI sceUltUlthreadRuntimeDestroy(void* runtime) { return ULT_OK; } +int APS5_VABI _sceUltWaitingQueueResourcePoolOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { + 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) { if (pool == nullptr) return ULT_ERROR_NULL; if ((reinterpret_cast(pool) & 7u) != 0) return ULT_ERROR_ALIGNMENT; @@ -966,6 +982,10 @@ int APS5_VABI _sceUltWaitingQueueResourcePoolCreate(void* pool, const char* name 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::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; @@ -990,6 +1010,10 @@ int APS5_VABI _sceUltMutexCreate(void* mutex, const char* name, void* waitingQue 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) { if (pool == nullptr) return ULT_ERROR_NULL; if ((reinterpret_cast(pool) & 7u) != 0) return ULT_ERROR_ALIGNMENT; @@ -1001,6 +1025,10 @@ int APS5_VABI _sceUltQueueDataResourcePoolCreate(void* pool, const char* name, s 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) { 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; @@ -1022,6 +1050,10 @@ int APS5_VABI _sceUltUlthreadRuntimeCreate(void* runtime, const char* name, std: 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) { return sceUltUlthreadCreate(ulthread, name, entry, arg, context, sizeContext, runtime, optParam, 0); } diff --git a/core/libs/prx/libSceUlt/SceUltTypes.hpp b/core/libs/prx/libSceUlt/SceUltTypes.hpp index f218503bbc..80558f8e1f 100644 --- a/core/libs/prx/libSceUlt/SceUltTypes.hpp +++ b/core/libs/prx/libSceUlt/SceUltTypes.hpp @@ -362,6 +362,7 @@ struct UltUlthreadState { std::atomic _exited{false}; char _name[32]{}; std::uint32_t _contextAttribute = 0; + bool _joining = false; }; #endif diff --git a/core/libs/prx/libSceUlt/include/Api.hpp b/core/libs/prx/libSceUlt/include/Api.hpp index c4c3213f0a..1a4413c06c 100644 --- a/core/libs/prx/libSceUlt/include/Api.hpp +++ b/core/libs/prx/libSceUlt/include/Api.hpp @@ -61,6 +61,13 @@ int APS5_VABI _sceUltQueueCreate(void* queue, const char* name, std::uint64_t da 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 _sceUltWaitingQueueResourcePoolOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI _sceUltSemaphoreOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI _sceUltQueueDataResourcePoolOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI _sceUltQueueOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +int APS5_VABI _sceUltUlthreadOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion); +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 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); diff --git a/core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp b/core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp index 489f3587ea..e01ac76287 100644 --- a/core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp +++ b/core/libs/prx/libSceUlt/src/ReaderWriterLock.cpp @@ -208,6 +208,14 @@ int APS5_VABI sceUltReaderWriterLockUnlockWrite(void* rwlock) { return ULT_OK; } +int APS5_VABI _sceUltReaderWriterLockOptParamInitialize(UltOptParam* optParam, std::uint32_t buildVersion) { + return sceUltReaderWriterLockOptParamInitialize(optParam, buildVersion); +} + +int APS5_VABI _sceUltReaderWriterLockCreate(void* rwlock, const char* name, void* waitingQueueResourcePool, const UltOptParam* optParam, std::uint32_t buildVersion) { + return sceUltReaderWriterLockCreate(rwlock, name, waitingQueueResourcePool, optParam, buildVersion); +} + int APS5_VABI sceUltGetReaderWriterLockInfo(void* object, UltReaderWriterLockInfo* info, UltWaitingThreadInfo* waitingThreads, std::uint32_t capacity) { if (object == nullptr || info == nullptr) return ULT_ERROR_NULL; if (waitingThreads == nullptr && capacity != 0) return ULT_ERROR_NULL; diff --git a/core/libs/prx/libSceUsbd/Export.cpp b/core/libs/prx/libSceUsbd/Export.cpp index edc6a19de0..ea86f3130f 100644 --- a/core/libs/prx/libSceUsbd/Export.cpp +++ b/core/libs/prx/libSceUsbd/Export.cpp @@ -7,8 +7,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -104,6 +106,109 @@ void DropDeviceReference(UsbdState& state, libusb_device* device) { if (--found->second == 0) state.devices.erase(found); } +struct UsbdEvents { + std::mutex lock; + std::condition_variable changed; + std::atomic owner{}; + std::atomic handler{}; + std::atomic completions{0}; +}; + +UsbdEvents& Events() { + static auto* events = new UsbdEvents(); + return *events; +} + +bool ValidTimeout(const UsbdTimeval* timeout) { + return timeout == nullptr || (timeout->seconds >= 0 && timeout->seconds <= INT_MAX && timeout->microseconds >= 0 && timeout->microseconds < 1000000); +} + +void AcquireEvents(UsbdEvents& events, const char* function) { + const auto self = std::this_thread::get_id(); + if (events.owner.load() == self) throw std::runtime_error(std::string(function) + ": this thread already holds the event lock"); + events.lock.lock(); + events.owner.store(self); +} + +void RequireEventLock(UsbdEvents& events, const char* function) { + if (events.owner.load() != std::this_thread::get_id()) throw std::runtime_error(std::string(function) + ": this thread does not hold the event lock"); +} + +void ReleaseEvents(UsbdEvents& events, const char* function) { + RequireEventLock(events, function); + events.owner.store(std::thread::id{}); + events.lock.unlock(); +} + +void LockEventsAndClaim(UsbdEvents& events, const char* function) { + AcquireEvents(events, function); + if (events.handler.load() == std::thread::id{}) events.handler.store(std::this_thread::get_id()); +} + +void UnlockEventsAndRelease(UsbdEvents& events, const char* function) { + RequireEventLock(events, function); + if (events.handler.load() == std::this_thread::get_id()) { + events.handler.store(std::thread::id{}); + events.changed.notify_all(); + } + ReleaseEvents(events, function); +} + +int WaitForChange(UsbdEvents& events, const UsbdTimeval* timeout) { + const auto self = std::this_thread::get_id(); + std::unique_lock guard(events.lock, std::adopt_lock); + events.owner.store(std::thread::id{}); + bool timedOut = false; + if (timeout == nullptr) { + events.changed.wait(guard); + } else { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout->seconds) + std::chrono::microseconds(timeout->microseconds); + timedOut = events.changed.wait_until(guard, deadline) == std::cv_status::timeout; + } + guard.release(); + events.owner.store(self); + if (events.handler.load() == std::thread::id{}) events.handler.store(self); + return timedOut ? 1 : 0; +} + +libusb_context* CurrentContext(); + +int HandleAsHandler(UsbdEvents& events, const UsbdTimeval* timeout) { + const auto self = std::this_thread::get_id(); + const auto before = events.completions.load(); + events.owner.store(std::thread::id{}); + events.lock.unlock(); + libusb_context* context = CurrentContext(); + int result = 0; + if (timeout != nullptr && context == nullptr) { + std::this_thread::sleep_for(std::chrono::seconds(timeout->seconds) + std::chrono::microseconds(timeout->microseconds)); + } else if (timeout != nullptr) { + timeval host{}; + host.tv_sec = static_cast(timeout->seconds); + host.tv_usec = static_cast(timeout->microseconds); + result = libusb_handle_events_timeout_completed(context, &host, nullptr); + } else { + while (result == 0 && events.completions.load() == before) { + if (context == nullptr) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } else { + timeval slice{}; + slice.tv_sec = 1; + result = libusb_handle_events_timeout_completed(context, &slice, nullptr); + } + } + } + events.lock.lock(); + events.owner.store(self); + events.changed.notify_all(); + return result; +} + +std::int32_t HandleEventsLocked(UsbdEvents& events, const UsbdTimeval* timeout) { + if (events.handler.load() == std::this_thread::get_id()) return ToGuestError(HandleAsHandler(events, timeout)); + return WaitForChange(events, timeout) != 0 ? ToGuestError(LIBUSB_ERROR_TIMEOUT) : 0; +} + void LIBUSB_CALL CompleteTransfer(libusb_transfer* host) { auto* guest = static_cast(host->user_data); guest->status = host->status; @@ -118,9 +223,16 @@ void LIBUSB_CALL CompleteTransfer(libusb_transfer* host) { state.inFlight.erase(guest); } libusb_free_transfer(host); + Events().completions.fetch_add(1); if (guest->callback != nullptr) guest->callback(guest); } +libusb_context* CurrentContext() { + auto& state = State(); + std::lock_guard lock(state.mutex); + return state.context; +} + int MaxPacketSize(libusb_device* device, std::uint8_t endpoint) { libusb_config_descriptor* config = nullptr; const int result = libusb_get_active_config_descriptor(device, &config); @@ -215,20 +327,12 @@ void APS5_VABI sceUsbdFreeDeviceList(void** list, std::int32_t unrefDevices) { std::int32_t APS5_VABI sceUsbdHandleEventsTimeout(const UsbdTimeval* timeout) { if (timeout == nullptr || timeout->seconds < 0 || timeout->microseconds < 0 || timeout->microseconds >= 1000000) return SCE_USBD_ERROR_INVALID_ARG; - libusb_context* context; - { - auto& state = State(); - std::lock_guard lock(state.mutex); - context = state.context; - } - if (context == nullptr) { - std::this_thread::sleep_for(std::chrono::seconds(timeout->seconds) + std::chrono::microseconds(timeout->microseconds)); - return 0; - } - timeval host{}; - host.tv_sec = static_cast(timeout->seconds); - host.tv_usec = static_cast(timeout->microseconds); - return ToGuestError(libusb_handle_events_timeout_completed(context, &host, nullptr)); + if (!ValidTimeout(timeout)) throw std::runtime_error(std::string(__func__) + ": a timeout of more than INT_MAX seconds is not supported"); + auto& events = Events(); + LockEventsAndClaim(events, __func__); + const std::int32_t result = HandleEventsLocked(events, timeout); + UnlockEventsAndRelease(events, __func__); + return result; } UsbdTransfer* APS5_VABI sceUsbdAllocTransfer(std::int32_t isoPackets) { @@ -358,7 +462,7 @@ std::int32_t APS5_VABI sceUsbdCancelTransfer(UsbdTransfer* transfer) { } std::int32_t APS5_VABI sceUsbdEventHandlingOk() { - return 1; + return Events().handler.load() == std::this_thread::get_id() ? 1 : 0; } void APS5_VABI sceUsbdFreeConfigDescriptor(void* config) { @@ -572,8 +676,7 @@ std::int32_t APS5_VABI sceUsbdDetachKernelDriver(void* deviceHandle, int interfa } std::int32_t APS5_VABI sceUsbdEventHandlerActive() { - NotImplemented_nid_no_patch(__func__); - return 0; + return Events().handler.load() != std::thread::id{} ? 1 : 0; } std::int32_t APS5_VABI sceUsbdGetConfigDescriptorByValue(void* device, std::uint8_t configurationValue, void** config) { @@ -661,21 +764,26 @@ std::int32_t APS5_VABI sceUsbdGetStringDescriptorAscii(void* deviceHandle, std:: } std::int32_t APS5_VABI sceUsbdHandleEvents() { - NotImplemented_nid_no_patch(__func__); - return 0; + auto& events = Events(); + LockEventsAndClaim(events, __func__); + const std::int32_t result = HandleEventsLocked(events, nullptr); + UnlockEventsAndRelease(events, __func__); + return result; } std::int32_t APS5_VABI sceUsbdHandleEventsLocked(UsbdTimeval* timeout) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (!ValidTimeout(timeout)) throw std::runtime_error(std::string(__func__) + ": a negative or oversized timeout has no known result"); + auto& events = Events(); + RequireEventLock(events, __func__); + return HandleEventsLocked(events, timeout); } void APS5_VABI sceUsbdLockEventWaiters() { - NotImplemented_nid_no_patch(__func__); + AcquireEvents(Events(), __func__); } void APS5_VABI sceUsbdLockEvents() { - NotImplemented_nid_no_patch(__func__); + LockEventsAndClaim(Events(), __func__); } void* APS5_VABI sceUsbdOpenDeviceWithVidPid(std::uint16_t vendorId, std::uint16_t productId) { @@ -715,21 +823,31 @@ std::int32_t APS5_VABI sceUsbdSetInterfaceAltSetting(void* deviceHandle, int int } std::int32_t APS5_VABI sceUsbdTryLockEvents() { - NotImplemented_nid_no_patch(__func__); + auto& events = Events(); + const auto self = std::this_thread::get_id(); + if (events.owner.load() == self || !events.lock.try_lock()) return 1; + if (events.handler.load() != std::thread::id{}) { + events.lock.unlock(); + return 1; + } + events.owner.store(self); + events.handler.store(self); return 0; } void APS5_VABI sceUsbdUnlockEventWaiters() { - NotImplemented_nid_no_patch(__func__); + ReleaseEvents(Events(), __func__); } void APS5_VABI sceUsbdUnlockEvents() { - NotImplemented_nid_no_patch(__func__); + UnlockEventsAndRelease(Events(), __func__); } std::int32_t APS5_VABI sceUsbdWaitForEvent(UsbdTimeval* timeout) { - NotImplemented_nid_no_patch(__func__); - return 0; + if (!ValidTimeout(timeout)) throw std::runtime_error(std::string(__func__) + ": a negative or oversized timeout has no known result"); + auto& events = Events(); + RequireEventLock(events, __func__); + return WaitForChange(events, timeout); } } diff --git a/core/libs/prx/libSceUserService/CMakeLists.txt b/core/libs/prx/libSceUserService/CMakeLists.txt index ee3f746eb4..53e0c73aac 100644 --- a/core/libs/prx/libSceUserService/CMakeLists.txt +++ b/core/libs/prx/libSceUserService/CMakeLists.txt @@ -27,6 +27,18 @@ if(BUILD_TESTING) endif() endif() +if(BUILD_TESTING) + add_test_executable(guest_user_service_user_kind_tests ${LIBS_INCLUDE_DIR}/tests/GuestUserServiceUserKind.cpp) + target_include_directories(guest_user_service_user_kind_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_user_service_user_kind_tests PRIVATE libSceUserService libSceSystemService) + add_test(NAME guest_user_service_user_kind COMMAND guest_user_service_user_kind_tests) + set_tests_properties(guest_user_service_user_kind PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_user_service_user_kind PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +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}) diff --git a/core/libs/prx/libSceUserService/Export.cpp b/core/libs/prx/libSceUserService/Export.cpp index fab4886e27..7601922a34 100644 --- a/core/libs/prx/libSceUserService/Export.cpp +++ b/core/libs/prx/libSceUserService/Export.cpp @@ -192,13 +192,12 @@ int APS5_VABI sceUserServiceGetEvent(SceUserServiceEvent* event) { } int APS5_VABI sceUserServiceGetGamePresets(int user_id, UserServiceGamePresets* presets) { - // 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. - const int index = UserIndex(user_id); - if (presets == nullptr || index < 0) { + if (presets == nullptr) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } + if (!IsUserLoggedIn(user_id)) { + return USER_SERVICE_ERROR_NOT_LOGGED_IN; + } presets->difficulty = 0; presets->priority = 0; presets->invert_vertical_view_for_1st_person_view = 0; @@ -354,14 +353,2588 @@ int APS5_VABI sceUserServiceGetTraditionalChineseInputType(int user_id, int32_t* } int APS5_VABI sceUserServiceGetNpAccountId(int user_id, uint64_t* account_id) { - if (!IsUserLoggedIn(user_id)) { - return USER_SERVICE_ERROR_UNKNOWN_USER; - } if (account_id == nullptr) { return USER_SERVICE_ERROR_INVALID_ARGUMENT; } + if (!IsUserLoggedIn(user_id)) { + return USER_SERVICE_ERROR_UNKNOWN_USER; + } *account_id = 0; return USER_SERVICE_OK; } +int APS5_VABI sceUserServiceSetGlsCameraContrast(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetParentalDvdRegion(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetChatStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSharePlayFramerateHost(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPasscode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetAppearOfflineSetting(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsAccessTokenUstream(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsBcTags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpCountryCode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetJapaneseInputType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsBroadcastersComment(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraTransparency(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIsHrtfEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcThursdayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcFridayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdTtl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVolumeForGenericUSB(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetControlCenterFunctionControlSettings(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetAudioFocus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetFriendFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraTransparency(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetUserStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetTraditionalChineseInputType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmd2MuteAssign(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIsCronosEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHoldAudioOutDevice(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHearingAssistWeakenAdjustment(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetDiscPlayerFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSharePlayFramerateHost(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetCaptureStatus2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetLightBarBaseBrightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraContrast(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcWednesdayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSharePlayResolutionHost(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetParentalBdAge(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetParentalGameAgeLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNotificationSettings2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPsnPasswordForDebug(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcAdditionalTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsLfpsSortOrder(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceLogin(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpLoginId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsBroadcastersCommentColor(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetParentalBrowser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPadSpeakerVolume(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsHintFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetIsFakePremium(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetAccessibilityKeyremapDataAston(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetKeyRepeatSpeed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetUserGroupName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetParentalDvdRegion(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraDepthLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetParentalContentControl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentLanguage(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVolumeForController(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsAccessTokenNiconicoLive(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsOverlayPosition(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetAppSortOrder(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraBgFilter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcSaturdayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetAccountRemarks(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGazeCalibData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsAccessTokenNiconicoLive(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmd2GazeTarget(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpAccountUpgradeFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsMessageFilterLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetImePointerMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetParentalDvd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSparkEqProfile(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcTuesdayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceRecognitionTutorialState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetFriendFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetEventCalendarType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcSaturdayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHearingAssistUsePreset(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetUserGroupName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetAutoLoginEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIPDLeft(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHintShowFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmd2CinematicAudioRotation(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetLoginFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcThursdayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcSundayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceRecognitionLastUsedOsk(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsIsFacebookEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetShareStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentEnableFeedback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceIsKratosUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetCreatedVersion(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSharePlayFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHearingAssistUsePreset(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPartyChatTranscriptionLanguageType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetA11yControllerADeviceUniqueIds(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetA11yControllerLedBrightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetKeyRepeatStartingTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsLiveQuality(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetProControllerAlreadyNoticed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsAccessTokenUstream(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetProControllerUniqueId3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHrtf(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsTtsFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpAge(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsFloatingMessage(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPlayTogetherFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmd2HideCameraImage(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsAccessTokenTwitch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetCaptureStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpSubAccount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPartyChatTranscriptionFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsIsMuteEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsBcTitle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHmd2Vibration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsLiveQuality2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetIsQuickSignup(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNotificationSettings_2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHomeDirectory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcSundayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetIPDLeft(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetKratosPrimaryUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceLogout(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpDateOfBirth(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcWednesdayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSparkEqParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetEventSortEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsLiveQuality3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNotificationSettings_1(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcMondayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIsFakePlus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcTuesdayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPartyMuteList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpNpId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdClockType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetProControllerBtAddress(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSparkEqIndex(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPsnPasswordForDebug(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetParentalGameWhiteList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCommunityId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceUpdateCronosCountryList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGazeCalibData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpAuthErrorFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHearingAssistUseAudiogramData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetAccessibilityKeyremapEnable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcFridayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsStreamingMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcFridayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetProControllerUniqueId1(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceAgentStatusBitFactory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetTeamShowAboutTeam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetEventSortEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsLfpsSortOrder(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHearingAssistMakeVoicesClearer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentEnableByButton(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSystemLoggerHashedAccountIdClockType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceTerminateForShellCore(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetParentalMorpheus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetCaptureStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetAgeVerified(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraBgFilter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsTtsVolume(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsAnonymousUserId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraReflection(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmuZoom(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsBcTitle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcFridayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetLoginFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHearingAssistAdjustmentLevelLeft(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetFaceRecognitionDeleteCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetTriggerEffectStrength(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsIsTwitterEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcSaturdayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHintShowFlag3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentSessionTimeout(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetParentalContentControl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraEffect(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcTuesdayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPartyMuteListA(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraEliminationLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsBroadcastersCommentColor(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetParentalBrowser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHearingAssistSeparateLR(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceIsUserStorageAccountBound(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHmd2Brightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraChromaKeyLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetProControllerUniqueId2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsBroadcastChannel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraEdgeLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraPosition(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetA11yControllerAName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcWednesdayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetMonoOutput(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPartyChatTranscriptionVoiceType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNotificationSettings(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetTopMenuTutorialFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetIPDRight(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcSaturdayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetImeLastUnit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpNpId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetTriggerEffectStrength(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsIsRecievedMessageHidden(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceAgentSessionTimeout(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsLanguageFilter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmd2Vibration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceIsLoggedIn(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcMondayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetAppSortOrder(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetMouseHandType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIsQuickSignup(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceIsKratosPrimaryUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceAgentEnableWup(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraDepthLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmuBrightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsHintFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceIsLoggedInWithoutLock(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetShareStatus3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsTtsVolume(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetMicLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetImeLastUnit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcThursdayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSaveDataAutoUpload(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetAccessibilityKeyremapDataAston(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVolumeForSidetone(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetProControllerUniqueId2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetFriendCustomListLastFocus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHmd2GazeTarget(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetKeyRepeatSpeed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetParentalDvd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetImePointerMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVolumeForController(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsBroadcastChannel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSystemLoggerHashedAccountIdParam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHintShowFlag2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHearingAssistPresetType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNotificationSettings_3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsAnonymousUserId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetA11yControllerBDeviceUniqueIds(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentEnableWup(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpDateOfBirth(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetKeyboardType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetParentalGame(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHearingAssistSeparateLR(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentTtsVolume(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcMondayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpSubAccount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetShareStatus2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCamCrop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceRecognitionLastUsedOsk(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSystemLoggerHashedAccountId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceRecognitionTutorialState(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsLiveQuality5(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHearingAssistUseAudiogramData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIPDRight(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcPlayTimeLastUpdated(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcTuesdayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNotificationBehavior(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPadProfileDataHuntList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHearingAssistAudiogramData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHintShowFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetTopMenuLimitItem(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcSundayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetParentalBdAge(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSaveDataSort(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHrtf(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetAppearOfflineSetting(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceAgentLanguage(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetIsRemotePlayAllowed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsStreamingMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetChatStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceAgentStatusBit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpCountryCode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetParentalGameWhiteList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHmd2ScreenSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetTriggerEffectEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetA11yControllerInitialSetupProgress(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVolumeForGenericUSB(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetControlCenterFunctionControlSettings(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsInitSpectating(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsIsMuteEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpLanguageCode2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetFriendCustomListLastFocus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPartyMuteList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetAccessibilityKeyremapData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcFridayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcMondayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetControlCenterStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSparkEqProfile(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsTtsPitch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsBroadcastService(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetAutoLoginEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcSaturdayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNotificationSettings_1(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHmd2MuteAssign(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPasscode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetMousePointerSpeed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsBroadcastUiLayout(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSharePlayFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetIsFakePlus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetAccountRemarks(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetAccessibilityZoom(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSystemLoggerHashedAccountIdTtl(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSaveDataSort(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPlayTogetherFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHearingAssistAdjustmentLevelRight(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsTtsSpeed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpEnv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetDiscPlayerFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHmd2PlayAreaShape(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsIsCameraHidden(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetUserStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetEventFilterTeamEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPsCloudFeedbackTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetUserGroupIndex(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPartyMuteListA(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpLanguageCode2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetShareStatus2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetForegroundUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsAccessTokenTwitch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpOfflineAccountAdult(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGamePresets(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNotificationSettings(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetImeInputType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVolumeForMorpheusSidetone(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsLiveQuality4(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHearingAssistPresetType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHearingAssistWeakenAdjustment(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcWednesdayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceIsGuestUser(std::int32_t userId) { + return (static_cast(userId) & 0xe0000000u) == 0x20000000u ? 1 : 0; +} + +int APS5_VABI sceUserServiceGetPadProfileDataHuntList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetAccessibilityKeyremapData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetCaptureStatus2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentTtsSpeed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcWednesdayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetAgeVerified(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraEdgeLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPartyChatTranscriptionLanguageType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceAgentTtsVolume(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcTuesdayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetUserWithHearingAssist(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpOnlineId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHintShowFlag3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNotificationSettings_3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsIsFacebookEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpEnv(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraBrightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceDestroyUser(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetTopMenuNotificationFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetCreatedVersion(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraChromaKeyLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpOfflineAccountId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetShareStatus3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetControlCenterStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetMicLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetTeamShowAboutTeam(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetEventVsh(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetA11yControllerBName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpAge(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPlayingBigAppTitleId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHintShowFlag2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetCaptureStatus3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSparkEqIndex(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcMondayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetEventSortTitle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpOnlineId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetA11yControllerBName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSaveDataAutoSync(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmd2Brightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHoldAudioOutDevice(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVibrationStrength(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceUnregisterEventCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetTopMenuNotificationFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcSundayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHearingAssistAudiogramData(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSaveDataAutoSync(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcFridayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsTtsSpeed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPartyChatTranscriptionFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcSaturdayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPartySettingFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetImeInputSettings(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsIsRecDisabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetA11yControllerAName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetEventFilterTeamEvent(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetA11yControllerBDeviceUniqueIds(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraEliminationLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVibrationStrength(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpAccountUpgradeFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetDeletedUserInfo(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSecureHomeDirectory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHearingAssistAdjustmentLevelRight(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPlatformPrivacyWs1Internal(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsIsRecDisabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraBrightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpLanguageCode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcThursdayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGazeActivate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPlatformPrivacyWs1(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVolumeForSidetone(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSaveDataTutorialFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcPlayTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsBroadcastService(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIsFakePremium(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsLiveQuality5(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSaveDataAutoUpload(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetSaveDataTutorialFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsLiveQuality4(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpLanguageCode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcThursdayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetFaceRecognitionDeleteCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentEnable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetEventCalendarType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetProControllerAlreadyNoticed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsTtsFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsBroadcastersComment(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcTzOffset(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcPlayTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentStatusBit(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNotificationSettings2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetEventUiFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsBroadcastUiLayout(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVibrationEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcAdditionalTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceIsSharePlayClientUser(std::int32_t userId) { + return (static_cast(userId) & 0xf0000000u) == 0x30000000u ? 1 : 0; +} + +int APS5_VABI sceUserServiceGetMonoOutput(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpLoginId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetShareStatus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsIsRecievedMessageHidden(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsLiveQuality2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmd2GazeActivate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCameraPosition(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsMessageFilterLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetUserGroupNameList(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceAgentEnableFeedback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGazeActivate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsIsTwitterEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetTopMenuLimitItem(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNpAuthErrorFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsIsCameraHidden(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetMousePointerSpeed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetFaceRecognitionRegisterCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmd2PlayAreaShape(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetUserName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetA11yControllerADeviceUniqueIds(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsFloatingMessage(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceInitializeForShellCore(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsBcTags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcMode(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetImeInputType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetProControllerUniqueId3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHmd2GazeActivate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetCaptureStatus3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraReflection(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetNotificationSettings_2(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsLiveQuality(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetFaceRecognitionRegisterCount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetKeyRepeatStartingTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIsRemotePlayAllowed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcMondayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsLiveQuality3(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPsCloudFeedbackTime(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceAgentTtsSpeed(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVoiceAgentEnable(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcWednesdayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetTopMenuTutorialFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIsPersonalizedHrtfEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPadSpeakerVolume(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetAccessibilityKeyremapEnableAston(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetHmd2ScreenSize(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsInitSpectating(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetLastLoginOrder(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsTtsPitch(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetMouseHandType(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetEventUiFlag(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetImeInputSettings(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetProControllerUniqueId1(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcPlayTimeLastUpdated(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHmuBrightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceRegisterEventCallback(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetParentalMorpheus(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcTuesdayDuration(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsOverlayPosition(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetEventSortTitle(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHmuZoom(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetVolumeForMorpheusSidetone(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsLanguageFilter(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVibrationEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetSharePlayResolutionHost(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcSundayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNpOfflineAccountAdult(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetTriggerEffectEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHearingAssistAdjustmentLevelLeft(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetLightBarBaseBrightness(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetVoiceAgentStatusBitFactory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetHearingAssistMakeVoicesClearer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCommunityId(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetGlsCamCrop(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcTzOffset(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetParentalGame(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPbtcThursdayHoursStart(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetIsHrtfEnabled(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetParentalGameAgeLevel(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetNotificationBehavior(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetUserGroupNum(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceSetPartySettingFlags(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetGlsCameraEffect(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetPbtcSundayHoursEnd(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceUserServiceGetIsSpartacusEnabled(std::int32_t, bool* enabled) { + if (enabled == nullptr) return USER_SERVICE_ERROR_INVALID_ARGUMENT; + *enabled = true; + return USER_SERVICE_OK; +} + } diff --git a/core/libs/prx/libSceUserService/UserService.hpp b/core/libs/prx/libSceUserService/UserService.hpp index 3169ed503c..4d0bf9de3c 100644 --- a/core/libs/prx/libSceUserService/UserService.hpp +++ b/core/libs/prx/libSceUserService/UserService.hpp @@ -61,6 +61,521 @@ 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); +int APS5_VABI sceUserServiceSetGlsCameraContrast(void); +int APS5_VABI sceUserServiceSetParentalDvdRegion(void); +int APS5_VABI sceUserServiceSetChatStatus(void); +int APS5_VABI sceUserServiceSetSharePlayFramerateHost(void); +int APS5_VABI sceUserServiceSetPasscode(void); +int APS5_VABI sceUserServiceGetAppearOfflineSetting(void); +int APS5_VABI sceUserServiceGetGlsAccessTokenUstream(void); +int APS5_VABI sceUserServiceSetGlsBcTags(void); +int APS5_VABI sceUserServiceSetNpCountryCode(void); +int APS5_VABI sceUserServiceSetJapaneseInputType(void); +int APS5_VABI sceUserServiceSetGlsBroadcastersComment(void); +int APS5_VABI sceUserServiceSetGlsCameraTransparency(void); +int APS5_VABI sceUserServiceGetIsHrtfEnabled(void); +int APS5_VABI sceUserServiceGetCurrentUserGroupIndex(void); +int APS5_VABI sceUserServiceGetPbtcThursdayHoursEnd(void); +int APS5_VABI sceUserServiceGetPbtcFridayHoursStart(void); +int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdTtl(void); +int APS5_VABI sceUserServiceGetRegisteredHomeUserIdList(void); +int APS5_VABI sceUserServiceSetVolumeForGenericUSB(void); +int APS5_VABI sceUserServiceSetControlCenterFunctionControlSettings(void); +int APS5_VABI sceUserServiceGetAudioFocus(void); +int APS5_VABI sceUserServiceSetFriendFlag(void); +int APS5_VABI sceUserServiceGetGlsCameraTransparency(void); +int APS5_VABI sceUserServiceGetUserStatus(void); +int APS5_VABI sceUserServiceSetTraditionalChineseInputType(void); +int APS5_VABI sceUserServiceGetHmd2MuteAssign(void); +int APS5_VABI sceUserServiceGetIsCronosEnabled(void); +int APS5_VABI sceUserServiceSetHoldAudioOutDevice(void); +int APS5_VABI sceUserServiceSetHearingAssistWeakenAdjustment(void); +int APS5_VABI sceUserServiceSetDiscPlayerFlag(void); +int APS5_VABI sceUserServiceGetSharePlayFramerateHost(void); +int APS5_VABI sceUserServiceGetCaptureStatus2(void); +int APS5_VABI sceUserServiceSetLightBarBaseBrightness(void); +int APS5_VABI sceUserServiceGetGlsCameraContrast(void); +int APS5_VABI sceUserServiceGetPbtcWednesdayDuration(void); +int APS5_VABI sceUserServiceGetSharePlayResolutionHost(void); +int APS5_VABI sceUserServiceSetParentalBdAge(void); +int APS5_VABI sceUserServiceSetParentalGameAgeLevel(void); +int APS5_VABI sceUserServiceSetNotificationSettings2(void); +int APS5_VABI sceUserServiceGetPsnPasswordForDebug(void); +int APS5_VABI sceUserServiceGetPbtcAdditionalTime(void); +int APS5_VABI sceUserServiceSetGlsLfpsSortOrder(void); +int APS5_VABI sceUserServiceLogin(void); +int APS5_VABI sceUserServiceGetNpLoginId(void); +int APS5_VABI sceUserServiceSetGlsBroadcastersCommentColor(void); +int APS5_VABI sceUserServiceSetParentalBrowser(void); +int APS5_VABI sceUserServiceGetPadSpeakerVolume(void); +int APS5_VABI sceUserServiceGetGlsHintFlag(void); +int APS5_VABI sceUserServiceSetIsFakePremium(void); +int APS5_VABI sceUserServiceGetAccessibilityKeyremapDataAston(void); +int APS5_VABI sceUserServiceSetKeyRepeatSpeed(void); +int APS5_VABI sceUserServiceSetUserGroupName(void); +int APS5_VABI sceUserServiceGetParentalDvdRegion(void); +int APS5_VABI sceUserServiceSetGlsCameraDepthLevel(void); +int APS5_VABI sceUserServiceGetParentalContentControl(void); +int APS5_VABI sceUserServiceGetVoiceAgentLanguage(void); +int APS5_VABI sceUserServiceSetVolumeForController(void); +int APS5_VABI sceUserServiceGetGlsAccessTokenNiconicoLive(void); +int APS5_VABI sceUserServiceSetGlsOverlayPosition(void); +int APS5_VABI sceUserServiceGetAppSortOrder(void); +int APS5_VABI sceUserServiceGetGlsCameraBgFilter(void); +int APS5_VABI sceUserServiceSetPbtcSaturdayDuration(void); +int APS5_VABI sceUserServiceSetAccountRemarks(void); +int APS5_VABI sceUserServiceSetGazeCalibData(void); +int APS5_VABI sceUserServiceSetGlsAccessTokenNiconicoLive(void); +int APS5_VABI sceUserServiceGetHmd2GazeTarget(void); +int APS5_VABI sceUserServiceSetNpAccountUpgradeFlag(void); +int APS5_VABI sceUserServiceGetGlsMessageFilterLevel(void); +int APS5_VABI sceUserServiceSetImePointerMode(void); +int APS5_VABI sceUserServiceGetParentalDvd(void); +int APS5_VABI sceUserServiceSetSparkEqProfile(void); +int APS5_VABI sceUserServiceGetPbtcTuesdayHoursStart(void); +int APS5_VABI sceUserServiceGetVoiceRecognitionTutorialState(void); +int APS5_VABI sceUserServiceGetFriendFlag(void); +int APS5_VABI sceUserServiceGetEventCalendarType(void); +int APS5_VABI sceUserServiceSetPbtcSaturdayHoursEnd(void); +int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdParam(void); +int APS5_VABI sceUserServiceSetHearingAssistUsePreset(void); +int APS5_VABI sceUserServiceGetUserGroupName(void); +int APS5_VABI sceUserServiceSetAutoLoginEnabled(void); +int APS5_VABI sceUserServiceGetIPDLeft(void); +int APS5_VABI sceUserServiceSetHintShowFlag(void); +int APS5_VABI sceUserServiceGetHmd2CinematicAudioRotation(void); +int APS5_VABI sceUserServiceSetLoginFlag(void); +int APS5_VABI sceUserServiceGetPbtcThursdayDuration(void); +int APS5_VABI sceUserServiceGetPbtcSundayHoursStart(void); +int APS5_VABI sceUserServiceSetVoiceRecognitionLastUsedOsk(void); +int APS5_VABI sceUserServiceGetGlsIsFacebookEnabled(void); +int APS5_VABI sceUserServiceSetShareStatus(void); +int APS5_VABI sceUserServiceGetVoiceAgentEnableFeedback(void); +int APS5_VABI sceUserServiceIsKratosUser(void); +int APS5_VABI sceUserServiceSetCreatedVersion(void); +int APS5_VABI sceUserServiceGetSharePlayFlags(void); +int APS5_VABI sceUserServiceGetHearingAssistUsePreset(void); +int APS5_VABI sceUserServiceSetPartyChatTranscriptionLanguageType(void); +int APS5_VABI sceUserServiceSetA11yControllerADeviceUniqueIds(void); +int APS5_VABI sceUserServiceGetA11yControllerLedBrightness(void); +int APS5_VABI sceUserServiceGetKeyRepeatStartingTime(void); +int APS5_VABI sceUserServiceSetGlsLiveQuality(void); +int APS5_VABI sceUserServiceGetProControllerAlreadyNoticed(void); +int APS5_VABI sceUserServiceSetGlsAccessTokenUstream(void); +int APS5_VABI sceUserServiceSetProControllerUniqueId3(void); +int APS5_VABI sceUserServiceSetHrtf(void); +int APS5_VABI sceUserServiceSetGlsTtsFlags(void); +int APS5_VABI sceUserServiceGetNpAge(void); +int APS5_VABI sceUserServiceSetGlsFloatingMessage(void); +int APS5_VABI sceUserServiceGetPlayTogetherFlags(void); +int APS5_VABI sceUserServiceGetHmd2HideCameraImage(void); +int APS5_VABI sceUserServiceGetGlsAccessTokenTwitch(void); +int APS5_VABI sceUserServiceSetCaptureStatus(void); +int APS5_VABI sceUserServiceSetNpSubAccount(void); +int APS5_VABI sceUserServiceSetPartyChatTranscriptionFlag(void); +int APS5_VABI sceUserServiceSetGlsIsMuteEnabled(void); +int APS5_VABI sceUserServiceSetGlsBcTitle(void); +int APS5_VABI sceUserServiceSetHmd2Vibration(void); +int APS5_VABI sceUserServiceGetGlsLiveQuality2(void); +int APS5_VABI sceUserServiceSetIsQuickSignup(void); +int APS5_VABI sceUserServiceSetNotificationSettings_2(void); +int APS5_VABI sceUserServiceGetHomeDirectory(void); +int APS5_VABI sceUserServiceSetPbtcSundayDuration(void); +int APS5_VABI sceUserServiceSetIPDLeft(void); +int APS5_VABI sceUserServiceGetKratosPrimaryUser(void); +int APS5_VABI sceUserServiceLogout(void); +int APS5_VABI sceUserServiceSetNpDateOfBirth(void); +int APS5_VABI sceUserServiceSetPbtcWednesdayHoursStart(void); +int APS5_VABI sceUserServiceGetSparkEqParam(void); +int APS5_VABI sceUserServiceSetEventSortEvent(void); +int APS5_VABI sceUserServiceSetGlsLiveQuality3(void); +int APS5_VABI sceUserServiceSetNotificationSettings_1(void); +int APS5_VABI sceUserServiceGetPbtcMondayHoursEnd(void); +int APS5_VABI sceUserServiceGetIsFakePlus(void); +int APS5_VABI sceUserServiceGetPbtcTuesdayHoursEnd(void); +int APS5_VABI sceUserServiceGetPartyMuteList(void); +int APS5_VABI sceUserServiceGetNpNpId(void); +int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountIdClockType(void); +int APS5_VABI sceUserServiceGetProControllerBtAddress(void); +int APS5_VABI sceUserServiceGetSparkEqIndex(void); +int APS5_VABI sceUserServiceSetPsnPasswordForDebug(void); +int APS5_VABI sceUserServiceSetParentalGameWhiteList(void); +int APS5_VABI sceUserServiceGetGlsCommunityId(void); +int APS5_VABI sceUserServiceUpdateCronosCountryList(void); +int APS5_VABI sceUserServiceGetGazeCalibData(void); +int APS5_VABI sceUserServiceSetNpAuthErrorFlag(void); +int APS5_VABI sceUserServiceGetHearingAssistUseAudiogramData(void); +int APS5_VABI sceUserServiceSetAccessibilityKeyremapEnable(void); +int APS5_VABI sceUserServiceSetPbtcFlag(void); +int APS5_VABI sceUserServiceGetPbtcFridayDuration(void); +int APS5_VABI sceUserServiceSetGlsStreamingMode(void); +int APS5_VABI sceUserServiceSetPbtcFridayHoursEnd(void); +int APS5_VABI sceUserServiceGetProControllerUniqueId1(void); +int APS5_VABI sceUserServiceSetVoiceAgentStatusBitFactory(void); +int APS5_VABI sceUserServiceSetTeamShowAboutTeam(void); +int APS5_VABI sceUserServiceGetEventSortEvent(void); +int APS5_VABI sceUserServiceGetGlsLfpsSortOrder(void); +int APS5_VABI sceUserServiceGetHearingAssistMakeVoicesClearer(void); +int APS5_VABI sceUserServiceGetVoiceAgentEnableByButton(void); +int APS5_VABI sceUserServiceGetSystemLoggerHashedAccountIdClockType(void); +int APS5_VABI sceUserServiceTerminateForShellCore(void); +int APS5_VABI sceUserServiceSetParentalMorpheus(void); +int APS5_VABI sceUserServiceSetSystemLoggerHashedAccountId(void); +int APS5_VABI sceUserServiceGetCaptureStatus(void); +int APS5_VABI sceUserServiceSetAgeVerified(void); +int APS5_VABI sceUserServiceSetGlsCameraBgFilter(void); +int APS5_VABI sceUserServiceSetGlsTtsVolume(void); +int APS5_VABI sceUserServiceSetGlsCameraSize(void); +int APS5_VABI sceUserServiceSetGlsAnonymousUserId(void); +int APS5_VABI sceUserServiceSetGlsCameraReflection(void); +int APS5_VABI sceUserServiceGetHmuZoom(void); +int APS5_VABI sceUserServiceGetGlsBcTitle(void); +int APS5_VABI sceUserServiceGetPbtcFridayHoursEnd(void); +int APS5_VABI sceUserServiceGetLoginFlag(void); +int APS5_VABI sceUserServiceGetHearingAssistAdjustmentLevelLeft(void); +int APS5_VABI sceUserServiceSetFaceRecognitionDeleteCount(void); +int APS5_VABI sceUserServiceGetTriggerEffectStrength(void); +int APS5_VABI sceUserServiceSetGlsIsTwitterEnabled(void); +int APS5_VABI sceUserServiceGetPbtcSaturdayHoursStart(void); +int APS5_VABI sceUserServiceSetHintShowFlag3(void); +int APS5_VABI sceUserServiceGetVoiceAgentSessionTimeout(void); +int APS5_VABI sceUserServiceSetParentalContentControl(void); +int APS5_VABI sceUserServiceSetGlsCameraEffect(void); +int APS5_VABI sceUserServiceSetPbtcTuesdayHoursEnd(void); +int APS5_VABI sceUserServiceGetPartyMuteListA(void); +int APS5_VABI sceUserServiceGetGlsCameraEliminationLevel(void); +int APS5_VABI sceUserServiceGetGlsBroadcastersCommentColor(void); +int APS5_VABI sceUserServiceGetParentalBrowser(void); +int APS5_VABI sceUserServiceSetHearingAssistSeparateLR(void); +int APS5_VABI sceUserServiceIsUserStorageAccountBound(void); +int APS5_VABI sceUserServiceSetHmd2Brightness(void); +int APS5_VABI sceUserServiceSetGlsCameraChromaKeyLevel(void); +int APS5_VABI sceUserServiceGetProControllerUniqueId2(void); +int APS5_VABI sceUserServiceSetGlsBroadcastChannel(void); +int APS5_VABI sceUserServiceGetGlsCameraEdgeLevel(void); +int APS5_VABI sceUserServiceGetGlsCameraPosition(void); +int APS5_VABI sceUserServiceGetA11yControllerAName(void); +int APS5_VABI sceUserServiceSetPbtcWednesdayHoursEnd(void); +int APS5_VABI sceUserServiceSetMonoOutput(void); +int APS5_VABI sceUserServiceSetPartyChatTranscriptionVoiceType(void); +int APS5_VABI sceUserServiceGetNotificationSettings(void); +int APS5_VABI sceUserServiceGetTopMenuTutorialFlag(void); +int APS5_VABI sceUserServiceSetIPDRight(void); +int APS5_VABI sceUserServiceGetPbtcSaturdayHoursEnd(void); +int APS5_VABI sceUserServiceGetImeLastUnit(void); +int APS5_VABI sceUserServiceSetNpNpId(void); +int APS5_VABI sceUserServiceSetTriggerEffectStrength(void); +int APS5_VABI sceUserServiceGetGlsIsRecievedMessageHidden(void); +int APS5_VABI sceUserServiceSetVoiceAgentSessionTimeout(void); +int APS5_VABI sceUserServiceSetGlsLanguageFilter(void); +int APS5_VABI sceUserServiceGetHmd2Vibration(void); +int APS5_VABI sceUserServiceIsLoggedIn(void); +int APS5_VABI sceUserServiceGetPbtcMondayHoursStart(void); +int APS5_VABI sceUserServiceSetAppSortOrder(void); +int APS5_VABI sceUserServiceSetMouseHandType(void); +int APS5_VABI sceUserServiceGetIsQuickSignup(void); +int APS5_VABI sceUserServiceIsKratosPrimaryUser(void); +int APS5_VABI sceUserServiceSetVoiceAgentEnableWup(void); +int APS5_VABI sceUserServiceGetGlsCameraDepthLevel(void); +int APS5_VABI sceUserServiceGetHmuBrightness(void); +int APS5_VABI sceUserServiceSetGlsHintFlag(void); +int APS5_VABI sceUserServiceIsLoggedInWithoutLock(void); +int APS5_VABI sceUserServiceSetShareStatus3(void); +int APS5_VABI sceUserServiceGetGlsTtsVolume(void); +int APS5_VABI sceUserServiceGetMicLevel(void); +int APS5_VABI sceUserServiceSetImeLastUnit(void); +int APS5_VABI sceUserServiceSetPbtcMode(void); +int APS5_VABI sceUserServiceSetPbtcThursdayHoursEnd(void); +int APS5_VABI sceUserServiceGetSaveDataAutoUpload(void); +int APS5_VABI sceUserServiceSetAccessibilityKeyremapDataAston(void); +int APS5_VABI sceUserServiceGetVolumeForSidetone(void); +int APS5_VABI sceUserServiceSetProControllerUniqueId2(void); +int APS5_VABI sceUserServiceGetFriendCustomListLastFocus(void); +int APS5_VABI sceUserServiceSetHmd2GazeTarget(void); +int APS5_VABI sceUserServiceGetKeyRepeatSpeed(void); +int APS5_VABI sceUserServiceSetParentalDvd(void); +int APS5_VABI sceUserServiceGetImePointerMode(void); +int APS5_VABI sceUserServiceGetVolumeForController(void); +int APS5_VABI sceUserServiceGetGlsBroadcastChannel(void); +int APS5_VABI sceUserServiceGetSystemLoggerHashedAccountIdParam(void); +int APS5_VABI sceUserServiceGetHintShowFlag2(void); +int APS5_VABI sceUserServiceSetHearingAssistPresetType(void); +int APS5_VABI sceUserServiceGetNotificationSettings_3(void); +int APS5_VABI sceUserServiceGetGlsAnonymousUserId(void); +int APS5_VABI sceUserServiceSetA11yControllerBDeviceUniqueIds(void); +int APS5_VABI sceUserServiceGetVoiceAgentEnableWup(void); +int APS5_VABI sceUserServiceGetNpDateOfBirth(void); +int APS5_VABI sceUserServiceSetKeyboardType(void); +int APS5_VABI sceUserServiceSetParentalGame(void); +int APS5_VABI sceUserServiceGetHearingAssistSeparateLR(void); +int APS5_VABI sceUserServiceGetVoiceAgentTtsVolume(void); +int APS5_VABI sceUserServiceGetPbtcMondayDuration(void); +int APS5_VABI sceUserServiceGetNpSubAccount(void); +int APS5_VABI sceUserServiceGetShareStatus2(void); +int APS5_VABI sceUserServiceGetGlsCamCrop(void); +int APS5_VABI sceUserServiceGetVoiceRecognitionLastUsedOsk(void); +int APS5_VABI sceUserServiceGetSystemLoggerHashedAccountId(void); +int APS5_VABI sceUserServiceSetVoiceRecognitionTutorialState(void); +int APS5_VABI sceUserServiceGetGlsLiveQuality5(void); +int APS5_VABI sceUserServiceSetHearingAssistUseAudiogramData(void); +int APS5_VABI sceUserServiceGetPbtcFlag(void); +int APS5_VABI sceUserServiceGetIPDRight(void); +int APS5_VABI sceUserServiceSetPbtcPlayTimeLastUpdated(void); +int APS5_VABI sceUserServiceSetPbtcTuesdayHoursStart(void); +int APS5_VABI sceUserServiceGetNotificationBehavior(void); +int APS5_VABI sceUserServiceSetPadProfileDataHuntList(void); +int APS5_VABI sceUserServiceGetHearingAssistAudiogramData(void); +int APS5_VABI sceUserServiceGetHintShowFlag(void); +int APS5_VABI sceUserServiceSetTopMenuLimitItem(void); +int APS5_VABI sceUserServiceSetPbtcSundayHoursEnd(void); +int APS5_VABI sceUserServiceGetParentalBdAge(void); +int APS5_VABI sceUserServiceGetSaveDataSort(void); +int APS5_VABI sceUserServiceGetHrtf(void); +int APS5_VABI sceUserServiceSetAppearOfflineSetting(void); +int APS5_VABI sceUserServiceSetVoiceAgentLanguage(void); +int APS5_VABI sceUserServiceSetIsRemotePlayAllowed(void); +int APS5_VABI sceUserServiceGetGlsStreamingMode(void); +int APS5_VABI sceUserServiceGetChatStatus(void); +int APS5_VABI sceUserServiceSetVoiceAgentStatusBit(void); +int APS5_VABI sceUserServiceGetNpCountryCode(void); +int APS5_VABI sceUserServiceGetParentalGameWhiteList(void); +int APS5_VABI sceUserServiceSetHmd2ScreenSize(void); +int APS5_VABI sceUserServiceGetTriggerEffectEnabled(void); +int APS5_VABI sceUserServiceGetA11yControllerInitialSetupProgress(void); +int APS5_VABI sceUserServiceGetVolumeForGenericUSB(void); +int APS5_VABI sceUserServiceGetControlCenterFunctionControlSettings(void); +int APS5_VABI sceUserServiceSetGlsInitSpectating(void); +int APS5_VABI sceUserServiceGetGlsIsMuteEnabled(void); +int APS5_VABI sceUserServiceGetNpLanguageCode2(void); +int APS5_VABI sceUserServiceSetFriendCustomListLastFocus(void); +int APS5_VABI sceUserServiceSetPartyMuteList(void); +int APS5_VABI sceUserServiceSetAccessibilityKeyremapData(void); +int APS5_VABI sceUserServiceSetPbtcFridayHoursStart(void); +int APS5_VABI sceUserServiceSetPbtcMondayHoursEnd(void); +int APS5_VABI sceUserServiceGetControlCenterStatus(void); +int APS5_VABI sceUserServiceGetSparkEqProfile(void); +int APS5_VABI sceUserServiceGetGlsTtsPitch(void); +int APS5_VABI sceUserServiceSetGlsBroadcastService(void); +int APS5_VABI sceUserServiceGetAutoLoginEnabled(void); +int APS5_VABI sceUserServiceSetPbtcSaturdayHoursStart(void); +int APS5_VABI sceUserServiceGetNotificationSettings_1(void); +int APS5_VABI sceUserServiceSetHmd2MuteAssign(void); +int APS5_VABI sceUserServiceGetPasscode(void); +int APS5_VABI sceUserServiceSetMousePointerSpeed(void); +int APS5_VABI sceUserServiceGetGlsBroadcastUiLayout(void); +int APS5_VABI sceUserServiceSetSharePlayFlags(void); +int APS5_VABI sceUserServiceSetIsFakePlus(void); +int APS5_VABI sceUserServiceGetAccountRemarks(void); +int APS5_VABI sceUserServiceSetAccessibilityZoom(void); +int APS5_VABI sceUserServiceGetSystemLoggerHashedAccountIdTtl(void); +int APS5_VABI sceUserServiceSetSaveDataSort(void); +int APS5_VABI sceUserServiceSetPlayTogetherFlags(void); +int APS5_VABI sceUserServiceSetHearingAssistAdjustmentLevelRight(void); +int APS5_VABI sceUserServiceGetGlsTtsSpeed(void); +int APS5_VABI sceUserServiceGetNpEnv(void); +int APS5_VABI sceUserServiceGetGlsCameraSize(void); +int APS5_VABI sceUserServiceGetDiscPlayerFlag(void); +int APS5_VABI sceUserServiceSetHmd2PlayAreaShape(void); +int APS5_VABI sceUserServiceSetGlsIsCameraHidden(void); +int APS5_VABI sceUserServiceSetUserStatus(void); +int APS5_VABI sceUserServiceGetEventFilterTeamEvent(void); +int APS5_VABI sceUserServiceSetPsCloudFeedbackTime(void); +int APS5_VABI sceUserServiceSetUserGroupIndex(void); +int APS5_VABI sceUserServiceSetPartyMuteListA(void); +int APS5_VABI sceUserServiceSetNpLanguageCode2(void); +int APS5_VABI sceUserServiceSetShareStatus2(void); +int APS5_VABI sceUserServiceSetForegroundUser(void); +int APS5_VABI sceUserServiceSetGlsAccessTokenTwitch(void); +int APS5_VABI sceUserServiceGetNpOfflineAccountAdult(void); +int APS5_VABI sceUserServiceSetGamePresets(void); +int APS5_VABI sceUserServiceSetNotificationSettings(void); +int APS5_VABI sceUserServiceGetImeInputType(void); +int APS5_VABI sceUserServiceGetVolumeForMorpheusSidetone(void); +int APS5_VABI sceUserServiceSetGlsLiveQuality4(void); +int APS5_VABI sceUserServiceGetHearingAssistPresetType(void); +int APS5_VABI sceUserServiceGetHearingAssistWeakenAdjustment(void); +int APS5_VABI sceUserServiceSetPbtcWednesdayDuration(void); +int APS5_VABI sceUserServiceIsGuestUser(std::int32_t userId); +int APS5_VABI sceUserServiceGetPadProfileDataHuntList(void); +int APS5_VABI sceUserServiceGetAccessibilityKeyremapData(void); +int APS5_VABI sceUserServiceSetCaptureStatus2(void); +int APS5_VABI sceUserServiceGetVoiceAgentTtsSpeed(void); +int APS5_VABI sceUserServiceGetPbtcWednesdayHoursEnd(void); +int APS5_VABI sceUserServiceGetAgeVerified(void); +int APS5_VABI sceUserServiceSetGlsCameraEdgeLevel(void); +int APS5_VABI sceUserServiceGetPartyChatTranscriptionLanguageType(void); +int APS5_VABI sceUserServiceSetVoiceAgentTtsVolume(void); +int APS5_VABI sceUserServiceGetPbtcTuesdayDuration(void); +int APS5_VABI sceUserServiceGetUserWithHearingAssist(void); +int APS5_VABI sceUserServiceGetNpOnlineId(void); +int APS5_VABI sceUserServiceGetHintShowFlag3(void); +int APS5_VABI sceUserServiceSetNotificationSettings_3(void); +int APS5_VABI sceUserServiceSetGlsIsFacebookEnabled(void); +int APS5_VABI sceUserServiceSetNpEnv(void); +int APS5_VABI sceUserServiceSetGlsCameraBrightness(void); +int APS5_VABI sceUserServiceDestroyUser(void); +int APS5_VABI sceUserServiceSetTopMenuNotificationFlag(void); +int APS5_VABI sceUserServiceGetCreatedVersion(void); +int APS5_VABI sceUserServiceGetGlsCameraChromaKeyLevel(void); +int APS5_VABI sceUserServiceGetNpOfflineAccountId(void); +int APS5_VABI sceUserServiceGetShareStatus3(void); +int APS5_VABI sceUserServiceSetControlCenterStatus(void); +int APS5_VABI sceUserServiceSetMicLevel(void); +int APS5_VABI sceUserServiceGetTeamShowAboutTeam(void); +int APS5_VABI sceUserServiceGetEventVsh(void); +int APS5_VABI sceUserServiceGetA11yControllerBName(void); +int APS5_VABI sceUserServiceSetNpAge(void); +int APS5_VABI sceUserServiceGetPlayingBigAppTitleId(void); +int APS5_VABI sceUserServiceSetHintShowFlag2(void); +int APS5_VABI sceUserServiceSetCaptureStatus3(void); +int APS5_VABI sceUserServiceSetSparkEqIndex(void); +int APS5_VABI sceUserServiceSetPbtcMondayDuration(void); +int APS5_VABI sceUserServiceSetEventSortTitle(void); +int APS5_VABI sceUserServiceSetNpOnlineId(void); +int APS5_VABI sceUserServiceSetA11yControllerBName(void); +int APS5_VABI sceUserServiceGetSaveDataAutoSync(void); +int APS5_VABI sceUserServiceGetDefaultNewUserGroupName(void); +int APS5_VABI sceUserServiceGetHmd2Brightness(void); +int APS5_VABI sceUserServiceGetHoldAudioOutDevice(void); +int APS5_VABI sceUserServiceSetVibrationStrength(void); +int APS5_VABI sceUserServiceUnregisterEventCallback(void); +int APS5_VABI sceUserServiceGetTopMenuNotificationFlag(void); +int APS5_VABI sceUserServiceGetPbtcSundayDuration(void); +int APS5_VABI sceUserServiceSetHearingAssistAudiogramData(void); +int APS5_VABI sceUserServiceSetSaveDataAutoSync(void); +int APS5_VABI sceUserServiceSetPbtcFridayDuration(void); +int APS5_VABI sceUserServiceSetGlsTtsSpeed(void); +int APS5_VABI sceUserServiceGetPartyChatTranscriptionFlag(void); +int APS5_VABI sceUserServiceGetPbtcSaturdayDuration(void); +int APS5_VABI sceUserServiceGetPartySettingFlags(void); +int APS5_VABI sceUserServiceGetImeInputSettings(void); +int APS5_VABI sceUserServiceSetGlsIsRecDisabled(void); +int APS5_VABI sceUserServiceSetA11yControllerAName(void); +int APS5_VABI sceUserServiceSetEventFilterTeamEvent(void); +int APS5_VABI sceUserServiceGetA11yControllerBDeviceUniqueIds(void); +int APS5_VABI sceUserServiceSetGlsCameraEliminationLevel(void); +int APS5_VABI sceUserServiceGetVibrationStrength(void); +int APS5_VABI sceUserServiceGetNpAccountUpgradeFlag(void); +int APS5_VABI sceUserServiceGetDeletedUserInfo(void); +int APS5_VABI sceUserServiceGetSecureHomeDirectory(void); +int APS5_VABI sceUserServiceGetHearingAssistAdjustmentLevelRight(void); +int APS5_VABI sceUserServiceGetPlatformPrivacyWs1Internal(void); +int APS5_VABI sceUserServiceGetGlsIsRecDisabled(void); +int APS5_VABI sceUserServiceGetGlsCameraBrightness(void); +int APS5_VABI sceUserServiceSetNpLanguageCode(void); +int APS5_VABI sceUserServiceGetPbtcThursdayHoursStart(void); +int APS5_VABI sceUserServiceSetGazeActivate(void); +int APS5_VABI sceUserServiceSetPlatformPrivacyWs1(void); +int APS5_VABI sceUserServiceSetVolumeForSidetone(void); +int APS5_VABI sceUserServiceSetSaveDataTutorialFlag(void); +int APS5_VABI sceUserServiceSetPbtcPlayTime(void); +int APS5_VABI sceUserServiceGetGlsBroadcastService(void); +int APS5_VABI sceUserServiceGetIsFakePremium(void); +int APS5_VABI sceUserServiceSetGlsLiveQuality5(void); +int APS5_VABI sceUserServiceSetSaveDataAutoUpload(void); +int APS5_VABI sceUserServiceGetSaveDataTutorialFlag(void); +int APS5_VABI sceUserServiceGetGlsLiveQuality4(void); +int APS5_VABI sceUserServiceGetNpLanguageCode(void); +int APS5_VABI sceUserServiceSetPbtcThursdayDuration(void); +int APS5_VABI sceUserServiceGetFaceRecognitionDeleteCount(void); +int APS5_VABI sceUserServiceGetVoiceAgentEnable(void); +int APS5_VABI sceUserServiceSetEventCalendarType(void); +int APS5_VABI sceUserServiceSetProControllerAlreadyNoticed(void); +int APS5_VABI sceUserServiceGetGlsTtsFlags(void); +int APS5_VABI sceUserServiceGetGlsBroadcastersComment(void); +int APS5_VABI sceUserServiceGetPbtcTzOffset(void); +int APS5_VABI sceUserServiceGetPbtcPlayTime(void); +int APS5_VABI sceUserServiceGetVoiceAgentStatusBit(void); +int APS5_VABI sceUserServiceGetNotificationSettings2(void); +int APS5_VABI sceUserServiceGetEventUiFlag(void); +int APS5_VABI sceUserServiceSetGlsBroadcastUiLayout(void); +int APS5_VABI sceUserServiceSetVibrationEnabled(void); +int APS5_VABI sceUserServiceSetPbtcAdditionalTime(void); +int APS5_VABI sceUserServiceIsSharePlayClientUser(std::int32_t userId); +int APS5_VABI sceUserServiceGetMonoOutput(void); +int APS5_VABI sceUserServiceSetNpLoginId(void); +int APS5_VABI sceUserServiceGetShareStatus(void); +int APS5_VABI sceUserServiceSetGlsIsRecievedMessageHidden(void); +int APS5_VABI sceUserServiceSetGlsLiveQuality2(void); +int APS5_VABI sceUserServiceGetHmd2GazeActivate(void); +int APS5_VABI sceUserServiceSetGlsCameraPosition(void); +int APS5_VABI sceUserServiceSetGlsMessageFilterLevel(void); +int APS5_VABI sceUserServiceGetUserGroupNameList(void); +int APS5_VABI sceUserServiceSetVoiceAgentEnableFeedback(void); +int APS5_VABI sceUserServiceGetGazeActivate(void); +int APS5_VABI sceUserServiceGetGlsIsTwitterEnabled(void); +int APS5_VABI sceUserServiceGetTopMenuLimitItem(void); +int APS5_VABI sceUserServiceGetNpAuthErrorFlag(void); +int APS5_VABI sceUserServiceGetGlsIsCameraHidden(void); +int APS5_VABI sceUserServiceGetMousePointerSpeed(void); +int APS5_VABI sceUserServiceSetFaceRecognitionRegisterCount(void); +int APS5_VABI sceUserServiceGetHmd2PlayAreaShape(void); +int APS5_VABI sceUserServiceSetUserName(void); +int APS5_VABI sceUserServiceGetA11yControllerADeviceUniqueIds(void); +int APS5_VABI sceUserServiceGetGlsFloatingMessage(void); +int APS5_VABI sceUserServiceInitializeForShellCore(void); +int APS5_VABI sceUserServiceGetGlsBcTags(void); +int APS5_VABI sceUserServiceGetPbtcMode(void); +int APS5_VABI sceUserServiceSetImeInputType(void); +int APS5_VABI sceUserServiceGetProControllerUniqueId3(void); +int APS5_VABI sceUserServiceSetHmd2GazeActivate(void); +int APS5_VABI sceUserServiceGetCaptureStatus3(void); +int APS5_VABI sceUserServiceGetGlsCameraReflection(void); +int APS5_VABI sceUserServiceGetNotificationSettings_2(void); +int APS5_VABI sceUserServiceGetGlsLiveQuality(void); +int APS5_VABI sceUserServiceGetFaceRecognitionRegisterCount(void); +int APS5_VABI sceUserServiceSetKeyRepeatStartingTime(void); +int APS5_VABI sceUserServiceGetIsRemotePlayAllowed(void); +int APS5_VABI sceUserServiceSetPbtcMondayHoursStart(void); +int APS5_VABI sceUserServiceGetGlsLiveQuality3(void); +int APS5_VABI sceUserServiceGetPsCloudFeedbackTime(void); +int APS5_VABI sceUserServiceSetVoiceAgentTtsSpeed(void); +int APS5_VABI sceUserServiceSetVoiceAgentEnable(void); +int APS5_VABI sceUserServiceGetPbtcWednesdayHoursStart(void); +int APS5_VABI sceUserServiceSetTopMenuTutorialFlag(void); +int APS5_VABI sceUserServiceGetIsPersonalizedHrtfEnabled(void); +int APS5_VABI sceUserServiceSetPadSpeakerVolume(void); +int APS5_VABI sceUserServiceGetAccessibilityKeyremapEnableAston(void); +int APS5_VABI sceUserServiceGetHmd2ScreenSize(void); +int APS5_VABI sceUserServiceGetGlsInitSpectating(void); +int APS5_VABI sceUserServiceGetLastLoginOrder(void); +int APS5_VABI sceUserServiceSetGlsTtsPitch(void); +int APS5_VABI sceUserServiceGetMouseHandType(void); +int APS5_VABI sceUserServiceSetEventUiFlag(void); +int APS5_VABI sceUserServiceSetImeInputSettings(void); +int APS5_VABI sceUserServiceSetProControllerUniqueId1(void); +int APS5_VABI sceUserServiceGetPbtcPlayTimeLastUpdated(void); +int APS5_VABI sceUserServiceSetHmuBrightness(void); +int APS5_VABI sceUserServiceRegisterEventCallback(void); +int APS5_VABI sceUserServiceGetParentalMorpheus(void); +int APS5_VABI sceUserServiceSetPbtcTuesdayDuration(void); +int APS5_VABI sceUserServiceGetGlsOverlayPosition(void); +int APS5_VABI sceUserServiceGetEventSortTitle(void); +int APS5_VABI sceUserServiceSetHmuZoom(void); +int APS5_VABI sceUserServiceSetVolumeForMorpheusSidetone(void); +int APS5_VABI sceUserServiceGetGlsLanguageFilter(void); +int APS5_VABI sceUserServiceGetVibrationEnabled(void); +int APS5_VABI sceUserServiceSetSharePlayResolutionHost(void); +int APS5_VABI sceUserServiceSetPbtcSundayHoursStart(void); +int APS5_VABI sceUserServiceSetNpOfflineAccountAdult(void); +int APS5_VABI sceUserServiceSetTriggerEffectEnabled(void); +int APS5_VABI sceUserServiceSetHearingAssistAdjustmentLevelLeft(void); +int APS5_VABI sceUserServiceGetLightBarBaseBrightness(void); +int APS5_VABI sceUserServiceGetVoiceAgentStatusBitFactory(void); +int APS5_VABI sceUserServiceSetHearingAssistMakeVoicesClearer(void); +int APS5_VABI sceUserServiceSetGlsCommunityId(void); +int APS5_VABI sceUserServiceSetGlsCamCrop(void); +int APS5_VABI sceUserServiceSetPbtcTzOffset(void); +int APS5_VABI sceUserServiceGetParentalGame(void); +int APS5_VABI sceUserServiceSetPbtcThursdayHoursStart(void); +int APS5_VABI sceUserServiceSetIsHrtfEnabled(void); +int APS5_VABI sceUserServiceGetParentalGameAgeLevel(void); +int APS5_VABI sceUserServiceSetNotificationBehavior(void); +int APS5_VABI sceUserServiceGetUserGroupNum(void); +int APS5_VABI sceUserServiceSetPartySettingFlags(void); +int APS5_VABI sceUserServiceGetGlsCameraEffect(void); +int APS5_VABI sceUserServiceGetPbtcSundayHoursEnd(void); +int APS5_VABI sceUserServiceGetIsSpartacusEnabled(std::int32_t userId, bool* enabled); } #endif diff --git a/core/libs/prx/libSceVdecCore/Export.cpp b/core/libs/prx/libSceVdecCore/Export.cpp index c1c5f2d155..4dd995dc9a 100644 --- a/core/libs/prx/libSceVdecCore/Export.cpp +++ b/core/libs/prx/libSceVdecCore/Export.cpp @@ -4,12 +4,18 @@ #include "prx/libSceVdecCore/include/Api.hpp" #include "prx/libSceVdecCore/include/Decoder.hpp" #include +#include +#include #include #include +#include +#include +#include #include #include #include #include +#include #include #include #include "SceTypes.hpp" @@ -17,12 +23,17 @@ extern "C" { #include +#include } namespace { constexpr int VdecCoreErrorInvalidArgument = static_cast(0x80C00001); constexpr int VdecCoreErrorInvalidCodecType = static_cast(0x80C00003); +constexpr int VdecCoreErrorNoPicture = static_cast(0x80C00010); +constexpr int VdecCoreErrorFatal = static_cast(0x80C00012); +constexpr int VdecCoreErrorDecodeRunning = static_cast(0x80C00018); +constexpr std::int64_t VdecCorePtsInvalid = -1; constexpr std::uint32_t CodecHevc = 4; constexpr std::uint32_t MaxCodecType = 4; constexpr std::uint32_t MaxDecoderType = 8; @@ -31,12 +42,15 @@ constexpr std::uint32_t DefaultFrameWidth = 1920; constexpr std::uint32_t DefaultFrameHeight = 1080; constexpr std::uint32_t DefaultHorizontalAlign = 256; constexpr std::uint32_t FrameHeightAlign = 16; +constexpr std::uint32_t FrameWidthAlign = 16; +constexpr std::uint32_t OutputPitchAlign = 64; constexpr std::uint32_t FrameBufferPadding = 0x1000; constexpr std::uint32_t FrameBufferGranularity = 0x10000; constexpr std::uint32_t FrameBufferAlignment = 256; constexpr std::uint64_t InstanceSize = 0x40000; constexpr std::uint64_t ComputeSize = 0x10000; constexpr std::uint32_t ComputeAlignment = 0x10000; +constexpr std::uint64_t MaxAccessUnitSize = static_cast(std::numeric_limits::max() - AV_INPUT_BUFFER_PADDING_SIZE - 1); struct Geometry { std::uint32_t width; @@ -46,15 +60,52 @@ struct Geometry { std::uint32_t frameBufferSize; }; +struct Command { + enum class Type { + Packet, + Reset, + Flush, + }; + Type type = Type::Packet; + AVPacket* packet = nullptr; + std::shared_ptr> done; +}; + struct Decoder { std::uint32_t codecType = 0; Geometry geometry{}; AVCodecContext* context = nullptr; + SwsContext* scaler = nullptr; + AVFrame* decodedFrame = nullptr; std::vector mappedBlocks; std::vector frameBufferSlots; + std::uint32_t currentSlot = 0; + std::vector fallbackFrameBuffer; + std::int64_t lastPts = VdecCorePtsInvalid; + std::int64_t lastDts = VdecCorePtsInvalid; + std::uint64_t lastUserData = 0; + std::mutex mutex; + std::condition_variable inputReady; + std::condition_variable outputReady; + std::deque commands; + std::deque frames; + bool busy = false; + std::jthread worker; ~Decoder() { + { + std::lock_guard lock(mutex); + worker.request_stop(); + } + inputReady.notify_all(); + if (worker.joinable()) worker.join(); + for (auto& command : commands) av_packet_free(&command.packet); + commands.clear(); + for (auto* frame : frames) av_frame_free(&frame); + frames.clear(); + av_frame_free(&decodedFrame); avcodec_free_context(&context); + sws_freeContext(scaler); } }; @@ -102,14 +153,142 @@ std::unordered_map>::iterator Find(Decoder return found; } +Decoder& Lookup(const void* decoder, const char* caller) { + auto& state = State(); + std::lock_guard lock(state.mutex); + return *Find(state, decoder, caller)->second; +} + +void DrainFrames(Decoder& decoder) { + while (true) { + AVFrame* frame = av_frame_alloc(); + if (frame == nullptr) break; + if (avcodec_receive_frame(decoder.context, frame) != 0) { + av_frame_free(&frame); + break; + } + std::lock_guard lock(decoder.mutex); + decoder.frames.push_back(frame); + } +} + +void RunWorker(std::stop_token stop, Decoder* decoder) { + while (!stop.stop_requested()) { + Command command; + { + std::unique_lock lock(decoder->mutex); + decoder->inputReady.wait(lock, [&] { return stop.stop_requested() || !decoder->commands.empty(); }); + if (stop.stop_requested()) break; + command = std::move(decoder->commands.front()); + decoder->commands.pop_front(); + decoder->busy = true; + } + switch (command.type) { + case Command::Type::Packet: { + int result = avcodec_send_packet(decoder->context, command.packet); + if (result == AVERROR_EOF) { + avcodec_flush_buffers(decoder->context); + result = avcodec_send_packet(decoder->context, command.packet); + } + if (result == AVERROR(EAGAIN)) { + DrainFrames(*decoder); + avcodec_send_packet(decoder->context, command.packet); + } + av_packet_free(&command.packet); + DrainFrames(*decoder); + break; + } + case Command::Type::Flush: + avcodec_send_packet(decoder->context, nullptr); + DrainFrames(*decoder); + avcodec_flush_buffers(decoder->context); + command.done->store(true, std::memory_order_release); + break; + case Command::Type::Reset: + avcodec_flush_buffers(decoder->context); + { + std::lock_guard lock(decoder->mutex); + for (auto* frame : decoder->frames) av_frame_free(&frame); + decoder->frames.clear(); + decoder->currentSlot = 0; + } + command.done->store(true, std::memory_order_release); + break; + } + std::lock_guard lock(decoder->mutex); + decoder->busy = !decoder->commands.empty(); + decoder->outputReady.notify_all(); + } +} + +void RunCommand(Decoder& decoder, Command::Type type, std::uint32_t* count) { + auto done = std::make_shared>(false); + std::unique_lock lock(decoder.mutex); + decoder.commands.push_back({type, nullptr, done}); + decoder.inputReady.notify_one(); + decoder.outputReady.wait(lock, [&] { return done->load(std::memory_order_acquire); }); + if (count != nullptr) *count = static_cast(decoder.frames.size()); +} + +AVFrame* ConvertNv12(Decoder& decoder, const AVFrame& frame) { + AVFrame* converted = av_frame_alloc(); + if (converted == nullptr) throw std::runtime_error("VdecCore: cannot allocate an NV12 frame"); + converted->format = AV_PIX_FMT_NV12; + converted->width = frame.width; + converted->height = frame.height; + converted->pts = frame.pts; + converted->pkt_dts = frame.pkt_dts; + converted->opaque = frame.opaque; + if (av_frame_get_buffer(converted, 0) < 0) { + av_frame_free(&converted); + throw std::runtime_error("VdecCore: cannot allocate an NV12 frame"); + } + decoder.scaler = sws_getCachedContext(decoder.scaler, frame.width, frame.height, static_cast(frame.format), frame.width, frame.height, AV_PIX_FMT_NV12, SWS_BILINEAR, nullptr, nullptr, nullptr); + if (decoder.scaler == nullptr || sws_scale(decoder.scaler, frame.data, frame.linesize, 0, frame.height, converted->data, converted->linesize) != frame.height) { + av_frame_free(&converted); + throw std::runtime_error("VdecCore: cannot convert a decoded picture to NV12"); + } + return converted; +} + +void CopyNv12(std::uint8_t* destination, const AVFrame& frame) { + const auto width = static_cast(frame.width); + const auto height = static_cast(frame.height); + const std::size_t pitch = AlignUp(width, OutputPitchAlign); + const std::size_t alignedHeight = AlignUp(height, FrameHeightAlign); + std::uint8_t* luma = destination; + std::uint8_t* chroma = destination + pitch * alignedHeight; + const auto lumaRow = [&](std::size_t row) { return frame.data[0] + row * static_cast(frame.linesize[0]); }; + const auto chromaRow = [&](std::size_t row) { return frame.data[1] + row * static_cast(frame.linesize[1]); }; + for (std::size_t row = 0; row < height; ++row) std::memcpy(luma + row * pitch, lumaRow(row), width); + for (std::size_t row = 0; row < height / 2; ++row) std::memcpy(chroma + row * pitch, chromaRow(row), width); + for (std::size_t row = height; row < alignedHeight && height > 0; ++row) std::memcpy(luma + row * pitch, lumaRow(height - 1), width); + for (std::size_t row = height / 2; row < alignedHeight / 2 && height / 2 > 0; ++row) std::memcpy(chroma + row * pitch, chromaRow(height / 2 - 1), width); +} + +std::uint64_t PictureTime(std::int64_t decoded, std::int64_t last) { + return static_cast(decoded != AV_NOPTS_VALUE ? decoded : last); +} + } extern "C" { +std::uint64_t VdecCoreMaxAccessUnitSize_nid_no_patch() { + return MaxAccessUnitSize; +} + +std::int32_t VdecCoreAccessUnitLength_nid_no_patch(std::uint64_t auSize) { + if (auSize == 0 || auSize > MaxAccessUnitSize) return -1; + return static_cast(auSize); +} + std::uint32_t VdecCoreFrameBufferSlots_nid_no_patch(const void* decoder, void** slots, std::uint32_t capacity) { auto& state = State(); std::lock_guard lock(state.mutex); - const auto& found = Find(state, decoder, "VdecCoreFrameBufferSlots")->second->frameBufferSlots; + auto& target = *Find(state, decoder, "VdecCoreFrameBufferSlots")->second; + std::lock_guard decoderLock(target.mutex); + const auto& found = target.frameBufferSlots; const auto copied = std::min(capacity, found.size()); std::copy_n(found.begin(), copied, slots); return static_cast(found.size()); @@ -132,6 +311,8 @@ int APS5_VABI sceVdecCoreCreateDecoder(const VdecCoreConfigInfo* config, const V created->codecType = config->codecType; created->geometry = FrameGeometry(*config); OpenCodec(*created); + created->decodedFrame = av_frame_alloc(); + created->worker = std::jthread(RunWorker, created.get()); void* handle = created.get(); auto& state = State(); std::lock_guard lock(state.mutex); @@ -158,13 +339,126 @@ int APS5_VABI sceVdecCoreFinalizeComputeResource() { return 0; } -int APS5_VABI sceVdecCoreFlushDecodeOutput() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVdecCoreFlushDecodeOutput(void* decoder, std::uint32_t* count, void*) { + if (decoder == nullptr) return VdecCoreErrorInvalidArgument; + RunCommand(Lookup(decoder, __func__), Command::Type::Flush, count); return 0; } -int APS5_VABI sceVdecCoreGetDecodeOutput() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVdecCoreGetDecodeOutput(void* decoder, VdecCoreFrameBuffer* frameBuffer, std::uint32_t, VdecCoreOutputInfo* output, void*) { + if (decoder == nullptr || output == nullptr) return VdecCoreErrorInvalidArgument; + auto& target = Lookup(decoder, __func__); + AVFrame* frame = nullptr; + { + std::lock_guard lock(target.mutex); + if (target.frames.empty()) { + output->frameWidth = target.geometry.width; + output->framePitch = AlignUp(target.geometry.width, OutputPitchAlign); + output->frameHeight = target.geometry.height; + output->pictureCount = 0; + output->frameBuffer = nullptr; + output->frameFormat = 0; + output->reserved0 = 0; + output->pictures[0].frameBuffer = nullptr; + output->pictures[1].frameBuffer = nullptr; + output->lumaBuffer = nullptr; + output->chromaBuffer = nullptr; + return VdecCoreErrorNoPicture; + } + frame = target.frames.front(); + target.frames.pop_front(); + } + struct Release { + AVFrame*& frame; + ~Release() { av_frame_free(&frame); } + } release{frame}; + if (frame->format != AV_PIX_FMT_NV12) { + AVFrame* converted = ConvertNv12(target, *frame); + av_frame_free(&frame); + frame = converted; + } + const auto width = static_cast(frame->width); + const auto height = static_cast(frame->height); + const std::uint32_t alignedWidth = AlignUp(width, FrameWidthAlign); + const std::uint32_t pitch = AlignUp(width, OutputPitchAlign); + const std::uint32_t alignedHeight = AlignUp(height, FrameHeightAlign); + const std::uint32_t pictureSize = pitch * alignedHeight * 3 / 2; + const std::uint32_t sarWidth = frame->sample_aspect_ratio.num > 0 ? static_cast(frame->sample_aspect_ratio.num) : 1; + const std::uint32_t sarHeight = frame->sample_aspect_ratio.den > 0 ? static_cast(frame->sample_aspect_ratio.den) : 1; + std::uint8_t* destination = nullptr; + std::uint64_t capacity = 0; + std::int64_t lastPts = 0; + std::int64_t lastDts = 0; + { + std::lock_guard lock(target.mutex); + lastPts = target.lastPts; + lastDts = target.lastDts; + if (frameBuffer != nullptr && frameBuffer->frameBuffer != nullptr) { + destination = static_cast(frameBuffer->frameBuffer); + capacity = frameBuffer->frameBufferSize > 0 ? frameBuffer->frameBufferSize : target.geometry.frameBufferSize; + } else if (!target.frameBufferSlots.empty()) { + destination = static_cast(target.frameBufferSlots[target.currentSlot % target.frameBufferSlots.size()]); + ++target.currentSlot; + capacity = target.geometry.frameBufferSize; + } else { + const std::uint64_t requested = frameBuffer != nullptr && frameBuffer->frameBufferSize > 0 ? frameBuffer->frameBufferSize : target.geometry.frameBufferSize; + capacity = std::max(std::uint64_t{pictureSize} + sizeof(VdecCoreHwPictureInfo), requested); + if (target.fallbackFrameBuffer.size() < capacity) target.fallbackFrameBuffer.resize(static_cast(capacity)); + destination = target.fallbackFrameBuffer.data(); + } + } + if (pictureSize > capacity) throw std::runtime_error("VdecCore: a decoded picture is larger than its frame buffer"); + CopyNv12(destination, *frame); + const std::uint64_t pts = PictureTime(frame->pts, lastPts); + const std::uint64_t dts = PictureTime(frame->pkt_dts, lastDts); + const auto userData = static_cast(reinterpret_cast(frame->opaque)); + if (capacity - pictureSize >= sizeof(VdecCoreHwPictureInfo)) { + auto* info = reinterpret_cast(destination + capacity - sizeof(VdecCoreHwPictureInfo)); + std::memset(info, 0, sizeof(*info)); + info->codecType = target.codecType == CodecHevc ? 1 : 0; + info->userData = userData; + info->pts = pts; + info->dts = dts; + info->valid = 1; + info->width = alignedWidth; + info->height = alignedHeight; + info->cropLeft = 0; + info->cropRight = pitch > width ? (pitch - width) / 2 : 0; + info->cropTop = 0; + info->cropBottom = alignedHeight > height ? (alignedHeight - height) / 2 : 0; + info->sarWidth = sarWidth; + info->sarHeight = sarHeight; + info->flags = 0x01; + if (info->cropRight > 0 || info->cropBottom > 0) info->flags |= 0x02; + } + output->frameWidth = alignedWidth; + output->framePitch = pitch; + output->frameHeight = alignedHeight; + output->pictureCount = 1; + output->frameBuffer = destination; + output->frameFormat = 0; + output->reserved0 = 0; + output->pictures[0].flags = target.codecType; + output->pictures[0].reserved0 = 0; + output->pictures[0].pts = pts; + output->pictures[0].dts = dts; + output->pictures[0].userData = userData; + output->pictures[0].pictureInfo = nullptr; + output->pictures[0].frameBuffer = destination; + output->pictures[0].frameBufferSize = pictureSize; + output->pictures[0].framePitch = pitch; + output->pictures[0].reserved1 = 0; + output->pictures[1].frameBuffer = nullptr; + output->aspectRatio = 1; + output->sarWidth = sarWidth; + output->videoFormat = 5; + output->colourPrimaries = 1; + output->transferCharacteristics = 1; + output->framePitchInBytes = pitch; + output->lumaBuffer = destination; + output->chromaBuffer = destination + static_cast(pitch) * alignedHeight; + output->videoFullRangeFlag = frame->color_range == AVCOL_RANGE_JPEG ? 1 : 0; + output->colourDescriptionPresentFlag = 1; return 0; } @@ -179,6 +473,7 @@ int APS5_VABI sceVdecCoreMapMemoryBlock(void* decoder, const VdecCoreMemoryBlock std::lock_guard lock(state.mutex); auto& target = *Find(state, decoder, __func__)->second; if (blocks == nullptr || count == 0) return 0; + std::lock_guard decoderLock(target.mutex); for (std::uint32_t index = 0; index < count; ++index) { const VdecCoreMemoryBlock block = blocks[index]; target.mappedBlocks.push_back(block); @@ -223,13 +518,42 @@ int APS5_VABI sceVdecCoreQueryInstanceSize(const VdecCoreConfigInfo* config, std return 0; } -int APS5_VABI sceVdecCoreResetDecoder() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVdecCoreResetDecoder(void* decoder, void*) { + if (decoder == nullptr) return VdecCoreErrorInvalidArgument; + RunCommand(Lookup(decoder, __func__), Command::Type::Reset, nullptr); return 0; } -int APS5_VABI sceVdecCoreSetDecodeInput() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVdecCoreSetDecodeInput(void* decoder, const VdecCoreInputData* input, void*, std::uint32_t* status) { + if (decoder == nullptr || input == nullptr) return VdecCoreErrorInvalidArgument; + auto& target = Lookup(decoder, __func__); + if (status != nullptr) { + status[0] = 1; + status[1] = 1; + } + const std::int32_t length = VdecCoreAccessUnitLength_nid_no_patch(input->auSize); + if (input->auData == nullptr || length < 0) return VdecCoreErrorInvalidArgument; + const std::int64_t pts = input->pts == VdecCorePtsInvalid ? AV_NOPTS_VALUE : input->pts; + const std::int64_t dts = input->dts == VdecCorePtsInvalid ? AV_NOPTS_VALUE : input->dts; + AVPacket* packet = av_packet_alloc(); + if (packet == nullptr) return VdecCoreErrorFatal; + if (av_new_packet(packet, length) < 0) { + av_packet_free(&packet); + return VdecCoreErrorFatal; + } + std::memcpy(packet->data, input->auData, static_cast(length)); + packet->pts = pts; + packet->dts = dts; + packet->opaque = reinterpret_cast(static_cast(input->userData)); + { + std::lock_guard lock(target.mutex); + target.lastPts = input->pts; + target.lastDts = input->dts; + target.lastUserData = input->userData; + target.commands.push_back({Command::Type::Packet, packet, nullptr}); + target.busy = true; + } + target.inputReady.notify_one(); return 0; } @@ -238,8 +562,12 @@ int APS5_VABI sceVdecCoreSetDecodeOutputSw() { return 0; } -int APS5_VABI sceVdecCoreSyncDecode() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVdecCoreSyncDecode(void* decoder, std::uint32_t* count) { + if (decoder == nullptr) return VdecCoreErrorInvalidArgument; + auto& target = Lookup(decoder, __func__); + std::unique_lock lock(target.mutex); + target.outputReady.wait(lock, [&] { return !target.frames.empty() || (!target.busy && target.commands.empty()); }); + if (count != nullptr) *count = static_cast(target.frames.size()); return 0; } @@ -248,13 +576,20 @@ int APS5_VABI sceVdecCoreSyncDecodeOutputSw() { return 0; } -int APS5_VABI sceVdecCoreSyncDecodeWptr() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVdecCoreSyncDecodeWptr(void* decoder, std::uint32_t* count, void*, void*) { + return sceVdecCoreSyncDecode(decoder, count); } -int APS5_VABI sceVdecCoreTrySyncDecode() { - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceVdecCoreTrySyncDecode(void* decoder, std::uint32_t* count) { + if (decoder == nullptr) return VdecCoreErrorInvalidArgument; + auto& target = Lookup(decoder, __func__); + std::lock_guard lock(target.mutex); + if (!target.frames.empty()) { + if (count != nullptr) *count = static_cast(target.frames.size()); + return 0; + } + if (count != nullptr) *count = 0; + if (target.busy || !target.commands.empty()) return VdecCoreErrorDecodeRunning; return 0; } @@ -263,9 +598,8 @@ int APS5_VABI sceVdecCoreTrySyncDecodeOutputSw() { return 0; } -int APS5_VABI sceVdecCoreTrySyncDecodeWptr() { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceVdecCoreTrySyncDecodeWptr(void* decoder, std::uint32_t* count, void*, void*) { + return sceVdecCoreTrySyncDecode(decoder, count); } } diff --git a/core/libs/prx/libSceVdecCore/include/Api.hpp b/core/libs/prx/libSceVdecCore/include/Api.hpp index 21278f7108..3161d0aabe 100644 --- a/core/libs/prx/libSceVdecCore/include/Api.hpp +++ b/core/libs/prx/libSceVdecCore/include/Api.hpp @@ -74,6 +74,86 @@ struct VdecCoreMemoryBlock { static_assert(sizeof(VdecCoreMemoryBlock) == 0x18); +struct VdecCoreFrameBuffer { + void* frameBuffer; + std::uint64_t frameBufferSize; +}; + +static_assert(sizeof(VdecCoreFrameBuffer) == 0x10); + +struct VdecCoreInputData { + const void* auData; + std::uint64_t auSize; + std::int64_t pts; + std::int64_t dts; + std::uint64_t userData; + std::uint32_t flags; + std::uint32_t streamId; +}; + +static_assert(sizeof(VdecCoreInputData) == 0x30); + +struct VdecCorePicture { + std::uint32_t flags; + std::uint32_t reserved0; + std::uint64_t pts; + std::uint64_t dts; + std::uint64_t userData; + void* pictureInfo; + void* frameBuffer; + std::uint32_t frameBufferSize; + std::uint32_t framePitch; + std::uint64_t reserved1; +}; + +static_assert(sizeof(VdecCorePicture) == 0x40); + +struct VdecCoreHwPictureInfo { + std::uint32_t codecType; + std::uint32_t reserved0; + std::uint64_t userData; + std::uint64_t pts; + std::uint64_t dts; + std::uint32_t valid; + std::uint32_t reserved1; + std::uint32_t width; + std::uint32_t height; + std::uint32_t cropLeft; + std::uint32_t cropRight; + std::uint32_t cropTop; + std::uint32_t cropBottom; + std::uint32_t sarWidth; + std::uint32_t sarHeight; + std::uint32_t reserved2; + std::uint32_t flags; + std::uint8_t reserved3[0x400 - 0x50]; +}; + +static_assert(sizeof(VdecCoreHwPictureInfo) == 0x400); + +struct VdecCoreOutputInfo { + std::uint32_t frameWidth; + std::uint32_t framePitch; + std::uint32_t frameHeight; + std::uint32_t pictureCount; + void* frameBuffer; + std::uint32_t frameFormat; + std::uint32_t reserved0; + VdecCorePicture pictures[2]; + std::uint32_t aspectRatio; + std::uint32_t sarWidth; + std::uint32_t videoFormat; + std::uint32_t colourPrimaries; + std::uint32_t transferCharacteristics; + std::uint32_t framePitchInBytes; + void* lumaBuffer; + void* chromaBuffer; + std::uint32_t videoFullRangeFlag; + std::uint32_t colourDescriptionPresentFlag; +}; + +static_assert(sizeof(VdecCoreOutputInfo) == 0xd0); + extern "C" { int APS5_VABI sceVdecCoreQueryMemorySizeSvp(); @@ -82,21 +162,21 @@ int APS5_VABI sceVdecCoreAnalyzeStream(); int APS5_VABI sceVdecCoreCreateDecoder(const VdecCoreConfigInfo* config, const VdecCoreMemoryInfo* memory, void* computeQueue, void** decoder); int APS5_VABI sceVdecCoreDeleteDecoder(void* decoder); int APS5_VABI sceVdecCoreFinalizeComputeResource(); -int APS5_VABI sceVdecCoreFlushDecodeOutput(); -int APS5_VABI sceVdecCoreGetDecodeOutput(); +int APS5_VABI sceVdecCoreFlushDecodeOutput(void* decoder, std::uint32_t* count, void* reserved); +int APS5_VABI sceVdecCoreGetDecodeOutput(void* decoder, VdecCoreFrameBuffer* frameBuffer, std::uint32_t flags, VdecCoreOutputInfo* output, void* reserved); int APS5_VABI sceVdecCoreInitializeComputeResource(); int APS5_VABI sceVdecCoreMapMemoryBlock(void* decoder, const VdecCoreMemoryBlock* blocks, std::uint32_t count); int APS5_VABI sceVdecCoreQueryComputeResourceInfo(VdecCoreComputeResourceInfo* info); int APS5_VABI sceVdecCoreQueryFrameBufferInfo(const VdecCoreConfigInfo* config, VdecCoreFrameBufferInfo* info); int APS5_VABI sceVdecCoreQueryInstanceSize(const VdecCoreConfigInfo* config, std::uint64_t* instanceSize, std::uint64_t* computeSize, std::uint64_t* reservedSize); -int APS5_VABI sceVdecCoreResetDecoder(); -int APS5_VABI sceVdecCoreSetDecodeInput(); +int APS5_VABI sceVdecCoreResetDecoder(void* decoder, void* resetParam); +int APS5_VABI sceVdecCoreSetDecodeInput(void* decoder, const VdecCoreInputData* input, void* reserved, std::uint32_t* status); int APS5_VABI sceVdecCoreSetDecodeOutputSw(); -int APS5_VABI sceVdecCoreSyncDecode(); +int APS5_VABI sceVdecCoreSyncDecode(void* decoder, std::uint32_t* count); int APS5_VABI sceVdecCoreSyncDecodeOutputSw(); -int APS5_VABI sceVdecCoreSyncDecodeWptr(); -int APS5_VABI sceVdecCoreTrySyncDecode(); +int APS5_VABI sceVdecCoreSyncDecodeWptr(void* decoder, std::uint32_t* count, void* writePointer, void* readPointer); +int APS5_VABI sceVdecCoreTrySyncDecode(void* decoder, std::uint32_t* count); int APS5_VABI sceVdecCoreTrySyncDecodeOutputSw(); -int APS5_VABI sceVdecCoreTrySyncDecodeWptr(); +int APS5_VABI sceVdecCoreTrySyncDecodeWptr(void* decoder, std::uint32_t* count, void* writePointer, void* readPointer); } diff --git a/core/libs/prx/libSceVdecCore/include/Decoder.hpp b/core/libs/prx/libSceVdecCore/include/Decoder.hpp index ea4d2d84db..32917a00d2 100644 --- a/core/libs/prx/libSceVdecCore/include/Decoder.hpp +++ b/core/libs/prx/libSceVdecCore/include/Decoder.hpp @@ -5,5 +5,7 @@ extern "C" { std::uint32_t VdecCoreFrameBufferSlots_nid_no_patch(const void* decoder, void** slots, std::uint32_t capacity); +std::uint64_t VdecCoreMaxAccessUnitSize_nid_no_patch(); +std::int32_t VdecCoreAccessUnitLength_nid_no_patch(std::uint64_t auSize); } diff --git a/core/libs/prx/libSceVdecSavc/Export.cpp b/core/libs/prx/libSceVdecSavc/Export.cpp index 72330582cd..e588d21cfd 100644 --- a/core/libs/prx/libSceVdecSavc/Export.cpp +++ b/core/libs/prx/libSceVdecSavc/Export.cpp @@ -16,9 +16,8 @@ int APS5_VABI sceSdecDestroySw() { return 0; } -int APS5_VABI sceSdecGetVersionSw() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceSdecGetVersionSw(std::uint32_t* version) { + if (version != nullptr) *version = 0u; } int APS5_VABI sceSdecQueryMemorySw() { diff --git a/core/libs/prx/libSceVdecSavc/include/Api.hpp b/core/libs/prx/libSceVdecSavc/include/Api.hpp index cd352d8c30..f8ff1fea47 100644 --- a/core/libs/prx/libSceVdecSavc/include/Api.hpp +++ b/core/libs/prx/libSceVdecSavc/include/Api.hpp @@ -1,12 +1,13 @@ #pragma once #include "SceTypes.hpp" +#include extern "C" { int APS5_VABI sceSdecCreateSw(); int APS5_VABI sceSdecDestroySw(); -int APS5_VABI sceSdecGetVersionSw(); +void APS5_VABI sceSdecGetVersionSw(std::uint32_t* version); int APS5_VABI sceSdecQueryMemorySw(); } diff --git a/core/libs/prx/libSceVdecSavc2/Export.cpp b/core/libs/prx/libSceVdecSavc2/Export.cpp index 6ca11f6ef7..9aac0725c3 100644 --- a/core/libs/prx/libSceVdecSavc2/Export.cpp +++ b/core/libs/prx/libSceVdecSavc2/Export.cpp @@ -16,9 +16,8 @@ int APS5_VABI sceSdecDestroySw2() { return 0; } -int APS5_VABI sceSdecGetVersionSw2() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceSdecGetVersionSw2(std::uint32_t* version) { + if (version != nullptr) *version = 0x13590001u; } int APS5_VABI sceSdecQueryMemorySw2() { diff --git a/core/libs/prx/libSceVdecSavc2/include/Api.hpp b/core/libs/prx/libSceVdecSavc2/include/Api.hpp index 985cdcd141..17af0d4219 100644 --- a/core/libs/prx/libSceVdecSavc2/include/Api.hpp +++ b/core/libs/prx/libSceVdecSavc2/include/Api.hpp @@ -1,12 +1,13 @@ #pragma once #include "SceTypes.hpp" +#include extern "C" { int APS5_VABI sceSdecCreateSw2(); int APS5_VABI sceSdecDestroySw2(); -int APS5_VABI sceSdecGetVersionSw2(); +void APS5_VABI sceSdecGetVersionSw2(std::uint32_t* version); int APS5_VABI sceSdecQueryMemorySw2(); } diff --git a/core/libs/prx/libSceVdecShevc/Export.cpp b/core/libs/prx/libSceVdecShevc/Export.cpp index 13d6337e2c..a45463f3e4 100644 --- a/core/libs/prx/libSceVdecShevc/Export.cpp +++ b/core/libs/prx/libSceVdecShevc/Export.cpp @@ -28,9 +28,8 @@ int APS5_VABI sceSdecGetFrameSwHevc() { return 0; } -int APS5_VABI sceSdecGetVersionSwHevc() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceSdecGetVersionSwHevc(std::uint32_t* version) { + if (version != nullptr) *version = 0x13590001u; } int APS5_VABI sceSdecQueryMemorySwHevc() { diff --git a/core/libs/prx/libSceVdecShevc/include/Api.hpp b/core/libs/prx/libSceVdecShevc/include/Api.hpp index f6685c6a64..b806d17435 100644 --- a/core/libs/prx/libSceVdecShevc/include/Api.hpp +++ b/core/libs/prx/libSceVdecShevc/include/Api.hpp @@ -1,6 +1,7 @@ #pragma once #include "SceTypes.hpp" +#include extern "C" { @@ -8,7 +9,7 @@ int APS5_VABI sceSdecCreateSwHevc(); int APS5_VABI sceSdecDestroySwHevc(); int APS5_VABI sceSdecExecuteSwHevc(); int APS5_VABI sceSdecGetFrameSwHevc(); -int APS5_VABI sceSdecGetVersionSwHevc(); +void APS5_VABI sceSdecGetVersionSwHevc(std::uint32_t* version); int APS5_VABI sceSdecQueryMemorySwHevc(); int APS5_VABI sceSdecResetSwHevc(); diff --git a/core/libs/prx/libSceVdecsw/Export.cpp b/core/libs/prx/libSceVdecsw/Export.cpp index 8eab884339..50bd647f93 100644 --- a/core/libs/prx/libSceVdecsw/Export.cpp +++ b/core/libs/prx/libSceVdecsw/Export.cpp @@ -319,7 +319,7 @@ int APS5_VABI sceVdecswGetPictureInfo(const OutputInfo* output, void* first, voi 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 (output->pictureCount > 1) throw std::runtime_error("Vdecsw: second field picture info is not implemented"); if (first == nullptr) return 0; std::uint64_t size; std::memcpy(&size, first, sizeof(size)); diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index 6a3b8b834c..0f2e75f24b 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -66,7 +66,7 @@ endif() add_test_executable(video_out_flip_tests EXCLUDE_FROM_ALL tests/Flip.cpp) target_include_directories(video_out_flip_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${ANYPS5_VULKAN_HEADERS_INCLUDE_DIR}) -target_link_libraries(video_out_flip_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc) +target_link_libraries(video_out_flip_tests PRIVATE libSceVideoOut libSceAgcDriver libkernel libc SDL2-static) set_target_properties(video_out_flip_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(video_out_flip_tests) if(BUILD_TESTING) diff --git a/core/libs/prx/libSceVideoOut/InventoryExports.cpp b/core/libs/prx/libSceVideoOut/InventoryExports.cpp index e298727bb5..77d4bde7ed 100644 --- a/core/libs/prx/libSceVideoOut/InventoryExports.cpp +++ b/core/libs/prx/libSceVideoOut/InventoryExports.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVideoOut/include/LaterExports.hpp" #include "prx/libSceVideoOut/include/LegacyExports.hpp" #include "prx/libc/include/general/ExportMacros.hpp" #include "prx/libSceVideoOut/include/InventoryExports.hpp" @@ -827,3 +828,59 @@ int APS5_VABI sceVideoOutSetConfigurationMode() { } } + +extern "C" { + +int APS5_VABI sceVideoOutDebugLatencyMeasureGetLatestLatency() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutGetHandleByBus() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutPostProcessorAddCallback() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutPostProcessorRemoveCallback() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutSetContentDynamicRange() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutSysCursorSetSize() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutSysGetCursorInfo() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutSysGetMonitorInfo() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutSysGetOutputStatusEx() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutSysOpenPostProcessor() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutSysSetDrrPeg() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutSysSetDrrPegRefreshRate() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoOutSysSetVrrSetting() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceVideoOut/include/ImeTextInput.hpp b/core/libs/prx/libSceVideoOut/include/ImeTextInput.hpp index 81cfe59fc1..a1293d2143 100644 --- a/core/libs/prx/libSceVideoOut/include/ImeTextInput.hpp +++ b/core/libs/prx/libSceVideoOut/include/ImeTextInput.hpp @@ -1,11 +1,19 @@ #ifndef CORE_LIBS_PRX_LIBSCEVIDEOOUT_IMETEXTINPUT_HPP #define CORE_LIBS_PRX_LIBSCEVIDEOOUT_IMETEXTINPUT_HPP +#include + #include "SDL_events.h" class ImeTextInput { public: + using ClipboardProvider = bool (*)(std::string& text); + + void SetClipboardProvider(ClipboardProvider provider) { clipboard = provider; } void HandleEvent(const SDL_Event& event, unsigned windowId); + +private: + ClipboardProvider clipboard = nullptr; }; #endif diff --git a/core/libs/prx/libSceVideoOut/include/LaterExports.hpp b/core/libs/prx/libSceVideoOut/include/LaterExports.hpp new file mode 100644 index 0000000000..7e0f2b58d9 --- /dev/null +++ b/core/libs/prx/libSceVideoOut/include/LaterExports.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideoOutDebugLatencyMeasureGetLatestLatency(); +int APS5_VABI sceVideoOutGetHandleByBus(); +int APS5_VABI sceVideoOutPostProcessorAddCallback(); +int APS5_VABI sceVideoOutPostProcessorRemoveCallback(); +int APS5_VABI sceVideoOutSetContentDynamicRange(); +int APS5_VABI sceVideoOutSysCursorSetSize(); +int APS5_VABI sceVideoOutSysGetCursorInfo(); +int APS5_VABI sceVideoOutSysGetMonitorInfo(); +int APS5_VABI sceVideoOutSysGetOutputStatusEx(); +int APS5_VABI sceVideoOutSysOpenPostProcessor(); +int APS5_VABI sceVideoOutSysSetDrrPeg(); +int APS5_VABI sceVideoOutSysSetDrrPegRefreshRate(); +int APS5_VABI sceVideoOutSysSetVrrSetting(); + +} diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index 39a987f026..76aed63d9c 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -96,7 +96,6 @@ static constexpr uint64_t VIDEO_OUT_OUTPUT_MODE_119_88HZ = 0x000000000000000FULL static constexpr std::uint32_t VIDEO_OUT_DYNAMIC_RANGE_SDR = 1; static constexpr std::uint32_t VIDEO_OUT_DYNAMIC_RANGE_HDR = 2; -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; diff --git a/core/libs/prx/libSceVideoOut/libSceVideoOut.hpp b/core/libs/prx/libSceVideoOut/libSceVideoOut.hpp index f917f95ea8..5bd2acab54 100644 --- a/core/libs/prx/libSceVideoOut/libSceVideoOut.hpp +++ b/core/libs/prx/libSceVideoOut/libSceVideoOut.hpp @@ -6,3 +6,4 @@ #include "prx/libSceVideoOut/include/VideoOut.hpp" #include "prx/libSceVideoOut/include/InventoryExports.hpp" #include "prx/libSceVideoOut/include/LegacyExports.hpp" +#include "prx/libSceVideoOut/include/LaterExports.hpp" diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index 99f1e68d53..aedd3f677b 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -39,6 +39,11 @@ int CommitBufferGroup(const std::string& function, VideoOutConfig& cfg, int setI if (cfg.closing) { throw std::runtime_error(function + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); } + if (!group.ps4) { + for (int i = 0; i < bufferNum; i++) { + if (cfg.buffers[bufferIndexStart + i].Occupied()) return VIDEO_OUT_ERROR_SLOT_OCCUPIED; + } + } if (setIndex < 0) { for (int index = 0; index < VIDEO_OUT_BUFFER_ATTRIBUTE_NUM_MAX && setIndex < 0; ++index) if (!cfg.groups[index].occupied) setIndex = index; @@ -156,8 +161,8 @@ int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIn group.attribute = *attribute; group.category = category; group.occupied = true; - CommitBufferGroup(__func__, *cfg, setIndex, bufferIndexStart, buffers, bufferNum, group); - return 0; + const int committed = CommitBufferGroup(__func__, *cfg, setIndex, bufferIndexStart, buffers, bufferNum, group); + return committed < 0 ? committed : 0; } catch (const ProcessShutdown&) { LibcAwaitExit_nid_postfix(); } diff --git a/core/libs/prx/libSceVideoOut/src/Event.cpp b/core/libs/prx/libSceVideoOut/src/Event.cpp index 2c23c1f375..2907c455ae 100644 --- a/core/libs/prx/libSceVideoOut/src/Event.cpp +++ b/core/libs/prx/libSceVideoOut/src/Event.cpp @@ -88,9 +88,7 @@ static int registerVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind, } static int deleteVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind) { - if (!VideoOutDriver::Get().IsOpen(handle)) { - throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); - } + if (!VideoOutDriver::Get().HasConfig(handle)) return VIDEO_OUT_ERROR_INVALID_HANDLE; if (eq == 0 || !EqueuePin_nid_postfix(eq)) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE"); } diff --git a/core/libs/prx/libSceVideoOut/src/ImeTextInput.cpp b/core/libs/prx/libSceVideoOut/src/ImeTextInput.cpp index 26ddb4b216..83f7acb0bf 100644 --- a/core/libs/prx/libSceVideoOut/src/ImeTextInput.cpp +++ b/core/libs/prx/libSceVideoOut/src/ImeTextInput.cpp @@ -5,9 +5,33 @@ #include "SDL_keyboard.h" +namespace { + +bool IsPasteShortcut(const SDL_KeyboardEvent& key) { + const unsigned modifiers = key.keysym.mod; + if ((modifiers & KMOD_ALT) != 0) return false; +#ifdef __APPLE__ + const bool command = (modifiers & KMOD_GUI) != 0; +#else + const bool command = (modifiers & KMOD_CTRL) != 0; +#endif + if (command && key.keysym.sym == SDLK_v) return true; + return (modifiers & KMOD_SHIFT) != 0 && !command && key.keysym.scancode == SDL_SCANCODE_INSERT; +} + +} + void ImeTextInput::HandleEvent(const SDL_Event& event, unsigned windowId) { ImeHostInput input{}; switch (event.type) { + case SDL_TEXTEDITING: + if (event.edit.windowID != windowId) return; + static_assert(sizeof(input.text) >= sizeof(event.edit.text)); + std::memcpy(input.text, event.edit.text, sizeof(event.edit.text)); + input.kind = ImeHostInputKind::Preedit; + input.start = event.edit.start; + input.length = event.edit.length; + break; case SDL_TEXTINPUT: if (event.text.windowID != windowId) return; static_assert(sizeof(input.text) >= sizeof(event.text.text)); @@ -16,6 +40,12 @@ void ImeTextInput::HandleEvent(const SDL_Event& event, unsigned windowId) { break; case SDL_KEYDOWN: if (event.key.windowID != windowId) return; + if (IsPasteShortcut(event.key)) { + if (event.key.repeat != 0 || clipboard == nullptr || !ImeCapturesKeyboard_nid_postfix()) return; + std::string text; + if (clipboard(text) && !text.empty()) ImePublishPaste_nid_postfix(text.data(), text.size()); + return; + } switch (event.key.keysym.scancode) { case SDL_SCANCODE_BACKSPACE: input.kind = ImeHostInputKind::Backspace; break; case SDL_SCANCODE_DELETE: input.kind = ImeHostInputKind::Delete; break; diff --git a/core/libs/prx/libSceVideoOut/src/MouseInput.cpp b/core/libs/prx/libSceVideoOut/src/MouseInput.cpp index 95b53ac6c0..ffa1a6d6a6 100644 --- a/core/libs/prx/libSceVideoOut/src/MouseInput.cpp +++ b/core/libs/prx/libSceVideoOut/src/MouseInput.cpp @@ -14,6 +14,8 @@ void MouseInput::HandleEvent(const SDL_Event& event, unsigned windowId) { if (event.motion.windowID != windowId || !focused || ModalDialogCapturesInput_nid_postfix()) return; input.x = scaled(event.motion.xrel, scaleX, remainderX); input.y = scaled(event.motion.yrel, scaleY, remainderY); + input.countsX = event.motion.xrel; + input.countsY = event.motion.yrel; break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: diff --git a/core/libs/prx/libSceVideoOut/src/Output.cpp b/core/libs/prx/libSceVideoOut/src/Output.cpp index df96251ac9..b442ca0c57 100644 --- a/core/libs/prx/libSceVideoOut/src/Output.cpp +++ b/core/libs/prx/libSceVideoOut/src/Output.cpp @@ -25,7 +25,7 @@ static VideoOutOutputStatus outputStatus(const auto& cfg) { 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; - status.flags = hdr ? VIDEO_OUT_OUTPUT_STATUS_FLAG_HDR : 0; + status.flags = 0; return status; } diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index 428ff3c71b..d4f972d1b3 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -90,6 +90,15 @@ std::optional configuredResolution() { return resolutionIndex(width, height); } +bool readHostClipboard(std::string& text) { + if (SDL_HasClipboardText() != SDL_TRUE) return false; + char* raw = SDL_GetClipboardText(); + if (raw == nullptr) return false; + text.assign(raw); + SDL_free(raw); + return true; +} + std::vector windowExtensions(SDL_Window* window) { return OnMainThread([&] { unsigned extensionCount = 0; @@ -754,6 +763,7 @@ void VideoOutDriver::presentLoop(std::stop_token token, std::promise& star keyboardInput.emplace(); imeTextInput.emplace(); dialogInput.emplace(); + imeTextInput->SetClipboardProvider(&readHostClipboard); }); while (!token.stop_requested()) { { diff --git a/core/libs/prx/libSceVideoOut/tests/Flip.cpp b/core/libs/prx/libSceVideoOut/tests/Flip.cpp index 466ed4dab8..0677ec9012 100644 --- a/core/libs/prx/libSceVideoOut/tests/Flip.cpp +++ b/core/libs/prx/libSceVideoOut/tests/Flip.cpp @@ -228,7 +228,7 @@ void testDeferredBuffer() { } void testCpuFlipOrder() { - const bool inOrder = std::getenv("APS5_CPU_FLIP_IN_ORDER") != nullptr; + const bool inOrder = APS5_ENABLE_TIMING_LOG || std::getenv("APS5_CPU_FLIP_IN_ORDER") != nullptr; const int handle = sceVideoOutOpen(255, 0, 0, nullptr); auto cfg = VideoOutDriver::Get().GetConfig(handle); std::vector allocation(65536 + 65535); @@ -255,7 +255,7 @@ void testCpuFlipOrder() { std::thread flipThread([&] { check(sceVideoOutSubmitFlip(handle, 0, 1, 5) == 0, "the flip thread's flip was not accepted"); }); flipThread.join(); const bool ahead = flipsWithin(1, std::chrono::milliseconds(inOrder ? 1000 : 8000)); - if (inOrder) check(!ahead, "with APS5_CPU_FLIP_IN_ORDER a flip completed while a queue-0 submission ahead of it was held"); + if (inOrder) check(!ahead, "with CPU flips in order a flip completed while a queue-0 submission ahead of it was held"); else check(ahead, "a flip from a thread that never submitted queue-0 work waited for a held queue-0 submission"); first->Release(); check(flipsWithin(1, std::chrono::seconds(30)), "the flip thread's flip did not complete"); @@ -486,7 +486,7 @@ void testControls() { } check(sceVideoOutDeleteOutputModeEvent(queue, handle) == 0, "duplicate output mode unsubscription failed"); expectFailure([&] { sceVideoOutDeleteOutputModeEvent(0, handle); }); - expectFailure([&] { sceVideoOutDeleteOutputModeEvent(queue, -1); }); + check(sceVideoOutDeleteOutputModeEvent(queue, -1) == VIDEO_OUT_ERROR_INVALID_HANDLE, "output mode unsubscription on an invalid handle was accepted"); check(sceVideoOutAddVblankEvent(queue, handle, nullptr) == 0 && sceVideoOutAddVblankEvent(queue, handle, &settings) == 0, "vblank subscription failed"); { std::lock_guard lock(cfg->mutex); @@ -523,6 +523,13 @@ void testControls() { check(!cfg->groups[3].occupied && !cfg->buffers[15].Occupied(), "a refused registration past the last buffer registered something"); } check(sceVideoOutRegisterBuffers2(handle, 0, 0, &buffer, 1, &attribute, 0, nullptr) == 0, "buffers with set reserved pointers were rejected"); + constexpr int slotOccupied = static_cast(0x80290010u); + VideoOutBuffers again{storage.data(), nullptr, {nullptr, nullptr}}; + check(sceVideoOutRegisterBuffers2(handle, 0, 0, &again, 1, &attribute, 0, nullptr) == slotOccupied, "a registration over a registered set and its buffers did not fail with SLOT_OCCUPIED"); + { + std::lock_guard lock(cfg->mutex); + check(cfg->buffers[0].groupIndex == 0 && cfg->groups[0].occupied, "a refused registration released the registered set"); + } check(sceVideoOutUnregisterBuffers(handle, 0) == 0, "buffer unregistration failed"); check(owner->GetTriggeredEvents(&event, 1) == 0, "VRR status event triggered without a VRR change"); KernelEvent vrrStatus{}; @@ -538,7 +545,7 @@ void testControls() { } check(sceVideoOutDeleteVrrActiveStatusEvent(queue, handle) == 0, "duplicate VRR status unsubscription failed"); expectFailure([&] { sceVideoOutDeleteVrrActiveStatusEvent(0, handle); }); - expectFailure([&] { sceVideoOutDeleteVrrActiveStatusEvent(queue, -1); }); + check(sceVideoOutDeleteVrrActiveStatusEvent(queue, -1) == VIDEO_OUT_ERROR_INVALID_HANDLE, "VRR status unsubscription on an invalid handle was accepted"); expectFailure([&] { sceVideoOutVrrPegToFixedRate(handle, 1, 0); }); expectFailure([&] { sceVideoOutVrrPegToFixedRate(handle, 0, 1); }); expectFailure([&] { sceVideoOutVrrPegToFixedRate(handle, 0x100000000ull, 0); }); @@ -1020,6 +1027,8 @@ void testUnopenedEventRegistration() { check(sceKernelCreateEqueue(&eq, "unopened") == 0, "the event queue was not created"); check(sceVideoOutAddFlipEvent(eq, 0, nullptr) == VIDEO_OUT_ERROR_INVALID_HANDLE, "flip event on an unopened port was accepted"); check(sceVideoOutAddVblankEvent(eq, 1, nullptr) == VIDEO_OUT_ERROR_INVALID_HANDLE, "vblank event on an unopened port was accepted"); + check(sceVideoOutDeleteFlipEvent(eq, 0) == VIDEO_OUT_ERROR_INVALID_HANDLE, "flip event deletion on an unopened port was accepted"); + check(sceVideoOutDeleteVblankEvent(eq, 1) == VIDEO_OUT_ERROR_INVALID_HANDLE, "vblank event deletion on an unopened port was accepted"); const int handle = sceVideoOutOpen(255, 0, 0, nullptr); check(sceVideoOutAddFlipEvent(eq, handle, nullptr) == 0, "flip event on an open port was refused"); check(sceVideoOutAddVblankEvent(eq, handle, nullptr) == 0, "vblank event on an open port was refused"); @@ -1027,6 +1036,8 @@ void testUnopenedEventRegistration() { check(sceVideoOutDeleteVblankEvent(eq, handle) == 0, "registered vblank event was not deleted"); sceVideoOutClose(handle); check(sceVideoOutAddFlipEvent(eq, handle, nullptr) == VIDEO_OUT_ERROR_INVALID_HANDLE, "flip event on a closed port was accepted"); + check(sceVideoOutDeleteFlipEvent(eq, handle) == VIDEO_OUT_ERROR_INVALID_HANDLE, "flip event deletion on a closed port was accepted"); + check(sceVideoOutDeleteVblankEvent(eq, handle) == VIDEO_OUT_ERROR_INVALID_HANDLE, "vblank event deletion on a closed port was accepted"); check(sceKernelDeleteEqueue(eq) == 0, "the event queue was not deleted"); LibcRunShutdown_nid_postfix(); } diff --git a/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp b/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp index 0a1427e747..7353409c3e 100644 --- a/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp +++ b/core/libs/prx/libSceVideoOut/tests/MouseInput.cpp @@ -216,6 +216,70 @@ int main() { input.HandleEvent(Motion(3, 3, 3), 3); Sum(handle, x, y); Require(x == 6 && y == 6, "an empty presentation changed the scale"); + + { + SDL_Event leftDown{}; + leftDown.type = SDL_MOUSEBUTTONDOWN; + leftDown.button.windowID = 3; + leftDown.button.button = SDL_BUTTON_LEFT; + leftDown.button.state = SDL_PRESSED; + input.HandleEvent(leftDown, 3); + MouseData data{}; + int read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 1, "left mouse button should use guest mouse bit 0"); + + SDL_Event leftUp{}; + leftUp.type = SDL_MOUSEBUTTONUP; + leftUp.button.windowID = 3; + leftUp.button.button = SDL_BUTTON_LEFT; + leftUp.button.state = SDL_RELEASED; + input.HandleEvent(leftUp, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 0, "left mouse button up should clear guest mouse bit 0"); + + SDL_Event rightDown{}; + rightDown.type = SDL_MOUSEBUTTONDOWN; + rightDown.button.windowID = 3; + rightDown.button.button = SDL_BUTTON_RIGHT; + rightDown.button.state = SDL_PRESSED; + input.HandleEvent(rightDown, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 2, "right mouse button should use guest mouse bit 1"); + + SDL_Event rightUp{}; + rightUp.type = SDL_MOUSEBUTTONUP; + rightUp.button.windowID = 3; + rightUp.button.button = SDL_BUTTON_RIGHT; + rightUp.button.state = SDL_RELEASED; + input.HandleEvent(rightUp, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 0, "right mouse button up should clear guest mouse bit 1"); + + SDL_Event middleDown{}; + middleDown.type = SDL_MOUSEBUTTONDOWN; + middleDown.button.windowID = 3; + middleDown.button.button = SDL_BUTTON_MIDDLE; + middleDown.button.state = SDL_PRESSED; + input.HandleEvent(middleDown, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 4, "middle mouse button should use guest mouse bit 2"); + + SDL_Event middleUp{}; + middleUp.type = SDL_MOUSEBUTTONUP; + middleUp.button.windowID = 3; + middleUp.button.button = SDL_BUTTON_MIDDLE; + middleUp.button.state = SDL_RELEASED; + input.HandleEvent(middleUp, 3); + read = sceMouseRead(handle, &data, 1); + Require(read == 1, "failed to read mouse data"); + Require(data.buttons == 0, "middle mouse button up should clear guest mouse bit 2"); + } + DialogMotionAndWheel(input, handle); DialogButtonPress(input, handle); DialogButtonRelease(input, handle); diff --git a/core/libs/prx/libSceVideoOut/tests/PadIdentify.cpp b/core/libs/prx/libSceVideoOut/tests/PadIdentify.cpp index cd07bf2cf7..c518e89a07 100644 --- a/core/libs/prx/libSceVideoOut/tests/PadIdentify.cpp +++ b/core/libs/prx/libSceVideoOut/tests/PadIdentify.cpp @@ -97,25 +97,27 @@ int Run(const char* mode) { if (std::strcmp(mode, "rumble") == 0) Pad::SetVibration(0, 200, 200); const auto bound = Clock::now(); DisplayWindow window; - PadInput padInput; - Require(padInput.SlotOccupied(0), "the virtual controller takes slot 0"); + { + PadInput padInput; + Require(padInput.SlotOccupied(0), "the virtual controller takes slot 0"); - if (std::strcmp(mode, "rumble") == 0) { - Pump(padInput, window, 1500); - Require(pulseEdges.empty(), "no identify pulse is sent while the title is rumbling"); - Require(!titleEdges.empty(), "the title's own rumble reaches the controller"); - Pad::SetVibration(0, 0, 0); - Pump(padInput, window, 1500); - Require(pulseEdges.size() == 1, "after the title stops, one pulse for player 1 is sent"); - Require(pulseEdges[0] - bound >= std::chrono::milliseconds(1500), "the pulse waits for the title to stay quiet"); - } else { - Pump(padInput, window, 3000); - if (std::strcmp(mode, "off") == 0) { - Require(pulseEdges.empty() && titleEdges.empty(), "off sends no pulse"); + if (std::strcmp(mode, "rumble") == 0) { + Pump(padInput, window, 1500); + Require(pulseEdges.empty(), "no identify pulse is sent while the title is rumbling"); + Require(!titleEdges.empty(), "the title's own rumble reaches the controller"); + Pad::SetVibration(0, 0, 0); + Pump(padInput, window, 1500); + Require(pulseEdges.size() == 1, "after the title stops, one pulse for player 1 is sent"); + Require(pulseEdges[0] - bound >= std::chrono::milliseconds(1500), "the pulse waits for the title to stay quiet"); } else { - Require(pulseEdges.size() == 1, "connect sends exactly one pulse for player 1"); - Require(pulseEdges[0] - bound >= std::chrono::milliseconds(450) && pulseEdges[0] - bound <= std::chrono::milliseconds(1500), - "the pulse follows the 500 ms quiet window"); + Pump(padInput, window, 3000); + if (std::strcmp(mode, "off") == 0) { + Require(pulseEdges.empty() && titleEdges.empty(), "off sends no pulse"); + } else { + Require(pulseEdges.size() == 1, "connect sends exactly one pulse for player 1"); + Require(pulseEdges[0] - bound >= std::chrono::milliseconds(450) && pulseEdges[0] - bound <= std::chrono::milliseconds(1500), + "the pulse follows the 500 ms quiet window"); + } } } diff --git a/core/libs/prx/libSceVideoRecording.native/Export.cpp b/core/libs/prx/libSceVideoRecording.native/Export.cpp index fb34206c21..5c5f43ab1c 100644 --- a/core/libs/prx/libSceVideoRecording.native/Export.cpp +++ b/core/libs/prx/libSceVideoRecording.native/Export.cpp @@ -1,3 +1,5 @@ +#include "prx/libc/include/general/ExportMacros.hpp" +#include "prx/libSceVideoRecording.native/include/InventoryExports.hpp" #include #include #include "SceTypes.hpp" @@ -8,3 +10,118 @@ extern "C" { APS5_DUMMY_FUN } + +extern "C" { + +int APS5_VABI sceVideoRecordingCopyBGRAtoP010() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingDisableScanInEncode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingEnableScanInEncode() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingFinalizeByAppId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingFinalizeByHandle() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingGetCountScanInAction() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingGetFinalizeActiveInfo() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingGetFormatByAppId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingGetFormatExtensionByAppId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingGetRecordedDurationByAppId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingGetRecordedDurationByHandle() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingGetStatusByAppId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingGetStatusByHandle() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingIsProfileChangeSupported() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingOpenByRAFeatureId() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingResetRecordedData() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingRestModeProcessStop() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingSetCopyrightImage() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingSetInfoByHandle() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingSetPrivacyImage() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingSetProfileLimit() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingSetResourceStateByHandle() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingStartByHandle() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVideoRecordingStopByHandle() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI libSceVideoRecording_nativeUnknown_26XG0o9fx8Q_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("26XG0o9fx8Q", libSceVideoRecording_nativeUnknown_26XG0o9fx8Q_nid_no_patch); + +int APS5_VABI libSceVideoRecording_nativeUnknown_WT4aTfe5OYk_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("WT4aTfe5OYk", libSceVideoRecording_nativeUnknown_WT4aTfe5OYk_nid_no_patch); + +int APS5_VABI libSceVideoRecording_nativeUnknown_iYtbtBx4jvE_nid_no_patch() { + NotImplemented_nid_no_patch(__func__); +} +APS5_EXPORT("iYtbtBx4jvE", libSceVideoRecording_nativeUnknown_iYtbtBx4jvE_nid_no_patch); + +} diff --git a/core/libs/prx/libSceVideoRecording.native/include/InventoryExports.hpp b/core/libs/prx/libSceVideoRecording.native/include/InventoryExports.hpp new file mode 100644 index 0000000000..64ccb00e35 --- /dev/null +++ b/core/libs/prx/libSceVideoRecording.native/include/InventoryExports.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVideoRecordingCopyBGRAtoP010(); +int APS5_VABI sceVideoRecordingDisableScanInEncode(); +int APS5_VABI sceVideoRecordingEnableScanInEncode(); +int APS5_VABI sceVideoRecordingFinalizeByAppId(); +int APS5_VABI sceVideoRecordingFinalizeByHandle(); +int APS5_VABI sceVideoRecordingGetCountScanInAction(); +int APS5_VABI sceVideoRecordingGetFinalizeActiveInfo(); +int APS5_VABI sceVideoRecordingGetFormatByAppId(); +int APS5_VABI sceVideoRecordingGetFormatExtensionByAppId(); +int APS5_VABI sceVideoRecordingGetRecordedDurationByAppId(); +int APS5_VABI sceVideoRecordingGetRecordedDurationByHandle(); +int APS5_VABI sceVideoRecordingGetStatusByAppId(); +int APS5_VABI sceVideoRecordingGetStatusByHandle(); +int APS5_VABI sceVideoRecordingIsProfileChangeSupported(); +int APS5_VABI sceVideoRecordingOpenByRAFeatureId(); +int APS5_VABI sceVideoRecordingResetRecordedData(); +int APS5_VABI sceVideoRecordingRestModeProcessStop(); +int APS5_VABI sceVideoRecordingSetCopyrightImage(); +int APS5_VABI sceVideoRecordingSetInfoByHandle(); +int APS5_VABI sceVideoRecordingSetPrivacyImage(); +int APS5_VABI sceVideoRecordingSetProfileLimit(); +int APS5_VABI sceVideoRecordingSetResourceStateByHandle(); +int APS5_VABI sceVideoRecordingStartByHandle(); +int APS5_VABI sceVideoRecordingStopByHandle(); +int APS5_VABI libSceVideoRecording_nativeUnknown_26XG0o9fx8Q_nid_no_patch(); +int APS5_VABI libSceVideoRecording_nativeUnknown_WT4aTfe5OYk_nid_no_patch(); +int APS5_VABI libSceVideoRecording_nativeUnknown_iYtbtBx4jvE_nid_no_patch(); + +} diff --git a/core/libs/prx/libSceVideoRecording.native/libSceVideoRecording.native.hpp b/core/libs/prx/libSceVideoRecording.native/libSceVideoRecording.native.hpp index 6f70f09bee..efb1b622fe 100644 --- a/core/libs/prx/libSceVideoRecording.native/libSceVideoRecording.native.hpp +++ b/core/libs/prx/libSceVideoRecording.native/libSceVideoRecording.native.hpp @@ -1 +1,2 @@ #pragma once +#include "prx/libSceVideoRecording.native/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceVideoRecordingP/Export.cpp b/core/libs/prx/libSceVideoRecordingP/Export.cpp index 7614cf388c..5dbff47173 100644 --- a/core/libs/prx/libSceVideoRecordingP/Export.cpp +++ b/core/libs/prx/libSceVideoRecordingP/Export.cpp @@ -10,9 +10,13 @@ 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(VideoRecordingParam) == 64); +static_assert(offsetof(VideoRecordingParam, duration) == 8); +static_assert(offsetof(VideoRecordingParam, mode) == 12); +static_assert(offsetof(VideoRecordingParam, affinityMask) == 16); +static_assert(offsetof(VideoRecordingParam, threadPriority) == 24); +static_assert(offsetof(VideoRecordingParam, option) == 32); +static_assert(offsetof(VideoRecordingParam, format) == 36); static_assert(sizeof(VideoRecordingParam2) == 40); static_assert(offsetof(VideoRecordingParam2, threadPriority) == 16); static_assert(offsetof(VideoRecordingParam2, duration) == 20); @@ -20,6 +24,38 @@ static_assert(offsetof(VideoRecordingParam2, format) == 36); namespace { constexpr int SceVideoRecordingStatusNone = 0; +constexpr int ErrorInvalidArgument = static_cast(0x80a80003); +constexpr int ErrorNotOpen = static_cast(0x80a80006); +constexpr int ErrorNoVideoFormat = static_cast(0x80a80008); +constexpr int MinimumHeapSize = 0x1000; +constexpr std::uint64_t MinimumParamSize = 0x10; +constexpr std::uint32_t MaximumDuration = 3600; + +bool IsVideoFormat(std::uint32_t format) { + switch (format) { + case 0x1: + case 0x2: + case 0x10: + case 0x11: + case 0x28: + case 0x40: + case 0x41: + return true; + default: + return false; + } +} + +int CheckParam(const VideoRecordingParam* param) { + if (param == nullptr || param->size < MinimumParamSize) return ErrorInvalidArgument; + if (param->duration == 0 || param->duration > MaximumDuration || param->mode > 1) return ErrorInvalidArgument; + const std::uint32_t option = param->size > offsetof(VideoRecordingParam, option) ? param->option : 0; + const std::uint32_t format = param->size > offsetof(VideoRecordingParam, format) ? param->format : 0; + if (option == 0 && format == 0) return MinimumHeapSize; + if (option > 1 || (format != 0 && !IsVideoFormat(format))) return ErrorInvalidArgument; + if (format == 0) return ErrorNoVideoFormat; + return 0; +} } extern "C" { @@ -31,8 +67,7 @@ void APS5_VABI _sceVideoRecordingQueryParam_nid_postfix(VideoRecordingQueryParam int APS5_VABI sceVideoRecordingClose(int discard) { (void)discard; - NotImplemented_nid_no_patch(__func__); - return 0; + return ErrorNotOpen; } int APS5_VABI sceVideoRecordingCloseByHandle(VideoRecordingContext* handle, int discard) { @@ -102,10 +137,10 @@ int APS5_VABI sceVideoRecordingInitialize(void* param) { } int APS5_VABI sceVideoRecordingOpen(const char* path, const VideoRecordingParam* param, void* heap, int heapSize) { - (void)path; - (void)param; - (void)heap; - (void)heapSize; + if (path == nullptr || param == nullptr || heap == nullptr || heapSize < 0) return ErrorInvalidArgument; + const int result = CheckParam(param); + if (result < 0) return result; + if (result == MinimumHeapSize && heapSize < MinimumHeapSize) return ErrorInvalidArgument; NotImplemented_nid_no_patch(__func__); return 0; } @@ -126,9 +161,9 @@ std::int64_t APS5_VABI sceVideoRecordingQueryHddSize2(const VideoRecordingParam2 } int APS5_VABI sceVideoRecordingQueryMemSize(const VideoRecordingParam* param) { - (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; + const int result = CheckParam(param); + if (result == 0) NotImplemented_nid_no_patch("sceVideoRecordingQueryMemSize (heap size for a video format)"); + return result; } int APS5_VABI sceVideoRecordingQueryMemSize2(const VideoRecordingParam2* param) { @@ -170,13 +205,11 @@ int APS5_VABI sceVideoRecordingSetInfo(std::int32_t info, const void* data, std: } int APS5_VABI sceVideoRecordingStart(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ErrorNotOpen; } int APS5_VABI sceVideoRecordingStop(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return ErrorNotOpen; } int APS5_VABI sceVideoRecordingTerm(void) { diff --git a/core/libs/prx/libSceVideoRecordingP/include/Api.hpp b/core/libs/prx/libSceVideoRecordingP/include/Api.hpp index e829728705..50d823b458 100644 --- a/core/libs/prx/libSceVideoRecordingP/include/Api.hpp +++ b/core/libs/prx/libSceVideoRecordingP/include/Api.hpp @@ -6,12 +6,16 @@ struct VideoRecordingContext; struct VideoRecordingParam { std::uint64_t size; - std::int32_t threadPriority; std::uint32_t duration; std::uint32_t mode; + std::uint64_t affinityMask; + std::int32_t threadPriority; std::int32_t outputMode; std::uint32_t option; std::uint32_t format; + std::uint32_t reserved40; + std::uint32_t reserved44; + std::uint8_t reserved48[16]; }; struct VideoRecordingParam2 { diff --git a/core/libs/prx/libSceVideodec2/CMakeLists.txt b/core/libs/prx/libSceVideodec2/CMakeLists.txt index 0c8eaed6fc..05974f0d3b 100644 --- a/core/libs/prx/libSceVideodec2/CMakeLists.txt +++ b/core/libs/prx/libSceVideodec2/CMakeLists.txt @@ -33,6 +33,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_videodec2_hevc_aliases_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideodec2HevcAliases.cpp) + target_include_directories(guest_videodec2_hevc_aliases_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_videodec2_hevc_aliases_tests PRIVATE libSceVideodec2 libc) + configure_windows_unwind(guest_videodec2_hevc_aliases_tests) + add_test(NAME guest_videodec2_hevc_aliases COMMAND guest_videodec2_hevc_aliases_tests) + set_tests_properties(guest_videodec2_hevc_aliases PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_videodec2_hevc_aliases PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_videodec2_unimplemented_tests ${LIBS_INCLUDE_DIR}/tests/GuestVideodec2Unimplemented.cpp) target_include_directories(guest_videodec2_unimplemented_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_videodec2_unimplemented_tests PRIVATE libSceVideodec2 libc) diff --git a/core/libs/prx/libSceVideodec2/Export.cpp b/core/libs/prx/libSceVideodec2/Export.cpp index a0ca268ea6..703b2fdc66 100644 --- a/core/libs/prx/libSceVideodec2/Export.cpp +++ b/core/libs/prx/libSceVideodec2/Export.cpp @@ -525,6 +525,14 @@ int APS5_VABI sceVideodec2CreateDecoder_nid_postfix(const DecoderConfigInfo* con return 0; } +int APS5_VABI sceVideodec2CreateHevcDecoder(const DecoderConfigInfo* config, const DecoderMemoryInfo* memory, std::uint64_t* handle) { + return sceVideodec2CreateDecoder_nid_postfix(config, memory, handle); +} + +int APS5_VABI sceVideodec2QueryHevcDecoderMemoryInfo(const DecoderConfigInfo* config, DecoderMemoryInfo* memory) { + return sceVideodec2QueryDecoderMemoryInfo_nid_postfix(config, memory); +} + int APS5_VABI sceVideodec2DeleteDecoder_nid_postfix(std::uint64_t handle) { std::lock_guard guard(lock); if (decoders.erase(handle) == 0) throw std::runtime_error("Videodec2: invalid decoder handle"); diff --git a/core/libs/prx/libSceVideodec2/Unimplemented.cpp b/core/libs/prx/libSceVideodec2/Unimplemented.cpp index dfe81fd3de..7cc8ced6e4 100644 --- a/core/libs/prx/libSceVideodec2/Unimplemented.cpp +++ b/core/libs/prx/libSceVideodec2/Unimplemented.cpp @@ -44,14 +44,6 @@ int APS5_VABI sceVideodec2CreateDecoderBid() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI sceVideodec2CreateHevcDecoder() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceVideodec2QueryHevcDecoderMemoryInfo() { - 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 index 0a24fc2b8b..3cb06dc1d3 100644 --- a/core/libs/prx/libSceVideodec2/include/Api.hpp +++ b/core/libs/prx/libSceVideodec2/include/Api.hpp @@ -285,6 +285,8 @@ int APS5_VABI sceVideodec2AllocateComputeQueue_nid_postfix(const SceVideodec2::C 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 sceVideodec2CreateHevcDecoder(const SceVideodec2::DecoderConfigInfo* config, const SceVideodec2::DecoderMemoryInfo* memory, std::uint64_t* handle); +int APS5_VABI sceVideodec2QueryHevcDecoderMemoryInfo(const SceVideodec2::DecoderConfigInfo* config, SceVideodec2::DecoderMemoryInfo* memory); 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); @@ -302,8 +304,6 @@ 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_nid_postfix(const SceVideodec2::OutputInfo* output, SceVideodec2::HevcPictureInfo* info); int APS5_VABI sceVideodec2InitLibrary(); diff --git a/core/libs/prx/libSceVoice/Export.cpp b/core/libs/prx/libSceVoice/Export.cpp index 9be3741a78..68f8712bd9 100644 --- a/core/libs/prx/libSceVoice/Export.cpp +++ b/core/libs/prx/libSceVoice/Export.cpp @@ -282,7 +282,8 @@ int APS5_VABI sceVoiceGetPortInfo(uint32_t port_id, VoicePortInfo* info) { auto& voice = State(); std::lock_guard lock(voice.mutex); if (!voice.initialized) return VoiceErrorNotInitialized; - if (info == nullptr) APS5_INVALID_ARG_EX; + if (port_id > 127) return InvalidPort; + if (info == nullptr) return InvalidArgument; const auto& port = RequirePort(voice, port_id, __func__); info->port_type = port.type; info->state = PortStateReady; @@ -320,13 +321,19 @@ int APS5_VABI sceVoiceGetPortAttr(std::uint32_t portId, std::int32_t attr, void* if (found == voice.ports.end()) return InvalidPort; const auto requiredType = attr == 1003 ? PortOutDevice : PortInDevice; if (found->second.type != requiredType) return InvalidArgument; + if (attr == 1001) { + *static_cast(value) = 0; + return 0; + } Fail(__func__, "device attribute backend is not implemented"); } int APS5_VABI sceVoiceReadFromOPort(uint32_t output_port_id, void* data, uint32_t* size) { - if (data == nullptr || size == nullptr) APS5_INVALID_ARG_EX; auto& voice = State(); std::lock_guard lock(voice.mutex); + if (!voice.initialized) return NotInitialized; + if (output_port_id > 127) return InvalidPort; + if (data == nullptr || size == nullptr) return InvalidArgument; if (IsInput(RequirePort(voice, output_port_id, __func__).type)) Fail(__func__, "port is not an output port"); *size = 0; return 0; diff --git a/core/libs/prx/libSceVoiceChat/Export.cpp b/core/libs/prx/libSceVoiceChat/Export.cpp index cb5daf8bbf..3035923d53 100644 --- a/core/libs/prx/libSceVoiceChat/Export.cpp +++ b/core/libs/prx/libSceVoiceChat/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceVoiceChat/include/InventoryExports.hpp" #include "prx/libSceVoiceChat/include/Api.hpp" #include #include @@ -34,6 +35,15 @@ int offlineRequest(const char* func) { extern "C" { +int APS5_VABI sceVoiceChatRegisterMicEventHandler2() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI sceVoiceChatUnregisterMicEventHandler2() { + NotImplemented_nid_no_patch(__func__); +} + + int APS5_VABI sceVoiceChatInitialize(void* param) { (void)param; return 0; } int APS5_VABI sceVoiceChatTerminate(void) { return 0; } int APS5_VABI sceVoiceChatRegisterHandlers(void* handlers, void* userArg) { (void)handlers; (void)userArg; return 0; } diff --git a/core/libs/prx/libSceVoiceChat/include/InventoryExports.hpp b/core/libs/prx/libSceVoiceChat/include/InventoryExports.hpp new file mode 100644 index 0000000000..46974b4d6e --- /dev/null +++ b/core/libs/prx/libSceVoiceChat/include/InventoryExports.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI sceVoiceChatRegisterMicEventHandler2(); +int APS5_VABI sceVoiceChatUnregisterMicEventHandler2(); + +} diff --git a/core/libs/prx/libSceVoiceChat/libSceVoiceChat.hpp b/core/libs/prx/libSceVoiceChat/libSceVoiceChat.hpp index 81d8245cc3..6e2f16d52e 100644 --- a/core/libs/prx/libSceVoiceChat/libSceVoiceChat.hpp +++ b/core/libs/prx/libSceVoiceChat/libSceVoiceChat.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceVoiceChat/include/Api.hpp" +#include "prx/libSceVoiceChat/include/InventoryExports.hpp" diff --git a/core/libs/prx/libSceVrTracker2/CMakeLists.txt b/core/libs/prx/libSceVrTracker2/CMakeLists.txt index cccd8e8455..b75b53ea64 100644 --- a/core/libs/prx/libSceVrTracker2/CMakeLists.txt +++ b/core/libs/prx/libSceVrTracker2/CMakeLists.txt @@ -13,4 +13,15 @@ if(BUILD_TESTING) set_tests_properties(guest_vr_tracker2 PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + + add_test_executable(guest_vr_tracker2_unnamed_tests tests/UninitializedNamedAnswers.cpp) + target_include_directories(guest_vr_tracker2_unnamed_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_vr_tracker2_unnamed_tests PRIVATE libSceVrTracker2 libkernel libc) + configure_windows_unwind(guest_vr_tracker2_unnamed_tests) + add_test(NAME guest_vr_tracker2_unnamed COMMAND guest_vr_tracker2_unnamed_tests) + set_tests_properties(guest_vr_tracker2_unnamed PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_vr_tracker2_unnamed PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp b/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp new file mode 100644 index 0000000000..552b1c4c02 --- /dev/null +++ b/core/libs/prx/libSceVrTracker2/tests/UninitializedNamedAnswers.cpp @@ -0,0 +1,67 @@ +#include "prx/libSceVrTracker2/include/Api.hpp" + +#include +#include +#include + +namespace { + +constexpr std::int32_t NotInitialized = static_cast(0x8A740001u); + +constexpr std::uint8_t Sentinel = 0x5a; +constexpr std::size_t ProbeSize = 16 * 1024; + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "FAIL: %s\n", what); + std::abort(); +} + +struct Probe { + unsigned char* bytes; + Probe() : bytes(new unsigned char[ProbeSize]) { + for (std::size_t index = 0; index < ProbeSize; ++index) bytes[index] = Sentinel; + } + ~Probe() { delete[] bytes; } + bool Untouched() const { + for (std::size_t index = 0; index < ProbeSize; ++index) { + if (bytes[index] != Sentinel) return false; + } + return true; + } +}; + +} + +int main() { + Probe output; + Probe parameter; + + Require(sceVrTracker2GetCoordinateSystem(reinterpret_cast(output.bytes)) == NotInitialized, + "get coordinate system before initialization"); + Require(output.Untouched(), "get coordinate system wrote nothing"); + + Require(sceVrTracker2GetPlayAreaBoundaryGeometry(reinterpret_cast(output.bytes)) == NotInitialized, + "get play area boundary geometry before initialization"); + Require(output.Untouched(), "get play area boundary geometry wrote nothing"); + + Require(sceVrTracker2GetPlayAreaOrientedBoundingBox(reinterpret_cast(output.bytes)) == NotInitialized, + "get play area oriented bounding box before initialization"); + Require(output.Untouched(), "get play area oriented bounding box wrote nothing"); + + Require(sceVrTracker2GetResult(reinterpret_cast(parameter.bytes), + reinterpret_cast(output.bytes)) == NotInitialized, + "get result before initialization"); + Require(parameter.Untouched(), "get result wrote nothing to the parameter"); + Require(output.Untouched(), "get result wrote nothing to the result"); + + Require(sceVrTracker2LocateCoordinateSystem(1, reinterpret_cast(output.bytes)) == NotInitialized, + "locate coordinate system before initialization"); + Require(output.Untouched(), "locate coordinate system wrote nothing"); + + Require(sceVrTracker2RegisterDevice(1, 2) == NotInitialized, "register device before initialization"); + Require(sceVrTracker2UnregisterDevice(2) == NotInitialized, "unregister device before initialization"); + Require(sceVrTracker2SetCoordinateSystem(1) == NotInitialized, "set coordinate system before initialization"); + Require(sceVrTracker2ResetLocalCoordinate(2) == NotInitialized, "reset local coordinate before initialization"); + return 0; +} diff --git a/core/libs/prx/libSceWorkspace/Export.cpp b/core/libs/prx/libSceWorkspace/Export.cpp index d81f4a5e32..4d87aa9956 100644 --- a/core/libs/prx/libSceWorkspace/Export.cpp +++ b/core/libs/prx/libSceWorkspace/Export.cpp @@ -1,10 +1,99 @@ -#include +#include "prx/libSceWorkspace/include/Api.hpp" #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" extern "C" { -APS5_DUMMY_FUN +int APS5_VABI module_start_nid_postfix(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceWriteFile(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceFinishWrite(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceApplyUpdateForFiles(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceApplyUpdateForAllFiles(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceIsBlockedByDataTransfer(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceMirrorBarrier(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceGetNumberOfPendingUpdates(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI module_stop_nid_postfix(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceUnmount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceApplyUpdate(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceDeletePath(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceMount(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceGetName(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceCreateFile(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceApplyUpdateForDirectory(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceIsUpdatePending(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceWorkspaceGetPendingUpdateEntries(void) { + NotImplemented_nid_no_patch(__func__); + return 0; +} } diff --git a/core/libs/prx/libSceWorkspace/include/Api.hpp b/core/libs/prx/libSceWorkspace/include/Api.hpp new file mode 100644 index 0000000000..1aa711e90d --- /dev/null +++ b/core/libs/prx/libSceWorkspace/include/Api.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI module_start_nid_postfix(void); +int APS5_VABI sceWorkspaceWriteFile(void); +int APS5_VABI sceWorkspaceFinishWrite(void); +int APS5_VABI sceWorkspaceApplyUpdateForFiles(void); +int APS5_VABI sceWorkspaceApplyUpdateForAllFiles(void); +int APS5_VABI sceWorkspaceIsBlockedByDataTransfer(void); +int APS5_VABI sceWorkspaceMirrorBarrier(void); +int APS5_VABI sceWorkspaceGetNumberOfPendingUpdates(void); +int APS5_VABI module_stop_nid_postfix(void); +int APS5_VABI sceWorkspaceUnmount(void); +int APS5_VABI sceWorkspaceApplyUpdate(void); +int APS5_VABI sceWorkspaceDeletePath(void); +int APS5_VABI sceWorkspaceMount(void); +int APS5_VABI sceWorkspaceGetName(void); +int APS5_VABI sceWorkspaceCreateFile(void); +int APS5_VABI sceWorkspaceApplyUpdateForDirectory(void); +int APS5_VABI sceWorkspaceIsUpdatePending(void); +int APS5_VABI sceWorkspaceGetPendingUpdateEntries(void); + +} diff --git a/core/libs/prx/libSceWorkspace/libSceWorkspace.hpp b/core/libs/prx/libSceWorkspace/libSceWorkspace.hpp index 6f70f09bee..9b189a41b3 100644 --- a/core/libs/prx/libSceWorkspace/libSceWorkspace.hpp +++ b/core/libs/prx/libSceWorkspace/libSceWorkspace.hpp @@ -1 +1,3 @@ #pragma once + +#include "prx/libSceWorkspace/include/Api.hpp" diff --git a/core/libs/prx/libSceXml/Export.cpp b/core/libs/prx/libSceXml/Export.cpp index 9515d73682..d83d703932 100644 --- a/core/libs/prx/libSceXml/Export.cpp +++ b/core/libs/prx/libSceXml/Export.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceXml/include/InventoryExports.hpp" #include "prx/libSceXml/include/Api.hpp" #include #include @@ -653,3 +654,139 @@ int APS5_VABI _ZNK3sce3Xml3Dom8Document10getSiblingENS1_6NodeIdE() { } } + +extern "C" { + +int APS5_VABI _ZNK3sce3Xml3Dom8Document11getNodeTypeENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document12getLastChildENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document7getTextENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document13getChildNodesENS1_6NodeIdEPNS1_8NodeListE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document11getNodeNameENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document20getElementsByTagNameENS1_6NodeIdEPKNS0_6StringEPNS1_8NodeListE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document13getEntityTypeENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml4Attr7getNameEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList8findItemENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document13getAttributesENS1_6NodeIdEPNS1_8NodeListE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document13getFirstChildENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList4itemEj() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml4Attr8getValueEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document11getAttrNameENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8NodeListixEj() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document9getStatusEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document14getSkippedTextENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList8findItemEPKNS0_6StringE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document9getEntityENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document11getNextAttrENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document12getAttrValueENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document12getFirstAttrENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList11isAvailableEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document7getRootEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document12getAttributeENS1_6NodeIdEPKNS0_6StringE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document10getXmlMetaEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document11isAvailableEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document9getParentENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList9getLengthEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document10isReadOnlyEv() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document13hasChildNodesENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml3Dom8Document13hasAttributesENS1_6NodeIdE() { + NotImplemented_nid_no_patch(__func__); +} + +int APS5_VABI _ZNK3sce3Xml4Attr11isAvailableEv() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libSceXml/include/InventoryExports.hpp b/core/libs/prx/libSceXml/include/InventoryExports.hpp new file mode 100644 index 0000000000..8cf56b51e7 --- /dev/null +++ b/core/libs/prx/libSceXml/include/InventoryExports.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI _ZNK3sce3Xml3Dom8Document11getNodeTypeENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document12getLastChildENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document7getTextENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document13getChildNodesENS1_6NodeIdEPNS1_8NodeListE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document11getNodeNameENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document20getElementsByTagNameENS1_6NodeIdEPKNS0_6StringEPNS1_8NodeListE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document13getEntityTypeENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml4Attr7getNameEv(); +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList8findItemENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document13getAttributesENS1_6NodeIdEPNS1_8NodeListE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document13getFirstChildENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList4itemEj(); +int APS5_VABI _ZNK3sce3Xml4Attr8getValueEv(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document11getAttrNameENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8NodeListixEj(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document9getStatusEv(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document14getSkippedTextENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList8findItemEPKNS0_6StringE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document9getEntityENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document11getNextAttrENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document12getAttrValueENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document12getFirstAttrENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList11isAvailableEv(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document7getRootEv(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document12getAttributeENS1_6NodeIdEPKNS0_6StringE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document10getXmlMetaEv(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document11isAvailableEv(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document9getParentENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8NodeList9getLengthEv(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document10isReadOnlyEv(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document13hasChildNodesENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml3Dom8Document13hasAttributesENS1_6NodeIdE(); +int APS5_VABI _ZNK3sce3Xml4Attr11isAvailableEv(); + +} diff --git a/core/libs/prx/libSceXml/libSceXml.hpp b/core/libs/prx/libSceXml/libSceXml.hpp index 02d5a6c3d7..832b8173ac 100644 --- a/core/libs/prx/libSceXml/libSceXml.hpp +++ b/core/libs/prx/libSceXml/libSceXml.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libSceXml/include/Api.hpp" +#include "prx/libSceXml/include/InventoryExports.hpp" diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 90509b697a..717cffac77 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -33,6 +33,17 @@ if(BUILD_TESTING) set_tests_properties(guest_stream_purge PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_bsearch_packed_tests ${LIBS_INCLUDE_DIR}/tests/GuestBsearchPacked.cpp) + target_include_directories(guest_bsearch_packed_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_bsearch_packed_tests PRIVATE libc) + configure_windows_unwind(guest_bsearch_packed_tests) + add_test(NAME guest_bsearch_packed COMMAND guest_bsearch_packed_tests) + set_tests_properties(guest_bsearch_packed PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_bsearch_packed PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_safe_error_tests ${LIBS_INCLUDE_DIR}/tests/GuestSafeErrors.cpp) target_include_directories(guest_safe_error_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_safe_error_tests PRIVATE libc) @@ -55,6 +66,19 @@ if(BUILD_TESTING) endif() endif() +if(BUILD_TESTING) + add_test_executable(guest_bounded_search_guards_tests ${LIBS_INCLUDE_DIR}/tests/GuestBoundedSearchGuards.cpp) + target_include_directories(guest_bounded_search_guards_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_bounded_search_guards_tests PRIVATE libc) + configure_windows_unwind(guest_bounded_search_guards_tests) + add_test(NAME guest_bounded_search_guards COMMAND guest_bounded_search_guards_tests) + set_tests_properties(guest_bounded_search_guards PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_bounded_search_guards PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/General.cpp @@ -116,9 +140,12 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/LocaleSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Process.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Hostname.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/DeviceName.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Environment.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Directory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Filesystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/VersionedFilesystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/VersionedFilesystemCalls.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Rename.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Errors.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Syslog.cpp @@ -134,6 +161,20 @@ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/src/MathExact.cpp TARGET_DIRECTORY libc PROPERTIES COMPILE_OPTIONS "-frounding-math") target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/BinaryWordIo.cpp) +target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/LegacySignals.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/SigvecExport.cpp) + +if(BUILD_TESTING) + add_test_executable(guest_sigvec_tests ${LIBS_INCLUDE_DIR}/tests/GuestSigvec.cpp) + target_include_directories(guest_sigvec_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sigvec_tests PRIVATE libc libkernel) + configure_windows_unwind(guest_sigvec_tests) + add_test(NAME guest_sigvec COMMAND guest_sigvec_tests) + set_tests_properties(guest_sigvec PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_sigvec PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() target_include_directories(libc PRIVATE ${LIBS_INCLUDE_DIR} @@ -154,6 +195,16 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") endif() if(BUILD_TESTING) + add_test_executable(guest_memcmp_protected_tests ${LIBS_INCLUDE_DIR}/tests/GuestMemcmpProtected.cpp) + target_include_directories(guest_memcmp_protected_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_memcmp_protected_tests PRIVATE libc) + configure_windows_unwind(guest_memcmp_protected_tests) + add_test(NAME guest_memcmp_protected COMMAND guest_memcmp_protected_tests) + set_tests_properties(guest_memcmp_protected PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_memcmp_protected PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() 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}) @@ -178,6 +229,16 @@ endif() target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/FloatingPoint.cpp) if(BUILD_TESTING) + add_test_executable(guest_memset_extent_tests ${LIBS_INCLUDE_DIR}/tests/GuestMemsetExtent.cpp) + target_include_directories(guest_memset_extent_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_memset_extent_tests PRIVATE libc) + configure_windows_unwind(guest_memset_extent_tests) + add_test(NAME guest_memset_extent COMMAND guest_memset_extent_tests) + set_tests_properties(guest_memset_extent PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_memset_extent PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_raise_except_tests ${LIBS_INCLUDE_DIR}/tests/GuestRaiseExcept.cpp) target_include_directories(guest_raise_except_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_raise_except_tests PRIVATE libc ${CMAKE_DL_LIBS}) @@ -230,6 +291,28 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endif() + + add_test_executable(guest_sce_v2_filesystem_tests ${LIBS_INCLUDE_DIR}/tests/GuestSceV2Filesystem.cpp) + target_include_directories(guest_sce_v2_filesystem_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sce_v2_filesystem_tests PRIVATE libc libkernel zarchive) + configure_windows_unwind(guest_sce_v2_filesystem_tests) + add_test(NAME guest_sce_v2_filesystem COMMAND guest_sce_v2_filesystem_tests) + set_tests_properties(guest_sce_v2_filesystem PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_sce_v2_filesystem PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + + add_test_executable(guest_sce_v2_filesystem_calls_tests ${LIBS_INCLUDE_DIR}/tests/GuestSceV2FilesystemCalls.cpp) + target_include_directories(guest_sce_v2_filesystem_calls_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sce_v2_filesystem_calls_tests PRIVATE libc libkernel zarchive) + configure_windows_unwind(guest_sce_v2_filesystem_calls_tests) + add_test(NAME guest_sce_v2_filesystem_calls COMMAND guest_sce_v2_filesystem_calls_tests) + set_tests_properties(guest_sce_v2_filesystem_calls PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_sce_v2_filesystem_calls PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() if(WIN32) @@ -273,6 +356,17 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_fwide_tests ${LIBS_INCLUDE_DIR}/tests/GuestFwide.cpp) + target_include_directories(guest_fwide_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_fwide_tests PRIVATE libc ${CMAKE_DL_LIBS}) + configure_windows_unwind(guest_fwide_tests) + add_test(NAME guest_fwide COMMAND guest_fwide_tests $ guest_fwide.tmp) + set_tests_properties(guest_fwide PROPERTIES TIMEOUT 10) + if(WIN32) + set_tests_properties(guest_fwide PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_memccpy_bounds_tests ${LIBS_INCLUDE_DIR}/tests/GuestMemccpyBounds.cpp) target_include_directories(guest_memccpy_bounds_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_memccpy_bounds_tests PRIVATE libc) @@ -283,6 +377,19 @@ if(BUILD_TESTING) set_tests_properties(guest_memccpy_bounds PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_explicit_memset_tests ${LIBS_INCLUDE_DIR}/tests/GuestExplicitMemset.cpp) + target_include_directories(guest_explicit_memset_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_explicit_memset_tests PRIVATE libc) + configure_windows_unwind(guest_explicit_memset_tests) + foreach(mode empty values ranges guard_pages) + add_test(NAME guest_explicit_memset_${mode} COMMAND guest_explicit_memset_tests ${mode}) + set_tests_properties(guest_explicit_memset_${mode} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_explicit_memset_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + add_test_executable(guest_allocating_strings_tests ${LIBS_INCLUDE_DIR}/tests/GuestAllocatingStrings.cpp) target_include_directories(guest_allocating_strings_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_allocating_strings_tests PRIVATE libc libkernel) @@ -316,6 +423,16 @@ if(BUILD_TESTING) set_tests_properties(guest_array_callbacks PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_strncmp_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrncmpBoundary.cpp) + target_include_directories(guest_strncmp_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_strncmp_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_strncmp_boundary_tests) + add_test(NAME guest_strncmp_boundary COMMAND guest_strncmp_boundary_tests) + set_tests_properties(guest_strncmp_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_strncmp_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_dynamic_cast_tests ${LIBS_INCLUDE_DIR}/tests/GuestDynamicCast.cpp) target_include_directories(guest_dynamic_cast_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_dynamic_cast_tests PRIVATE libc) @@ -345,6 +462,16 @@ if(BUILD_TESTING) set_tests_properties(guest_mktime PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_strcpy_extent_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrcpyExtent.cpp) + target_include_directories(guest_strcpy_extent_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_strcpy_extent_tests PRIVATE libc) + configure_windows_unwind(guest_strcpy_extent_tests) + add_test(NAME guest_strcpy_extent COMMAND guest_strcpy_extent_tests) + set_tests_properties(guest_strcpy_extent PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_strcpy_extent PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_time_return_tests ${LIBS_INCLUDE_DIR}/tests/GuestTimeReturn.cpp) target_include_directories(guest_time_return_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_time_return_tests PRIVATE libc) @@ -384,6 +511,16 @@ if(BUILD_TESTING) set_tests_properties(guest_scan_count PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_wcsncat_extent_tests ${LIBS_INCLUDE_DIR}/tests/GuestWcsncatExtent.cpp) + target_include_directories(guest_wcsncat_extent_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_wcsncat_extent_tests PRIVATE libc) + configure_windows_unwind(guest_wcsncat_extent_tests) + add_test(NAME guest_wcsncat_extent COMMAND guest_wcsncat_extent_tests) + set_tests_properties(guest_wcsncat_extent PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_wcsncat_extent PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_access_tests ${LIBS_INCLUDE_DIR}/tests/GuestAccess.cpp) target_include_directories(guest_access_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_access_tests PRIVATE libc) @@ -449,6 +586,16 @@ if(BUILD_TESTING) set_tests_properties(guest_multibyte_locale PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_span_protected_tests ${LIBS_INCLUDE_DIR}/tests/GuestSpanProtected.cpp) + target_include_directories(guest_span_protected_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_span_protected_tests PRIVATE libc) + configure_windows_unwind(guest_span_protected_tests) + add_test(NAME guest_span_protected COMMAND guest_span_protected_tests) + set_tests_properties(guest_span_protected PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_span_protected 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) @@ -482,6 +629,16 @@ if(BUILD_TESTING) set_tests_properties(guest_bounds_checking PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_timingsafe_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestTimingsafeBoundary.cpp) + target_include_directories(guest_timingsafe_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_timingsafe_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_timingsafe_boundary_tests) + add_test(NAME guest_timingsafe_boundary COMMAND guest_timingsafe_boundary_tests) + set_tests_properties(guest_timingsafe_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_timingsafe_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_zero_runs_tests ${LIBS_INCLUDE_DIR}/tests/GuestZeroRuns.cpp) target_include_directories(guest_zero_runs_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_zero_runs_tests PRIVATE libc) @@ -505,6 +662,16 @@ if(BUILD_TESTING) set_tests_properties(guest_float_builtins PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_wide_length_protected_tests ${LIBS_INCLUDE_DIR}/tests/GuestWideLengthProtected.cpp) + target_include_directories(guest_wide_length_protected_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_wide_length_protected_tests PRIVATE libc) + configure_windows_unwind(guest_wide_length_protected_tests) + add_test(NAME guest_wide_length_protected COMMAND guest_wide_length_protected_tests) + set_tests_properties(guest_wide_length_protected PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_wide_length_protected PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_character_tests ${LIBS_INCLUDE_DIR}/tests/GuestCharacter.cpp) target_include_directories(guest_character_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_character_tests PRIVATE libc) @@ -540,6 +707,15 @@ if(BUILD_TESTING) set_tests_properties(guest_math PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_dsign_tests ${LIBS_INCLUDE_DIR}/tests/GuestDsign.cpp) + target_include_directories(guest_dsign_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_dsign_tests PRIVATE libc) + configure_windows_unwind(guest_dsign_tests) + add_test(NAME guest_dsign COMMAND guest_dsign_tests) + if(WIN32) + set_tests_properties(guest_dsign PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_strtod_subnormal_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrtodSubnormal.cpp) target_include_directories(guest_strtod_subnormal_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_strtod_subnormal_tests PRIVATE libc) @@ -549,6 +725,16 @@ if(BUILD_TESTING) set_tests_properties(guest_strtod_subnormal PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_strnstr_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrnstrBoundary.cpp) + target_include_directories(guest_strnstr_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_strnstr_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_strnstr_boundary_tests) + add_test(NAME guest_strnstr_boundary COMMAND guest_strnstr_boundary_tests) + set_tests_properties(guest_strnstr_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_strnstr_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_random_state_tests ${LIBS_INCLUDE_DIR}/tests/GuestRandomState.cpp) target_include_directories(guest_random_state_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_random_state_tests PRIVATE libc) @@ -576,6 +762,16 @@ if(BUILD_TESTING) set_tests_properties(guest_random_device_seed PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_memcpy_protected_tests ${LIBS_INCLUDE_DIR}/tests/GuestMemcpyProtected.cpp) + target_include_directories(guest_memcpy_protected_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_memcpy_protected_tests PRIVATE libc) + configure_windows_unwind(guest_memcpy_protected_tests) + add_test(NAME guest_memcpy_protected COMMAND guest_memcpy_protected_tests) + set_tests_properties(guest_memcpy_protected PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_memcpy_protected PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_count_ones_tests ${LIBS_INCLUDE_DIR}/tests/GuestCountOnes.cpp) target_include_directories(guest_count_ones_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_count_ones_tests PRIVATE libc) @@ -588,6 +784,20 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + add_test_executable(guest_c23_bits_tests ${LIBS_INCLUDE_DIR}/tests/GuestC23Bits.cpp) + target_include_directories(guest_c23_bits_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_c23_bits_tests PRIVATE libc) + configure_windows_unwind(guest_c23_bits_tests) + foreach(operation first_leading_zero first_leading_one first_trailing_zero first_trailing_one has_single_bit bit_width bit_floor bit_ceil) + foreach(width uc us ui ul ull) + add_test(NAME guest_c23_bits_${operation}_${width} COMMAND guest_c23_bits_tests ${operation}_${width}) + set_tests_properties(guest_c23_bits_${operation}_${width} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_c23_bits_${operation}_${width} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() add_test_executable(guest_integer_builtins_tests ${LIBS_INCLUDE_DIR}/tests/GuestIntegerBuiltins.cpp) target_include_directories(guest_integer_builtins_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_integer_builtins_tests PRIVATE libc) @@ -609,6 +819,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() endforeach() + add_test_executable(guest_strcoll_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrcollBoundary.cpp) + target_include_directories(guest_strcoll_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_strcoll_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_strcoll_boundary_tests) + add_test(NAME guest_strcoll_boundary COMMAND guest_strcoll_boundary_tests) + set_tests_properties(guest_strcoll_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_strcoll_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_wide_stream_scan_tests ${LIBS_INCLUDE_DIR}/tests/GuestWideStreamScan.cpp) target_include_directories(guest_wide_stream_scan_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_wide_stream_scan_tests PRIVATE libc) @@ -637,6 +857,16 @@ if(BUILD_TESTING) set_tests_properties(guest_execute_once PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_wide_token_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestWideTokenBoundary.cpp) + target_include_directories(guest_wide_token_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_wide_token_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_wide_token_boundary_tests) + add_test(NAME guest_wide_token_boundary COMMAND guest_wide_token_boundary_tests) + set_tests_properties(guest_wide_token_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_wide_token_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_std_execute_once_tests ${LIBS_INCLUDE_DIR}/tests/GuestStdExecuteOnce.cpp) target_include_directories(guest_std_execute_once_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_std_execute_once_tests PRIVATE libc) @@ -668,6 +898,24 @@ if(BUILD_TESTING) set_tests_properties(guest_math_exact PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_dinkumware_float_bits_tests ${LIBS_INCLUDE_DIR}/tests/GuestDinkumwareFloatBits.cpp) + target_include_directories(guest_dinkumware_float_bits_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_dinkumware_float_bits_tests PRIVATE libc) + configure_windows_unwind(guest_dinkumware_float_bits_tests) + add_test(NAME guest_dinkumware_float_bits COMMAND guest_dinkumware_float_bits_tests) + if(WIN32) + set_tests_properties(guest_dinkumware_float_bits PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_sce_v2_float_helpers_tests ${LIBS_INCLUDE_DIR}/tests/GuestSceV2FloatHelpers.cpp) + target_include_directories(guest_sce_v2_float_helpers_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sce_v2_float_helpers_tests PRIVATE libc) + configure_windows_unwind(guest_sce_v2_float_helpers_tests) + add_test(NAME guest_sce_v2_float_helpers COMMAND guest_sce_v2_float_helpers_tests) + if(WIN32) + set_tests_properties(guest_sce_v2_float_helpers PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_bessel_tests ${LIBS_INCLUDE_DIR}/tests/GuestBessel.cpp) target_include_directories(guest_bessel_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_bessel_tests PRIVATE libc) @@ -677,6 +925,16 @@ if(BUILD_TESTING) set_tests_properties(guest_bessel PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_wcslen_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestWcslenBoundary.cpp) + target_include_directories(guest_wcslen_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_wcslen_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_wcslen_boundary_tests) + add_test(NAME guest_wcslen_boundary COMMAND guest_wcslen_boundary_tests) + set_tests_properties(guest_wcslen_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_wcslen_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_libc_threads_tests ${LIBS_INCLUDE_DIR}/tests/GuestLibcThreads.cpp) target_include_directories(guest_libc_threads_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libc_threads_tests PRIVATE libc libkernel) @@ -687,6 +945,16 @@ if(BUILD_TESTING) set_tests_properties(guest_libc_threads PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_cnd_at_thread_exit_tests ${LIBS_INCLUDE_DIR}/tests/GuestCndAtThreadExit.cpp) + target_include_directories(guest_cnd_at_thread_exit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_cnd_at_thread_exit_tests PRIVATE libc) + configure_windows_unwind(guest_cnd_at_thread_exit_tests) + add_test(NAME guest_cnd_at_thread_exit COMMAND guest_cnd_at_thread_exit_tests) + set_tests_properties(guest_cnd_at_thread_exit PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_cnd_at_thread_exit 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 libkernel) @@ -707,6 +975,16 @@ if(BUILD_TESTING) set_tests_properties(guest_libc_static_imports PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_wcscmp_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestWcscmpBoundary.cpp) + target_include_directories(guest_wcscmp_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_wcscmp_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_wcscmp_boundary_tests) + add_test(NAME guest_wcscmp_boundary COMMAND guest_wcscmp_boundary_tests) + set_tests_properties(guest_wcscmp_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_wcscmp_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_fiopen_tests ${LIBS_INCLUDE_DIR}/tests/GuestFiopen.cpp) target_include_directories(guest_fiopen_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_fiopen_tests PRIVATE libc) @@ -736,6 +1014,16 @@ if(BUILD_TESTING) set_tests_properties(guest_realpath PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_strxfrm_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrxfrmBoundary.cpp) + target_include_directories(guest_strxfrm_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_strxfrm_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_strxfrm_boundary_tests) + add_test(NAME guest_strxfrm_boundary COMMAND guest_strxfrm_boundary_tests) + set_tests_properties(guest_strxfrm_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_strxfrm_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_path_case_tests ${LIBS_INCLUDE_DIR}/tests/GuestPathCase.cpp) target_include_directories(guest_path_case_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_path_case_tests PRIVATE libc libkernel) @@ -764,6 +1052,16 @@ if(BUILD_TESTING) set_tests_properties(guest_directory PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_decimal_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestDecimalBoundary.cpp) + target_include_directories(guest_decimal_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_decimal_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_decimal_boundary_tests) + add_test(NAME guest_decimal_boundary COMMAND guest_decimal_boundary_tests) + set_tests_properties(guest_decimal_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_decimal_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_environment_tests ${LIBS_INCLUDE_DIR}/tests/GuestEnvironment.cpp) target_include_directories(guest_environment_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_environment_tests PRIVATE libc) @@ -782,6 +1080,16 @@ if(BUILD_TESTING) set_tests_properties(guest_dinkumware_atomics PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_dinkumware_division_tests ${LIBS_INCLUDE_DIR}/tests/GuestDinkumwareDivision.cpp) + target_include_directories(guest_dinkumware_division_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_dinkumware_division_tests PRIVATE libc) + configure_windows_unwind(guest_dinkumware_division_tests) + add_test(NAME guest_dinkumware_division COMMAND guest_dinkumware_division_tests) + if(MINGW) + set_tests_properties(guest_dinkumware_division 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) @@ -791,6 +1099,16 @@ if(BUILD_TESTING) set_tests_properties(guest_dinkumware_internals PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_device_name_tests ${LIBS_INCLUDE_DIR}/tests/GuestDeviceName.cpp) + target_include_directories(guest_device_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_device_name_tests PRIVATE libc) + configure_windows_unwind(guest_device_name_tests) + add_test(NAME guest_device_name COMMAND guest_device_name_tests) + set_tests_properties(guest_device_name PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_device_name 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) @@ -810,6 +1128,20 @@ if(BUILD_TESTING) set_tests_properties(guest_vector_algorithms PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_find_last_trivial_tests ${LIBS_INCLUDE_DIR}/tests/GuestFindLastTrivial.cpp) + target_include_directories(guest_find_last_trivial_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_find_last_trivial_tests PRIVATE libc) + configure_windows_unwind(guest_find_last_trivial_tests) + foreach(width 1 2 4 8) + foreach(mode empty positions values guard_pages) + add_test(NAME guest_find_last_trivial_${width}_${mode} COMMAND guest_find_last_trivial_tests ${width} ${mode}) + set_tests_properties(guest_find_last_trivial_${width}_${mode} PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_find_last_trivial_${width}_${mode} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() + endforeach() add_test_executable(guest_memrchr_tests ${LIBS_INCLUDE_DIR}/tests/GuestMemrchr.cpp) target_include_directories(guest_memrchr_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_memrchr_tests PRIVATE libc) @@ -866,6 +1198,15 @@ if(BUILD_TESTING) set_tests_properties(guest_interfaces PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_sce_v2_binary_scaling_tests ${LIBS_INCLUDE_DIR}/tests/GuestSceV2BinaryScaling.cpp) + target_include_directories(guest_sce_v2_binary_scaling_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sce_v2_binary_scaling_tests PRIVATE libc) + configure_windows_unwind(guest_sce_v2_binary_scaling_tests) + add_test(NAME guest_sce_v2_binary_scaling COMMAND guest_sce_v2_binary_scaling_tests) + if(WIN32) + set_tests_properties(guest_sce_v2_binary_scaling PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_qsort_tests ${LIBS_INCLUDE_DIR}/tests/GuestQsort.cpp) target_include_directories(guest_qsort_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_qsort_tests PRIVATE libc) @@ -1080,6 +1421,16 @@ if(BUILD_TESTING) set_tests_properties(guest_new_handler_default_heap PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_bzero_extent_tests ${LIBS_INCLUDE_DIR}/tests/GuestBzeroExtent.cpp) + target_include_directories(guest_bzero_extent_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_bzero_extent_tests PRIVATE libc) + configure_windows_unwind(guest_bzero_extent_tests) + add_test(NAME guest_bzero_extent COMMAND guest_bzero_extent_tests) + set_tests_properties(guest_bzero_extent PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_bzero_extent PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_search_tests ${LIBS_INCLUDE_DIR}/tests/GuestSearch.cpp) target_include_directories(guest_search_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_search_tests PRIVATE libc) @@ -1089,6 +1440,16 @@ if(BUILD_TESTING) set_tests_properties(guest_search PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_wmemchr_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestWmemchrBoundary.cpp) + target_include_directories(guest_wmemchr_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_wmemchr_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_wmemchr_boundary_tests) + add_test(NAME guest_wmemchr_boundary COMMAND guest_wmemchr_boundary_tests) + set_tests_properties(guest_wmemchr_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_wmemchr_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_demangle_tests ${LIBS_INCLUDE_DIR}/tests/GuestDemangle.cpp) target_include_directories(guest_demangle_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_demangle_tests PRIVATE libc) @@ -1333,6 +1694,16 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() + add_test_executable(guest_strcmp_boundary_tests ${LIBS_INCLUDE_DIR}/tests/GuestStrcmpBoundary.cpp) + target_include_directories(guest_strcmp_boundary_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_strcmp_boundary_tests PRIVATE libc) + configure_windows_unwind(guest_strcmp_boundary_tests) + add_test(NAME guest_strcmp_boundary COMMAND guest_strcmp_boundary_tests) + set_tests_properties(guest_strcmp_boundary PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_strcmp_boundary PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_heap_report_for_game_tests ${LIBS_INCLUDE_DIR}/tests/GuestHeapReportForGame.cpp) target_include_directories(guest_heap_report_for_game_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_heap_report_for_game_tests PRIVATE libc) @@ -1395,6 +1766,18 @@ if(BUILD_TESTING) endif() endif() +add_test_executable(guest_sce_v2_locale_tests EXCLUDE_FROM_ALL ${LIBS_INCLUDE_DIR}/tests/GuestSceV2Locale.cpp) +target_include_directories(guest_sce_v2_locale_tests PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(guest_sce_v2_locale_tests PRIVATE libc) +configure_windows_unwind(guest_sce_v2_locale_tests) +if(BUILD_TESTING) + set_target_properties(guest_sce_v2_locale_tests PROPERTIES EXCLUDE_FROM_ALL FALSE) + add_test(NAME guest_sce_v2_locale COMMAND guest_sce_v2_locale_tests) + if(WIN32) + set_tests_properties(guest_sce_v2_locale PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") + endif() +endif() + add_test_executable(guest_libc_mutex_init_with_name_tests EXCLUDE_FROM_ALL ${LIBS_INCLUDE_DIR}/tests/GuestLibcMutexInitWithName.cpp) target_include_directories(guest_libc_mutex_init_with_name_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_libc_mutex_init_with_name_tests PRIVATE libc) diff --git a/core/libs/prx/libc/include/Api.hpp b/core/libs/prx/libc/include/Api.hpp index 2e55965a17..6f8d573656 100644 --- a/core/libs/prx/libc/include/Api.hpp +++ b/core/libs/prx/libc/include/Api.hpp @@ -6,9 +6,11 @@ #include "prx/libc/include/GuestTime.hpp" #include "prx/libc/include/GuestLocale.hpp" #include "prx/libc/include/GuestDirectory.hpp" +#include "prx/libc/include/GuestErrno.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/exceptions/Unwind.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" +#include "prx/libc/include/LegacySignals.hpp" #include "SceTypes.hpp" #include #include @@ -104,6 +106,7 @@ struct FutureErrorObject { const void* vtable; GuestLocale::String message; Erro 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(); @@ -227,6 +230,29 @@ 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); +int APS5_VABI _ZNSt8__sce_v210filesystem5_StatEPKcPNS0_5permsEPi_nid_postfix(const char* path, int* perms, int* error); +void* APS5_VABI _ZNSt8__sce_v210filesystem9_Open_dirERA260_cPKcRiRNS0_9file_typeE_nid_postfix(char* target, const char* path, int* error, int* type); +char* APS5_VABI _ZNSt8__sce_v210filesystem9_Read_dirERA260_cPvRNS0_9file_typeE_nid_postfix(char* target, void* handle, int* type); +int APS5_VABI _ZNSt8__sce_v210filesystem10_Close_dirEPv_nid_postfix(void* handle); +bool APS5_VABI _ZNSt8__sce_v210filesystem12_Current_getERA260_c_nid_postfix(char* target); +void APS5_VABI _ZNSt8__sce_v210filesystem18_Xfilesystem_errorEPKcNS_10error_codeE_nid_postfix(); +void APS5_VABI _ZNSt8__sce_v210filesystem18_Xfilesystem_errorEPKcRKNS0_4pathENS_10error_codeE_nid_postfix(); +int APS5_VABI _ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix(const char* path, int* perms, int* error); +int APS5_VABI _ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsE_nid_postfix(const char* path, int* perms); +std::uint64_t APS5_VABI _ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix(const char* path, int* error); +std::uint64_t APS5_VABI _ZNSt8__sce_v210filesystem10_File_sizeEPKc_nid_postfix(const char* path); +std::int64_t APS5_VABI _ZNSt8__sce_v210filesystem16_Last_write_timeEPKc_nid_postfix(const char* path); +int APS5_VABI _ZNSt8__sce_v210filesystem16_Last_write_timeEPKcPlS3__nid_postfix(const char* path, std::int64_t* seconds, std::int64_t* nanoseconds); +bool APS5_VABI _ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix(const char* path, std::int64_t microseconds); +int APS5_VABI _ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix(const char* path, const char* existing); +bool APS5_VABI _ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix(const char* path); +int APS5_VABI _ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix(const char* path); +int APS5_VABI _ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix(const char* from, const char* to); +int APS5_VABI _ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix(const char* path, std::uint64_t size); +int APS5_VABI _ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix(const char* path, int perms); +int APS5_VABI _ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix(const char* from, const char* to); +int APS5_VABI _ZNSt8__sce_v210filesystem8_StatvfsEPKcRNS0_10space_infoE_nid_postfix(const char* path, std::uint64_t* space); +int APS5_VABI _ZNSt8__sce_v210filesystem13_Current_set2EPKc_nid_postfix(const char* path); 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); @@ -405,11 +431,14 @@ 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 fwide_nid_postfix(FileStream* stream, int mode); 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); +char* APS5_VABI devname_nid_postfix(std::uint32_t device, std::uint16_t type); +char* APS5_VABI devname_r_nid_postfix(std::uint32_t device, std::uint16_t type, char* buffer, int 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); @@ -460,6 +489,54 @@ std::uint32_t APS5_VABI stdc_count_ones_us_nid_postfix(std::uint16_t value); std::uint32_t APS5_VABI stdc_count_ones_ui_nid_postfix(std::uint32_t value); std::uint32_t APS5_VABI stdc_count_ones_ul_nid_postfix(std::uint64_t value); std::uint32_t APS5_VABI stdc_count_ones_ull_nid_postfix(std::uint64_t value); + +std::uint32_t APS5_VABI stdc_first_leading_zero_uc_nid_postfix(std::uint8_t value); +std::uint32_t APS5_VABI stdc_first_leading_zero_us_nid_postfix(std::uint16_t value); +std::uint32_t APS5_VABI stdc_first_leading_zero_ui_nid_postfix(std::uint32_t value); +std::uint32_t APS5_VABI stdc_first_leading_zero_ul_nid_postfix(std::uint64_t value); +std::uint32_t APS5_VABI stdc_first_leading_zero_ull_nid_postfix(std::uint64_t value); + +std::uint32_t APS5_VABI stdc_first_leading_one_uc_nid_postfix(std::uint8_t value); +std::uint32_t APS5_VABI stdc_first_leading_one_us_nid_postfix(std::uint16_t value); +std::uint32_t APS5_VABI stdc_first_leading_one_ui_nid_postfix(std::uint32_t value); +std::uint32_t APS5_VABI stdc_first_leading_one_ul_nid_postfix(std::uint64_t value); +std::uint32_t APS5_VABI stdc_first_leading_one_ull_nid_postfix(std::uint64_t value); + +std::uint32_t APS5_VABI stdc_first_trailing_zero_uc_nid_postfix(std::uint8_t value); +std::uint32_t APS5_VABI stdc_first_trailing_zero_us_nid_postfix(std::uint16_t value); +std::uint32_t APS5_VABI stdc_first_trailing_zero_ui_nid_postfix(std::uint32_t value); +std::uint32_t APS5_VABI stdc_first_trailing_zero_ul_nid_postfix(std::uint64_t value); +std::uint32_t APS5_VABI stdc_first_trailing_zero_ull_nid_postfix(std::uint64_t value); + +std::uint32_t APS5_VABI stdc_first_trailing_one_uc_nid_postfix(std::uint8_t value); +std::uint32_t APS5_VABI stdc_first_trailing_one_us_nid_postfix(std::uint16_t value); +std::uint32_t APS5_VABI stdc_first_trailing_one_ui_nid_postfix(std::uint32_t value); +std::uint32_t APS5_VABI stdc_first_trailing_one_ul_nid_postfix(std::uint64_t value); +std::uint32_t APS5_VABI stdc_first_trailing_one_ull_nid_postfix(std::uint64_t value); + +bool APS5_VABI stdc_has_single_bit_uc_nid_postfix(std::uint8_t value); +bool APS5_VABI stdc_has_single_bit_us_nid_postfix(std::uint16_t value); +bool APS5_VABI stdc_has_single_bit_ui_nid_postfix(std::uint32_t value); +bool APS5_VABI stdc_has_single_bit_ul_nid_postfix(std::uint64_t value); +bool APS5_VABI stdc_has_single_bit_ull_nid_postfix(std::uint64_t value); + +std::uint32_t APS5_VABI stdc_bit_width_uc_nid_postfix(std::uint8_t value); +std::uint32_t APS5_VABI stdc_bit_width_us_nid_postfix(std::uint16_t value); +std::uint32_t APS5_VABI stdc_bit_width_ui_nid_postfix(std::uint32_t value); +std::uint32_t APS5_VABI stdc_bit_width_ul_nid_postfix(std::uint64_t value); +std::uint32_t APS5_VABI stdc_bit_width_ull_nid_postfix(std::uint64_t value); + +std::uint8_t APS5_VABI stdc_bit_floor_uc_nid_postfix(std::uint8_t value); +std::uint16_t APS5_VABI stdc_bit_floor_us_nid_postfix(std::uint16_t value); +std::uint32_t APS5_VABI stdc_bit_floor_ui_nid_postfix(std::uint32_t value); +std::uint64_t APS5_VABI stdc_bit_floor_ul_nid_postfix(std::uint64_t value); +std::uint64_t APS5_VABI stdc_bit_floor_ull_nid_postfix(std::uint64_t value); + +std::uint8_t APS5_VABI stdc_bit_ceil_uc_nid_postfix(std::uint8_t value); +std::uint16_t APS5_VABI stdc_bit_ceil_us_nid_postfix(std::uint16_t value); +std::uint32_t APS5_VABI stdc_bit_ceil_ui_nid_postfix(std::uint32_t value); +std::uint64_t APS5_VABI stdc_bit_ceil_ul_nid_postfix(std::uint64_t value); +std::uint64_t APS5_VABI stdc_bit_ceil_ull_nid_postfix(std::uint64_t value); int APS5_VABI __cmpdi2_nid_postfix(std::int64_t a, std::int64_t b); int APS5_VABI __cmpti2_nid_postfix(Int128 a, Int128 b); int APS5_VABI __ucmpdi2_nid_postfix(std::uint64_t a, std::uint64_t b); @@ -541,9 +618,17 @@ 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(); +GuestLocale::TimeInfo* APS5_VABI _Getptimes_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); +GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale5_InitEb_nid_postfix(bool doIncref); +GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix(); +void APS5_VABI _ZNSt8__sce_v28ios_base7_AddstdEPS0__nid_postfix(GuestLocale::SceV2IosBase* self); +void APS5_VABI _ZNSt8__sce_v28ios_base14_Ios_base_dtorEPS0__nid_postfix(GuestLocale::SceV2IosBase* self); +void APS5_VABI _ZNSt8__sce_v215_Facet_RegisterEPNS_11_Facet_baseE_nid_postfix(GuestLocale::Facet* facet); +GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale7_Locimp11_New_LocimpERKS1__nid_postfix(const GuestLocale::Implementation* source); +void APS5_VABI _ZNSt8__sce_v26locale7_Locimp14_Locimp_AddfacEPS1_PNS0_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); @@ -727,6 +812,8 @@ 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); +short APS5_VABI _FDsign_nid_postfix(float value); +short APS5_VABI _Dsign_nid_postfix(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); @@ -782,6 +869,38 @@ 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); +short APS5_VABI _Dint_nid_postfix(double*, short); +short APS5_VABI _FDint_nid_postfix(float*, short); +short APS5_VABI _LDint_nid_postfix(long double*, short); +short APS5_VABI _Dscale_nid_postfix(double*, std::int64_t); +short APS5_VABI _FDscale_nid_postfix(float*, std::int64_t); +short APS5_VABI _LDscale_nid_postfix(long double*, std::int64_t); +short APS5_VABI _Dunscale_nid_postfix(short*, double*); +short APS5_VABI _FDunscale_nid_postfix(short*, float*); +short APS5_VABI _LDunscale_nid_postfix(short*, long double*); +short APS5_VABI _ZNSt8__sce_v27_DscaleEPdl_nid_postfix(double* value, std::int64_t exponent); +short APS5_VABI _ZNSt8__sce_v28_FDscaleEPfl_nid_postfix(float* value, std::int64_t exponent); +short APS5_VABI _ZNSt8__sce_v28_LDscaleEPel_nid_postfix(double* value, std::int64_t exponent); +short APS5_VABI _ZNSt8__sce_v29_DunscaleEPsPd_nid_postfix(short* exponent, double* value); +short APS5_VABI _ZNSt8__sce_v210_FDunscaleEPsPf_nid_postfix(short* exponent, float* value); +short APS5_VABI _ZNSt8__sce_v210_LDunscaleEPsPe_nid_postfix(short* exponent, double* value); +short APS5_VABI _Dnorm_nid_postfix(std::uint16_t*); +short APS5_VABI _FDnorm_nid_postfix(std::uint16_t*); +short APS5_VABI _LDnorm_nid_postfix(std::uint16_t*); +short APS5_VABI _ZNSt8__sce_v26_DtestEPd_nid_postfix(double* value); +short APS5_VABI _ZNSt8__sce_v27_FDtestEPf_nid_postfix(float* value); +short APS5_VABI _ZNSt8__sce_v27_LDtestEPe_nid_postfix(double* value); +short APS5_VABI _ZNSt8__sce_v25_DintEPds_nid_postfix(double* value, short scale); +short APS5_VABI _ZNSt8__sce_v26_FDintEPfs_nid_postfix(float* value, short scale); +short APS5_VABI _ZNSt8__sce_v26_LDintEPes_nid_postfix(double* value, short scale); +short APS5_VABI _ZNSt8__sce_v26_DnormEPNS_5_DvalE_nid_postfix(std::uint16_t* words); +short APS5_VABI _ZNSt8__sce_v27_FDnormEPNS_5_FvalE_nid_postfix(std::uint16_t* words); +int APS5_VABI _Fpcomp_nid_postfix(double, double); +int APS5_VABI _FFpcomp_nid_postfix(float, float); +int APS5_VABI _LFpcomp_nid_postfix(long double, long double); +double APS5_VABI _Poly_nid_postfix(double, const double*, int); +float APS5_VABI _FPoly_nid_postfix(float, const float*, int); +long double APS5_VABI _LPoly_nid_postfix(long double, const long double*, int); 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); @@ -830,7 +949,6 @@ std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const ch 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); @@ -984,16 +1102,19 @@ 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 stpncpy_nid_postfix(char* destination, const char* source, std::size_t count); 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 memset_explicit_nid_postfix(void* destination, int value, size_t count); 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); +int APS5_VABI timingsafe_bcmp_nid_postfix(const void* b1, const void* b2, size_t n); int APS5_VABI timingsafe_memcmp_nid_postfix(const void* b1, const void* b2, size_t len); const void* APS5_VABI memchr_nid_postfix(const void* s, int c, size_t n); void* APS5_VABI memrchr_nid_postfix(const void* s, int c, size_t n); @@ -1112,6 +1233,9 @@ 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 _Cnd_register_at_thread_exit_nid_postfix(void* condition, void* mutex, int* ready); +void APS5_VABI _Cnd_unregister_at_thread_exit_nid_postfix(void* mutex); +void APS5_VABI _Cnd_do_broadcast_at_thread_exit_nid_postfix() noexcept; 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); @@ -1158,6 +1282,7 @@ 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); +int64_t APS5_VABI timelocal_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); @@ -1176,6 +1301,10 @@ const void* APS5_VABI __std_find_trivial_1_nid_postfix(const void* first, const 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_last_trivial_1_nid_postfix(const void* first, const void* last, std::uint8_t value); +const void* APS5_VABI __std_find_last_trivial_2_nid_postfix(const void* first, const void* last, std::uint16_t value); +const void* APS5_VABI __std_find_last_trivial_4_nid_postfix(const void* first, const void* last, std::uint32_t value); +const void* APS5_VABI __std_find_last_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); @@ -1517,6 +1646,11 @@ 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); +unsigned long long APS5_VABI _WStoull_nid_postfix(const char16_t* str, char16_t** endptr, int base); +long long APS5_VABI _WStoll_nid_postfix(const char16_t* str, char16_t** endptr, int base); +double APS5_VABI _WStod_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten); +float APS5_VABI _WStof_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten); +long double APS5_VABI _WStold_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten); 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); @@ -1525,6 +1659,8 @@ std::uint64_t APS5_VABI _Stoulx_nid_postfix(const char* text, char** end, int ba 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*); +extern std::uint64_t _ZNSt8numpunctIwE2idE_nid_postfix; +std::size_t APS5_VABI _ZNSt8numpunctIwE7_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/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index d93e847dd2..a2f9bedde0 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,34 @@ extern "C" void ForgetDirectoryStream_nid_no_patch(int descriptor); extern "C" int _IO_switch_to_get_mode(std::FILE* stream); #endif extern "C" int DuplicateHostDescriptor_nid_no_patch(int descriptor); +extern "C" void LibcRegisterClosedDescriptors_nid_no_patch(void* (*begin)(int descriptor), void (*end)(void* closing)); +extern "C" void* BeginClosingDescriptor_nid_no_patch(int descriptor); +extern "C" void EndClosingDescriptor_nid_no_patch(void* closing); + +class ClosingDescriptor { +public: + ClosingDescriptor([[maybe_unused]] std::FILE* stream, int descriptor) +#ifdef _WIN32 + : _stream(stream), _closing(BeginClosingDescriptor_nid_no_patch(descriptor)) {} +#else + : _closing(BeginClosingDescriptor_nid_no_patch(descriptor)) {} +#endif + ~ClosingDescriptor() { EndClosingDescriptor_nid_no_patch(_closing); } + ClosingDescriptor(const ClosingDescriptor&) = delete; + ClosingDescriptor& operator=(const ClosingDescriptor&) = delete; + +private: +#ifdef _WIN32 + struct LockedStream { + explicit LockedStream(std::FILE* value) : stream(value) { ::_lock_file(stream); } + ~LockedStream() { ::_unlock_file(stream); } + LockedStream(const LockedStream&) = delete; + LockedStream& operator=(const LockedStream&) = delete; + std::FILE* stream; + } _stream; +#endif + void* _closing; +}; struct GuestFilePrefix { unsigned char* position = nullptr; @@ -70,6 +99,7 @@ class FileStream { bool guestError = false; bool randomDevice = false; bool directory = false; + std::atomic _orientation{0}; char16_t lowSurrogate = 0; unsigned pushedBack = 0; @@ -100,6 +130,13 @@ public: return _handle != nullptr; } + int Orientation(int mode) { + if (mode == 0) return _orientation.load(std::memory_order_relaxed); + int expected = 0; + _orientation.compare_exchange_strong(expected, mode > 0 ? 1 : -1, std::memory_order_relaxed); + return _orientation.load(std::memory_order_relaxed); + } + bool IsDynamic() const { return _dynamic; } @@ -110,6 +147,7 @@ public: } void Detach() { + _orientation.store(0, std::memory_order_relaxed); _handle = nullptr; _guest.flags = 0; _guest.descriptor = -1; @@ -182,6 +220,7 @@ public: _guest = {}; guestError = false; randomDevice = false; + _orientation.store(0, std::memory_order_relaxed); lowSurrogate = 0; pushedBack = 0; #ifdef _WIN32 @@ -191,10 +230,12 @@ public: if (std::exchange(directory, false)) ForgetDirectoryStream_nid_no_patch(replaced); auto* const standard = HostStandardHandle_nid_no_patch(replaced); GuestFileLockForget_nid_no_patch(replaced); + std::optional closing(std::in_place, previous, replaced); if (open && !filename.empty()) { if (!IsHostStandard()) { _handle = nullptr; std::fclose(previous); + closing.reset(); _handle = open(filename, mode); } else { std::wstring resetMode; @@ -234,6 +275,7 @@ public: KeepStandardHandle_nid_no_patch(replaced, standard); #else if (::fileno(previous) == 2) KeepHostStandardError_nid_no_patch(); + const ClosingDescriptor closing(previous, ::fileno(previous)); const int kept = IsHostStandard() ? DuplicateHostDescriptor_nid_no_patch(::fileno(previous)) : -1; if (kept >= 0) ::flockfile(previous); _handle = std::freopen(filename.c_str(), mode, previous); @@ -283,6 +325,17 @@ public: #endif } + bool OpenForReading() { +#ifdef _WIN32 + constexpr long UcrtReading = 0x0001; + constexpr long UcrtUpdate = 0x0004; + std::atomic_ref state(reinterpret_cast(GetHandle())->flags); + return (state.load() & (UcrtReading | UcrtUpdate)) != 0; +#else + return true; +#endif + } + bool SwitchToReadingUnlocked() { #ifdef _WIN32 constexpr long UcrtReading = 0x0001; @@ -290,6 +343,12 @@ public: constexpr long UcrtUpdate = 0x0004; auto* native = GetHandle(); std::atomic_ref state(reinterpret_cast(native)->flags); + if ((state.load() & (UcrtReading | UcrtUpdate)) == 0) { + errno = 9; + guestError = true; + SyncUnlockedStatus(); + return false; + } if ((state.load() & (UcrtReading | UcrtUpdate)) != UcrtUpdate) return true; const bool switched = (state.load() & UcrtWriting) == 0 || ::_fflush_nolock(native) == 0; if (switched) state.fetch_or(UcrtReading); @@ -382,10 +441,15 @@ public: #ifdef _WIN32 GuestFileLockForget_nid_no_patch(::_fileno(_handle)); if (std::exchange(directory, false)) ForgetDirectoryStream_nid_no_patch(::_fileno(_handle)); + const ClosingDescriptor closing(_handle, ::_fileno(_handle)); +#else + const ClosingDescriptor closing(_handle, ::fileno(_handle)); #endif - const int result = std::fclose(std::exchange(_handle, nullptr)); + const int result = std::fclose(_handle); + _handle = nullptr; _guest.flags = 0; _guest.descriptor = -1; + _orientation.store(0, std::memory_order_relaxed); lowSurrogate = 0; pushedBack = 0; return result; diff --git a/core/libs/prx/libc/include/FilesystemError.hpp b/core/libs/prx/libc/include/FilesystemError.hpp index b3b6b2aca3..12f8262558 100644 --- a/core/libs/prx/libc/include/FilesystemError.hpp +++ b/core/libs/prx/libc/include/FilesystemError.hpp @@ -2,6 +2,10 @@ #define CORE_LIBS_PRX_LIBC_INCLUDE_FILESYSTEMERROR_HPP #include +#ifdef _WIN32 +#include +#include "prx/libkernel/File/include/WindowsSymbolicLink.hpp" +#endif #include "General.hpp" inline int FilesystemError(const std::error_code& error) { @@ -23,10 +27,71 @@ inline int FilesystemError(const std::error_code& error) { return 5; } +enum class LinkPrefix_nid_no_patch { Resolves, Missing, BlockedByFile }; + +inline bool HasNonDirectoryPrefix_nid_no_patch(const std::filesystem::path& native); + +#ifdef _WIN32 +inline std::filesystem::path FinalDirectory_nid_no_patch(const std::filesystem::path& directory) { + const HANDLE 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 directory; + std::wstring name(MAX_PATH, L'\0'); + DWORD length = GetFinalPathNameByHandleW(handle, name.data(), static_cast(name.size()), VOLUME_NAME_DOS); + if (length >= name.size()) { + name.resize(length + 1); + length = GetFinalPathNameByHandleW(handle, name.data(), static_cast(name.size()), VOLUME_NAME_DOS); + } + CloseHandle(handle); + if (length == 0 || length >= name.size()) return directory; + name.resize(length); + if (name.rfind(L"\\\\?\\UNC\\", 0) == 0) name.replace(0, 7, L"\\"); + else if (name.rfind(L"\\\\?\\", 0) == 0) name.erase(0, 4); + return name; +} +#endif + +inline LinkPrefix_nid_no_patch LinkPrefixState_nid_no_patch(const std::filesystem::path& prefix) { +#ifdef _WIN32 + constexpr unsigned MaxDepth = 32; + thread_local unsigned depth = 0; + if (depth >= MaxDepth) return LinkPrefix_nid_no_patch::Resolves; + const DWORD attributes = GetFileAttributesW(prefix.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) return LinkPrefix_nid_no_patch::Resolves; + const HANDLE handle = CreateFileW(prefix.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) { + CloseHandle(handle); + return LinkPrefix_nid_no_patch::Resolves; + } + const DWORD code = GetLastError(); + if (code != ERROR_FILE_NOT_FOUND && code != ERROR_PATH_NOT_FOUND) return LinkPrefix_nid_no_patch::Resolves; + std::string text; + try { + if (!File::WindowsSymbolicLink::Read(prefix, text, "lookup")) return LinkPrefix_nid_no_patch::Resolves; + } catch (const std::system_error&) { + return LinkPrefix_nid_no_patch::Resolves; + } + const std::filesystem::path target(text); + struct DepthGuard { + explicit DepthGuard(unsigned& value) : value(value) { ++value; } + ~DepthGuard() { --value; } + unsigned& value; + } guard(depth); + const bool blocked = HasNonDirectoryPrefix_nid_no_patch((target.is_absolute() ? target : FinalDirectory_nid_no_patch(prefix.parent_path()) / target) / "."); + return blocked ? LinkPrefix_nid_no_patch::BlockedByFile : LinkPrefix_nid_no_patch::Missing; +#else + (void)prefix; + return LinkPrefix_nid_no_patch::Resolves; +#endif +} + inline bool HasNonDirectoryPrefix_nid_no_patch(const std::filesystem::path& native) { std::error_code error; const auto trimmed = native.has_filename() ? native : native.parent_path(); for (auto prefix = trimmed.parent_path(); prefix.has_relative_path(); prefix = prefix.parent_path()) { + const auto link = LinkPrefixState_nid_no_patch(prefix); + if (link != LinkPrefix_nid_no_patch::Resolves) return link == LinkPrefix_nid_no_patch::BlockedByFile; const auto status = std::filesystem::status(prefix, error); if (std::filesystem::exists(status)) return !std::filesystem::is_directory(status); } diff --git a/core/libs/prx/libc/include/FilesystemForwards.hpp b/core/libs/prx/libc/include/FilesystemForwards.hpp new file mode 100644 index 0000000000..97e5c2e761 --- /dev/null +++ b/core/libs/prx/libc/include/FilesystemForwards.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "SceTypes.hpp" +#include "general/VabiMacros.hpp" + +#include +#include + +struct FilesystemForwards_nid_no_patch { + int (APS5_VABI* stat)(const char*, FileStat*); + int (APS5_VABI* fstat)(int, FileStat*); + int (APS5_VABI* open)(const char*, int, std::uint16_t); + std::int64_t (APS5_VABI* read)(int, void*, std::size_t); + std::int64_t (APS5_VABI* write)(int, const void*, std::size_t); + int (APS5_VABI* close)(int); + int (APS5_VABI* mkdir)(const char*, std::uint16_t); + int (APS5_VABI* unlink)(const char*); + int (APS5_VABI* rename)(const char*, const char*); + int (APS5_VABI* truncate)(const char*, std::int64_t); + int (APS5_VABI* chmod)(const char*, std::uint16_t); + int (APS5_VABI* fchmod)(int, std::uint16_t); + int (APS5_VABI* utimes)(const char*, const KernelTimeval*); + int (APS5_VABI* posixStat)(const char*, FileStat*); + int (APS5_VABI* posixRmdir)(const char*); +}; + +extern "C" void RegisterFilesystemForwards_nid_no_patch(const FilesystemForwards_nid_no_patch* forwards); diff --git a/core/libs/prx/libc/include/GuestAllocations.hpp b/core/libs/prx/libc/include/GuestAllocations.hpp index 3b5c8a3a79..4ea19487df 100644 --- a/core/libs/prx/libc/include/GuestAllocations.hpp +++ b/core/libs/prx/libc/include/GuestAllocations.hpp @@ -73,6 +73,7 @@ void GuestAllocationsUnmap_nid_postfix(void* mutation, const void* pointer, std: Lease GuestAllocationsAcquire_nid_postfix(); Lease GuestAllocationsAcquireMapped_nid_no_patch(const Mapped& mapped); Lease GuestAllocationsAcquireAll_nid_postfix(); +Lease GuestAllocationsAcquireRegistered_nid_postfix(); std::shared_ptr GuestAllocationsLookup_nid_postfix(std::uintptr_t address, bool next); // Changes whenever a mutation ends, so callers can cache facts about guest mappings between changes. std::uint64_t GuestAllocationsGeneration_nid_postfix(); diff --git a/core/libs/prx/libc/include/GuestArena.hpp b/core/libs/prx/libc/include/GuestArena.hpp index 297d71a1c4..74880ddac0 100644 --- a/core/libs/prx/libc/include/GuestArena.hpp +++ b/core/libs/prx/libc/include/GuestArena.hpp @@ -17,6 +17,11 @@ inline constexpr std::uintptr_t GuestAddressOffset = 0; #ifndef _WIN32 using SharedBackingResolver = bool (*)(std::uintptr_t address, std::size_t bytes, int* file, std::uint64_t* offset); using SharedBackingWriter = bool (*)(std::uintptr_t address, const void* source, std::size_t bytes); +struct SharedBackingRun { + std::uint64_t offset; + std::size_t bytes; +}; +using SharedBackingRunsResolver = bool (*)(std::uintptr_t address, std::size_t bytes, int* file, SharedBackingRun* runs, std::size_t capacity, std::size_t* count); #endif extern "C" { @@ -51,6 +56,7 @@ std::uint64_t GuestArenaCommitGeneration_nid_postfix(); void GuestArenaSetPrivateMappingObserver_nid_postfix(void (*callback)(std::uintptr_t address, std::size_t bytes, std::uint64_t generation)); void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection); void GuestArenaMapDirect_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection); +void GuestArenaMapZero_nid_postfix(void* pointer, std::size_t bytes); void GuestArenaMapFile_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection, std::uint32_t viewProtection, std::size_t sectionBytes); void* GuestArenaMapAlias_nid_postfix(std::uintptr_t address, std::size_t bytes); void GuestArenaUnmapAlias_nid_postfix(void* alias); @@ -68,6 +74,8 @@ void GuestArenaSetSharedBacking_nid_postfix(SharedBackingResolver resolver); void GuestArenaSetSharedBackingWriter_nid_no_patch(SharedBackingWriter writer); bool GuestArenaWriteSharedBacking_nid_no_patch(std::uintptr_t address, const void* source, std::size_t bytes); bool GuestArenaSharedBacking_nid_postfix(std::uintptr_t address, std::size_t bytes, int* file, std::uint64_t* offset); +void GuestArenaSetSharedBackingRuns_nid_postfix(SharedBackingRunsResolver resolver); +bool GuestArenaSharedBackingRuns_nid_postfix(std::uintptr_t address, std::size_t bytes, int* file, SharedBackingRun* runs, std::size_t capacity, std::size_t* count); bool GuestArenaOverlapsHostRegion_nid_no_patch(std::uintptr_t address, std::size_t bytes); void GuestArenaReleaseHostRegion_nid_no_patch(const void* pointer, std::size_t bytes); #endif diff --git a/core/libs/prx/libc/include/GuestErrno.hpp b/core/libs/prx/libc/include/GuestErrno.hpp new file mode 100644 index 0000000000..00d2aa0f77 --- /dev/null +++ b/core/libs/prx/libc/include/GuestErrno.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include "general/VabiMacros.hpp" + +extern "C" int* APS5_VABI __error_nid_postfix(); diff --git a/core/libs/prx/libc/include/GuestFilesystemPrimitives.hpp b/core/libs/prx/libc/include/GuestFilesystemPrimitives.hpp new file mode 100644 index 0000000000..5907b5a047 --- /dev/null +++ b/core/libs/prx/libc/include/GuestFilesystemPrimitives.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/GuestDirectory.hpp" +#include "prx/libkernel/include/Api.hpp" + +#include +#include +#include + +namespace GuestFilesystem { + +inline constexpr std::size_t NameCapacity = 260; +inline constexpr std::size_t CurrentCapacity = 256; +inline constexpr int NoSuchFile = 2; +inline constexpr int Exists = 17; +inline constexpr int NotDirectory = 20; +inline constexpr int Invalid = 22; +inline constexpr int NameTooLong = 63; +inline constexpr std::uint16_t DefaultDirectoryMode = 0777; +inline 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, +}; + +inline int& GuestErrno() { + return *__error_nid_postfix(); +} + +inline 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; + } +} + +inline 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; +} + +inline 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'; +} + +inline bool IsDotEntry(const char* name) { + return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); +} + +inline bool CurrentGet(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; +} + +inline char* ReadDir(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; +} + +inline void* OpenDir(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; + ReadDir(target, handle, type); + if (target[0] != '\0') return handle; + closedir_nid_postfix(handle); + return nullptr; +} + +inline int CloseDir(void* handle) { + return closedir_nid_postfix(handle); +} + +} diff --git a/core/libs/prx/libc/include/GuestLocale.hpp b/core/libs/prx/libc/include/GuestLocale.hpp index 958adc5cac..4991822517 100644 --- a/core/libs/prx/libc/include/GuestLocale.hpp +++ b/core/libs/prx/libc/include/GuestLocale.hpp @@ -34,6 +34,30 @@ struct Facet { std::uint32_t reserved; }; +struct TimeInfo { + const char* amPm; + const char* days; + const char* abbreviatedDays; + const char* fullDays; + const char* months; + const char* abbreviatedMonths; + const char* fullMonths; + const char* formats; + const char* dateTimeFormat; + const char* dateFormat; + const char* timeFormat; + const char* timeFormatAmPm; + const char* eraFormats; + const char* eraDateTimeFormat; + const char* eraDateFormat; + const char* eraTimeFormat; + const char* eraTimeFormatAmPm; + const char* era; + const char* alternativeDigits; + const char* daylightSaving; + const char* timeZone; +}; + struct Implementation { Facet base; Facet** facets; @@ -98,6 +122,24 @@ struct NumpunctVtable { String* (APS5_VABI *truename)(String* result, const NumpunctFacet* self); }; +struct WideNumpunctFacet { + Facet base; + const char* grouping; + std::uint16_t decimalPoint; + std::uint16_t thousandsSep; + const std::uint16_t* falsename; + const std::uint16_t* truename; +}; + +struct WideNumpunctVtable { + FacetVtable facet; + std::uint16_t (APS5_VABI *decimalPoint)(const WideNumpunctFacet* self); + std::uint16_t (APS5_VABI *thousandsSep)(const WideNumpunctFacet* self); + String* (APS5_VABI *grouping)(String* result, const WideNumpunctFacet* self); + String* (APS5_VABI *falsename)(String* result, const WideNumpunctFacet* self); + String* (APS5_VABI *truename)(String* result, const WideNumpunctFacet* self); +}; + struct IosBase { const void* vtable; std::int64_t standardStream; @@ -112,6 +154,20 @@ struct IosBase { Implementation** locale; }; +struct SceV2IosBase { + const void* vtable; + std::int64_t standardStream; + unsigned char reserved[0x20]; + void* streamNodes; + void* callbackNodes; + Implementation** locale; +}; + +static_assert(offsetof(SceV2IosBase, streamNodes) == 0x30); +static_assert(offsetof(SceV2IosBase, callbackNodes) == 0x38); +static_assert(offsetof(SceV2IosBase, locale) == 0x40); +static_assert(sizeof(SceV2IosBase) == 0x48); + struct Streambuf; struct StreambufVtable { @@ -193,6 +249,14 @@ 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(sizeof(WideNumpunctFacet) == 0x30); +static_assert(offsetof(WideNumpunctFacet, grouping) == 0x10); +static_assert(offsetof(WideNumpunctFacet, decimalPoint) == 0x18); +static_assert(offsetof(WideNumpunctFacet, thousandsSep) == 0x1a); +static_assert(offsetof(WideNumpunctFacet, falsename) == 0x20); +static_assert(offsetof(WideNumpunctFacet, truename) == 0x28); +static_assert(offsetof(WideNumpunctVtable, decimalPoint) == 0x20); +static_assert(offsetof(WideNumpunctVtable, truename) == 0x40); static_assert(alignof(LocinfoStorage) == 8); static_assert(sizeof(SceV2Locinfo) == 24); static_assert(offsetof(SceV2Locinfo, name) == 8); diff --git a/core/libs/prx/libc/include/GuestWriteWatch.hpp b/core/libs/prx/libc/include/GuestWriteWatch.hpp index 96f09551c6..cce53aace7 100644 --- a/core/libs/prx/libc/include/GuestWriteWatch.hpp +++ b/core/libs/prx/libc/include/GuestWriteWatch.hpp @@ -21,6 +21,9 @@ int GuestWriteWatchProtect_nid_postfix(void* pointer, std::size_t bytes, int pro int GuestWriteWatchProtection_nid_postfix(std::uintptr_t address, std::uintptr_t limit, std::uintptr_t* runEnd); bool GuestWriteWatchCollectArmed_nid_postfix(std::uintptr_t address, std::size_t bytes, void (*written)(void* context, std::uintptr_t begin, std::uintptr_t end), void* context); bool GuestWriteWatchCollectFresh_nid_postfix(std::uintptr_t address, std::size_t bytes, void (*written)(void* context, std::uintptr_t begin, std::uintptr_t end), void* context); +bool GuestWriteWatchSetReadTrapResolver_nid_postfix(void (*resolve)(std::uintptr_t begin, std::uintptr_t end)); +bool GuestWriteWatchTrapReads_nid_postfix(std::uintptr_t address, std::size_t bytes); +void GuestWriteWatchReleaseReads_nid_postfix(std::uintptr_t address, std::size_t bytes); } diff --git a/core/libs/prx/libc/include/LegacySignals.hpp b/core/libs/prx/libc/include/LegacySignals.hpp new file mode 100644 index 0000000000..0f99fb0ef0 --- /dev/null +++ b/core/libs/prx/libc/include/LegacySignals.hpp @@ -0,0 +1,27 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_LEGACYSIGNALS_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_LEGACYSIGNALS_HPP + +#include "prx/libkernel/include/Api.hpp" +#include +#include + +struct GuestSignalVector { + std::uintptr_t handler; + int mask; + int flags; +}; + +static_assert(sizeof(GuestSignalVector) == 16 && offsetof(GuestSignalVector, mask) == 8 && offsetof(GuestSignalVector, flags) == 12); + +using LegacySigaction_nid_no_patch = int (APS5_VABI *)(int, const GuestSigaction*, GuestSigaction*); + +extern "C" { + +void RegisterSigvec_nid_no_patch(LegacySigaction_nid_no_patch action); +int LegacySigvec_nid_no_patch(int signal, const GuestSignalVector* replacement, GuestSignalVector* previous); +int APS5_VABI sigvec_nid_disambig1(int signal, const GuestSignalVector* replacement, GuestSignalVector* previous); +int APS5_VABI sigvec_nid_postfix(int signal, const GuestSignalVector* replacement, GuestSignalVector* previous); + +} + +#endif diff --git a/core/libs/prx/libc/include/PrimedSharedMutex.hpp b/core/libs/prx/libc/include/PrimedSharedMutex.hpp new file mode 100644 index 0000000000..7f6a9d8e7f --- /dev/null +++ b/core/libs/prx/libc/include/PrimedSharedMutex.hpp @@ -0,0 +1,14 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_PRIMEDSHAREDMUTEX_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_PRIMEDSHAREDMUTEX_HPP + +#include + +class PrimedSharedMutex : public std::shared_mutex { +public: + PrimedSharedMutex() { + lock(); + unlock(); + } +}; + +#endif diff --git a/core/libs/prx/libc/include/ScanWideField.hpp b/core/libs/prx/libc/include/ScanWideField.hpp index f210ec5d5e..c34330d411 100644 --- a/core/libs/prx/libc/include/ScanWideField.hpp +++ b/core/libs/prx/libc/include/ScanWideField.hpp @@ -1,13 +1,11 @@ #ifndef CORE_LIBS_PRX_LIBC_INCLUDE_SCANWIDEFIELD_HPP #define CORE_LIBS_PRX_LIBC_INCLUDE_SCANWIDEFIELD_HPP -#include #include namespace LibcDetail { -std::size_t ReadWideScanCharacter(const char* text, char32_t* point); -bool InWideScanset(const std::string& specifier, char32_t point); +bool InWideScanset(const std::string& specifier, unsigned char byte); void RequireNoPositionalWideScan(const char* format); } diff --git a/core/libs/prx/libc/include/WindowsMappings.hpp b/core/libs/prx/libc/include/WindowsMappings.hpp index 98d3b80a64..ee8f9b279c 100644 --- a/core/libs/prx/libc/include/WindowsMappings.hpp +++ b/core/libs/prx/libc/include/WindowsMappings.hpp @@ -108,6 +108,17 @@ public: reset(reinterpret_cast(address), bytes); } + void MapZero(void* address, std::size_t bytes) { + std::lock_guard lock(mutex); + if (zeroSection == nullptr && (zeroSection = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READONLY, 0, static_cast(pageBytes), nullptr)) == nullptr) fail("create the read-only zero guest page"); + const auto start = reinterpret_cast(address); + reset(start, bytes); + for (auto cursor = start; cursor < start + bytes; cursor += pageBytes) { + split(cursor, pageBytes); + if (!map(zeroSection, GetCurrentProcess(), reinterpret_cast(cursor), 0, pageBytes, MEM_REPLACE_PLACEHOLDER, PAGE_READONLY, nullptr, 0)) fail("map the read-only zero guest page"); + } + } + void Map(void* address, std::size_t bytes, HANDLE section, std::uint64_t offset, DWORD protection, bool watched = false, bool resident = false, DWORD viewProtection = PAGE_EXECUTE_READWRITE, std::size_t sectionBytes = SIZE_MAX, std::vector>* createdPrivate = nullptr) { if (sectionBytes < bytes && (resident || sectionBytes == 0 || bytes - sectionBytes >= pageBytes)) throw std::runtime_error("shared guest mapping extends a whole page past its section"); std::lock_guard lock(mutex); @@ -1308,6 +1319,7 @@ private: static inline std::atomic queries{0}; #endif std::mutex mutex; + HANDLE zeroSection = nullptr; AllocateFunction allocate = nullptr; MapFunction map = nullptr; UnmapFunction unmap = nullptr; diff --git a/core/libs/prx/libc/include/WindowsScanning.hpp b/core/libs/prx/libc/include/WindowsScanning.hpp index ab3f6ef625..98909e04e8 100644 --- a/core/libs/prx/libc/include/WindowsScanning.hpp +++ b/core/libs/prx/libc/include/WindowsScanning.hpp @@ -9,6 +9,36 @@ namespace LibcDetail { +inline std::string TitleScanset(const char* begin, const char* end, bool negated) { + bool members[256]{}; + for (const char* member = begin; member != end;) { + const auto first = static_cast(member[0]); + if (end - member > 2 && member[1] == '-') { + const auto last = static_cast(member[2]); + for (unsigned value = first; value <= last; ++value) members[value] = true; + member += 3; + } else { + members[first] = true; + ++member; + } + } + bool empty = true; + for (unsigned value = 1; value < 256; ++value) { + if (negated) members[value] = !members[value]; + if (members[value] && value != '^' && value != '-') empty = false; + } + const bool complement = empty; + std::string text = complement ? "[^" : "["; + const auto listed = [&](unsigned value) { return members[value] != complement; }; + if (listed(']')) text += ']'; + for (unsigned value = 1; value < 256; ++value) { + if (listed(value) && value != ']' && value != '-' && value != '^') text += static_cast(value); + } + if (listed('^')) text += '^'; + if (listed('-')) text += '-'; + return text + ']'; +} + template inline int ScanWindowsArguments_nid_no_patch(const char* format, const void* source, TScanner scan) { if (!format || !source) { errno = 22; return EOF; } @@ -46,8 +76,11 @@ inline int ScanWindowsArguments_nid_no_patch(const char* format, const void* sou if ((length == "h" && *format == 'h') || (length == "l" && *format == 'l')) length += *format++; } const char conversion = *format; - if (!conversion) { errno = 22; return EOF; } - if (!std::strchr("diouxXaAeEfFgGcspn[", conversion)) { + if (!conversion || !std::strchr("diouxXaAeEfFgGcspn[SC", conversion)) { + translated.resize(directive); + break; + } + if (conversion == 'S' || conversion == 'C') { NotImplemented_nid_no_patch("vsscanf format conversion"); return EOF; } @@ -55,15 +88,19 @@ inline int ScanWindowsArguments_nid_no_patch(const char* format, const void* sou 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; + else if (!std::strchr("sc[", conversion) || length == "l") translated += length; if (conversion == '[') { - if (*format == '^') translated += *format++; - if (*format == ']') translated += *format++; - while (*format && *format != ']') translated += *format++; - if (*format != ']') { errno = 22; return EOF; } - translated += *format++; - } + const bool negated = *format == '^'; + if (negated) ++format; + const char* begin = format; + if (*format == ']') ++format; + while (*format && *format != ']') ++format; + if (*format != ']') { + translated.resize(directive); + break; + } + translated += TitleScanset(begin, format++, negated); + } else translated += conversion; 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}); diff --git a/core/libs/prx/libc/include/WindowsVerbatimPath.hpp b/core/libs/prx/libc/include/WindowsVerbatimPath.hpp new file mode 100644 index 0000000000..b9e6e03048 --- /dev/null +++ b/core/libs/prx/libc/include/WindowsVerbatimPath.hpp @@ -0,0 +1,265 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSVERBATIMPATH_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_WINDOWSVERBATIMPATH_HPP + +#include +#include +#include +#include +#include +#include + +constexpr std::size_t WindowsMaxPathLength = 260; +constexpr std::size_t WindowsMaxDirectoryPathLength = 248; + +template +constexpr CharT ToAsciiLower_nid_no_patch(CharT c) { + return (c >= static_cast('A') && c <= static_cast('Z')) + ? static_cast(c + (static_cast('a') - static_cast('A'))) + : c; +} + +template +constexpr bool IsDosDeviceStem_nid_no_patch(std::basic_string_view stem) { + while (!stem.empty() && (stem.back() == static_cast(' ') || stem.back() == static_cast('.'))) { + stem.remove_suffix(1); + } + if (stem.size() == 3) { + const auto c0 = ToAsciiLower_nid_no_patch(stem[0]); + const auto c1 = ToAsciiLower_nid_no_patch(stem[1]); + const auto c2 = ToAsciiLower_nid_no_patch(stem[2]); + return (c0 == static_cast('c') && c1 == static_cast('o') && c2 == static_cast('n')) || + (c0 == static_cast('p') && c1 == static_cast('r') && c2 == static_cast('n')) || + (c0 == static_cast('a') && c1 == static_cast('u') && c2 == static_cast('x')) || + (c0 == static_cast('n') && c1 == static_cast('u') && c2 == static_cast('l')); + } + if (stem.size() == 4) { + const auto c0 = ToAsciiLower_nid_no_patch(stem[0]); + const auto c1 = ToAsciiLower_nid_no_patch(stem[1]); + const auto c2 = ToAsciiLower_nid_no_patch(stem[2]); + if ((c0 == static_cast('c') && c1 == static_cast('o') && c2 == static_cast('m')) || + (c0 == static_cast('l') && c1 == static_cast('p') && c2 == static_cast('t'))) { + const auto c3 = stem[3]; + if (c3 >= static_cast('1') && c3 <= static_cast('9')) { + return true; + } + using UCharT = std::make_unsigned_t; + const auto u3 = static_cast(c3); + if (u3 == static_cast(0x00B9) || u3 == static_cast(0x00B2) || u3 == static_cast(0x00B3)) { + return true; + } + } + } + if constexpr (sizeof(CharT) == 1) { + if (stem.size() == 5) { + const auto c0 = ToAsciiLower_nid_no_patch(stem[0]); + const auto c1 = ToAsciiLower_nid_no_patch(stem[1]); + const auto c2 = ToAsciiLower_nid_no_patch(stem[2]); + if ((c0 == static_cast('c') && c1 == static_cast('o') && c2 == static_cast('m')) || + (c0 == static_cast('l') && c1 == static_cast('p') && c2 == static_cast('t'))) { + using UCharT = std::make_unsigned_t; + const auto u3 = static_cast(stem[3]); + const auto u4 = static_cast(stem[4]); + if (u3 == static_cast(0xC2) && + (u4 == static_cast(0xB9) || u4 == static_cast(0xB2) || u4 == static_cast(0xB3))) { + return true; + } + } + } + } + if (stem.size() == 6) { + const auto c0 = ToAsciiLower_nid_no_patch(stem[0]); + const auto c1 = ToAsciiLower_nid_no_patch(stem[1]); + const auto c2 = ToAsciiLower_nid_no_patch(stem[2]); + const auto c3 = ToAsciiLower_nid_no_patch(stem[3]); + const auto c4 = ToAsciiLower_nid_no_patch(stem[4]); + const auto c5 = stem[5]; + return c0 == static_cast('c') && c1 == static_cast('o') && c2 == static_cast('n') && + c3 == static_cast('i') && c4 == static_cast('n') && c5 == static_cast('$'); + } + if (stem.size() == 7) { + const auto c0 = ToAsciiLower_nid_no_patch(stem[0]); + const auto c1 = ToAsciiLower_nid_no_patch(stem[1]); + const auto c2 = ToAsciiLower_nid_no_patch(stem[2]); + const auto c3 = ToAsciiLower_nid_no_patch(stem[3]); + const auto c4 = ToAsciiLower_nid_no_patch(stem[4]); + const auto c5 = ToAsciiLower_nid_no_patch(stem[5]); + const auto c6 = stem[6]; + return c0 == static_cast('c') && c1 == static_cast('o') && c2 == static_cast('n') && + c3 == static_cast('o') && c4 == static_cast('u') && c5 == static_cast('t') && + c6 == static_cast('$'); + } + return false; +} + +template +inline bool IsDosDeviceStem_nid_no_patch(const std::basic_string& stem) { + return IsDosDeviceStem_nid_no_patch(std::basic_string_view(stem)); +} + +template +inline bool IsDosDeviceStem_nid_no_patch(const CharT* stem) { + return IsDosDeviceStem_nid_no_patch(std::basic_string_view(stem)); +} + +template +constexpr bool ComponentNeedsVerbatimPrefix_nid_no_patch(std::basic_string_view component) { + if (component.empty()) return false; + if (component.size() == 1 && component[0] == static_cast('.')) return false; + if (component.size() == 2 && component[0] == static_cast('.') && component[1] == static_cast('.')) return false; + if (component.back() == static_cast('.') || component.back() == static_cast(' ')) return true; + const auto dotPos = component.find(static_cast('.')); + const auto stem = (dotPos == std::basic_string_view::npos) ? component : component.substr(0, dotPos); + return IsDosDeviceStem_nid_no_patch(stem); +} + +template +inline bool ComponentNeedsVerbatimPrefix_nid_no_patch(const std::basic_string& component) { + return ComponentNeedsVerbatimPrefix_nid_no_patch(std::basic_string_view(component)); +} + +template +inline bool ComponentNeedsVerbatimPrefix_nid_no_patch(const CharT* component) { + return ComponentNeedsVerbatimPrefix_nid_no_patch(std::basic_string_view(component)); +} + +template +bool PathNeedsVerbatimPrefix_nid_no_patch(std::basic_string_view path) { + const CharT backslash = static_cast('\\'); + const CharT slash = static_cast('/'); + const CharT qmark = static_cast('?'); + const CharT dot = static_cast('.'); + if (path.size() >= 4 && + (path[0] == backslash || path[0] == slash) && + (path[1] == backslash || path[1] == slash) && + (path[2] == qmark || path[2] == dot) && + (path[3] == backslash || path[3] == slash)) { + return false; + } + std::size_t start = 0; + while (start < path.size()) { + while (start < path.size() && (path[start] == slash || path[start] == backslash)) { + ++start; + } + if (start >= path.size()) break; + std::size_t end = start; + while (end < path.size() && path[end] != slash && path[end] != backslash) { + ++end; + } + auto component = path.substr(start, end - start); + if (component.size() >= 2 && component[1] == static_cast(':') && + ((component[0] >= static_cast('a') && component[0] <= static_cast('z')) || + (component[0] >= static_cast('A') && component[0] <= static_cast('Z')))) { + component = component.substr(2); + } + if (ComponentNeedsVerbatimPrefix_nid_no_patch(component)) return true; + start = end; + } + return false; +} + +template +inline bool PathNeedsVerbatimPrefix_nid_no_patch(const std::basic_string& path) { + return PathNeedsVerbatimPrefix_nid_no_patch(std::basic_string_view(path)); +} + +template +inline bool PathNeedsVerbatimPrefix_nid_no_patch(const CharT* path) { + return PathNeedsVerbatimPrefix_nid_no_patch(std::basic_string_view(path)); +} + +inline bool NeedsVerbatimPrefix_nid_no_patch(const std::filesystem::path& path) { + using CharT = std::filesystem::path::value_type; + const auto& native = path.native(); + return PathNeedsVerbatimPrefix_nid_no_patch(std::basic_string_view(native)); +} + +template +inline StringT FormatVerbatimWindowsPath_nid_no_patch(const StringT& normalizedPath) { + using CharT = typename StringT::value_type; + const CharT backslash = static_cast('\\'); + const CharT slash = static_cast('/'); + const CharT qmark = static_cast('?'); + const CharT dot = static_cast('.'); + + if (normalizedPath.size() >= 4 && + (normalizedPath[0] == backslash || normalizedPath[0] == slash) && + (normalizedPath[1] == backslash || normalizedPath[1] == slash) && + (normalizedPath[2] == qmark || normalizedPath[2] == dot) && + (normalizedPath[3] == backslash || normalizedPath[3] == slash)) { + StringT result = normalizedPath; + for (auto& c : result) { + if (c == slash) c = backslash; + } + return result; + } + + StringT clean = normalizedPath; + for (auto& c : clean) { + if (c == slash) c = backslash; + } + + if (clean.size() >= 2 && clean[0] == backslash && clean[1] == backslash) { + if constexpr (std::is_same_v) { + return std::wstring(L"\\\\?\\UNC\\") + clean.substr(2); + } else { + return std::string(R"(\\?\UNC\)") + clean.substr(2); + } + } + + if constexpr (std::is_same_v) { + return std::wstring(L"\\\\?\\") + clean; + } else { + return std::string(R"(\\?\)") + clean; + } +} + +template +inline std::basic_string MakeVerbatimWindowsPathForTest_nid_no_patch( + std::basic_string_view normalizedPath, + std::size_t maxPathLength = WindowsMaxDirectoryPathLength) { + if (normalizedPath.size() < maxPathLength && !PathNeedsVerbatimPrefix_nid_no_patch(normalizedPath)) { + return std::basic_string(normalizedPath); + } + return FormatVerbatimWindowsPath_nid_no_patch(std::basic_string(normalizedPath)); +} + +template +inline std::basic_string MakeVerbatimWindowsPathForTest_nid_no_patch( + const std::basic_string& normalizedPath, + std::size_t maxPathLength = WindowsMaxDirectoryPathLength) { + return MakeVerbatimWindowsPathForTest_nid_no_patch(std::basic_string_view(normalizedPath), maxPathLength); +} + +template +inline std::basic_string MakeVerbatimWindowsPathForTest_nid_no_patch( + const CharT* normalizedPath, + std::size_t maxPathLength = WindowsMaxDirectoryPathLength) { + return MakeVerbatimWindowsPathForTest_nid_no_patch(std::basic_string_view(normalizedPath), maxPathLength); +} + +#ifdef _WIN32 +inline std::filesystem::path NativeVerbatimPath_nid_no_patch( + const std::filesystem::path& path, + std::error_code& error, + std::size_t maxPathLength = WindowsMaxDirectoryPathLength) { + const auto& input = path.native(); + if (input.empty()) return path; + if (input.starts_with(L"\\\\?\\") || input.starts_with(L"\\\\.\\")) return path; + auto absolute = path.is_absolute() ? path : (std::filesystem::current_path(error) / path); + if (error) return {}; + absolute = absolute.lexically_normal().make_preferred(); + const auto& native = absolute.native(); + if (native.size() < maxPathLength && !NeedsVerbatimPrefix_nid_no_patch(absolute)) return absolute; + if (native.starts_with(L"\\\\?\\") || native.starts_with(L"\\\\.\\")) return absolute; + return std::filesystem::path(FormatVerbatimWindowsPath_nid_no_patch(native)); +} +#else +inline std::filesystem::path NativeVerbatimPath_nid_no_patch( + const std::filesystem::path& path, + std::error_code&, + std::size_t = WindowsMaxDirectoryPathLength) { + return path; +} +#endif + +#endif diff --git a/core/libs/prx/libc/src/AppArchive.cpp b/core/libs/prx/libc/src/AppArchive.cpp index 8195fed12f..ef261b3688 100644 --- a/core/libs/prx/libc/src/AppArchive.cpp +++ b/core/libs/prx/libc/src/AppArchive.cpp @@ -35,9 +35,13 @@ bool SameComponent(std::string_view left, std::string_view right) { Archive& Opened() { auto& archive = State(); + { + std::lock_guard lock(archive.mutex); + if (archive.opened) return archive; + } + const auto app0 = ResolveDiskPath_nid_no_patch("/app0").lexically_normal(); std::lock_guard lock(archive.mutex); if (archive.opened) return archive; - const auto app0 = ResolveDiskPath_nid_no_patch("/app0").lexically_normal(); auto candidate = app0; candidate += ".zar"; std::error_code error; diff --git a/core/libs/prx/libc/src/DeviceName.cpp b/core/libs/prx/libc/src/DeviceName.cpp new file mode 100644 index 0000000000..b9b6fcf032 --- /dev/null +++ b/core/libs/prx/libc/src/DeviceName.cpp @@ -0,0 +1,55 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +namespace { +constexpr std::uint32_t NoDevice = 0xFFFFFFFFu; +constexpr std::uint16_t TypeMask = 0170000; +constexpr std::uint16_t CharacterType = 0020000; +constexpr std::uint16_t BlockType = 0060000; +constexpr int ErrorOverflow = 84; +constexpr std::size_t SpecialNameLength = 63; + +void CopyString(char* destination, std::size_t size, const char* text, std::size_t length) { + if (size == 0) return; + const std::size_t count = std::min(length, size - 1); + std::copy_n(text, count, destination); + destination[count] = '\0'; +} +} + +extern "C" char* APS5_VABI devname_r_nid_postfix(std::uint32_t device, std::uint16_t type, char* buffer, int length) { + const auto size = static_cast(length); + const bool character = (type & TypeMask) == CharacterType; + if (device == NoDevice || !(character || (device & TypeMask) == BlockType)) { + CopyString(buffer, size, "#NODEV", 6); + return buffer; + } + char text[4 + 2 + sizeof(device) * 2 + 1]; + char* end = text; + *end++ = '#'; + *end++ = character ? 'C' : 'B'; + *end++ = ':'; + if (device != 0) { + *end++ = '0'; + *end++ = 'x'; + } + int digits = 1; + for (std::uint32_t rest = device >> 4; rest != 0; rest >>= 4) ++digits; + for (int index = digits - 1; index >= 0; --index) *end++ = "0123456789abcdef"[(device >> (index * 4)) & 0xF]; + if (size > static_cast(INT_MAX) + 1) { + *__error_nid_postfix() = ErrorOverflow; + *buffer = '\0'; + return buffer; + } + CopyString(buffer, size, text, static_cast(end - text)); + return buffer; +} + +extern "C" char* APS5_VABI devname_nid_postfix(std::uint32_t device, std::uint16_t type) { + static char buffer[SpecialNameLength + 1]; + return devname_r_nid_postfix(device, type, buffer, sizeof(buffer)); +} diff --git a/core/libs/prx/libc/src/Directory.cpp b/core/libs/prx/libc/src/Directory.cpp index 63848ea167..374de0f86b 100644 --- a/core/libs/prx/libc/src/Directory.cpp +++ b/core/libs/prx/libc/src/Directory.cpp @@ -17,6 +17,7 @@ #include #ifdef _WIN32 #include "prx/libc/include/WindowsDescriptor.hpp" +#include "prx/libc/include/WindowsVerbatimPath.hpp" #include #include #endif @@ -63,12 +64,15 @@ int NativeOpenError(DWORD error) { return WindowsOpenError_nid_no_patch(error); } NativeDirectory* OpenNative(const std::filesystem::path& path) { - const DWORD attributes = GetFileAttributesW(path.c_str()); + std::error_code error; + const auto verbatim = NativeVerbatimPath_nid_no_patch(path, error, WindowsMaxDirectoryPathLength); + const auto& openPath = error ? path : verbatim; + const DWORD attributes = GetFileAttributesW(openPath.c_str()); if (attributes == INVALID_FILE_ATTRIBUTES) { errno = NativeOpenError(GetLastError()); return nullptr; } if ((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { errno = ENOTDIR; if ((attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { - const HANDLE followed = CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + const HANDLE followed = CreateFileW(openPath.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (followed != INVALID_HANDLE_VALUE) CloseHandle(followed); else if (GetLastError() == ERROR_CANT_RESOLVE_FILENAME) errno = ELOOP; @@ -76,7 +80,7 @@ NativeDirectory* OpenNative(const std::filesystem::path& path) { return nullptr; } auto native = std::make_unique_for_overwrite(); - native->handle = CreateFileW(path.c_str(), FILE_LIST_DIRECTORY | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + native->handle = CreateFileW(openPath.c_str(), FILE_LIST_DIRECTORY | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (native->handle == INVALID_HANDLE_VALUE) { errno = NativeOpenError(GetLastError()); return nullptr; } BY_HANDLE_FILE_INFORMATION identity{}; diff --git a/core/libs/prx/libc/src/Errors.cpp b/core/libs/prx/libc/src/Errors.cpp index f934c4de09..93bbf075f5 100644 --- a/core/libs/prx/libc/src/Errors.cpp +++ b/core/libs/prx/libc/src/Errors.cpp @@ -93,7 +93,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)); - + _Stderr_nid_postfix.Orientation(-1); 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 d942dbff41..7a97bedd10 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -23,6 +23,7 @@ #include #include #include "prx/libc/include/WindowsDescriptor.hpp" +#include "prx/libc/include/WindowsVerbatimPath.hpp" #endif #include "prx/libc/include/SonyCrt.hpp" @@ -69,16 +70,10 @@ static std::optional ParseFileMode(const char* mode) { 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; + std::error_code pathError; + const auto resolved = NativeVerbatimPath_nid_no_patch(path, pathError); + if (pathError) { errno = pathError.default_error_condition().value(); return std::nullopt; } + return resolved.native(); } #endif @@ -159,33 +154,33 @@ static bool IsPipeStream(std::FILE* native) { } #endif -static void SwapElements(unsigned char* left, unsigned char* right, size_t size) { +static void ReentrantSwapElements(unsigned char* left, unsigned char* right, size_t size) { for (size_t index = 0; index < size; ++index) std::swap(left[index], right[index]); } -static void SwapRanges(unsigned char* left, unsigned char* right, size_t bytes) { - if (bytes > 0) SwapElements(left, right, bytes); +static void ReentrantSwapRanges(unsigned char* left, unsigned char* right, size_t bytes) { + if (bytes > 0) ReentrantSwapElements(left, right, bytes); } template -static unsigned char* MedianOfThree(unsigned char* a, unsigned char* b, unsigned char* c, Compare compare) { +static unsigned char* ReentrantMedianOfThree(unsigned char* a, unsigned char* b, unsigned char* c, Compare compare) { return compare(a, b) < 0 ? (compare(b, c) < 0 ? b : (compare(a, c) < 0 ? c : a)) : (compare(b, c) > 0 ? b : (compare(a, c) < 0 ? a : c)); } template -static void InsertionSort(unsigned char* base, size_t count, size_t size, Compare compare) { +static void ReentrantInsertionSort(unsigned char* base, size_t count, size_t size, Compare compare) { for (unsigned char* next = base + size; next < base + count * size; next += size) for (unsigned char* element = next; element > base && compare(element - size, element) > 0; element -= size) - SwapElements(element, element - size, size); + ReentrantSwapElements(element, element - size, size); } template -static void SortElements(unsigned char* base, size_t count, size_t size, Compare compare) { +static void ReentrantSortElements(unsigned char* base, size_t count, size_t size, Compare compare) { for (;;) { if (count < 7) { - InsertionSort(base, count, size, compare); + ReentrantInsertionSort(base, count, size, compare); return; } unsigned char* middle = base + (count / 2) * size; @@ -194,13 +189,13 @@ static void SortElements(unsigned char* base, size_t count, size_t size, Compare unsigned char* high = base + (count - 1) * size; if (count > 40) { const size_t step = (count / 8) * size; - low = MedianOfThree(low, low + step, low + 2 * step, compare); - middle = MedianOfThree(middle - step, middle, middle + step, compare); - high = MedianOfThree(high - 2 * step, high - step, high, compare); + low = ReentrantMedianOfThree(low, low + step, low + 2 * step, compare); + middle = ReentrantMedianOfThree(middle - step, middle, middle + step, compare); + high = ReentrantMedianOfThree(high - 2 * step, high - step, high, compare); } - middle = MedianOfThree(low, middle, high, compare); + middle = ReentrantMedianOfThree(low, middle, high, compare); } - SwapElements(base, middle, size); + ReentrantSwapElements(base, middle, size); unsigned char* equalLow = base + size; unsigned char* scanLow = equalLow; unsigned char* scanHigh = base + (count - 1) * size; @@ -211,7 +206,7 @@ static void SortElements(unsigned char* base, size_t count, size_t size, Compare while (scanLow <= scanHigh && (result = compare(scanLow, base)) <= 0) { if (result == 0) { swapped = true; - SwapElements(equalLow, scanLow, size); + ReentrantSwapElements(equalLow, scanLow, size); equalLow += size; } scanLow += size; @@ -219,41 +214,181 @@ static void SortElements(unsigned char* base, size_t count, size_t size, Compare while (scanLow <= scanHigh && (result = compare(scanHigh, base)) >= 0) { if (result == 0) { swapped = true; - SwapElements(scanHigh, equalHigh, size); + ReentrantSwapElements(scanHigh, equalHigh, size); equalHigh -= size; } scanHigh -= size; } if (scanLow > scanHigh) break; - SwapElements(scanLow, scanHigh, size); + ReentrantSwapElements(scanLow, scanHigh, size); swapped = true; scanLow += size; scanHigh -= size; } if (!swapped) { - InsertionSort(base, count, size, compare); + ReentrantInsertionSort(base, count, size, compare); return; } unsigned char* const end = base + count * size; size_t moved = std::min(equalLow - base, scanLow - equalLow); - SwapRanges(base, scanLow - moved, moved); + ReentrantSwapRanges(base, scanLow - moved, moved); moved = std::min(equalHigh - scanHigh, end - equalHigh - size); - SwapRanges(scanLow, end - moved, moved); + ReentrantSwapRanges(scanLow, end - moved, moved); const size_t lowBytes = scanLow - equalLow; const size_t highBytes = equalHigh - scanHigh; if (lowBytes <= highBytes) { - if (lowBytes > size) SortElements(base, lowBytes / size, size, compare); + if (lowBytes > size) ReentrantSortElements(base, lowBytes / size, size, compare); if (highBytes <= size) return; base = end - highBytes; count = highBytes / size; } else { - if (highBytes > size) SortElements(end - highBytes, highBytes / size, size, compare); + if (highBytes > size) ReentrantSortElements(end - highBytes, highBytes / size, size, compare); if (lowBytes <= size) return; count = lowBytes / size; } } } +static void SwapElements(unsigned char* left, unsigned char* right, size_t size) { + if (left == right) return; + std::swap_ranges(left, left + size, right); +} + +static void RotateRight(unsigned char* first, unsigned char* last, size_t size) { + unsigned char buffer[256]; + for (size_t offset = 0; offset < size; offset += sizeof(buffer)) { + const size_t chunk = std::min(sizeof(buffer), size - offset); + std::memcpy(buffer, last + offset, chunk); + for (unsigned char* element = last; element != first; element -= size) std::memcpy(element + offset, element - size + offset, chunk); + std::memcpy(first + offset, buffer, chunk); + } +} + +template +static void MedianOfThree(unsigned char* first, unsigned char* middle, unsigned char* last, size_t size, Compare& compare) { + if (compare(middle, first) < 0) SwapElements(middle, first, size); + if (compare(last, middle) < 0) SwapElements(last, middle, size); + if (compare(middle, first) < 0) SwapElements(middle, first, size); +} + +template +static void Median(unsigned char* first, unsigned char* middle, unsigned char* last, size_t size, Compare& compare) { + const size_t distance = static_cast(last - first) / size; + if (distance <= 40) { + MedianOfThree(first, middle, last, size, compare); + return; + } + const size_t step = (distance / 8 + 1) * size; + MedianOfThree(first, first + step, first + 2 * step, size, compare); + MedianOfThree(middle - step, middle, middle + step, size, compare); + MedianOfThree(last - 2 * step, last - step, last, size, compare); + MedianOfThree(first + step, middle, last - step, size, compare); +} + +template +static void AdjustHeap(unsigned char* base, size_t hole, size_t count, size_t size, Compare& compare) { + const auto at = [base, size](size_t index) { return base + index * size; }; + size_t index = hole; + while (2 * index + 2 <= count) { + size_t child = 2 * index + 2; + if (child == count || compare(at(child), at(child - 1)) < 0) --child; + SwapElements(at(index), at(child), size); + index = child; + } + while (index > hole) { + const size_t parent = (index - 1) / 2; + if (compare(at(index), at(parent)) <= 0) break; + SwapElements(at(parent), at(index), size); + index = parent; + } +} + +template +static void HeapSort(unsigned char* base, size_t count, size_t size, Compare& compare) { + for (size_t hole = count / 2; hole-- > 0;) AdjustHeap(base, hole, count, size, compare); + for (size_t remaining = count; remaining >= 2;) { + SwapElements(base, base + (remaining - 1) * size, size); + if (--remaining < 2) break; + AdjustHeap(base, 0, remaining, size, compare); + } +} + +template +static void InsertionSort(unsigned char* base, size_t count, size_t size, Compare& compare) { + for (size_t index = 1; index < count; ++index) { + unsigned char* const next = base + index * size; + if (compare(next, base) < 0) { + RotateRight(base, next, size); + continue; + } + unsigned char* stop = next - size; + while (compare(next, stop) < 0) stop -= size; + if (stop + size != next) RotateRight(stop + size, next, size); + } +} + +template +static void SortElements(unsigned char* first, size_t count, size_t ideal, size_t size, Compare& compare) { + while (ideal != 0 && count >= 33) { + unsigned char* const last = first + count * size; + unsigned char* const middle = first + count / 2 * size; + Median(first, middle, last - size, size, compare); + unsigned char* pivotFirst = middle; + while (first < pivotFirst && compare(pivotFirst - size, pivotFirst) == 0) pivotFirst -= size; + unsigned char* pivotLast = middle + size; + while (pivotLast < last && compare(pivotLast, pivotFirst) == 0) pivotLast += size; + unsigned char* greaterFirst = pivotLast; + unsigned char* greaterLast = pivotFirst; + for (;;) { + for (; greaterFirst < last; greaterFirst += size) { + const int result = compare(pivotFirst, greaterFirst); + if (result < 0) continue; + if (result > 0) break; + SwapElements(pivotLast, greaterFirst, size); + pivotLast += size; + } + for (; first < greaterLast; greaterLast -= size) { + const int result = compare(greaterLast - size, pivotFirst); + if (result < 0) continue; + if (result > 0) break; + pivotFirst -= size; + SwapElements(pivotFirst, greaterLast - size, size); + } + if (greaterLast == first && greaterFirst == last) break; + if (greaterLast == first) { + if (pivotLast != greaterFirst) SwapElements(pivotFirst, pivotLast, size); + SwapElements(pivotFirst, greaterFirst, size); + greaterFirst += size; + pivotFirst += size; + pivotLast += size; + } else if (greaterFirst == last) { + greaterLast -= size; + pivotFirst -= size; + pivotLast -= size; + if (greaterLast != pivotFirst) SwapElements(greaterLast, pivotFirst, size); + SwapElements(pivotFirst, pivotLast, size); + } else { + greaterLast -= size; + SwapElements(greaterFirst, greaterLast, size); + greaterFirst += size; + } + } + ideal = (ideal >> 1) + (ideal >> 2); + const size_t lowCount = static_cast(pivotFirst - first) / size; + const size_t highCount = static_cast(last - pivotLast) / size; + if (lowCount > highCount) { + SortElements(pivotLast, highCount, ideal, size, compare); + count = lowCount; + } else { + SortElements(first, lowCount, ideal, size, compare); + first = pivotLast; + count = highCount; + } + } + if (count >= 33) HeapSort(first, count, size, compare); + else if (count >= 2) InsertionSort(first, count, size, compare); +} + namespace { template @@ -327,12 +462,42 @@ FileStream* APS5_VABI fdopen_nid_postfix(int descriptor, const char* mode) { } catch (const std::bad_alloc&) { errno = 12; return nullptr; } } +static bool IsRandomDevicePath(std::string_view path) { + return path == "/dev/random" || path == "/dev/urandom"; +} + +#ifdef _WIN32 +static const std::filesystem::path NullDevicePath = "NUL"; +#else +static const std::filesystem::path NullDevicePath = "/dev/null"; +#endif + +static std::FILE* OpenNullDevice(const std::filesystem::path& = {}, const std::string& = {}) { + return std::fopen(NullDevicePath.string().c_str(), "rb"); +} + static int OpenFailure(const std::filesystem::path& path, bool writes, int hostError) { const int error = GuestErrnoFromHost_nid_no_patch(hostError); std::error_code status; return writes && error == 13 && std::filesystem::is_directory(path, status) ? 21 : error; } +static void* (*g_beginClosingDescriptor)(int) = nullptr; +static void (*g_endClosingDescriptor)(void*) = nullptr; + +void LibcRegisterClosedDescriptors_nid_no_patch(void* (*begin)(int), void (*end)(void*)) { + g_beginClosingDescriptor = begin; + g_endClosingDescriptor = end; +} + +void* BeginClosingDescriptor_nid_no_patch(int descriptor) { + return g_beginClosingDescriptor ? g_beginClosingDescriptor(descriptor) : nullptr; +} + +void EndClosingDescriptor_nid_no_patch(void* closing) { + if (g_endClosingDescriptor) g_endClosingDescriptor(closing); +} + #ifdef _WIN32 static int (*g_openDirectoryDescriptor)(const wchar_t*) = nullptr; static void (*g_forgetDirectoryDescriptor)(int) = nullptr; @@ -386,6 +551,17 @@ FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode const auto parsed = ParseFileMode(mode); if (!parsed) { errno = 22; return nullptr; } try { + if (IsRandomDevicePath(filename)) { + if (parsed->writes) throw std::runtime_error(std::string(__func__) + ": writing to " + filename + " is not implemented"); + if (!stream->Reopen(NullDevicePath, "rb", OpenNullDevice)) { + const int error = errno; + if (stream->IsDynamic()) delete stream; + errno = error; + return nullptr; + } + stream->MarkRandomDevice(); + return stream; + } std::filesystem::path path; if (const int error = *filename ? ResolvePathError_nid_no_patch(filename, path) : 2) { if (stream->IsHostStandard()) stream->Detach(); @@ -437,13 +613,9 @@ FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) errno = EINVAL; return nullptr; } - if (const std::string_view path(filename); path == "/dev/random" || path == "/dev/urandom") { + if (IsRandomDevicePath(filename)) { if (parsed->writes) throw std::runtime_error(std::string(__func__) + ": writing to " + filename + " is not implemented"); -#ifdef _WIN32 - std::unique_ptr device(std::fopen("NUL", "rb"), std::fclose); -#else - std::unique_ptr device(std::fopen("/dev/null", "rb"), std::fclose); -#endif + std::unique_ptr device(OpenNullDevice(), std::fclose); if (!device) return nullptr; auto stream = std::make_unique(device.get(), true); device.release(); @@ -542,6 +714,7 @@ size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, File if (stream && !stream->IsOpen()) { errno = 9; return 0; } auto* handle = GetStreamHandle(stream); if (size == 0 || count == 0) return 0; + stream->Orientation(-1); if (!buffer) throw std::runtime_error("fread: null buffer"); const auto bytes = size * count; if (bytes > std::numeric_limits::max() - reinterpret_cast(buffer)) throw std::overflow_error("fread: buffer address overflow"); @@ -563,6 +736,7 @@ size_t APS5_VABI fwrite_nid_postfix(const void* buffer, size_t size, size_t coun if (stream && !stream->IsOpen()) { errno = 9; return 0; } auto* handle = GetNativeStream(stream); if (size == 0 || count == 0) return 0; + stream->Orientation(-1); if (!buffer) throw std::runtime_error("fwrite: null buffer"); if (count > std::numeric_limits::max() / size) throw std::overflow_error("fwrite: buffer size overflow"); const auto bytes = size * count; @@ -635,6 +809,7 @@ int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream) { if (!str) throw std::runtime_error("fputs: null string"); if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); + stream->Orientation(-1); if (*str != '\0' && !stream->SwitchToWriting()) return -1; const int result = std::fputs(str, native); stream->SyncStatus(); @@ -886,14 +1061,10 @@ int APS5_VABI heapsort_nid_postfix(void* base, size_t count, size_t size, GuestC } void APS5_VABI qsort_nid_postfix(void* base, size_t count, size_t size, int (APS5_VABI *compare)(const void*, const void*)) { - if (!compare) throw std::invalid_argument("qsort: null comparator"); - if (size == 0) throw std::invalid_argument("qsort: zero element size"); - if (count == 0) return; - if (!base) throw std::invalid_argument("qsort: null base"); - if (count > std::numeric_limits::max() / size) throw std::overflow_error("qsort: array size overflow"); - if (count == 1) return; - - SortElements(static_cast(base), count, size, compare); + if (!base || !compare) return; + if (size == 0 && count >= 33) throw std::invalid_argument("qsort: zero element size"); + if (size != 0 && count > std::numeric_limits::max() / size) throw std::overflow_error("qsort: array size overflow"); + SortElements(static_cast(base), count, count, size, compare); } int APS5_VABI qsort_s_nid_postfix(void* base, size_t count, size_t size, GuestContextCompare compare, void* context) { @@ -907,8 +1078,10 @@ int APS5_VABI qsort_s_nid_postfix(void* base, size_t count, size_t size, GuestCo SonyInvokeConstraintHandler(violation, nullptr, 22); return 22; } - if (count > 1 && size != 0) - SortElements(static_cast(base), count, size, [compare, context](const void* left, const void* right) { return compare(left, right, context); }); + if (count == 0) return 0; + if (size == 0 && count >= 33) throw std::invalid_argument("qsort_s: zero element size"); + auto withContext = [compare, context](const void* left, const void* right) { return compare(left, right, context); }; + SortElements(static_cast(base), count, count, size, withContext); return 0; } @@ -920,7 +1093,7 @@ void APS5_VABI qsort_r_nid_postfix(void* base, size_t count, size_t size, void* if (count > std::numeric_limits::max() / size) throw std::overflow_error("qsort_r: array size overflow"); if (count == 1) return; - SortElements(static_cast(base), count, size, [compare, thunk](const void* left, const void* right) { return compare(thunk, left, right); }); + ReentrantSortElements(static_cast(base), count, size, [compare, thunk](const void* left, const void* right) { return compare(thunk, left, right); }); } } diff --git a/core/libs/prx/libc/src/Filesystem.cpp b/core/libs/prx/libc/src/Filesystem.cpp index b6c54e3d2d..4664d57ff0 100644 --- a/core/libs/prx/libc/src/Filesystem.cpp +++ b/core/libs/prx/libc/src/Filesystem.cpp @@ -8,15 +8,19 @@ #define NOMINMAX #endif #include +#include "prx/libc/include/WindowsVerbatimPath.hpp" #else #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, + std::error_code pathError; + const auto verbatim = NativeVerbatimPath_nid_no_patch(path, pathError); + const auto& targetPath = pathError ? path : verbatim; + const DWORD attributes = GetFileAttributesW(targetPath.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) return targetPath; + const HANDLE handle = CreateFileW(targetPath.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'); @@ -147,17 +151,20 @@ extern "C" int APS5_VABI remove_nid_postfix(const char* path) { } std::error_code error; #ifdef _WIN32 - const DWORD attributes = GetFileAttributesW(resolved.c_str()); + std::error_code pathError; + const auto verbatim = NativeVerbatimPath_nid_no_patch(resolved, pathError); + const auto& targetPath = pathError ? resolved : verbatim; + const DWORD attributes = GetFileAttributesW(targetPath.c_str()); bool removed = false; if (attributes != INVALID_FILE_ATTRIBUTES) { const DWORD writable = attributes & ~FILE_ATTRIBUTE_READONLY; const bool unlocked = writable != attributes && - SetFileAttributesW(resolved.c_str(), writable ? writable : FILE_ATTRIBUTE_NORMAL); + SetFileAttributesW(targetPath.c_str(), writable ? writable : FILE_ATTRIBUTE_NORMAL); removed = (attributes & FILE_ATTRIBUTE_DIRECTORY) ? - RemoveDirectoryW(resolved.c_str()) != 0 : DeleteFileW(resolved.c_str()) != 0; + RemoveDirectoryW(targetPath.c_str()) != 0 : DeleteFileW(targetPath.c_str()) != 0; if (!removed && unlocked) { const DWORD nativeError = GetLastError(); - SetFileAttributesW(resolved.c_str(), attributes); + SetFileAttributesW(targetPath.c_str(), attributes); SetLastError(nativeError); } } diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index 087f3b15b7..6d20c4f2fc 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -58,6 +58,11 @@ bool RequiresGuestFormatting(const char* format) { std::size_t HostFormatSize(std::size_t size) { return std::min(size, static_cast(INT_MAX) + 1); } +#endif + +bool IsWideScan(const std::string& length, char conversion) { + return conversion == 'S' || conversion == 'C' || (length == "l" && (conversion == 's' || conversion == 'c' || conversion == '[')); +} bool HasGuestWideScan(const char* format) { if (format == nullptr) return false; @@ -65,8 +70,9 @@ bool HasGuestWideScan(const char* format) { ++format; if (*format == '%') { ++format; continue; } while (*format && std::strchr("-+ #0'0123456789.*$", *format)) ++format; - if (*format == 'S' || *format == 'C' || (*format == 'l' && format[1] != '\0' && std::strchr("sc[", format[1]))) - return true; + const char* length = format; + while (*format && std::strchr("hljztL", *format)) ++format; + if (*format && IsWideScan(std::string(length, format), *format)) return true; if (*format == '[') { ++format; if (*format == '^') ++format; @@ -84,14 +90,6 @@ bool ScansWideInProcess(const char* format) { return true; } -void RejectGuestWideScan(const char* format) { - if (HasGuestWideScan(format)) NotImplemented_nid_no_patch("fscanf 16-bit wide conversion on Linux"); -} - -bool IsWideScan(const std::string& length, char conversion) { - return conversion == 'S' || conversion == 'C' || (length == "l" && (conversion == 's' || conversion == 'c' || conversion == '[')); -} - const char* CutAtQuadScanDirective(const char* format, std::string& head) { const char* const quad = LibcDetail::FindQuadScanDirective(format); if (quad == nullptr) return format; @@ -99,12 +97,11 @@ const char* CutAtQuadScanDirective(const char* format, std::string& head) { return head.c_str(); } -#endif - std::string HostLength(const std::string& length, char conversion) { if (std::strchr("sc[", conversion) && length != "l") return {}; #ifdef _WIN32 if (length == "l" && std::strchr("diouxXn", conversion) != nullptr) return "ll"; + if (length != "l" && std::strchr("sc[", conversion) != nullptr) return std::string(); #else (void)conversion; #endif @@ -207,6 +204,7 @@ public: int Peek() const { return *position == '\0' ? EOF : static_cast(*position); } void Skip() { ++position; } + void PutBack(int byte) { if (byte != EOF) --position; } void Advance(std::ptrdiff_t count) { position += count; } std::ptrdiff_t Consumed() const { return position - start; } const char* Remaining() const { return position; } @@ -236,6 +234,12 @@ public: ++consumed; } + void PutBack(int byte) { + if (byte == EOF) return; + if (std::ungetc(byte, file) == EOF) throw std::runtime_error("wide scan character pushback failed"); + --consumed; + } + void Advance(std::ptrdiff_t count) { consumed += count; } std::ptrdiff_t Consumed() const { return consumed; } @@ -271,7 +275,10 @@ int ScanSource(TSource source, const char* format, bool secure, NextPointer next unsigned long width = 0; while (std::isdigit(static_cast(*cursor))) width = width * 10 + static_cast(*cursor++ - '0'); const char* lengthStart = cursor; - while (std::strchr("hljztL", *cursor) != nullptr && *cursor != '\0') ++cursor; + if (*cursor != '\0' && std::strchr("hljztL", *cursor)) { + const char first = *cursor++; + if ((first == 'h' || first == 'l') && *cursor == first) ++cursor; + } const std::string length(lengthStart, cursor); const char conversion = *cursor; if (conversion == '\0') return finish(assigned); @@ -282,7 +289,12 @@ int ScanSource(TSource source, const char* format, bool secure, NextPointer next if (*cursor == ']') ++cursor; while (*cursor != '\0' && *cursor != ']') ++cursor; if (*cursor != ']') return finish(assigned); +#ifdef _WIN32 + const bool negated = setStart[1] == '^'; + specifier = LibcDetail::TitleScanset(setStart + 1 + negated, cursor, negated); +#else specifier.assign(setStart, cursor + 1); +#endif } ++cursor; if (conversion == 'n') { @@ -300,15 +312,13 @@ int ScanSource(TSource source, const char* format, bool secure, NextPointer next } continue; } -#ifndef _WIN32 - 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 (IsWideScan(length, conversion)) { + if (conversion == 'C') return finish(assigned); + const char kind = conversion == 'S' ? 's' : conversion; if (kind == 's') { - while (std::isspace(static_cast(*field))) ++field; + while (std::isspace(source.Peek())) source.Skip(); } - if (*field == '\0') return finish(converted ? assigned : EOF); + if (source.Peek() == EOF) return finish(converted ? assigned : EOF); auto* target = suppress ? nullptr : static_cast(nextPointer()); unsigned long limit = ULONG_MAX; if (secure && !suppress) { @@ -321,38 +331,54 @@ int ScanSource(TSource source, const char* format, bool secure, NextPointer next } const unsigned long wanted = width != 0 ? width : (kind == 'c' ? 1 : ULONG_MAX); const unsigned long taken = std::min(wanted, limit); - std::u16string text; - const auto accepts = [&](char32_t point) { - if (kind == 's') return point >= 0x80 || !std::isspace(static_cast(point)); - if (kind == '[') return LibcDetail::InWideScanset(specifier, point); + const auto accepts = [&](int byte) { + if (kind == 's') return !std::isspace(byte); + if (kind == '[') return LibcDetail::InWideScanset(specifier, static_cast(byte)); return true; }; - while (text.size() < taken && *field != '\0') { - char32_t point = 0; - const std::size_t size = LibcDetail::ReadWideScanCharacter(field, &point); - if (!accepts(point)) break; - text.push_back(static_cast(point)); - field += size; + std::u16string text; + unsigned char state[16]{}; + char pending[2]{}; + std::size_t held = 0; + for (unsigned long read = 0; read < wanted; ++read) { + const int next = source.Peek(); + if (next == EOF || !accepts(next) || (held == 0 && text.size() == taken)) break; + pending[held] = static_cast(next); + std::uint16_t unit = 0; + const std::size_t result = mbrtowc_nid_postfix(&unit, pending, held + 1, state); + if (result == static_cast(-1)) { + source.Skip(); + source.PutBack(static_cast(pending[held])); + if (held != 0) source.PutBack(static_cast(pending[0])); + if (target != nullptr) std::memcpy(target, text.data(), text.size() * sizeof(char16_t)); + return finish(read == 0 && !converted ? EOF : assigned); + } + source.Skip(); + if (result == static_cast(-2)) { + ++held; + continue; + } + held = 0; + text.push_back(static_cast(unit)); } + if (target != nullptr) std::memcpy(target, text.data(), text.size() * sizeof(char16_t)); + if (held != 0) return finish(converted ? assigned : EOF); if (text.empty()) return finish(assigned); - if (target != nullptr && kind != 'c' && wanted > limit && text.size() == limit && *field != '\0') { - char32_t point = 0; - LibcDetail::ReadWideScanCharacter(field, &point); - if (accepts(point)) { + if (target != nullptr && kind != 'c' && wanted > limit && text.size() == limit) { + const int next = source.Peek(); + if (next != EOF && accepts(next)) { target[0] = u'\0'; return finish(assigned); } } if (target != nullptr) { - std::memcpy(target, text.data(), text.size() * sizeof(char16_t)); if (kind != 'c') target[text.size()] = u'\0'; ++assigned; } - source.Advance(field - input); converted = true; continue; } -#endif + if (!std::strchr("diouxXaAeEfFgGcspn[SC", conversion)) return finish(assigned); if (conversion != 'c' && conversion != '[') { while (std::isspace(source.Peek())) source.Skip(); if (source.Peek() == EOF) return finish(converted ? assigned : EOF); @@ -444,10 +470,9 @@ int ScanGuestStream(FileStream* stream, const char* format, NextPointer nextPoin ~Unlock() { funlockfile_nid_postfix(stream); } } unlock{stream}; auto* native = GetNativeStream(stream); + stream->Orientation(-1); if (!stream->SwitchToReading()) return EOF; -#ifndef _WIN32 - RejectGuestWideScan(format); -#endif + if (HasGuestWideScan(format)) LibcDetail::RequireNoPositionalWideScan(format); const int result = ScanSource(StreamSource(native), format, true, nextPointer, nextCapacity); stream->SyncStatus(); return result; @@ -495,6 +520,7 @@ int APS5_VABI vasprintf_nid_postfix(char** destination, const char* format, VaLi int APS5_VABI vfprintf_nid_postfix(FileStream* stream, const char* format, VaList* args) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); + stream->Orientation(-1); if (!stream->SwitchToWriting()) return -1; #ifdef _WIN32 std::string buffer; @@ -538,17 +564,19 @@ int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...) { int APS5_VABI fscanf_nid_postfix(FileStream* stream, const char* format, ...) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); + stream->Orientation(-1); if (!stream->SwitchToReading()) return EOF; #ifdef _WIN32 __builtin_sysv_va_list args; __builtin_sysv_va_start(args, format); - const int result = LibcDetail::ScanFileWindows_nid_no_patch(native, format, args); + const int result = ScansWideInProcess(format) ? ScanSource(StreamSource(native), format, false, [&] { return __builtin_va_arg(args, void*); }, [] { return 0u; }) + : LibcDetail::ScanFileWindows_nid_no_patch(native, format, args); __builtin_sysv_va_end(args); #else - RejectGuestWideScan(format); std::va_list args; va_start(args, format); - const int result = HostVfscanf(native, format, args); + const int result = ScansWideInProcess(format) ? ScanSource(StreamSource(native), format, false, [&] { return va_arg(args, void*); }, [] { return 0u; }) + : HostVfscanf(native, format, args); va_end(args); #endif stream->SyncStatus(); @@ -562,6 +590,7 @@ int APS5_VABI printf_nid_postfix(const char* format, ...) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } __builtin_sysv_va_list args; __builtin_sysv_va_start(args, format); + _Stdout_nid_postfix.Orientation(-1); const int result = LibcDetail::PrintWindows(format, args); __builtin_sysv_va_end(args); return result; @@ -571,6 +600,7 @@ int APS5_VABI libc_printf_nid_postfix(const char* format, ...) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } __builtin_sysv_va_list args; __builtin_sysv_va_start(args, format); + _Stdout_nid_postfix.Orientation(-1); const int result = LibcDetail::PrintWindows(format, args); __builtin_sysv_va_end(args); return result; @@ -598,6 +628,7 @@ int APS5_VABI printf_nid_postfix(const char* format, ...) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } std::va_list args; va_start(args, format); + _Stdout_nid_postfix.Orientation(-1); const int result = RequiresGuestFormatting(format) ? LibcDetail::PrintWindows(format, args) : std::vfprintf(GuestStandardOutputFile_nid_no_patch(), format, args); va_end(args); return result; @@ -607,6 +638,7 @@ int APS5_VABI libc_printf_nid_postfix(const char* format, ...) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } std::va_list args; va_start(args, format); + _Stdout_nid_postfix.Orientation(-1); const int result = RequiresGuestFormatting(format) ? LibcDetail::PrintWindows(format, args) : std::vfprintf(GuestStandardOutputFile_nid_no_patch(), format, args); va_end(args); return result; @@ -631,15 +663,15 @@ int APS5_VABI sprintf_nid_postfix(char* buffer, const char* format, ...) { #endif int APS5_VABI vsscanf_nid_postfix(const char* input, const char* format, VaList* args) { -#ifdef _WIN32 - return LibcDetail::ScanWindows(input, format, args); -#else std::string head; format = CutAtQuadScanDirective(format, head); if (ScansWideInProcess(format)) { LibcDetail::FormatArguments arguments(args); return ScanGuest(input, format, false, [&] { return arguments.Next(); }, [] { return 0u; }); } +#ifdef _WIN32 + return LibcDetail::ScanWindows(input, format, args); +#else std::va_list copy; va_copy(copy, *reinterpret_cast(args)); const int result = HostVsscanf(input, format, copy); @@ -694,8 +726,10 @@ int APS5_VABI sscanf_s_nid_postfix(const char* buffer, const char* format, ...) int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } #ifdef _WIN32 + _Stdout_nid_postfix.Orientation(-1); return LibcDetail::PrintWindows(str, c); #else + _Stdout_nid_postfix.Orientation(-1); if (RequiresGuestFormatting(str)) return LibcDetail::PrintWindows(str, c); std::va_list copy; va_copy(copy, *reinterpret_cast(c)); @@ -752,6 +786,7 @@ 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); + stream->Orientation(-1); if (!stream->SwitchToWriting()) return -1; std::string text; int count; @@ -809,6 +844,7 @@ int APS5_VABI printf_s_nid_postfix(const char* format, ...) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } __builtin_sysv_va_list args; __builtin_sysv_va_start(args, format); + _Stdout_nid_postfix.Orientation(-1); const int result = LibcDetail::PrintWindows(format, args); __builtin_sysv_va_end(args); return result; @@ -850,6 +886,7 @@ int APS5_VABI printf_s_nid_postfix(const char* format, ...) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } std::va_list args; va_start(args, format); + _Stdout_nid_postfix.Orientation(-1); const int result = RequiresGuestFormatting(format) ? LibcDetail::PrintWindows(format, args) : std::vfprintf(GuestStandardOutputFile_nid_no_patch(), format, args); va_end(args); return result; @@ -860,6 +897,7 @@ int APS5_VABI printf_s_nid_postfix(const char* format, ...) { int APS5_VABI puts_nid_postfix(const char* s) { if (!_Stdout_nid_postfix.IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(&_Stdout_nid_postfix); + _Stdout_nid_postfix.Orientation(-1); if (!_Stdout_nid_postfix.SwitchToWriting()) return EOF; if (std::fputs(s, native) < 0 || std::fputc('\n', native) == EOF) return EOF; return 0; @@ -870,16 +908,22 @@ 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); + stream->Orientation(-1); if (!stream->SwitchToReading()) return EOF; + int result = 0; + if (ScansWideInProcess(format)) { + LibcDetail::FormatArguments arguments(args); + result = ScanSource(StreamSource(native), format, false, [&] { return arguments.Next(); }, [] { return 0u; }); + } else { #ifdef _WIN32 - const int result = LibcDetail::ScanFileWindows_nid_no_patch(native, format, args); + result = LibcDetail::ScanFileWindows_nid_no_patch(native, format, args); #else - RejectGuestWideScan(format); - std::va_list copy; - va_copy(copy, *reinterpret_cast(args)); - const int result = HostVfscanf(native, format, copy); - va_end(copy); + std::va_list copy; + va_copy(copy, *reinterpret_cast(args)); + result = HostVfscanf(native, format, copy); + va_end(copy); #endif + } stream->SyncStatus(); stream->KeepDirectoryReadError(result == -1); return result; diff --git a/core/libs/prx/libc/src/FormattingWide.cpp b/core/libs/prx/libc/src/FormattingWide.cpp index 32d668bc85..48d681d16f 100644 --- a/core/libs/prx/libc/src/FormattingWide.cpp +++ b/core/libs/prx/libc/src/FormattingWide.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" @@ -30,26 +31,30 @@ bool In(char16_t character, const char* set) { return character != 0 && character < 128 && std::strchr(set, static_cast(character)) != nullptr; } -void AppendUtf16(std::u16string& out, const char* text, std::size_t limit) { - for (std::size_t index = 0; out.size() < limit && text[index] != '\0';) { +class WideEncodingError : public std::invalid_argument { +public: + explicit WideEncodingError(std::u16string written) : std::invalid_argument("Invalid multibyte string argument"), partial(std::move(written)) {} + std::u16string partial; +}; + +bool AppendUtf16(std::u16string& out, const char* text, std::size_t limit) { + std::size_t index = 0; + for (std::size_t count = 0; count == 0 || count < limit; ++count) { const unsigned char lead = static_cast(text[index]); - std::size_t extra = lead >= 0xf0 ? 3 : lead >= 0xe0 ? 2 : lead >= 0xc0 ? 1 : 0; - std::uint32_t code = extra == 3 ? lead & 0x07 : extra == 2 ? lead & 0x0f : extra == 1 ? lead & 0x1f : lead; - std::size_t used = 1; - for (; used <= extra; ++used) { + if (lead == 0) break; + if ((lead >= 0x80 && lead < 0xc0) || lead >= 0xf0) return false; + const std::size_t extra = lead >= 0xe0 ? 2 : lead >= 0xc0 ? 1 : 0; + std::uint32_t code = extra == 2 ? lead & 0x0f : extra == 1 ? lead & 0x1f : lead; + for (std::size_t used = 1; used <= extra; ++used) { const unsigned char next = static_cast(text[index + used]); - if ((next & 0xc0) != 0x80) { extra = 0; code = lead; used = 1; break; } + if ((next & 0xc0) != 0x80) return false; code = (code << 6) | (next & 0x3f); } - if (extra != 0) used = extra + 1; - index += used; - if (code >= 0x10000) { - if (limit - out.size() < 2) break; - code -= 0x10000; - out.push_back(static_cast(0xd800 + (code >> 10))); - out.push_back(static_cast(0xdc00 + (code & 0x3ff))); - } else out.push_back(static_cast(code)); + index += extra + 1; + if (code == 0) break; + if (count < limit) out.push_back(static_cast(code)); } + return true; } std::string ToUtf8(const std::u16string& text) { @@ -220,7 +225,7 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = AppendNumber(out, spec + narrow, value); } } - } else if (conversion == u'p' && length.empty()) { + } else if (conversion == u'p' && (length.empty() || length == "l")) { const std::string text = LibcDetail::FormatPointer(spec, args.Next()); out.append(text.begin(), text.end()); } else if (conversion == u'c' || conversion == u'C') { @@ -240,7 +245,8 @@ std::u16string FormatWide(const char16_t* format, VaList* source, bool secure = } else { const char* value = args.Next(); if (value == nullptr && secure) throw std::invalid_argument("Null string argument"); - AppendUtf16(text, value != nullptr ? value : "(null)", limit); + if (!AppendUtf16(text, value != nullptr ? value : "(null)", limit)) throw WideEncodingError(out); + if (text.empty()) width = 0; } AppendPadded(out, text, left, width); } else if (conversion == u'n' && integerLength && spec == "%" && !secure) { @@ -271,6 +277,12 @@ int APS5_VABI vswprintf_nid_postfix(char16_t* buffer, std::size_t size, const ch std::memcpy(buffer, text.data(), copied * sizeof(char16_t)); buffer[copied] = 0; return copied == text.size() ? static_cast(copied) : -1; + } catch (const WideEncodingError& error) { + const std::size_t copied = error.partial.size() < size - 1 ? error.partial.size() : size - 1; + std::memcpy(buffer, error.partial.data(), copied * sizeof(char16_t)); + buffer[copied] = 0; + errno = 86; + return -1; } catch (const std::exception&) { buffer[0] = 0; errno = 22; @@ -365,6 +377,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; } + _Stdout_nid_postfix.Orientation(1); if (!__stdoutp_nid_postfix->SwitchToWriting()) return -1; APS5_VA_BEGIN(format); int result = -1; @@ -372,6 +385,8 @@ int APS5_VABI wprintf_nid_postfix(const char16_t* format, ...) { const std::u16string text = FormatWide(format, reinterpret_cast(args)); const std::string bytes = ToUtf8(text); if (std::fwrite(bytes.data(), 1, bytes.size(), GetNativeStream(__stdoutp_nid_postfix)) == bytes.size()) result = static_cast(text.size()); + } catch (const WideEncodingError&) { + errno = 86; } catch (const std::exception&) { errno = 22; } @@ -382,12 +397,15 @@ int APS5_VABI wprintf_nid_postfix(const char16_t* format, ...) { int APS5_VABI vfwprintf_nid_postfix(FileStream* stream, const char16_t* format, VaList* args) { if (stream == nullptr || format == nullptr) { errno = 22; return -1; } if (!stream->IsOpen()) { errno = 9; return -1; } + stream->Orientation(1); if (!stream->SwitchToWriting()) return -1; int result = -1; try { const std::u16string text = FormatWide(format, args); const std::string bytes = ToUtf8(text); if (std::fwrite(bytes.data(), 1, bytes.size(), GetNativeStream(stream)) == bytes.size()) result = static_cast(text.size()); + } catch (const WideEncodingError&) { + errno = 86; } catch (const std::exception&) { errno = 22; } @@ -409,6 +427,7 @@ int APS5_VABI vwprintf_nid_postfix(const char16_t* format, VaList* 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; } + stream->Orientation(1); if (!stream->SwitchToWriting()) return -1; int result = -1; try { @@ -443,6 +462,7 @@ int APS5_VABI wprintf_s_nid_postfix(const char16_t* format, ...) { 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; } + stream->Orientation(1); try { const std::string bytes = ToUtf8(std::u16string(1, value)); auto* native = GetNativeStream(stream); @@ -459,6 +479,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; } + stream->Orientation(1); try { const std::u16string text(str); const std::string bytes = ToUtf8(text); @@ -482,6 +503,7 @@ int APS5_VABI putwchar_nid_postfix(char16_t value) { } char16_t* APS5_VABI fgetws_nid_postfix(char16_t* buffer, int size, FileStream* stream) { + if (stream != nullptr) stream->Orientation(1); if (size < 2) return nullptr; if (buffer == nullptr || stream == nullptr) { if (stream != nullptr) stream->SetError(); diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp index e0988c8c62..cb518ad307 100644 --- a/core/libs/prx/libc/src/GuestAllocations.cpp +++ b/core/libs/prx/libc/src/GuestAllocations.cpp @@ -47,6 +47,7 @@ struct Registry { std::shared_ptr readableLease; std::shared_ptr allLease; std::uint64_t backingSerials = 0; + std::vector> retiring; }; Registry& registry() { @@ -70,6 +71,13 @@ void renewBacking(std::uintptr_t address, std::size_t bytes) { } } +bool isRetiring(const Range& range) { + for (const auto& [begin, end] : registry().retiring) { + if (range.address < end && begin < range.address + range.bytes) return true; + } + return false; +} + std::atomic generation{1}; std::atomic invalidator{nullptr}; std::atomic pinWaiter{nullptr}; @@ -433,6 +441,20 @@ void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* poi auto* state = static_cast(mutation); const auto deadline = std::chrono::steady_clock::now() + pinWait(); int syncedRounds = 0; + bool marked = false; + const auto unmark = [&] { + if (!marked) return; + if (!state->lock.owns_lock()) state->lock.lock(); + auto& retiring = registry().retiring; + retiring.erase(std::find(retiring.begin(), retiring.end(), std::pair{address, end})); + registry().readableLease.reset(); + registry().allLease.reset(); + marked = false; + }; + struct Unmark { + const decltype(unmark)& release; + ~Unmark() { release(); } + } unmarkOnExit{unmark}; for (;;) { registry().readableLease.reset(); registry().allLease.reset(); @@ -448,9 +470,14 @@ void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* poi } } if (!pinned) { + unmark(); if (state != nullptr && bytes != 0) state->unpinned.emplace_back(address, bytes); return; } + if (!marked && state != nullptr && state->lock.owns_lock()) { + registry().retiring.emplace_back(address, end); + marked = true; + } if (std::chrono::steady_clock::now() >= deadline) PinnedFailure(address, bytes, ("waited " + std::to_string(pinWait().count()) + " ms for the lease (APS5_PIN_WAIT_MS)").c_str()); const auto waiter = pinWaiter.load(std::memory_order_acquire); bool progressed = false; @@ -477,10 +504,21 @@ void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* poi } } +decltype(registry().ranges)::iterator firstRangeTouching(std::uintptr_t address) { + auto& ranges = registry().ranges; + auto it = ranges.lower_bound(address); + if (it == ranges.begin()) return it; + const auto& previous = *std::prev(it); + if (previous.second->bytes != 0 && address < previous.first + previous.second->bytes) return std::prev(it); + return it; +} + void GuestAllocationsRequireAvailable_nid_postfix(void*, const void* pointer, std::size_t bytes) { const auto address = reinterpret_cast(pointer); require(address != 0 && bytes != 0 && bytes <= std::numeric_limits::max() - address, "invalid fixed guest mapping"); - for (const auto& [base, range] : registry().ranges) { + const auto first = firstRangeTouching(address); + for (auto it = first; it != registry().ranges.end(); ++it) { + const auto& [base, range] = *it; if (base >= address + bytes) break; if (base + range->bytes > address) { char message[160]; @@ -493,7 +531,9 @@ void GuestAllocationsRequireAvailable_nid_postfix(void*, const void* pointer, st bool GuestAllocationsOverlaps_nid_postfix(void*, const void* pointer, std::size_t bytes) { const auto address = reinterpret_cast(pointer); require(address != 0 && bytes != 0 && bytes <= std::numeric_limits::max() - address, "invalid guest allocation range"); - for (const auto& [base, range] : registry().ranges) { + const auto first = firstRangeTouching(address); + for (auto it = first; it != registry().ranges.end(); ++it) { + const auto& [base, range] = *it; if (base >= address + bytes) break; if (base + range->bytes > address) return true; } @@ -740,10 +780,11 @@ Lease GuestAllocationsAcquire_nid_postfix() { std::lock_guard lock(registry().mutex); if (auto shared = registry().readableLease) return Lease(std::move(shared)); auto ranges = std::make_shared(); + ranges->reserve(registry().ranges.size()); for (const auto& [address, range] : registry().ranges) { - if (range->readable && range->bytes != 0) ranges->push_back(range); + if (range->readable && range->bytes != 0 && !isRetiring(*range)) ranges->push_back(range); } - registry().readableLease = ranges; + if (registry().retiring.empty()) registry().readableLease = ranges; return Lease(std::move(ranges)); } @@ -783,6 +824,19 @@ Lease GuestAllocationsAcquireMapped_nid_no_patch(const Mapped& mapped) { return Lease(std::move(ranges)); } +Lease GuestAllocationsAcquireRegistered_nid_postfix() { + std::lock_guard lock(registry().mutex); + if (registry().retiring.empty()) { + if (auto shared = registry().readableLease) return Lease(std::move(shared)); + } + auto ranges = std::make_shared(); + for (const auto& [address, range] : registry().ranges) { + if (range->readable && range->bytes != 0) ranges->push_back(range); + } + if (registry().retiring.empty()) registry().readableLease = ranges; + return Lease(std::move(ranges)); +} + std::shared_ptr GuestAllocationsLookup_nid_postfix(std::uintptr_t address, bool next) { std::lock_guard lock(registry().mutex); const auto& ranges = registry().ranges; @@ -805,9 +859,9 @@ Lease GuestAllocationsAcquireAll_nid_postfix() { auto ranges = std::make_shared(); ranges->reserve(registry().ranges.size()); for (const auto& [address, range] : registry().ranges) { - if (range->bytes != 0) ranges->push_back(range); + if (range->bytes != 0 && !isRetiring(*range)) ranges->push_back(range); } - registry().allLease = ranges; + if (registry().retiring.empty()) registry().allLease = ranges; return Lease(std::move(ranges)); } diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index efce967753..a1bb54c48e 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -565,6 +565,14 @@ void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, noteAccess(pointer, bytes); } +void GuestArenaMapZero_nid_postfix(void* pointer, std::size_t bytes) { + if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("zero page mapping", pointer, bytes); + const auto generation = commitGeneration.fetch_add(1, std::memory_order_acq_rel) + 1; + WindowsMappings::Get().MapZero(pointer, bytes); + if (const auto callback = privateMappingObserver.load(std::memory_order_acquire)) callback(reinterpret_cast(pointer), bytes, generation); + noteAccess(pointer, bytes); +} + void GuestArenaMapDirect_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection) { if (!Arena::Get().Contains(pointer, bytes)) throw OutsideArena("direct memory mapping", pointer, bytes); const auto generation = commitGeneration.fetch_add(1, std::memory_order_acq_rel) + 1; @@ -750,6 +758,7 @@ namespace { std::atomic sharedBackingResolver{nullptr}; std::atomic sharedBackingWriter{nullptr}; +std::atomic sharedBackingRunsResolver{nullptr}; } @@ -771,6 +780,15 @@ bool GuestArenaSharedBacking_nid_postfix(std::uintptr_t address, std::size_t byt return resolver != nullptr && resolver(address, bytes, file, offset); } +void GuestArenaSetSharedBackingRuns_nid_postfix(SharedBackingRunsResolver resolver) { + sharedBackingRunsResolver.store(resolver, std::memory_order_release); +} + +bool GuestArenaSharedBackingRuns_nid_postfix(std::uintptr_t address, std::size_t bytes, int* file, SharedBackingRun* runs, std::size_t capacity, std::size_t* count) { + const auto resolver = sharedBackingRunsResolver.load(std::memory_order_acquire); + return resolver != nullptr && resolver(address, bytes, file, runs, capacity, count); +} + bool GuestArenaOverlapsHostRegion_nid_no_patch(std::uintptr_t address, std::size_t bytes) { return Arena::Get().OverlapsHostRegion(address, bytes); } diff --git a/core/libs/prx/libc/src/GuestHeap.cpp b/core/libs/prx/libc/src/GuestHeap.cpp index 2c6d1ab563..a584f85f5e 100644 --- a/core/libs/prx/libc/src/GuestHeap.cpp +++ b/core/libs/prx/libc/src/GuestHeap.cpp @@ -384,8 +384,7 @@ void* GuestHeapRealign_nid_postfix(void* pointer, std::size_t bytes, std::size_t const auto block = validatedBlock(pointer, range.bytes); if (bytes == 0) { 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"); + if (reinterpret_cast(pointer) % alignment != 0) return allocate(mutation, alignment, 0, range.gpu); return emptyInPlace(mutation, pointer, range.gpu); } if (alignment != 0 && (alignment & (alignment - 1)) == 0 && reinterpret_cast(pointer) % alignment == 0 && keepsBlockSize(block, pointer, alignment, bytes)) diff --git a/core/libs/prx/libc/src/GuestWriteWatch.cpp b/core/libs/prx/libc/src/GuestWriteWatch.cpp index f0ee7c4066..81d043896a 100644 --- a/core/libs/prx/libc/src/GuestWriteWatch.cpp +++ b/core/libs/prx/libc/src/GuestWriteWatch.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -30,7 +31,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -366,9 +369,10 @@ public: auto& entry = pages[(page - begin) / _page]; const std::uint8_t recorded = state & ProtectionMask; const bool armed = (state & Protected) != 0 && (entry & ProtectionMask) == (recorded & ~PROT_WRITE); - if (armed || !queried[(page - begin) / _page]) entry = static_cast(Written | Fresh | recorded); + if (armed || (state & ReadTrapped) != 0 || !queried[(page - begin) / _page]) entry = static_cast(Written | Fresh | recorded); else state = static_cast(state & ~Protected); }); + dropTraps(begin, end); erase(begin, end); _ranges.emplace(begin, Range{end, std::move(pages)}); } @@ -379,6 +383,7 @@ public: end = pageEnd(end); if (end <= begin) return false; const Lock lock(_lock); + dropTraps(begin, end); return erase(begin, end); } @@ -429,24 +434,26 @@ public: begin = pageStart(begin); end = pageEnd(end); if (end <= begin) return; - const Lock lock(_lock); - forEachPage(begin, end, [&](std::uintptr_t, std::uint8_t& state) { - state = static_cast(Written | (state & Fresh) | (protection & ProtectionMask)); + withoutTraps(begin, end, false, [&] { + forEachPage(begin, end, [&](std::uintptr_t, std::uint8_t& state) { + state = static_cast(Written | (state & Fresh) | (protection & ProtectionMask)); + }); }); } int Protect(std::uintptr_t begin, std::uintptr_t end, int protection) { if (!_available) return ::mprotect(reinterpret_cast(begin), end - begin, protection) == 0 ? 0 : errno; - const Lock lock(_lock); - if (::mprotect(reinterpret_cast(begin), end - begin, protection) != 0) return errno; const auto first = pageStart(begin); const auto last = pageEnd(end); - if (first < last) { - forEachPage(first, last, [&](std::uintptr_t, std::uint8_t& state) { - state = static_cast(Written | (state & Fresh) | (protection & ProtectionMask)); - }); - } - return 0; + return withoutTraps(first, last, true, [&] { + if (::mprotect(reinterpret_cast(begin), end - begin, protection) != 0) return errno; + if (first < last) { + forEachPage(first, last, [&](std::uintptr_t, std::uint8_t& state) { + state = static_cast(Written | (state & Fresh) | (protection & ProtectionMask)); + }); + } + return 0; + }); } void BeginHostWrite(std::uintptr_t begin, std::uintptr_t end) { @@ -454,11 +461,12 @@ public: begin = pageStart(begin); end = pageEnd(end); if (end <= begin) return; - const Lock lock(_lock); - _hostWrites.emplace_back(begin, end); - forEachPage(begin, end, [&](std::uintptr_t page, std::uint8_t& state) { - if ((state & Protected) != 0) ::mprotect(reinterpret_cast(page), _page, state & ProtectionMask); - state = static_cast((state & (ProtectionMask | Fresh)) | Written); + withoutTraps(begin, end, true, [&] { + _hostWrites.emplace_back(begin, end); + forEachPage(begin, end, [&](std::uintptr_t page, std::uint8_t& state) { + if ((state & Protected) != 0) ::mprotect(reinterpret_cast(page), _page, state & ProtectionMask); + state = static_cast((state & (ProtectionMask | Fresh)) | Written); + }); }); } @@ -489,12 +497,75 @@ public: return -1; } + bool SetReadTrapResolver(void (*resolver)(std::uintptr_t, std::uintptr_t)) { + if (!_available || resolver == nullptr) return false; + std::call_once(_resolverStarted, [this] { startResolver(); }); + if (!_resolverRunning.load(std::memory_order_acquire)) return false; + _resolver.store(resolver, std::memory_order_release); + return true; + } + + bool TrapReads(std::uintptr_t begin, std::uintptr_t end) { + if (!_available || end <= begin || _resolver.load(std::memory_order_acquire) == nullptr) return false; + const auto first = pageStart(begin); + const auto last = pageEnd(end); + const Lock lock(_lock); + if (!covers(first, last)) return false; + const auto [same, other] = _traps.equal_range(begin); + if (std::any_of(same, other, [&](const auto& trap) { return trap.second == end; })) return true; + for (const auto& [hostBegin, hostEnd] : _hostWrites) + if (hostBegin < last && first < hostEnd) return false; + if (::mprotect(reinterpret_cast(first), last - first, PROT_NONE) != 0) return false; + _traps.emplace(begin, end); + forEachPage(first, last, [](std::uintptr_t, std::uint8_t& state) { state |= ReadTrapped; }); + _trapping.store(true, std::memory_order_release); + return true; + } + + void ReleaseReads(std::uintptr_t begin, std::uintptr_t end) { + if (!_available || end <= begin) return; + const Lock lock(_lock); + const auto [first, last] = _traps.equal_range(begin); + const auto found = std::find_if(first, last, [&](const auto& trap) { return trap.second == end; }); + if (found == last) return; + _traps.erase(found); + untrap(begin, end); + } + private: static constexpr std::uint8_t ProtectionMask = PROT_READ | PROT_WRITE | PROT_EXEC; static constexpr std::uint8_t Written = 0x10; static constexpr std::uint8_t Protected = 0x20; static constexpr std::uint8_t Fresh = 0x40; static constexpr std::uintptr_t FaultAheadBlock = 65536; + static constexpr std::uint8_t ReadTrapped = 0x80; + static constexpr std::size_t ResolveSlots = 32; + static constexpr std::size_t RetrySlots = 64; + static constexpr unsigned UntrappedRetries = 16; + static constexpr unsigned GenerationRetries = RetrySlots * UntrappedRetries; + + enum SlotState : unsigned { Free, Claimed, Queued, Running, Done }; + enum class Owner { Any, Self, Others }; + + struct Slot { + std::atomic state{Free}; + std::uintptr_t page = 0; + semaphore_t done = MACH_PORT_NULL; + }; + + struct Resolving { + std::uintptr_t begin; + std::uintptr_t end; + pthread_t owner; + }; + + struct Retry { + std::uintptr_t page = 0; + pthread_t thread = nullptr; + std::uint64_t generation = 0; + std::uint64_t used = 0; + unsigned count = 0; + }; struct Range { std::uintptr_t end; @@ -572,7 +643,9 @@ private: static void fault(int signal, siginfo_t* info, void* context) { auto& watch = Get(); - if (isWriteFault(info, context) && watch.record(reinterpret_cast(info->si_addr))) return; + const bool write = isWriteFault(info, context); + if (info->si_code > 0 && info->si_code < SI_USER && watch.resolveTrap(reinterpret_cast(info->si_addr), write)) return; + if (write && watch.record(reinterpret_cast(info->si_addr))) return; const auto& previous = signal == SIGBUS ? watch._previousBus : watch._previousSegv; if ((previous.sa_flags & SA_SIGINFO) != 0 && previous.sa_sigaction != nullptr) { previous.sa_sigaction(signal, info, context); @@ -603,7 +676,7 @@ private: if (_faultAhead > 1 && index >= 2 && writtenOpen(index - 1) && writtenOpen(index - 2)) { const auto blockPages = static_cast(((page | (FaultAheadBlock - 1)) + 1 - page) / _page); const auto limit = std::min({_faultAhead, blockPages, pages.size() - index}); - while (count < limit && (pages[index + count] & Protected) != 0 && (pages[index + count] & ProtectionMask) == protection) ++count; + while (count < limit && (pages[index + count] & (Protected | ReadTrapped)) == Protected && (pages[index + count] & ProtectionMask) == protection) ++count; } if (::mprotect(reinterpret_cast(page), count * _page, protection) != 0) return false; for (std::size_t at = index; at < index + count; ++at) pages[at] = static_cast((pages[at] & (ProtectionMask | Fresh)) | Written); @@ -615,6 +688,199 @@ private: return mach_vm_read_overwrite(mach_task_self(), address, sizeof(value), reinterpret_cast(&value), &copied) == KERN_SUCCESS && copied == sizeof(value); } + bool resolveTrap(std::uintptr_t address, bool write) { + if (!_trapping.load(std::memory_order_acquire)) return false; + const auto page = pageStart(address); + { + const FaultLock lock(_lock); + auto* state = pageState(page); + if (state == nullptr) return false; + if ((*state & ReadTrapped) == 0) return retryUntrapped(page, *state, write); + if (resolvingCovers(page, page + _page, Owner::Self) || pthread_equal(pthread_self(), _resolverThread) || (!trapCovers(page, page + _page) && !resolvingCovers(page, page + _page, Owner::Any))) { + *state = static_cast(*state & ~ReadTrapped); + ++_untrapGeneration; + return ::mprotect(reinterpret_cast(page), _page, untrappedProtection(*state)) == 0; + } + } + Slot* slot = nullptr; + while (slot == nullptr) { + for (auto& candidate : _slots) { + unsigned expected = Free; + if (candidate.state.compare_exchange_strong(expected, Claimed, std::memory_order_acq_rel)) { + slot = &candidate; + break; + } + } + if (slot == nullptr) sched_yield(); + } + slot->page = page; + slot->state.store(Queued, std::memory_order_release); + semaphore_signal(_wake); + while (semaphore_wait(slot->done) == KERN_ABORTED) {} + slot->state.store(Free, std::memory_order_release); + return true; + } + + void startResolver() { + if (semaphore_create(mach_task_self(), &_wake, SYNC_POLICY_FIFO, 0) != KERN_SUCCESS) return; + for (auto& slot : _slots) + if (semaphore_create(mach_task_self(), &slot.done, SYNC_POLICY_FIFO, 0) != KERN_SUCCESS) return; + pthread_attr_t attributes; + pthread_attr_init(&attributes); + pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED); + const bool created = pthread_create(&_resolverThread, &attributes, [](void* watch) -> void* { + static_cast(watch)->resolveLoop(); + return nullptr; + }, this) == 0; + pthread_attr_destroy(&attributes); + _resolverRunning.store(created, std::memory_order_release); + } + + void resolveLoop() { + pthread_setname_np("write watch read traps"); + for (;;) { + if (semaphore_wait(_wake) != KERN_SUCCESS) continue; + for (auto& slot : _slots) { + unsigned expected = Queued; + if (!slot.state.compare_exchange_strong(expected, Running, std::memory_order_acq_rel)) continue; + const auto page = slot.page; + waitForResolving(page, page + _page); + resolve(page, page + _page); + { + const Lock lock(_lock); + untrap(page, page + _page); + } + slot.state.store(Done, std::memory_order_release); + semaphore_signal(slot.done); + } + } + } + + void resolve(std::uintptr_t begin, std::uintptr_t end) { + std::vector> taken; + const auto self = pthread_self(); + { + const Lock lock(_lock); + taken = takeTraps(begin, end); + for (const auto& [first, last] : taken) _resolving.push_back({first, last, self}); + } + if (taken.empty()) return; + const auto resolver = _resolver.load(std::memory_order_acquire); + for (const auto& [first, last] : taken) resolver(first, last); + const Lock lock(_lock); + for (const auto& [first, last] : taken) { + const auto found = std::find_if(_resolving.begin(), _resolving.end(), [&](const Resolving& entry) { return entry.begin == first && entry.end == last && pthread_equal(entry.owner, self); }); + if (found != _resolving.end()) _resolving.erase(found); + } + for (const auto& [first, last] : taken) untrap(first, last); + } + + void waitForResolving(std::uintptr_t begin, std::uintptr_t end) { + for (;;) { + { + const Lock lock(_lock); + if (!resolvingCovers(begin, end, Owner::Others)) return; + } + sched_yield(); + } + } + + bool resolvingCovers(std::uintptr_t begin, std::uintptr_t end, Owner owner) const { + const auto self = pthread_self(); + return std::any_of(_resolving.begin(), _resolving.end(), [&](const Resolving& entry) { + if (pageStart(entry.begin) >= end || begin >= pageEnd(entry.end)) return false; + return owner == Owner::Any || (pthread_equal(entry.owner, self) != 0) == (owner == Owner::Self); + }); + } + + bool retryUntrapped(std::uintptr_t page, std::uint8_t state, bool write) { + if ((untrappedProtection(state) & (write ? PROT_WRITE : PROT_READ)) == 0) return false; + if (_retryGeneration != _untrapGeneration) { + _retryGeneration = _untrapGeneration; + _generationRetries = 0; + } + if (++_generationRetries > GenerationRetries) return false; + const auto thread = pthread_self(); + auto found = std::find_if(_retries.begin(), _retries.end(), [&](const Retry& retry) { return retry.used != 0 && retry.page == page && pthread_equal(retry.thread, thread); }); + if (found == _retries.end()) found = std::min_element(_retries.begin(), _retries.end(), [](const Retry& left, const Retry& right) { return left.used < right.used; }); + if (found->used == 0 || found->page != page || !pthread_equal(found->thread, thread) || found->generation != _untrapGeneration) *found = {page, thread, _untrapGeneration, 0, 0}; + found->used = ++_retryUses; + return ++found->count <= UntrappedRetries; + } + + template + std::invoke_result_t withoutTraps(std::uintptr_t begin, std::uintptr_t end, bool restore, TFunction function) { + for (;;) { + if (_trapping.load(std::memory_order_acquire)) { + waitForResolving(begin, end); + resolve(begin, end); + } + const Lock lock(_lock); + if (trapCovers(pageStart(begin), pageEnd(end)) || resolvingCovers(pageStart(begin), pageEnd(end), Owner::Others)) continue; + if (restore) untrap(begin, end); + else forEachPage(pageStart(begin), pageEnd(end), [](std::uintptr_t, std::uint8_t& state) { state = static_cast(state & ~ReadTrapped); }); + return function(); + } + } + + std::vector> takeTraps(std::uintptr_t begin, std::uintptr_t end) { + std::vector> taken; + auto first = pageStart(begin); + auto last = pageEnd(end); + for (bool grew = true; grew;) { + grew = false; + for (auto it = _traps.begin(); it != _traps.end();) { + if (it->first >= last || it->second <= first) { + ++it; + continue; + } + first = std::min(first, pageStart(it->first)); + last = std::max(last, pageEnd(it->second)); + taken.emplace_back(*it); + it = _traps.erase(it); + grew = true; + } + } + return taken; + } + + void dropTraps(std::uintptr_t begin, std::uintptr_t end) { + for (const auto& [first, last] : takeTraps(begin, end)) untrap(first, last); + untrap(begin, end); + } + + bool trapCovers(std::uintptr_t begin, std::uintptr_t end) const { + return std::any_of(_traps.begin(), _traps.end(), [&](const auto& trap) { return trap.first < end && begin < trap.second; }); + } + + void untrap(std::uintptr_t begin, std::uintptr_t end) { + const auto first = pageStart(begin); + const auto last = pageEnd(end); + std::vector> remaining; + for (const auto& trap : _traps) + if (trap.first < last && first < trap.second) remaining.push_back(trap); + forEachPage(first, last, [&](std::uintptr_t page, std::uint8_t& state) { + if ((state & ReadTrapped) == 0) return; + if (std::any_of(remaining.begin(), remaining.end(), [&](const auto& trap) { return trap.first < page + _page && page < trap.second; })) return; + if (resolvingCovers(page, page + _page, Owner::Others)) return; + state = static_cast(state & ~ReadTrapped); + ::mprotect(reinterpret_cast(page), _page, untrappedProtection(state)); + ++_untrapGeneration; + }); + } + + static int untrappedProtection(std::uint8_t state) { + return (state & Protected) != 0 ? (state & ProtectionMask & ~PROT_WRITE) : (state & ProtectionMask); + } + + std::uint8_t* pageState(std::uintptr_t page) { + auto it = _ranges.upper_bound(page); + if (it == _ranges.begin()) return nullptr; + --it; + if (page >= it->second.end) return nullptr; + return &it->second.pages[(page - it->first) / _page]; + } + std::uintptr_t pageStart(std::uintptr_t address) const { return address & ~(_page - 1); } @@ -643,7 +909,7 @@ private: runProtection = -1; }; forEachPage(begin, end, [&](std::uintptr_t page, std::uint8_t& state) { - const int wanted = (state & Protected) != 0 ? (state & ProtectionMask & ~PROT_WRITE) : -1; + const int wanted = (state & ReadTrapped) != 0 ? PROT_NONE : (state & Protected) != 0 ? (state & ProtectionMask & ~PROT_WRITE) : -1; if (wanted != runProtection || page != previous) { flush(previous); runStart = page; @@ -684,7 +950,7 @@ private: const auto cutEnd = std::min(end, rangeEnd); for (auto page = cutBegin; page < cutEnd; page += _page) { const auto state = pages[(page - rangeBegin) / _page]; - if ((state & Protected) != 0) ::mprotect(reinterpret_cast(page), _page, state & ProtectionMask); + if ((state & (Protected | ReadTrapped)) != 0) ::mprotect(reinterpret_cast(page), _page, state & ProtectionMask); } if (rangeBegin < cutBegin) _ranges.emplace(rangeBegin, Range{cutBegin, std::vector(pages.begin(), pages.begin() + (cutBegin - rangeBegin) / _page)}); if (cutEnd < rangeEnd) it = _ranges.emplace(cutEnd, Range{rangeEnd, std::vector(pages.begin() + (cutEnd - rangeBegin) / _page, pages.end())}).first; @@ -698,6 +964,20 @@ private: Mutex _lock; std::map _ranges; std::vector> _hostWrites; + std::multimap _traps; + std::vector _resolving; + std::array _retries{}; + std::uint64_t _retryUses = 0; + std::uint64_t _retryGeneration = 0; + unsigned _generationRetries = 0; + std::uint64_t _untrapGeneration = 0; + std::atomic _trapping{false}; + std::atomic _resolver{nullptr}; + std::once_flag _resolverStarted; + std::atomic _resolverRunning{false}; + pthread_t _resolverThread{}; + semaphore_t _wake = MACH_PORT_NULL; + std::array _slots; struct sigaction _previousBus {}; struct sigaction _previousSegv {}; }; @@ -854,4 +1134,32 @@ bool GuestWriteWatchCollectArmed_nid_postfix(std::uintptr_t address, std::size_t #endif } +bool GuestWriteWatchSetReadTrapResolver_nid_postfix(void (*resolve)(std::uintptr_t begin, std::uintptr_t end)) { +#if defined(__APPLE__) + return Watch::Get().SetReadTrapResolver(resolve); +#else + static_cast(resolve); + return false; +#endif +} + +bool GuestWriteWatchTrapReads_nid_postfix(std::uintptr_t address, std::size_t bytes) { +#if defined(__APPLE__) + return bytes != 0 && address + bytes > address && Watch::Get().TrapReads(address, address + bytes); +#else + static_cast(address); + static_cast(bytes); + return false; +#endif +} + +void GuestWriteWatchReleaseReads_nid_postfix(std::uintptr_t address, std::size_t bytes) { +#if defined(__APPLE__) + if (bytes != 0 && address + bytes > address) Watch::Get().ReleaseReads(address, address + bytes); +#else + static_cast(address); + static_cast(bytes); +#endif +} + } diff --git a/core/libs/prx/libc/src/IntegerBuiltins.cpp b/core/libs/prx/libc/src/IntegerBuiltins.cpp index ae0bdca936..1510b72245 100644 --- a/core/libs/prx/libc/src/IntegerBuiltins.cpp +++ b/core/libs/prx/libc/src/IntegerBuiltins.cpp @@ -1,6 +1,7 @@ #include "prx/libc/include/Api.hpp" #include #include +#include #include #include @@ -167,6 +168,12 @@ int CountTrailingZeros128(Uint128 value) { return low != 0 ? std::countr_zero(low) : 64 + std::countr_zero(High(value)); } +template +TValue CeilBit(TValue value) { + constexpr TValue highest = TValue{1} << (std::numeric_limits::digits - 1); + return value > highest ? TValue{0} : std::bit_ceil(value); +} + template int Compare(TValue a, TValue b) { if (a < b) return 0; @@ -260,6 +267,54 @@ std::uint32_t APS5_VABI stdc_count_ones_ui_nid_postfix(std::uint32_t value) { re std::uint32_t APS5_VABI stdc_count_ones_ul_nid_postfix(std::uint64_t value) { return std::popcount(value); } std::uint32_t APS5_VABI stdc_count_ones_ull_nid_postfix(std::uint64_t value) { return std::popcount(value); } +std::uint32_t APS5_VABI stdc_first_leading_zero_uc_nid_postfix(std::uint8_t value) { return value == std::numeric_limits::max() ? 0 : std::countl_one(value) + 1; } +std::uint32_t APS5_VABI stdc_first_leading_zero_us_nid_postfix(std::uint16_t value) { return value == std::numeric_limits::max() ? 0 : std::countl_one(value) + 1; } +std::uint32_t APS5_VABI stdc_first_leading_zero_ui_nid_postfix(std::uint32_t value) { return value == std::numeric_limits::max() ? 0 : std::countl_one(value) + 1; } +std::uint32_t APS5_VABI stdc_first_leading_zero_ul_nid_postfix(std::uint64_t value) { return value == std::numeric_limits::max() ? 0 : std::countl_one(value) + 1; } +std::uint32_t APS5_VABI stdc_first_leading_zero_ull_nid_postfix(std::uint64_t value) { return value == std::numeric_limits::max() ? 0 : std::countl_one(value) + 1; } + +std::uint32_t APS5_VABI stdc_first_leading_one_uc_nid_postfix(std::uint8_t value) { return value == 0 ? 0 : std::countl_zero(value) + 1; } +std::uint32_t APS5_VABI stdc_first_leading_one_us_nid_postfix(std::uint16_t value) { return value == 0 ? 0 : std::countl_zero(value) + 1; } +std::uint32_t APS5_VABI stdc_first_leading_one_ui_nid_postfix(std::uint32_t value) { return value == 0 ? 0 : std::countl_zero(value) + 1; } +std::uint32_t APS5_VABI stdc_first_leading_one_ul_nid_postfix(std::uint64_t value) { return value == 0 ? 0 : std::countl_zero(value) + 1; } +std::uint32_t APS5_VABI stdc_first_leading_one_ull_nid_postfix(std::uint64_t value) { return value == 0 ? 0 : std::countl_zero(value) + 1; } + +std::uint32_t APS5_VABI stdc_first_trailing_zero_uc_nid_postfix(std::uint8_t value) { return value == std::numeric_limits::max() ? 0 : std::countr_one(value) + 1; } +std::uint32_t APS5_VABI stdc_first_trailing_zero_us_nid_postfix(std::uint16_t value) { return value == std::numeric_limits::max() ? 0 : std::countr_one(value) + 1; } +std::uint32_t APS5_VABI stdc_first_trailing_zero_ui_nid_postfix(std::uint32_t value) { return value == std::numeric_limits::max() ? 0 : std::countr_one(value) + 1; } +std::uint32_t APS5_VABI stdc_first_trailing_zero_ul_nid_postfix(std::uint64_t value) { return value == std::numeric_limits::max() ? 0 : std::countr_one(value) + 1; } +std::uint32_t APS5_VABI stdc_first_trailing_zero_ull_nid_postfix(std::uint64_t value) { return value == std::numeric_limits::max() ? 0 : std::countr_one(value) + 1; } + +std::uint32_t APS5_VABI stdc_first_trailing_one_uc_nid_postfix(std::uint8_t value) { return value == 0 ? 0 : std::countr_zero(value) + 1; } +std::uint32_t APS5_VABI stdc_first_trailing_one_us_nid_postfix(std::uint16_t value) { return value == 0 ? 0 : std::countr_zero(value) + 1; } +std::uint32_t APS5_VABI stdc_first_trailing_one_ui_nid_postfix(std::uint32_t value) { return value == 0 ? 0 : std::countr_zero(value) + 1; } +std::uint32_t APS5_VABI stdc_first_trailing_one_ul_nid_postfix(std::uint64_t value) { return value == 0 ? 0 : std::countr_zero(value) + 1; } +std::uint32_t APS5_VABI stdc_first_trailing_one_ull_nid_postfix(std::uint64_t value) { return value == 0 ? 0 : std::countr_zero(value) + 1; } + +bool APS5_VABI stdc_has_single_bit_uc_nid_postfix(std::uint8_t value) { return std::has_single_bit(value); } +bool APS5_VABI stdc_has_single_bit_us_nid_postfix(std::uint16_t value) { return std::has_single_bit(value); } +bool APS5_VABI stdc_has_single_bit_ui_nid_postfix(std::uint32_t value) { return std::has_single_bit(value); } +bool APS5_VABI stdc_has_single_bit_ul_nid_postfix(std::uint64_t value) { return std::has_single_bit(value); } +bool APS5_VABI stdc_has_single_bit_ull_nid_postfix(std::uint64_t value) { return std::has_single_bit(value); } + +std::uint32_t APS5_VABI stdc_bit_width_uc_nid_postfix(std::uint8_t value) { return std::bit_width(value); } +std::uint32_t APS5_VABI stdc_bit_width_us_nid_postfix(std::uint16_t value) { return std::bit_width(value); } +std::uint32_t APS5_VABI stdc_bit_width_ui_nid_postfix(std::uint32_t value) { return std::bit_width(value); } +std::uint32_t APS5_VABI stdc_bit_width_ul_nid_postfix(std::uint64_t value) { return std::bit_width(value); } +std::uint32_t APS5_VABI stdc_bit_width_ull_nid_postfix(std::uint64_t value) { return std::bit_width(value); } + +std::uint8_t APS5_VABI stdc_bit_floor_uc_nid_postfix(std::uint8_t value) { return std::bit_floor(value); } +std::uint16_t APS5_VABI stdc_bit_floor_us_nid_postfix(std::uint16_t value) { return std::bit_floor(value); } +std::uint32_t APS5_VABI stdc_bit_floor_ui_nid_postfix(std::uint32_t value) { return std::bit_floor(value); } +std::uint64_t APS5_VABI stdc_bit_floor_ul_nid_postfix(std::uint64_t value) { return std::bit_floor(value); } +std::uint64_t APS5_VABI stdc_bit_floor_ull_nid_postfix(std::uint64_t value) { return std::bit_floor(value); } + +std::uint8_t APS5_VABI stdc_bit_ceil_uc_nid_postfix(std::uint8_t value) { return CeilBit(value); } +std::uint16_t APS5_VABI stdc_bit_ceil_us_nid_postfix(std::uint16_t value) { return CeilBit(value); } +std::uint32_t APS5_VABI stdc_bit_ceil_ui_nid_postfix(std::uint32_t value) { return CeilBit(value); } +std::uint64_t APS5_VABI stdc_bit_ceil_ul_nid_postfix(std::uint64_t value) { return CeilBit(value); } +std::uint64_t APS5_VABI stdc_bit_ceil_ull_nid_postfix(std::uint64_t value) { return CeilBit(value); } + int APS5_VABI __cmpdi2_nid_postfix(std::int64_t a, std::int64_t b) { return Compare(a, b); } int APS5_VABI __cmpti2_nid_postfix(Int128 a, Int128 b) { return Compare(a, b); } int APS5_VABI __ucmpdi2_nid_postfix(std::uint64_t a, std::uint64_t b) { return Compare(a, b); } diff --git a/core/libs/prx/libc/src/Interfaces.cpp b/core/libs/prx/libc/src/Interfaces.cpp index 5dd301556e..27dd2cba78 100644 --- a/core/libs/prx/libc/src/Interfaces.cpp +++ b/core/libs/prx/libc/src/Interfaces.cpp @@ -20,12 +20,12 @@ #include #include #include -#include #include #include #endif #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -118,7 +118,63 @@ std::vector BroadcastAddress(const std::vector& addr return broadcast; } +#if defined(_WIN32) || defined(__linux__) || defined(__APPLE__) +constexpr std::uint8_t guestLinkFamily = 18; +constexpr std::size_t guestLinkNameArea = 8 + 16; +constexpr std::size_t guestLinkMinimum = 54; +constexpr std::uint8_t guestTypeOther = 0x1; +constexpr std::uint8_t guestTypeEthernet = 0x6; +constexpr std::uint8_t guestTypePpp = 0x17; +constexpr std::uint8_t guestTypeLoopback = 0x18; +constexpr std::uint8_t guestLinkStateDown = 1; +constexpr std::uint8_t guestLinkStateUp = 2; +constexpr std::size_t guestInterfaceDataSize = 152; + +template +void Store(std::vector& bytes, std::size_t offset, TValue value) { + std::memcpy(bytes.data() + offset, &value, sizeof(value)); +} + +std::vector GuestLinkAddress(const std::string& name, std::uint16_t index, std::uint8_t type, const std::uint8_t* hardware, std::size_t hardwareLength) { + std::size_t size = std::max(guestLinkMinimum, guestLinkNameArea + hardwareLength); + size = Aligned(size); + if (size > 0xFF) throw std::runtime_error("getifaddrs: link-level address of " + name + " does not fit sockaddr_dl"); + std::vector guest(size, 0); + guest[0] = static_cast(size); + guest[1] = guestLinkFamily; + Store(guest, 2, index); + guest[4] = type; + guest[5] = static_cast(name.size()); + guest[6] = static_cast(hardwareLength); + std::memcpy(guest.data() + 8, name.data(), name.size()); + if (hardwareLength != 0) std::memcpy(guest.data() + 8 + name.size(), hardware, hardwareLength); + return guest; +} +#endif + #ifdef _WIN32 +std::vector GuestInterfaceData(const IP_ADAPTER_ADDRESSES* adapter, std::uint8_t type, std::uint8_t hardwareLength) { + MIB_IF_ROW2 row{}; + row.InterfaceLuid = adapter->Luid; + const auto result = GetIfEntry2(&row); + if (result != NO_ERROR) throw std::runtime_error("getifaddrs: GetIfEntry2 failed with " + std::to_string(result)); + std::vector data(guestInterfaceDataSize, 0); + data[0] = type; + data[2] = hardwareLength; + data[3] = type == guestTypeEthernet ? 14 : 0; + data[4] = adapter->OperStatus == IfOperStatusUp ? guestLinkStateUp : guestLinkStateDown; + Store(data, 6, static_cast(guestInterfaceDataSize)); + Store(data, 8, static_cast(adapter->Mtu)); + Store(data, 16, static_cast(row.TransmitLinkSpeed)); + const std::pair counters[] = { + {row.InUcastPkts + row.InNUcastPkts, 24}, {row.InErrors, 32}, {row.OutUcastPkts + row.OutNUcastPkts, 40}, {row.OutErrors, 48}, + {row.InOctets, 64}, {row.OutOctets, 72}, {row.InNUcastPkts, 80}, {row.OutNUcastPkts, 88}, + {row.InDiscards, 96}, {row.OutDiscards, 104}, {row.InUnknownProtos, 112}, + }; + for (const auto& [value, offset] : counters) Store(data, offset, value); + return data; +} + std::vector PrefixMask(int family, unsigned prefix) { const unsigned bits = family == AF_INET ? 32 : 128; if (prefix > bits) prefix = bits; @@ -158,11 +214,19 @@ int HostEntries(std::vector& entries) { if (result == ERROR_NOT_ENOUGH_MEMORY) { *__error_nid_postfix() = 12; return -1; } if (result != NO_ERROR) throw std::runtime_error("getifaddrs: GetAdaptersAddresses failed with " + std::to_string(result)); for (auto* adapter = reinterpret_cast(storage.data()); adapter; adapter = adapter->Next) { - std::string name; + const std::string name = InterfaceName(adapter); + const auto index = adapter->IfIndex ? adapter->IfIndex : adapter->Ipv6IfIndex; + if (index == 0 || index > 0xFFFF) throw std::runtime_error("getifaddrs: interface index of " + name + " does not fit sockaddr_dl"); + const std::uint8_t type = adapter->IfType == IF_TYPE_ETHERNET_CSMACD ? guestTypeEthernet + : adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK ? guestTypeLoopback : adapter->IfType == IF_TYPE_PPP ? guestTypePpp : guestTypeOther; + if (adapter->PhysicalAddressLength > sizeof(adapter->PhysicalAddress)) throw std::runtime_error("getifaddrs: invalid hardware address length on " + name); + const std::uint8_t hardwareLength = type == guestTypeLoopback ? 0 : static_cast(adapter->PhysicalAddressLength); + entries.push_back(Entry{name, InterfaceFlags(adapter), + GuestLinkAddress(name, static_cast(index), type, adapter->PhysicalAddress, hardwareLength), {}, {}, + GuestInterfaceData(adapter, type, hardwareLength)}); for (auto* unicast = adapter->FirstUnicastAddress; unicast; unicast = unicast->Next) { const sockaddr* address = unicast->Address.lpSockaddr; if (!address || (address->sa_family != AF_INET && address->sa_family != AF_INET6)) continue; - if (name.empty()) name = InterfaceName(adapter); Entry entry{name, InterfaceFlags(adapter), GuestAddress(address), PrefixMask(address->sa_family, unicast->OnLinkPrefixLength), {}}; if (address->sa_family == AF_INET && (entry.flags & guestBroadcast)) entry.destination = BroadcastAddress(entry.address, entry.netmask); @@ -172,18 +236,6 @@ int HostEntries(std::vector& entries) { return 0; } #else -#if defined(__linux__) || defined(__APPLE__) -constexpr std::uint8_t guestLinkFamily = 18; -constexpr std::size_t guestLinkNameArea = 8 + 16; -constexpr std::size_t guestLinkMinimum = 54; -constexpr std::uint8_t guestTypeOther = 0x1; -constexpr std::uint8_t guestTypeEthernet = 0x6; -constexpr std::uint8_t guestTypePpp = 0x17; -constexpr std::uint8_t guestTypeLoopback = 0x18; -constexpr std::uint8_t guestLinkStateDown = 1; -constexpr std::uint8_t guestLinkStateUp = 2; -constexpr std::size_t guestInterfaceDataSize = 152; -#endif #ifdef __linux__ constexpr unsigned int hostLowerUp = 0x10000; #endif @@ -195,29 +247,6 @@ struct HostInterface { std::vector addresses; }; -#if defined(__linux__) || defined(__APPLE__) -template -void Store(std::vector& bytes, std::size_t offset, TValue value) { - std::memcpy(bytes.data() + offset, &value, sizeof(value)); -} - -std::vector GuestLinkAddress(const std::string& name, std::uint16_t index, std::uint8_t type, const std::uint8_t* hardware, std::size_t hardwareLength) { - std::size_t size = std::max(guestLinkMinimum, guestLinkNameArea + hardwareLength); - size = Aligned(size); - if (size > 0xFF) throw std::runtime_error("getifaddrs: link-level address of " + name + " does not fit sockaddr_dl"); - std::vector guest(size, 0); - guest[0] = static_cast(size); - guest[1] = guestLinkFamily; - Store(guest, 2, index); - guest[4] = type; - guest[5] = static_cast(name.size()); - guest[6] = static_cast(hardwareLength); - std::memcpy(guest.data() + 8, name.data(), name.size()); - if (hardwareLength != 0) std::memcpy(guest.data() + 8 + name.size(), hardware, hardwareLength); - return guest; -} -#endif - #ifdef __linux__ std::optional ReadSysfsCounter(const std::string& name, const char* file) { std::ifstream input("/sys/class/net/" + name + "/" + file); diff --git a/core/libs/prx/libc/src/LegacySignals.cpp b/core/libs/prx/libc/src/LegacySignals.cpp new file mode 100644 index 0000000000..4e935693fc --- /dev/null +++ b/core/libs/prx/libc/src/LegacySignals.cpp @@ -0,0 +1,47 @@ +#include "prx/libc/libc.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include +#include +#include +#include + +namespace { + +std::atomic signalAction{}; + +} + +extern "C" { + +void RegisterSigvec_nid_no_patch(LegacySigaction_nid_no_patch action) { + signalAction.store(action, std::memory_order_release); +} + +int LegacySigvec_nid_no_patch(int signal, const GuestSignalVector* replacement, GuestSignalVector* previous) { + const auto forward = signalAction.load(std::memory_order_acquire); + if (forward == nullptr) throw std::runtime_error("sigvec: libkernel has not registered sigaction"); + GuestSigaction action{}; + if (replacement != nullptr) { + action.handler = replacement->handler; + action.flags = replacement->flags ^ 2; + action.mask.bits[0] = static_cast(replacement->mask); + } + std::optional access; + if (previous != nullptr) { + access.emplace(previous, sizeof(*previous)); + if (!access->Open()) { + *__error_nid_postfix() = 14; + return -1; + } + } + GuestSigaction old{}; + const int result = forward(signal, replacement == nullptr ? nullptr : &action, previous == nullptr ? nullptr : &old); + if (result == 0 && previous != nullptr) { + previous->handler = old.handler; + previous->mask = std::bit_cast(old.mask.bits[0]); + previous->flags = old.flags ^ 2; + } + return result; +} + +} diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index fb8343411e..b852745dc4 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -216,6 +216,69 @@ std::int64_t APS5_VABI WideCollateHash(const GuestLocale::CollateFacet*, const c const GuestLocale::CollateVtable g_wideCollateVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, WideCollateCompare, WideCollateTransform, WideCollateHash}; +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(GuestLocale::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; +} + +const char g_wideNumpunctGrouping[] = ""; +const std::uint16_t g_wideNumpunctFalseName[] = {'f', 'a', 'l', 's', 'e', 0}; +const std::uint16_t g_wideNumpunctTrueName[] = {'t', 'r', 'u', 'e', 0}; + +GuestLocale::String* APS5_VABI AssignWideCString(GuestLocale::String* result, const std::uint16_t* source) { + if (result == nullptr || source == nullptr) throw std::invalid_argument("numpunct string: null argument"); + std::size_t length = 0; + while (source[length] != 0) ++length; + const auto bytes = length * GuestWideCharacterSize; + char* data = result->buffer; + std::size_t capacity = GuestWideInlineCapacity; + if (length > GuestWideInlineCapacity) { + capacity = length | GuestWideInlineCapacity; + data = static_cast(GuestLocale::AllocateStringStorage((capacity + 1) * GuestWideCharacterSize)); + } + if (bytes != 0) std::memcpy(data, source, bytes); + data[bytes] = 0; + data[bytes + 1] = 0; + if (data != result->buffer) result->pointer = data; + result->size = length; + result->capacity = capacity; + return result; +} + +std::uint16_t APS5_VABI WideNumpunctDecimalPoint(const GuestLocale::WideNumpunctFacet* self) { + return self->decimalPoint; +} + +std::uint16_t APS5_VABI WideNumpunctThousandsSep(const GuestLocale::WideNumpunctFacet* self) { + return self->thousandsSep; +} + +GuestLocale::String* APS5_VABI WideNumpunctGrouping(GuestLocale::String* result, const GuestLocale::WideNumpunctFacet* self) { + return AssignCString(result, self->grouping); +} + +GuestLocale::String* APS5_VABI WideNumpunctFalsename(GuestLocale::String* result, const GuestLocale::WideNumpunctFacet* self) { + return AssignWideCString(result, self->falsename); +} + +GuestLocale::String* APS5_VABI WideNumpunctTruename(GuestLocale::String* result, const GuestLocale::WideNumpunctFacet* self) { + return AssignWideCString(result, self->truename); +} + +const GuestLocale::WideNumpunctVtable g_wideNumpunctVtable{{DestroyCollate, DeleteCollate, RetainFacet, ReleaseFacet}, WideNumpunctDecimalPoint, WideNumpunctThousandsSep, WideNumpunctGrouping, WideNumpunctFalsename, WideNumpunctTruename}; + bool APS5_VABI CodecvtAlwaysNoconv(const GuestLocale::Facet*) { return true; } @@ -241,23 +304,6 @@ std::size_t APS5_VABI CodecvtLength(GuestLocale::Facet*, std::uint64_t*, const c 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(GuestLocale::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; } @@ -634,6 +680,7 @@ std::uint64_t _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_ 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 _ZNSt8numpunctIwE2idE_nid_postfix = 0; std::uint64_t _ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0; std::uintptr_t _ZTVSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[13] {}; std::uintptr_t _ZTVSt8time_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE_nid_postfix[13] {}; @@ -713,6 +760,118 @@ GuestLocale::Implementation* APS5_VABI _ZNSt6locale16_GetgloballocaleEv_nid_post namespace { +constexpr std::int64_t SceV2StandardStreamSlots = 8; +std::mutex g_sceV2StandardStreamMutex; +GuestLocale::SceV2IosBase* g_sceV2StandardStreams[SceV2StandardStreamSlots + 1] = {}; +unsigned char g_sceV2StandardStreamOpens[SceV2StandardStreamSlots + 1] = {}; + +struct SceV2StreamNode { + SceV2StreamNode* next; + unsigned char reserved[24]; +}; + +struct SceV2CallbackNode { + SceV2CallbackNode* next; + std::int32_t id; + std::uint32_t reserved; + void (APS5_VABI *callback)(int, void*, std::int32_t); +}; + +static_assert(sizeof(SceV2StreamNode) == 0x20); +static_assert(sizeof(SceV2CallbackNode) == 0x18); + +std::vector g_sceV2RegisteredFacets; + +} + +std::uint64_t _ZNSt8__sce_v25ctypeIcE2idE_nid_postfix = 0; +std::uint64_t _ZNSt8__sce_v25ctypeIwE2idE_nid_postfix = 0; +std::uint64_t _ZNSt8__sce_v28numpunctIcE2idE_nid_postfix = 0; +std::uint64_t _ZNSt8__sce_v27num_putIcNS_19ostreambuf_iteratorIcNS_11char_traitsIcEEEEE2idE_nid_postfix = 0; +std::uint64_t _ZNSt8__sce_v27codecvtIwc9_MbstatetE2idE_nid_postfix = 0; +std::int32_t _ZNSt8__sce_v26locale2id7_Id_cntE_nid_postfix = 0; +GuestLocale::Implementation* _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix = &g_classicLocale; +alignas(16) unsigned char _ZNSt8__sce_v24coutE_nid_postfix[0x400] {}; +alignas(16) unsigned char _ZNSt8__sce_v24cerrE_nid_postfix[0x400] {}; +alignas(16) unsigned char _ZNSt8__sce_v25wcoutE_nid_postfix[0x400] {}; +alignas(16) unsigned char _ZNSt8__sce_v25wcerrE_nid_postfix[0x400] {}; +alignas(16) unsigned char _ZNSt8__sce_v23cinE_nid_postfix[0x400] {}; +alignas(16) unsigned char _ZNSt8__sce_v24wcinE_nid_postfix[0x400] {}; + +GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale5_InitEb_nid_postfix(bool doIncref) { + std::lock_guard lock(g_localeInitMutex); + if (doIncref) RetainLocale(&g_classicLocale.base); + return &g_classicLocale; +} + +GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix() { + return _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; +} + +void APS5_VABI _ZNSt8__sce_v28ios_base7_AddstdEPS0__nid_postfix(GuestLocale::SceV2IosBase* self) { + if (self == nullptr) throw std::invalid_argument("ios_base _Addstd: null stream"); + std::lock_guard lock(g_sceV2StandardStreamMutex); + for (self->standardStream = 1; self->standardStream < SceV2StandardStreamSlots; ++self->standardStream) { + if (g_sceV2StandardStreams[self->standardStream] == nullptr || g_sceV2StandardStreams[self->standardStream] == self) break; + } + g_sceV2StandardStreams[self->standardStream] = self; + ++g_sceV2StandardStreamOpens[self->standardStream]; +} + +void APS5_VABI _ZNSt8__sce_v28ios_base14_Ios_base_dtorEPS0__nid_postfix(GuestLocale::SceV2IosBase* self) { + if (self == nullptr) throw std::invalid_argument("ios_base destructor: null object"); + { + std::lock_guard lock(g_sceV2StandardStreamMutex); + if (self->standardStream > 0) { + if (self->standardStream > SceV2StandardStreamSlots) throw std::runtime_error("ios_base destructor: unknown standard stream slot"); + if (0 < --g_sceV2StandardStreamOpens[self->standardStream]) return; + } + } + for (auto* node = static_cast(self->callbackNodes); node != nullptr; node = node->next) { + node->callback(0, self, node->id); + } + for (auto* node = static_cast(self->streamNodes); node != nullptr;) { + auto* next = node->next; + _ZdlPv_nid_postfix(node); + node = next; + } + self->streamNodes = nullptr; + for (auto* node = static_cast(self->callbackNodes); node != nullptr;) { + auto* next = node->next; + _ZdlPv_nid_postfix(node); + node = next; + } + self->callbackNodes = nullptr; + auto** locale = self->locale; + if (locale != nullptr && locale != &_ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix) { + GuestLocale::Implementation* implementation = *locale; + if (implementation != nullptr) { + const GuestLocale::FacetVtable* vtable = implementation->base.vtable; + GuestLocale::Facet* released = vtable != nullptr ? vtable->release(&implementation->base) : nullptr; + if (released != nullptr && released->vtable != nullptr) released->vtable->deleteObject(released); + } + } + self->locale = nullptr; +} + +void APS5_VABI _ZNSt8__sce_v215_Facet_RegisterEPNS_11_Facet_baseE_nid_postfix(GuestLocale::Facet* facet) { + std::lock_guard lock(g_localeInitMutex); + g_sceV2RegisteredFacets.push_back(facet); +} + +GuestLocale::Implementation* APS5_VABI _ZNSt8__sce_v26locale7_Locimp11_New_LocimpERKS1__nid_postfix(const GuestLocale::Implementation* source) { + if (source == nullptr) throw std::invalid_argument("_Locimp constructor: null source"); + auto* self = static_cast(_Znwm_nid_postfix(sizeof(GuestLocale::Implementation))); + _ZNSt6locale7_LocimpC1ERKS0__nid_postfix(self, source); + return self; +} + +void APS5_VABI _ZNSt8__sce_v26locale7_Locimp14_Locimp_AddfacEPS1_PNS0_5facetEm_nid_postfix(GuestLocale::Implementation* self, GuestLocale::Facet* facet, std::size_t id) { + _ZNSt6locale7_Locimp7_AddfacEPNS_5facetEm_nid_postfix(self, facet, id); +} + +namespace { + GuestLocaleFacade g_classicLocaleFacade{&g_classicLocale}; } @@ -758,6 +917,15 @@ std::size_t APS5_VABI _ZNSt8numpunctIcE7_GetcatEPPKNSt6locale5facetEPKS1__nid_po return 4; } +std::size_t APS5_VABI _ZNSt8numpunctIwE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(GuestLocale::Facet** facet, const GuestLocale::Implementation* const*) { + if (facet != nullptr && *facet == nullptr) { + auto* punct = static_cast(_Znwm_nid_postfix(sizeof(GuestLocale::WideNumpunctFacet))); + *punct = {{&g_wideNumpunctVtable.facet, 0, 0}, g_wideNumpunctGrouping, '.', ',', g_wideNumpunctFalseName, g_wideNumpunctTrueName}; + *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))); @@ -840,6 +1008,21 @@ mbstate_t* APS5_VABI _Getpwcstate_nid_postfix() { return &state; } +GuestLocale::TimeInfo* APS5_VABI _Getptimes_nid_postfix() { + static_assert(sizeof(GuestLocale::TimeInfo) == 0xa8); + static constexpr char days[] = ":Sun:Sunday:Mon:Monday:Tue:Tuesday:Wed:Wednesday:Thu:Thursday:Fri:Friday:Sat:Saturday"; + static constexpr char months[] = ":Jan:January:Feb:February:Mar:March:Apr:April:May:May:Jun:June:Jul:July:Aug:August" + ":Sep:September:Oct:October:Nov:November:Dec:December"; + static constexpr char formats[] = "|%a %b %e %T %Y|%m/%d/%y|%H:%M:%S|%I:%M:%S %p"; + static constexpr char era[] = ""; + static constexpr char alternativeDigits[] = ""; + static constexpr char daylightSaving[] = ""; + static constexpr char timeZone[] = ""; + static GuestLocale::TimeInfo times {":AM:PM", days, days, days, months, months, months, formats, formats, formats, formats, + formats, formats, formats, formats, formats, formats, era, alternativeDigits, daylightSaving, timeZone}; + return × +} + std::uint32_t APS5_VABI _Towctrans_nid_postfix(std::uint32_t c, std::uint64_t desc) { constexpr std::uint64_t ToLower = 1; constexpr std::uint64_t ToUpper = 2; diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 592dbfa127..4118c24566 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -58,7 +58,7 @@ * * Developed at SunSoft, a Sun Microsystems, Inc. business. * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice + * software is freely granted, provided that this notice * is preserved. * ==================================================== */ @@ -579,7 +579,22 @@ float APS5_VABI scalbnf_nid_postfix(float x, int exponent) { long double APS5_VABI scalbnl_nid_postfix(long double x, int exponent) { return std::scalbn(x, exponent); } float APS5_VABI significandf_nid_postfix(float x) { return x == 0.0f || !std::isfinite(x) ? x : std::scalbn(x, -std::ilogb(x)); } double APS5_VABI significand_nid_postfix(double x) { return x == 0.0 || !std::isfinite(x) ? x : std::scalbn(x, -std::ilogb(x)); } -double APS5_VABI frexp_nid_postfix(double x, int* exponent) { return std::frexp(x, exponent); } +double APS5_VABI frexp_nid_postfix(double x, int* exponent) { + auto bits = std::bit_cast(x); + auto magnitude = bits & 0x7fffffffffffffffull; + *exponent = 0; + if (magnitude == 0 || magnitude >= 0x7ff0000000000000ull) return x; + int adjustment = 0; + if (magnitude < 0x0010000000000000ull) { + volatile double subnormal = x; + x = subnormal * 0x1p54; + bits = std::bit_cast(x); + magnitude = bits & 0x7fffffffffffffffull; + adjustment = -54; + } + *exponent = static_cast(magnitude >> 52) - 1022 + adjustment; + return std::bit_cast((bits & 0x800fffffffffffffull) | 0x3fe0000000000000ull); +} float APS5_VABI frexpf_nid_postfix(float x, int* exponent) { auto bits = std::bit_cast(x); auto magnitude = bits & 0x7fffffffu; @@ -607,7 +622,6 @@ 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; } long double APS5_VABI nearbyintl_nid_postfix(long double x) { return std::nearbyint(x); } -double APS5_VABI nearbyint_nid_postfix(double x) { return std::isnan(x) ? x + x : std::nearbyint(x); } double APS5_VABI cbrt_nid_postfix(double x) { const std::uint32_t B1 = 715094163; @@ -667,10 +681,8 @@ float APS5_VABI copysignf_nid_postfix(float x, float 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::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); } @@ -743,10 +755,137 @@ long double APS5_VABI frexpl_nid_postfix(long double x, int* exponent) { if (!std::isfinite(x)) return x; return std::frexp(x, exponent); } -double APS5_VABI exp_nid_postfix(double x) { return std::exp(x); } -double APS5_VABI atan_nid_postfix(double x) { return std::atan(x); } +/* + * ==================================================== + * Copyright (C) 2004 by Sun Microsystems, Inc. All rights reserved. + * + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ +/* Adapted from FreeBSD release/11.4.0 lib/msun/src/e_exp.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/msun/src/e_exp.c */ +double APS5_VABI exp_nid_postfix(double x) { + static constexpr double halF[2] = {0.5, -0.5}; + static constexpr double ln2HI[2] = {6.93147180369123816490e-01, -6.93147180369123816490e-01}; + static constexpr double ln2LO[2] = {1.90821492927058770002e-10, -1.90821492927058770002e-10}; + static volatile double huge = 1.0e+300; + static volatile double twom1000 = 9.33263618503218878990e-302; + const double invln2 = 1.44269504088896338700e+00; + const double P1 = 1.66666666666666019037e-01; + const double P2 = -2.77777777770155933842e-03; + const double P3 = 6.61375632143793436117e-05; + const double P4 = -1.65339022054652515390e-06; + const double P5 = 4.13813679705723846039e-08; + double hi = 0.0; + double lo = 0.0; + std::int32_t k = 0; + const std::uint64_t bits = std::bit_cast(x); + std::uint32_t hx = static_cast(bits >> 32); + const std::int32_t xsb = (hx >> 31) & 1; + hx &= 0x7fffffff; + if (hx >= 0x40862e42) { + if (hx >= 0x7ff00000) { + if (((hx & 0xfffff) | static_cast(bits)) != 0) { + return x + x; + } + return xsb == 0 ? x : 0.0; + } + if (x > 7.09782712893383973096e+02) { + errno = ERANGE; + return huge * huge; + } + if (x < -7.45133219101941108420e+02) { + errno = ERANGE; + return twom1000 * twom1000; + } + } + if (x == 1.0) { + return std::bit_cast(std::uint64_t{0x4005bf0a8b145769}); + } + if (hx > 0x3fd62e42) { + if (hx < 0x3ff0a2b2) { + hi = x - ln2HI[xsb]; + lo = ln2LO[xsb]; + k = 1 - xsb - xsb; + } else { + k = static_cast(invln2 * x + halF[xsb]); + const double t = k; + hi = x - t * ln2HI[0]; + lo = t * ln2LO[0]; + } + x = hi - lo; + } else if (hx < 0x3e300000) { + return 1.0 + x; + } + const double t = x * x; + const double twopk = std::bit_cast(static_cast(0x3ff + (k >= -1021 ? k : k + 1000)) << 52); + const double c = x - t * (P1 + t * (P2 + t * (P3 + t * (P4 + t * P5)))); + if (k == 0) { + return 1.0 - ((x * c) / (c - 2.0) - x); + } + const double y = 1.0 - ((lo - (x * c) / (2.0 - c)) - hi); + if (k >= -1021) { + if (k == 1024) { + return y * 2.0 * 0x1p1023; + } + return y * twopk; + } + return y * twopk * twom1000; +} +double APS5_VABI atan_nid_postfix(double x) { + static constexpr double atanhi[] = {4.63647609000806093515e-01, 7.85398163397448278999e-01, 9.82793723247329054082e-01, 1.57079632679489655800e+00}; + static constexpr double atanlo[] = {2.26987774529616870924e-17, 3.06161699786838301793e-17, 1.39033110312309984516e-17, 6.12323399573676603587e-17}; + static constexpr double aT[] = { + 3.33333333333329318027e-01, -1.99999999998764832476e-01, 1.42857142725034663711e-01, -1.11111104054623557880e-01, + 9.09088713343650656196e-02, -7.69187620504482999495e-02, 6.66107313738753120669e-02, -5.83357013379057348645e-02, + 4.97687799461593236017e-02, -3.65315727442169155270e-02, 1.62858201153657823623e-02, + }; + static volatile double atanloInfinity = 6.12323399573676603587e-17; + const std::uint64_t bits = std::bit_cast(x); + const std::int32_t hx = static_cast(bits >> 32); + const std::int32_t ix = hx & 0x7fffffff; + int id; + if (ix >= 0x44100000) { + if (ix > 0x7ff00000 || (ix == 0x7ff00000 && static_cast(bits) != 0)) { + return x + x; + } + return hx > 0 ? atanhi[3] + atanloInfinity : -atanhi[3] - atanloInfinity; + } + if (ix < 0x3fdc0000) { + if (ix < 0x3e400000) { + return x; + } + id = -1; + } else { + x = std::fabs(x); + if (ix < 0x3ff30000) { + if (ix < 0x3fe60000) { + id = 0; + x = (2.0 * x - 1.0) / (2.0 + x); + } else { + id = 1; + x = (x - 1.0) / (x + 1.0); + } + } else if (ix < 0x40038000) { + id = 2; + x = (x - 1.5) / (1.0 + 1.5 * x); + } else { + id = 3; + x = -1.0 / x; + } + } + double z = x * x; + const double w = z * z; + const double s1 = z * (aT[0] + w * (aT[2] + w * (aT[4] + w * (aT[6] + w * (aT[8] + w * aT[10]))))); + const double s2 = w * (aT[1] + w * (aT[3] + w * (aT[5] + w * (aT[7] + w * aT[9])))); + if (id < 0) { + return x - x * (s1 + s2); + } + z = atanhi[id] - ((x * (s1 + s2) - atanlo[id]) - x); + return hx < 0 ? -z : z; +} 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) { const double ln2_hi = 6.93147180369123816490e-01; @@ -1353,7 +1492,7 @@ static double SinhLdexpExpHalf(double x) { * * Developed at SunSoft, a Sun Microsystems, Inc. business. * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice + * software is freely granted, provided that this notice * is preserved. * ==================================================== */ @@ -1609,7 +1748,75 @@ float APS5_VABI atan2f_nid_postfix(float y, float x) { default: return (z - pi_lo) - pi; } } -double APS5_VABI atan2_nid_postfix(double y, double x) { return std::atan2(y, x); } +double APS5_VABI atan2_nid_postfix(double y, double x) { + static volatile double tiny = 1.0e-300; + static volatile double pi_lo = 1.2246467991473531772E-16; + const double pi_o_4 = 7.8539816339744827900E-01; + const double pi_o_2 = 1.5707963267948965580E+00; + const double pi = 3.1415926535897931160E+00; + const std::uint64_t xbits = std::bit_cast(x); + const std::uint64_t ybits = std::bit_cast(y); + const std::int32_t hx = static_cast(xbits >> 32); + const std::int32_t hy = static_cast(ybits >> 32); + const std::uint32_t lx = static_cast(xbits); + const std::uint32_t ly = static_cast(ybits); + const std::int32_t ix = hx & 0x7fffffff; + const std::int32_t iy = hy & 0x7fffffff; + if ((static_cast(ix) | ((lx | (0u - lx)) >> 31)) > 0x7ff00000u || + (static_cast(iy) | ((ly | (0u - ly)) >> 31)) > 0x7ff00000u) { + volatile double sum = x + y; + (void)sum; + return std::bit_cast(((ybits & 0x7fffffffffffffffull) > 0x7ff0000000000000ull ? ybits : xbits) | 0x0008000000000000ull); + } + if (hx == 0x3ff00000 && lx == 0) { + return atan_nid_postfix(y); + } + int m = ((hy >> 31) & 1) | ((hx >> 30) & 2); + if ((static_cast(iy) | ly) == 0) { + if (m < 2) { + return y; + } + return m == 2 ? pi + tiny : -pi - tiny; + } + if ((static_cast(ix) | lx) == 0) { + return hy < 0 ? -pi_o_2 - tiny : pi_o_2 + tiny; + } + if (ix == 0x7ff00000) { + if (iy == 0x7ff00000) { + switch (m) { + case 0: return pi_o_4 + tiny; + case 1: return -pi_o_4 - tiny; + case 2: return 3.0 * pi_o_4 + tiny; + default: return -3.0 * pi_o_4 - tiny; + } + } + switch (m) { + case 0: return 0.0; + case 1: return -0.0; + case 2: return pi + tiny; + default: return -pi - tiny; + } + } + if (iy == 0x7ff00000) { + return hy < 0 ? -pi_o_2 - tiny : pi_o_2 + tiny; + } + const std::int32_t k = (iy - ix) >> 20; + double z; + if (k > 60) { + z = pi_o_2 + 0.5 * pi_lo; + m &= 1; + } else if (hx < 0 && k < -60) { + z = 0.0; + } else { + z = atan_nid_postfix(std::fabs(y / x)); + } + switch (m) { + case 0: return z; + case 1: return -z; + case 2: return pi - (z - pi_lo); + default: return (z - pi_lo) - pi; + } +} static inline std::int32_t PowfWord(float f) { return std::bit_cast(f); } static inline float PowfFloat(std::int32_t w) { return std::bit_cast(w); } @@ -1855,7 +2062,116 @@ float APS5_VABI logf_nid_postfix(float x) { 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); } +static double KernelLog(double x) { + const double Lg1 = 6.666666666666735130e-01; + const double Lg2 = 3.999999999940941908e-01; + const double Lg3 = 2.857142874366239149e-01; + const double Lg4 = 2.222219843214978396e-01; + const double Lg5 = 1.818357216161805012e-01; + const double Lg6 = 1.531383769920937332e-01; + const double Lg7 = 1.479819860511658591e-01; + std::int32_t hx = static_cast(std::bit_cast(x) >> 32); + const double f = x - 1.0; + if ((0x000fffff & (2 + hx)) < 3) { + if (f == 0.0) { + return 0.0; + } + return f * f * (0.33333333333333333 * f - 0.5); + } + const double s = f / (2.0 + f); + const double z = s * s; + hx &= 0x000fffff; + std::int32_t i = hx - 0x6147a; + const double w = z * z; + const std::int32_t j = 0x6b851 - hx; + const double t1 = w * (Lg2 + w * (Lg4 + w * Lg6)); + const double t2 = z * (Lg1 + w * (Lg3 + w * (Lg5 + w * Lg7))); + i |= j; + const double R = t2 + t1; + if (i > 0) { + const double hfsq = 0.5 * f * f; + return s * (hfsq + R) - hfsq; + } + return s * (R - f); +} + +double APS5_VABI log10_nid_postfix(double x) { + const double two54 = 1.80143985094819840000e+16; + const double ivln10hi = 4.34294481878168880939e-01; + const double ivln10lo = 2.50829467116452752298e-11; + const double log10_2hi = 3.01029995663611771306e-01; + const double log10_2lo = 3.69423907715893078616e-13; + static volatile double zero = 0.0; + std::uint64_t bits = std::bit_cast(x); + std::int32_t hx = static_cast(bits >> 32); + const std::uint32_t lx = static_cast(bits); + std::int32_t k = 0; + if (hx < 0x00100000) { + if (((hx & 0x7fffffff) | lx) == 0) { + return -std::numeric_limits::infinity(); + } + if (hx < 0) { + _Feraise_nid_postfix(0x01); + return (x - x) / zero; + } + k -= 54; + x *= two54; + bits = std::bit_cast(x); + hx = static_cast(bits >> 32); + } + if (hx >= 0x7ff00000) { + return x + x; + } + k += (hx >> 20) - 1023; + hx &= 0x000fffff; + const std::int32_t i = (hx + 0x95f64) & 0x100000; + x = std::bit_cast((static_cast(static_cast(hx | (i ^ 0x3ff00000))) << 32) | (bits & 0xffffffffu)); + k += i >> 20; + const double y = k; + const double f = KernelLog(x); + x = x - 1; + const double hi = std::bit_cast(std::bit_cast(x) & 0xffffffff00000000ull); + const double lo = x - hi; + const double z = y * log10_2lo + (x + f) * ivln10lo + (lo + f) * ivln10hi + hi * ivln10hi; + return z + y * log10_2hi; +} + +double APS5_VABI log2_nid_postfix(double x) { + const double two54 = 1.80143985094819840000e+16; + const double ivln2hi = 1.44269504072144627571e+00; + const double ivln2lo = 1.67517131648865118353e-10; + static volatile double zero = 0.0; + std::uint64_t bits = std::bit_cast(x); + std::int32_t hx = static_cast(bits >> 32); + const std::uint32_t lx = static_cast(bits); + std::int32_t k = 0; + if (hx < 0x00100000) { + if (((hx & 0x7fffffff) | lx) == 0) { + return -std::numeric_limits::infinity(); + } + if (hx < 0) { + _Feraise_nid_postfix(0x01); + return (x - x) / zero; + } + k -= 54; + x *= two54; + bits = std::bit_cast(x); + hx = static_cast(bits >> 32); + } + if (hx >= 0x7ff00000) { + return x + x; + } + k += (hx >> 20) - 1023; + hx &= 0x000fffff; + const std::int32_t i = (hx + 0x95f64) & 0x100000; + x = std::bit_cast((static_cast(static_cast(hx | (i ^ 0x3ff00000))) << 32) | (bits & 0xffffffffu)); + k += i >> 20; + const double f = KernelLog(x); + x = x - 1; + const double hi = std::bit_cast(std::bit_cast(x) & 0xffffffff00000000ull); + const double lo = x - hi; + return (x + f) * ivln2lo + (lo + f) * ivln2hi + hi * ivln2hi + k; +} float APS5_VABI ldexpf_nid_postfix(float x, int exp) { const int saved_errno = errno; const float result = std::scalbn(x, exp); @@ -2033,7 +2349,6 @@ double APS5_VABI tanh_nid_postfix(double x) { return jx >= 0 ? z : -z; } - float APS5_VABI tanhf_nid_postfix(float x) { const float one = 1.0f, two = 2.0f; static volatile float tiny = 1.0e-30f; @@ -2132,6 +2447,18 @@ short APS5_VABI _Dtest_nid_postfix(const double* value) { return Finite; } +short APS5_VABI _FDsign_nid_postfix(float value) { + std::uint32_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + return static_cast((bits >> 16) & 0x8000u); +} + +short APS5_VABI _Dsign_nid_postfix(double value) { + std::uint64_t bits; + std::memcpy(&bits, &value, sizeof(bits)); + return static_cast((bits >> 48) & 0x8000u); +} + int APS5_VABI __isnanf_nid_postfix(float x) { return std::isnan(x) ? 1 : 0; } int APS5_VABI isnanf_nid_postfix(float x) { return __isnanf_nid_postfix(x); } int APS5_VABI isnan_nid_postfix(double x) { return __isnan_nid_postfix(x); } diff --git a/core/libs/prx/libc/src/MathExact.cpp b/core/libs/prx/libc/src/MathExact.cpp index abf586e8f5..7423ded136 100644 --- a/core/libs/prx/libc/src/MathExact.cpp +++ b/core/libs/prx/libc/src/MathExact.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/Api.hpp" #include +#include #include #include #include @@ -7,6 +8,7 @@ #include #include #include +#include #include "prx/libc/include/General.hpp" @@ -79,6 +81,182 @@ int Exponent(std::int64_t exponent) { return static_cast(std::clamp(exponent, INT_MIN, INT_MAX)); } +void StoreExtended(long double* value, const ExtendedBits& bits) { + unsigned char bytes[sizeof(long double)] = {}; + std::memcpy(bytes, value, sizeof(bytes)); + std::memcpy(bytes, &bits.mantissa, sizeof(bits.mantissa)); + std::memcpy(bytes + 8, &bits.signExponent, sizeof(bits.signExponent)); + std::memcpy(value, bytes, sizeof(bytes)); +} + +bool ExtendedNan(const ExtendedBits& bits) { + return (bits.signExponent & 0x7fff) == 0x7fff && (bits.mantissa & 0x7fffffffffffffffull) != 0; +} + +long double ExtendedValue(const ExtendedBits& bits) { + const int exponent = bits.signExponent & 0x7fff; + const long double magnitude = std::ldexp(static_cast(bits.mantissa), (exponent == 0 ? 1 : exponent) - 16383 - 63); + return (bits.signExponent & 0x8000) != 0 ? -magnitude : magnitude; +} + +template +short DropFraction(void* value, short scale) { + constexpr TBits Sign = TBits{1} << (FractionBits + ExponentBits); + constexpr TBits FractionMask = (TBits{1} << FractionBits) - 1; + constexpr int Maximum = (1 << ExponentBits) - 1; + constexpr int Bias = (1 << (ExponentBits - 1)) - 2; + TBits bits; + std::memcpy(&bits, value, sizeof(bits)); + const int field = static_cast((bits >> FractionBits) & Maximum); + if (field == Maximum) return (bits & FractionMask) != 0 ? NotANumber : Infinite; + if ((bits & ~Sign) == 0) return Zero; + const short drop = static_cast(Bias + FractionBits + 1 - field - scale); + if (drop <= 0) return Zero; + if (drop > FractionBits) { + bits &= Sign; + std::memcpy(value, &bits, sizeof(bits)); + return Finite; + } + const TBits mask = (TBits{1} << drop) - 1; + const TBits lost = bits & mask; + bits &= ~mask; + std::memcpy(value, &bits, sizeof(bits)); + return lost != 0 ? Finite : Zero; +} + +template +short ResultCode(TFloat value) { + if (std::isinf(value)) return Infinite; + return value == 0 ? Zero : Finite; +} + +template +short ScaleBinary(TFloat* value, std::int64_t exponent) { + if (std::isnan(*value)) return NotANumber; + if (std::isinf(*value)) return Infinite; + if (*value == 0) return Zero; + ErrnoGuard guard; + *value = std::ldexp(*value, static_cast(std::clamp(exponent, -100000, 100000))); + return ResultCode(*value); +} + +template +short UnscaleBinary(short* exponent, TFloat* value) { + *exponent = 0; + if (std::isnan(*value)) return NotANumber; + if (std::isinf(*value)) return Infinite; + if (*value == 0) return Zero; + int unscaled = 0; + *value = std::frexp(*value, &unscaled); + *exponent = static_cast(unscaled); + return Finite; +} + +template +short NormalizeFraction(void* value) { + constexpr TBits FractionMask = (TBits{1} << FractionBits) - 1; + constexpr TBits Sign = TBits{1} << (sizeof(TBits) * CHAR_BIT - 1); + TBits bits; + std::memcpy(&bits, value, sizeof(bits)); + TBits fraction = bits & FractionMask; + short exponent = 1; + if (fraction != 0) { + const int shift = FractionBits + 1 - static_cast(std::bit_width(fraction)); + exponent = static_cast(1 - shift); + fraction = (fraction << shift) & FractionMask; + } + bits = (bits & Sign) | fraction; + std::memcpy(value, &bits, sizeof(bits)); + return exponent; +} + +template +short ScaleFieldBits(void* value, std::int64_t exponent) { + constexpr TBits Sign = TBits{1} << (FractionBits + ExponentBits); + constexpr TBits Hidden = TBits{1} << FractionBits; + constexpr TBits FractionMask = Hidden - 1; + constexpr std::int64_t Maximum = (std::int64_t{1} << ExponentBits) - 1; + TBits bits; + std::memcpy(&bits, value, sizeof(bits)); + const TBits sign = bits & Sign; + TBits fraction = bits & FractionMask; + std::int64_t field = static_cast((bits >> FractionBits) & Maximum); + if (field == Maximum) return fraction != 0 ? NotANumber : Infinite; + if (field == 0) { + if (fraction == 0) return Zero; + const int shift = FractionBits + 1 - static_cast(std::bit_width(fraction)); + fraction = (fraction << shift) & FractionMask; + field = 1 - shift; + } + if (exponent > 0 && Maximum - field <= exponent) { + bits = sign | (static_cast(Maximum) << FractionBits); + std::memcpy(value, &bits, sizeof(bits)); + return Infinite; + } + if (-field < exponent) { + bits = sign | (static_cast(exponent + field) << FractionBits) | fraction; + std::memcpy(value, &bits, sizeof(bits)); + return Finite; + } + const auto shifted = static_cast(static_cast(exponent) + static_cast(field - 1)); + if (shifted < -(FractionBits + 1) || shifted >= 0) { + std::memcpy(value, &sign, sizeof(sign)); + return Zero; + } + const int drop = static_cast(-shifted); + const TBits significand = Hidden | fraction; + TBits result = significand >> drop; + const TBits lost = significand & ((TBits{1} << drop) - 1); + const TBits half = TBits{1} << (drop - 1); + if (lost > half || (lost == half && (result & 1) != 0)) ++result; + bits = sign | result; + std::memcpy(value, &bits, sizeof(bits)); + return result != 0 ? Finite : Zero; +} + +template +short UnscaleFieldBits(short* exponent, void* value) { + constexpr TBits Sign = TBits{1} << (FractionBits + ExponentBits); + constexpr TBits FractionMask = (TBits{1} << FractionBits) - 1; + constexpr int Maximum = (1 << ExponentBits) - 1; + constexpr int Half = (1 << (ExponentBits - 1)) - 2; + TBits bits; + std::memcpy(&bits, value, sizeof(bits)); + TBits fraction = bits & FractionMask; + int field = static_cast((bits >> FractionBits) & Maximum); + if (field == Maximum) { + *exponent = 0; + return fraction != 0 ? NotANumber : Infinite; + } + if (field == 0) { + if (fraction == 0) { + *exponent = 0; + return Zero; + } + const int shift = FractionBits + 1 - static_cast(std::bit_width(fraction)); + fraction = (fraction << shift) & FractionMask; + field = 1 - shift; + } + bits = (bits & Sign) | (static_cast(Half) << FractionBits) | fraction; + std::memcpy(value, &bits, sizeof(bits)); + *exponent = static_cast(field - Half); + return Finite; +} + +template +int Compare(TFloat x, TFloat y) { + if (x < y) return 1; + if (x > y) return 4; + return 2; +} + +template +TFloat FusedPolynomial(TFloat x, const TFloat* table, int degree) { + TFloat result = table[0]; + for (int index = 1; index <= degree; ++index) result = std::fma(result, x, table[index]); + return result; +} + } extern "C" { @@ -177,9 +355,12 @@ long double APS5_VABI fmal_nid_postfix(long double x, long double y, long double return std::fma(x, y, z); } +double APS5_VABI floor_nid_postfix(double x) { return std::isnan(x) ? x + x : std::floor(x); } +float APS5_VABI floorf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::floor(x); } double APS5_VABI rint_nid_postfix(double x) { return std::isnan(x) ? x + x : std::rint(x); } float APS5_VABI rintf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::rint(x); } float APS5_VABI nearbyintf_nid_postfix(float x) { return std::isnan(x) ? x + x : std::rintf(x); } +double APS5_VABI nearbyint_nid_postfix(double x) { return std::isnan(x) ? x + x : std::rint(x); } std::int64_t APS5_VABI lrint_nid_postfix(double x) { return _mm_cvtsd_si64(_mm_set_sd(x)); } @@ -250,4 +431,103 @@ int APS5_VABI _FDclass_nid_postfix(float x) { return TestFloat(x); } int APS5_VABI _LDclass_nid_postfix(long double x) { return TestExtended(Bits(x)); } short APS5_VABI _LDsign_nid_postfix(long double x) { return static_cast(Bits(x).signExponent & 0x8000); } +short APS5_VABI _Dint_nid_postfix(double* value, short scale) { return DropFraction(value, scale); } +short APS5_VABI _FDint_nid_postfix(float* value, short scale) { return DropFraction(value, scale); } + +short APS5_VABI _LDint_nid_postfix(long double* value, short scale) { + ExtendedBits bits = Bits(*value); + const int field = bits.signExponent & 0x7fff; + if (field == 0x7fff) return ExtendedNan(bits) ? NotANumber : Infinite; + if (bits.mantissa == 0) return Zero; + const short drop = static_cast(0x3ffe + 64 - field - scale); + if (drop <= 0) return Zero; + if (drop >= 64) { + StoreExtended(value, {0, static_cast(bits.signExponent & 0x8000)}); + return Finite; + } + const std::uint64_t mask = (std::uint64_t{1} << drop) - 1; + const std::uint64_t lost = bits.mantissa & mask; + bits.mantissa &= ~mask; + StoreExtended(value, bits); + return lost != 0 ? Finite : Zero; +} + +short APS5_VABI _Dscale_nid_postfix(double* value, std::int64_t exponent) { return ScaleBinary(value, exponent); } +short APS5_VABI _FDscale_nid_postfix(float* value, std::int64_t exponent) { return ScaleBinary(value, exponent); } + +short APS5_VABI _LDscale_nid_postfix(long double* value, std::int64_t exponent) { + const ExtendedBits bits = Bits(*value); + if ((bits.signExponent & 0x7fff) == 0x7fff) return ExtendedNan(bits) ? NotANumber : Infinite; + if (bits.mantissa == 0) { + StoreExtended(value, {0, static_cast(bits.signExponent & 0x8000)}); + return Zero; + } + *value = ExtendedValue(bits); + const short code = ScaleBinary(value, exponent); + if (code == Zero) *value = 0.0L; + return code; +} + +short APS5_VABI _Dunscale_nid_postfix(short* exponent, double* value) { return UnscaleBinary(exponent, value); } +short APS5_VABI _FDunscale_nid_postfix(short* exponent, float* value) { return UnscaleBinary(exponent, value); } + +short APS5_VABI _LDunscale_nid_postfix(short* exponent, long double* value) { + const ExtendedBits bits = Bits(*value); + if ((bits.signExponent & 0x7fff) == 0x7fff) { + *exponent = 0; + return ExtendedNan(bits) ? NotANumber : Infinite; + } + if (bits.mantissa == 0) { + *exponent = 0; + return Zero; + } + *value = ExtendedValue(bits); + return UnscaleBinary(exponent, value); +} + +short APS5_VABI _ZNSt8__sce_v27_DscaleEPdl_nid_postfix(double* value, std::int64_t exponent) { return ScaleFieldBits(value, exponent); } +short APS5_VABI _ZNSt8__sce_v28_FDscaleEPfl_nid_postfix(float* value, std::int64_t exponent) { return ScaleFieldBits(value, exponent); } +short APS5_VABI _ZNSt8__sce_v28_LDscaleEPel_nid_postfix(double* value, std::int64_t exponent) { return ScaleFieldBits(value, exponent); } +short APS5_VABI _ZNSt8__sce_v29_DunscaleEPsPd_nid_postfix(short* exponent, double* value) { return UnscaleFieldBits(exponent, value); } +short APS5_VABI _ZNSt8__sce_v210_FDunscaleEPsPf_nid_postfix(short* exponent, float* value) { return UnscaleFieldBits(exponent, value); } +short APS5_VABI _ZNSt8__sce_v210_LDunscaleEPsPe_nid_postfix(short* exponent, double* value) { return UnscaleFieldBits(exponent, value); } + +short APS5_VABI _Dnorm_nid_postfix(std::uint16_t* words) { return NormalizeFraction(words); } +short APS5_VABI _FDnorm_nid_postfix(std::uint16_t* words) { return NormalizeFraction(words); } + +short APS5_VABI _LDnorm_nid_postfix(std::uint16_t* words) { + std::uint64_t mantissa; + std::memcpy(&mantissa, words, sizeof(mantissa)); + if (mantissa == 0) throw std::invalid_argument("_LDnorm: a zero significand never returns on the console"); + const int shift = std::countl_zero(mantissa); + mantissa <<= shift; + std::memcpy(words, &mantissa, sizeof(mantissa)); + return static_cast(-shift); +} + +short APS5_VABI _ZNSt8__sce_v26_DtestEPd_nid_postfix(double* value) { return _Dtest_nid_postfix(value); } +short APS5_VABI _ZNSt8__sce_v27_FDtestEPf_nid_postfix(float* value) { return _FDtest_nid_postfix(value); } +short APS5_VABI _ZNSt8__sce_v27_LDtestEPe_nid_postfix(double* value) { return _Dtest_nid_postfix(value); } +short APS5_VABI _ZNSt8__sce_v25_DintEPds_nid_postfix(double* value, short scale) { return _Dint_nid_postfix(value, scale); } +short APS5_VABI _ZNSt8__sce_v26_FDintEPfs_nid_postfix(float* value, short scale) { return _FDint_nid_postfix(value, scale); } +short APS5_VABI _ZNSt8__sce_v26_LDintEPes_nid_postfix(double* value, short scale) { return _Dint_nid_postfix(value, scale); } +short APS5_VABI _ZNSt8__sce_v26_DnormEPNS_5_DvalE_nid_postfix(std::uint16_t* words) { return _Dnorm_nid_postfix(words); } +short APS5_VABI _ZNSt8__sce_v27_FDnormEPNS_5_FvalE_nid_postfix(std::uint16_t* words) { return _FDnorm_nid_postfix(words); } + +int APS5_VABI _Fpcomp_nid_postfix(double x, double y) { return std::isnan(x) || std::isnan(y) ? 0 : Compare(x, y); } +int APS5_VABI _FFpcomp_nid_postfix(float x, float y) { return std::isnan(x) || std::isnan(y) ? 0 : Compare(x, y); } +int APS5_VABI _LFpcomp_nid_postfix(long double x, long double y) { return ExtendedNan(Bits(x)) || ExtendedNan(Bits(y)) ? 0 : Compare(x, y); } + +double APS5_VABI _Poly_nid_postfix(double x, const double* table, int degree) { return FusedPolynomial(x, table, degree); } +float APS5_VABI _FPoly_nid_postfix(float x, const float* table, int degree) { return FusedPolynomial(x, table, degree); } + +long double APS5_VABI _LPoly_nid_postfix(long double x, const long double* table, int degree) { + long double result = table[0]; + for (int index = 1; index <= degree; ++index) { + result *= x; + result += table[index]; + } + return result; +} + } diff --git a/core/libs/prx/libc/src/Rename.cpp b/core/libs/prx/libc/src/Rename.cpp index f8e4941f4c..4d93860a6e 100644 --- a/core/libs/prx/libc/src/Rename.cpp +++ b/core/libs/prx/libc/src/Rename.cpp @@ -6,18 +6,34 @@ #include #include #ifdef _WIN32 +#include "prx/libc/include/WindowsVerbatimPath.hpp" #include #include #include #include #endif +#ifdef _WIN32 +static bool IsWindowsLink(const std::filesystem::path& path) { + WIN32_FIND_DATAW data{}; + const HANDLE search = FindFirstFileW(path.c_str(), &data); + if (search == INVALID_HANDLE_VALUE) return false; + FindClose(search); + return (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && + (data.dwReserved0 == IO_REPARSE_TAG_SYMLINK || data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT); +} +#else +static bool IsWindowsLink(const std::filesystem::path&) { + return false; +} +#endif + std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, const std::filesystem::path& destination, [[maybe_unused]] bool replacesExisting) { namespace fs = std::filesystem; std::error_code error; #ifdef _WIN32 if (replacesExisting) { - const auto target = fs::absolute(destination, error); + const auto target = NativeVerbatimPath_nid_no_patch(destination, error); if (error) return error; struct RenameInfo { DWORD flags; HANDLE root; DWORD nameBytes; WCHAR name[1]; }; static_assert(sizeof(RenameInfo) == sizeof(FILE_RENAME_INFO) && offsetof(RenameInfo, name) == offsetof(FILE_RENAME_INFO, FileName)); @@ -26,25 +42,37 @@ std::error_code RenameNative_nid_no_patch(const std::filesystem::path& source, c std::vector buffer(sizeof(RenameInfo) + nameBytes); auto* info = reinterpret_cast(buffer.data()); constexpr DWORD replaceExisting = 0x00000001, posixSemantics = 0x00000002, ignoreReadOnly = 0x00000040; - const auto attributes = GetFileAttributesW(destination.c_str()); + const auto attributes = GetFileAttributesW(target.c_str()); info->flags = replaceExisting | posixSemantics | (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_READONLY) ? ignoreReadOnly : 0); info->nameBytes = static_cast(nameBytes); std::memcpy(info->name, target.c_str(), nameBytes); - const auto handle = CreateFileW(source.c_str(), DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + const auto sourcePath = NativeVerbatimPath_nid_no_patch(source, error); + const auto& openSource = error ? source : sourcePath; + const auto handle = CreateFileW(openSource.c_str(), DELETE, 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 std::error_code(static_cast(GetLastError()), std::system_category()); constexpr auto renameInfoEx = static_cast(22); if (!SetFileInformationByHandle(handle, renameInfoEx, info, static_cast(buffer.size()))) error = std::error_code(static_cast(GetLastError()), std::system_category()); CloseHandle(handle); return error; } -#endif + const auto verbatimSource = NativeVerbatimPath_nid_no_patch(source, error); + if (error) return error; + const auto verbatimDest = NativeVerbatimPath_nid_no_patch(destination, error); + if (error) return error; + fs::rename(verbatimSource, verbatimDest, error); + return error; +#else fs::rename(source, destination, error); return error; +#endif } #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, + std::error_code error; + const auto verbatim = NativeVerbatimPath_nid_no_patch(directory, error); + const auto& checkDir = error ? directory : verbatim; + const auto handle = CreateFileW(checkDir.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); @@ -78,11 +106,21 @@ static int RenamePath_nid_no_patch(const std::filesystem::path& source, const st constexpr int GuestEnotempty = 66; namespace fs = std::filesystem; std::error_code error; - const auto sourceStatus = LinkStatus(source, error); +#ifdef _WIN32 + std::error_code verbatimError; + const auto verbatimSource = NativeVerbatimPath_nid_no_patch(source, verbatimError); + const auto& actualSource = verbatimError ? source : verbatimSource; + const auto verbatimDest = NativeVerbatimPath_nid_no_patch(destination, verbatimError); + const auto& actualDest = verbatimError ? destination : verbatimDest; +#else + const auto& actualSource = source; + const auto& actualDest = destination; +#endif + const auto sourceStatus = LinkStatus(actualSource, error); if (error && error != std::errc::no_such_file_or_directory) return FilesystemError(error); if (sourceStatus.type() == fs::file_type::not_found) return GuestEnoent; - const auto parent = destination.parent_path().empty() ? fs::path(".") : destination.parent_path(); + const auto parent = actualDest.parent_path().empty() ? fs::path(".") : actualDest.parent_path(); error.clear(); const auto parentStatus = fs::status(parent, error); if (error) return FilesystemError(error); @@ -90,15 +128,15 @@ static int RenamePath_nid_no_patch(const std::filesystem::path& source, const st if (!fs::is_directory(parentStatus)) return GuestEnotdir; error.clear(); - const auto destinationStatus = LinkStatus(destination, error); + const auto destinationStatus = LinkStatus(actualDest, error); if (error && error != std::errc::no_such_file_or_directory) return FilesystemError(error); const bool destinationExists = destinationStatus.type() != fs::file_type::not_found; - const bool sourceIsDirectory = fs::is_directory(sourceStatus); - const bool destinationIsDirectory = destinationExists && fs::is_directory(destinationStatus); + const bool sourceIsDirectory = fs::is_directory(sourceStatus) && !IsWindowsLink(source); + const bool destinationIsDirectory = destinationExists && fs::is_directory(destinationStatus) && !IsWindowsLink(destination); bool sameFile = false; if (destinationExists) { error.clear(); - sameFile = fs::equivalent(source, destination, error); + sameFile = fs::equivalent(actualSource, actualDest, error); if (error == std::errc::no_such_file_or_directory) error.clear(); #ifdef __APPLE__ if (error == std::errc::not_supported) error.clear(); @@ -109,26 +147,33 @@ static int RenamePath_nid_no_patch(const std::filesystem::path& source, const st } if (sourceIsDirectory && !sameFile) { error.clear(); - const auto canonicalSource = fs::weakly_canonical(source, error); + const auto canonicalSource = fs::weakly_canonical(actualSource, error); if (error) return FilesystemError(error); - const auto canonicalDestination = fs::weakly_canonical(destination, error); + const auto canonicalDestination = fs::weakly_canonical(actualDest, error); if (error) return FilesystemError(error); const auto relative = canonicalDestination.lexically_relative(canonicalSource); if (!relative.empty() && *relative.begin() != "..") return GuestEinval; } if (destinationExists && !sameFile && destinationIsDirectory) { error.clear(); - if (!fs::is_empty(destination, error)) { + if (!fs::is_empty(actualDest, error)) { return error ? FilesystemError(error) : GuestEnotempty; } } - error = RenameNative_nid_no_patch(source, destination, destinationExists && (!sourceIsDirectory || !sameFile)); + error = RenameNative_nid_no_patch(actualSource, actualDest, destinationExists && (!sourceIsDirectory || !sameFile)); return error ? FilesystemError(error) : 0; } static int SourceLookupError_nid_no_patch(const std::filesystem::path& source) { std::error_code status; - const auto sourceStatus = LinkStatus(source, status); +#ifdef _WIN32 + std::error_code verbatimError; + const auto verbatim = NativeVerbatimPath_nid_no_patch(source, verbatimError); + const auto& actualSource = verbatimError ? source : verbatim; +#else + const auto& actualSource = source; +#endif + const auto sourceStatus = LinkStatus(actualSource, status); if (HasNonDirectoryPrefix_nid_no_patch(source)) return 20; if (status && status != std::errc::no_such_file_or_directory) return FilesystemError(status); return sourceStatus.type() == std::filesystem::file_type::not_found ? 2 : 0; @@ -157,7 +202,14 @@ extern "C" int RenameGuestPath_nid_no_patch(const char* from, const char* to) { int error = RenamePath_nid_no_patch(source, destination); if (error == 2) { std::error_code status; - if (HasNonDirectoryPrefix_nid_no_patch(source) || (std::filesystem::exists(source, status) && HasNonDirectoryPrefix_nid_no_patch(destination))) error = 20; +#ifdef _WIN32 + std::error_code verbatimError; + const auto verbatim = NativeVerbatimPath_nid_no_patch(source, verbatimError); + const auto& actualSource = verbatimError ? source : verbatim; +#else + const auto& actualSource = source; +#endif + if (HasNonDirectoryPrefix_nid_no_patch(source) || (std::filesystem::exists(actualSource, status) && HasNonDirectoryPrefix_nid_no_patch(destination))) error = 20; } #ifdef _WIN32 constexpr int GuestEloop = 62; diff --git a/core/libs/prx/libc/src/ScanWideField.cpp b/core/libs/prx/libc/src/ScanWideField.cpp index 5d5a35559d..cdc07afa5c 100644 --- a/core/libs/prx/libc/src/ScanWideField.cpp +++ b/core/libs/prx/libc/src/ScanWideField.cpp @@ -3,66 +3,29 @@ #include -namespace { - -std::size_t DecodeUtf8(const char* text, char32_t* point) { - const auto lead = static_cast(text[0]); - if (lead < 0x80) { - *point = lead; - return 1; - } - std::size_t size = 0; - char32_t value = 0; - if ((lead & 0xE0) == 0xC0) { size = 2; value = lead & 0x1F; } - else if ((lead & 0xF0) == 0xE0) { size = 3; value = lead & 0x0F; } - else if ((lead & 0xF8) == 0xF0) { size = 4; value = lead & 0x07; } - else return 0; - for (std::size_t index = 1; index < size; ++index) { - const auto next = static_cast(text[index]); - if ((next & 0xC0) != 0x80) return 0; - value = (value << 6) | (next & 0x3F); - } - constexpr char32_t Smallest[] = {0, 0, 0x80, 0x800, 0x10000}; - if (value < Smallest[size] || value > 0x10FFFF || (value >= 0xD800 && value <= 0xDFFF)) return 0; - *point = value; - return size; -} - -} - namespace LibcDetail { -std::size_t ReadWideScanCharacter(const char* text, char32_t* point) { - const std::size_t size = DecodeUtf8(text, point); - if (size == 0) NotImplemented_nid_no_patch("scanf 16-bit wide conversion of input that is not UTF-8"); - if (*point > 0xFFFF) NotImplemented_nid_no_patch("scanf 16-bit wide conversion of a character outside the BMP"); - return size; -} - -bool InWideScanset(const std::string& specifier, char32_t point) { +bool InWideScanset(const std::string& specifier, unsigned char byte) { std::size_t index = 1; const bool negated = specifier[index] == '^'; if (negated) ++index; const std::size_t end = specifier.size() - 1; bool member = false; - for (std::size_t first = index; index < end; ++index) { - const auto value = static_cast(specifier[index]); - if (value >= 0x80) NotImplemented_nid_no_patch("scanf 16-bit wide scanset with a character outside ASCII"); - if (specifier[index] == '-' && index != first && index + 1 < end) { - const auto low = static_cast(specifier[index - 1]); - const auto high = static_cast(specifier[index + 1]); - if (high >= 0x80) NotImplemented_nid_no_patch("scanf 16-bit wide scanset with a character outside ASCII"); - if (point >= low && point <= high) member = true; + while (index < end && !member) { + const auto first = static_cast(specifier[index]); + if (end - index >= 3 && specifier[index + 1] == '-') { + member = byte >= first && byte <= static_cast(specifier[index + 2]); + index += 3; + } else { + member = byte == first; ++index; - continue; } - if (point == value) member = true; } return member != negated; } void RequireNoPositionalWideScan(const char* format) { - if (std::strchr(format, '$') != nullptr) NotImplemented_nid_no_patch("sscanf positional arguments with a 16-bit wide conversion on Linux"); + if (std::strchr(format, '$') != nullptr) NotImplemented_nid_no_patch("sscanf positional arguments with a 16-bit wide conversion"); } } diff --git a/core/libs/prx/libc/src/ScanningWide.cpp b/core/libs/prx/libc/src/ScanningWide.cpp index d44728f094..865a4248e3 100644 --- a/core/libs/prx/libc/src/ScanningWide.cpp +++ b/core/libs/prx/libc/src/ScanningWide.cpp @@ -521,12 +521,14 @@ extern FileStream* __stdinp_nid_postfix; int APS5_VABI vfwscanf_nid_postfix(FileStream* stream, const char16_t* format, VaList* args) { if (stream == nullptr || format == nullptr) return EOF; + stream->Orientation(1); const LockedStream lock(stream); return WideScanner("vfwscanf", false, WideInput(stream), format, args).Run(); } int APS5_VABI vfwscanf_s_nid_postfix(FileStream* stream, const char16_t* format, VaList* args) { if (stream == nullptr || format == nullptr) return EOF; + stream->Orientation(1); const LockedStream lock(stream); return WideScanner("vfwscanf_s", true, WideInput(stream), format, args).Run(); } diff --git a/core/libs/prx/libc/src/SigvecExport.cpp b/core/libs/prx/libc/src/SigvecExport.cpp new file mode 100644 index 0000000000..fcfeed0d19 --- /dev/null +++ b/core/libs/prx/libc/src/SigvecExport.cpp @@ -0,0 +1,5 @@ +#include "prx/libc/include/LegacySignals.hpp" + +extern "C" int APS5_VABI sigvec_nid_disambig1(int signal, const GuestSignalVector* replacement, GuestSignalVector* previous) { + return LegacySigvec_nid_no_patch(signal, replacement, previous); +} diff --git a/core/libs/prx/libc/src/StandardStreams.cpp b/core/libs/prx/libc/src/StandardStreams.cpp index 8819a6175d..6dae398fb2 100644 --- a/core/libs/prx/libc/src/StandardStreams.cpp +++ b/core/libs/prx/libc/src/StandardStreams.cpp @@ -87,9 +87,14 @@ FileStream* __stdoutp_nid_postfix = &_Stdout_nid_postfix; FileStream* __stderrp_nid_postfix = &_Stderr_nid_postfix; int __isthreaded_nid_postfix = 1; +int APS5_VABI fwide_nid_postfix(FileStream* stream, int mode) { + return stream->Orientation(mode); +} + int APS5_VABI fgetc_nid_postfix(FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); + stream->Orientation(-1); if (!stream->SwitchToReading()) return -1; const int result = std::fgetc(native); stream->SyncStatus(); @@ -101,6 +106,7 @@ std::int32_t APS5_VABI fgetwc_nid_postfix(FileStream* stream) { errno = 22; return -1; } + stream->Orientation(1); if (const char16_t low = stream->TakeLowSurrogate(); low != 0) return low; stream->NoteWideRead(); const auto invalid = [stream] { @@ -130,6 +136,7 @@ std::int32_t APS5_VABI fgetwc_nid_postfix(FileStream* stream) { std::int32_t APS5_VABI ungetwc_nid_postfix(std::int32_t value, FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } + if (stream) stream->Orientation(1); if (value == -1) return -1; if (value < 0 || value > 255) { errno = 86; @@ -137,6 +144,7 @@ std::int32_t APS5_VABI ungetwc_nid_postfix(std::int32_t value, FileStream* strea return -1; } auto* native = GetNativeStream(stream); + if (!stream->OpenForReading()) return -1; if (!stream->SwitchToReading()) return -1; const int result = std::ungetc(value, native); stream->SyncStatus(); @@ -147,6 +155,7 @@ int APS5_VABI getc_nid_postfix(FileStream* stream) { return fgetc_nid_postfix(st int APS5_VABI getc_unlocked_nid_postfix(FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); + stream->Orientation(-1); if (!stream->SwitchToReadingUnlocked()) return -1; #ifdef _WIN32 const int result = _fgetc_nolock(native); @@ -190,6 +199,7 @@ int APS5_VABI getchar_unlocked_nid_postfix() { return getc_unlocked_nid_postfix( int APS5_VABI fputc_nid_postfix(int value, FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } auto* native = GetNativeStream(stream); + stream->Orientation(-1); if (!stream->SwitchToWriting()) return -1; const int result = std::fputc(value, native); stream->SyncStatus(); @@ -206,6 +216,7 @@ int APS5_VABI putc_unlocked_nid_postfix(int value, FileStream* stream) { const int result = putc_unlocked(value, native); #endif stream->SyncUnlockedStatus(); + stream->Orientation(-1); return result; } int APS5_VABI __swbuf_nid_postfix(int value, FileStream* stream) { return fputc_nid_postfix(value, stream); } @@ -213,7 +224,9 @@ int APS5_VABI putchar_nid_postfix(int value) { return fputc_nid_postfix(value, _ int APS5_VABI putchar_unlocked_nid_postfix(int value) { return putc_unlocked_nid_postfix(value, __stdoutp_nid_postfix); } int APS5_VABI ungetc_nid_postfix(int value, FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return -1; } + if (value != EOF && stream && !stream->OpenForReading()) return -1; auto* native = GetNativeStream(stream); + stream->Orientation(-1); if (value != EOF && !stream->SwitchToReading()) return -1; const int result = std::ungetc(value, native); stream->SyncStatus(); @@ -221,6 +234,7 @@ int APS5_VABI ungetc_nid_postfix(int value, FileStream* stream) { } char* APS5_VABI fgets_nid_postfix(char* buffer, int size, FileStream* stream) { if (stream && !stream->IsOpen()) { errno = 9; return nullptr; } + if (stream) stream->Orientation(-1); if (size <= 0) { stream->SetError(); errno = 22; diff --git a/core/libs/prx/libc/src/StringExtensions.cpp b/core/libs/prx/libc/src/StringExtensions.cpp index a1b2c2af2b..44aa0b08ed 100644 --- a/core/libs/prx/libc/src/StringExtensions.cpp +++ b/core/libs/prx/libc/src/StringExtensions.cpp @@ -150,6 +150,17 @@ char* APS5_VABI stpcpy_nid_postfix(char* destination, const char* source) { return destination + length; } +char* APS5_VABI stpncpy_nid_postfix(char* destination, const char* source, std::size_t count) { + for (; count != 0; --count, ++destination, ++source) { + if ((*destination = *source) == '\0') { + char* result = destination; + while (--count != 0) *++destination = '\0'; + return result; + } + } + return destination; +} + char* APS5_VABI strtok_r_nid_postfix(char* text, const char* delimiters, char** state) { static const char exhausted[] = ""; if (text == nullptr) text = *state; @@ -171,6 +182,7 @@ char* APS5_VABI strtok_nid_postfix(char* text, const char* delimiters) { } int APS5_VABI __inet_aton_nid_postfix(const char* text, void* address) { + if (text == nullptr) throw std::invalid_argument("__inet_aton: null text"); std::uint8_t parts[3]; std::size_t count = 0; std::uint64_t value = 0; diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index a4123cc8e8..b744273d3a 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -307,6 +307,12 @@ void* APS5_VABI memset_nid_postfix(void* s, int c, size_t n) { return std::memset(s, c, n); } +void* APS5_VABI memset_explicit_nid_postfix(void* destination, int value, size_t count) { + auto* bytes = static_cast(destination); + for (size_t index = 0; index < count; ++index) bytes[index] = static_cast(value); + return destination; +} + void APS5_VABI bzero_nid_postfix(void* destination, size_t count) { std::memset(destination, 0, count); } @@ -576,7 +582,7 @@ 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) { - return std::memcmp(s1, s2, n); + return n != 0 && std::memcmp(s1, s2, n) != 0 ? 1 : 0; } size_t APS5_VABI strspn_nid_postfix(const char* s, const char* accept) { @@ -829,7 +835,7 @@ 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) { - return ParseAsciiPrefix(str, endptr, [](const char* text, char** end) { return ParseFloat(text, end, std::strtod); }); + return ParseAsciiPrefix(str, endptr, [](const char* text, char** end) { return strtod_nid_postfix(text, end); }); } float APS5_VABI wcstof_nid_postfix(const char16_t* str, char16_t** endptr) { @@ -889,10 +895,81 @@ unsigned long long APS5_VABI wcstoumax_nid_postfix(const char16_t* str, char16_t } std::uint64_t APS5_VABI _WStoul_nid_postfix(const char16_t* str, char16_t** endptr, int base) { - if (base < 0 || base == 1 || base > 36) throw std::invalid_argument("_WStoul: unsupported base " + std::to_string(base)); return wcstoul_nid_postfix(str, endptr, base); } +unsigned long long APS5_VABI _WStoull_nid_postfix(const char16_t* str, char16_t** endptr, int base) { + static constexpr char16_t digits[] = u"0123456789abcdefghijklmnopqrstuvwxyz"; + static constexpr signed char maxDigits[] = {0, 0, 65, 41, 33, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 17, 16, 16, + 16, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13}; + const char16_t* current = str; + while (_Iswctype_nid_postfix(*current, 9)) ++current; + const char16_t sign = *current == u'-' || *current == u'+' ? *current++ : u'+'; + if (base < 0 || base == 1 || base > 36) { + if (endptr) *endptr = const_cast(str); + return 0; + } + const bool hexPrefix = current[0] == u'0' && (current[1] | 0x20) == u'x'; + if (base == 0) base = current[0] != u'0' ? 10 : hexPrefix ? 16 : 8; + if (base == 16 && hexPrefix) current += 2; + const char16_t* const start = current; + while (*current == u'0') ++current; + const char16_t* const significant = current; + unsigned long long value = 0; + unsigned long long previous = 0; + unsigned long long digit = 0; + for (const char16_t* found; (found = wmemchr_nid_postfix(digits, static_cast(_Towctrans_nid_postfix(*current, 1)), + static_cast(base))) != nullptr; ++current) { + previous = value; + digit = static_cast(found - digits); + value = value * static_cast(base) + digit; + } + if (current == start) { + if (endptr) *endptr = const_cast(str); + return 0; + } + const std::ptrdiff_t excess = (current - significant) - maxDigits[base]; + if (excess > 0 || (excess == 0 && (value < digit || (value - digit) / static_cast(base) != previous))) { + errno = ERANGE; + value = std::numeric_limits::max(); + } else if (sign == u'-') { + value = 0 - value; + } + if (endptr) *endptr = const_cast(current); + return value; +} + +long long APS5_VABI _WStoll_nid_postfix(const char16_t* str, char16_t** endptr, int base) { + const char16_t* current = str; + while (_Iswctype_nid_postfix(*current, 9)) ++current; + const char16_t sign = *current == u'-' || *current == u'+' ? *current++ : u'+'; + char16_t* stop = nullptr; + const unsigned long long magnitude = _WStoull_nid_postfix(current, &stop, base); + if (stop == current) { + if (endptr) *endptr = const_cast(str); + return 0; + } + if (endptr) *endptr = stop; + constexpr unsigned long long limit = 0x8000000000000000ULL; + if (sign == u'-' ? magnitude > limit : magnitude >= limit) { + errno = ERANGE; + return sign == u'-' ? std::numeric_limits::min() : std::numeric_limits::max(); + } + return static_cast(sign == u'-' ? 0 - magnitude : magnitude); +} + +double APS5_VABI _WStod_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten) { + return ParseAsciiPrefix(str, endptr, [pten](const char* text, char** end) { return _Stodx_nid_postfix(text, end, pten, nullptr); }); +} + +float APS5_VABI _WStof_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten) { + return ParseAsciiPrefix(str, endptr, [pten](const char* text, char** end) { return _Stofx_nid_postfix(text, end, pten, nullptr); }); +} + +long double APS5_VABI _WStold_nid_postfix(const char16_t* str, char16_t** endptr, std::int64_t pten) { + return ParseAsciiPrefix(str, endptr, [pten](const char* text, char** end) { return _Stoldx_nid_postfix(text, end, pten, nullptr); }); +} + int APS5_VABI wcscoll_nid_postfix(const char16_t* first, const char16_t* second) { return wcscmp_nid_postfix(first, second); } @@ -1021,6 +1098,32 @@ int APS5_VABI wcscat_s_nid_postfix(char16_t* dest, size_t destsz, const char16_t return wcsncat_s_nid_postfix(dest, destsz, src, static_cast(-1)); } +/* + * Copyright (c) 2010 Damien Miller. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +/* Adapted from FreeBSD release/11.4.0 lib/libc/string/timingsafe_bcmp.c: https://github.com/freebsd/freebsd-src/blob/release/11.4.0/lib/libc/string/timingsafe_bcmp.c */ +int APS5_VABI timingsafe_bcmp_nid_postfix(const void* b1, const void* b2, size_t n) { + const auto* p1 = static_cast(b1); + const auto* p2 = static_cast(b2); + int ret = 0; + for (size_t i = 0; i < n; ++i) { + ret |= p1[i] ^ p2[i]; + } + return ret != 0; +} + int APS5_VABI timingsafe_memcmp_nid_postfix(const void* b1, const void* b2, size_t len) { const auto* p1 = static_cast(b1); const auto* p2 = static_cast(b2); diff --git a/core/libs/prx/libc/src/Threads.cpp b/core/libs/prx/libc/src/Threads.cpp index 2f5b773c4a..86ef3cd1e0 100644 --- a/core/libs/prx/libc/src/Threads.cpp +++ b/core/libs/prx/libc/src/Threads.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include #include #include +#include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX @@ -434,6 +436,53 @@ int APS5_VABI _Cnd_signal_nid_postfix(void** handle) { return SignalCondition_nid_no_patch(handle); } +struct AtThreadExitEntry { + std::thread::id thread; + DinkumwareCondition* condition; + DinkumwareMutex* mutex; + int* ready; +}; + +static std::mutex atThreadExitLock; +static std::list atThreadExitEntries; + +void APS5_VABI _Cnd_do_broadcast_at_thread_exit_nid_postfix() noexcept { + const auto thread = std::this_thread::get_id(); + std::list pending; + { + const std::lock_guard lock(atThreadExitLock); + for (auto entry = atThreadExitEntries.begin(); entry != atThreadExitEntries.end();) { + if (entry->thread == thread) { + auto moved = entry++; + pending.splice(pending.end(), atThreadExitEntries, moved); + } else { + ++entry; + } + } + } + for (const auto& entry : pending) { + if (entry.ready) *entry.ready = 1; + void* condition = entry.condition; + _Cnd_broadcast_nid_postfix(&condition); + void* mutex = entry.mutex; + _Mtx_unlock_nid_postfix(&mutex); + } +} + +void APS5_VABI _Cnd_register_at_thread_exit_nid_postfix(void* conditionHandle, void* mutexHandle, int* ready) { + auto* condition = ResolveCondition(&conditionHandle, "_Cnd_register_at_thread_exit"); + auto* mutex = ResolveMutex(&mutexHandle, "_Cnd_register_at_thread_exit"); + const std::lock_guard lock(atThreadExitLock); + atThreadExitEntries.push_back({std::this_thread::get_id(), condition, mutex, ready}); +} + +void APS5_VABI _Cnd_unregister_at_thread_exit_nid_postfix(void* mutexHandle) { + auto* mutex = ResolveMutex(&mutexHandle, "_Cnd_unregister_at_thread_exit"); + const std::lock_guard lock(atThreadExitLock); + atThreadExitEntries.erase(std::remove_if(atThreadExitEntries.begin(), atThreadExitEntries.end(), + [mutex](const AtThreadExitEntry& entry) { return entry.mutex == mutex; }), atThreadExitEntries.end()); +} + 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); @@ -472,7 +521,9 @@ void RegisterPthreadDetach_nid_no_patch(PthreadDetachFn_nid_no_patch fn) { static void* APS5_VABI PadEntry_nid_no_patch(void* argument) { auto* pad = static_cast(argument); const auto go = pad->vtable[0]; - return reinterpret_cast(static_cast(go(pad))); + const auto result = go(pad); + _Cnd_do_broadcast_at_thread_exit_nid_postfix(); + return reinterpret_cast(static_cast(result)); } void APS5_VABI _ZNSt4_PadC2Ev_nid_postfix(GuestPad* pad) { @@ -595,6 +646,7 @@ void* APS5_VABI _Thrd_id_nid_postfix(void) { } [[noreturn]] void APS5_VABI _Thrd_exit_nid_postfix(int code) { + _Cnd_do_broadcast_at_thread_exit_nid_postfix(); ThreadForwards(__func__).exit(reinterpret_cast(static_cast(static_cast(code)))); std::abort(); } diff --git a/core/libs/prx/libc/src/Time.cpp b/core/libs/prx/libc/src/Time.cpp index 66f46dcc80..c8bebdd422 100644 --- a/core/libs/prx/libc/src/Time.cpp +++ b/core/libs/prx/libc/src/Time.cpp @@ -6,9 +6,8 @@ #include #include #include -#include #include -#include +#include #include #include @@ -244,31 +243,57 @@ constexpr const char* MONTH_FULL_NAMES[] = {"January", "February", "March", "Apr "October", "November", "December"}; constexpr int YEAR_BASE = 1900; +template struct FormatBuffer { - char* cursor; - char* limit; - bool full; + TChar* cursor; + std::size_t remaining; + bool full = false; + bool failed = false; }; -void addPiece(FormatBuffer& buffer, const char* text, std::size_t length) { - if (buffer.full) return; - if (length > static_cast(buffer.limit - buffer.cursor)) { +template +void addPiece(FormatBuffer& buffer, const TChar* text, std::size_t length) { + if (buffer.full || buffer.failed) return; + if (length > buffer.remaining) { buffer.full = true; return; } - std::memcpy(buffer.cursor, text, length); + std::memcpy(buffer.cursor, text, length * sizeof(TChar)); buffer.cursor += length; + buffer.remaining -= length; } -void addText(FormatBuffer& buffer, const char* text) { - addPiece(buffer, text, std::strlen(text)); +template +void addText(FormatBuffer& buffer, const char* text) { + if (buffer.full || buffer.failed) return; + if constexpr (std::is_same_v) { + addPiece(buffer, text, std::strlen(text)); + } else { + if (buffer.remaining == 0) return; + unsigned char state[16]{}; + const char* source = text; + const std::size_t required = mbsrtowcs_nid_postfix(nullptr, &source, 0, state); + if (required == static_cast(-1)) { + buffer.failed = true; + return; + } + std::memset(state, 0, sizeof(state)); + source = text; + const std::size_t length = mbsrtowcs_nid_postfix(buffer.cursor, &source, buffer.remaining, state); + if (length == static_cast(-1)) buffer.failed = true; + else if (required <= buffer.remaining) { + buffer.cursor += length; + buffer.remaining -= length; + } + } } 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) { +template +void addNumber(FormatBuffer& buffer, std::int64_t value, int modulus, const char* format) { char text[16]; std::snprintf(text, sizeof(text), format, clampedField(value, modulus)); addText(buffer, text); @@ -325,9 +350,11 @@ IsoWeek isoWeek(const GuestTm& time) { return {previousYear, (372 + previousLeap - lastWeekday) / 7 + (midweek(previousFirstWeekday) ? 1 : 0)}; } -void formatTime(FormatBuffer& buffer, const char* format, const GuestTm& time); +template +void formatTime(FormatBuffer& buffer, const TFormatChar* format, const GuestTm& time); -void addZoneOffset(FormatBuffer& buffer, const GuestTm& time) { +template +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; @@ -336,7 +363,8 @@ void addZoneOffset(FormatBuffer& buffer, const GuestTm& time) { addText(buffer, text); } -bool addConversion(FormatBuffer& buffer, char conversion, const GuestTm& time) { +template +bool addConversion(FormatBuffer& buffer, TFormatChar 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; @@ -384,10 +412,28 @@ bool addConversion(FormatBuffer& buffer, char conversion, const GuestTm& time) { } } -void formatTime(FormatBuffer& buffer, const char* format, const GuestTm& time) { - for (; *format != '\0' && !buffer.full; ++format) { +template +bool addWideConversion(FormatBuffer& buffer, TFormatChar conversion, const GuestTm& time) { + std::string text(64, '\0'); + for (;;) { + FormatBuffer expanded{text.data(), text.size()}; + if (!addConversion(expanded, conversion, time)) return false; + if (!expanded.full && expanded.remaining != 0) { + *expanded.cursor = '\0'; + addText(buffer, text.c_str()); + return true; + } + if (text.size() > text.max_size() / 2) throw std::length_error("wcsftime conversion exceeds string capacity"); + text.resize(text.size() * 2); + } +} + +template +void formatTime(FormatBuffer& buffer, const TFormatChar* format, const GuestTm& time) { + for (; *format != '\0' && !buffer.full && !buffer.failed; ++format) { if (*format != '%' || format[1] == '\0') { - addPiece(buffer, format, 1); + const TChar literal = *format; + addPiece(buffer, &literal, 1); continue; } ++format; @@ -395,17 +441,27 @@ void formatTime(FormatBuffer& buffer, const char* format, const GuestTm& time) { 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 constexpr (std::is_same_v) { + if (!addConversion(buffer, *format, time)) { + const char unknown[] = {'%', *format}; + addPiece(buffer, unknown, sizeof(unknown)); + } + } else if (!addWideConversion(buffer, *format, time)) { + const TChar unknown[] = {'%', *format}; + const std::size_t length = buffer.remaining < 2 ? buffer.remaining : 2; + std::memcpy(buffer.cursor, unknown, length * sizeof(TChar)); + if (buffer.remaining >= 2) { + buffer.cursor += 2; + buffer.remaining -= 2; + } } } } std::size_t formatGuestTime(char* destination, std::size_t size, const char* format, const GuestTm& time) { - FormatBuffer buffer{destination, destination + size, false}; + FormatBuffer buffer{destination, size}; formatTime(buffer, format == nullptr ? "%c" : format, time); - if (buffer.full || buffer.cursor == buffer.limit) return 0; + if (buffer.full || buffer.remaining == 0) return 0; *buffer.cursor = '\0'; return static_cast(buffer.cursor - destination); } @@ -593,6 +649,11 @@ int64_t APS5_VABI mktime_nid_postfix(GuestTm* timeptr) { return libc_mktime_nid_postfix(timeptr); } +int64_t APS5_VABI timelocal_nid_postfix(GuestTm* timeptr) { + if (timeptr != nullptr) timeptr->tm_isdst = -1; + return libc_mktime_nid_postfix(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(); @@ -626,43 +687,25 @@ size_t APS5_VABI strftime_nid_postfix(char* str, size_t count, const char* forma } size_t APS5_VABI wcsftime_nid_postfix(std::uint16_t* str, size_t count, const std::uint16_t* format, const GuestTm* timeptr) { - constexpr int GuestEnomem = 12; constexpr int GuestEinval = 22; - 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; - const std::unique_ptr narrowFormat(new (std::nothrow) char[formatLength + 1]); - if (!narrowFormat) { - errno = GuestEnomem; - return 0; - } - wcsrtombs_nid_postfix(narrowFormat.get(), &formatCursor, formatLength + 1, nullptr); - const auto maxCharacterBytes = static_cast(_Getmbcurmax_nid_postfix()); if (std::numeric_limits::max() / maxCharacterBytes <= count) { errno = GuestEinval; return 0; } - const std::unique_ptr narrow(new (std::nothrow) char[count * maxCharacterBytes]); - if (!narrow) { - errno = GuestEnomem; - return 0; - } const int callerError = errno; - 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(nullptr, &narrowCursor, 0, nullptr); - if (length == Failed || length >= count) return 0; - mbsrtowcs_nid_postfix(str, &narrowCursor, count, nullptr); - - return length; + const ZoneScope zone; + tzset_nid_postfix(); + FormatBuffer buffer{str, count}; + formatTime(buffer, format, *timeptr); + if (buffer.failed) return 0; + errno = callerError; + if (buffer.full || buffer.remaining == 0) return 0; + *buffer.cursor = 0; + return count - buffer.remaining; } // The guest's CLOCKS_PER_SEC is 1000000: clock() reports process CPU time in microseconds. diff --git a/core/libs/prx/libc/src/VectorAlgorithms.cpp b/core/libs/prx/libc/src/VectorAlgorithms.cpp index e56da3b63b..a536953460 100644 --- a/core/libs/prx/libc/src/VectorAlgorithms.cpp +++ b/core/libs/prx/libc/src/VectorAlgorithms.cpp @@ -27,6 +27,21 @@ const void* Find(const void* first, const void* last, TValue value) { constexpr std::uintptr_t UnsizedBlock = 32; +template +const void* FindLast(const void* first, const void* last, TValue value) { + if (first == last) return last; + const auto beginAddress = reinterpret_cast(first); + const auto endAddress = reinterpret_cast(last); + if (!first || !last || endAddress < beginAddress || (endAddress - beginAddress) % sizeof(TValue) != 0) NotImplemented_nid_no_patch(__func__); + const auto* begin = static_cast(first); + auto* current = static_cast(last); + while (current != begin) { + current -= sizeof(TValue); + if (Read(current) == value) return current; + } + return last; +} + template const void* FindUnsized(const void* first, TValue value) { auto* start = static_cast(first); @@ -112,6 +127,10 @@ const void* APS5_VABI __std_find_trivial_1_nid_postfix(const void* first, const const void* APS5_VABI __std_find_trivial_2_nid_postfix(const void* first, const void* last, std::uint16_t value) { return Find(first, last, value); } const void* APS5_VABI __std_find_trivial_4_nid_postfix(const void* first, const void* last, std::uint32_t value) { return Find(first, last, value); } const void* APS5_VABI __std_find_trivial_8_nid_postfix(const void* first, const void* last, std::uint64_t value) { return Find(first, last, value); } +const void* APS5_VABI __std_find_last_trivial_1_nid_postfix(const void* first, const void* last, std::uint8_t value) { return FindLast(first, last, value); } +const void* APS5_VABI __std_find_last_trivial_2_nid_postfix(const void* first, const void* last, std::uint16_t value) { return FindLast(first, last, value); } +const void* APS5_VABI __std_find_last_trivial_4_nid_postfix(const void* first, const void* last, std::uint32_t value) { return FindLast(first, last, value); } +const void* APS5_VABI __std_find_last_trivial_8_nid_postfix(const void* first, const void* last, std::uint64_t value) { return FindLast(first, last, value); } const void* APS5_VABI __std_find_trivial_unsized_1_nid_postfix(const void* first, std::uint8_t value) { return FindUnsized(first, value); } const void* APS5_VABI __std_find_trivial_unsized_2_nid_postfix(const void* first, std::uint16_t value) { return FindUnsized(first, value); } const void* APS5_VABI __std_find_trivial_unsized_4_nid_postfix(const void* first, std::uint32_t value) { return FindUnsized(first, value); } diff --git a/core/libs/prx/libc/src/VersionedFilesystem.cpp b/core/libs/prx/libc/src/VersionedFilesystem.cpp new file mode 100644 index 0000000000..ed22464987 --- /dev/null +++ b/core/libs/prx/libc/src/VersionedFilesystem.cpp @@ -0,0 +1,130 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/AppArchive.hpp" +#include "prx/libc/include/FilesystemError.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestFilesystemPrimitives.hpp" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif + +namespace { + +GuestFilesystem::FileType FromNativeType(std::filesystem::file_type type) { + switch (type) { + case std::filesystem::file_type::none: return GuestFilesystem::TypeNone; + case std::filesystem::file_type::not_found: return GuestFilesystem::TypeNotFound; + case std::filesystem::file_type::regular: return GuestFilesystem::TypeRegular; + case std::filesystem::file_type::directory: return GuestFilesystem::TypeDirectory; + case std::filesystem::file_type::symlink: return GuestFilesystem::TypeSymlink; + case std::filesystem::file_type::block: return GuestFilesystem::TypeBlock; + case std::filesystem::file_type::character: return GuestFilesystem::TypeCharacter; + case std::filesystem::file_type::fifo: return GuestFilesystem::TypeFifo; + case std::filesystem::file_type::socket: return GuestFilesystem::TypeSocket; + default: return GuestFilesystem::TypeUnknown; + } +} + +bool IsNotFound(int code) { + return code == GuestFilesystem::NoSuchFile || code == GuestFilesystem::NotDirectory || + code == GuestFilesystem::Invalid || code == GuestFilesystem::NameTooLong; +} + +#ifdef _WIN32 +std::filesystem::path StatusPath(const std::filesystem::path& path) { + const auto native = path.native(); + if (native.starts_with(L"\\\\?\\") || native.starts_with(L"\\\\.\\")) return path; + std::error_code error; + const auto absolute = std::filesystem::absolute(path, error); + if (error) return path; + const auto normalized = absolute.lexically_normal().make_preferred().native(); + if (normalized.size() < MAX_PATH) return absolute; + return normalized.starts_with(L"\\\\") ? std::filesystem::path(L"\\\\?\\UNC\\" + normalized.substr(2)) + : std::filesystem::path(L"\\\\?\\" + normalized); +} +#endif + +int StatType(const char* path, int* perms, int* error) { + errno = 0; + std::string copy; + int code = CopyGuestPath_nid_no_patch(path, copy); + if (code == 0 && copy.empty()) code = GuestFilesystem::NoSuchFile; + std::filesystem::path native; + if (code == 0) code = ResolvePathError_nid_no_patch(copy.c_str(), native, false, true); + int type; + if (code != 0) { + errno = code; + type = IsNotFound(code) ? GuestFilesystem::TypeNotFound : GuestFilesystem::TypeNone; + } else if (AppArchiveOnly_nid_no_patch(native)) { + AppArchiveNode node{}; + if (!AppArchiveLookup_nid_no_patch(native, &node)) + throw std::logic_error("sce v2 filesystem: " + native.string() + " left app0.zar while reading its status"); + errno = 0; + if (perms) *perms = node.Directory ? 0555 : 0444; + type = node.Directory ? GuestFilesystem::TypeDirectory : GuestFilesystem::TypeRegular; + } else { + std::error_code failure; +#ifdef _WIN32 + const auto status = std::filesystem::status(StatusPath(native), failure); +#else + const auto status = std::filesystem::status(native, failure); +#endif + if (!failure && status.type() != std::filesystem::file_type::not_found) { + errno = 0; + if (perms) *perms = static_cast(status.permissions()) & GuestFilesystem::PermissionBits; + type = FromNativeType(status.type()); + } else { + code = failure ? FilesystemError(failure) : GuestFilesystem::NoSuchFile; + if (code == GuestFilesystem::NoSuchFile) code = NotFoundError_nid_no_patch(native); + errno = code; + type = IsNotFound(code) ? GuestFilesystem::TypeNotFound : GuestFilesystem::TypeNone; + } + } + if (error) *error = errno; + return type; +} + +} + +extern "C" { + +int APS5_VABI _ZNSt8__sce_v210filesystem5_StatEPKcPNS0_5permsEPi_nid_postfix(const char* path, int* perms, int* error) { + return StatType(path, perms, error); +} + +void* APS5_VABI _ZNSt8__sce_v210filesystem9_Open_dirERA260_cPKcRiRNS0_9file_typeE_nid_postfix(char* target, const char* path, int* error, int* type) { + return GuestFilesystem::OpenDir(target, path, error, type); +} + +char* APS5_VABI _ZNSt8__sce_v210filesystem9_Read_dirERA260_cPvRNS0_9file_typeE_nid_postfix(char* target, void* handle, int* type) { + return GuestFilesystem::ReadDir(target, handle, type); +} + +int APS5_VABI _ZNSt8__sce_v210filesystem10_Close_dirEPv_nid_postfix(void* handle) { + return GuestFilesystem::CloseDir(handle); +} + +bool APS5_VABI _ZNSt8__sce_v210filesystem12_Current_getERA260_c_nid_postfix(char* target) { + target[0] = '/'; + target[1] = '\0'; + return true; +} + +void APS5_VABI _ZNSt8__sce_v210filesystem18_Xfilesystem_errorEPKcNS_10error_codeE_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +void APS5_VABI _ZNSt8__sce_v210filesystem18_Xfilesystem_errorEPKcRKNS0_4pathENS_10error_codeE_nid_postfix() { + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libc/src/VersionedFilesystemCalls.cpp b/core/libs/prx/libc/src/VersionedFilesystemCalls.cpp new file mode 100644 index 0000000000..ea3eb73e13 --- /dev/null +++ b/core/libs/prx/libc/src/VersionedFilesystemCalls.cpp @@ -0,0 +1,212 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/FilesystemForwards.hpp" +#include "prx/libc/include/General.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint32_t KernelErrnoBase = 0x80020000u; +constexpr std::uint32_t KernelExists = 0x80020011u; +constexpr std::uint32_t KernelNoSuchFile = 0x80020002u; +constexpr std::uint64_t NotFoundCodes = 0x2000000000140001ull; +constexpr int OutOfMemory = 12; +constexpr int NotSupported = 45; +constexpr int NoSystemCall = 78; +constexpr int TypeNotFound = -1; +constexpr int TypeNone = 0; +constexpr int TypeUnknown = 8; +constexpr int TypesByMode[] = {6, 5, 8, 2, 8, 4, 8, 1, 8, 3, 8, 7}; +constexpr std::uint16_t DefaultDirectoryMode = 0777; +constexpr std::uint16_t CopiedPermissions = 06777; +constexpr int CopyTargetFlags = 0x601; +constexpr std::uint16_t CopyTargetMode = 0777; +constexpr std::size_t CopyBlock = 0x10000; +constexpr std::int64_t MicrosecondsPerSecond = 1000000; +constexpr std::int64_t NanosecondsPerMicrosecond = 1000; + +std::atomic registeredForwards{nullptr}; + +const FilesystemForwards_nid_no_patch& Kernel(const char* function) { + const auto* forwards = registeredForwards.load(std::memory_order_acquire); + if (!forwards) throw std::runtime_error(std::string(function) + ": libkernel filesystem calls are not registered"); + return *forwards; +} + +int ErrorOf(int result) { + const auto code = static_cast(result); + return (code & 0xffff0000u) == KernelErrnoBase ? static_cast(code & 0xffffu) : 1; +} + +int StatusOf(int result) { + return result == 0 ? 0 : ErrorOf(result); +} + +bool NotFound(int result) { + const auto index = static_cast(result) - KernelNoSuchFile; + return index <= 0x3du && ((NotFoundCodes >> index) & 1u) != 0; +} + +int TypeOf(std::uint16_t mode) { + const auto index = static_cast(mode) - 0x1000u; + return index >= 0xc000u ? TypeUnknown : TypesByMode[index >> 12]; +} + +int StatusType(const char* function, const char* path, int* perms, int* error) { + FileStat status{}; + const int result = Kernel(function).stat(path, &status); + int type = TypeNone; + if (NotFound(result)) { + type = TypeNotFound; + } else if (result == 0) { + if (perms) *perms = status.st_mode & 0xfff; + type = TypeOf(status.st_mode); + } + if (error) *error = StatusOf(result); + return type; +} + +int CopyContents(const FilesystemForwards_nid_no_patch& kernel, int source, int target, std::byte* buffer) { + for (;;) { + const auto count = kernel.read(source, buffer, CopyBlock); + if (count == 0) { + FileStat status{}; + int result = kernel.fstat(source, &status); + if (result == 0) result = kernel.fchmod(target, static_cast(status.st_mode & CopiedPermissions)); + return StatusOf(result); + } + if (count < 0) return StatusOf(static_cast(count)); + for (std::int64_t done = 0; done < count;) { + const auto written = kernel.write(target, buffer + done, static_cast(count - done)); + if (written < 0) return StatusOf(static_cast(written)); + done += written; + } + } +} + +} + +extern "C" { + +void RegisterFilesystemForwards_nid_no_patch(const FilesystemForwards_nid_no_patch* forwards) { + registeredForwards.store(forwards, std::memory_order_release); +} + +int APS5_VABI _ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix(const char* path, int* perms, int* error) { + return StatusType(__func__, path, perms, error); +} + +int APS5_VABI _ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsE_nid_postfix(const char* path, int* perms) { + return StatusType(__func__, path, perms, nullptr); +} + +std::uint64_t APS5_VABI _ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix(const char* path, int* error) { + FileStat status{}; + const int result = Kernel(__func__).stat(path, &status); + if (result != 0 && error) *error = result; + return result != 0 ? UINT64_MAX : static_cast(status.st_size); +} + +std::uint64_t APS5_VABI _ZNSt8__sce_v210filesystem10_File_sizeEPKc_nid_postfix(const char* path) { + FileStat status{}; + return Kernel(__func__).stat(path, &status) != 0 ? UINT64_MAX : static_cast(status.st_size); +} + +std::int64_t APS5_VABI _ZNSt8__sce_v210filesystem16_Last_write_timeEPKc_nid_postfix(const char* path) { + FileStat status{}; + return Kernel(__func__).posixStat(path, &status) != 0 ? -1 : status.st_mtim.tv_sec; +} + +int APS5_VABI _ZNSt8__sce_v210filesystem16_Last_write_timeEPKcPlS3__nid_postfix(const char* path, std::int64_t* seconds, std::int64_t* nanoseconds) { + FileStat status{}; + const int result = Kernel(__func__).stat(path, &status); + if (result != 0) return ErrorOf(result); + *seconds = status.st_mtim.tv_sec; + *nanoseconds = status.st_mtim.tv_nsec; + return 0; +} + +bool APS5_VABI _ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix(const char* path, std::int64_t microseconds) { + const auto& kernel = Kernel(__func__); + FileStat status{}; + if (kernel.stat(path, &status) != 0) return false; + std::int64_t seconds = microseconds / MicrosecondsPerSecond; + std::int64_t remainder = microseconds - seconds * MicrosecondsPerSecond; + if (remainder < 0) { + --seconds; + remainder += MicrosecondsPerSecond; + } + const KernelTimeval times[2] = {{status.st_atim.tv_sec, status.st_atim.tv_nsec / NanosecondsPerMicrosecond}, {seconds, remainder}}; + return kernel.utimes(path, times) == 0; +} + +int APS5_VABI _ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix(const char* path, const char* existing) { + const auto& kernel = Kernel(__func__); + std::uint16_t mode = DefaultDirectoryMode; + if (existing) { + FileStat status{}; + const int result = kernel.stat(existing, &status); + if (result != 0) return -ErrorOf(result); + mode = status.st_mode; + } + const int result = kernel.mkdir(path, mode); + if (result == 0) return 1; + if (static_cast(result) == KernelExists) return 0; + return -ErrorOf(result); +} + +bool APS5_VABI _ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix(const char* path) { + return Kernel(__func__).posixRmdir(path) == 0; +} + +int APS5_VABI _ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix(const char* path) { + return StatusOf(Kernel(__func__).unlink(path)); +} + +int APS5_VABI _ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix(const char* from, const char* to) { + return StatusOf(Kernel(__func__).rename(from, to)); +} + +int APS5_VABI _ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix(const char* path, std::uint64_t size) { + return StatusOf(Kernel(__func__).truncate(path, static_cast(size))); +} + +int APS5_VABI _ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix(const char* path, int perms) { + return StatusOf(Kernel(__func__).chmod(path, static_cast(perms))); +} + +int APS5_VABI _ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix(const char* from, const char* to) { + const auto& kernel = Kernel(__func__); + const int source = kernel.open(from, 0, 0); + if (source < 0) return ErrorOf(source); + const int target = kernel.open(to, CopyTargetFlags, CopyTargetMode); + if (target < 0) { + kernel.close(source); + return ErrorOf(target); + } + const std::unique_ptr buffer(new (std::nothrow) std::byte[CopyBlock]); + const int result = buffer ? CopyContents(kernel, source, target, buffer.get()) : OutOfMemory; + kernel.close(target); + kernel.close(source); + return result; +} + +int APS5_VABI _ZNSt8__sce_v210filesystem8_StatvfsEPKcRNS0_10space_infoE_nid_postfix(const char*, std::uint64_t* space) { + space[0] = UINT64_MAX; + space[1] = UINT64_MAX; + space[2] = UINT64_MAX; + return NoSystemCall; +} + +int APS5_VABI _ZNSt8__sce_v210filesystem13_Current_set2EPKc_nid_postfix(const char* path) { + return std::strncmp(path, "/", 2) == 0 ? 0 : NotSupported; +} + +} diff --git a/core/libs/prx/libc/src/exception/StandardExceptions.cpp b/core/libs/prx/libc/src/exception/StandardExceptions.cpp index 8f6ced59c1..542653a235 100644 --- a/core/libs/prx/libc/src/exception/StandardExceptions.cpp +++ b/core/libs/prx/libc/src/exception/StandardExceptions.cpp @@ -100,9 +100,9 @@ const char* PlainWhat(const ExceptionObject* object) { if (std::strcmp(name, "St9exception") == 0) return "std::exception"; if (std::strcmp(name, "St8bad_cast") == 0) return "std::bad_cast"; if (std::strcmp(name, "St10bad_typeid") == 0) return "std::bad_typeid"; - if (std::strcmp(name, "St9bad_alloc") == 0) return "std::bad_alloc"; - if (std::strcmp(name, "St20bad_array_new_length") == 0) return "std::bad_array_new_length"; - if (std::strcmp(name, "St17bad_function_call") == 0) return "std::bad_function_call"; + if (std::strcmp(name, "St9bad_alloc") == 0) return "bad allocation"; + if (std::strcmp(name, "St20bad_array_new_length") == 0) return "bad allocation"; + if (std::strcmp(name, "St17bad_function_call") == 0) return "bad function call"; if (std::strcmp(name, "St12bad_weak_ptr") == 0) return "bad_weak_ptr"; if (std::strcmp(name, "St18bad_variant_access") == 0) return "bad_variant_access"; if (std::strcmp(name, "St19bad_optional_access") == 0) return "bad_optional_access"; diff --git a/core/libs/prx/libc/src/exception/TypeInfo.cpp b/core/libs/prx/libc/src/exception/TypeInfo.cpp index a1d58f019b..60f343163c 100644 --- a/core/libs/prx/libc/src/exception/TypeInfo.cpp +++ b/core/libs/prx/libc/src/exception/TypeInfo.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" +#include #include namespace LibcException { @@ -39,8 +40,50 @@ bool APS5_VABI DynamicCastTypeInfo(const ClassTypeInfo*, std::ptrdiff_t, ClassTy throw std::runtime_error("RTTI __dyncast_result is not supported"); } -ClassTypeInfo::__sub_kind APS5_VABI FindPublicTypeInfo(const ClassTypeInfo*, std::ptrdiff_t, const void*, const ClassTypeInfo*, const void*) { - throw std::runtime_error("RTTI __do_find_public_src is not supported"); +ClassTypeInfo::__sub_kind APS5_VABI FindPublicTypeInfo(const ClassTypeInfo* self, std::ptrdiff_t src2dst, + const void* object, const ClassTypeInfo* sourceType, + const void* source) { + constexpr int notContained = 1; + constexpr int containedPublic = 6; + constexpr int containedMask = 4; + constexpr int publicMask = 2; + constexpr int virtualMask = 1; + if (!self || !sourceType) throw std::invalid_argument("RTTI __do_find_public_src: null type"); + const auto* vtable = *reinterpret_cast(self); + const char* kind = vtable[-1]->name(); + if (std::strcmp(kind, "N10__cxxabiv120__si_class_type_infoE") == 0) { + if (object == source && *self == *sourceType) + return static_cast(containedPublic); + const auto* single = static_cast(self); + return FindPublicTypeInfo(single->__base_type, src2dst, object, sourceType, source); + } + if (std::strcmp(kind, "N10__cxxabiv121__vmi_class_type_infoE") == 0) { + if (object == source && *self == *sourceType) + return static_cast(containedPublic); + } else { + return static_cast(object == source ? containedPublic : notContained); + } + + const auto* multiple = static_cast(self); + for (unsigned index = multiple->__base_count; index-- > 0;) { + const auto& base = multiple->__base_info[index]; + if ((base.__offset_flags & publicMask) == 0) continue; + const bool isVirtual = (base.__offset_flags & virtualMask) != 0; + if (isVirtual && src2dst == -3) continue; + auto offset = base.__offset_flags >> 8; + const void* baseObject = object; + if (isVirtual) { + if (!object) continue; + const auto* addressPoint = *static_cast(object); + std::memcpy(&offset, reinterpret_cast(addressPoint) + offset, sizeof(offset)); + } + if (object) baseObject = static_cast(object) + offset; + const auto result = FindPublicTypeInfo(base.__base_type, src2dst, baseObject, sourceType, source); + const int bits = static_cast(result); + if ((bits & containedMask) == 0) continue; + return static_cast(bits | (isVirtual ? virtualMask : 0)); + } + return static_cast(notContained); } } @@ -62,25 +105,192 @@ struct TypeInfoVtable { static_assert(offsetof(TypeInfoVtable, destroy) == 2 * sizeof(void*)); static_assert(sizeof(TypeInfoVtable) == 11 * sizeof(void*)); +struct SiTypeRecord { const void* const* vtable; const char* name; const std::type_info* base; }; +struct PointerTypeRecord { const void* const* vtable; const char* name; unsigned flags; const std::type_info* pointee; }; + +} + +namespace { +const std::type_info* AsTypeInfo(const LibcException::SiTypeRecord& record) { + return reinterpret_cast(&record); +} } extern "C" { -LibcException::TypeInfoVtable _ZTVN10__cxxabiv117__class_type_infoE_nid_postfix {0, &typeid(__cxxabiv1::__class_type_info)}; -LibcException::TypeInfoVtable _ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix {0, &typeid(__cxxabiv1::__si_class_type_info)}; -LibcException::TypeInfoVtable _ZTVN10__cxxabiv121__vmi_class_type_infoE_nid_postfix {0, &typeid(__cxxabiv1::__vmi_class_type_info)}; +extern LibcException::SiTypeRecord _ZTISt9type_info_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv116__enum_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv117__array_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv117__class_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv117__pbase_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv119__pointer_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv120__function_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv120__si_class_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv121__vmi_class_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv123__fundamental_type_infoE_nid_postfix; +extern LibcException::SiTypeRecord _ZTIN10__cxxabiv129__pointer_to_member_type_infoE_nid_postfix; +LibcException::TypeInfoVtable _ZTVN10__cxxabiv117__class_type_infoE_nid_postfix { + 0, + AsTypeInfo(_ZTIN10__cxxabiv117__class_type_infoE_nid_postfix) +}; +LibcException::TypeInfoVtable _ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix { + 0, + AsTypeInfo(_ZTIN10__cxxabiv120__si_class_type_infoE_nid_postfix) +}; +LibcException::TypeInfoVtable _ZTVN10__cxxabiv121__vmi_class_type_infoE_nid_postfix { + 0, + AsTypeInfo(_ZTIN10__cxxabiv121__vmi_class_type_infoE_nid_postfix) +}; LibcException::TypeInfoVtable _ZTVN10__cxxabiv119__pointer_type_infoE_nid_postfix { 0, - &typeid(__cxxabiv1::__pointer_type_info), + AsTypeInfo(_ZTIN10__cxxabiv119__pointer_type_infoE_nid_postfix), LibcException::DestroyTypeInfo, LibcException::DeleteTypeInfo, LibcException::IsPointerTypeInfoTrue }; LibcException::TypeInfoVtable _ZTVN10__cxxabiv120__function_type_infoE_nid_postfix { 0, - &typeid(__cxxabiv1::__function_type_info), + AsTypeInfo(_ZTIN10__cxxabiv120__function_type_infoE_nid_postfix), LibcException::DestroyTypeInfo, LibcException::DeleteTypeInfo, LibcException::IsPointerTypeInfo, LibcException::IsFunctionTypeInfoTrue }; +LibcException::TypeInfoVtable _ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix { + 0, + AsTypeInfo(_ZTIN10__cxxabiv123__fundamental_type_infoE_nid_postfix) +}; +LibcException::TypeInfoVtable _ZTVN10__cxxabiv116__shim_type_infoE_nid_postfix { + 0, + AsTypeInfo(_ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix) +}; +LibcException::TypeInfoVtable _ZTVN10__cxxabiv116__enum_type_infoE_nid_postfix { + 0, + AsTypeInfo(_ZTIN10__cxxabiv116__enum_type_infoE_nid_postfix) +}; +LibcException::TypeInfoVtable _ZTVN10__cxxabiv117__array_type_infoE_nid_postfix { + 0, + AsTypeInfo(_ZTIN10__cxxabiv117__array_type_infoE_nid_postfix) +}; +LibcException::TypeInfoVtable _ZTVN10__cxxabiv117__pbase_type_infoE_nid_postfix { + 0, + AsTypeInfo(_ZTIN10__cxxabiv117__pbase_type_infoE_nid_postfix) +}; +LibcException::TypeInfoVtable _ZTVN10__cxxabiv129__pointer_to_member_type_infoE_nid_postfix { + 0, + AsTypeInfo(_ZTIN10__cxxabiv129__pointer_to_member_type_infoE_nid_postfix) +}; +} + +namespace { +struct FundamentalTypeRecord { const void* vtable; const char* name; }; +} + +extern "C" { +FundamentalTypeRecord _ZTIv_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "v"}; +FundamentalTypeRecord _ZTIw_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "w"}; +FundamentalTypeRecord _ZTIb_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "b"}; +FundamentalTypeRecord _ZTIc_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "c"}; +FundamentalTypeRecord _ZTIa_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "a"}; +FundamentalTypeRecord _ZTIh_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "h"}; +FundamentalTypeRecord _ZTIs_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "s"}; +FundamentalTypeRecord _ZTIt_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "t"}; +FundamentalTypeRecord _ZTIi_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "i"}; +FundamentalTypeRecord _ZTIj_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "j"}; +FundamentalTypeRecord _ZTIl_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "l"}; +FundamentalTypeRecord _ZTIm_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "m"}; +FundamentalTypeRecord _ZTIx_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "x"}; +FundamentalTypeRecord _ZTIy_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "y"}; +FundamentalTypeRecord _ZTIn_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "n"}; +FundamentalTypeRecord _ZTIo_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "o"}; +FundamentalTypeRecord _ZTIf_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "f"}; +FundamentalTypeRecord _ZTId_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "d"}; +FundamentalTypeRecord _ZTIe_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "e"}; +FundamentalTypeRecord _ZTIg_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "g"}; +FundamentalTypeRecord _ZTIDn_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "Dn"}; +FundamentalTypeRecord _ZTIDs_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "Ds"}; +FundamentalTypeRecord _ZTIDi_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "Di"}; +FundamentalTypeRecord _ZTIDu_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "Du"}; +FundamentalTypeRecord _ZTIDh_nid_postfix {reinterpret_cast(&_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix.destroy), "Dh"}; +} + +namespace { +const void* const* ClassVtable() { + return reinterpret_cast(&_ZTVN10__cxxabiv117__class_type_infoE_nid_postfix) + 2; +} +const void* const* SingleVtable() { + return reinterpret_cast(&_ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix) + 2; +} +const void* const* PointerVtable() { + return reinterpret_cast(&_ZTVN10__cxxabiv119__pointer_type_infoE_nid_postfix) + 2; +} +const std::type_info* AsTypeInfo(const FundamentalTypeRecord& record) { + return reinterpret_cast(&record); +} +} + +extern "C" { +LibcException::SiTypeRecord _ZTISt9type_info_nid_postfix {ClassVtable(), "St9type_info", nullptr}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv116__shim_type_infoE", AsTypeInfo(_ZTISt9type_info_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv116__enum_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv116__enum_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv117__array_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv117__array_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv117__class_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv117__class_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv117__pbase_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv117__pbase_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv119__pointer_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv119__pointer_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv117__pbase_type_infoE_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv120__function_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv120__function_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv120__si_class_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv120__si_class_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv117__class_type_infoE_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv121__vmi_class_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv121__vmi_class_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv117__class_type_infoE_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv123__fundamental_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv123__fundamental_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix)}; +LibcException::SiTypeRecord _ZTIN10__cxxabiv129__pointer_to_member_type_infoE_nid_postfix {SingleVtable(), "N10__cxxabiv129__pointer_to_member_type_infoE", AsTypeInfo(_ZTIN10__cxxabiv117__pbase_type_infoE_nid_postfix)}; + +LibcException::PointerTypeRecord _ZTIPv_nid_postfix {PointerVtable(), "Pv", 0, AsTypeInfo(_ZTIv_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPw_nid_postfix {PointerVtable(), "Pw", 0, AsTypeInfo(_ZTIw_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPb_nid_postfix {PointerVtable(), "Pb", 0, AsTypeInfo(_ZTIb_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPc_nid_postfix {PointerVtable(), "Pc", 0, AsTypeInfo(_ZTIc_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPa_nid_postfix {PointerVtable(), "Pa", 0, AsTypeInfo(_ZTIa_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPh_nid_postfix {PointerVtable(), "Ph", 0, AsTypeInfo(_ZTIh_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPs_nid_postfix {PointerVtable(), "Ps", 0, AsTypeInfo(_ZTIs_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPt_nid_postfix {PointerVtable(), "Pt", 0, AsTypeInfo(_ZTIt_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPi_nid_postfix {PointerVtable(), "Pi", 0, AsTypeInfo(_ZTIi_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPj_nid_postfix {PointerVtable(), "Pj", 0, AsTypeInfo(_ZTIj_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPl_nid_postfix {PointerVtable(), "Pl", 0, AsTypeInfo(_ZTIl_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPm_nid_postfix {PointerVtable(), "Pm", 0, AsTypeInfo(_ZTIm_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPx_nid_postfix {PointerVtable(), "Px", 0, AsTypeInfo(_ZTIx_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPy_nid_postfix {PointerVtable(), "Py", 0, AsTypeInfo(_ZTIy_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPn_nid_postfix {PointerVtable(), "Pn", 0, AsTypeInfo(_ZTIn_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPo_nid_postfix {PointerVtable(), "Po", 0, AsTypeInfo(_ZTIo_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPf_nid_postfix {PointerVtable(), "Pf", 0, AsTypeInfo(_ZTIf_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPd_nid_postfix {PointerVtable(), "Pd", 0, AsTypeInfo(_ZTId_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPe_nid_postfix {PointerVtable(), "Pe", 0, AsTypeInfo(_ZTIe_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPg_nid_postfix {PointerVtable(), "Pg", 0, AsTypeInfo(_ZTIg_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPDn_nid_postfix {PointerVtable(), "PDn", 0, AsTypeInfo(_ZTIDn_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPDs_nid_postfix {PointerVtable(), "PDs", 0, AsTypeInfo(_ZTIDs_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPDi_nid_postfix {PointerVtable(), "PDi", 0, AsTypeInfo(_ZTIDi_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPDu_nid_postfix {PointerVtable(), "PDu", 0, AsTypeInfo(_ZTIDu_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPDh_nid_postfix {PointerVtable(), "PDh", 0, AsTypeInfo(_ZTIDh_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKv_nid_postfix {PointerVtable(), "PKv", 1, AsTypeInfo(_ZTIv_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKw_nid_postfix {PointerVtable(), "PKw", 1, AsTypeInfo(_ZTIw_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKb_nid_postfix {PointerVtable(), "PKb", 1, AsTypeInfo(_ZTIb_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKc_nid_postfix {PointerVtable(), "PKc", 1, AsTypeInfo(_ZTIc_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKa_nid_postfix {PointerVtable(), "PKa", 1, AsTypeInfo(_ZTIa_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKh_nid_postfix {PointerVtable(), "PKh", 1, AsTypeInfo(_ZTIh_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKs_nid_postfix {PointerVtable(), "PKs", 1, AsTypeInfo(_ZTIs_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKt_nid_postfix {PointerVtable(), "PKt", 1, AsTypeInfo(_ZTIt_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKi_nid_postfix {PointerVtable(), "PKi", 1, AsTypeInfo(_ZTIi_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKj_nid_postfix {PointerVtable(), "PKj", 1, AsTypeInfo(_ZTIj_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKl_nid_postfix {PointerVtable(), "PKl", 1, AsTypeInfo(_ZTIl_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKm_nid_postfix {PointerVtable(), "PKm", 1, AsTypeInfo(_ZTIm_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKx_nid_postfix {PointerVtable(), "PKx", 1, AsTypeInfo(_ZTIx_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKy_nid_postfix {PointerVtable(), "PKy", 1, AsTypeInfo(_ZTIy_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKn_nid_postfix {PointerVtable(), "PKn", 1, AsTypeInfo(_ZTIn_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKo_nid_postfix {PointerVtable(), "PKo", 1, AsTypeInfo(_ZTIo_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKf_nid_postfix {PointerVtable(), "PKf", 1, AsTypeInfo(_ZTIf_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKd_nid_postfix {PointerVtable(), "PKd", 1, AsTypeInfo(_ZTId_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKe_nid_postfix {PointerVtable(), "PKe", 1, AsTypeInfo(_ZTIe_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKg_nid_postfix {PointerVtable(), "PKg", 1, AsTypeInfo(_ZTIg_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKDn_nid_postfix {PointerVtable(), "PKDn", 1, AsTypeInfo(_ZTIDn_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKDs_nid_postfix {PointerVtable(), "PKDs", 1, AsTypeInfo(_ZTIDs_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKDi_nid_postfix {PointerVtable(), "PKDi", 1, AsTypeInfo(_ZTIDi_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKDu_nid_postfix {PointerVtable(), "PKDu", 1, AsTypeInfo(_ZTIDu_nid_postfix)}; +LibcException::PointerTypeRecord _ZTIPKDh_nid_postfix {PointerVtable(), "PKDh", 1, AsTypeInfo(_ZTIDh_nid_postfix)}; } diff --git a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp index ca9e252ab4..0f342e0c79 100644 --- a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp +++ b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp @@ -76,6 +76,7 @@ bool IsFatal(DWORD code) { case EXCEPTION_PRIV_INSTRUCTION: case EXCEPTION_STACK_OVERFLOW: case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_INT_OVERFLOW: case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: case EXCEPTION_IN_PAGE_ERROR: return true; @@ -372,7 +373,7 @@ LONG WINAPI ReportCrash(EXCEPTION_POINTERS* info) { char line[MAX_PATH + 64]; char threadName[128] = ""; if (PWSTR description = ThreadDescription(GetCurrentThread())) { - WideCharToMultiByte(CP_UTF8, 0, description, -1, threadName, sizeof(threadName), nullptr, nullptr); + WideCharToMultiByte(CP_UTF8, 0, description, -1, threadName, sizeof(threadName) - 1, nullptr, nullptr); LocalFree(description); } Report("\nFATAL: unhandled exception 0x%08lx on thread %lu '%s'\n", record->ExceptionCode, GetCurrentThreadId(), threadName); @@ -590,7 +591,7 @@ void ReportAllThreads() { ResumeThread(thread); char name[128] = ""; if (PWSTR description = ThreadDescription(thread)) { - WideCharToMultiByte(CP_UTF8, 0, description, -1, name, sizeof(name), nullptr, nullptr); + WideCharToMultiByte(CP_UTF8, 0, description, -1, name, sizeof(name) - 1, nullptr, nullptr); LocalFree(description); } CloseHandle(thread); 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 8a8658366e..c6013bb4db 100644 --- a/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp +++ b/core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp @@ -3,6 +3,7 @@ #include #elif defined(__APPLE__) #include +#include #include #include #include @@ -14,6 +15,7 @@ #include #else #include +#include #include #include #include @@ -89,6 +91,23 @@ bool Selected(std::string_view library) { return false; } +std::string DefiningLibrary(const void* target) { +#ifdef _WIN32 + MEMORY_BASIC_INFORMATION memory{}; + if (VirtualQuery(target, &memory, sizeof(memory)) != sizeof(memory)) return {}; + std::wstring path(32768, L'\0'); + const auto length = GetModuleFileNameW(static_cast(memory.AllocationBase), path.data(), static_cast(path.size())); + if (length == 0 || length >= path.size()) return {}; + path.resize(length); + const auto filename = std::filesystem::path(path).filename().u8string(); + return {reinterpret_cast(filename.data()), filename.size()}; +#else + Dl_info info{}; + if (::dladdr(target, &info) == 0 || info.dli_fname == nullptr) return {}; + return std::filesystem::path(info.dli_fname).filename().string(); +#endif +} + 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); @@ -429,7 +448,15 @@ void APS5_VABI CallTraceRecord_nid_no_patch(const void* record, std::uint64_t re } void* TraceImport_nid_no_patch(void* target, const char* name, const char* library) { - if (target == nullptr || name == nullptr || library == nullptr || !Selected(library)) return target; + if (target == nullptr || name == nullptr || library == nullptr) return target; + std::string definingLibrary; + if (*library == '\0') { + const auto* selected = std::getenv("APS5_TRACE_CALLS"); + if (selected == nullptr || *selected == '\0') return target; + definingLibrary = DefiningLibrary(target); + library = definingLibrary.c_str(); + } + if (!Selected(library)) return target; std::lock_guard lock(g_mutex); if (g_ring.load(std::memory_order_relaxed) == nullptr) { MapThunkArea(); diff --git a/core/libs/prx/libcairo/CMakeLists.txt b/core/libs/prx/libcairo/CMakeLists.txt new file mode 100644 index 0000000000..f24b385f75 --- /dev/null +++ b/core/libs/prx/libcairo/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(libcairo SHARED EXCLUDE_FROM_ALL Export.cpp Matrix.cpp) +target_include_directories(libcairo PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libcairo) + +if(BUILD_TESTING) + add_test_executable(guest_cairo_matrix_tests ${LIBS_INCLUDE_DIR}/tests/GuestCairoMatrix.cpp) + target_include_directories(guest_cairo_matrix_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_cairo_matrix_tests PRIVATE libcairo libc) + configure_windows_unwind(guest_cairo_matrix_tests) + add_test(NAME guest_cairo_matrix COMMAND guest_cairo_matrix_tests) + set_tests_properties(guest_cairo_matrix PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_cairo_matrix PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libcairo/Export.cpp b/core/libs/prx/libcairo/Export.cpp index db5ce4bbcd..15829b95e8 100644 --- a/core/libs/prx/libcairo/Export.cpp +++ b/core/libs/prx/libcairo/Export.cpp @@ -56,10 +56,6 @@ int APS5_VABI cairo_set_antialias() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_matrix_translate() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_rotate() { NotImplemented_nid_no_patch(__func__); } @@ -104,22 +100,10 @@ int APS5_VABI cairo_pattern_reference() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_matrix_scale() { - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI cairo_matrix_invert() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_scaled_font_get_font_options() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_matrix_init_translate() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_surface_get_type() { NotImplemented_nid_no_patch(__func__); } @@ -184,10 +168,6 @@ int APS5_VABI cairo_rectangle() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_matrix_multiply() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_get_matrix() { NotImplemented_nid_no_patch(__func__); } @@ -264,10 +244,6 @@ int APS5_VABI cairo_destroy() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_matrix_init() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_clip_extents() { NotImplemented_nid_no_patch(__func__); } @@ -276,10 +252,6 @@ int APS5_VABI cairo_set_scaled_font() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_matrix_transform_point() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_image_surface_get_format() { NotImplemented_nid_no_patch(__func__); } @@ -292,10 +264,6 @@ int APS5_VABI cairo_fill() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_matrix_transform_distance() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_transform() { NotImplemented_nid_no_patch(__func__); } @@ -408,10 +376,6 @@ int APS5_VABI cairo_region_reference() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_matrix_init_identity() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_scaled_font_status() { NotImplemented_nid_no_patch(__func__); } @@ -596,10 +560,6 @@ int APS5_VABI cairo_get_operator() { NotImplemented_nid_no_patch(__func__); } -int APS5_VABI cairo_matrix_rotate() { - NotImplemented_nid_no_patch(__func__); -} - int APS5_VABI cairo_scaled_font_create() { NotImplemented_nid_no_patch(__func__); } diff --git a/core/libs/prx/libcairo/Matrix.cpp b/core/libs/prx/libcairo/Matrix.cpp new file mode 100644 index 0000000000..fcb0865d67 --- /dev/null +++ b/core/libs/prx/libcairo/Matrix.cpp @@ -0,0 +1,100 @@ +#include "prx/libcairo/include/Matrix.hpp" +#include + +namespace { + +CairoMatrix Product(const CairoMatrix& a, const CairoMatrix& b) { + return { + a.xx * b.xx + a.yx * b.xy, + a.xx * b.yx + a.yx * b.yy, + a.xy * b.xx + a.yy * b.xy, + a.xy * b.yx + a.yy * b.yy, + a.x0 * b.xx + a.y0 * b.xy + b.x0, + a.x0 * b.yx + a.y0 * b.yy + b.y0 + }; +} + +} + +extern "C" { + +void APS5_VABI cairo_matrix_init(CairoMatrix* matrix, double xx, double yx, double xy, double yy, double x0, double y0) { + *matrix = {xx, yx, xy, yy, x0, y0}; +} + +void APS5_VABI cairo_matrix_init_identity(CairoMatrix* matrix) { + *matrix = {1, 0, 0, 1, 0, 0}; +} + +void APS5_VABI cairo_matrix_init_translate(CairoMatrix* matrix, double tx, double ty) { + *matrix = {1, 0, 0, 1, tx, ty}; +} + +void APS5_VABI cairo_matrix_multiply(CairoMatrix* result, const CairoMatrix* a, const CairoMatrix* b) { + *result = Product(*a, *b); +} + +void APS5_VABI cairo_matrix_translate(CairoMatrix* matrix, double tx, double ty) { + const CairoMatrix translation{1, 0, 0, 1, tx, ty}; + *matrix = Product(translation, *matrix); +} + +void APS5_VABI cairo_matrix_scale(CairoMatrix* matrix, double sx, double sy) { + const CairoMatrix scaling{sx, 0, 0, sy, 0, 0}; + *matrix = Product(scaling, *matrix); +} + +void APS5_VABI cairo_matrix_rotate(CairoMatrix* matrix, double radians) { + const double c = std::cos(radians); + const double s = std::sin(radians); + const CairoMatrix rotation{c, s, -s, c, 0, 0}; + *matrix = Product(rotation, *matrix); +} + +int APS5_VABI cairo_matrix_invert(CairoMatrix* matrix) { + if (matrix->xy == 0 && matrix->yx == 0) { + matrix->x0 = -matrix->x0; + matrix->y0 = -matrix->y0; + if (matrix->xx != 1) { + if (matrix->xx == 0) return 5; + matrix->xx = 1 / matrix->xx; + matrix->x0 *= matrix->xx; + } + if (matrix->yy != 1) { + if (matrix->yy == 0) return 5; + matrix->yy = 1 / matrix->yy; + matrix->y0 *= matrix->yy; + } + return 0; + } + + const CairoMatrix original = *matrix; + const double determinant = original.xx * original.yy - original.yx * original.xy; + if (!std::isfinite(determinant) || determinant == 0) return 5; + const double inverse = 1 / determinant; + *matrix = { + original.yy * inverse, + -original.yx * inverse, + -original.xy * inverse, + original.xx * inverse, + (original.xy * original.y0 - original.yy * original.x0) * inverse, + (original.yx * original.x0 - original.xx * original.y0) * inverse + }; + return 0; +} + +void APS5_VABI cairo_matrix_transform_distance(const CairoMatrix* matrix, double* dx, double* dy) { + const double x = *dx; + const double y = *dy; + *dx = matrix->xx * x + matrix->xy * y; + *dy = matrix->yx * x + matrix->yy * y; +} + +void APS5_VABI cairo_matrix_transform_point(const CairoMatrix* matrix, double* x, double* y) { + const double oldX = *x; + const double oldY = *y; + *x = matrix->xx * oldX + matrix->xy * oldY + matrix->x0; + *y = matrix->yx * oldX + matrix->yy * oldY + matrix->y0; +} + +} diff --git a/core/libs/prx/libcairo/include/InventoryExports.hpp b/core/libs/prx/libcairo/include/InventoryExports.hpp index c5e5ee360e..4bbd829fad 100644 --- a/core/libs/prx/libcairo/include/InventoryExports.hpp +++ b/core/libs/prx/libcairo/include/InventoryExports.hpp @@ -17,7 +17,6 @@ int APS5_VABI cairo_image_surface_get_stride(); int APS5_VABI cairo_user_to_device(); int APS5_VABI cairo_reference(); int APS5_VABI cairo_set_antialias(); -int APS5_VABI cairo_matrix_translate(); int APS5_VABI cairo_rotate(); int APS5_VABI cairo_image_surface_create_from_png_stream(); int APS5_VABI cairo_get_fill_rule(); @@ -29,10 +28,7 @@ int APS5_VABI cairo_get_dash(); int APS5_VABI cairo_set_source_rgb(); int APS5_VABI cairo_scaled_font_destroy(); int APS5_VABI cairo_pattern_reference(); -int APS5_VABI cairo_matrix_scale(); -int APS5_VABI cairo_matrix_invert(); int APS5_VABI cairo_scaled_font_get_font_options(); -int APS5_VABI cairo_matrix_init_translate(); int APS5_VABI cairo_surface_get_type(); int APS5_VABI cairo_in_stroke(); int APS5_VABI cairo_scaled_font_get_ctm(); @@ -49,7 +45,6 @@ int APS5_VABI cairo_curve_to(); int APS5_VABI cairo_surface_reference(); int APS5_VABI cairo_copy_path(); int APS5_VABI cairo_rectangle(); -int APS5_VABI cairo_matrix_multiply(); int APS5_VABI cairo_get_matrix(); int APS5_VABI cairo_user_to_device_distance(); int APS5_VABI cairo_scaled_font_extents(); @@ -69,14 +64,11 @@ int APS5_VABI cairo_scaled_font_text_extents(); int APS5_VABI cairo_has_current_point(); int APS5_VABI cairo_pattern_create_linear(); int APS5_VABI cairo_destroy(); -int APS5_VABI cairo_matrix_init(); int APS5_VABI cairo_clip_extents(); int APS5_VABI cairo_set_scaled_font(); -int APS5_VABI cairo_matrix_transform_point(); int APS5_VABI cairo_image_surface_get_format(); int APS5_VABI cairo_region_create(); int APS5_VABI cairo_fill(); -int APS5_VABI cairo_matrix_transform_distance(); int APS5_VABI cairo_transform(); int APS5_VABI cairo_set_line_join(); int APS5_VABI cairo_pop_group_to_source(); @@ -105,7 +97,6 @@ int APS5_VABI cairo_surface_mark_dirty_rectangle(); int APS5_VABI cairo_append_path(); int APS5_VABI cairo_region_union_rectangle(); int APS5_VABI cairo_region_reference(); -int APS5_VABI cairo_matrix_init_identity(); int APS5_VABI cairo_scaled_font_status(); int APS5_VABI cairo_in_fill(); int APS5_VABI cairo_get_line_join(); @@ -152,7 +143,6 @@ int APS5_VABI cairo_get_current_point(); int APS5_VABI cairo_move_to(); int APS5_VABI cairo_scale(); int APS5_VABI cairo_get_operator(); -int APS5_VABI cairo_matrix_rotate(); int APS5_VABI cairo_scaled_font_create(); int APS5_VABI cairo_pattern_create_radial(); int APS5_VABI cairo_surface_get_content(); diff --git a/core/libs/prx/libcairo/include/Matrix.hpp b/core/libs/prx/libcairo/include/Matrix.hpp new file mode 100644 index 0000000000..7c49006ac1 --- /dev/null +++ b/core/libs/prx/libcairo/include/Matrix.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "SceTypes.hpp" +#include + +struct CairoMatrix { + double xx; + double yx; + double xy; + double yy; + double x0; + double y0; +}; + +static_assert(sizeof(CairoMatrix) == 6 * sizeof(double)); +static_assert(offsetof(CairoMatrix, yx) == sizeof(double)); +static_assert(offsetof(CairoMatrix, x0) == 4 * sizeof(double)); + +extern "C" { +void APS5_VABI cairo_matrix_init(CairoMatrix* matrix, double xx, double yx, double xy, double yy, double x0, double y0); +void APS5_VABI cairo_matrix_init_identity(CairoMatrix* matrix); +void APS5_VABI cairo_matrix_init_translate(CairoMatrix* matrix, double tx, double ty); +void APS5_VABI cairo_matrix_translate(CairoMatrix* matrix, double tx, double ty); +void APS5_VABI cairo_matrix_scale(CairoMatrix* matrix, double sx, double sy); +void APS5_VABI cairo_matrix_rotate(CairoMatrix* matrix, double radians); +int APS5_VABI cairo_matrix_invert(CairoMatrix* matrix); +void APS5_VABI cairo_matrix_multiply(CairoMatrix* result, const CairoMatrix* a, const CairoMatrix* b); +void APS5_VABI cairo_matrix_transform_point(const CairoMatrix* matrix, double* x, double* y); +void APS5_VABI cairo_matrix_transform_distance(const CairoMatrix* matrix, double* dx, double* dy); +} diff --git a/core/libs/prx/libcairo/libcairo.hpp b/core/libs/prx/libcairo/libcairo.hpp index d3d5da7295..1370182733 100644 --- a/core/libs/prx/libcairo/libcairo.hpp +++ b/core/libs/prx/libcairo/libcairo.hpp @@ -1,3 +1,4 @@ #pragma once #include "prx/libcairo/include/InventoryExports.hpp" +#include "prx/libcairo/include/Matrix.hpp" diff --git a/core/libs/prx/libfreetype/Export.cpp b/core/libs/prx/libfreetype/Export.cpp index b8064f6b90..705f5e9e4d 100644 --- a/core/libs/prx/libfreetype/Export.cpp +++ b/core/libs/prx/libfreetype/Export.cpp @@ -5,128 +5,128 @@ extern "C" { -int APS5_VABI libfreetypeUnknown__plus_AF9KH7aZbc_nid_no_patch() { +int APS5_VABI libfreetypePaletteDataGet_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("+AF9KH7aZbc", libfreetypeUnknown__plus_AF9KH7aZbc_nid_no_patch); +APS5_EXPORT("+AF9KH7aZbc", libfreetypePaletteDataGet_nid_no_patch); -int APS5_VABI libfreetypeUnknown__plus_sDVkl3aAR8_nid_no_patch() { +int APS5_VABI libfreetypeGetVarAxisFlags_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("+sDVkl3aAR8", libfreetypeUnknown__plus_sDVkl3aAR8_nid_no_patch); +APS5_EXPORT("+sDVkl3aAR8", libfreetypeGetVarAxisFlags_nid_no_patch); -int APS5_VABI libfreetypeUnknown_iuebdW9Mi0U_nid_no_patch() { +int APS5_VABI libfreetypeSetDefaultProperties_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("iuebdW9Mi0U", libfreetypeUnknown_iuebdW9Mi0U_nid_no_patch); +APS5_EXPORT("iuebdW9Mi0U", libfreetypeSetDefaultProperties_nid_no_patch); -int APS5_VABI libfreetypeUnknown_qCyybpSyZzI_nid_no_patch() { +int APS5_VABI libfreetypePaletteSelect_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("qCyybpSyZzI", libfreetypeUnknown_qCyybpSyZzI_nid_no_patch); +APS5_EXPORT("qCyybpSyZzI", libfreetypePaletteSelect_nid_no_patch); -int APS5_VABI libfreetypeUnknown_ypZKJwPSVgQ_nid_no_patch() { +int APS5_VABI libfreetypeGetColorGlyphLayer_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("ypZKJwPSVgQ", libfreetypeUnknown_ypZKJwPSVgQ_nid_no_patch); +APS5_EXPORT("ypZKJwPSVgQ", libfreetypeGetColorGlyphLayer_nid_no_patch); } extern "C" { -int APS5_VABI freetypeLegacyUnknown_4kVFLShKGkQ_nid_no_patch() { +int APS5_VABI freetypeLegacyGetBDFProperty_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("4kVFLShKGkQ", freetypeLegacyUnknown_4kVFLShKGkQ_nid_no_patch); +APS5_EXPORT("4kVFLShKGkQ", freetypeLegacyGetBDFProperty_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_C3_minus_qsnriefE_nid_no_patch() { +int APS5_VABI freetypeLegacyBitmapBlend_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("C3-qsnriefE", freetypeLegacyUnknown_C3_minus_qsnriefE_nid_no_patch); +APS5_EXPORT("C3-qsnriefE", freetypeLegacyBitmapBlend_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_DYbLD_minus_s1oiQ_nid_no_patch() { +int APS5_VABI freetypeLegacyFtcManagerNew_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("DYbLD-s1oiQ", freetypeLegacyUnknown_DYbLD_minus_s1oiQ_nid_no_patch); +APS5_EXPORT("DYbLD-s1oiQ", freetypeLegacyFtcManagerNew_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_EPNrOPQ5B14_nid_no_patch() { +int APS5_VABI freetypeLegacyStreamOpenGzip_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("EPNrOPQ5B14", freetypeLegacyUnknown_EPNrOPQ5B14_nid_no_patch); +APS5_EXPORT("EPNrOPQ5B14", freetypeLegacyStreamOpenGzip_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_JuHk_minus_xDmRmI_nid_no_patch() { +int APS5_VABI freetypeLegacyStreamOpenBzip2_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("JuHk-xDmRmI", freetypeLegacyUnknown_JuHk_minus_xDmRmI_nid_no_patch); +APS5_EXPORT("JuHk-xDmRmI", freetypeLegacyStreamOpenBzip2_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_K9ZOPLPOqbY_nid_no_patch() { +int APS5_VABI freetypeLegacyGetMMWeightVector_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("K9ZOPLPOqbY", freetypeLegacyUnknown_K9ZOPLPOqbY_nid_no_patch); +APS5_EXPORT("K9ZOPLPOqbY", freetypeLegacyGetMMWeightVector_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_KVRW5HPnbgU_nid_no_patch() { +int APS5_VABI freetypeLegacyGetBDFCharsetID_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("KVRW5HPnbgU", freetypeLegacyUnknown_KVRW5HPnbgU_nid_no_patch); +APS5_EXPORT("KVRW5HPnbgU", freetypeLegacyGetBDFCharsetID_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_MDsMjUGulyY_nid_no_patch() { +int APS5_VABI freetypeLegacyGetSfntLangTag_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("MDsMjUGulyY", freetypeLegacyUnknown_MDsMjUGulyY_nid_no_patch); +APS5_EXPORT("MDsMjUGulyY", freetypeLegacyGetSfntLangTag_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_NbFQEYieSS0_nid_no_patch() { +int APS5_VABI freetypeLegacyPaletteSetForegroundColor_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("NbFQEYieSS0", freetypeLegacyUnknown_NbFQEYieSS0_nid_no_patch); +APS5_EXPORT("NbFQEYieSS0", freetypeLegacyPaletteSetForegroundColor_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_OGVR5us9otU_nid_no_patch() { +int APS5_VABI freetypeLegacySetMMWeightVector_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("OGVR5us9otU", freetypeLegacyUnknown_OGVR5us9otU_nid_no_patch); +APS5_EXPORT("OGVR5us9otU", freetypeLegacySetMMWeightVector_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_PS_minus_iwpPa2tc_nid_no_patch() { +int APS5_VABI freetypeLegacyGetMMBlendCoordinates_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("PS-iwpPa2tc", freetypeLegacyUnknown_PS_minus_iwpPa2tc_nid_no_patch); +APS5_EXPORT("PS-iwpPa2tc", freetypeLegacyGetMMBlendCoordinates_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_V476bsxEylk_nid_no_patch() { +int APS5_VABI freetypeLegacySetNamedInstance_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("V476bsxEylk", freetypeLegacyUnknown_V476bsxEylk_nid_no_patch); +APS5_EXPORT("V476bsxEylk", freetypeLegacySetNamedInstance_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_VKuZXhyjtoo_nid_no_patch() { +int APS5_VABI freetypeLegacyFaceProperties_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("VKuZXhyjtoo", freetypeLegacyUnknown_VKuZXhyjtoo_nid_no_patch); +APS5_EXPORT("VKuZXhyjtoo", freetypeLegacyFaceProperties_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_Xxtq_plus_Qr5lzI_nid_no_patch() { +int APS5_VABI freetypeLegacyLibrarySetLcdGeometry_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("Xxtq+Qr5lzI", freetypeLegacyUnknown_Xxtq_plus_Qr5lzI_nid_no_patch); +APS5_EXPORT("Xxtq+Qr5lzI", freetypeLegacyLibrarySetLcdGeometry_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_boXwTAWsas0_nid_no_patch() { +int APS5_VABI freetypeLegacyErrorString_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("boXwTAWsas0", freetypeLegacyUnknown_boXwTAWsas0_nid_no_patch); +APS5_EXPORT("boXwTAWsas0", freetypeLegacyErrorString_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_ctqkAfkma_minus_k_nid_no_patch() { +int APS5_VABI freetypeLegacyStreamOpenLZW_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("ctqkAfkma-k", freetypeLegacyUnknown_ctqkAfkma_minus_k_nid_no_patch); +APS5_EXPORT("ctqkAfkma-k", freetypeLegacyStreamOpenLZW_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_hKvvpNeUdKo_nid_no_patch() { +int APS5_VABI freetypeLegacyGzipUncompress_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("hKvvpNeUdKo", freetypeLegacyUnknown_hKvvpNeUdKo_nid_no_patch); +APS5_EXPORT("hKvvpNeUdKo", freetypeLegacyGzipUncompress_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_sZq9oSwMGSs_nid_no_patch() { +int APS5_VABI freetypeLegacyNewGlyph_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("sZq9oSwMGSs", freetypeLegacyUnknown_sZq9oSwMGSs_nid_no_patch); +APS5_EXPORT("sZq9oSwMGSs", freetypeLegacyNewGlyph_nid_no_patch); -int APS5_VABI freetypeLegacyUnknown_suWwpZzQl00_nid_no_patch() { +int APS5_VABI freetypeLegacyGetWinFNTHeader_nid_no_patch() { NotImplemented_nid_no_patch(__func__); } -APS5_EXPORT("suWwpZzQl00", freetypeLegacyUnknown_suWwpZzQl00_nid_no_patch); +APS5_EXPORT("suWwpZzQl00", freetypeLegacyGetWinFNTHeader_nid_no_patch); } diff --git a/core/libs/prx/libfreetype/include/InventoryExports.hpp b/core/libs/prx/libfreetype/include/InventoryExports.hpp index 032458af25..26bea5f451 100644 --- a/core/libs/prx/libfreetype/include/InventoryExports.hpp +++ b/core/libs/prx/libfreetype/include/InventoryExports.hpp @@ -4,10 +4,10 @@ extern "C" { -int APS5_VABI libfreetypeUnknown__plus_AF9KH7aZbc_nid_no_patch(); -int APS5_VABI libfreetypeUnknown__plus_sDVkl3aAR8_nid_no_patch(); -int APS5_VABI libfreetypeUnknown_iuebdW9Mi0U_nid_no_patch(); -int APS5_VABI libfreetypeUnknown_qCyybpSyZzI_nid_no_patch(); -int APS5_VABI libfreetypeUnknown_ypZKJwPSVgQ_nid_no_patch(); +int APS5_VABI libfreetypePaletteDataGet_nid_no_patch(); +int APS5_VABI libfreetypeGetVarAxisFlags_nid_no_patch(); +int APS5_VABI libfreetypeSetDefaultProperties_nid_no_patch(); +int APS5_VABI libfreetypePaletteSelect_nid_no_patch(); +int APS5_VABI libfreetypeGetColorGlyphLayer_nid_no_patch(); } diff --git a/core/libs/prx/libfreetype/include/LegacyExports.hpp b/core/libs/prx/libfreetype/include/LegacyExports.hpp index a3aaf7c301..7829df1fa4 100644 --- a/core/libs/prx/libfreetype/include/LegacyExports.hpp +++ b/core/libs/prx/libfreetype/include/LegacyExports.hpp @@ -4,24 +4,24 @@ extern "C" { -int APS5_VABI freetypeLegacyUnknown_4kVFLShKGkQ_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_C3_minus_qsnriefE_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_DYbLD_minus_s1oiQ_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_EPNrOPQ5B14_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_JuHk_minus_xDmRmI_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_K9ZOPLPOqbY_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_KVRW5HPnbgU_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_MDsMjUGulyY_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_NbFQEYieSS0_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_OGVR5us9otU_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_PS_minus_iwpPa2tc_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_V476bsxEylk_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_VKuZXhyjtoo_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_Xxtq_plus_Qr5lzI_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_boXwTAWsas0_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_ctqkAfkma_minus_k_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_hKvvpNeUdKo_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_sZq9oSwMGSs_nid_no_patch(); -int APS5_VABI freetypeLegacyUnknown_suWwpZzQl00_nid_no_patch(); +int APS5_VABI freetypeLegacyGetBDFProperty_nid_no_patch(); +int APS5_VABI freetypeLegacyBitmapBlend_nid_no_patch(); +int APS5_VABI freetypeLegacyFtcManagerNew_nid_no_patch(); +int APS5_VABI freetypeLegacyStreamOpenGzip_nid_no_patch(); +int APS5_VABI freetypeLegacyStreamOpenBzip2_nid_no_patch(); +int APS5_VABI freetypeLegacyGetMMWeightVector_nid_no_patch(); +int APS5_VABI freetypeLegacyGetBDFCharsetID_nid_no_patch(); +int APS5_VABI freetypeLegacyGetSfntLangTag_nid_no_patch(); +int APS5_VABI freetypeLegacyPaletteSetForegroundColor_nid_no_patch(); +int APS5_VABI freetypeLegacySetMMWeightVector_nid_no_patch(); +int APS5_VABI freetypeLegacyGetMMBlendCoordinates_nid_no_patch(); +int APS5_VABI freetypeLegacySetNamedInstance_nid_no_patch(); +int APS5_VABI freetypeLegacyFaceProperties_nid_no_patch(); +int APS5_VABI freetypeLegacyLibrarySetLcdGeometry_nid_no_patch(); +int APS5_VABI freetypeLegacyErrorString_nid_no_patch(); +int APS5_VABI freetypeLegacyStreamOpenLZW_nid_no_patch(); +int APS5_VABI freetypeLegacyGzipUncompress_nid_no_patch(); +int APS5_VABI freetypeLegacyNewGlyph_nid_no_patch(); +int APS5_VABI freetypeLegacyGetWinFNTHeader_nid_no_patch(); } diff --git a/core/libs/prx/libkernel/Aio/src/Aio.cpp b/core/libs/prx/libkernel/Aio/src/Aio.cpp index e228a480e5..90b425bcc1 100644 --- a/core/libs/prx/libkernel/Aio/src/Aio.cpp +++ b/core/libs/prx/libkernel/Aio/src/Aio.cpp @@ -252,6 +252,28 @@ int ValidateIds(const std::int32_t* id, std::int32_t num, const void* out) { return 0; } +int CopyStateOut(std::int32_t* out, std::int32_t state) { + return GuestCopyOut_nid_no_patch(out, &state, sizeof(state)) ? 0 : SCE_KERNEL_ERROR_EFAULT; +} + +int ApplyToIds(const std::int32_t* guestIds, std::int32_t num, std::int32_t* out, const char* function, bool requireFinished, + std::int32_t (*operation)(std::int32_t)) { + const int error = ValidateIds(guestIds, num, out); + if (error != 0) return error; + const std::size_t bytes = static_cast(num) * sizeof(std::int32_t); + std::array ids{}; + if (!GuestCopyIn_nid_no_patch(ids.data(), guestIds, bytes)) return SCE_KERNEL_ERROR_EFAULT; + std::array states{}; + { + std::lock_guard lock(g_mutex); + if (requireFinished) { + for (std::int32_t i = 0; i < num; ++i) RequireFinishedLocked(ids[i], function); + } + for (std::int32_t i = 0; i < num; ++i) states[i] = operation(ids[i]); + } + return GuestCopyOut_nid_no_patch(out, states.data(), bytes) ? 0 : SCE_KERNEL_ERROR_EFAULT; +} + } extern "C" { @@ -271,36 +293,32 @@ void AioAdvanceIdentityForTest_nid_no_patch(std::int64_t next) { int APS5_VABI sceKernelAioDeleteRequest(int32_t id, int32_t* ret) { if (ret == nullptr) return SCE_KERNEL_ERROR_EFAULT; - std::lock_guard lock(g_mutex); - RequireFinishedLocked(id, __func__); - *ret = DeleteLocked(id); - return 0; + std::int32_t result; + { + std::lock_guard lock(g_mutex); + RequireFinishedLocked(id, __func__); + result = DeleteLocked(id); + } + return CopyStateOut(ret, result); } int APS5_VABI sceKernelAioDeleteRequests(int32_t* id, int32_t num, int32_t* ret) { - const int error = ValidateIds(id, num, ret); - if (error != 0) return error; - std::lock_guard lock(g_mutex); - for (int32_t i = 0; i < num; ++i) RequireFinishedLocked(id[i], __func__); - for (int32_t i = 0; i < num; ++i) ret[i] = DeleteLocked(id[i]); - return 0; + return ApplyToIds(id, num, ret, __func__, true, DeleteLocked); } int APS5_VABI sceKernelAioCancelRequest(int32_t id, int32_t* state) { if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT; - std::lock_guard lock(g_mutex); - RequireFinishedLocked(id, __func__); - *state = CancelLocked(id); - return 0; + std::int32_t result; + { + std::lock_guard lock(g_mutex); + RequireFinishedLocked(id, __func__); + result = CancelLocked(id); + } + return CopyStateOut(state, result); } int APS5_VABI sceKernelAioCancelRequests(int32_t* id, int32_t num, int32_t* state) { - const int error = ValidateIds(id, num, state); - if (error != 0) return error; - std::lock_guard lock(g_mutex); - for (int32_t i = 0; i < num; ++i) RequireFinishedLocked(id[i], __func__); - for (int32_t i = 0; i < num; ++i) state[i] = CancelLocked(id[i]); - return 0; + return ApplyToIds(id, num, state, __func__, true, CancelLocked); } int APS5_VABI sceKernelAioInitializeImpl(void* param, int32_t size) { @@ -349,9 +367,12 @@ int APS5_VABI sceKernelAioSubmitWriteCommandsMultiple(KernelAioRwRequest* req, i int APS5_VABI sceKernelAioPollRequest(int32_t id, int32_t* state) { if (state == nullptr) return SCE_KERNEL_ERROR_EFAULT; - std::lock_guard lock(g_mutex); - *state = ReportLocked(id); - return 0; + std::int32_t result; + { + std::lock_guard lock(g_mutex); + result = ReportLocked(id); + } + return CopyStateOut(state, result); } int APS5_VABI sceKernelAioWaitRequests(int32_t* id, int32_t num, int32_t* state, uint32_t mode, uint32_t* usec) { @@ -423,11 +444,7 @@ 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); - if (error != 0) return error; - std::lock_guard lock(g_mutex); - for (int32_t i = 0; i < num; ++i) state[i] = ReportLocked(id[i]); - return 0; + return ApplyToIds(id, num, state, __func__, false, ReportLocked); } } diff --git a/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp b/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp index bae222a7c4..270a4d24c2 100644 --- a/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp +++ b/core/libs/prx/libkernel/AppMetadata/src/AppMetadata.cpp @@ -56,8 +56,26 @@ void ensureTitleLoaded() { g_paramSfoAttribute = parsed.paramSfoAttribute; std::memcpy(g_userDefinedParams, parsed.userDefinedParams, sizeof(g_userDefinedParams)); g_titleLoaded = true; + if (g_titleId[0] != '\0') { + const auto app0 = ResolvePath_nid_no_patch("/app0"); + AddPathAlias_nid_no_patch((std::string("/mnt/sandbox/") + g_titleId + "_000/app0").c_str(), app0.string().c_str()); + } } +const bool g_sandboxApp0Mounted = [] { + try { + const auto run = std::filesystem::current_path(); + const auto metadata = run / "app0" / "sce_sys"; + if (!std::filesystem::exists(metadata / "param.json") && !std::filesystem::exists(metadata / "param.sfo")) return false; + const auto parsed = parseParams(metadata / "param.json", metadata / "param.sfo"); + if (parsed.titleId.empty()) return false; + AddPathAlias_nid_no_patch((std::string("/mnt/sandbox/") + parsed.titleId + "_000/app0").c_str(), (run / "app0").string().c_str()); + return true; + } catch (const std::exception&) { + return false; + } +}(); + const bool g_downloadDataMounted = [] { try { const auto run = std::filesystem::current_path(); diff --git a/core/libs/prx/libkernel/Apr/src/Apr.cpp b/core/libs/prx/libkernel/Apr/src/Apr.cpp index 0ba7bbafeb..e79d6c5e09 100644 --- a/core/libs/prx/libkernel/Apr/src/Apr.cpp +++ b/core/libs/prx/libkernel/Apr/src/Apr.cpp @@ -41,6 +41,7 @@ namespace { constexpr int GUEST_ENOENT = 2; +constexpr int GUEST_EFAULT = 14; constexpr int GUEST_EINVAL = 22; struct AprFile { @@ -416,7 +417,7 @@ void _requirePrt(AmmState& state, std::uint64_t address, std::uint64_t size) { void _zeroPrt(std::uint64_t address, std::uint64_t size) { constexpr int GpuRead = 0x10; void* target = reinterpret_cast(address); - if (DoMapAnon(&target, size, GpuRead, GuestMapFixed) != 0) throw std::runtime_error("AMM: cannot map the unbacked PRT pages"); + if (DoMapZero(&target, size, GpuRead, GuestMapFixed) != 0) throw std::runtime_error("AMM: cannot map the unbacked PRT pages"); } void _ammUnmap(const Apr::AmmUnmapCommand& command) { @@ -861,13 +862,16 @@ int APS5_VABI sceKernelAprResolveFilepathsWithPrefixToIds(const char* prefix, co int APS5_VABI sceKernelAprGetFileSize(uint32_t id, uint64_t* size) { if (!size) return _fail(GUEST_EINVAL); - *size = _file(id).size; + const std::uint64_t bytes = _file(id).size; + if (!GuestCopyOut_nid_no_patch(size, &bytes, sizeof(bytes))) return _fail(GUEST_EFAULT); return 0; } int APS5_VABI sceKernelAprGetFileStat(uint32_t id, FileStat* stat) { if (!stat) return _fail(GUEST_EINVAL); - File::FillFileStat(_file(id).path, stat); + FileStat result{}; + File::FillFileStat(_file(id).path, &result); + if (!GuestCopyOut_nid_no_patch(stat, &result, sizeof(result))) return _fail(GUEST_EFAULT); return 0; } diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 79f5520b73..61052c0814 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Equeue/src/Kqueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/File/src/DirectoryDescriptor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/File/src/FileLock.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/File/src/FilesystemForwards.cpp ${CMAKE_CURRENT_SOURCE_DIR}/File/src/NativeStat.cpp ${CMAKE_CURRENT_SOURCE_DIR}/File/src/Terminal.cpp ${CMAKE_CURRENT_SOURCE_DIR}/File/src/Open.cpp @@ -81,6 +82,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Error.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/ThermalSensors.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Signals.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/System/src/SigvecRegistration.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/SignalSets.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Notification.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/UserContext.cpp @@ -121,6 +123,15 @@ if(BUILD_TESTING) set_tests_properties(kernel_error PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_direct_memory_for_app_tests ${LIBS_INCLUDE_DIR}/tests/GuestDirectMemoryForApp.cpp) + target_include_directories(guest_direct_memory_for_app_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_direct_memory_for_app_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_direct_memory_for_app_tests) + add_test(NAME guest_direct_memory_for_app COMMAND guest_direct_memory_for_app_tests) + if(WIN32) + set_tests_properties(guest_direct_memory_for_app PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_apr_read_tests ${LIBS_INCLUDE_DIR}/tests/GuestAprRead.cpp) target_include_directories(guest_apr_read_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_apr_read_tests PRIVATE libkernel libc) @@ -218,6 +229,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_sem_getvalue_uninit_tests ${LIBS_INCLUDE_DIR}/tests/GuestSemGetvalueUninit.cpp) + target_include_directories(guest_sem_getvalue_uninit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sem_getvalue_uninit_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_sem_getvalue_uninit_tests) + add_test(NAME guest_sem_getvalue_uninit COMMAND guest_sem_getvalue_uninit_tests) + set_tests_properties(guest_sem_getvalue_uninit PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_sem_getvalue_uninit 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) @@ -600,6 +621,13 @@ if(BUILD_TESTING) set_tests_properties(guest_kernel_is_stack PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_test_executable(guest_main_stack_tests ${LIBS_INCLUDE_DIR}/tests/GuestMainStack.cpp) + target_include_directories(guest_main_stack_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_main_stack_tests PRIVATE libkernel libc) + add_test(NAME guest_main_stack COMMAND guest_main_stack_tests) + set_tests_properties(guest_main_stack PROPERTIES TIMEOUT 20) + endif() if(APPLE) add_test_executable(guest_main_stack_move_tests ${LIBS_INCLUDE_DIR}/tests/GuestMainStackMove.cpp) target_include_directories(guest_main_stack_move_tests PRIVATE ${LIBS_INCLUDE_DIR}) @@ -806,6 +834,46 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_join_detach PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pthread_handle_after_exit_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadHandleAfterExit.cpp) + target_include_directories(guest_pthread_handle_after_exit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_handle_after_exit_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_handle_after_exit_tests) + add_test(NAME guest_pthread_handle_after_exit COMMAND guest_pthread_handle_after_exit_tests) + set_tests_properties(guest_pthread_handle_after_exit PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_handle_after_exit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_pthread_detach_attr_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadDetachAttr.cpp) + target_include_directories(guest_pthread_detach_attr_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_detach_attr_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_detach_attr_tests) + add_test(NAME guest_pthread_detach_attr COMMAND guest_pthread_detach_attr_tests) + set_tests_properties(guest_pthread_detach_attr PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_detach_attr PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_pthread_released_handle_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadReleasedHandle.cpp) + target_include_directories(guest_pthread_released_handle_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_released_handle_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_released_handle_tests) + add_test(NAME guest_pthread_released_handle COMMAND guest_pthread_released_handle_tests) + set_tests_properties(guest_pthread_released_handle PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_released_handle PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_pthread_final_release_race_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadFinalReleaseRace.cpp) + target_include_directories(guest_pthread_final_release_race_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_final_release_race_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_final_release_race_tests) + add_test(NAME guest_pthread_final_release_race COMMAND guest_pthread_final_release_race_tests) + set_tests_properties(guest_pthread_final_release_race PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_final_release_race PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_pthread_attr_errors_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadAttrErrors.cpp) target_include_directories(guest_pthread_attr_errors_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_pthread_attr_errors_tests PRIVATE libkernel libc) @@ -1033,6 +1101,16 @@ if(BUILD_TESTING) set_tests_properties(guest_pthread_cancel_recovery guest_pthread_cancel_recovery_coarse PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_semaphore_handoff_tests ${LIBS_INCLUDE_DIR}/tests/GuestSemaphoreHandoff.cpp) + target_include_directories(guest_semaphore_handoff_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_semaphore_handoff_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_semaphore_handoff_tests) + add_test(NAME guest_semaphore_handoff COMMAND guest_semaphore_handoff_tests) + set_tests_properties(guest_semaphore_handoff PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_semaphore_handoff PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_semaphore_tests ${LIBS_INCLUDE_DIR}/tests/GuestSemaphore.cpp) target_include_directories(guest_semaphore_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_semaphore_tests PRIVATE libkernel libc) @@ -1108,6 +1186,13 @@ if(BUILD_TESTING) "PATH=path_list_prepend:$") endif() add_test_executable(guest_pthread_second_join_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadSecondJoin.cpp) + if(APPLE) + add_test_executable(guest_pthread_host_fault_mask_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadHostFaultMask.cpp) + target_include_directories(guest_pthread_host_fault_mask_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_host_fault_mask_tests PRIVATE libkernel libc) + add_test(NAME guest_pthread_host_fault_mask COMMAND guest_pthread_host_fault_mask_tests) + set_tests_properties(guest_pthread_host_fault_mask PROPERTIES TIMEOUT 20) + endif() target_include_directories(guest_pthread_second_join_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_pthread_second_join_tests PRIVATE libkernel libc) configure_windows_unwind(guest_pthread_second_join_tests) @@ -1196,8 +1281,9 @@ if(BUILD_TESTING) add_test(NAME guest_itimer_pending_default COMMAND guest_itimer_tests pending-default) set_tests_properties(guest_itimer_pending_default PROPERTIES TIMEOUT 20 PASS_REGULAR_EXPRESSION "SIGALRM: a pending SIGALRM was unblocked with the default disposition; the process terminates") + add_test(NAME guest_itimer_short_expiry COMMAND guest_itimer_tests short-expiry) if(WIN32) - set_tests_properties(guest_itimer guest_itimer_pending_default PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_itimer guest_itimer_pending_default guest_itimer_short_expiry PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test_executable(guest_parent_process_tests ${LIBS_INCLUDE_DIR}/tests/GuestParentProcess.cpp) @@ -1634,6 +1720,15 @@ if(BUILD_TESTING) set_tests_properties(guest_thread_sanitizer PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$;PATH=path_list_prepend:$") endif() + add_test_executable(guest_sanitizer_replace_tables_tests ${LIBS_INCLUDE_DIR}/tests/GuestSanitizerReplaceTables.cpp) + target_include_directories(guest_sanitizer_replace_tables_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_sanitizer_replace_tables_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_sanitizer_replace_tables_tests) + add_test(NAME guest_sanitizer_replace_tables COMMAND guest_sanitizer_replace_tables_tests) + if(WIN32) + set_tests_properties(guest_sanitizer_replace_tables PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_system_command_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemCommand.cpp) target_include_directories(guest_system_command_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_system_command_tests PRIVATE libkernel libc) @@ -1785,6 +1880,13 @@ if(BUILD_TESTING) set_tests_properties(guest_socket_pair guest_recvmsg_memory PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + if(NOT WIN32) + add_test_executable(guest_socket_rights_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocketRights.cpp) + target_include_directories(guest_socket_rights_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_socket_rights_tests PRIVATE libkernel libc) + add_test(NAME guest_socket_rights COMMAND guest_socket_rights_tests) + set_tests_properties(guest_socket_rights PROPERTIES TIMEOUT 20) + endif() add_test_executable(guest_kernel_app_archive_tests ${LIBS_INCLUDE_DIR}/tests/GuestKernelAppArchive.cpp) target_include_directories(guest_kernel_app_archive_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_kernel_app_archive_tests PRIVATE libSceLibcInternal libkernel libc zarchive) @@ -2169,6 +2271,16 @@ if(BUILD_TESTING) set_tests_properties(guest_posix_specific PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pipe_fault_tests ${LIBS_INCLUDE_DIR}/tests/GuestPipeFault.cpp) + target_include_directories(guest_pipe_fault_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pipe_fault_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pipe_fault_tests) + add_test(NAME guest_pipe_fault COMMAND guest_pipe_fault_tests) + set_tests_properties(guest_pipe_fault PROPERTIES TIMEOUT 15) + if(WIN32) + set_tests_properties(guest_pipe_fault PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_rename_dot_tests ${LIBS_INCLUDE_DIR}/tests/GuestRenameDot.cpp) target_include_directories(guest_rename_dot_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_rename_dot_tests PRIVATE libkernel libc) @@ -2189,6 +2301,16 @@ if(BUILD_TESTING) set_tests_properties(guest_truncate_extend PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_posix_exit_tests ${LIBS_INCLUDE_DIR}/tests/GuestPosixExit.cpp) + target_include_directories(guest_posix_exit_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_posix_exit_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_posix_exit_tests) + add_test(NAME guest_posix_exit COMMAND guest_posix_exit_tests) + set_tests_properties(guest_posix_exit PROPERTIES TIMEOUT 30) + if(WIN32) + set_tests_properties(guest_posix_exit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_fstatfs_tests ${LIBS_INCLUDE_DIR}/tests/GuestFstatfs.cpp) target_include_directories(guest_fstatfs_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_fstatfs_tests PRIVATE libkernel libc) @@ -2231,8 +2353,14 @@ if(BUILD_TESTING) set_tests_properties(guest_kernel_fcntl_sync PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_stream_close_tests ${LIBS_INCLUDE_DIR}/tests/GuestStreamClose.cpp) + target_include_directories(guest_stream_close_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_stream_close_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_stream_close_tests) + add_test(NAME guest_stream_close COMMAND guest_stream_close_tests) + set_tests_properties(guest_stream_close PROPERTIES TIMEOUT 15) if(WIN32) - set_tests_properties(guest_kernel_fcntl guest_append_writes PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_kernel_fcntl guest_append_writes guest_stream_close PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") add_test_executable(guest_file_lock_tests ${LIBS_INCLUDE_DIR}/tests/GuestFileLock.cpp) target_include_directories(guest_file_lock_tests PRIVATE ${LIBS_INCLUDE_DIR}) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 7eb7903cd0..c562b3872d 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -36,6 +36,13 @@ #include #include #include +#elif defined(__APPLE__) +#include "prx/libc/include/specifics/linux/ElfTypes.hpp" +#include +#include +#include +#include +#include #else #include #include "prx/libc/include/WindowsDescriptor.hpp" @@ -241,6 +248,31 @@ void* MapAtOrAbove(std::uintptr_t start, size_t len, int prot, size_t alignment) } throw std::runtime_error("Hinted mmap kept racing with other mappings"); } +#elif defined(__APPLE__) +void* MapAtOrAbove(std::uintptr_t start, size_t len, int prot, size_t alignment) { + auto address = static_cast(start); + const auto result = mach_vm_map(mach_task_self(), &address, len, alignment - 1, VM_FLAGS_ANYWHERE, MEMORY_OBJECT_NULL, 0, FALSE, prot, VM_PROT_ALL, VM_INHERIT_DEFAULT); + if (result != KERN_SUCCESS) throw std::runtime_error(std::string("Hinted mach_vm_map failed: ") + mach_error_string(result)); + if (address < start) { + mach_vm_deallocate(mach_task_self(), address, len); + throw std::runtime_error("No free range above the mapping address hint"); + } + return reinterpret_cast(address); +} + +void* MapFixedNoReplace(void* addr, size_t len, int prot) { + auto address = reinterpret_cast(addr); + const auto result = mach_vm_map(mach_task_self(), &address, len, 0, VM_FLAGS_FIXED, MEMORY_OBJECT_NULL, 0, FALSE, prot, VM_PROT_ALL, VM_INHERIT_DEFAULT); + if (result == KERN_NO_SPACE) { + errno = EEXIST; + return MAP_FAILED; + } + if (result != KERN_SUCCESS) { + errno = ENOMEM; + return MAP_FAILED; + } + return reinterpret_cast(address); +} #endif void ValidateLength(size_t len) { @@ -398,6 +430,11 @@ public: std::snprintf(message, sizeof(message), "create direct memory backing of 0x%llx bytes (%llu MiB)", static_cast(size), static_cast((size + 0xFFFFF) >> 20)); throw std::system_error(error, std::system_category(), message); } +#elif defined(__APPLE__) + static_cast(start); + memory_object_size_t size = bytes; + const auto result = mach_make_memory_entry_64(mach_task_self(), &size, 0, MAP_MEM_NAMED_CREATE | VM_PROT_ALL, &entry, MACH_PORT_NULL); + if (result != KERN_SUCCESS) throw std::runtime_error(std::string("create direct memory backing: ") + mach_error_string(result)); #else file = PhysicalFile(); fileOffset = start; @@ -430,6 +467,8 @@ public: } else if (!GuestArena::GuestArenaForgetReusableSection_nid_no_patch(section) || !SectionPool::Get().Keep(section, bytes)) { CloseHandle(section); } +#elif defined(__APPLE__) + mach_port_deallocate(mach_task_self(), entry); #else if (hostWriteView != nullptr) ::munmap(hostWriteView, bytes); #endif @@ -445,6 +484,10 @@ public: GuestArena::GuestArenaCommit_nid_postfix(reinterpret_cast(address), bytes, 0x04, PS5_PAGE_SIZE); #elif defined(_WIN32) GuestArena::GuestArenaMapDirect_nid_postfix(reinterpret_cast(address), bytes, section, offset, WinProtFromPosix(protection)); +#elif defined(__APPLE__) + auto target = static_cast(address); + const auto result = mach_vm_map(mach_task_self(), &target, bytes, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, entry, offset, FALSE, protection, VM_PROT_ALL, VM_INHERIT_SHARE); + if (result != KERN_SUCCESS) throw std::runtime_error(std::string("map direct memory backing: ") + mach_error_string(result)); #else if (::mmap(reinterpret_cast(address), bytes, protection, MAP_SHARED | MAP_FIXED, file, static_cast(fileOffset + offset)) == MAP_FAILED) throw std::system_error(errno, std::generic_category(), "map direct memory backing"); #endif @@ -456,6 +499,8 @@ private: #if defined(__EMSCRIPTEN__) #elif defined(_WIN32) HANDLE section = nullptr; +#elif defined(__APPLE__) + mach_port_t entry = MACH_PORT_NULL; #else int file = -1; void* hostWriteView = nullptr; @@ -644,7 +689,7 @@ void ValidatePhysicalRange(std::uint64_t phys, std::size_t len) { throw std::invalid_argument(message); } -#if defined(__linux__) +#if defined(__linux__) || defined(__APPLE__) void WatchMapping(std::uintptr_t address, std::size_t len) { GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(reinterpret_cast(address), len); const auto end = address + len; @@ -689,6 +734,39 @@ bool SharedBacking(std::uintptr_t address, std::size_t bytes, int* file, std::ui return true; } +bool SharedBackingRuns(std::uintptr_t address, std::size_t bytes, int* file, GuestArena::SharedBackingRun* runs, std::size_t capacity, std::size_t* count) { + std::lock_guard lock(g_directLock); + *count = 0; + const auto next = g_directMappings.upper_bound(address); + if (bytes == 0 || bytes > std::numeric_limits::max() - address || next == g_directMappings.begin()) return false; + auto it = std::prev(next); + if (address >= it->second.end) return false; + const int backingFile = it->second.backing->File(); + const auto end = address + bytes; + std::size_t filled = 0; + for (auto cursor = address; cursor < end;) { + if (it == g_directMappings.end() || it->first > cursor || it->second.backing->File() != backingFile) return false; + const auto phys = it->second.phys + (cursor - it->first); + const auto page = g_physPages.find(phys - phys % PS5_PAGE_SIZE); + if (page == g_physPages.end() || page->second.backing != it->second.backing) return false; + const auto stop = std::min(end, static_cast(it->second.end)); + const auto offset = it->second.backing->FileOffset() + page->second.offset + phys % PS5_PAGE_SIZE; + if (filled != 0 && runs[filled - 1].offset + runs[filled - 1].bytes == offset) { + runs[filled - 1].bytes += stop - cursor; + } else { + if (filled == capacity) return false; + runs[filled++] = GuestArena::SharedBackingRun{offset, static_cast(stop - cursor)}; + } + cursor = stop; + ++it; + } + const int duplicate = fcntl(backingFile, F_DUPFD_CLOEXEC, 0); + if (duplicate < 0) throw std::system_error(errno, std::generic_category(), "duplicate direct memory backing"); + *file = duplicate; + *count = filled; + return true; +} + bool WriteSharedBacking(std::uintptr_t address, const void* source, std::size_t bytes) { if (bytes == 0 || bytes > std::numeric_limits::max() - address) return false; std::lock_guard lock(g_directLock); @@ -738,7 +816,7 @@ void AddMapping(std::uintptr_t address, std::size_t len, std::uint64_t phys, int if (records) InsertMapping(address + (cursor - phys), DirectMapping{address + (cursor - phys) + bytes, cursor, it->second.backing->MemoryType(), nativeProt, it->second.backing}); cursor = runEnd; } -#if defined(__linux__) +#if defined(__linux__) || defined(__APPLE__) WatchMapping(address, len); #endif } @@ -775,7 +853,7 @@ void RequireAliasesUnpinned(GuestAllocations::Mutation& mutation, std::uint64_t #endif } -bool RemapFixedIntoRegistered(GuestAllocations::Mutation& mutation, void* addr, size_t len, int prot, int flags, int64_t physStart = -1) { +bool RemapFixedIntoRegistered(GuestAllocations::Mutation& mutation, void* addr, size_t len, int prot, int flags, int64_t physStart = -1, [[maybe_unused]] bool zero = false) { constexpr int GuestMapFixed = 0x10; constexpr int GuestMapNoOverwrite = 0x80; if (addr == nullptr || (flags & GuestMapFixed) == 0 || (flags & GuestMapNoOverwrite) != 0 || !mutation.Covers(addr, len)) return false; @@ -798,8 +876,11 @@ bool RemapFixedIntoRegistered(GuestAllocations::Mutation& mutation, void* addr, } else { EraseMappings(address, address + len); #ifdef _WIN32 - GuestArena::GuestArenaReset_nid_postfix(addr, len); - if (nativeProtection != PROT_NONE) CommitArenaRange(addr, len, WinProtFromPosix(nativeProtection)); + if (zero) GuestArena::GuestArenaMapZero_nid_postfix(addr, len); + else { + GuestArena::GuestArenaReset_nid_postfix(addr, len); + if (nativeProtection != PROT_NONE) CommitArenaRange(addr, len, WinProtFromPosix(nativeProtection)); + } #elif defined(__EMSCRIPTEN__) GuestArena::GuestArenaReset_nid_postfix(addr, len); GuestArena::GuestArenaCommit_nid_postfix(addr, len, 0x04, PS5_PAGE_SIZE); @@ -813,7 +894,7 @@ bool RemapFixedIntoRegistered(GuestAllocations::Mutation& mutation, void* addr, return true; } -#if defined(__linux__) || defined(__EMSCRIPTEN__) +#if defined(__linux__) || defined(__EMSCRIPTEN__) || defined(__APPLE__) bool ArenaAvailable() { return GuestArena::GuestArenaAvailable_nid_postfix(); } void* ArenaCommit(void* addr, size_t len, int prot) { @@ -832,7 +913,7 @@ void ArenaForget(void* addr, size_t len) { #endif void Unmap(void* addr, size_t len) { -#if defined(__linux__) || defined(__EMSCRIPTEN__) +#if defined(__linux__) || defined(__EMSCRIPTEN__) || defined(__APPLE__) if (ArenaAvailable() && GuestArena::GuestArenaContains_nid_postfix(addr, len)) GuestArena::GuestArenaReset_nid_postfix(addr, len); else if (munmap(addr, len) != 0) throw std::system_error(errno, std::generic_category(), "munmap failed"); @@ -884,7 +965,7 @@ void* MapPlaced(void* addr, size_t len, int prot, int flags, size_t alignment) { } } #endif -#if defined(__linux__) || defined(__EMSCRIPTEN__) +#if defined(__linux__) || defined(__EMSCRIPTEN__) || defined(__APPLE__) if (ArenaAvailable() && !GuestArena::GuestArenaContains_nid_postfix(addr, len)) throw std::invalid_argument("fixed mapping is outside the guest address space arena"); if (ArenaAvailable()) { @@ -893,11 +974,15 @@ void* MapPlaced(void* addr, size_t len, int prot, int flags, size_t alignment) { GuestArena::GuestArenaRelease_nid_postfix(addr, len); throw std::system_error(errno, std::generic_category(), "Arena mapping failed"); } - const int placement = (flags & GuestMapNoOverwrite) != 0 ? MAP_FIXED_NOREPLACE : MAP_FIXED; -#else - const int placement = MAP_FIXED; #endif +#if defined(__linux__) || defined(__EMSCRIPTEN__) + const int placement = (flags & GuestMapNoOverwrite) != 0 ? MAP_FIXED_NOREPLACE : MAP_FIXED; void* result = mmap(addr, len, prot, MAP_PRIVATE | MAP_ANONYMOUS | placement, -1, 0); +#elif defined(__APPLE__) + void* result = (flags & GuestMapNoOverwrite) != 0 ? MapFixedNoReplace(addr, len, prot) : mmap(addr, len, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); +#else + void* result = mmap(addr, len, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); +#endif if (result == MAP_FAILED) { // return SCE_KERNEL_ERROR_ENOMEM; throw std::system_error(errno, std::generic_category(), "Fixed mmap failed"); @@ -905,7 +990,7 @@ void* MapPlaced(void* addr, size_t len, int prot, int flags, size_t alignment) { return result; } if (addr) { -#if defined(__linux__) || defined(__EMSCRIPTEN__) +#if defined(__linux__) || defined(__EMSCRIPTEN__) || defined(__APPLE__) if (ArenaAvailable()) { void* placed = GuestArena::GuestArenaAllocateFromHint_nid_postfix( reinterpret_cast(addr), len, alignment); @@ -925,7 +1010,7 @@ void* MapPlaced(void* addr, size_t len, int prot, int flags, size_t alignment) { #ifdef _WIN32 return mmap_aligned(len, prot, alignment); #endif -#if defined(__linux__) || defined(__EMSCRIPTEN__) +#if defined(__linux__) || defined(__EMSCRIPTEN__) || defined(__APPLE__) if (ArenaAvailable()) { void* placed = GuestArena::GuestArenaAllocate_nid_postfix(len, alignment); if (void* committed = ArenaCommit(placed, len, prot); committed != MAP_FAILED) return committed; @@ -1009,7 +1094,7 @@ void RecordProtection(const void* addr, size_t len, int prot) { void* MapAligned(void* addr, size_t len, int prot, int flags, size_t alignment) { void* mapped = MapPlaced(addr, len, prot, flags, alignment); -#if defined(__linux__) +#if defined(__linux__) || defined(__APPLE__) GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(mapped, len); #endif return mapped; @@ -1044,7 +1129,7 @@ void UnmapRegistered(GuestAllocations::Mutation& mutation, void* addr, size_t le auto* pieceAddress = const_cast(piece); std::lock_guard lock(g_directLock); EraseMappings(reinterpret_cast(piece), reinterpret_cast(piece) + pieceBytes); -#if defined(__linux__) || defined(__EMSCRIPTEN__) +#if defined(__linux__) || defined(__EMSCRIPTEN__) || defined(__APPLE__) Unmap(pieceAddress, pieceBytes); #else if (KernelArena::Get().Contains(pieceAddress, pieceBytes)) munmap(pieceAddress, pieceBytes); @@ -1114,6 +1199,35 @@ int DoMapDirect(void** addr, size_t len, int prot, int flags, int64_t physStart, return 0; } +static int MapAnonymous(void** addr, size_t len, int prot, int flags, size_t alignment, bool exclusive, bool zero) { + GuestAllocations::Mutation mutation; + if (exclusive && mutation.Overlaps(*addr, len)) return SCE_KERNEL_ERROR_ENOMEM; + flags = PlaceHintInReservation(*addr, len, flags); + if (FixedNoOverwriteConflict(mutation, *addr, len, flags)) return SCE_KERNEL_ERROR_ENOMEM; + if (RemapFixedIntoRegistered(mutation, *addr, len, prot, flags, -1, zero)) { + EraseReservations(*addr, len); + RecordProtection(*addr, len, prot); + return 0; + } + ReplaceFixedOverlap(mutation, *addr, len, flags); + void* mapped = MapAligned(*addr, len, zero ? PROT_NONE : LinuxProtFromSce(prot), flags, alignment); + try { +#ifdef _WIN32 + if (zero) GuestArena::GuestArenaMapZero_nid_postfix(mapped, len); +#endif + mutation.Add(mapped, len, (prot & 3) != 0, (prot & 2) != 0, (prot & GuestProtGpuReadWrite) != 0); + if ((prot & GuestProtGpuReadWrite) != 0) mutation.NoteGpuMapping(mapped, len); + } catch (...) { + Unmap(mapped, len); + throw; + } + *addr = mapped; + EraseReservations(mapped, len); + RecordProtection(mapped, len, prot); + Trace("map anon %p+0x%zx prot=0x%x flags=0x%x", mapped, len, prot, flags); + return 0; +} + int DoMapAnon(void** addr, size_t len, int prot, int flags, size_t alignment, bool exclusive, [[maybe_unused]] bool allowStackReplacement) { ValidateOutput(addr); if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0) return SCE_KERNEL_ERROR_EINVAL; @@ -1164,29 +1278,18 @@ int DoMapAnon(void** addr, size_t len, int prot, int flags, size_t alignment, bo return result; } #endif - GuestAllocations::Mutation mutation; - if (exclusive && mutation.Overlaps(*addr, len)) return SCE_KERNEL_ERROR_ENOMEM; - flags = PlaceHintInReservation(*addr, len, flags); - if (FixedNoOverwriteConflict(mutation, *addr, len, flags)) return SCE_KERNEL_ERROR_ENOMEM; - if (RemapFixedIntoRegistered(mutation, *addr, len, prot, flags)) { - EraseReservations(*addr, len); - RecordProtection(*addr, len, prot); - return 0; - } - ReplaceFixedOverlap(mutation, *addr, len, flags); - void* mapped = MapAligned(*addr, len, LinuxProtFromSce(prot), flags, alignment); - try { - mutation.Add(mapped, len, (prot & 3) != 0, (prot & 2) != 0, (prot & GuestProtGpuReadWrite) != 0); - if ((prot & GuestProtGpuReadWrite) != 0) mutation.NoteGpuMapping(mapped, len); - } catch (...) { - Unmap(mapped, len); - throw; - } - *addr = mapped; - EraseReservations(mapped, len); - RecordProtection(mapped, len, prot); - Trace("map anon %p+0x%zx prot=0x%x flags=0x%x", mapped, len, prot, flags); - return 0; + return MapAnonymous(addr, len, prot, flags, alignment, exclusive, false); +} + +int DoMapZero(void** addr, size_t len, int prot, int flags) { +#ifdef _WIN32 + ValidateOutput(addr); + if (len == 0 || (len & (PS5_PAGE_SIZE - 1)) != 0) return SCE_KERNEL_ERROR_EINVAL; + if (LinuxProtFromSce(prot) != PROT_READ) throw std::invalid_argument("Zero pages are only readable: " + std::to_string(prot)); + return MapAnonymous(addr, len, prot, flags, PS5_PAGE_SIZE, false, true); +#else + return DoMapAnon(addr, len, prot, flags); +#endif } int DoMapFileBacked_nid_no_patch(void** addr, size_t len, int prot, int flags, int descriptor, std::uint64_t offset, size_t alignment) { @@ -1344,37 +1447,41 @@ static bool IsGuestModuleImage(const void* base) { return false; } #else -struct GuestImageSearch { - std::uintptr_t address = 0; +struct GuestImageOverlap { std::uintptr_t base = 0; - std::uintptr_t pageSize = 0; bool guestModule = false; - bool first = true; - bool main = false; - bool found = false; + bool containsStart = false; }; -static int FindImageContaining(dl_phdr_info* image, std::size_t, void* data) { +struct GuestImageSearch { + std::uintptr_t start = 0; + std::uintptr_t end = 0; + std::uintptr_t pageSize = 0; + bool first = true; + std::vector images; +}; + +static int FindImagesOverlapping(dl_phdr_info* image, std::size_t, void* data) { auto& search = *static_cast(data); const bool main = search.first; search.first = false; + GuestImageOverlap overlap{.base = image->dlpi_addr, .guestModule = image->dlpi_name != nullptr && std::string_view(image->dlpi_name).ends_with(".guest.prx")}; + bool overlaps = false; for (int index = 0; index < image->dlpi_phnum; ++index) { const auto& header = image->dlpi_phdr[index]; if (header.p_type != PT_LOAD) continue; const auto start = (image->dlpi_addr + header.p_vaddr) & ~(search.pageSize - 1); const auto end = (image->dlpi_addr + header.p_vaddr + header.p_memsz + search.pageSize - 1) & ~(search.pageSize - 1); - if (search.address < start || search.address >= end) continue; - search.base = image->dlpi_addr; - search.guestModule = image->dlpi_name != nullptr && std::string_view(image->dlpi_name).ends_with(".guest.prx"); - search.main = main; - search.found = true; - return 1; + if (search.end <= start || search.start >= end) continue; + overlaps = true; + if (search.start >= start) overlap.containsStart = true; } + if (overlaps && !main) search.images.push_back(overlap); return 0; } #endif -int DoMprotect(const void* addr, size_t len, int prot) { +int DoMprotect(const void* addr, size_t len, int prot, bool requireMappedStart) { Trace("protect %p+0x%zx prot=0x%x", addr, len, prot); const auto address = reinterpret_cast(addr); constexpr auto pageMask = static_cast(PS5_PAGE_SIZE - 1); @@ -1437,33 +1544,31 @@ int DoMprotect(const void* addr, size_t len, int prot) { } GuestAllocations::Mutation mutation; #ifdef _WIN32 - MEMORY_BASIC_INFORMATION memory{}; - if (VirtualQuery(pointer, &memory, sizeof(memory)) != sizeof(memory)) throw std::runtime_error("Cannot query guest memory protection range"); - if (memory.Type == MEM_IMAGE) { - if (memory.AllocationBase == GetModuleHandleW(nullptr)) mutation.RegisterMainImage(); - else if (IsGuestModuleImage(memory.AllocationBase)) mutation.RegisterImage(memory.AllocationBase); - else throw std::invalid_argument("Memory protection of a foreign image is not supported"); - } for (auto cursor = first; cursor < end;) { MEMORY_BASIC_INFORMATION view{}; if (VirtualQuery(reinterpret_cast(cursor), &view, sizeof(view)) != sizeof(view)) throw std::runtime_error("Cannot query guest memory protection range"); + if (view.Type == MEM_IMAGE) { + if (view.AllocationBase == GetModuleHandleW(nullptr)) mutation.RegisterMainImage(); + else if (IsGuestModuleImage(view.AllocationBase)) mutation.RegisterImage(view.AllocationBase); + else if (cursor == first) throw std::invalid_argument("Memory protection of a foreign image is not supported"); + } if (view.Type == MEM_MAPPED && (nativeProtection & ~PosixRightsFromWin(view.AllocationProtect)) != 0) throw std::system_error(EACCES, std::generic_category(), "mprotect above the maximum protection of a mapped view"); cursor = reinterpret_cast(view.BaseAddress) + view.RegionSize; } #else mutation.RegisterMainImage(); - GuestImageSearch image{.address = address, .pageSize = static_cast(::sysconf(_SC_PAGESIZE))}; - dl_iterate_phdr(FindImageContaining, &image); - if (image.found && !image.main) { - if (!image.guestModule) throw std::invalid_argument("Memory protection of a foreign image is not supported"); - mutation.RegisterImage(reinterpret_cast(image.base)); + GuestImageSearch search{.start = first, .end = end, .pageSize = static_cast(::sysconf(_SC_PAGESIZE))}; + dl_iterate_phdr(FindImagesOverlapping, &search); + for (const auto& image : search.images) { + if (image.guestModule) mutation.RegisterImage(reinterpret_cast(image.base)); + else if (image.containsStart) throw std::invalid_argument("Memory protection of a foreign image is not supported"); } mutation.TrimToRegistered(PS5_PAGE_SIZE, pointer, bytes); #endif std::vector> spans; mutation.ForRegistered(pointer, bytes, [&](const void* span, std::size_t spanBytes) { spans.emplace_back(span, spanBytes); }); - if (spans.empty() || spans.front().first != pointer) return SCE_KERNEL_ERROR_EINVAL; + if (requireMappedStart && (spans.empty() || spans.front().first != pointer)) return SCE_KERNEL_ERROR_EINVAL; for (const auto& [span, spanBytes] : spans) mutation.RequireUnpinned(span, spanBytes); for (const auto& [span, spanBytes] : spans) { mutation.Protect(span, spanBytes, (prot & 3) != 0, (prot & 2) != 0, (prot & GuestProtGpuReadWrite) != 0, [&] { @@ -1479,8 +1584,8 @@ int DoMprotect(const void* addr, size_t len, int prot) { return 0; } -int DoMtypeprotect(const void* addr, size_t len, int type, int prot) { - const int result = DoMprotect(addr, len, prot); +int DoMtypeprotect(const void* addr, size_t len, int type, int prot, bool requireMappedStart) { + const int result = DoMprotect(addr, len, prot, requireMappedStart); if (result != 0) return result; const auto address = reinterpret_cast(addr); constexpr auto pageMask = static_cast(PS5_PAGE_SIZE - 1); @@ -1554,6 +1659,7 @@ void CreateDirectMemoryBacking(int64_t start, size_t len, int memoryType) { static std::once_flag registered; std::call_once(registered, [] { GuestArena::GuestArenaSetSharedBacking_nid_postfix(&SharedBacking); + GuestArena::GuestArenaSetSharedBackingRuns_nid_postfix(&SharedBackingRuns); GuestArena::GuestArenaSetSharedBackingWriter_nid_no_patch(&WriteSharedBacking); }); #endif @@ -1657,6 +1763,21 @@ bool QueryDirectMapping(std::uintptr_t address, std::uintptr_t* start, std::uint return true; } +bool DirectMemoryAliased(std::uintptr_t address, std::size_t bytes) { + const auto end = address + bytes; + std::lock_guard lock(g_directLock); + auto it = g_directMappings.upper_bound(address); + if (it != g_directMappings.begin()) --it; + for (; it != g_directMappings.end() && it->first < end; ++it) { + if (it->second.end <= address) continue; + const auto first = std::max(address, it->first); + const auto last = std::min(end, it->second.end); + const auto phys = it->second.phys + (first - it->first); + if (MappingsSharingPhys(phys, phys + (last - first)).size() > 1) return true; + } + return false; +} + std::vector DirectMemoryViews(int64_t start, size_t len) { const auto first = static_cast(start); const auto last = first + len; diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp index 604e85b34c..2787be97b1 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp @@ -24,6 +24,7 @@ void DirectMemoryFinishRelease(int64_t start, size_t len); void DirectMemoryAbandonRelease(int64_t start, size_t len) noexcept; void CreateDirectMemoryBacking(int64_t start, size_t len, int memoryType); bool QueryDirectMapping(std::uintptr_t address, std::uintptr_t* start, std::uintptr_t* end, std::uint64_t* offset, int* memoryType); +bool DirectMemoryAliased(std::uintptr_t address, std::size_t bytes); void ForgetDirectMemory(int64_t start, size_t len); struct DirectMemoryView { @@ -39,11 +40,12 @@ int DirectMemoryLargestFree(int64_t searchStart, int64_t searchEnd, size_t align size_t DirectMemoryFreeRun(uint64_t offset, uint64_t limit); int DoMapDirect(void** addr, size_t len, int prot, int flags, int64_t physStart, size_t alignment); int DoMapAnon(void** addr, size_t len, int prot, int flags, size_t alignment = PS5_PAGE_SIZE, bool exclusive = false, bool allowStackReplacement = false); +int DoMapZero(void** addr, size_t len, int prot, int flags); int DoMapFileBacked_nid_no_patch(void** addr, size_t len, int prot, int flags, int descriptor, std::uint64_t offset, size_t alignment); void ReleaseFlexibleMappingRange(std::uintptr_t address, size_t len); int DoMapShared(void** addr, size_t len, int prot, HostSharedMemory object); -int DoMprotect(const void* addr, size_t len, int prot); -int DoMtypeprotect(const void* addr, size_t len, int type, int prot); +int DoMprotect(const void* addr, size_t len, int prot, bool requireMappedStart = true); +int DoMtypeprotect(const void* addr, size_t len, int type, int prot, bool requireMappedStart = true); int DoMunmap(void* addr, size_t len); int DoReserveVirtual(void** addr, size_t len, int flags, size_t alignment); bool GuestProtection(uintptr_t addr, int* prot); @@ -51,6 +53,7 @@ bool GuestReservation(std::uintptr_t addr, std::uintptr_t* start, std::uintptr_t bool GuestRangeMapped(std::uintptr_t start, std::uintptr_t end); void ForgetJitSharedMemory(int descriptor); void DuplicateJitSharedMemory(int from, int to); +bool IsJitSharedMemory(int descriptor); void GuestPageTableUsage(std::uint64_t granularity, std::uint64_t* cpu_entries, std::uint64_t* gpu_entries); #endif diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index ef0324cf29..60798b7c19 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -236,6 +236,10 @@ int APS5_VABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, in return sceKernelAllocateDirectMemory(0, static_cast(DIRECT_MEMORY_SIZE), len, alignment, memory_type, phys_addr_out); } +int APS5_VABI sceKernelAllocateDirectMemoryForApp(int64_t search_start, int64_t search_end, size_t len, size_t alignment, int memory_type, int64_t* phys_addr_out) { + return sceKernelAllocateDirectMemory(search_start, search_end, len, alignment, memory_type, phys_addr_out); +} + int APS5_VABI sceKernelAvailableDirectMemorySize(int64_t search_start, int64_t search_end, size_t alignment, int64_t* phys_addr_out, size_t* size_out) { int64_t start = 0; size_t size = 0; @@ -322,7 +326,7 @@ int32_t APS5_VABI sceKernelMapNamedFlexibleMemoryInternal(void** addr_in_out, si int APS5_VABI sceKernelMprotect(const void* addr, size_t len, int prot) { constexpr int DefinedProtection = 0x3F7; if (len == 0) return 0; - return DoMprotect(addr, len, prot & DefinedProtection); + return DoMprotect(addr, len, prot & DefinedProtection, false); } int APS5_VABI sceKernelMunmap(uint64_t vaddr, size_t len) { @@ -474,16 +478,18 @@ int APS5_VABI sceKernelCheckedReleaseDirectMemory(int64_t start, size_t len) { int APS5_VABI sceKernelMtypeprotect(const void* addr, size_t len, int type, int prot) { if (len == 0) return 0; - return DoMtypeprotect(addr, len, type, prot); + return DoMtypeprotect(addr, len, type, prot, false); } int APS5_VABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, int* prot) { VirtualQueryInfo info{}; const int result = sceKernelVirtualQuery(addr, 0, &info, sizeof(info)); if (result != 0) return result; - if (start) *start = reinterpret_cast(info.start); - if (end) *end = reinterpret_cast(info.end); - if (prot) *prot = info.protection; + const auto startAddress = reinterpret_cast(info.start); + const auto endAddress = reinterpret_cast(info.end); + if (start != nullptr && !GuestCopyOut_nid_no_patch(start, &startAddress, sizeof(startAddress))) return SCE_KERNEL_ERROR_EFAULT; + if (end != nullptr && !GuestCopyOut_nid_no_patch(end, &endAddress, sizeof(endAddress))) return SCE_KERNEL_ERROR_EFAULT; + if (prot != nullptr && !GuestCopyOut_nid_no_patch(prot, &info.protection, sizeof(info.protection))) return SCE_KERNEL_ERROR_EFAULT; return 0; } @@ -494,8 +500,10 @@ int APS5_VABI sceKernelIsStack(void* addr, void** start, void** end) { VirtualQueryInfo info{}; if (sceKernelVirtualQuery(addr, 0, &info, sizeof(info)) != 0) return SCE_KERNEL_ERROR_EACCES; } - if (start) *start = reinterpret_cast(stackStart); - if (end) *end = reinterpret_cast(stackEnd); + const auto startAddress = reinterpret_cast(stackStart); + const auto endAddress = reinterpret_cast(stackEnd); + if (start != nullptr && !GuestCopyOut_nid_no_patch(start, &startAddress, sizeof(startAddress))) return SCE_KERNEL_ERROR_EFAULT; + if (end != nullptr && !GuestCopyOut_nid_no_patch(end, &endAddress, sizeof(endAddress))) return SCE_KERNEL_ERROR_EFAULT; return 0; } @@ -518,7 +526,8 @@ int APS5_VABI sceKernelConfiguredFlexibleMemorySize(size_t* size) { int APS5_VABI sceKernelInternalMemoryGetAvailableSize(std::size_t* size) { if (!size) return -1; - throw std::logic_error("sceKernelInternalMemoryGetAvailableSize: internal pool statistics are not implemented"); + NotImplemented_nid_no_patch(__func__); + return 0; } int APS5_VABI sceKernelSetVirtualRangeName(const void* addr, uint64_t len, const char* name) { @@ -852,6 +861,10 @@ int UnlockHostPages(std::uintptr_t start, std::uintptr_t end) { extern "C" { +bool APS5_VABI DirectMemoryAliased_nid_postfix(std::uintptr_t address, std::size_t bytes) { + return DirectMemoryAliased(address, bytes); +} + int APS5_VABI sceKernelMlock_nid_postfix(void* address, std::uint64_t length) { std::uintptr_t start = 0; std::uintptr_t end = 0; diff --git a/core/libs/prx/libkernel/DirectMemory/Jit.cpp b/core/libs/prx/libkernel/DirectMemory/Jit.cpp index 60e266a543..dde88e322d 100644 --- a/core/libs/prx/libkernel/DirectMemory/Jit.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Jit.cpp @@ -180,6 +180,11 @@ void ForgetJitSharedMemory(int descriptor) { g_jitDescriptors.erase(descriptor); } +bool IsJitSharedMemory(int descriptor) { + std::lock_guard lock(g_jitLock); + return g_jitDescriptors.contains(descriptor); +} + void DuplicateJitSharedMemory(int from, int to) { std::lock_guard lock(g_jitLock); g_jitDescriptors.erase(to); diff --git a/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp b/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp index dd76e29ee8..2f3cd26c9e 100644 --- a/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp @@ -281,7 +281,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 & GuestDefinedProtection) != 0) return failed(GuestInvalid); + if (DoMprotect(address, length, protection & GuestDefinedProtection, false) != 0) return failed(GuestInvalid); return 0; } catch (const std::bad_alloc&) { return failed(GuestNoMemory); @@ -312,8 +312,8 @@ int APS5_VABI mlockall_nid_postfix(int flags) { SetError(GuestInvalid); return -1; } - if ((flags & GuestLockFuture) != 0) throw std::runtime_error("mlockall: MCL_FUTURE is not implemented: later mappings are not locked"); - throw std::runtime_error("mlockall: MCL_CURRENT is not implemented: the process also holds host stacks, heaps, modules and driver mappings"); + NotImplemented_nid_no_patch("mlockall"); + return 0; } int APS5_VABI munlockall_nid_postfix() { @@ -331,6 +331,7 @@ int APS5_VABI msync_nid_postfix(void* address, std::size_t length, int flags) { constexpr std::uintptr_t mask = PS5_PAGE_SIZE - 1; const auto first = reinterpret_cast(address); const auto start = first & ~mask; + if (length > std::numeric_limits::max() - (first - start) - mask) return failed(GuestInvalid); const std::uintptr_t size = (length + (first - start) + mask) & ~mask; const auto end = start + size; if (end < start) return failed(GuestInvalid); diff --git a/core/libs/prx/libkernel/Equeue/Equeue.cpp b/core/libs/prx/libkernel/Equeue/Equeue.cpp index fca356d296..8a26c794ac 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.cpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.cpp @@ -514,6 +514,21 @@ int KernelEqueuePrivate::AddEvent(const KernelEqueueEvent& event) { for (auto& pending : it->pendingEvents) { pending.udata = event.event.udata; } + if (it->pendingEvents.size() > kMaxPendingEvents) { + it->pendingEvents.erase(it->pendingEvents.begin(), it->pendingEvents.begin() + static_cast::difference_type>(it->pendingEvents.size() - kMaxPendingEvents)); + } + } else if (event.pendingEvents.size() > kMaxPendingEvents) { + KernelEqueueEvent truncated; + truncated.triggered = event.triggered; + truncated.enabled = event.enabled; + truncated.deadlineNs = event.deadlineNs; + truncated.intervalNs = event.intervalNs; + truncated.lowWater = event.lowWater; + truncated.event = event.event; + truncated.filter = event.filter; + truncated.pendingEvents.assign(event.pendingEvents.end() - static_cast::difference_type>(kMaxPendingEvents), event.pendingEvents.end()); + m_events.push_back(std::move(truncated)); + if (event.triggered) ++m_activations; } else { m_events.push_back(event); if (event.triggered) ++m_activations; @@ -543,6 +558,9 @@ int KernelEqueuePrivate::TriggerEvent(uintptr_t ident, int16_t filter, void* tri } else { it->triggered = true; } + if (it->pendingEvents.size() > KernelEqueuePrivate::kMaxPendingEvents) { + it->pendingEvents.erase(it->pendingEvents.begin(), it->pendingEvents.begin() + static_cast::difference_type>(it->pendingEvents.size() - KernelEqueuePrivate::kMaxPendingEvents)); + } if (!was && it->triggered) ++m_activations; } m_cond.NotifyOne(); diff --git a/core/libs/prx/libkernel/Equeue/Equeue.hpp b/core/libs/prx/libkernel/Equeue/Equeue.hpp index 498d78b4ff..07dcd58d99 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.hpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.hpp @@ -75,6 +75,8 @@ struct KernelEqueueEvent { class KernelEqueuePrivate { public: + static constexpr std::size_t kMaxPendingEvents = 64; + explicit KernelEqueuePrivate(KernelEqueue handle, bool reportOncePerWait = false); ~KernelEqueuePrivate(); diff --git a/core/libs/prx/libkernel/Equeue/src/EdgeWatch.cpp b/core/libs/prx/libkernel/Equeue/src/EdgeWatch.cpp index 60e001c6b3..f8f0499e02 100644 --- a/core/libs/prx/libkernel/Equeue/src/EdgeWatch.cpp +++ b/core/libs/prx/libkernel/Equeue/src/EdgeWatch.cpp @@ -8,6 +8,10 @@ #ifdef __linux__ #include #include +#elif defined(__APPLE__) +#include +#include +#include #endif namespace EdgeWatch { @@ -37,6 +41,43 @@ bool Watch::TakeEdge() { return ::epoll_wait(m_descriptor, &ready, 1, 0) > 0; } +#elif defined(__APPLE__) + +std::shared_ptr Watch::Open(std::intptr_t hostDescriptor, bool write) { + auto watch = std::make_shared(-1); + const int descriptor = ::kqueue(); + if (descriptor < 0) { + throw std::runtime_error("kevent: EV_CLEAR: kqueue failed, host errno " + std::to_string(errno)); + } + watch->m_descriptor = descriptor; + if (::fcntl(descriptor, F_SETFD, FD_CLOEXEC) != 0) { + throw std::runtime_error("kevent: EV_CLEAR: setting close-on-exec failed, host errno " + std::to_string(errno)); + } + struct kevent interest{}; + EV_SET(&interest, static_cast(hostDescriptor), write ? EVFILT_WRITE : EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, nullptr); + if (::kevent(descriptor, &interest, 1, nullptr, 0, nullptr) != 0) { + throw std::runtime_error("kevent: EV_CLEAR: watching host descriptor " + std::to_string(hostDescriptor) + " failed, host errno " + std::to_string(errno)); + } + return watch; +} + +Watch::~Watch() { + if (m_descriptor >= 0) ::close(m_descriptor); +} + +bool Watch::TakeEdge() { + struct kevent ready{}; + const timespec timeout{}; + int result; + do { + result = ::kevent(m_descriptor, nullptr, 0, &ready, 1, &timeout); + } while (result < 0 && errno == EINTR); + if (result < 0) { + throw std::runtime_error("kevent: EV_CLEAR: retrieving host event failed, host errno " + std::to_string(errno)); + } + return result > 0; +} + #else std::shared_ptr Watch::Open(std::intptr_t, bool) { diff --git a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp index 65ae972430..4351a30052 100644 --- a/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp +++ b/core/libs/prx/libkernel/EventFlag/src/EventFlag.cpp @@ -4,6 +4,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/PrimedSharedMutex.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" #include @@ -97,7 +98,7 @@ struct EventFlagHandle { bool opened = false; }; -std::shared_mutex eventFlagRegistryMutex; +PrimedSharedMutex eventFlagRegistryMutex; std::unordered_map> eventFlagRegistry; std::shared_ptr ResolveEventFlag(KernelEventFlag handle) { diff --git a/core/libs/prx/libkernel/File/include/File.hpp b/core/libs/prx/libkernel/File/include/File.hpp index 7920418181..e90c7c760d 100644 --- a/core/libs/prx/libkernel/File/include/File.hpp +++ b/core/libs/prx/libkernel/File/include/File.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #ifdef __APPLE__ @@ -36,7 +37,19 @@ namespace File { std::optional WindowsDescriptorPath_nid_no_patch(int descriptor); #endif int HostStandardError(); -[[nodiscard]] std::unique_lock LockPositionedTransfer(int descriptor); +void ForgetClosedDescriptor(int descriptor); +class PositionedTransferLock { +public: + explicit PositionedTransferLock(std::shared_ptr mutex) : _mutex(std::move(mutex)), _lock(*_mutex) {} + PositionedTransferLock(PositionedTransferLock&&) = default; + PositionedTransferLock& operator=(PositionedTransferLock&&) = delete; + void unlock() { _lock.unlock(); } + +private: + std::shared_ptr _mutex; + std::unique_lock _lock; +}; +[[nodiscard]] PositionedTransferLock LockPositionedTransfer(int descriptor); void SharePositionedTransfer(int from, int to); #ifdef _WIN32 std::int64_t WriteAtEnd(int descriptor, const void* buf, std::size_t nbytes); diff --git a/core/libs/prx/libkernel/File/src/ArchivedFile.cpp b/core/libs/prx/libkernel/File/src/ArchivedFile.cpp index 2f1a17cc66..8ec73f951f 100644 --- a/core/libs/prx/libkernel/File/src/ArchivedFile.cpp +++ b/core/libs/prx/libkernel/File/src/ArchivedFile.cpp @@ -35,6 +35,7 @@ void FillNode(const AppArchiveNode& node, FileStat* sb) { sb->st_nlink = node.Directory ? 2 : 1; sb->st_size = static_cast(node.Size); sb->st_blocks = static_cast((node.Size + 511) / 512); + if (node.Directory) sb->st_blksize = 0x10000; } } diff --git a/core/libs/prx/libkernel/File/src/DescriptorControl.cpp b/core/libs/prx/libkernel/File/src/DescriptorControl.cpp index 1c5f3dddd9..3e0bc5ff00 100644 --- a/core/libs/prx/libkernel/File/src/DescriptorControl.cpp +++ b/core/libs/prx/libkernel/File/src/DescriptorControl.cpp @@ -309,7 +309,11 @@ std::optional Classify(int fd, const char* function) { if (S_ISFIFO(status.st_mode)) return Kind::Pipe; if (S_ISCHR(status.st_mode)) return Kind::Device; if (S_ISSOCK(status.st_mode)) return Kind::Socket; +#ifdef __linux__ + return std::nullopt; +#else throw std::runtime_error(std::string(function) + ": descriptor " + std::to_string(fd) + " is neither a file, a directory, a pipe, a socket nor a character device"); +#endif } int HostStatus(int fd, const char* function) { diff --git a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp index 11002b4f93..28d75e6f20 100644 --- a/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp +++ b/core/libs/prx/libkernel/File/src/DirectoryDescriptor.cpp @@ -24,6 +24,7 @@ #ifdef _WIN32 #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/WindowsVerbatimPath.hpp" #include #include #include @@ -31,10 +32,28 @@ #include #include #include +#ifdef __APPLE__ +#include +#endif #endif namespace { +#ifndef _WIN32 +std::filesystem::path HostDescriptorTarget(int fd, std::error_code& error) { +#ifdef __APPLE__ + char buffer[MAXPATHLEN]; + if (::fcntl(fd, F_GETPATH, buffer) != 0) { + error = std::error_code(errno, std::generic_category()); + return {}; + } + return std::filesystem::path(buffer); +#else + return std::filesystem::read_symlink(std::filesystem::path("/proc/self/fd") / std::to_string(fd), error); +#endif +} +#endif + constexpr std::uint8_t GuestUnknownType = 0; constexpr std::uint8_t GuestDirectoryType = 4; constexpr std::uint8_t GuestRegularType = 8; @@ -209,9 +228,11 @@ void Load(DirectoryState& state, int fd) { #ifdef _WIN32 int OpenPlaceholder(const std::filesystem::path& path) { std::error_code error; - if (std::filesystem::is_directory(path, error)) { + const auto verbatimPath = NativeVerbatimPath_nid_no_patch(path, error, WindowsMaxDirectoryPathLength); + const auto& openPath = error ? path : verbatimPath; + if (std::filesystem::is_directory(openPath, error)) { SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; - const auto handle = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + const auto handle = CreateFileW(openPath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &security, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); if (handle == INVALID_HANDLE_VALUE) { errno = WindowsOpenError_nid_no_patch(GetLastError()); @@ -301,8 +322,7 @@ std::optional DirectoryDescriptorPath(int fd) { #else if (fd < 0) return std::nullopt; std::error_code error; - const auto link = std::filesystem::path("/proc/self/fd") / std::to_string(fd); - const auto target = std::filesystem::read_symlink(link, error); + const auto target = HostDescriptorTarget(fd, error); if (error) return std::nullopt; return target; #endif @@ -312,8 +332,7 @@ std::optional DirectoryDescriptorPath(int fd) { #else if (fd < 0) return std::nullopt; std::error_code error; - const auto link = std::filesystem::path("/proc/self/fd") / std::to_string(fd); - const auto target = std::filesystem::read_symlink(link, error); + const auto target = HostDescriptorTarget(fd, error); if (error) return std::nullopt; if (!std::filesystem::is_directory(target, error)) return std::nullopt; return target; diff --git a/core/libs/prx/libkernel/File/src/FilesystemForwards.cpp b/core/libs/prx/libkernel/File/src/FilesystemForwards.cpp new file mode 100644 index 0000000000..83eb7605a6 --- /dev/null +++ b/core/libs/prx/libkernel/File/src/FilesystemForwards.cpp @@ -0,0 +1,35 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/FilesystemForwards.hpp" + +namespace { + +struct FilesystemForwardsRegistration { + FilesystemForwardsRegistration() { + static const FilesystemForwards_nid_no_patch forwards{ + sceKernelStat, + sceKernelFstat, + sceKernelOpen, + sceKernelRead, + sceKernelWrite, + sceKernelClose, + sceKernelMkdir, + sceKernelUnlink, + sceKernelRename, + sceKernelTruncate_nid_postfix, + sceKernelChmod_nid_postfix, + sceKernelFchmod, + sceKernelUtimes_nid_postfix, + stat_nid_postfix, + rmdir_nid_postfix, + }; + RegisterFilesystemForwards_nid_no_patch(&forwards); + } + ~FilesystemForwardsRegistration() { RegisterFilesystemForwards_nid_no_patch(nullptr); } + FilesystemForwardsRegistration(const FilesystemForwardsRegistration&) = delete; + FilesystemForwardsRegistration& operator=(const FilesystemForwardsRegistration&) = delete; +}; + +const FilesystemForwardsRegistration registration; + +} diff --git a/core/libs/prx/libkernel/File/src/NativeStat.cpp b/core/libs/prx/libkernel/File/src/NativeStat.cpp index 8eb02f7a92..f1f2fe59ed 100644 --- a/core/libs/prx/libkernel/File/src/NativeStat.cpp +++ b/core/libs/prx/libkernel/File/src/NativeStat.cpp @@ -97,6 +97,49 @@ static int DoLstat(const std::filesystem::path& p, NativeStat* st) { st->isSymbolicLink = true; return 0; } +#elif defined(__APPLE__) +#include +#include +#include +#include +using NativeStat = struct stat; +static int StatAt(const std::filesystem::path& p, NativeStat* st, int flags) { + std::string rest = p.native(); + int directory = AT_FDCWD; + while (rest.size() >= PATH_MAX) { + const std::size_t split = rest.rfind('/', PATH_MAX - 1); + if (split == std::string::npos) { + if (directory != AT_FDCWD) ::close(directory); + errno = ENAMETOOLONG; + return -1; + } + const std::string prefix = split == 0 ? std::string("/") : rest.substr(0, split); + const int next = ::openat(directory, prefix.c_str(), O_SEARCH | O_CLOEXEC); + const int error = errno; + if (directory != AT_FDCWD) ::close(directory); + if (next < 0) { + errno = error; + return -1; + } + directory = next; + rest.erase(0, split + 1); + rest.erase(0, rest.find_first_not_of('/')); + } + const int result = ::fstatat(directory, rest.empty() ? "." : rest.c_str(), st, flags); + const int error = errno; + if (directory != AT_FDCWD) ::close(directory); + errno = error; + return result; +} +static int DoStat(const std::filesystem::path& p, NativeStat* st) { + return StatAt(p, st, 0); +} +static int DoFstat(int fd, NativeStat* st) { + return ::fstat(fd, st); +} +static int DoLstat(const std::filesystem::path& p, NativeStat* st) { + return StatAt(p, st, AT_SYMLINK_NOFOLLOW); +} #else #include using NativeStat = struct stat; @@ -153,12 +196,21 @@ static void CopyNativeStat(const NativeStat& st, FileStat* sb) { sb->st_rdev = static_cast(st.st_rdev); sb->st_blksize = static_cast(st.st_blksize); sb->st_blocks = static_cast(st.st_blocks); +#if defined(__APPLE__) + sb->st_atim.tv_sec = static_cast(st.st_atimespec.tv_sec); + sb->st_atim.tv_nsec = static_cast(st.st_atimespec.tv_nsec); + sb->st_mtim.tv_sec = static_cast(st.st_mtimespec.tv_sec); + sb->st_mtim.tv_nsec = static_cast(st.st_mtimespec.tv_nsec); + sb->st_ctim.tv_sec = static_cast(st.st_ctimespec.tv_sec); + sb->st_ctim.tv_nsec = static_cast(st.st_ctimespec.tv_nsec); +#else sb->st_atim.tv_sec = static_cast(st.st_atim.tv_sec); sb->st_atim.tv_nsec = static_cast(st.st_atim.tv_nsec); sb->st_mtim.tv_sec = static_cast(st.st_mtim.tv_sec); sb->st_mtim.tv_nsec = static_cast(st.st_mtim.tv_nsec); sb->st_ctim.tv_sec = static_cast(st.st_ctim.tv_sec); sb->st_ctim.tv_nsec = static_cast(st.st_ctim.tv_nsec); +#endif #if defined(__APPLE__) sb->st_birthtim.tv_sec = static_cast(st.st_birthtimespec.tv_sec); sb->st_birthtim.tv_nsec = static_cast(st.st_birthtimespec.tv_nsec); @@ -169,6 +221,7 @@ static void CopyNativeStat(const NativeStat& st, FileStat* sb) { sb->st_birthtim.tv_nsec = static_cast(st.st_birthtim.tv_nsec); #endif #endif + if ((sb->st_mode & 0170000) == 0040000) sb->st_blksize = 0x10000; } namespace File { diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index 2575257632..068ef2304d 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -7,6 +7,7 @@ #include "prx/libc/include/RenameDot.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/AppArchive.hpp" +#include "prx/libc/include/FileStream.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/SharedMemory.hpp" @@ -25,6 +26,8 @@ #include #include #include +#include +#include #include #include @@ -34,6 +37,7 @@ #include #include #include "prx/libc/include/WindowsDescriptor.hpp" +#include "prx/libc/include/WindowsVerbatimPath.hpp" #include "prx/libkernel/File/include/WindowsSymbolicLink.hpp" static int NativeOpen(const std::filesystem::path& p, int nativeFlags, std::uint16_t mode) { const int access = nativeFlags & (_O_RDONLY | _O_WRONLY | _O_RDWR); @@ -113,15 +117,7 @@ static int NativeUnlink(const std::filesystem::path& p) { return -1; } static std::filesystem::path NativeMetadataPath(const std::filesystem::path& path, std::error_code& error) { - const auto& input = path.native(); - if (input.starts_with(L"\\\\?\\") || input.starts_with(L"\\\\.\\")) return path; - auto absolute = std::filesystem::absolute(path, error); - if (error) return {}; - absolute = absolute.lexically_normal().make_preferred(); - const auto& native = absolute.native(); - if (native.size() < MAX_PATH || native.starts_with(L"\\\\?\\") || native.starts_with(L"\\\\.\\")) return absolute; - return native.starts_with(L"\\\\") ? std::filesystem::path(L"\\\\?\\UNC\\" + native.substr(2)) - : std::filesystem::path(L"\\\\?\\" + native); + return NativeVerbatimPath_nid_no_patch(path, error); } static bool NativeSymbolicLinkLoop(const std::filesystem::path& path) { return File::WindowsSymbolicLinkLoop(path); @@ -208,6 +204,14 @@ static int SceIoErrorFromErrno(int error) { return static_cast(0x80020000u | static_cast(guest)); } +static int TrailingSlashCreationError(const std::filesystem::path& native, int flags) { + if ((flags & SCE_KERNEL_O_CREAT) == 0 || native.has_filename() || !native.has_relative_path()) return 0; + std::error_code error; + const auto status = std::filesystem::status(native, error); + if (error) return FilesystemError(error); + return status.type() == std::filesystem::file_type::not_found ? 2 : 0; +} + static int OpenNative(const std::filesystem::path& native, int flags, std::uint16_t mode) { if (AppArchiveHasDirectory_nid_no_patch(native)) { std::error_code error; @@ -228,13 +232,16 @@ static int OpenNative(const std::filesystem::path& native, int flags, std::uint1 return *archived; } #ifdef _WIN32 + std::error_code pathError; + const auto metadataOpenPath = NativeMetadataPath(native, pathError); + const auto& openNative = pathError ? native : metadataOpenPath; if ((flags & (SCE_KERNEL_O_DIRECTORY | SCE_KERNEL_O_CREAT)) == SCE_KERNEL_O_DIRECTORY) { std::error_code error; - const auto status = std::filesystem::status(native, error); + const auto status = std::filesystem::status(openNative, 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 ((flags & (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL)) == (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL) && openNative.has_filename()) { + const DWORD attributes = GetFileAttributesW(openNative.c_str()); if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) return SceErrorFromErrno(EEXIST); } #endif @@ -243,14 +250,18 @@ static int OpenNative(const std::filesystem::path& native, int flags, std::uint1 std::error_code error; if (std::filesystem::is_symlink(native, error)) return SceErrorFromErrno(GuestEmlink); #ifdef _WIN32 - const DWORD attributes = GetFileAttributesW(native.c_str()); + const DWORD attributes = GetFileAttributesW(openNative.c_str()); if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { std::string junctionTarget; - if (File::WindowsSymbolicLink::ReadJunction(native, junctionTarget, "open")) return SceErrorFromErrno(GuestEmlink); + if (File::WindowsSymbolicLink::ReadJunction(openNative, junctionTarget, "open")) return SceErrorFromErrno(GuestEmlink); } #endif } +#ifdef _WIN32 + int fd = NativeOpen(openNative, MapFlags(flags), mode); +#else int fd = NativeOpen(native, MapFlags(flags), mode); +#endif #ifdef __APPLE__ if (fd >= 0 && (flags & (SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL)) == SCE_KERNEL_O_CREAT) { struct stat opened{}; @@ -276,6 +287,12 @@ static int OpenNative(const std::filesystem::path& native, int flags, std::uint1 } } #endif + if (fd < 0 && (errno == EISDIR || errno == EINVAL)) { + const int openError = errno; + const int lookupError = TrailingSlashCreationError(native, flags); + errno = lookupError == 2 ? ENOENT : openError; + if (lookupError != 0 && lookupError != 2) return SceKernelError(lookupError); + } #ifdef _WIN32 if (fd < 0 && (errno == ENOENT || errno == ENOTDIR)) { std::error_code metadataError; @@ -292,6 +309,45 @@ static int OpenNative(const std::filesystem::path& native, int flags, std::uint1 return fd; } +void File::ForgetClosedDescriptor(int descriptor) { + File::ForgetRandomDevice(descriptor); + File::ForgetDescriptor(descriptor); + ForgetJitSharedMemory(descriptor); +#ifdef _WIN32 + File::ForgetFileLock(descriptor); +#endif + File::ForgetDirectoryDescriptor(descriptor); +} + +namespace { + +void* BeginClosingStreamDescriptor(int descriptor) { + EqueueDescriptorClosed(descriptor); +#ifdef _WIN32 + auto positioned = std::make_unique(File::LockPositionedTransfer(descriptor)); + File::ForgetClosedDescriptor(descriptor); + return positioned.release(); +#else + File::ForgetClosedDescriptor(descriptor); + return nullptr; +#endif +} + +void EndClosingStreamDescriptor(void* closing) { + delete static_cast(closing); +} + +struct ClosedDescriptorRegistration { + ClosedDescriptorRegistration() { LibcRegisterClosedDescriptors_nid_no_patch(&BeginClosingStreamDescriptor, &EndClosingStreamDescriptor); } + ~ClosedDescriptorRegistration() { LibcRegisterClosedDescriptors_nid_no_patch(nullptr, nullptr); } + ClosedDescriptorRegistration(const ClosedDescriptorRegistration&) = delete; + ClosedDescriptorRegistration& operator=(const ClosedDescriptorRegistration&) = delete; +}; + +const ClosedDescriptorRegistration g_closedDescriptorRegistration; + +} + extern "C" { int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) { @@ -340,13 +396,7 @@ int APS5_VABI sceKernelClose(int d) { #ifdef _WIN32 const auto positioned = File::LockPositionedTransfer(d); #endif - File::ForgetRandomDevice(d); - File::ForgetDescriptor(d); - ForgetJitSharedMemory(d); -#ifdef _WIN32 - File::ForgetFileLock(d); -#endif - File::ForgetDirectoryDescriptor(d); + File::ForgetClosedDescriptor(d); if (NativeClose(d) != 0) { if (errno == EBADF) return SCE_KERNEL_ERROR_EBADF; throw std::runtime_error(std::string(__func__) + ": close failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno)); @@ -356,6 +406,7 @@ int APS5_VABI sceKernelClose(int d) { std::int64_t APS5_VABI sceKernelRead(int d, void* buf, std::size_t nbytes) { if (File::IsDirectoryDescriptor(d)) return SceErrorFromErrno(EISDIR); + if (IsKqueueDescriptor_nid_no_patch(d)) return nbytes == 0 ? 0 : SCE_KERNEL_ERROR_EOPNOTSUPP; if (GuestSockets::IsOpen(d)) { const auto n = GuestSockets::Read(d, buf, nbytes); return n < 0 ? SceKernelError(*__error_nid_postfix()) : n; @@ -376,6 +427,7 @@ 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) { if (File::IsVirtualDirectoryDescriptor(d)) return SceErrorFromErrno(EISDIR); + if (IsKqueueDescriptor_nid_no_patch(d)) return SCE_KERNEL_ERROR_EOPNOTSUPP; if (GuestSockets::IsOpen(d)) { const auto n = GuestSockets::Write(d, buf, nbytes); return n < 0 ? SceKernelError(*__error_nid_postfix()) : n; @@ -405,7 +457,7 @@ static bool SeekOverflows(int d, std::int64_t offset, int whence) { } std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence) { - if (GuestSockets::IsOpen(d)) return SCE_KERNEL_ERROR_ESPIPE; + if (GuestSockets::IsOpen(d) || IsKqueueDescriptor_nid_no_patch(d)) return SCE_KERNEL_ERROR_ESPIPE; if (whence < 0 || whence > 2) { throw std::invalid_argument(std::string(__func__) + ": invalid whence=" + std::to_string(whence)); } @@ -431,8 +483,7 @@ static int StatNative(const std::filesystem::path& native, bool directory, FileS FileStat archived{}; if (File::FillArchivedStat(native, &archived)) { if (directory && (archived.st_mode & 0170000) != 0040000) return SceErrorFromErrno(GuestEnotdir); - if (sb == nullptr) return SceErrorFromErrno(GuestEfault); - *sb = archived; + if (sb == nullptr || !GuestCopyOut_nid_no_patch(sb, &archived, sizeof(archived))) return SceErrorFromErrno(GuestEfault); return 0; } auto metadataPath = native; @@ -459,7 +510,7 @@ static int StatNative(const std::filesystem::path& native, bool directory, FileS int APS5_VABI sceKernelStat(const char* path, FileStat* sb) { std::string copy; - if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(error); path = copy.c_str(); if (copy.empty()) return SceErrorFromErrno(2); std::filesystem::path native; @@ -480,38 +531,48 @@ int StatInDirectory_nid_no_patch(int directory, const char* path, FileStat* sb, int APS5_VABI sceKernelUnlink(const char* path) { std::string copy; - if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(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); +#ifdef _WIN32 + std::error_code unlinkPathError; + const auto metadataUnlinkPath = NativeMetadataPath(native, unlinkPathError); + const auto& unlinkNative = unlinkPathError ? native : metadataUnlinkPath; +#else + const auto& unlinkNative = native; +#endif constexpr int GuestEperm = 1; std::error_code error; - bool directory = std::filesystem::is_directory(std::filesystem::symlink_status(native, error)); + bool directory = std::filesystem::is_directory(std::filesystem::symlink_status(unlinkNative, error)); #ifdef _WIN32 constexpr DWORD DirectoryLink = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT; - const DWORD linkAttributes = GetFileAttributesW(native.c_str()); + const DWORD linkAttributes = GetFileAttributesW(unlinkNative.c_str()); if (linkAttributes != INVALID_FILE_ATTRIBUTES && (linkAttributes & DirectoryLink) == DirectoryLink) directory = true; +#endif +#ifdef _WIN32 + if (unlinkNative.has_filename() && unlinkNative.filename().native().find_first_of(L"*?<>\"") == std::wstring::npos) { + WIN32_FIND_DATAW found{}; + if (const HANDLE find = FindFirstFileW(unlinkNative.c_str(), &found); find != INVALID_HANDLE_VALUE) { + FindClose(find); + constexpr DWORD DirectoryLink = FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY; + if ((found.dwFileAttributes & DirectoryLink) == DirectoryLink && found.dwReserved0 == IO_REPARSE_TAG_SYMLINK) { + if (!RemoveDirectoryW(unlinkNative.c_str())) return SceErrorFromErrno(GetLastError() == ERROR_ACCESS_DENIED ? 13 : 5); + RecordWrittenPath_nid_no_patch(native); + return 0; + } + } + } #endif if (directory) { #ifdef _WIN32 - if (native.has_filename()) { - WIN32_FIND_DATAW found{}; - if (const HANDLE find = FindFirstFileW(native.c_str(), &found); find != INVALID_HANDLE_VALUE) { - FindClose(find); - if ((found.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && found.dwReserved0 == IO_REPARSE_TAG_SYMLINK) { - if (!RemoveDirectoryW(native.c_str())) return SceErrorFromErrno(GetLastError() == ERROR_ACCESS_DENIED ? 13 : 5); - RecordWrittenPath_nid_no_patch(native); - return 0; - } - } - } - const DWORD attributes = GetFileAttributesW(native.c_str()); + const DWORD attributes = GetFileAttributesW(unlinkNative.c_str()); if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { std::string junctionTarget; - if (File::WindowsSymbolicLink::ReadJunction(native, junctionTarget, "unlink")) { - if (RemoveDirectoryW(native.c_str()) == 0) return SceErrorFromErrno(GuestEperm); + if (File::WindowsSymbolicLink::ReadJunction(unlinkNative, junctionTarget, "unlink")) { + if (RemoveDirectoryW(unlinkNative.c_str()) == 0) return SceErrorFromErrno(GuestEperm); RecordWrittenPath_nid_no_patch(native); return 0; } @@ -519,9 +580,9 @@ int APS5_VABI sceKernelUnlink(const char* path) { #endif return SceErrorFromErrno(GuestEperm); } - if (NativeUnlink(native) != 0) { + if (NativeUnlink(unlinkNative) != 0) { #ifdef _WIN32 - if (NativeSymbolicLinkLoop(native.parent_path())) return SCE_KERNEL_ERROR_ELOOP; + if (NativeSymbolicLinkLoop(unlinkNative.parent_path())) return SCE_KERNEL_ERROR_ELOOP; #endif return SceErrorFromErrno(errno == ENOENT ? NotFoundError_nid_no_patch(native) : errno); } @@ -558,4 +619,4 @@ int APS5_VABI sceKernelFcntl(int d, int command, ...) { return result < 0 ? SceKernelError(*__error_nid_postfix()) : result; } -} +} \ No newline at end of file diff --git a/core/libs/prx/libkernel/File/src/SharedMemory.cpp b/core/libs/prx/libkernel/File/src/SharedMemory.cpp index 394c1387a8..e2c2a05e24 100644 --- a/core/libs/prx/libkernel/File/src/SharedMemory.cpp +++ b/core/libs/prx/libkernel/File/src/SharedMemory.cpp @@ -40,6 +40,10 @@ extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handle #include #include #include +#ifdef __APPLE__ +#include +#include +#endif #endif namespace { @@ -118,6 +122,20 @@ bool IsHostSharedMemoryDescriptor(int descriptor) { 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)); +#elif defined(__APPLE__) + struct stat status{}; + if (::fstat(descriptor, &status) != 0 || !S_ISREG(status.st_mode) || status.st_nlink != 0) return false; + char objectPath[MAXPATHLEN]; + if (::fcntl(descriptor, F_GETPATH, objectPath) != 0) return false; + const std::string_view path(objectPath); + const auto slash = path.rfind('/'); + if (slash == std::string_view::npos || !path.substr(slash + 1).starts_with("anyps5-shm-")) return false; + static const std::string directory = [] { + std::error_code error; + auto canonical = std::filesystem::canonical(std::filesystem::temp_directory_path(error), error); + return error ? std::string{} : canonical.string(); + }(); + return !directory.empty() && std::string_view(objectPath, slash) == directory; #else (void)descriptor; return false; @@ -601,6 +619,9 @@ int OpenNamedFile(const char* path, int flags, int mode) { int validationError = 0; const auto hostName = NamedHostName(path, validationError); if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid); +#ifdef __APPLE__ + NotImplemented_nid_no_patch("shm_open"); +#endif HostShmLock namespaceLock; if (namespaceLock.Error() != 0) { errno = namespaceLock.Error(); @@ -677,6 +698,11 @@ int CreateAnonymousFile() { } errno = EEXIST; return -1; +#elif defined(__APPLE__) + auto path = (std::filesystem::temp_directory_path() / "anyps5-shm-XXXXXX").string(); + const int descriptor = mkostemp(path.data(), O_CLOEXEC); + if (descriptor >= 0) ::unlink(path.c_str()); + return descriptor; #else const int descriptor = memfd_create("anyps5 shm_open", MFD_CLOEXEC); return descriptor; @@ -1040,6 +1066,9 @@ extern "C" int APS5_VABI shm_unlink_nid_postfix(const char* path) { int validationError = 0; const auto hostName = NamedHostName(path, validationError); if (validationError != 0) return Fail(NamedGuestError(validationError) ? NamedGuestError(validationError) : GuestInvalid); +#ifdef __APPLE__ + NotImplemented_nid_no_patch("shm_unlink"); +#endif HostShmLock namespaceLock; if (namespaceLock.Error() != 0) { errno = namespaceLock.Error(); diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index a0d6f80550..5c682df352 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -8,6 +8,7 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/FilesystemError.hpp" #include "prx/libc/include/RenameDot.hpp" +#include "prx/libc/include/WindowsVerbatimPath.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include "prx/libkernel/File/include/File.hpp" @@ -75,20 +76,49 @@ template static auto WithoutParameterHandler(TCall call) { return result; } static int NativeRmdir(const std::filesystem::path& path) { - return ::_wrmdir(path.wstring().c_str()); + std::error_code error; + const auto verbatim = NativeVerbatimPath_nid_no_patch(path, error, WindowsMaxDirectoryPathLength); + const auto& target = error ? path : verbatim; + if (::RemoveDirectoryW(target.c_str())) return 0; + errno = WindowsOpenError_nid_no_patch(GetLastError()); + return -1; +} +static bool IsDirectoryEmpty(const std::filesystem::path& path) { + if (path.empty()) return true; + const auto search = (path.native().back() == L'\\' || path.native().back() == L'/') ? (path.native() + L"*") : (path.native() + L"\\*"); + WIN32_FIND_DATAW data{}; + const HANDLE handle = ::FindFirstFileW(search.c_str(), &data); + if (handle == INVALID_HANDLE_VALUE) return true; + bool empty = true; + do { + const auto* name = data.cFileName; + if (name[0] == L'.' && (name[1] == L'\0' || (name[1] == L'.' && name[2] == L'\0'))) continue; + empty = false; + break; + } while (::FindNextFileW(handle, &data)); + ::FindClose(handle); + return empty; } static bool RemoveEmptyDirectory(const std::filesystem::path& path) { - struct _stat64 status{}; - const bool unlocked = ::_wstat64(path.c_str(), &status) == 0 && !(status.st_mode & _S_IWRITE) && - ::_wchmod(path.c_str(), _S_IREAD | _S_IWRITE) == 0; + std::error_code pathError; + const auto verbatim = NativeVerbatimPath_nid_no_patch(path, pathError, WindowsMaxDirectoryPathLength); + const auto& target = pathError ? path : verbatim; + const DWORD attributes = ::GetFileAttributesW(target.c_str()); + const bool unlocked = attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_READONLY) && + ::SetFileAttributesW(target.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY); std::error_code error; - if (std::filesystem::remove(path, error)) return true; - if (unlocked) ::_wchmod(path.c_str(), _S_IREAD); - return false; + const bool removed = ::RemoveDirectoryW(target.c_str()) != 0 || std::filesystem::remove(target, error); + if (!removed && unlocked) ::SetFileAttributesW(target.c_str(), attributes); + return removed; } static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) { (void)mode; - return ::_wmkdir(path.wstring().c_str()); + std::error_code error; + const auto verbatim = NativeVerbatimPath_nid_no_patch(path, error, WindowsMaxDirectoryPathLength); + const auto& target = error ? path : verbatim; + if (::CreateDirectoryW(target.c_str(), nullptr)) return 0; + errno = WindowsOpenError_nid_no_patch(GetLastError()); + return -1; } static std::optional HandlePath(HANDLE handle) { std::wstring path(MAX_PATH, L'\0'); @@ -119,12 +149,15 @@ std::optional File::WindowsDescriptorPath_nid_no_patch(in return HandlePath(handle); } static int NativeChmod(const std::filesystem::path& path, int mode) { - const DWORD attributes = GetFileAttributesW(path.c_str()); + std::error_code pathError; + const auto verbatim = NativeVerbatimPath_nid_no_patch(path, pathError); + const auto& checkPath = pathError ? path : verbatim; + const DWORD attributes = GetFileAttributesW(checkPath.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); + return ::_wchmod(checkPath.wstring().c_str(), mode); } - const HANDLE handle = CreateFileW(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + const HANDLE handle = CreateFileW(checkPath.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()); @@ -134,7 +167,9 @@ static int NativeChmod(const std::filesystem::path& path, int mode) { CloseHandle(handle); if (!target) return -1; RecordWrittenPath_nid_no_patch(*target); - return ::_wchmod(target->wstring().c_str(), mode); + const auto verbatimTarget = NativeVerbatimPath_nid_no_patch(*target, pathError); + const auto& finalTarget = pathError ? *target : verbatimTarget; + return ::_wchmod(finalTarget.wstring().c_str(), mode); } static int NativeFchmod(int descriptor, int mode) { const auto path = File::WindowsDescriptorPath_nid_no_patch(descriptor); @@ -172,11 +207,14 @@ static int NativeFtruncate(int descriptor, std::int64_t length) { } } static int NativeTruncate(const std::filesystem::path& path, std::int64_t length) { - const int descriptor = ::_wopen(path.c_str(), _O_WRONLY | _O_BINARY); + std::error_code error; + const auto verbatim = NativeVerbatimPath_nid_no_patch(path, error); + const auto& target = error ? path : verbatim; + const int descriptor = ::_wopen(target.c_str(), _O_WRONLY | _O_BINARY); if (descriptor < 0) return errno; - const int error = NativeFtruncate(descriptor, length); + const int err = NativeFtruncate(descriptor, length); ::_close(descriptor); - return error; + return err; } static int SetTimes(HANDLE handle, const KernelTimeval* times) { FILETIME access{}, modified{}; @@ -195,7 +233,10 @@ static int SetTimes(HANDLE handle, const KernelTimeval* times) { 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_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + std::error_code error; + const auto verbatim = NativeVerbatimPath_nid_no_patch(path, error); + const auto& target = error ? path : verbatim; + const auto handle = CreateFileW(target.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); @@ -246,7 +287,7 @@ struct PositionedLock { }; std::mutex g_positionedTableLock; std::unordered_map> g_positionedLocks; -std::unique_lock File::LockPositionedTransfer(int descriptor) { +File::PositionedTransferLock File::LockPositionedTransfer(int descriptor) { std::shared_ptr entry; { std::lock_guard table(g_positionedTableLock); @@ -254,7 +295,7 @@ std::unique_lock File::LockPositionedTransfer(int descriptor) { if (!slot) slot = std::make_shared(); entry = slot; } - return std::unique_lock(entry->mutex); + return PositionedTransferLock(std::shared_ptr(entry, &entry->mutex)); } void File::SharePositionedTransfer(int from, int to) { std::lock_guard table(g_positionedTableLock); @@ -373,6 +414,9 @@ static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nb #include #include #include +#ifdef __APPLE__ +extern "C" ssize_t __getdirentries64(int fd, void* buffer, size_t bytes, off_t* base); +#endif static int NativeRmdir(const std::filesystem::path& path) { return ::rmdir(path.c_str()); } @@ -596,26 +640,46 @@ static bool NativeIsPipe(int descriptor) { } #else #include +#ifdef __APPLE__ +#include +#else #include +#endif static bool NativeIsPipe(int descriptor) { +#ifdef __APPLE__ + struct stat file{}; + struct statfs kind{}; + return ::fstat(descriptor, &file) == 0 && S_ISFIFO(file.st_mode) && ::fstatfs(descriptor, &kind) != 0; +#else constexpr long PipeFilesystem = 0x50495045; struct stat file{}; struct statfs kind{}; return ::fstat(descriptor, &file) == 0 && S_ISFIFO(file.st_mode) && ::fstatfs(descriptor, &kind) == 0 && static_cast(kind.f_type) == PipeFilesystem; +#endif } static int NativeFstatfs(int descriptor, KernelStatfs* fs) { struct stat file{}; if (::fstat(descriptor, &file) != 0) return SceErrorFromErrno(errno) & 0xffff; +#ifdef __APPLE__ + if (S_ISSOCK(file.st_mode)) return GUEST_EINVAL; + if (NativeIsPipe(descriptor)) return GUEST_EINVAL; +#else if (S_ISSOCK(file.st_mode) && (::fcntl(descriptor, F_GETFL) & O_PATH) == 0) return GUEST_EINVAL; if (NativeIsPipe(descriptor)) return GUEST_EINVAL; +#endif 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; if (host.f_flag & ST_NOSUID) fs->f_flags |= KERNEL_MNT_NOSUID; +#ifdef __APPLE__ + struct statfs mount{}; + if (::fstatfs(descriptor, &mount) == 0 && (mount.f_flags & MNT_NOEXEC)) fs->f_flags |= KERNEL_MNT_NOEXEC; +#else if (host.f_flag & ST_NOEXEC) fs->f_flags |= KERNEL_MNT_NOEXEC; +#endif fs->f_bsize = host.f_frsize != 0 ? host.f_frsize : host.f_bsize; fs->f_iosize = host.f_bsize; fs->f_blocks = host.f_blocks; @@ -630,8 +694,6 @@ static int NativeFstatfs(int descriptor, KernelStatfs* fs) { extern "C" int APS5_VABI pipe_nid_postfix(int* descriptors) { if (!descriptors) return PosixFailure(GUEST_EFAULT); - const GuestArena::HostWrite destination(descriptors, 2 * sizeof(int)); - if (!destination.Open()) return PosixFailure(GUEST_EFAULT); int native[2]; #ifdef _WIN32 const int result = ::_pipe(native, 65536, _O_BINARY); @@ -641,7 +703,11 @@ extern "C" int APS5_VABI pipe_nid_postfix(int* descriptors) { if (result != 0) return PosixFailure(SceErrorFromErrno(errno) & 0xffff); File::RememberPipe(native[0]); File::RememberPipe(native[1]); - std::memcpy(descriptors, native, sizeof(native)); + if (!GuestCopyOut_nid_no_patch(descriptors, native, sizeof(native))) { + close_nid_postfix(native[0]); + close_nid_postfix(native[1]); + return PosixFailure(GUEST_EFAULT); + } return 0; } @@ -652,7 +718,12 @@ static int PathError(const char* path, std::string& copy) { #ifdef _WIN32 bool DescriptorIsOpen_nid_no_patch(int descriptor) { - return descriptor >= 0 && WithoutParameterHandler([descriptor] { return ::_get_osfhandle(descriptor) == -1 ? -1 : 0; }) == 0; + return descriptor >= 0 && WithoutParameterHandler([descriptor] { + const auto handle = ::_get_osfhandle(descriptor); + if (handle == -1) return -1; + DWORD flags = 0; + return handle == -2 || GetHandleInformation(reinterpret_cast(handle), &flags) ? 0 : -1; + }) == 0; } #else bool DescriptorIsOpen_nid_no_patch(int descriptor) { @@ -667,6 +738,8 @@ static std::filesystem::path WithoutTrailingSeparator(const std::filesystem::pat static bool HasNonDirectoryPrefix(const std::filesystem::path& native) { std::error_code error; for (auto prefix = WithoutTrailingSeparator(native).parent_path(); prefix.has_relative_path(); prefix = prefix.parent_path()) { + const auto link = LinkPrefixState_nid_no_patch(prefix); + if (link != LinkPrefix_nid_no_patch::Resolves) return link == LinkPrefix_nid_no_patch::BlockedByFile; const auto status = std::filesystem::status(prefix, error); if (std::filesystem::exists(status)) return !std::filesystem::is_directory(status); } @@ -695,20 +768,26 @@ 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); + std::error_code pathError; + const auto verbatim = NativeVerbatimPath_nid_no_patch(native, pathError); + const auto& checkNative = pathError ? native : verbatim; + const auto named = checkNative.has_filename() ? checkNative : WithoutTrailingSeparator(checkNative); 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; + return checkNative.has_filename() || (target.st_mode & 0170000) == 0040000 ? 0 : GUEST_ENOTDIR; } + const auto& statusNative = checkNative; +#else + const auto& statusNative = native; #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; + const auto status = std::filesystem::status(statusNative, error); + if (std::filesystem::exists(status)) return statusNative.has_filename() || std::filesystem::is_directory(status) ? 0 : GUEST_ENOTDIR; if (status.type() != std::filesystem::file_type::not_found) return SceErrorFromErrno(error.default_error_condition().value()) & 0xffff; - const bool fileWithSeparator = !native.has_filename() && std::filesystem::exists(WithoutTrailingSeparator(native), error); - return fileWithSeparator || HasNonDirectoryPrefix(native) ? GUEST_ENOTDIR : GUEST_ENOENT; + const bool fileWithSeparator = !statusNative.has_filename() && std::filesystem::exists(WithoutTrailingSeparator(statusNative), error); + return fileWithSeparator || HasNonDirectoryPrefix(statusNative) ? GUEST_ENOTDIR : GUEST_ENOENT; } static constexpr std::pair GUEST_FILE_FLAGS[] = { @@ -717,7 +796,7 @@ static constexpr std::pair GUEST_FILE_FLAGS[] = { {0x00000400, "UF_REPARSE"}, {0x00000800, "UF_ARCHIVE"}, {0x00001000, "UF_READONLY"}, {0x00008000, "UF_HIDDEN"}, {0x00010000, "SF_ARCHIVED"}, {0x00020000, "SF_IMMUTABLE"}, {0x00040000, "SF_APPEND"}, {0x00100000, "SF_NOUNLINK"}, {0x00200000, "SF_SNAPSHOT"}}; -static constexpr std::uint64_t GUEST_SF_SNAPSHOT = 0x00200000; +static constexpr std::uint64_t GUEST_SF_SETTABLE = 0xffff0000; static std::string FileFlagNames(std::uint64_t flags) { std::string names; @@ -733,7 +812,7 @@ static int SetFileFlags(const char* function, const std::string& target, std::ui std::uint64_t known = 0; for (const auto& entry : GUEST_FILE_FLAGS) known |= entry.first; if ((flags & ~known) != 0) return GUEST_EOPNOTSUPP; - if ((flags & GUEST_SF_SNAPSHOT) != 0) return GUEST_EPERM; + if ((flags & GUEST_SF_SETTABLE) != 0) return GUEST_EPERM; if (flags != 0) throw std::runtime_error(std::string(function) + ": setting " + FileFlagNames(flags) + " on " + target + " is not supported, the host keeps no BSD file flags"); return 0; @@ -806,12 +885,8 @@ int APS5_VABI close_nid_postfix(int d) { #ifdef _WIN32 const auto positioned = File::LockPositionedTransfer(d); #endif - File::ForgetRandomDevice(d); - File::ForgetDescriptor(d); - ForgetJitSharedMemory(d); - File::ForgetDirectoryDescriptor(d); + File::ForgetClosedDescriptor(d); #ifdef _WIN32 - File::ForgetFileLock(d); const int shared = File::CloseSharedMemoryDescriptor_nid_no_patch(d); if (shared != -2) return shared; return WithoutParameterHandler([d] { return ::_close(d); }); @@ -931,7 +1006,7 @@ int APS5_VABI dup2_nid_postfix(int from, int to) { int APS5_VABI flock_nid_postfix(int d, int operation) { if (File::IsVirtualDirectoryDescriptor(d)) return PosixFailure(GUEST_EISDIR); - if (GuestSockets::IsOpen(d)) return PosixFailure(GUEST_EOPNOTSUPP); + if (GuestSockets::IsOpen(d) || IsKqueueDescriptor_nid_no_patch(d)) return PosixFailure(GUEST_EOPNOTSUPP); File::RefuseArchivedFile(d, "flock"); #ifdef _WIN32 const auto handle = reinterpret_cast(WithoutParameterHandler([d] { return ::_get_osfhandle(d); })); @@ -1166,8 +1241,15 @@ int APS5_VABI sceKernelCheckReachability(const char* path) { std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(path, native, true, true)) return SceKernelError(error); if (AppArchiveOnly_nid_no_patch(native)) return 0; +#ifdef _WIN32 + std::error_code pathError; + const auto verbatim = NativeVerbatimPath_nid_no_patch(native, pathError); + const auto& checkNative = pathError ? native : verbatim; +#else + const auto& checkNative = native; +#endif std::error_code error; - return std::filesystem::exists(native, error) ? 0 : SCE_KERNEL_ERROR_ENOENT; + return std::filesystem::exists(checkNative, error) ? 0 : SCE_KERNEL_ERROR_ENOENT; } int APS5_VABI sceKernelFstat(int d, FileStat* sb) { @@ -1189,7 +1271,7 @@ int APS5_VABI sceKernelFstat(int d, FileStat* sb) { int APS5_VABI sceKernelFsync(int fd) { if (File::IsVirtualDirectoryDescriptor(fd)) return SceErrorFromErrno(GUEST_EISDIR); - if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL); + if (GuestSockets::IsOpen(fd) || IsKqueueDescriptor_nid_no_patch(fd)) return SceErrorFromErrno(GUEST_EINVAL); File::RefuseArchivedFile(fd, "fsync"); #ifdef _WIN32 if (NativeSync(fd) != 0) return SceErrorFromErrno(errno); @@ -1201,10 +1283,12 @@ int APS5_VABI sceKernelFsync(int fd) { int APS5_VABI sceKernelFdatasync(int fd) { if (File::IsVirtualDirectoryDescriptor(fd)) return SceErrorFromErrno(GUEST_EISDIR); - if (GuestSockets::IsOpen(fd)) return SceErrorFromErrno(GUEST_EINVAL); + if (GuestSockets::IsOpen(fd) || IsKqueueDescriptor_nid_no_patch(fd)) return SceErrorFromErrno(GUEST_EINVAL); File::RefuseArchivedFile(fd, "fdatasync"); #ifdef _WIN32 if (NativeSync(fd) != 0) return SceErrorFromErrno(errno); +#elif defined(__APPLE__) + if (::fsync(fd) != 0) return SceErrorFromErrno(errno); #else if (::fdatasync(fd) != 0) return SceErrorFromErrno(errno); #endif @@ -1255,6 +1339,85 @@ static bool InsideApp0(int fd) { return inside; } +#ifdef __APPLE__ +static unsigned char GuestEntryType(int fd, const struct dirent& 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"); + constexpr std::size_t GuestHeaderBytes = 8; + constexpr std::size_t GuestMaxName = 255; + if (buf == nullptr) return SceErrorFromErrno(GUEST_EFAULT); + if (nbytes <= 0) return SceErrorFromErrno(GUEST_EINVAL); + const GuestArena::HostWrite destination(buf, static_cast(nbytes)); + const GuestArena::HostWrite baseDestination(basep, basep == nullptr ? 0 : sizeof(*basep)); + if (!destination.Open() || !baseDestination.Open()) return SceErrorFromErrno(GUEST_EFAULT); + if (File::IsDirectoryDescriptor(fd)) return File::ReadDirectoryDescriptor(fd, buf, nbytes, basep); + struct stat status{}; + if (::fstat(fd, &status) != 0) return SceErrorFromErrno(errno); + if (!S_ISDIR(status.st_mode)) return SceErrorFromErrno(GUEST_EINVAL); + const off_t base = ::lseek(fd, 0, SEEK_CUR); + if (base < 0) return SceErrorFromErrno(errno); + if (basep != nullptr) *basep = static_cast(base); + if (::lseek(fd, 0, SEEK_SET) < 0) return SceErrorFromErrno(errno); + std::vector listing; + for (;;) { + constexpr std::size_t Chunk = 64 * 1024; + const auto start = listing.size(); + listing.resize(start + Chunk); + off_t position = 0; + const auto read = ::__getdirentries64(fd, listing.data() + start, Chunk, &position); + if (read < 0) { + const int error = errno; + ::lseek(fd, base, SEEK_SET); + return SceErrorFromErrno(error); + } + listing.resize(start + static_cast(read)); + if (read == 0) break; + } + const bool followLinks = InsideApp0(fd); + std::size_t written = 0; + off_t index = 0; + for (std::size_t offset = 0; offset < listing.size(); ++index) { + const auto* entry = reinterpret_cast(listing.data() + offset); + if (entry->d_reclen == 0) break; + offset += entry->d_reclen; + if (index < base) continue; + const auto nameLength = std::strlen(entry->d_name); + const auto record = (GuestHeaderBytes + nameLength + 1 + 3) & ~std::size_t{3}; + if (nameLength > GuestMaxName || written + record > static_cast(nbytes)) { + if (::lseek(fd, index, SEEK_SET) < 0) return SceErrorFromErrno(errno); + if (written != 0) return static_cast(written); + return nameLength > GuestMaxName ? static_cast(0x80020000u | GUEST_ENAMETOOLONG) : SceErrorFromErrno(GUEST_EINVAL); + } + char* out = buf + written; + std::memset(out, 0, record); + const auto fileNumber = static_cast(entry->d_ino); + const auto recordLength = static_cast(record); + const auto nameBytes = static_cast(nameLength); + std::memcpy(out, &fileNumber, sizeof(fileNumber)); + std::memcpy(out + 4, &recordLength, sizeof(recordLength)); + out[6] = static_cast(GuestEntryType(fd, *entry, followLinks)); + out[7] = static_cast(nameBytes); + std::memcpy(out + GuestHeaderBytes, entry->d_name, nameLength); + written += record; + } + if (::lseek(fd, index, SEEK_SET) < 0) return SceErrorFromErrno(errno); + return static_cast(written); +} +#else 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; @@ -1324,6 +1487,7 @@ int APS5_VABI sceKernelGetdirentries(int fd, char* buf, int nbytes, int64_t* bas if (::lseek(fd, resume, SEEK_SET) < 0) return SceErrorFromErrno(errno); return static_cast(written); } +#endif int APS5_VABI sceKernelGetdents(int fd, char* buf, int nbytes) { return sceKernelGetdirentries(fd, buf, nbytes, nullptr); @@ -1346,22 +1510,29 @@ int APS5_VABI getdents_nid_postfix(int fd, char* buf, int nbytes) { int APS5_VABI sceKernelMkdir(const char* path, uint16_t mode) { (void)mode; std::string copy; - if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(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(); - std::error_code error; - 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); + std::error_code pathError; + const auto verbatimNative = NativeVerbatimPath_nid_no_patch(native, pathError, WindowsMaxDirectoryPathLength); + const auto& targetNative = pathError ? native : verbatimNative; +#else + const auto& targetNative = native; #endif - return SceErrorFromErrno(HasNonDirectoryPrefix(native) ? GUEST_ENOTDIR : GUEST_ENOENT); + std::error_code error; + if (std::filesystem::exists(targetNative, error)) return SceErrorFromErrno(GUEST_EEXIST); + if (const int failure = PathLookupFailure(error)) return failure; + if (!std::filesystem::is_directory(WithoutTrailingSeparator(targetNative).parent_path(), error)) { +#ifdef _WIN32 + if (File::WindowsSymbolicLinkLoop(WithoutTrailingSeparator(targetNative).parent_path())) return SceKernelError(GUEST_ELOOP); +#endif + return SceErrorFromErrno(HasNonDirectoryPrefix(targetNative) ? GUEST_ENOTDIR : GUEST_ENOENT); } - if (!std::filesystem::create_directory(native, error)) return SceErrorFromErrno(error.value() ? error.value() : GUEST_EIO); + if (!std::filesystem::create_directory(targetNative, error)) return SceErrorFromErrno(error.value() ? error.value() : GUEST_EIO); RecordWrittenPath_nid_no_patch(native); return 0; } @@ -1556,6 +1727,16 @@ int64_t APS5_VABI sceKernelPwrite(int d, const void* buf, size_t nbytes, int64_t return result < 0 ? SceErrorFromErrno(errno) : result; } +#ifdef __APPLE__ +static std::int64_t EmptyIovecsResult(int d, bool write, bool positioned) { + const int flags = ::fcntl(d, F_GETFL); + if (flags < 0) return SceErrorFromErrno(errno); + if ((flags & O_ACCMODE) == (write ? O_RDONLY : O_WRONLY)) return SceErrorFromErrno(EBADF); + if (positioned && ::lseek(d, 0, SEEK_CUR) < 0) return SceErrorFromErrno(errno); + return 0; +} +#endif + int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* guestIov, int iovcnt) { if (File::IsDirectoryDescriptor(d)) return SceErrorFromErrno(GUEST_EISDIR); std::vector vectors; @@ -1569,6 +1750,9 @@ int64_t APS5_VABI sceKernelReadv(int d, const KernelIovec* guestIov, int iovcnt) } if (File::IsRandomDevice(d)) return ReadRandomIovecs(d, iov, iovcnt); if (const auto archived = File::ReadArchivedIovecs(d, iov, iovcnt, nullptr)) return *archived; +#ifdef __APPLE__ + if (iovcnt == 0) return EmptyIovecsResult(d, false, false); +#endif const auto result = static_cast(::readv(d, NativeIovecs(iov), iovcnt)); return result < 0 ? SceErrorFromErrno(errno) : result; } @@ -1586,6 +1770,9 @@ int64_t APS5_VABI sceKernelWritev(int d, const KernelIovec* guestIov, int iovcnt PrepareWriteIovecs(iov, iovcnt); const auto sharedResult = File::WritevSharedMemory_nid_no_patch(d, NativeIovecs(iov), iovcnt); if (sharedResult != -2) return sharedResult < 0 ? SceErrorFromErrno(errno) : sharedResult; +#ifdef __APPLE__ + if (iovcnt == 0) return EmptyIovecsResult(d, true, false); +#endif const auto result = static_cast(::writev(d, NativeIovecs(iov), iovcnt)); return result < 0 ? SceErrorFromErrno(errno) : result; } @@ -1601,6 +1788,9 @@ int64_t APS5_VABI sceKernelPreadv(int d, const KernelIovec* guestIov, int iovcnt if (!OpenIovecs(iov, iovcnt, destinations)) return SceErrorFromErrno(GUEST_EFAULT); if (File::IsRandomDevice(d)) return ReadRandomIovecs(d, iov, iovcnt); if (const auto archived = File::ReadArchivedIovecs(d, iov, iovcnt, &offset)) return *archived; +#ifdef __APPLE__ + if (iovcnt == 0) return EmptyIovecsResult(d, false, true); +#endif const auto result = static_cast(::preadv(d, NativeIovecs(iov), iovcnt, static_cast(offset))); return result < 0 ? SceErrorFromErrno(errno) : result; } @@ -1617,6 +1807,7 @@ int64_t APS5_VABI sceKernelPwritev(int d, const KernelIovec* guestIov, int iovcn const auto sharedResult = File::PwritevSharedMemory_nid_no_patch(d, NativeIovecs(iov), iovcnt, offset); if (sharedResult != -2) return sharedResult < 0 ? SceErrorFromErrno(errno) : sharedResult; #ifdef __APPLE__ + if (iovcnt == 0) return EmptyIovecsResult(d, true, true); if (const auto appended = File::WritevAtEnd(d, NativeIovecs(iov), iovcnt); appended != -2) return appended < 0 ? SceErrorFromErrno(errno) : appended; #endif const auto result = static_cast(::pwritev(d, NativeIovecs(iov), iovcnt, static_cast(offset))); @@ -1643,33 +1834,44 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) { int APS5_VABI sceKernelRmdir(const char* path) { std::string copy; - if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(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); +#ifdef _WIN32 + std::error_code pathError; + const auto verbatimNative = NativeVerbatimPath_nid_no_patch(native, pathError, WindowsMaxDirectoryPathLength); + const auto& targetNative = pathError ? native : verbatimNative; +#else + const auto& targetNative = native; +#endif FileStat linkStatus{}; - if (native.has_filename() && File::FillLinkStat(native, &linkStatus) && (linkStatus.st_mode & 0170000) == 0120000) return SceErrorFromErrno(GUEST_ENOTDIR); + if (targetNative.has_filename() && File::FillLinkStat(targetNative, &linkStatus) && (linkStatus.st_mode & 0170000) == 0120000) return SceErrorFromErrno(GUEST_ENOTDIR); std::error_code error; - const auto status = std::filesystem::status(native, error); + const auto status = std::filesystem::status(targetNative, error); if (const int failure = PathLookupFailure(error)) return failure; #ifdef _WIN32 - const DWORD attributes = GetFileAttributesW(native.c_str()); + const DWORD attributes = GetFileAttributesW(targetNative.c_str()); if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { std::string junctionTarget; - if (File::WindowsSymbolicLink::ReadJunction(native, junctionTarget, "rmdir")) return SceErrorFromErrno(GUEST_ENOTDIR); + if (File::WindowsSymbolicLink::ReadJunction(targetNative, junctionTarget, "rmdir")) return SceErrorFromErrno(GUEST_ENOTDIR); } #endif if (!std::filesystem::is_directory(status)) { #ifdef _WIN32 - if (File::WindowsSymbolicLinkLoop(WithoutTrailingSeparator(native).parent_path())) return SceKernelError(GUEST_ELOOP); + if (File::WindowsSymbolicLinkLoop(WithoutTrailingSeparator(targetNative).parent_path())) return SceKernelError(GUEST_ELOOP); #endif - const bool blockedByFile = HasNonDirectoryPrefix(native) || std::filesystem::exists(WithoutTrailingSeparator(native), error); + const bool blockedByFile = HasNonDirectoryPrefix(targetNative) || std::filesystem::exists(WithoutTrailingSeparator(targetNative), error); return SceErrorFromErrno(blockedByFile ? GUEST_ENOTDIR : GUEST_ENOENT); } - if (!std::filesystem::is_empty(native, error)) return SceErrorFromErrno(GUEST_ENOTEMPTY); - if (!RemoveEmptyDirectory(native)) return SceErrorFromErrno(GUEST_EIO); +#ifdef _WIN32 + if (!IsDirectoryEmpty(targetNative)) return SceErrorFromErrno(GUEST_ENOTEMPTY); +#else + if (!std::filesystem::is_empty(targetNative, error)) return SceErrorFromErrno(GUEST_ENOTEMPTY); +#endif + if (!RemoveEmptyDirectory(targetNative)) return SceErrorFromErrno(GUEST_EIO); RecordWrittenPath_nid_no_patch(native); return 0; } @@ -1687,7 +1889,7 @@ extern "C" { int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) { std::string copy; - if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(error); path = copy.c_str(); if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT); std::filesystem::path native; @@ -1727,24 +1929,26 @@ static bool CopyInTimes(const KernelTimeval*& times, KernelTimeval (&local)[2]) int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t length) { if (length < 0) return SceErrorFromErrno(GUEST_EINVAL); std::string copy; - if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(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; - const auto status = std::filesystem::status(native, error); + const auto verbatim = NativeVerbatimPath_nid_no_patch(native, error); + const auto& target = error ? native : verbatim; + const auto status = std::filesystem::status(target, error); if (const int failure = PathLookupFailure(error)) return failure; if (!std::filesystem::exists(status)) { #ifdef _WIN32 - if (File::WindowsSymbolicLinkLoop(native)) return SceKernelError(GUEST_ELOOP); + if (File::WindowsSymbolicLinkLoop(target)) return SceKernelError(GUEST_ELOOP); #endif return SceErrorFromErrno(NotFoundError_nid_no_patch(native)); } if (std::filesystem::is_directory(status)) return SceErrorFromErrno(GUEST_EISDIR); - if (const int failure = NativeTruncate(native, length)) { + if (const int failure = NativeTruncate(target, length)) { #ifdef _WIN32 - if (File::WindowsSymbolicLinkLoop(native)) return SceKernelError(GUEST_ELOOP); + if (File::WindowsSymbolicLinkLoop(target)) return SceKernelError(GUEST_ELOOP); #endif return SceErrorFromErrno(failure); } @@ -1765,7 +1969,7 @@ int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* if (!CopyInTimes(times, local)) return SCE_KERNEL_ERROR_EFAULT; if (InvalidTimes(times)) return SCE_KERNEL_ERROR_EINVAL; std::string copy; - if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceErrorFromErrno(error); + if (const int error = CopyGuestPath_nid_no_patch(path, copy)) return SceKernelError(error); path = copy.c_str(); if (copy.empty()) return SceErrorFromErrno(GUEST_ENOENT); std::filesystem::path native; diff --git a/core/libs/prx/libkernel/File/src/Terminal.cpp b/core/libs/prx/libkernel/File/src/Terminal.cpp index d8220e00bb..cdd4f8eec5 100644 --- a/core/libs/prx/libkernel/File/src/Terminal.cpp +++ b/core/libs/prx/libkernel/File/src/Terminal.cpp @@ -131,4 +131,6 @@ int APS5_VABI tcsetpgrp_nid_postfix(int descriptor, int group) { static_cast(group); return Fail(ProcessGroupError(descriptor, "tcsetpgrp: the SIGIO owner of a pipe is not supported")); } + +int APS5_VABI tcgetsid_nid_postfix(int descriptor) { return Fail(TerminalError(descriptor)); } } diff --git a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp index a0f46af6d2..250680c0cd 100644 --- a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp +++ b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -23,11 +24,17 @@ #include #else #include +#ifdef __APPLE__ +#include +#include +#include +#else #include #ifdef __linux__ #include #endif #endif +#endif #include "prx/libc/include/HostThreadLocal.hpp" #ifdef _WIN32 @@ -73,6 +80,10 @@ struct Module { bool global = false; #ifdef _WIN32 std::uintptr_t globalOrder = 0; +#endif +#ifdef __APPLE__ + const void* image = nullptr; + const void* loadBias = nullptr; #endif ~Module() { if (owned && native) { @@ -96,7 +107,59 @@ void* Symbol(Module& module, const char* name) { return ::dlsym(module.native, name); #endif } -#ifndef _WIN32 +#ifdef __APPLE__ +bool FromGuestModule(void* address) { + Dl_info info{}; + if (address == nullptr || !::dladdr(address, &info) || info.dli_fname == nullptr || info.dli_fbase == nullptr) return false; + if (static_cast(info.dli_fbase)->filetype == MH_EXECUTE) return true; + const std::string path(info.dli_fname); + return path.ends_with(".prx"); +} + +void FindImage(Module& module, bool mainProgram) { + for (std::uint32_t index = 0; index < _dyld_image_count() && module.image == nullptr; ++index) { + const auto* header = reinterpret_cast(_dyld_get_image_header(index)); + if (header == nullptr) continue; + if (mainProgram) { + if (header->filetype == MH_EXECUTE) module.image = header; + continue; + } + void* loaded = ::dlopen(_dyld_get_image_name(index), RTLD_LAZY | RTLD_NOLOAD); + if (loaded == nullptr) continue; + if (loaded == module.native) module.image = header; + ::dlclose(loaded); + } + module.loadBias = module.image; + unsigned long size = 0; + const auto* meta = module.image ? getsectiondata(static_cast(module.image), "__APS5DATA", "__meta", &size) : nullptr; + std::uint64_t version = 0; + if (meta != nullptr && size >= 0x60 && (std::memcpy(&version, meta, sizeof(version)), version == 2)) { + std::uint64_t loadBias = 0; + std::memcpy(&loadBias, meta + 0x08, sizeof(loadBias)); + module.loadBias = reinterpret_cast(static_cast(loadBias)); + } +} + +bool HiddenImage(const void* image) { + if (image == nullptr || static_cast(image)->filetype == MH_EXECUTE) return false; + bool local = false; + for (const auto& [key, module] : modules) { + if (module->image != image) continue; + if (module->global) return false; + local = true; + } + return local; +} + +void* DefaultScopeSymbol(const char* symbolName) { + auto* result = ::dlsym(RTLD_DEFAULT, symbolName); + if (!FromGuestModule(result)) return nullptr; + Dl_info info{}; + if (!::dladdr(result, &info) || info.dli_fbase == nullptr) return result; + std::lock_guard lock(modulesMutex); + return HiddenImage(info.dli_fbase) ? nullptr : result; +} +#elif !defined(_WIN32) const link_map* MainProgramMap() { static const link_map* const map = [] { void* handle = ::dlopen(nullptr, RTLD_LAZY); @@ -278,8 +341,12 @@ void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) { #else const auto source = path ? ResolvePath_nid_no_patch(path) : std::filesystem::path{}; const auto resolved = path ? RelinkedModulePath_nid_no_patch(source) : std::filesystem::path{}; +#ifdef __APPLE__ + const int nativeFlags = ((flags & 3) == 1 ? RTLD_LAZY : RTLD_NOW) | RTLD_GLOBAL; +#else const int nativeFlags = ((flags & 3) == 1 ? RTLD_LAZY : RTLD_NOW) | ((flags & 0x100) ? RTLD_GLOBAL : RTLD_LOCAL); +#endif module->native = ::dlopen(path ? resolved.c_str() : nullptr, nativeFlags); if (!module->native) { if (path) LoadFailure(source, resolved, ::dlerror()); else Error(::dlerror()); @@ -288,6 +355,9 @@ void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) { #ifdef __linux__ if (path) InitializeLinuxGuestModule(module->native); #endif +#ifdef __APPLE__ + FindImage(*module, path == nullptr); +#endif #endif std::lock_guard lock(modulesMutex); const auto handle = nextHandle++; @@ -313,9 +383,17 @@ void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name) { if (!name || !*name) { Error("dlsym: empty symbol name"); return nullptr; } try { std::vector> search; + bool defaultScope = handle == nullptr || handle == reinterpret_cast(static_cast(-2)); { std::lock_guard lock(modulesMutex); - if (handle == nullptr || handle == reinterpret_cast(static_cast(-2))) { +#ifdef __APPLE__ + if (!defaultScope) { + auto found = modules.find(reinterpret_cast(handle)); + defaultScope = found != modules.end() && found->second->image != nullptr && + static_cast(found->second->image)->filetype == MH_EXECUTE; + } +#endif + if (defaultScope) { for (const auto& [key, module] : modules) if (module->global) search.push_back(module); #ifdef _WIN32 std::sort(search.begin(), search.end(), [](const auto& left, const auto& right) { return left->globalOrder < right->globalOrder; }); @@ -329,7 +407,7 @@ void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name) { } for (const auto& module : search) if (auto* result = FindSymbol(*module, name)) return result; #ifndef _WIN32 - if (handle == nullptr || handle == reinterpret_cast(static_cast(-2))) { + if (defaultScope) { if (auto* result = DefaultScopeSymbol(name)) return result; const auto nid = Nid::ComputeNid(name, ""); if (auto* result = DefaultScopeSymbol(nid.c_str())) return result; @@ -358,6 +436,8 @@ int APS5_VABI dlclose_nid_postfix(void* handle) { GuestAllocations::Mutation mutation; #ifdef _WIN32 mutation.UnregisterImage(module->native); +#elif defined(__APPLE__) + mutation.UnregisterImage(module->loadBias); #else mutation.UnregisterImage(reinterpret_cast(static_cast(module->native)->l_addr)); #endif diff --git a/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp b/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp index 37115f46f1..b9b95408ff 100644 --- a/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp +++ b/core/libs/prx/libkernel/Module/src/ModuleInfo.cpp @@ -23,7 +23,14 @@ #include #else #include +#ifdef __APPLE__ +#include +#include +#include +#include "prx/libc/include/specifics/linux/ElfTypes.hpp" +#else #include +#endif #include #include #include @@ -72,7 +79,159 @@ std::uint64_t PageSpan(std::uint64_t address, std::uint64_t size) { return ((address + size + pageMask) & ~pageMask) - (address & ~pageMask); } -#ifndef _WIN32 +#if defined(__APPLE__) +struct MachImage { + const mach_header_64* header; + std::uintptr_t slide; + const char* path; +}; + +std::vector MachImages() { + std::vector images; + const auto count = _dyld_image_count(); + for (std::uint32_t index = 0; index < count; ++index) { + const auto* header = reinterpret_cast(_dyld_get_image_header(index)); + if (header == nullptr || header->magic != MH_MAGIC_64) continue; + const MachImage image{header, static_cast(_dyld_get_image_vmaddr_slide(index)), _dyld_get_image_name(index)}; + if (header->filetype == MH_EXECUTE) images.insert(images.begin(), image); + else images.push_back(image); + } + return images; +} + +template +void ForEachSegment(const MachImage& image, TVisit&& visit) { + const auto* command = reinterpret_cast(image.header + 1); + for (std::uint32_t i = 0; i < image.header->ncmds; ++i) { + if (command->cmd == LC_SEGMENT_64) visit(*reinterpret_cast(command)); + command = reinterpret_cast(reinterpret_cast(command) + command->cmdsize); + } +} + +bool GuestSegment(const segment_command_64& segment) { + return std::strncmp(segment.segname, "__ELF", 5) == 0; +} + +bool Relinked(const MachImage& image) { + bool relinked = false; + ForEachSegment(image, [&](const segment_command_64& segment) { relinked = relinked || GuestSegment(segment); }); + return relinked; +} + +bool Reported(const segment_command_64& segment, bool relinked) { + if (segment.vmsize == 0 || (segment.initprot & (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE)) == 0) return false; + return relinked ? GuestSegment(segment) : std::strncmp(segment.segname, SEG_LINKEDIT, sizeof(segment.segname)) != 0; +} + +std::int32_t Protection(const segment_command_64& segment) { + return ((segment.initprot & VM_PROT_READ) ? ProtRead : 0) | ((segment.initprot & VM_PROT_WRITE) ? ProtWrite : 0) | + ((segment.initprot & VM_PROT_EXECUTE) ? ProtExecute : 0); +} + +std::string_view ImagePath(const MachImage& image) { + return image.path ? image.path : ""; +} + +std::string ImageName(const MachImage& image) { + const std::string path(ImagePath(image)); + return ModuleName(path.substr(path.find_last_of('/') + 1)); +} + +void* ImageHandle(const MachImage& image) { + void* handle = ::dlopen(image.header->filetype == MH_EXECUTE ? nullptr : image.path, RTLD_LAZY | RTLD_NOLOAD); + if (!handle) throw std::runtime_error("module info: cannot open a loaded image"); + ::dlclose(handle); + return handle; +} + +const MachImage* ImageContaining(const std::vector& images, std::uintptr_t address) { + for (const auto& image : images) { + bool found = false; + ForEachSegment(image, [&](const segment_command_64& segment) { + const auto start = image.slide + segment.vmaddr; + if (segment.vmsize != 0 && std::strncmp(segment.segname, SEG_PAGEZERO, sizeof(segment.segname)) != 0 && + address >= start && address - start < segment.vmsize) + found = true; + }); + if (found) return ℑ + } + return nullptr; +} + +template +bool Fill(std::uintptr_t address, TInfo& info) { + const auto images = MachImages(); + const auto* image = ImageContaining(images, address); + if (!image) return false; + SetName(info, ImageName(*image)); + const bool relinked = Relinked(*image); + ForEachSegment(*image, [&](const segment_command_64& command) { + if (!Reported(command, relinked) || info.segment_count >= std::size(info.segments)) return; + auto& segment = info.segments[info.segment_count++]; + segment.address = image->slide + command.vmaddr; + segment.size = ToU32(command.vmsize, "segment size"); + segment.prot = Protection(command); + }); + if constexpr (Extended) { + info.id = ModuleIdForImage_nid_no_patch(ImageHandle(*image)); + info.ref_count = 1; + unsigned long size = 0; + if (const auto* frames = getsectiondata(image->header, SEG_TEXT, "__eh_frame", &size); frames && size != 0) { + info.eh_frame_addr = reinterpret_cast(frames); + info.eh_frame_size = ToU32(size, "eh_frame size"); + } + if (const auto* meta = getsectiondata(image->header, "__APS5DATA", "__meta", &size); meta && size >= 0x60) { + std::uint64_t version = 0, loadBias = 0, count = 0; + std::memcpy(&version, meta, sizeof(version)); + std::memcpy(&loadBias, meta + 0x08, sizeof(loadBias)); + std::memcpy(&count, meta + 0x58, sizeof(count)); + if (version == 2 && count <= (size - 0x60) / sizeof(Elf64_Phdr)) { + for (std::uint64_t index = 0; index < count; ++index) { + Elf64_Phdr header; + std::memcpy(&header, meta + 0x60 + index * sizeof(header), sizeof(header)); + if (header.p_type != PT_GNU_EH_FRAME) continue; + info.eh_frame_hdr_addr = loadBias + header.p_vaddr; + info.eh_frame_hdr_size = ToU32(header.p_memsz, "eh_frame_hdr size"); + break; + } + } + } + } + return true; +} + +std::vector GuestImages() { + std::vector images; + for (const auto& image : MachImages()) { + if (image.header->filetype != MH_EXECUTE && !ImagePath(image).ends_with(".prx")) continue; + images.push_back({ImageHandle(image), reinterpret_cast(image.header)}); + } + return images; +} + +std::uint64_t GuestSegmentBytes() { + constexpr auto allAccess = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE; + std::uint64_t bytes = 0; + for (const auto& image : MachImages()) { + if (image.header->filetype != MH_EXECUTE && !ImagePath(image).ends_with(GuestModuleSuffix)) continue; + const bool relinked = Relinked(image); + ForEachSegment(image, [&](const segment_command_64& segment) { + const bool header = std::strncmp(segment.segname, SEG_TEXT, sizeof(segment.segname)) == 0; + if (Reported(segment, relinked) && (relinked || !header) && (segment.initprot & allAccess) != allAccess) + bytes += PageSpan(segment.vmaddr, segment.vmsize); + }); + } + return bytes; +} + +bool SystemImage(const GuestImage& image) { + const auto* header = reinterpret_cast(image.address); + if (header->filetype == MH_EXECUTE) return false; + Dl_info symbol{}; + if (!::dladdr(header, &symbol) || symbol.dli_fname == nullptr) return true; + return !std::string_view(symbol.dli_fname).ends_with(GuestModuleSuffix); +} +#elif !defined(_WIN32) template struct ImageSearch { std::uintptr_t address; @@ -366,7 +525,7 @@ int FillModuleInfo(KernelModule handle, TInfo* info, bool titleOnly) { if (titleOnly && SystemImage(image)) return SCE_KERNEL_ERROR_EPERM; TInfo result{}; result.st_size = sizeof(TInfo); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) if (!Fill(image.address, result)) return SCE_KERNEL_ERROR_ESRCH; #else ImageSearch search{image.address, &result, false}; @@ -408,6 +567,11 @@ int APS5_VABI sceKernelGetModuleInfoFromAddr(std::uint64_t address, int flags, M 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}; +#elif defined(__APPLE__) + Dl_info symbol{}; + const bool found = Fill(static_cast(address), result) && + ::dladdr(reinterpret_cast(address), &symbol) != 0 && symbol.dli_fbase != nullptr; + const GuestImage image{nullptr, found ? reinterpret_cast(symbol.dli_fbase) : 0}; #else Dl_info symbol{}; link_map* native = nullptr; @@ -457,7 +621,7 @@ std::int32_t APS5_VABI sceKernelGetLibkernelTextLocation_nid_postfix(void** addr const auto images = GuestImages(); if (images.empty()) throw std::runtime_error("sceKernelGetLibkernelTextLocation: the main program is not loaded"); ModuleInfo info{}; -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) const bool found = Fill(images.front().address, info); #else ImageSearch search{images.front().address, &info, false}; diff --git a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp index 35db4e3053..d3e76df821 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp @@ -74,7 +74,7 @@ int APS5_VABI pthread_condattr_setclock_nid_postfix(PthreadCondattr* attr, Kerne int APS5_VABI pthread_condattr_getclock_nid_postfix(const PthreadCondattr* attr, KernelClockid* clockId) { if (!attr || !*attr) return PosixThread::GUEST_EINVAL; - if (!clockId) NotImplemented_nid_no_patch(__func__); + if (!clockId) throw std::invalid_argument("pthread_condattr_getclock: null clock id"); *clockId = (*attr)->_clockid; return 0; } diff --git a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp index 3030699cf3..b9b9beb672 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp @@ -3,6 +3,7 @@ #include "prx/libkernel/Pthread/include/Cond.hpp" #include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/Api.hpp" +#include #include #include #include @@ -39,68 +40,139 @@ static bool AcceptsSchedParam(int policy, int priority) { return (priority >= 256 && priority <= 767) || (priority >= 896 && priority <= 959); } static constexpr std::size_t GUEST_CPUSET_BYTES = 32; +static constexpr std::size_t GUEST_GETAFFINITY_ERANGE_BYTES = 24; static constexpr int GUEST_ERANGE = 34; extern "C" { 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; + constexpr int LevelRoot = 1, LevelCpuset = 2, LevelWhich = 3; + constexpr int WhichThread = 1, WhichProcess = 2, WhichCpuset = 3, WhichIrq = 4, WhichJail = 5, WhichDomain = 6; constexpr int GuestEsrch = 3, GuestEfault = 14, GuestEinval = 22, GuestErange = 34; constexpr std::size_t MaximumSize = 256 / 8; + constexpr KernelCpumask RootCpumask = 0xffff; const auto fail = [](int error) { *__error_nid_postfix() = error; return -1; }; - if (size < sizeof(KernelCpumask) || size > MaximumSize) return fail(GuestErange); + if (size < sizeof(KernelCpumask) || size >= 24) return fail(GuestErange); + if (size > 16) NotImplemented_nid_no_patch("cpuset_getaffinity with a size of 17 to 23 bytes"); if (!mask) return fail(GuestEfault); auto* self = scePthreadSelf(); KernelCpumask affinity = 0; - if (which == WhichThread) { + switch (which) { + case WhichThread: { const auto tid = id == -1 ? self->tid : id; if (!GetLiveGuestThreadAffinity_nid_no_patch(tid, affinity)) return fail(GuestEsrch); - } else if (which == WhichProcess) { + break; + } + case WhichProcess: if (id != -1 && id != getpid_nid_postfix()) return fail(GuestEsrch); - } else if (which >= 3 && which <= 6) { - NotImplemented_nid_no_patch("cpuset_getaffinity other than the calling thread or process"); - } else { + break; + case WhichCpuset: + if (id != -1) NotImplemented_nid_no_patch("cpuset_getaffinity of a numbered cpuset"); + break; + case WhichJail: + NotImplemented_nid_no_patch("cpuset_getaffinity of a jail's cpuset"); + break; + case WhichIrq: + case WhichDomain: + break; + default: return fail(GuestEinval); } - if (level < 1 || level > 3) return fail(GuestEinval); - if (level != LevelWhich) NotImplemented_nid_no_patch("cpuset_getaffinity other than the calling thread or process"); - if (which == WhichProcess) affinity = LiveGuestThreadsAffinity(); - std::memset(mask, 0, size); - std::memcpy(mask, &affinity, sizeof(affinity)); + switch (level) { + case LevelRoot: + case LevelCpuset: + if (which == WhichIrq || which == WhichDomain) return fail(GuestEinval); + affinity = level == LevelRoot ? RootCpumask : sceKernelGetAvailableCpumask(); + break; + case LevelWhich: + if (which == WhichIrq) NotImplemented_nid_no_patch("cpuset_getaffinity of an interrupt"); + if (which == WhichDomain) NotImplemented_nid_no_patch("cpuset_getaffinity of a memory domain"); + if (which == WhichProcess) affinity = LiveGuestThreadsAffinity(); + else if (which == WhichCpuset) affinity = sceKernelGetAvailableCpumask(); + break; + default: + return fail(GuestEinval); + } + std::array buffer{}; + std::memcpy(buffer.data(), &affinity, sizeof(affinity)); + if (!GuestCopyOut_nid_no_patch(mask, buffer.data(), size)) return fail(GuestEfault); return 0; } int APS5_VABI cpuset_setaffinity_nid_postfix(int level, int which, std::int64_t id, std::size_t size, const void* mask) { - constexpr int LevelWhich = 3, WhichThread = 1, WhichProcess = 2; + constexpr int LevelRoot = 1, LevelCpuset = 2, LevelWhich = 3; + constexpr int WhichThread = 1, WhichProcess = 2, WhichCpuset = 3, WhichIrq = 4, WhichJail = 5, WhichDomain = 6; + constexpr int WhichInterruptHandler = 7, WhichInterruptThread = 8; constexpr int GuestEsrch = 3, GuestEdeadlk = 11, GuestEfault = 14, GuestEinval = 22, GuestErange = 34; constexpr std::size_t MaximumSize = 256 / 8; const auto fail = [](int error) { *__error_nid_postfix() = error; return -1; }; - if (size < sizeof(KernelCpumask) || size > MaximumSize) return fail(GuestErange); + if (size < sizeof(KernelCpumask) || size >= 24) return fail(GuestErange); + if (size > 16) NotImplemented_nid_no_patch("cpuset_setaffinity with a size of 17 to 23 bytes"); if (!mask) return fail(GuestEfault); - const auto* bytes = static_cast(mask); + std::array buffer{}; + if (!GuestCopyIn_nid_no_patch(buffer.data(), mask, size)) return fail(GuestEfault); for (std::size_t index = sizeof(KernelCpumask); index < size; ++index) { - if (bytes[index] != 0) return fail(GuestEinval); + if (buffer[index] != 0) return fail(GuestEinval); } auto* self = scePthreadSelf(); - if (which == WhichThread) { - if (id != -1 && id != self->tid && !LiveGuestThreadExists(id)) return fail(GuestEsrch); - } else if (which == WhichProcess) { + const auto threadExists = [&] { return id == -1 || id == self->tid || LiveGuestThreadExists(id); }; + switch (level) { + case LevelRoot: + case LevelCpuset: + switch (which) { + case WhichThread: + if (!threadExists()) return fail(GuestEsrch); + break; + case WhichProcess: + if (id != -1 && id != getpid_nid_postfix()) return fail(GuestEsrch); + break; + case WhichCpuset: + if (id != -1) NotImplemented_nid_no_patch("cpuset_setaffinity of a numbered cpuset"); + break; + case WhichJail: + NotImplemented_nid_no_patch("cpuset_setaffinity of a jail's cpuset"); + break; + case WhichIrq: + case WhichDomain: + default: + return fail(GuestEinval); + } + NotImplemented_nid_no_patch(level == LevelRoot ? "cpuset_setaffinity at CPU_LEVEL_ROOT" : "cpuset_setaffinity at CPU_LEVEL_CPUSET"); + return fail(GuestEinval); + case LevelWhich: + break; + default: + return fail(GuestEinval); + } + switch (which) { + case WhichThread: + if (!threadExists()) return fail(GuestEsrch); + break; + case WhichProcess: if (id != -1 && id != getpid_nid_postfix()) return fail(GuestEsrch); - } else if (which >= 3 && which <= 6) { - NotImplemented_nid_no_patch("cpuset_setaffinity other than the calling thread or process"); - } else { + break; + case WhichCpuset: + NotImplemented_nid_no_patch(id == -1 ? "cpuset_setaffinity of the calling thread's base cpuset" : "cpuset_setaffinity of a numbered cpuset"); + break; + case WhichJail: + NotImplemented_nid_no_patch("cpuset_setaffinity of a jail's cpuset"); + break; + case WhichIrq: + case WhichInterruptHandler: + case WhichInterruptThread: + NotImplemented_nid_no_patch("cpuset_setaffinity of an interrupt"); + break; + default: return fail(GuestEinval); } - if (level < 1 || level > 3) return fail(GuestEinval); - if (level != LevelWhich) NotImplemented_nid_no_patch("cpuset_setaffinity other than the calling thread or process"); KernelCpumask requested = 0; - std::memcpy(&requested, bytes, sizeof(requested)); + std::memcpy(&requested, buffer.data(), sizeof(requested)); const KernelCpumask affinity = requested & sceKernelGetAvailableCpumask(); if (affinity == 0) return fail(GuestEdeadlk); if (which == WhichThread) { @@ -112,9 +184,9 @@ int APS5_VABI cpuset_setaffinity_nid_postfix(int level, int which, std::int64_t 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; + constexpr int GuestEperm = 1, GuestEsrch = 3, GuestEfault = 14; constexpr int GuestSchedFifo = 1, GuestSchedRr = 3; - constexpr std::uint16_t Realtime = 2, Normal = 3, Fifo = 10; + constexpr std::uint16_t Realtime = 2, Normal = 3, Idle = 4, Fifo = 10, FifoBit = 8; const auto fail = [](int error) { *__error_nid_postfix() = error; return -1; @@ -136,6 +208,8 @@ int APS5_VABI rtprio_thread_nid_postfix(int function, int lwpid, GuestRtPrio* pr if (!GuestCopyOut_nid_no_patch(priority, ¤t, sizeof(current))) return fail(GuestEfault); return 0; } + const auto requestedClass = static_cast(requested.type & ~FifoBit); + if (requestedClass == Realtime || requestedClass == Idle) return fail(GuestEperm); 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 (requested.type == Normal && requested.prio == 0) return 0; @@ -266,14 +340,15 @@ 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) { if (!thread) return PosixThread::GUEST_EINVAL; - if (cpusetSize >= GUEST_CPUSET_BYTES) return GUEST_ERANGE; - if (cpusetSize != 8 && cpusetSize != 16) NotImplemented_nid_no_patch("pthread_getaffinity_np with a set other than 8, 16 or 32 bytes and more"); + if (cpusetSize < sizeof(KernelCpumask) || cpusetSize >= GUEST_GETAFFINITY_ERANGE_BYTES) return GUEST_ERANGE; + if (cpusetSize > 16) NotImplemented_nid_no_patch("pthread_getaffinity_np with a set of 17 to 23 bytes"); if (!cpuset) return PosixThread::GUEST_EFAULT; KernelCpumask mask = 0; const int result = scePthreadGetaffinity(thread, &mask); if (result != 0) return PosixThread::ToErrno(result); - std::memset(cpuset, 0, cpusetSize); - std::memcpy(cpuset, &mask, sizeof(mask)); + std::array buffer{}; + std::memcpy(buffer.data(), &mask, sizeof(mask)); + if (!GuestCopyOut_nid_no_patch(cpuset, buffer.data(), cpusetSize)) return PosixThread::GUEST_EFAULT; return 0; } @@ -286,7 +361,7 @@ int APS5_VABI pthread_setaffinity_np_nid_postfix(Pthread thread, std::size_t cpu if (cpusetSize != GUEST_CPUSET_BYTES) return GUEST_ERANGE; if (!cpuset) return PosixThread::GUEST_EFAULT; KernelCpumask requested = 0; - std::memcpy(&requested, cpuset, sizeof(requested)); + if (!GuestCopyIn_nid_no_patch(&requested, cpuset, sizeof(requested))) return PosixThread::GUEST_EFAULT; const KernelCpumask affinity = requested & sceKernelGetAvailableCpumask(); if (affinity == 0) return GuestEdeadlk; return PosixThread::ToErrno(scePthreadSetaffinity(thread, affinity)); diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index 8bc33af5c0..a3928f8881 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -162,7 +162,7 @@ struct PthreadPrivate { std::string name; std::atomic _finished; void* _retval; - bool _detached; + std::atomic _detached; bool _adopted; Pthread* _handleStorage; std::atomic _joined; @@ -204,5 +204,8 @@ bool GetLiveGuestThreadSched_nid_no_patch(std::int64_t tid, int& policy, int& pr 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); +#if defined(__linux__) +extern "C" [[noreturn]] void APS5_VABI EnterGuestMainStack_nid_no_patch(void* arguments, void (APS5_VABI* entry)(void*)); +#endif #endif diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index f96e2fb430..55eef3bf94 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -7,7 +7,9 @@ #include "../include/WindowsThreadLocal.hpp" #include "../include/Cancel.hpp" #include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/Time/include/TimedWait.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/general/AsmMacros.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/CpuTopology.hpp" @@ -35,6 +37,7 @@ #include #include #include +#include #include #endif @@ -59,6 +62,7 @@ static constexpr int GUEST_EINVAL = 22; 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; +static constexpr std::uint16_t GUEST_THREAD_FPU_CONTROL = 0x037f; #ifdef _WIN32 #include @@ -75,6 +79,12 @@ struct ThreadArgs { #endif }; +#ifdef __APPLE__ +static void KeepHostFaultSignalsUnblocked(sigset_t& mask) { + for (const int signal : {SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP}) sigdelset(&mask, signal); +} +#endif + static std::atomic threadDtors{nullptr}; static std::atomic threadAtexitCount{nullptr}; static std::atomic threadAtexitReport{nullptr}; @@ -118,6 +128,11 @@ static void UnregisterGuestStackMapping(void* mapping) { std::lock_guard lock(guestStackMappingsLock); guestStackMappings.erase(reinterpret_cast(mapping)); } + +static constexpr std::uintptr_t GUEST_MAIN_STACK_TOP = 0x7eeffc000; +static constexpr int GUEST_MAP_FIXED = 0x10; +static std::atomic guestMainStackLow{0}; +static std::atomic guestMainStackHigh{0}; #endif #if defined(__linux__) @@ -208,6 +223,51 @@ static void ReleaseSpareStacks() { for (const auto& stack : release) ReleaseFinishedStack(stack); } +static std::size_t GuestMainStackBytes() { + constexpr std::size_t fallback = std::size_t{8} << 20; + constexpr std::size_t largest = std::size_t{1} << 30; + rlimit stack{}; + if (getrlimit(RLIMIT_STACK, &stack) != 0 || stack.rlim_cur == RLIM_INFINITY || stack.rlim_cur == 0) return fallback; + const auto bytes = static_cast(std::min(stack.rlim_cur, largest)); + return (bytes + PS5_PAGE_SIZE - 1) / PS5_PAGE_SIZE * PS5_PAGE_SIZE; +} + +extern "C" [[noreturn]] void APS5_VABI EnterGuestMainStack_nid_no_patch(void* arguments, void (APS5_VABI* entry)(void*)) { + if (entry == nullptr) throw std::invalid_argument("EnterGuestMainStack: null entry point"); + if (guestMainStackLow.load(std::memory_order_acquire) != 0) throw std::runtime_error("EnterGuestMainStack: the guest main stack was already entered"); + std::uintptr_t arenaBase = 0; + std::size_t arenaBytes = 0; + GuestArena::GuestArenaRange_nid_postfix(&arenaBase, &arenaBytes); + const std::uintptr_t top = GuestArena::GuestAddressOffset + GUEST_MAIN_STACK_TOP; + const std::size_t bytes = GuestMainStackBytes() + GUEST_STACK_GUARD; + if (top - bytes < arenaBase || top > arenaBase + arenaBytes) + throw std::runtime_error("EnterGuestMainStack: the guest arena does not cover the console's main stack range"); + void* mapping = reinterpret_cast(top - bytes); + const int mapped = DoMapAnon(&mapping, bytes, 3, GUEST_MAP_FIXED, PS5_PAGE_SIZE, true); + if (mapped == SCE_KERNEL_ERROR_ENOMEM) { + char message[160]; + std::snprintf(message, sizeof(message), "EnterGuestMainStack: the guest main stack [%#zx, %#zx) overlaps a guest mapping", static_cast(top - bytes), static_cast(top)); + throw std::runtime_error(message); + } + if (mapped != 0 || reinterpret_cast(mapping) + bytes != top) + throw std::runtime_error("EnterGuestMainStack: cannot map the guest main stack"); + if (DoMprotect(mapping, GUEST_STACK_GUARD, 0) != 0) throw std::runtime_error("EnterGuestMainStack: cannot protect the guest main stack guard"); + guestMainStackHigh.store(top, std::memory_order_release); + guestMainStackLow.store(reinterpret_cast(mapping) + GUEST_STACK_GUARD, std::memory_order_release); + __asm__ volatile( + "mov %0, %%rsp\n\t" + "pushq $0\n\t" + "pushq $0\n\t" + "mov %%rsp, %%rbp\n\t" + "xor %%esi, %%esi\n\t" + "call *%2\n\t" + "ud2" + : + : "r"(top), "D"(arguments), "a"(entry) + : "memory"); + __builtin_unreachable(); +} + 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{}; @@ -299,6 +359,16 @@ static std::uintptr_t PrecommittedStackStart(std::uintptr_t low, std::uintptr_t #endif static bool HostStackLimits(std::uintptr_t* low, std::uintptr_t* high) { +#if defined(__linux__) + const auto mainLow = guestMainStackLow.load(std::memory_order_acquire); + const auto mainHigh = guestMainStackHigh.load(std::memory_order_acquire); + const auto frame = reinterpret_cast(__builtin_frame_address(0)); + if (mainLow != 0 && frame >= mainLow && frame < mainHigh) { + *low = mainLow; + *high = mainHigh; + return true; + } +#endif #ifdef _WIN32 ULONG_PTR lowLimit = 0; ULONG_PTR highLimit = 0; @@ -565,6 +635,15 @@ std::unique_lock LockLiveGuestThread(const PthreadPrivate* thread) { return {}; } +static std::unique_lock LockRegisteredGuestThread(const PthreadPrivate* thread) { + auto& registry = CpuClocks(); + std::unique_lock lock(registry.lock); + for (const auto& [id, registered] : registry.threads) { + if (registered == thread) return lock; + } + return {}; +} + bool GuestThreadIsLive(const PthreadPrivate* thread) { return LockLiveGuestThread(thread).owns_lock(); } @@ -743,6 +822,9 @@ static void finishThread(PthreadPrivate* self, void* retval) { self->_finished.store(true, std::memory_order_release); } self->_join_cv.NotifyAll(); +#ifdef _WIN32 + TimedWait::ReapDeliveryDrain(); +#endif } bool ThreadCancel::Requested() { @@ -806,6 +888,7 @@ static void RunThread(std::unique_ptr args) { BindHostCpuClock(self); TimedWait::BindThreadWaitState(&self->waitCount); CurrentGuestState().current = self; + asm volatile("fldcw %0" : : "m"(GUEST_THREAD_FPU_CONTROL)); RegisterStack(self); #ifndef _WIN32 const sigset_t signalMask = args->signalMask; @@ -814,18 +897,33 @@ static void RunThread(std::unique_ptr args) { self->suspended.wait(true, std::memory_order_acquire); #ifdef _WIN32 DeliverPendingException_nid_no_patch(self); +#else +#ifdef __APPLE__ + sigset_t hostMask = signalMask; + KeepHostFaultSignalsUnblocked(hostMask); + pthread_sigmask(SIG_SETMASK, &hostMask, nullptr); #else pthread_sigmask(SIG_SETMASK, &signalMask, nullptr); +#endif #endif finishThread(self, Aps5CallGuestEntry_nid_no_patch(entry, arg)); CurrentGuestState().current = nullptr; } +using FinalThreadReleaseTestHook = void (*)(Pthread); +static std::atomic finalThreadReleaseTestTarget{nullptr}; +static std::atomic finalThreadReleaseTestHook{nullptr}; + static void ReleaseThread(PthreadPrivate* thread) { if (thread->_adopted) return; if (thread->references.fetch_sub(1, std::memory_order_acq_rel) != 1) return; + auto* expected = thread; + if (finalThreadReleaseTestTarget.compare_exchange_strong(expected, nullptr, std::memory_order_acq_rel)) { + if (const auto hook = finalThreadReleaseTestHook.exchange(nullptr, std::memory_order_acq_rel)) + hook(reinterpret_cast(thread)); + } #ifdef _WIN32 if (thread->wakeEvent) CloseHandle(static_cast(thread->wakeEvent)); @@ -835,6 +933,38 @@ static void ReleaseThread(PthreadPrivate* thread) { delete thread; } +class GuestThreadReference { +public: + GuestThreadReference() = default; + bool Retain(PthreadPrivate* value) { + auto references = value->references.load(std::memory_order_relaxed); + while (references != 0) { + if (value->references.compare_exchange_weak(references, references + 1, std::memory_order_acq_rel, std::memory_order_relaxed)) { + thread = value; + return true; + } + } + return false; + } + void Reset() { + if (!thread) return; + auto* retained = thread; + thread = nullptr; + ReleaseThread(retained); + } + ~GuestThreadReference() { Reset(); } + GuestThreadReference(const GuestThreadReference&) = delete; + GuestThreadReference& operator=(const GuestThreadReference&) = delete; + +private: + PthreadPrivate* thread = nullptr; +}; + +extern "C" void APS5_Test_SetFinalThreadReleaseHook(Pthread thread, FinalThreadReleaseTestHook hook) { + finalThreadReleaseTestHook.store(hook, std::memory_order_relaxed); + finalThreadReleaseTestTarget.store(static_cast(thread), std::memory_order_release); +} + static std::mutex joinedLock; static std::map joinedThreads; @@ -894,6 +1024,14 @@ struct NativeThreadArgs { }; #ifdef _WIN32 +static std::wstring ThreadDescription(const std::string& name) { + if (name.empty()) return {}; + const int length = MultiByteToWideChar(CP_UTF8, 0, name.data(), static_cast(name.size()), nullptr, 0); + std::wstring description(static_cast(length), L'\0'); + MultiByteToWideChar(CP_UTF8, 0, name.data(), static_cast(name.size()), description.data(), length); + return description; +} + static unsigned __stdcall StartNativeThread(void* opaque) { std::unique_ptr args(static_cast(opaque)); auto* self = args->guest->self; @@ -909,8 +1047,7 @@ static unsigned __stdcall StartNativeThread(void* opaque) { self->threadId = std::this_thread::get_id(); CurrentGuestState().current = self; if (!self->name.empty()) { - const std::wstring description(self->name.begin(), self->name.end()); - SetThreadDescription(GetCurrentThread(), description.c_str()); + SetThreadDescription(GetCurrentThread(), ThreadDescription(self->name).c_str()); ApplyJobAffinity(*self, nullptr); } args->initialized.set_value(); @@ -1053,6 +1190,9 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread if (created == 0) { sigset_t allSignals; sigfillset(&allSignals); +#ifdef __APPLE__ + KeepHostFaultSignalsUnblocked(allSignals); +#endif pthread_sigmask(SIG_BLOCK, &allSignals, nullptr); created = pthread_create(&p->hostThread, &nativeAttr, StartNativeThread, native.get()); pthread_sigmask(SIG_SETMASK, &creatorMask, nullptr); @@ -1089,15 +1229,26 @@ int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, Pthread int APS5_VABI JoinThread_nid_no_patch(Pthread thread, void** retval, const KernelTimespec* abstime) { if (!thread) return SCE_KERNEL_ERROR_EINVAL; + GuestThreadReference reference; + auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; + if (!reference.Retain(thread)) return SCE_KERNEL_ERROR_ESRCH; if (thread == CurrentGuestState().current) return SCE_KERNEL_ERROR_EDEADLK; if (thread->_detached) return SCE_KERNEL_ERROR_EINVAL; if (thread->_joined.load(std::memory_order_acquire)) return SCE_KERNEL_ERROR_ESRCH; + registered.unlock(); + reference.Reset(); ThreadCancel::Check(); + registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; + if (!reference.Retain(thread)) return SCE_KERNEL_ERROR_ESRCH; { std::unique_lock lock(thread->_join_mtx); if (thread->_detached) return SCE_KERNEL_ERROR_EINVAL; + if (thread->_joined.load(std::memory_order_acquire)) return SCE_KERNEL_ERROR_ESRCH; if (thread->_joining) return SCE_KERNEL_ERROR_EOPNOTSUPP; thread->_joining = true; + registered.unlock(); while (!thread->_finished.load()) { std::optional deadline; if (abstime != nullptr) { @@ -1146,12 +1297,17 @@ int APS5_VABI scePthreadJoin(Pthread thread, void** retval) { int APS5_VABI scePthreadDetach(Pthread thread) { if (!thread) return SCE_KERNEL_ERROR_EINVAL; + GuestThreadReference reference; + auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; + if (!reference.Retain(thread)) return SCE_KERNEL_ERROR_ESRCH; { std::lock_guard lock(thread->_join_mtx); 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; } + registered.unlock(); #ifdef _WIN32 ReleaseThread(thread); #else @@ -1241,6 +1397,11 @@ void APS5_VABI scePthreadYield() { int APS5_VABI scePthreadCancel(Pthread thread) { if (!thread) return SCE_KERNEL_ERROR_ESRCH; + GuestThreadReference reference; + auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; + if (!reference.Retain(thread)) return SCE_KERNEL_ERROR_ESRCH; + registered.unlock(); thread->cancelPending.store(true); for (;;) { { @@ -1290,12 +1451,16 @@ KernelCpumask APS5_VABI sceKernelGetAvailableCpumask(void) { int APS5_VABI scePthreadGetaffinity(Pthread thread, KernelCpumask* mask) { if (!thread || !mask) return SCE_KERNEL_ERROR_EINVAL; + auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; *mask = thread->affinity.load(std::memory_order_relaxed); return SCE_OK; } int APS5_VABI scePthreadGetname(Pthread thread, char* name) { if (!thread || !name) return SCE_KERNEL_ERROR_EINVAL; + auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(thread->nameLock); const std::size_t length = std::min(thread->name.size(), THREAD_NAME_CAPACITY - 1); std::memcpy(name, thread->name.data(), length); @@ -1305,6 +1470,8 @@ int APS5_VABI scePthreadGetname(Pthread thread, char* name) { int APS5_VABI scePthreadGetprio(Pthread thread, int* prio) { if (!thread || !prio) return SCE_KERNEL_ERROR_EINVAL; + auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; *prio = thread->priority.load(std::memory_order_relaxed); return SCE_OK; } @@ -1315,11 +1482,12 @@ int APS5_VABI scePthreadGetthreadid(void) { int APS5_VABI scePthreadRename(Pthread thread, const char* name) { if (!thread || !name) return SCE_KERNEL_ERROR_EINVAL; + auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; std::lock_guard lock(thread->nameLock); thread->name = name; #ifdef _WIN32 - const std::wstring description(thread->name.begin(), thread->name.end()); - SetThreadDescription(static_cast(thread->nativeHandle), description.c_str()); + SetThreadDescription(static_cast(thread->nativeHandle), ThreadDescription(thread->name).c_str()); ApplyJobAffinity(*thread, thread->nativeHandle); #endif return SCE_OK; @@ -1327,6 +1495,8 @@ int APS5_VABI scePthreadRename(Pthread thread, const char* name) { int APS5_VABI scePthreadSetaffinity(Pthread thread, KernelCpumask mask) { if (!thread || mask == 0) return SCE_KERNEL_ERROR_EINVAL; + auto registered = LockRegisteredGuestThread(thread); + if (!registered) return SCE_KERNEL_ERROR_ESRCH; thread->affinity.store(mask, std::memory_order_relaxed); return SCE_OK; } diff --git a/core/libs/prx/libkernel/Semaphore/include/Semaphore.hpp b/core/libs/prx/libkernel/Semaphore/include/Semaphore.hpp index 08c0505979..7cba2c8da9 100644 --- a/core/libs/prx/libkernel/Semaphore/include/Semaphore.hpp +++ b/core/libs/prx/libkernel/Semaphore/include/Semaphore.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -13,6 +14,12 @@ constexpr int KERNEL_SEMA_OK = 0; +struct KernelSemaWaiter { + std::int32_t need; + bool released = false; + int priority = 0; +}; + struct KernelSemaPrivate { KernelSemaPrivate(std::int32_t initCount, std::int32_t maxCount, std::string name, bool isFifo); @@ -23,9 +30,10 @@ struct KernelSemaPrivate { std::int32_t initCount; std::int32_t maxCount; bool isFifo; + bool priorityQueue = false; bool deleted = false; std::int32_t waiterCount = 0; - std::int32_t cancelableCount = 0; + std::list queue; std::uint64_t cancelGeneration = 0; }; diff --git a/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp b/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp index c02cac3109..2d86ba2c66 100644 --- a/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp +++ b/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp @@ -1,6 +1,8 @@ #include "prx/libkernel/Semaphore/include/Semaphore.hpp" +#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libc/include/General.hpp" +#include #include #include #include @@ -29,6 +31,7 @@ int APS5_VABI sceKernelCreateSema(KernelSema* sem, const char* name, uint32_t at return SCE_KERNEL_ERROR_EINVAL; } *sem = new KernelSemaPrivate(init, max, std::string(name), (attr & 3u) == 1u); + (*sem)->priorityQueue = (attr & 3u) == 2u; if (TraceSync()) std::fprintf(stderr, "[sema] create %p '%s' attr=0x%x init=%d max=%d\n", static_cast(*sem), name, attr, init, max); return KERNEL_SEMA_OK; } @@ -64,6 +67,16 @@ int APS5_VABI sceKernelSignalSema(KernelSema sem, int count) { const auto before = sem->tokenCount; sem->tokenCount += count; if (TraceSync()) std::fprintf(stderr, "[sema] signal %p '%s' count=%d have=%d->%d\n", static_cast(sem), sem->name.c_str(), count, before, sem->tokenCount); + for (auto it = sem->queue.begin(); it != sem->queue.end();) { + KernelSemaWaiter* waiter = *it; + if (waiter->need > sem->tokenCount) { + ++it; + continue; + } + sem->tokenCount -= waiter->need; + waiter->released = true; + it = sem->queue.erase(it); + } sem->condition.NotifyAll(); return KERNEL_SEMA_OK; } @@ -75,53 +88,64 @@ int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time) if (need <= 0) { APS5_INVALID_ARG_EX; } + if (need > sem->maxCount) { + return SCE_KERNEL_ERROR_EINVAL; + } std::unique_lock lock(sem->mutex); - const std::uint64_t generation = sem->cancelGeneration; - ++sem->waiterCount; - ++sem->cancelableCount; - struct WaiterGuard { - KernelSemaPrivate* sem; - std::uint64_t generation; - ~WaiterGuard() { - --sem->waiterCount; - if (sem->cancelGeneration == generation) { - --sem->cancelableCount; - } - sem->condition.NotifyAll(); - } - } waiterGuard{sem, generation}; - - const auto waitStart = std::chrono::steady_clock::now(); const void* caller = __builtin_return_address(0); if (TraceSync()) std::fprintf(stderr, "[sema] wait begin %p '%s' need=%d have=%d timeout=%lld caller=%p\n", static_cast(sem), sem->name.c_str(), need, sem->tokenCount, time ? static_cast(*time) : -1LL, caller); const auto traceSyncWaitEnd = [&](int result) { if (TraceSync()) std::fprintf(stderr, "[sema] wait end %p '%s' result=0x%x have=%d caller=%p\n", static_cast(sem), sem->name.c_str(), result, sem->tokenCount, caller); }; - const auto traceWait = [&](bool timedOut) { - KernelTraceWait_nid_postfix("sema", caller, static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - waitStart).count()), timedOut); - }; - const auto released = [&] { return sem->tokenCount >= need || sem->deleted || sem->cancelGeneration != generation; }; - if (time == nullptr) { - sem->condition.Wait(lock, released); - traceWait(false); - if (sem->deleted) { - traceSyncWaitEnd(SCE_KERNEL_ERROR_EACCES); - return SCE_KERNEL_ERROR_EACCES; - } - if (sem->cancelGeneration != generation) { - traceSyncWaitEnd(SCE_KERNEL_ERROR_ECANCELED); - return SCE_KERNEL_ERROR_ECANCELED; - } + if (sem->deleted) { + traceSyncWaitEnd(SCE_KERNEL_ERROR_EACCES); + return SCE_KERNEL_ERROR_EACCES; + } + if (sem->tokenCount >= need) { sem->tokenCount -= need; traceSyncWaitEnd(KERNEL_SEMA_OK); return KERNEL_SEMA_OK; } + const std::uint64_t generation = sem->cancelGeneration; + KernelSemaWaiter waiter{need}; + if (sem->priorityQueue && scePthreadGetprio(scePthreadSelf(), &waiter.priority) == 0) { + const auto lower = std::find_if(sem->queue.begin(), sem->queue.end(), [&](const KernelSemaWaiter* queued) { return queued->priority > waiter.priority; }); + sem->queue.insert(lower, &waiter); + } else { + sem->queue.push_back(&waiter); + } + ++sem->waiterCount; + struct WaiterGuard { + KernelSemaPrivate* sem; + KernelSemaWaiter* waiter; + ~WaiterGuard() { + --sem->waiterCount; + if (!waiter->released) { + sem->queue.remove(waiter); + } + sem->condition.NotifyAll(); + } + } waiterGuard{sem, &waiter}; - const std::uint64_t deadline = TimedWait::DeadlineNanos(*time); - const bool acquired = sem->condition.WaitUntil(lock, deadline, released); - *time = static_cast(TimedWait::RemainingMicros(deadline)); + const auto waitStart = std::chrono::steady_clock::now(); + const auto traceWait = [&](bool timedOut) { + KernelTraceWait_nid_postfix("sema", caller, static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - waitStart).count()), timedOut); + }; + const auto released = [&] { return waiter.released || sem->deleted || sem->cancelGeneration != generation; }; + bool acquired = true; + if (time == nullptr) { + sem->condition.Wait(lock, released); + } else { + const std::uint64_t deadline = TimedWait::DeadlineNanos(*time); + acquired = sem->condition.WaitUntil(lock, deadline, released); + *time = static_cast(TimedWait::RemainingMicros(deadline)); + } traceWait(!acquired); + if (waiter.released) { + traceSyncWaitEnd(KERNEL_SEMA_OK); + return KERNEL_SEMA_OK; + } if (sem->deleted) { traceSyncWaitEnd(SCE_KERNEL_ERROR_EACCES); return SCE_KERNEL_ERROR_EACCES; @@ -130,13 +154,8 @@ int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time) traceSyncWaitEnd(SCE_KERNEL_ERROR_ECANCELED); return SCE_KERNEL_ERROR_ECANCELED; } - if (!acquired) { - traceSyncWaitEnd(SCE_KERNEL_ERROR_ETIMEDOUT); - return SCE_KERNEL_ERROR_ETIMEDOUT; - } - sem->tokenCount -= need; - traceSyncWaitEnd(KERNEL_SEMA_OK); - return KERNEL_SEMA_OK; + traceSyncWaitEnd(SCE_KERNEL_ERROR_ETIMEDOUT); + return SCE_KERNEL_ERROR_ETIMEDOUT; } int APS5_VABI sceKernelCancelSema(KernelSema sem, int count, int* threads) { @@ -149,10 +168,10 @@ int APS5_VABI sceKernelCancelSema(KernelSema sem, int count, int* threads) { return SCE_KERNEL_ERROR_EINVAL; } if (threads != nullptr) { - *threads = sem->cancelableCount; + *threads = static_cast(sem->queue.size()); } sem->tokenCount = count < 0 ? sem->initCount : count; - sem->cancelableCount = 0; + sem->queue.clear(); ++sem->cancelGeneration; sem->condition.NotifyAll(); return KERNEL_SEMA_OK; diff --git a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp index 55956c9294..3b4b3a93b3 100644 --- a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp +++ b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp @@ -21,6 +21,13 @@ using Closer = bool (*)(int descriptor, int* error); using OptionSetter = bool (*)(int descriptor, int level, int option, const void* value, std::uint32_t length, int* error); using OptionGetter = bool (*)(int descriptor, int level, int option, void* value, std::uint32_t* length, int* error); using StatusControl = bool (*)(int descriptor, int command, int flags, int* result, int* error); +struct Forwards { + int (*family)(int descriptor); + int (*bind)(int descriptor, const void* address, std::uint32_t length); + int (*getsockname)(int descriptor, void* address, std::uint32_t* length); + std::int64_t (*sendto)(int descriptor, const void* buffer, std::uint64_t length, int flags, const void* address, std::uint32_t addressLength, int* error); + std::int64_t (*recvfrom)(int descriptor, void* buffer, std::uint64_t length, int flags, void* address, std::uint32_t* addressLength, int* error); +}; struct NativeOwner { int (*transfer)(NativeOwner* owner, bool send, void* buffer, int length) = nullptr; }; @@ -28,6 +35,7 @@ struct NativeOwner { using NativeLookup = bool (*)(int descriptor, std::shared_ptr* owner, std::uintptr_t* native); } +extern "C" void KernelSetSocketForwards_nid_no_patch(const KernelSocketPoll::Forwards* forwards); extern "C" void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller); extern "C" void KernelSetSocketCloser_nid_no_patch(KernelSocketPoll::Closer closer); extern "C" bool KernelCloseSocket_nid_no_patch(int descriptor, int* error); diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index fa5a9d493e..b8f8ab3a47 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -268,8 +268,12 @@ int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* return -1; } for (const auto& entry : hosts.entries) { - if ((entry.revents & KernelSocketPoll::Unknown) != 0) - throw std::runtime_error("select: descriptor " + std::to_string(entry.descriptor) + " is neither a socket, a pipe, a file, a directory nor a kqueue"); + if ((entry.revents & KernelSocketPoll::Unknown) == 0) continue; + if (!DescriptorIsOpen_nid_no_patch(entry.descriptor)) { + *__error_nid_postfix() = GuestEbadf; + return -1; + } + throw std::runtime_error("select: descriptor " + std::to_string(entry.descriptor) + " is neither a socket, a pipe, a file, a directory nor a kqueue"); } groups.push_back(std::move(hosts)); } diff --git a/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp b/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp index 027d43d4c1..e5e046868a 100644 --- a/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp +++ b/core/libs/prx/libkernel/Socket/src/SocketOptions.cpp @@ -13,7 +13,6 @@ #endif #endif #include "prx/libkernel/Socket/include/SocketError.hpp" -#include "prx/libc/include/GuestArena.hpp" #include "prx/libkernel/Socket/include/SocketOptions.hpp" #include "prx/libc/include/General.hpp" #include @@ -158,14 +157,14 @@ int IpReceiveBit(int option) { } } +int ReadValue(void* destination, const void* source, std::size_t bytes) { + return GuestCopyIn_nid_no_patch(destination, source, bytes) ? 0 : ErrorFault; +} + int Copy(void* output, std::uint32_t* length, const void* value, std::uint32_t size) { const std::uint32_t count = output ? std::min(*length, size) : 0; - const GuestArena::HostWrite destination(count == 0 ? nullptr : output, count); - if (!destination.Open()) return ErrorFault; - const GuestArena::HostWrite lengthOutput(length, sizeof(*length)); - if (!lengthOutput.Open()) return ErrorFault; - if (count != 0) std::memcpy(output, value, count); - *length = count; + if (count != 0 && !GuestCopyOut_nid_no_patch(output, value, count)) return ErrorFault; + if (!GuestCopyOut_nid_no_patch(length, &count, sizeof(count))) return ErrorFault; return 0; } @@ -182,7 +181,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if (option == GuestLinger) { if (length < sizeof(GuestLingerValue)) return ErrorInvalid; GuestLingerValue guest; - std::memcpy(&guest, value, sizeof(guest)); + if (const int fault = ReadValue(&guest, value, sizeof(guest))) return fault; if (guest.seconds < 0 || guest.seconds > MaximumLinger) return ErrorDomain; #ifdef _WIN32 linger request{}; @@ -199,7 +198,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if (option == GuestSendTimeout || option == GuestReceiveTimeout) { if (length < sizeof(GuestTimeval)) return ErrorInvalid; GuestTimeval time; - std::memcpy(&time, value, sizeof(time)); + if (const int fault = ReadValue(&time, value, sizeof(time))) return fault; if (time.seconds < 0 || time.microseconds < 0 || time.microseconds >= 1000000) return ErrorDomain; (option == GuestSendTimeout ? state.sendTimeout : state.receiveTimeout) = TimeoutOf(time); return 0; @@ -207,7 +206,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if (option == GuestNoSigpipe || option == GuestNonBlocking) { if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if (option == GuestNoSigpipe) { state.noSignal = number != 0; return 0; @@ -228,7 +227,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if (native_boolean || buffer || lowWater) { if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if (buffer || lowWater) { if (number < 1) return ErrorInvalid; EnsureBuffers(state, family, type); @@ -253,7 +252,7 @@ int SetSocketLevelOption(std::uintptr_t native, SocketOptionState& state, int fa if ((option & (KeptFlags | WriteOnlyFlags)) != 0 && (option & (option - 1)) == 0) { if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if ((option & KeptFlags) == 0) return 0; const int flags = number != 0 ? state.keptFlags | option : state.keptFlags & ~option; if (option == GuestReuseAddress || option == GuestReusePort) { @@ -355,7 +354,7 @@ int SetTcpLevelOption(std::uintptr_t native, SocketOptionState& state, bool tcp, return ErrorNoProtocolOption; if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if (const int bit = TcpFlagBit(option); bit != 0) { state.tcpFlags = number != 0 ? state.tcpFlags | bit : state.tcpFlags & ~bit; return 0; @@ -413,7 +412,7 @@ int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, if (option == GuestIpAddMembership || option == GuestIpDropMembership) { if (length < sizeof(ip_mreq)) return ErrorInvalid; ip_mreq request{}; - std::memcpy(&request, value, sizeof(request)); + if (const int fault = ReadValue(&request, value, sizeof(request))) return fault; const auto* group = reinterpret_cast(&request.imr_multiaddr); if ((group[0] & 0xf0) != 0xe0) return ErrorInvalid; std::uint32_t groupBits; @@ -441,14 +440,15 @@ int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, if (length < sizeof(in_addr)) return ErrorInvalid; in_addr address{}; if (length == MulticastRequestWithIndex) { + unsigned char request[MulticastRequestWithIndex]; + if (const int fault = ReadValue(request, value, sizeof(request))) return fault; std::int32_t index; - std::memcpy(&index, static_cast(value) + 8, sizeof(index)); + std::memcpy(&index, request + 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; })) + if (std::any_of(request, request + 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 (const int fault = ReadValue(&address, value, sizeof(address))) return fault; } if (::setsockopt(handle, IPPROTO_IP, IP_MULTICAST_IF, reinterpret_cast(&address), sizeof(address))) return HostError(); std::memcpy(&state.multicastInterface, &address, sizeof(address)); @@ -457,14 +457,14 @@ int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, if (const int bit = IpReceiveBit(option); bit != 0) { if (length < sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; 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)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; const int stored = number & 0xff; if (option == GuestIpTypeOfService) { state.ipTypeOfService = stored; @@ -478,11 +478,11 @@ int SetIpLevelOption(std::uintptr_t native, SocketOptionState& state, bool ipv4, unsigned int number; if (length == 1) { unsigned char byte; - std::memcpy(&byte, value, 1); + if (const int fault = ReadValue(&byte, value, 1)) return fault; number = byte; } else { if (length < sizeof(number)) return ErrorInvalid; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; if (option == GuestIpMulticastTimeToLive && number > 255) return ErrorInvalid; } const int host = option == GuestIpMulticastLoop ? ((number & 0xff) != 0 ? 1 : 0) : static_cast(number); @@ -521,7 +521,7 @@ int SetIpv6LevelOption(std::uintptr_t native, bool ipv6, int option, const void* if (option != GuestIpv6Only) return ErrorNoProtocolOption; if (length != sizeof(int)) return ErrorInvalid; int number; - std::memcpy(&number, value, sizeof(number)); + if (const int fault = ReadValue(&number, value, sizeof(number))) return fault; const int enabled = number != 0 ? 1 : 0; if (::setsockopt(static_cast(native), IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&enabled), sizeof(enabled))) return HostError(); return 0; diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp index 6b6c80aec1..07d7885068 100644 --- a/core/libs/prx/libkernel/Socket/src/Udp.cpp +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -37,7 +37,10 @@ #include "prx/libkernel/Socket/include/SocketError.hpp" #include "prx/libkernel/Socket/include/SocketOptions.hpp" #include "prx/libkernel/Socket/include/SocketRuntime.hpp" +#include "prx/libkernel/File/include/ArchivedFile.hpp" #include "prx/libkernel/File/include/DescriptorControl.hpp" +#include "prx/libkernel/File/include/DirectoryDescriptor.hpp" +#include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libkernel/File/include/RandomDevice.hpp" @@ -267,6 +270,16 @@ int Install(std::shared_ptr socket) { } return number; } +std::atomic socketForwards{nullptr}; +struct Forwarded { + const KernelSocketPoll::Forwards* forwards = nullptr; + int family = 0; +}; +Forwarded ForwardsFor(int descriptor) { + const auto* forwards = GuestSockets::IsOpen(descriptor) ? nullptr : socketForwards.load(std::memory_order_acquire); + const int family = forwards == nullptr ? 0 : forwards->family(descriptor); + return family == 0 ? Forwarded{} : Forwarded{forwards, family}; +} std::shared_ptr Lookup(int descriptor) { std::lock_guard lock(socketsMutex); const auto found = sockets.find(descriptor); @@ -328,6 +341,10 @@ bool PeerConnected(const Socket& socket) { socklen_t size = sizeof(peer); return ::getpeername(socket.value, reinterpret_cast(&peer), &size) == 0; } +bool Multicast(const sockaddr_storage& address) { + if (address.ss_family == AF_INET) return (reinterpret_cast(&reinterpret_cast(address).sin_addr)[0] & 0xf0) == 0xe0; + return address.ss_family == AF_INET6 && reinterpret_cast(&reinterpret_cast(address).sin6_addr)[0] == 0xff; +} 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; @@ -649,6 +666,10 @@ extern "C" void KernelReleaseSocketNumber_nid_no_patch(int number) { ReleaseNumber(number); } +extern "C" void KernelSetSocketForwards_nid_no_patch(const KernelSocketPoll::Forwards* forwards) { + socketForwards.store(forwards, std::memory_order_release); +} + namespace { #ifdef _WIN32 using NativePollDescriptor = WSAPOLLFD; @@ -756,8 +777,10 @@ bool QueryPipe(int descriptor, const char* function, ACCESS_MASK& access, PipeLo if (!query) throw std::runtime_error(std::string(function) + ": NtQueryInformationFile is unavailable"); PipeIoStatus status{}; if (query(handle, &status, &access, sizeof(access), FileAccessInformationClass) < 0 || - query(handle, &status, &pipe, sizeof(pipe), FilePipeLocalInformationClass) < 0) + query(handle, &status, &pipe, sizeof(pipe), FilePipeLocalInformationClass) < 0) { + if (reinterpret_cast(::_get_osfhandle(descriptor)) != handle || GetFileType(handle) != FILE_TYPE_PIPE) return false; throw std::runtime_error(std::string(function) + ": querying pipe descriptor " + std::to_string(descriptor) + " failed"); + } return true; } bool PipeReadiness(int descriptor, short events, short& revents) { @@ -837,7 +860,7 @@ int GuestSockets::DuplicateTo(int descriptor, int target) { } } } - if (replaced) EqueueDescriptorClosed(target); + EqueueDescriptorClosed(target); { std::lock_guard lock(socketsMutex); closeOnExecDescriptors.erase(target); @@ -1297,6 +1320,7 @@ 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); + if (static_cast(length) < 0) return Fail(22); int netError = 0; if (!GuestSockets::IsOpen(descriptor) && KernelSetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) { return netError == 0 ? 0 : Fail(netError); @@ -1314,26 +1338,25 @@ int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option, } int APS5_VABI getsockopt_nid_postfix(int descriptor, int level, int option, void* value, std::uint32_t* length) { + std::uint32_t declared = 0; + if (value && !GuestCopyIn_nid_no_patch(&declared, length, sizeof(declared))) return Fail(14); + if (value && !GuestCopyOut_nid_no_patch(length, &declared, sizeof(declared))) return Fail(14); int netError = 0; - if (!GuestSockets::IsOpen(descriptor) && KernelGetSocketOption_nid_no_patch(descriptor, level, option, value, length, &netError)) { - return netError == 0 ? 0 : Fail(netError); + if (!GuestSockets::IsOpen(descriptor) && KernelGetSocketOption_nid_no_patch(descriptor, level, option, value, &declared, &netError)) { + if (netError != 0) return Fail(netError); + return GuestCopyOut_nid_no_patch(length, &declared, sizeof(declared)) ? 0 : Fail(14); } const auto socket = Lookup(descriptor); if (!socket) return -1; if (level != 0xffff && level != 0 && level != 6 && level != 41) return Fail(22); - if (!length) return Fail(22); - if (!value) { - const GuestArena::HostWrite lengthOutput(length, sizeof(*length)); - if (!lengthOutput.Open()) return Fail(14); - *length = 0; - } std::lock_guard lock(socket->modeMutex); const auto native = static_cast(socket->value); - 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->family, socket->type, option, value, length); - return error == 0 ? 0 : Fail(error); + const int error = level == 0 ? GuestSockets::GetIpLevelOption(socket->options, socket->family == 2, socket->type == 2, option, value, &declared) + : level == 6 ? GuestSockets::GetTcpLevelOption(native, socket->options, socket->family != GuestUnix && socket->type == 1, option, value, &declared) + : level == 41 ? GuestSockets::GetIpv6LevelOption(native, socket->family == 28, option, value, &declared) + : GuestSockets::GetSocketLevelOption(native, socket->options, socket->family, socket->type, option, value, &declared); + if (error != 0) return Fail(error); + return GuestCopyOut_nid_no_patch(length, &declared, sizeof(declared)) ? 0 : Fail(14); } int APS5_VABI socket_nid_postfix(int family, int type, int protocol) { const bool nonblocking = (type & GuestNonBlocking) != 0; @@ -1453,9 +1476,12 @@ int APS5_VABI connect_nid_postfix(int descriptor, const void* address, std::uint sockaddr_storage native{}; socklen_t size; if (!Address(socket->family, address, mapped ? 28 : length, native, size)) return -1; + if (native.ss_family == AF_INET6 && IN6_IS_ADDR_UNSPECIFIED(&reinterpret_cast(native).sin6_addr)) + reinterpret_cast(native).sin6_addr = in6addr_loopback; #ifdef _WIN32 if (socket->type == 1) RefuseClosedLoopbackAtOnce_nid_no_patch(socket->value, reinterpret_cast(&native)); #endif + if (socket->type == 1 && Multicast(native) && !PeerConnected(*socket)) return Fail(47); if (ZeroPortConnect(*socket, address, length)) return -1; std::unique_lock lock(socket->modeMutex); socket->options.reportedError = 0; @@ -1543,6 +1569,13 @@ std::int64_t APS5_VABI recv_nid_postfix(int descriptor, void* buffer, std::uint6 } 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); + if (const auto [forwards, family] = ForwardsFor(descriptor); forwards != nullptr) { + sockaddr_storage checked{}; + socklen_t checkedSize; + if (!Address(family, address, length, checked, checkedSize)) return -1; + const int error = forwards->bind(descriptor, address, length); + return error == 0 ? 0 : Fail(error); + } const auto socket = Lookup(descriptor); if (!socket) return -1; sockaddr_storage native{}; @@ -1550,6 +1583,7 @@ int APS5_VABI bind_nid_postfix(int descriptor, const void* address, std::uint32_ const int mapped = LongMappedDualStack(*socket, address, length); if (mapped < 0) return -1; if (!Address(socket->family, address, mapped ? 28 : length, native, size)) return -1; + if (socket->type == 1 && Multicast(native)) return Fail(47); std::unique_lock lock(socket->modeMutex, std::defer_lock); if (socket->type == 2) lock.lock(); if (::bind(socket->value, reinterpret_cast(&native), size)) return Fail(NativeError()); @@ -1592,11 +1626,29 @@ int APS5_VABI shutdown_nid_postfix(int descriptor, int how) { for (const int alias : DescriptorsOf(socket)) EqueueDescriptorReadShut(alias); return unconnected ? Fail(57) : 0; } +namespace { +int CopyAddressOut(const sockaddr_storage& native, bool unnamed, void* address, std::uint32_t* length, std::uint32_t size) { + unsigned char bytes[28]{}; + if (unnamed) UnnamedAddress(bytes, &size); + else GuestAddress(native, bytes, &size); + if (size != 0 && !GuestCopyOut_nid_no_patch(address, bytes, size)) return Fail(14); + if (!GuestCopyOut_nid_no_patch(length, &size, sizeof(size))) return Fail(14); + return 0; +} +} int APS5_VABI getsockname_nid_postfix(int descriptor, void* address, std::uint32_t* length) { + std::uint32_t capacity = 0; + if (!GuestCopyIn_nid_no_patch(&capacity, length, sizeof(capacity))) return Fail(14); + if (const auto* forwards = ForwardsFor(descriptor).forwards; forwards != nullptr) { + unsigned char name[28]{}; + std::uint32_t size = std::min(capacity, sizeof(name)); + if (const int error = forwards->getsockname(descriptor, name, &size)) return Fail(error); + if (size != 0 && !GuestCopyOut_nid_no_patch(address, name, size)) return Fail(14); + return GuestCopyOut_nid_no_patch(length, &size, sizeof(size)) ? 0 : Fail(14); + } const auto socket = Lookup(descriptor); if (!socket) return -1; - if (!address || !length) return Fail(14); - if (socket->family == GuestUnix) return UnnamedAddress(address, length); + if (socket->family == GuestUnix) return CopyAddressOut({}, true, address, length, capacity); sockaddr_storage native{}; socklen_t size = sizeof(native); if (LocalAddress(*socket, native, size)) { @@ -1608,27 +1660,33 @@ int APS5_VABI getsockname_nid_postfix(int descriptor, void* address, std::uint32 return Fail(NativeError()); #endif } - GuestAddress(native, address, length); - return 0; + return CopyAddressOut(native, false, address, length, capacity); } int APS5_VABI getpeername_nid_postfix(int descriptor, void* address, std::uint32_t* length) { + std::uint32_t capacity = 0; + if (!GuestCopyIn_nid_no_patch(&capacity, length, sizeof(capacity))) return Fail(14); 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); + if (socket->family == GuestUnix) return CopyAddressOut({}, true, address, length, capacity); sockaddr_storage native{}; socklen_t size = sizeof(native); if (::getpeername(socket->value, reinterpret_cast(&native), &size)) return Fail(NativeError()); - GuestAddress(native, address, length); - return 0; + return CopyAddressOut(native, false, address, length, capacity); } int APS5_VABI ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument) { if (!GuestSockets::IsOpen(descriptor)) return File::Ioctl(descriptor, request, argument); if (File::IoctlRequestMalformed(request)) return Fail(25); const auto socket = Lookup(descriptor); if (!socket) return -1; - if (!argument) return Fail(14); + const auto command = static_cast(request); + if (command == 0x20006601 || command == 0x20006602) { + std::lock_guard registry(socketsMutex); + if (command == 0x20006601) closeOnExecDescriptors.insert(descriptor); + else closeOnExecDescriptors.erase(descriptor); + return 0; + } + if (!argument && (request & 0x1fff0000 || !(request & 0x20000000))) return Fail(14); if (request != 0x8004667e && request != 0x4004667f) return Fail(25); std::lock_guard lock(socket->modeMutex); if (request == 0x4004667f && socket->receiveShut) { @@ -1660,6 +1718,17 @@ std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, st if (!GuestCopyIn_nid_no_patch(destinationCopy.data(), address, addressLength)) return Fail(14); address = destinationCopy.data(); } + if (const auto [forwards, family] = ForwardsFor(descriptor); forwards != nullptr) { + if ((flags & ~GuestSendFlags) != 0) return Fail(45); + if (length > INT_MAX) return Fail(40); + if (!buffer && length) return Fail(14); + sockaddr_storage checked{}; + socklen_t checkedSize; + if (address && !Address(family, address, addressLength, checked, checkedSize)) return -1; + int error = 0; + const auto sent = forwards->sendto(descriptor, buffer, length, flags, address, addressLength, &error); + return error == 0 ? sent : Fail(error); + } const auto socket = Lookup(descriptor); if (!socket) return -1; if ((flags & ~GuestSendFlags) != 0) return Fail(45); @@ -1693,6 +1762,18 @@ std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::u int flags, void* address, std::uint32_t* addressLength) { std::uint32_t capacity = 0; if (addressLength && !GuestCopyIn_nid_no_patch(&capacity, addressLength, sizeof(capacity))) return Fail(14); + if (const auto* forwards = ForwardsFor(descriptor).forwards; forwards != nullptr) { + if ((flags & ~GuestReceiveFlags) != 0) return Fail(45); + if (length > INT_MAX) return Fail(40); + if ((!buffer && length) || (address && !addressLength)) return Fail(14); + unsigned char name[28]{}; + std::uint32_t reported = std::min(capacity, sizeof(name)); + int error = 0; + const auto received = forwards->recvfrom(descriptor, buffer, length, flags, address ? name : nullptr, address ? &reported : nullptr, &error); + if (error != 0) return Fail(error); + if (!addressLength) return received; + return ReportAddress(address, addressLength, name, address ? reported : capacity) ? received : Fail(14); + } const auto socket = Lookup(descriptor); if (!socket) return -1; if ((flags & ~GuestReceiveFlags) != 0) return Fail(45); @@ -1754,10 +1835,153 @@ static std::int64_t MessageLength(const GuestMsghdr* message) { } return static_cast(total); } +constexpr int GuestControlCloseOnExec = 0x40000; +#ifndef _WIN32 +constexpr int GuestSocketLevel = 0xffff; +constexpr int GuestRights = 1; +constexpr int GuestControlTruncated = 0x20; +constexpr std::size_t GuestControlHeader = 16; +constexpr std::size_t MostPassedDescriptors = 253; +std::size_t GuestControlSpace(std::size_t bytes) { + return GuestControlHeader + ((bytes + 7u) & ~std::size_t{7}); +} +int PassedDescriptors(const GuestMsghdr& message, std::vector& descriptors) { + const auto* control = static_cast(message.control); + std::size_t left = message.controlLength; + std::size_t at = 0; + while (left != 0) { + std::uint32_t length = 0; + int level = 0; + int type = 0; + if (left < 12) return 22; + std::memcpy(&length, control + at, sizeof(length)); + std::memcpy(&level, control + at + 4, sizeof(level)); + std::memcpy(&type, control + at + 8, sizeof(type)); + if (level != GuestSocketLevel || length > left || length < GuestControlHeader) return 22; + if (type != GuestRights) throw std::runtime_error("sendmsg: control message type " + std::to_string(type) + " is not implemented"); + const std::size_t count = (length - GuestControlHeader) / sizeof(int); + for (std::size_t index = 0; index < count; ++index) { + int passed = -1; + std::memcpy(&passed, control + at + GuestControlHeader + index * sizeof(int), sizeof(passed)); + if (passed < 0 || !DescriptorIsOpen_nid_no_patch(passed)) return 9; + if (IsKqueueDescriptor_nid_no_patch(passed)) return 45; + File::RefuseArchivedFile(passed, "sendmsg"); + const char* emulated = File::IsRandomDevice(passed) ? "a random device" : File::IsDirectoryDescriptor(passed) ? "a directory" : IsJitSharedMemory(passed) ? "a JIT shared memory" : nullptr; + if (emulated != nullptr) NotImplemented_nid_no_patch(("sendmsg: passing " + std::string(emulated) + " descriptor, whose state is libkernel's, fd=" + std::to_string(passed)).c_str()); + descriptors.push_back(passed); + } + const auto space = GuestControlSpace(length - GuestControlHeader); + if (space >= left) break; + at += space; + left -= space; + } + return 0; +} +std::int64_t SendPassing(Socket& socket, const GuestMsghdr& message, std::vector& data, int flags) { + std::vector descriptors; + if (const int error = PassedDescriptors(message, descriptors)) return Fail(error); + if ((flags & ~GuestSendFlags) != 0) return Fail(45); + if (message.name) return Fail(47); + if (!descriptors.empty() && data.empty() && socket.type == 1) NotImplemented_nid_no_patch("sendmsg: SCM_RIGHTS without data on a stream socket"); + if (const int waited = WaitForTransfer(socket, true, flags)) return Fail(waited); + iovec vector{data.data(), data.size()}; + msghdr host{}; + host.msg_iov = &vector; + host.msg_iovlen = 1; + std::vector control(CMSG_SPACE(descriptors.size() * sizeof(int))); + if (!descriptors.empty()) { + host.msg_control = control.data(); + host.msg_controllen = control.size(); + auto* header = CMSG_FIRSTHDR(&host); + header->cmsg_level = SOL_SOCKET; + header->cmsg_type = SCM_RIGHTS; + header->cmsg_len = CMSG_LEN(descriptors.size() * sizeof(int)); + std::memcpy(CMSG_DATA(header), descriptors.data(), descriptors.size() * sizeof(int)); + } + const auto result = ::sendmsg(socket.value, &host, SocketSendFlags(socket, flags)); + return result < 0 ? Fail(SendError(socket.type)) : result; +} +int GuestFromHostSocket(int domain) { + return domain == AF_UNIX ? GuestUnix : domain == AF_INET ? 2 : domain == AF_INET6 ? 28 : -1; +} +int GuestFromHostSocketType(int type) { + return type == SOCK_STREAM ? 1 : type == SOCK_DGRAM ? 2 : type == SOCK_SEQPACKET ? 5 : -1; +} +std::shared_ptr PassedSocket(int native) { + struct stat status{}; + if (::fstat(native, &status) != 0) throw std::runtime_error("recvmsg: a received descriptor cannot be queried, errno " + std::to_string(errno)); + if (!S_ISSOCK(status.st_mode)) return nullptr; + sockaddr_storage address{}; + socklen_t addressLength = sizeof(address); + int type = 0; + socklen_t length = sizeof(type); + if (::getsockname(native, reinterpret_cast(&address), &addressLength) != 0 || ::getsockopt(native, SOL_SOCKET, SO_TYPE, &type, &length) != 0) + throw std::runtime_error("recvmsg: a received socket cannot be queried, errno " + std::to_string(errno)); + const int domain = address.ss_family; + const int family = GuestFromHostSocket(domain); + const int guestType = GuestFromHostSocketType(type); + if (family < 0 || guestType < 0) throw std::runtime_error("recvmsg: a received socket of host domain " + std::to_string(domain) + " and type " + std::to_string(type) + " is not supported"); + return std::make_shared(native, family, guestType); +} +class ReceivedRights { +public: + ReceivedRights() = default; + ReceivedRights(const ReceivedRights&) = delete; + ReceivedRights& operator=(const ReceivedRights&) = delete; + ~ReceivedRights() { Close(); } + void Hold(int native) { natives.push_back(native); } + int Install(bool closeOnExec) { + while (!natives.empty()) { + auto socket = PassedSocket(natives.front()); + const int native = natives.front(); + natives.erase(natives.begin()); + if (!socket) { + numbers.push_back(native); + continue; + } + const int number = ::Install(std::move(socket)); + if (number < 0) return *__error_nid_postfix(); + numbers.push_back(number); + if (closeOnExec) { + std::lock_guard lock(socketsMutex); + closeOnExecDescriptors.insert(number); + } + } + return 0; + } + const std::vector& Numbers() const { return numbers; } + void Close() { + for (const int number : numbers) { + if (GuestSockets::IsOpen(number)) GuestSockets::Close(number); + else ::close(number); + } + for (const int native : natives) ::close(native); + numbers.clear(); + natives.clear(); + } + void Commit() { + numbers.clear(); + natives.clear(); + } +private: + std::vector natives; + std::vector numbers; +}; +#endif std::int64_t APS5_VABI sendmsg_nid_postfix(int descriptor, const GuestMsghdr* message, int flags) { const auto total = MessageLength(message); if (total < 0) return -1; - if (message->control && message->controlLength) throw std::runtime_error("sendmsg: control data is not supported"); + const bool control = message->control && message->controlLength; +#ifdef _WIN32 + if (control) throw std::runtime_error("sendmsg: control data is not supported on Windows"); +#else + std::shared_ptr passing; + if (control) { + passing = Lookup(descriptor); + if (!passing) return -1; + if (passing->family != GuestUnix) throw std::runtime_error("sendmsg: control data on an IP socket is not implemented"); + } +#endif std::vector buffer; try { buffer.resize(static_cast(total)); @@ -1769,6 +1993,9 @@ std::int64_t APS5_VABI sendmsg_nid_postfix(int descriptor, const GuestMsghdr* me if (message->iov[i].length) std::memcpy(buffer.data() + offset, message->iov[i].base, message->iov[i].length); offset += message->iov[i].length; } +#ifndef _WIN32 + if (passing) return SendPassing(*passing, *message, buffer, flags); +#endif return sendto_nid_postfix(descriptor, buffer.data(), buffer.size(), flags, message->name, message->name ? message->nameLength : 0u); } #ifdef _WIN32 @@ -1866,7 +2093,13 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, if (total < 0) return -1; const auto socket = Lookup(descriptor); if (!socket) return -1; - if ((flags & ~GuestReceiveFlags) != 0) return Fail(45); + const std::size_t controlCapacity = header.controlLength; +#ifdef _WIN32 + const bool passing = false; +#else + const bool passing = socket->family == GuestUnix && header.control && header.controlLength && (flags & GuestPeek) == 0; +#endif + if ((flags & ~(GuestReceiveFlags | (passing ? GuestControlCloseOnExec : 0))) != 0) return Fail(45); const bool records = socket->type == 2 || socket->type == 5; std::vector buffer; try { @@ -1877,11 +2110,50 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, sockaddr_storage native{}; socklen_t size = 0; std::int64_t received = 0; +#ifndef _WIN32 + ReceivedRights rights; + bool controlTruncated = false; +#endif const bool shut = ReceiveShut(*socket); std::vector stamps; if (!shut && (total != 0 || socket->type == 1 || ReadableNow(*socket))) { size = sizeof(native); if (const int waited = WaitForTransfer(*socket, false, flags)) return Fail(waited); +#ifndef _WIN32 + if (passing) { + std::vector control(CMSG_SPACE(MostPassedDescriptors * sizeof(int))); + iovec vector{buffer.data(), buffer.size()}; + msghdr host{}; + host.msg_name = &native; + host.msg_namelen = size; + host.msg_iov = &vector; + host.msg_iovlen = 1; + host.msg_control = control.data(); + host.msg_controllen = control.size(); +#ifdef MSG_CMSG_CLOEXEC + constexpr int receiveCloseOnExec = MSG_CMSG_CLOEXEC; +#else + constexpr int receiveCloseOnExec = 0; +#endif + received = ::recvmsg(socket->value, &host, NativeReceiveFlags(flags, socket->type) | ((flags & GuestControlCloseOnExec) != 0 ? receiveCloseOnExec : 0)); + if (received < 0) return Fail(NativeError()); + size = host.msg_namelen; + controlTruncated = (host.msg_flags & MSG_CTRUNC) != 0; + for (auto* header = CMSG_FIRSTHDR(&host); header; header = CMSG_NXTHDR(&host, header)) { + if (header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS) continue; + const auto count = (header->cmsg_len - CMSG_LEN(0)) / sizeof(int); + for (std::size_t index = 0; index < count; ++index) { + int handed = -1; + std::memcpy(&handed, CMSG_DATA(header) + index * sizeof(int), sizeof(handed)); + if constexpr (receiveCloseOnExec == 0) { + if ((flags & GuestControlCloseOnExec) != 0) ::fcntl(handed, F_SETFD, FD_CLOEXEC); + } + rights.Hold(handed); + } + } + if (const int error = rights.Install((flags & GuestControlCloseOnExec) != 0)) return Fail(error); + } else +#endif received = ::recvfrom(socket->value, buffer.data(), static_cast(buffer.size()), NativeReceiveFlags(flags, socket->type), reinterpret_cast(&native), &size); if (received < 0) { @@ -1925,13 +2197,39 @@ std::int64_t APS5_VABI recvmsg_nid_postfix(int descriptor, GuestMsghdr* message, if (!WriteGuest(header.name, address, header.nameLength)) return Fail(14); if (!WriteGuest(&message->nameLength, &header.nameLength, sizeof(header.nameLength))) return Fail(14); } - const std::size_t controlCapacity = header.controlLength; - const std::size_t copied = std::min(stamps.size(), controlCapacity); - if (copied != 0 && !WriteGuest(header.control, stamps.data(), copied)) return Fail(14); - if (copied < stamps.size()) header.flags |= 0x20; - header.controlLength = static_cast(copied); + header.controlLength = 0; +#ifndef _WIN32 + const auto& passed = rights.Numbers(); + if (passing && !passed.empty()) { + const auto bytes = passed.size() * sizeof(int); + std::vector record(GuestControlSpace(bytes), 0); + const auto length = static_cast(GuestControlHeader + bytes); + std::memcpy(record.data(), &length, sizeof(length)); + std::memcpy(record.data() + 4, &GuestSocketLevel, sizeof(GuestSocketLevel)); + std::memcpy(record.data() + 8, &GuestRights, sizeof(GuestRights)); + std::memcpy(record.data() + GuestControlHeader, passed.data(), bytes); + const bool fits = record.size() <= controlCapacity; + if (fits && WriteGuest(header.control, record.data(), record.size())) { + header.controlLength = static_cast(record.size()); + } else { + if (fits) return Fail(14); + rights.Close(); + controlTruncated = true; + } + } + if (controlTruncated) header.flags |= GuestControlTruncated; +#endif + if (!stamps.empty()) { + const std::size_t copied = std::min(stamps.size(), controlCapacity); + if (copied != 0 && !WriteGuest(header.control, stamps.data(), copied)) return Fail(14); + if (copied < stamps.size()) header.flags |= 0x20; + header.controlLength = static_cast(copied); + } constexpr auto results = offsetof(GuestMsghdr, controlLength); if (!WriteGuest(reinterpret_cast(message) + results, reinterpret_cast(&header) + results, sizeof(header) - results)) return Fail(14); +#ifndef _WIN32 + rights.Commit(); +#endif return received; } diff --git a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp index 358c534bae..efaf57f906 100644 --- a/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp +++ b/core/libs/prx/libkernel/SyncOnAddress/src/SyncOnAddress.cpp @@ -25,9 +25,21 @@ constexpr int SYNC_ON_ADDRESS_OK = 0; constexpr int UMTX_OP_RESERVED1 = 1; constexpr int UMTX_OP_WAIT = 2; constexpr int UMTX_OP_WAKE = 3; +constexpr int UMTX_OP_MUTEX_TRYLOCK = 4; +constexpr int UMTX_OP_MUTEX_LOCK = 5; +constexpr int UMTX_OP_MUTEX_UNLOCK = 6; +constexpr int UMTX_OP_SET_CEILING = 7; +constexpr int UMTX_OP_CV_WAIT = 8; +constexpr int UMTX_OP_CV_SIGNAL = 9; +constexpr int UMTX_OP_CV_BROADCAST = 10; constexpr int UMTX_OP_WAIT_UINT = 11; +constexpr int UMTX_OP_RW_RDLOCK = 12; +constexpr int UMTX_OP_RW_WRLOCK = 13; +constexpr int UMTX_OP_RW_UNLOCK = 14; constexpr int UMTX_OP_WAIT_UINT_PRIVATE = 15; constexpr int UMTX_OP_WAKE_PRIVATE = 16; +constexpr int UMTX_OP_MUTEX_WAIT = 17; +constexpr int UMTX_OP_MUTEX_WAKE = 18; constexpr int UMTX_OP_SEM_WAIT = 19; constexpr int UMTX_OP_SEM_WAKE = 20; constexpr int UMTX_OP_NWAKE_PRIVATE = 21; @@ -35,8 +47,23 @@ constexpr int UMTX_OP_MAX = 27; constexpr std::uint32_t UMTX_ABSTIME = 1; constexpr int GUEST_CLOCK_REALTIME = 0; +constexpr std::uint32_t UMUTEX_UNOWNED = 0; +constexpr std::uint32_t UMUTEX_CONTESTED = 0x80000000u; +constexpr std::uint32_t UMUTEX_PRIO_PROTECT = 0x8; +constexpr std::uint32_t UMUTEX_ROBUST = 0x10; +constexpr std::uint32_t CVWAIT_ABSTIME = 0x2; +constexpr std::uint32_t CVWAIT_CLOCKID = 0x4; +constexpr std::uint32_t URWLOCK_PREFER_READER = 0x2; +constexpr std::uint32_t URWLOCK_WRITE_OWNER = 0x80000000u; +constexpr std::uint32_t URWLOCK_WRITE_WAITERS = 0x40000000u; +constexpr std::uint32_t URWLOCK_READ_WAITERS = 0x20000000u; +constexpr std::uint32_t URWLOCK_MAX_READERS = 0x1fffffffu; + +constexpr int GUEST_EPERM = 1; constexpr int GUEST_EFAULT = 14; +constexpr int GUEST_EBUSY = 16; constexpr int GUEST_EINVAL = 22; +constexpr int GUEST_EAGAIN = 35; constexpr int GUEST_EOPNOTSUPP = 45; constexpr int GUEST_ETIMEDOUT = 60; @@ -55,6 +82,30 @@ struct UserSemaphore { std::uint32_t flags; }; +struct UserMutex { + std::uint32_t owner; + std::uint32_t flags; + std::uint32_t ceilings[2]; + std::uint32_t spare[4]; +}; + +struct UserCondition { + std::uint32_t hasWaiters; + std::uint32_t flags; + std::uint32_t clockId; + std::uint32_t spare; +}; + +struct UserRwlock { + std::uint32_t state; + std::uint32_t flags; + std::uint32_t blockedReaders; + std::uint32_t blockedWriters; + std::uint32_t spare[4]; +}; + +static_assert(sizeof(UserMutex) == 32 && sizeof(UserCondition) == 16 && sizeof(UserRwlock) == 32); + struct AddressWaiter { TimedWait::SingleWaiterCondition condition; bool woken = false; @@ -335,6 +386,359 @@ int SemWake(UserSemaphore* semaphore) { return 0; } +std::uint32_t CurrentOwnerId() { + return static_cast(scePthreadGetthreadid()) & ~UMUTEX_CONTESTED; +} + +std::size_t WaiterCount(std::uintptr_t key) { + auto& bucket = BucketOf(key); + std::lock_guard lock(bucket.mutex); + const auto entry = bucket.waiters.find(key); + return entry == bucket.waiters.end() ? 0 : entry->second.size(); +} + +int CheckMutex(UserMutex* mutex) { + if (mutex == nullptr) { + return GUEST_EFAULT; + } + if (!IsAlignedAddress(reinterpret_cast(mutex))) { + APS5_INVALID_ARG_EX; + } + const std::uint32_t flags = std::atomic_ref(mutex->flags).load(); + if ((flags & UMUTEX_ROBUST) != 0) { + NotImplemented_nid_no_patch("_umtx_op on a robust umutex"); + } + return 0; +} + +bool TryAcquireMutex(UserMutex* mutex, std::uint32_t id, int& error) { + std::atomic_ref owner(mutex->owner); + std::uint32_t current = owner.load(); + for (;;) { + if (current == UMUTEX_UNOWNED || current == UMUTEX_CONTESTED) { + if (owner.compare_exchange_weak(current, id | current)) { + error = 0; + return true; + } + continue; + } + if ((current & ~UMUTEX_CONTESTED) == id) { + error = 0; + return true; + } + error = GUEST_EBUSY; + return false; + } +} + +void MarkContested(UserMutex* mutex) { + std::atomic_ref owner(mutex->owner); + std::uint32_t current = owner.load(); + while (current != UMUTEX_UNOWNED && (current & UMUTEX_CONTESTED) == 0 && !owner.compare_exchange_weak(current, current | UMUTEX_CONTESTED)) { + } +} + +int MutexTrylock(UserMutex* mutex) { + if (const int error = CheckMutex(mutex); error != 0) { + return error; + } + if ((std::atomic_ref(mutex->flags).load() & UMUTEX_PRIO_PROTECT) != 0) { + return GUEST_EINVAL; + } + int error = 0; + TryAcquireMutex(mutex, CurrentOwnerId(), error); + return error; +} + +int MutexLock(UserMutex* mutex, bool acquire, const void* sizeArgument, const void* timeArgument, const void* caller) { + if (const int error = CheckMutex(mutex); error != 0) { + return error; + } + if ((std::atomic_ref(mutex->flags).load() & UMUTEX_PRIO_PROTECT) != 0) { + return GUEST_EINVAL; + } + std::optional deadline; + if (const int error = UmtxDeadline(sizeArgument, timeArgument, deadline); error != 0) { + return error; + } + const std::uint32_t id = CurrentOwnerId(); + const auto key = reinterpret_cast(&mutex->owner); + for (;;) { + bool done = false; + int error = 0; + const bool woken = WaitOnKeyUntil(key, [&] { + if (acquire) { + done = TryAcquireMutex(mutex, id, error); + } else { + const std::uint32_t current = std::atomic_ref(mutex->owner).load(); + done = current == UMUTEX_UNOWNED || current == UMUTEX_CONTESTED; + } + if (!done) { + MarkContested(mutex); + } + return done; + }, deadline, caller); + if (done) { + return 0; + } + if (!woken) { + return GUEST_ETIMEDOUT; + } + if (!acquire) { + return 0; + } + } +} + +int MutexUnlock(UserMutex* mutex) { + if (const int error = CheckMutex(mutex); error != 0) { + return error; + } + if ((std::atomic_ref(mutex->flags).load() & UMUTEX_PRIO_PROTECT) != 0) { + NotImplemented_nid_no_patch("_umtx_op unlock of a priority-protected umutex"); + } + std::atomic_ref owner(mutex->owner); + if ((owner.load() & ~UMUTEX_CONTESTED) != CurrentOwnerId()) { + return GUEST_EPERM; + } + const auto key = reinterpret_cast(&mutex->owner); + owner.store(WaiterCount(key) > 1 ? UMUTEX_CONTESTED : UMUTEX_UNOWNED); + WakeAddress(key, 1, false); + return 0; +} + +int MutexWake(UserMutex* mutex) { + if (const int error = CheckMutex(mutex); error != 0) { + return error; + } + std::atomic_ref owner(mutex->owner); + if ((owner.load() & ~UMUTEX_CONTESTED) != 0) { + return 0; + } + const auto key = reinterpret_cast(&mutex->owner); + if (WaiterCount(key) <= 1) { + std::uint32_t contested = UMUTEX_CONTESTED; + owner.compare_exchange_strong(contested, UMUTEX_UNOWNED); + } + WakeAddress(key, 1, false); + return 0; +} + +int ConditionDeadline(UserCondition* condition, std::uint64_t flags, const void* timeArgument, std::optional& deadlineNanos) { + deadlineNanos.reset(); + if (timeArgument == nullptr) { + return 0; + } + const auto timeout = *static_cast(timeArgument); + if (timeout.tv_sec < 0 || timeout.tv_nsec < 0 || timeout.tv_nsec >= NANOS_PER_SECOND) { + return GUEST_EINVAL; + } + std::uint64_t remaining = 0; + if ((flags & CVWAIT_ABSTIME) == 0) { + remaining = SaturatingNanos(timeout.tv_sec, timeout.tv_nsec); + } else { + const int clock = (flags & CVWAIT_CLOCKID) != 0 ? static_cast(std::atomic_ref(condition->clockId).load()) : GUEST_CLOCK_REALTIME; + KernelTimespec now{}; + clock_gettime_nid_postfix(clock, &now); + remaining = SaturatingNanos(timeout.tv_sec - now.tv_sec, timeout.tv_nsec - now.tv_nsec); + } + if (remaining < UNBOUNDED_WAIT_NANOS) { + deadlineNanos = TimedWait::NowNanos() + remaining; + } + return 0; +} + +int ConditionWait(UserCondition* condition, UserMutex* mutex, std::uint64_t flags, const void* timeArgument, const void* caller) { + if (condition == nullptr) { + return GUEST_EFAULT; + } + if (const int error = CheckMutex(mutex); error != 0) { + return error; + } + if ((std::atomic_ref(mutex->flags).load() & UMUTEX_PRIO_PROTECT) != 0) { + NotImplemented_nid_no_patch("_umtx_op condition wait on a priority-protected umutex"); + } + std::optional deadline; + if (const int error = ConditionDeadline(condition, flags, timeArgument, deadline); error != 0) { + return error; + } + const auto key = reinterpret_cast(condition); + auto& bucket = BucketOf(key); + AddressWaiter waiter; + int error = 0; + bool woken = false; + std::optional waited; + DeferringDelivery([&] { + std::unique_lock lock(bucket.mutex); + std::atomic_ref(condition->hasWaiters).store(1); + auto& queue = bucket.waiters[key]; + const auto position = queue.insert(queue.end(), &waiter); + lock.unlock(); + error = MutexUnlock(mutex); + lock.lock(); + if (error == 0) { + const auto isWoken = [&] { return waiter.woken; }; + const auto waitStart = std::chrono::steady_clock::now(); + if (!deadline) { + waiter.condition.Wait(lock, isWoken); + } else { + waiter.condition.WaitUntil(lock, *deadline, isWoken); + } + waited = std::chrono::duration_cast(std::chrono::steady_clock::now() - waitStart); + } + woken = waiter.woken; + if (!woken) { + auto& current = bucket.waiters[key]; + current.erase(position); + if (current.empty()) { + bucket.waiters.erase(key); + std::atomic_ref(condition->hasWaiters).store(0); + } + } + }); + if (waited) { + KernelTraceWait_nid_postfix("cv", caller, static_cast(waited->count()), !woken); + } + if (error != 0) { + return error; + } + return woken ? 0 : GUEST_ETIMEDOUT; +} + +int ConditionWake(UserCondition* condition, bool all) { + if (condition == nullptr) { + return GUEST_EFAULT; + } + const auto key = reinterpret_cast(condition); + DeferringDelivery([&] { + auto& bucket = BucketOf(key); + std::lock_guard lock(bucket.mutex); + const auto entry = bucket.waiters.find(key); + if (entry == bucket.waiters.end()) { + std::atomic_ref(condition->hasWaiters).store(0); + return; + } + auto& queue = entry->second; + do { + AddressWaiter* waiter = queue.front(); + queue.pop_front(); + waiter->woken = true; + waiter->condition.NotifyOne(); + } while (all && !queue.empty()); + if (queue.empty()) { + bucket.waiters.erase(entry); + std::atomic_ref(condition->hasWaiters).store(0); + } + }); + return 0; +} + +int CheckRwlock(UserRwlock* rwlock) { + if (rwlock == nullptr) { + return GUEST_EFAULT; + } + if (!IsAlignedAddress(reinterpret_cast(rwlock))) { + APS5_INVALID_ARG_EX; + } + return 0; +} + +void ClearRwlockBits(UserRwlock* rwlock, std::uint32_t bits) { + std::atomic_ref(rwlock->state).fetch_and(~bits); +} + +int RwlockLock(UserRwlock* rwlock, bool writer, std::uint64_t operationFlags, const void* sizeArgument, const void* timeArgument, const void* caller) { + if (const int error = CheckRwlock(rwlock); error != 0) { + return error; + } + std::optional deadline; + if (const int error = UmtxDeadline(sizeArgument, timeArgument, deadline); error != 0) { + return error; + } + std::atomic_ref state(rwlock->state); + std::atomic_ref blocked(writer ? rwlock->blockedWriters : rwlock->blockedReaders); + const std::uint32_t waiterBit = writer ? URWLOCK_WRITE_WAITERS : URWLOCK_READ_WAITERS; + const bool preferReader = ((std::atomic_ref(rwlock->flags).load() | operationFlags) & URWLOCK_PREFER_READER) != 0; + const auto key = reinterpret_cast(writer ? &rwlock->blockedWriters : &rwlock->state); + for (;;) { + int error = 0; + bool done = false; + const bool woken = WaitOnKeyUntil(key, [&] { + std::uint32_t current = state.load(); + for (;;) { + const bool free = writer ? (current & (URWLOCK_WRITE_OWNER | URWLOCK_MAX_READERS)) == 0 + : (current & URWLOCK_WRITE_OWNER) == 0 && (preferReader || (current & URWLOCK_WRITE_WAITERS) == 0); + if (!free) { + if ((current & waiterBit) == 0 && !state.compare_exchange_weak(current, current | waiterBit)) { + continue; + } + break; + } + if (!writer && (current & URWLOCK_MAX_READERS) == URWLOCK_MAX_READERS) { + error = GUEST_EAGAIN; + done = true; + return true; + } + if (state.compare_exchange_weak(current, writer ? current | URWLOCK_WRITE_OWNER : current + 1)) { + done = true; + return true; + } + } + blocked.fetch_add(1); + return false; + }, deadline, caller); + if (done) { + return error; + } + std::uint32_t left; + { + std::lock_guard lock(BucketOf(key).mutex); + left = blocked.fetch_sub(1) - 1; + if (left == 0 && (writer || woken)) { + ClearRwlockBits(rwlock, waiterBit); + if (blocked.load() != 0) { + std::uint32_t current = state.load(); + while ((current & waiterBit) == 0 && !state.compare_exchange_weak(current, current | waiterBit)) { + } + } + } + } + if (!woken) { + return GUEST_ETIMEDOUT; + } + } +} + +int RwlockUnlock(UserRwlock* rwlock) { + if (const int error = CheckRwlock(rwlock); error != 0) { + return error; + } + std::atomic_ref state(rwlock->state); + std::uint32_t current = state.load(); + std::uint32_t next = 0; + do { + if ((current & URWLOCK_WRITE_OWNER) != 0) { + next = current & ~URWLOCK_WRITE_OWNER; + } else if ((current & URWLOCK_MAX_READERS) != 0) { + next = current - 1; + } else { + return GUEST_EPERM; + } + } while (!state.compare_exchange_weak(current, next)); + if ((next & URWLOCK_MAX_READERS) != 0) { + return 0; + } + const bool preferReader = (std::atomic_ref(rwlock->flags).load() & URWLOCK_PREFER_READER) != 0; + const bool writers = (next & URWLOCK_WRITE_WAITERS) != 0; + const bool readers = (next & URWLOCK_READ_WAITERS) != 0; + if (writers && (!preferReader || !readers)) { + WakeAddress(reinterpret_cast(&rwlock->blockedWriters), 1, false); + } else if (readers) { + WakeAddress(reinterpret_cast(&rwlock->state), std::numeric_limits::max(), false); + } + return 0; +} + } // namespace extern "C" { @@ -392,6 +796,40 @@ int APS5_VABI _umtx_op_nid_postfix(void* object, int operation, std::uint64_t va case UMTX_OP_SEM_WAKE: error = SemWake(static_cast(object)); break; + case UMTX_OP_MUTEX_TRYLOCK: + error = MutexTrylock(static_cast(object)); + break; + case UMTX_OP_MUTEX_LOCK: + case UMTX_OP_MUTEX_WAIT: + error = MutexLock(static_cast(object), operation == UMTX_OP_MUTEX_LOCK, sizeArgument, timeArgument, caller); + break; + case UMTX_OP_MUTEX_UNLOCK: + error = MutexUnlock(static_cast(object)); + break; + case UMTX_OP_MUTEX_WAKE: + error = MutexWake(static_cast(object)); + break; + case UMTX_OP_SET_CEILING: + if (object == nullptr || (std::atomic_ref(static_cast(object)->flags).load() & UMUTEX_PRIO_PROTECT) == 0) { + error = object == nullptr ? GUEST_EFAULT : GUEST_EINVAL; + } else { + NotImplemented_nid_no_patch("_umtx_op ceiling of a priority-protected umutex"); + } + break; + case UMTX_OP_CV_WAIT: + error = ConditionWait(static_cast(object), static_cast(sizeArgument), value, timeArgument, caller); + break; + case UMTX_OP_CV_SIGNAL: + case UMTX_OP_CV_BROADCAST: + error = ConditionWake(static_cast(object), operation == UMTX_OP_CV_BROADCAST); + break; + case UMTX_OP_RW_RDLOCK: + case UMTX_OP_RW_WRLOCK: + error = RwlockLock(static_cast(object), operation == UMTX_OP_RW_WRLOCK, value, sizeArgument, timeArgument, caller); + break; + case UMTX_OP_RW_UNLOCK: + error = RwlockUnlock(static_cast(object)); + break; default: if (operation < 0 || operation >= UMTX_OP_MAX) { error = GUEST_EINVAL; diff --git a/core/libs/prx/libkernel/System/src/Exception.cpp b/core/libs/prx/libkernel/System/src/Exception.cpp index e26b1e26f2..095f8b5ad9 100644 --- a/core/libs/prx/libkernel/System/src/Exception.cpp +++ b/core/libs/prx/libkernel/System/src/Exception.cpp @@ -290,8 +290,11 @@ bool StackWritable(DWORD64 low, DWORD64 high) { struct QueuedDelivery { GuestExceptionHandler handler; int signum; + Pthread thread; std::atomic taken{false}; std::atomic waiting{false}; + std::atomic posted{false}; + std::atomic closed{false}; std::atomic references{1}; }; @@ -299,6 +302,22 @@ void ReleaseQueued(QueuedDelivery* queued) { if (queued != nullptr && queued->references.fetch_sub(1, std::memory_order_acq_rel) == 1) delete queued; } +void SettleQueued(QueuedDelivery* queued) { + if (queued != nullptr) queued->closed.store(true, std::memory_order_release); +} + +void DeferredWaitingEntry(void* parameter) { + auto* queued = static_cast(parameter); + const auto handler = queued->handler; + const int signum = queued->signum; + const bool claimed = !queued->closed.load(std::memory_order_acquire) && !queued->thread->_finished.load(std::memory_order_acquire) && !queued->taken.exchange(true, std::memory_order_acq_rel); + ReleaseQueued(queued); + if (!claimed) return; + CONTEXT context{}; + RtlCaptureContext(&context); + Deliver(handler, signum, context); +} + void CALLBACK WaitingEntry(ULONG_PTR parameter) { auto* queued = reinterpret_cast(parameter); const auto handler = queued->handler; @@ -735,7 +754,7 @@ Attempt TryDeliver(Pthread thread, HANDLE native, GuestExceptionHandler handler, continue; } if (thread->waitCount.load(std::memory_order_seq_cst) > 0) { - if (queued == nullptr) queued = new QueuedDelivery{handler, signum}; + if (queued == nullptr) queued = new QueuedDelivery{handler, signum, thread}; const bool accepted = QueueWaitingDelivery(native, queued); ResumeThread(native); if (!accepted) throw std::runtime_error("sceKernelRaiseException: cannot queue delivery to the waiting thread"); @@ -756,10 +775,16 @@ Attempt TryDeliver(Pthread thread, HANDLE native, GuestExceptionHandler handler, Redirect(native, delivery); return Attempt::Delivered; } - if (running == CodeKind::Host && queued == nullptr) queued = new QueuedDelivery{handler, signum}; + bool postAfterResume = false; + if (running == CodeKind::Host) { + if (queued == nullptr) queued = new QueuedDelivery{handler, signum, thread}; + postAfterResume = !queued->posted.exchange(true, std::memory_order_acq_rel); + if (postAfterResume) queued->references.fetch_add(1, std::memory_order_acq_rel); + } DWORD64 unknown = 0; if (arm != nullptr && running == CodeKind::Host && !restoring) ArmReturnBreak(native, delivery.context, queued, *arm, unknown); ResumeThread(native); + if (postAfterResume) TimedWait::QueueForDeliveryDrain(GetThreadId(native), &thread->_finished, &DeferredWaitingEntry, queued); if (running == CodeKind::Unknown) { static_cast(ClassifyImage(image)); continue; @@ -823,6 +848,7 @@ private: continue; } DisarmReturnBreak(raise.native, raise.arm); + SettleQueued(raise.queued); ReleaseQueued(raise.queued); CloseHandle(raise.native); } @@ -864,6 +890,7 @@ bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) { const auto attempt = TryDeliver(thread, native, handler, signum, queued, &arm); if (attempt == Attempt::Delivered || attempt == Attempt::Exited) { DisarmReturnBreak(native, arm); + SettleQueued(queued); ReleaseQueued(queued); return attempt == Attempt::Delivered; } @@ -871,6 +898,7 @@ bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) { if (attempt == Attempt::Restoring && now >= retryDeadline) { const bool taken = queued != nullptr && queued->taken.exchange(true, std::memory_order_acq_rel); DisarmReturnBreak(native, arm); + SettleQueued(queued); ReleaseQueued(queued); if (taken) return true; throw std::runtime_error("sceKernelRaiseException: the target thread stayed inside NtContinue for 1 s"); @@ -879,6 +907,7 @@ bool RaiseOn(Pthread thread, GuestExceptionHandler handler, int signum) { HANDLE owned = nullptr; if (!DuplicateHandle(GetCurrentProcess(), native, GetCurrentProcess(), &owned, 0, FALSE, DUPLICATE_SAME_ACCESS)) { DisarmReturnBreak(native, arm); + SettleQueued(queued); ReleaseQueued(queued); throw std::runtime_error("sceKernelRaiseException: cannot keep the target thread for a later delivery"); } diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index 999dec4541..f2d8762f28 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -239,6 +239,7 @@ bool hasPriorityRange(int policy) { } constexpr int guestNoProcess = 3; +constexpr int guestAccess = 13; constexpr int guestFault = 14; constexpr int guestInvalid = 22; constexpr int guestRlimitCpu = 0; @@ -1231,6 +1232,10 @@ int APS5_VABI setpriority_nid_postfix(int which, int who, int niceval) { *__error_nid_postfix() = guestNoProcess; return -1; } + if (niceval < 0) { + *__error_nid_postfix() = guestAccess; + return -1; + } if (which != guestPriorityProcess) { throw std::runtime_error(std::string("setpriority: setting the nice value of ") + (which == guestPriorityGroup ? "PRIO_PGRP" : "PRIO_USER") + " to " + std::to_string(niceval) + diff --git a/core/libs/prx/libkernel/System/src/Rtld.cpp b/core/libs/prx/libkernel/System/src/Rtld.cpp index cbce3c4e09..9e83e59506 100644 --- a/core/libs/prx/libkernel/System/src/Rtld.cpp +++ b/core/libs/prx/libkernel/System/src/Rtld.cpp @@ -17,8 +17,12 @@ #include #else #include +#ifdef __APPLE__ +#include +#else #include #endif +#endif #ifdef _WIN32 extern "C" void ForgetUnloadedUnwindImages_nid_no_patch(); @@ -39,6 +43,15 @@ const void* imageContaining(const void* address, bool reference, void*& handle, if (!GetModuleHandleExW(flags, reinterpret_cast(address), &module)) return nullptr; if (reference) handle = module; return module; +#elif defined(__APPLE__) + Dl_info info{}; + if (!dladdr(address, &info) || !info.dli_fbase) return nullptr; + const auto* header = static_cast(info.dli_fbase); + if (reference && header->filetype != MH_EXECUTE && info.dli_fname && *info.dli_fname) { + handle = dlopen(info.dli_fname, RTLD_LAZY | RTLD_NOLOAD); + if (!handle) throw std::runtime_error(std::string(caller) + ": cannot reference " + info.dli_fname); + } + return info.dli_fbase; #else Dl_info info{}; link_map* image = nullptr; diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index 56f806e9ec..3f17f91f61 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -9,7 +9,6 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include -#include #include #include #include @@ -363,7 +362,7 @@ namespace { constexpr std::int64_t MaxItimerSeconds = 0x7fffffff / 2; struct AlarmState { std::mutex lock; - std::condition_variable changed; + TimedWait::Condition changed; std::optional deadline; std::chrono::microseconds interval{0}; bool threadStarted = false; @@ -411,18 +410,19 @@ void RunAlarms() { std::unique_lock lock(alarms.lock); for (;;) { if (!alarms.deadline) { - alarms.changed.wait(lock); + alarms.changed.Wait(lock); continue; } const auto deadline = *alarms.deadline; - if (alarms.changed.wait_until(lock, deadline) != std::cv_status::timeout || alarms.deadline != deadline) continue; + const auto now = std::chrono::steady_clock::now(); + if (now < deadline) { + const auto remaining = std::chrono::duration_cast(deadline - now).count(); + alarms.changed.WaitUntil(lock, TimedWait::NowNanos() + static_cast(remaining)); + continue; + } const auto interval = alarms.interval; if (interval.count() == 0) alarms.deadline.reset(); - else { - const auto now = std::chrono::steady_clock::now(); - const auto periods = now < deadline ? 1 : (now - deadline) / interval + 1; - alarms.deadline = deadline + periods * interval; - } + else alarms.deadline = deadline + ((now - deadline) / interval + 1) * interval; lock.unlock(); ExpireAlarm(interval); lock.lock(); @@ -439,7 +439,7 @@ void NotifyAlarms(AlarmState& alarms) { std::thread(RunAlarms).detach(); alarms.threadStarted = true; } - alarms.changed.notify_all(); + alarms.changed.NotifyAll(); } } diff --git a/core/libs/prx/libkernel/System/src/SigvecRegistration.cpp b/core/libs/prx/libkernel/System/src/SigvecRegistration.cpp new file mode 100644 index 0000000000..495a909494 --- /dev/null +++ b/core/libs/prx/libkernel/System/src/SigvecRegistration.cpp @@ -0,0 +1,13 @@ +#include "prx/libc/include/LegacySignals.hpp" + +namespace { + +struct RegisterSigvecOnLoad { + RegisterSigvecOnLoad() { + RegisterSigvec_nid_no_patch(sigaction_nid_postfix); + } +}; + +const RegisterSigvecOnLoad registerSigvec; + +} diff --git a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp index a29ed2a43f..8c79416c18 100644 --- a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp +++ b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp @@ -601,6 +601,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.proc.proc") == 0) { + static constexpr int mib[] = {1, 14, 8}; + 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); @@ -625,7 +629,10 @@ int APS5_VABI sysctlbyname_nid_postfix(const char* name, void* oldValue, std::si return CopyOut(block, sizeof(block), oldValue, oldLength); } if (std::strcmp(name, "vm.overcommit") == 0) { - if (newValue != nullptr) throw std::runtime_error(std::string(__func__) + ": setting vm.overcommit"); + if (newValue != nullptr) { + *__error_nid_postfix() = 1; + return -1; + } static constexpr int overcommit = 0; return CopyOut(&overcommit, sizeof(overcommit), oldValue, oldLength); } @@ -803,6 +810,7 @@ int APS5_VABI sysctlnametomib_nid_postfix(const char* name, int* mib, std::size_ {"kern.maxfilesperproc", {1, 27}, 2}, {"kern.maxproc", {1, 6}, 2}, {"kern.proc.pathname", {1, 14, 12}, 3}, + {"kern.proc.proc", {1, 14, 8}, 3}, {"kern.ps_strings", {1, 32}, 2}, {"kern.rng_pseudo", {1, 50}, 2}, {"vm.loadavg", {2, 2}, 2}, diff --git a/core/libs/prx/libkernel/System/src/UserContext.cpp b/core/libs/prx/libkernel/System/src/UserContext.cpp index ea580d7132..c984430564 100644 --- a/core/libs/prx/libkernel/System/src/UserContext.cpp +++ b/core/libs/prx/libkernel/System/src/UserContext.cpp @@ -226,7 +226,7 @@ int APS5_VABI Aps5GetcontextFinish_nid_no_patch(UserUcontext* context, const Reg machine.fpformat = FpFormatXmm; machine.ownedfp = FpOwnedFpu; std::memcpy(machine.fpstate, frame->fxsave, sizeof(frame->fxsave)); - std::memcpy(context, &captured, offsetof(UserUcontext, link)); + if (!GuestCopyOut_nid_no_patch(context, &captured, offsetof(UserUcontext, link))) return Fail(GuestEfault); return 0; } @@ -280,28 +280,32 @@ extern "C" { int APS5_VABI setcontext_nid_postfix(const UserUcontext* context) { if (context == nullptr) return Fail(GuestEinval); - const auto& machine = context->mcontext; + UserUcontext local{}; + if (!GuestCopyIn_nid_no_patch(&local, context, offsetof(UserUcontext, link))) return Fail(GuestEfault); + const auto& machine = local.mcontext; if (machine.len != sizeof(UserMcontext) || (machine.flags & ~McontextFlagMask) != 0) return Fail(GuestEinval); - RestoreContext(context, FpuSource::Context); + RestoreContext(&local, FpuSource::Context); } int SigreturnContext_nid_no_patch(const UserUcontext* context) { if (context == nullptr) return Fail(GuestEfault); - const auto& machine = context->mcontext; + UserUcontext local{}; + if (!GuestCopyIn_nid_no_patch(&local, context, sizeof(local))) return Fail(GuestEfault); + const auto& machine = local.mcontext; if ((machine.flags & ~McontextFlagMask) != 0) return Fail(GuestEinval); if (((machine.rflags ^ __builtin_ia32_readeflags_u64()) & ~UserChangeableFlags) != 0) return Fail(GuestEinval); if ((machine.cs & 3) != UserPrivilegeLevel) NotImplemented_nid_no_patch("sigreturn: a %cs that is not a user selector, for which FreeBSD raises SIGBUS"); if ((machine.flags & McontextHasFpxstate) != 0) NotImplemented_nid_no_patch("sigreturn: an extended FPU state (_MC_HASFPXSTATE)"); - if (machine.fpformat == FpFormatNodev) RestoreContext(context, FpuSource::Current); + if (machine.fpformat == FpFormatNodev) RestoreContext(&local, FpuSource::Current); if (machine.fpformat != FpFormatXmm) return Fail(GuestEinval); - if (machine.ownedfp == FpOwnedNone) RestoreContext(context, FpuSource::Initial); + if (machine.ownedfp == FpOwnedNone) RestoreContext(&local, FpuSource::Initial); if (machine.ownedfp != FpOwnedFpu && machine.ownedfp != FpOwnedPcb) return Fail(GuestEinval); - RestoreContext(context, FpuSource::Context); + RestoreContext(&local, FpuSource::Context); } int APS5_VABI Aps5SwapcontextFinish_nid_no_patch(UserUcontext* saved, const UserUcontext* next, const RegisterFrame* frame) { if (saved == nullptr || next == nullptr) return Fail(GuestEinval); - Aps5GetcontextFinish_nid_no_patch(saved, frame); + if (const int error = Aps5GetcontextFinish_nid_no_patch(saved, frame)) return error; return setcontext_nid_postfix(next); } diff --git a/core/libs/prx/libkernel/Time/Ktimer.cpp b/core/libs/prx/libkernel/Time/Ktimer.cpp index 933a20bc60..e3874ac020 100644 --- a/core/libs/prx/libkernel/Time/Ktimer.cpp +++ b/core/libs/prx/libkernel/Time/Ktimer.cpp @@ -118,6 +118,7 @@ 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) { if (value == nullptr) return static_cast(Fail(GuestEfault)); + if ((flags & ~TimerAbstime) != 0) return static_cast(Fail(GuestEinval)); std::lock_guard lock(g_ktimerLock); Ktimer* timer = FindTimer(id); if (timer == nullptr) return static_cast(Fail(GuestEinval)); diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index fefda9aeb8..dcdb12d277 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -582,6 +582,10 @@ static int ReadClock(int clockId, KernelTimespec* tp) { } int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { + if (!IsCpuClock(clockId) && (clockId < 0 || clockId >= 32)) { + *__error_nid_postfix() = GUEST_EINVAL; + return -1; + } KernelTimespec value{}; if (ReadClock(clockId, &value) != 0) return -1; if (tp == nullptr) { @@ -724,9 +728,8 @@ int APS5_VABI clock_settime_nid_postfix(int clockId, const KernelTimespec* tp) { *__error_nid_postfix() = GUEST_EINVAL; return -1; } - char requested[48]; - std::snprintf(requested, sizeof(requested), "%lld.%09lld", static_cast(time.tv_sec), static_cast(time.tv_nsec)); - throw std::runtime_error(std::string("clock_settime: setting CLOCK_REALTIME to ") + requested + " is not supported, the host clock is not changed"); + NotImplemented_nid_no_patch("clock_settime"); + return 0; } int SetTimeOfDay_nid_no_patch(const KernelTimeval* tv, const KernelTimezone* tz) { @@ -821,17 +824,35 @@ int APS5_VABI sceKernelConvertLocaltimeToUtc(int64_t local_time, int64_t reserve (void)reserved; const HostZone guess = HostZoneAt(local_time); const HostZone zone = HostZoneAt(local_time - guess.standardEast - guess.daylight); - if (utc_time != nullptr) *utc_time = local_time - zone.standardEast - zone.daylight; - if (timezone != nullptr) *timezone = ZoneState(local_time, zone); - if (dst_seconds != nullptr) *dst_seconds = static_cast(HostDaylightAmount(local_time - zone.standardEast - zone.daylight)); + if (utc_time != nullptr) { + const int64_t converted = local_time - zone.standardEast - zone.daylight; + if (!GuestCopyOut_nid_no_patch(utc_time, &converted, sizeof(converted))) return SceKernelErrorEfault; + } + if (timezone != nullptr) { + const KernelTimesec state = ZoneState(local_time, zone); + if (!GuestCopyOut_nid_no_patch(timezone, &state, sizeof(state))) return SceKernelErrorEfault; + } + if (dst_seconds != nullptr) { + const int32_t daylight = static_cast(HostDaylightAmount(local_time - zone.standardEast - zone.daylight)); + if (!GuestCopyOut_nid_no_patch(dst_seconds, &daylight, sizeof(daylight))) return SceKernelErrorEfault; + } return 0; } int APS5_VABI sceKernelConvertUtcToLocaltime(int64_t utc_time, int64_t* local_time, KernelTimesec* st, int32_t* dst_sec) { const HostZone zone = HostZoneAt(utc_time); - if (local_time != nullptr) *local_time = utc_time + zone.standardEast + zone.daylight; - if (st != nullptr) *st = ZoneState(utc_time, zone); - if (dst_sec != nullptr) *dst_sec = static_cast(zone.daylight); + if (local_time != nullptr) { + const int64_t converted = utc_time + zone.standardEast + zone.daylight; + if (!GuestCopyOut_nid_no_patch(local_time, &converted, sizeof(converted))) return SceKernelErrorEfault; + } + if (st != nullptr) { + const KernelTimesec state = ZoneState(utc_time, zone); + if (!GuestCopyOut_nid_no_patch(st, &state, sizeof(state))) return SceKernelErrorEfault; + } + if (dst_sec != nullptr) { + const int32_t daylight = static_cast(zone.daylight); + if (!GuestCopyOut_nid_no_patch(dst_sec, &daylight, sizeof(daylight))) return SceKernelErrorEfault; + } return 0; } diff --git a/core/libs/prx/libkernel/Time/TimedWait.cpp b/core/libs/prx/libkernel/Time/TimedWait.cpp index e49b8d43de..299ba46b1b 100644 --- a/core/libs/prx/libkernel/Time/TimedWait.cpp +++ b/core/libs/prx/libkernel/Time/TimedWait.cpp @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include #include #ifdef _WIN32 @@ -50,6 +52,14 @@ struct ThreadWaitState { std::atomic own{0}; std::atomic* bound = &own; }; + +struct DeferredDrain { + void (*run)(void*); + void* argument; +}; + +std::mutex drainBoxesLock; +std::unordered_map> drainBoxes; ThreadWaitState& WaitState() { #ifdef _WIN32 return WindowsThreadLocal::Get(); @@ -72,6 +82,7 @@ void DrainApcs() { state->fetch_sub(1, std::memory_order_seq_cst); if (testAlert) testAlert(); else SleepEx(0, TRUE); + ReapDeliveryDrain(); } DWORD AlertableWait(DWORD count, const HANDLE* handles, DWORD milliseconds) { @@ -159,6 +170,48 @@ struct Waiter { } }; +std::atomic& DrainPostHook() { + static std::atomic hook{nullptr}; + return hook; +} + +void SetDeliveryDrainPostHook(void (*hook)()) { + DrainPostHook().store(hook, std::memory_order_release); +} + +void QueueForDeliveryDrain(unsigned long thread, const std::atomic* targetFinished, void (*run)(void*), void* argument) { + if (const auto hook = DrainPostHook().load(std::memory_order_acquire)) hook(); + bool queued = false; + { + const std::lock_guard lock(drainBoxesLock); + if (targetFinished == nullptr || !targetFinished->load(std::memory_order_acquire)) { + drainBoxes[thread].push_back({run, argument}); + queued = true; + } + } + if (!queued) run(argument); +} + +void ReapDeliveryDrain() { + std::vector deferred; + { + const std::lock_guard lock(drainBoxesLock); + const auto box = drainBoxes.find(GetCurrentThreadId()); + if (box != drainBoxes.end()) { + deferred = std::move(box->second); + drainBoxes.erase(box); + } + } + for (const auto& entry : deferred) entry.run(entry.argument); +} + +std::size_t DeliveryDrainPending() { + const std::lock_guard lock(drainBoxesLock); + std::size_t pending = 0; + for (const auto& box : drainBoxes) pending += box.second.size(); + return pending; +} + namespace { Waiter* ThisThreadWaiter() { diff --git a/core/libs/prx/libkernel/Time/include/TimedWait.hpp b/core/libs/prx/libkernel/Time/include/TimedWait.hpp index 9ad4e09f03..e072e1a860 100644 --- a/core/libs/prx/libkernel/Time/include/TimedWait.hpp +++ b/core/libs/prx/libkernel/Time/include/TimedWait.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,11 @@ public: void DeliverDeferred(); #ifdef _WIN32 +void QueueForDeliveryDrain(unsigned long thread, const std::atomic* targetFinished, void (*run)(void*), void* argument); +void ReapDeliveryDrain(); +std::size_t DeliveryDrainPending(); +void SetDeliveryDrainPostHook(void (*hook)()); + constexpr unsigned PARK_SPINS = 1000; void SpinPause(); diff --git a/core/libs/prx/libkernel/Unimplemented.cpp b/core/libs/prx/libkernel/Unimplemented.cpp index 20c5d80c94..10b8e862d2 100644 --- a/core/libs/prx/libkernel/Unimplemented.cpp +++ b/core/libs/prx/libkernel/Unimplemented.cpp @@ -128,6 +128,11 @@ int APS5_VABI sceKernelGetProcessAbiType() { return 0; } +int APS5_VABI sceKernelProtectDirectMemoryForPID(int64_t offset, size_t len, int prot, int pid) { + NotImplemented_nid_no_patch(__func__); + return 0; +} + } extern "C" { diff --git a/core/libs/prx/libkernel/include/Api.hpp b/core/libs/prx/libkernel/include/Api.hpp index ebcc80b0a9..9131f61a63 100644 --- a/core/libs/prx/libkernel/include/Api.hpp +++ b/core/libs/prx/libkernel/include/Api.hpp @@ -207,6 +207,8 @@ int APS5_VABI sceKernelWriteModifyMtypeProtectCommand(void* dst, std::uint64_t a 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 sceKernelAllocateDirectMemoryForApp(int64_t searchStart, int64_t searchEnd, size_t len, size_t alignment, int memoryType, int64_t* physAddrOut); +int APS5_VABI sceKernelProtectDirectMemoryForPID(int64_t offset, size_t len, int prot, int pid); 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); @@ -243,6 +245,7 @@ int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int numEntries, i 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(); +bool APS5_VABI DirectMemoryAliased_nid_postfix(std::uintptr_t address, std::size_t bytes); 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); @@ -702,6 +705,7 @@ 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 tcgetsid_nid_postfix(int descriptor); int APS5_VABI sched_getparam_nid_postfix(int pid, KernelSchedParam* param); int APS5_VABI sched_getscheduler_nid_postfix(int pid); int APS5_VABI sched_setparam_nid_postfix(int pid, const KernelSchedParam* param); diff --git a/core/libs/prx/libkernel_sys/AdditionalExports.cpp b/core/libs/prx/libkernel_sys/AdditionalExports.cpp index 38cfeae140..0c574123e4 100644 --- a/core/libs/prx/libkernel_sys/AdditionalExports.cpp +++ b/core/libs/prx/libkernel_sys/AdditionalExports.cpp @@ -1,6 +1,15 @@ #include "prx/libkernel_sys/include/AdditionalExports.hpp" #include "prx/libc/include/General.hpp" +#include +#include + +namespace { + +std::atomic g_flag1WXQlxMpnNA{0}; + +} + extern "C" { int APS5_VABI __cfi_slowpath_diag_nid_postfix() { @@ -213,8 +222,9 @@ int APS5_VABI libkernel_sysUnknown_0CV40L3tznI_nid_no_patch() { APS5_EXPORT("0CV40L3tznI", libkernel_sysUnknown_0CV40L3tznI_nid_no_patch); -int APS5_VABI libkernel_sysUnknown_1WXQlxMpnNA_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::uint64_t APS5_VABI libkernel_sysUnknown_1WXQlxMpnNA_nid_no_patch() { + g_flag1WXQlxMpnNA.store(1, std::memory_order_relaxed); + return 0; } APS5_EXPORT("1WXQlxMpnNA", libkernel_sysUnknown_1WXQlxMpnNA_nid_no_patch); @@ -428,8 +438,8 @@ int APS5_VABI libkernel_sysUnknown_C3xgqN95IoE_nid_no_patch() { APS5_EXPORT("C3xgqN95IoE", libkernel_sysUnknown_C3xgqN95IoE_nid_no_patch); -int APS5_VABI libkernel_sysUnknown_C49jelxiaVE_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::uint64_t APS5_VABI libkernel_sysUnknown_C49jelxiaVE_nid_no_patch() { + return 0x20018u; } APS5_EXPORT("C49jelxiaVE", libkernel_sysUnknown_C49jelxiaVE_nid_no_patch); @@ -485,8 +495,8 @@ int APS5_VABI libkernel_sysUnknown_FIUPjhnoI_plus_o_nid_no_patch() { APS5_EXPORT("FIUPjhnoI+o", libkernel_sysUnknown_FIUPjhnoI_plus_o_nid_no_patch); -int APS5_VABI libkernel_sysUnknown_FR0odl35yQY_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::uint64_t APS5_VABI libkernel_sysUnknown_FR0odl35yQY_nid_no_patch() { + return g_flag1WXQlxMpnNA.load(std::memory_order_relaxed); } APS5_EXPORT("FR0odl35yQY", libkernel_sysUnknown_FR0odl35yQY_nid_no_patch); @@ -619,8 +629,8 @@ int APS5_VABI libkernel_sysUnknown_MKlp_minus_bW1bz8_nid_no_patch() { APS5_EXPORT("MKlp-bW1bz8", libkernel_sysUnknown_MKlp_minus_bW1bz8_nid_no_patch); -int APS5_VABI libkernel_sysUnknown_N94a0T4F4_plus_U_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::uint64_t APS5_VABI libkernel_sysUnknown_N94a0T4F4_plus_U_nid_no_patch() { + return 0x8002004eu; } APS5_EXPORT("N94a0T4F4+U", libkernel_sysUnknown_N94a0T4F4_plus_U_nid_no_patch); @@ -986,8 +996,8 @@ int APS5_VABI libkernel_sysUnknown_gBiaAGKEvHM_nid_no_patch() { APS5_EXPORT("gBiaAGKEvHM", libkernel_sysUnknown_gBiaAGKEvHM_nid_no_patch); -int APS5_VABI libkernel_sysUnknown_gJ5gpW45mU0_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::uint64_t APS5_VABI libkernel_sysUnknown_gJ5gpW45mU0_nid_no_patch() { + return 0; } APS5_EXPORT("gJ5gpW45mU0", libkernel_sysUnknown_gJ5gpW45mU0_nid_no_patch); @@ -1142,8 +1152,8 @@ int APS5_VABI sceKernelInternalUnmapMapperSharedMemory() { } -int APS5_VABI libkernel_sysUnknown_pCbBddNl66Y_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::uint64_t APS5_VABI libkernel_sysUnknown_pCbBddNl66Y_nid_no_patch() { + return 0; } APS5_EXPORT("pCbBddNl66Y", libkernel_sysUnknown_pCbBddNl66Y_nid_no_patch); @@ -1272,8 +1282,8 @@ int APS5_VABI libkernel_sysUnknown_vfYaxkxnJXQ_nid_no_patch() { APS5_EXPORT("vfYaxkxnJXQ", libkernel_sysUnknown_vfYaxkxnJXQ_nid_no_patch); -int APS5_VABI libkernel_sysUnknown_vq2_plus_HyP5U5I_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::uint64_t APS5_VABI libkernel_sysUnknown_vq2_plus_HyP5U5I_nid_no_patch() { + return 0; } APS5_EXPORT("vq2+HyP5U5I", libkernel_sysUnknown_vq2_plus_HyP5U5I_nid_no_patch); @@ -1330,14 +1340,14 @@ int APS5_VABI libkernel_sysUnknown_z0qKN5kkVj0_nid_no_patch() { APS5_EXPORT("z0qKN5kkVj0", libkernel_sysUnknown_z0qKN5kkVj0_nid_no_patch); -int APS5_VABI libkernel_sysUnknown_zAloppPJxy0_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::uint64_t APS5_VABI libkernel_sysUnknown_zAloppPJxy0_nid_no_patch() { + return 0; } APS5_EXPORT("zAloppPJxy0", libkernel_sysUnknown_zAloppPJxy0_nid_no_patch); -int APS5_VABI libkernel_sysUnknown_z_plus_e0vV_minus_tnH4_nid_no_patch() { - NotImplemented_nid_no_patch(__func__); +std::uint64_t APS5_VABI libkernel_sysUnknown_z_plus_e0vV_minus_tnH4_nid_no_patch() { + return 0; } APS5_EXPORT("z+e0vV-tnH4", libkernel_sysUnknown_z_plus_e0vV_minus_tnH4_nid_no_patch); diff --git a/core/libs/prx/libkernel_sys/Aio.cpp b/core/libs/prx/libkernel_sys/Aio.cpp index f81b5842de..6f6dc680a5 100644 --- a/core/libs/prx/libkernel_sys/Aio.cpp +++ b/core/libs/prx/libkernel_sys/Aio.cpp @@ -1,9 +1,9 @@ #include "prx/libkernel_sys/include/Api.hpp" #include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/Api.hpp" +#include "prx/libkernel/Time/include/TimedWait.hpp" #include #include -#include #include #include #include @@ -71,7 +71,7 @@ struct Job { struct Aio { std::mutex lock; - std::condition_variable changed; + TimedWait::Condition changed; bool used = false; bool held = false; bool started = false; @@ -104,7 +104,7 @@ void Worker() { auto& aio = Instance(); std::unique_lock lock(aio.lock); for (;;) { - aio.changed.wait(lock, [&] { return !aio.held && !aio.queued.empty(); }); + aio.changed.Wait(lock, [&] { return !aio.held && !aio.queued.empty(); }); auto job = aio.queued.front(); aio.queued.pop_front(); job->state = State::Running; @@ -113,7 +113,7 @@ void Worker() { lock.lock(); job->state = State::Done; aio.done.push_back(job); - aio.changed.notify_all(); + aio.changed.NotifyAll(); } } @@ -122,7 +122,7 @@ void Complete(Aio& aio, const std::shared_ptr& job, std::int64_t status, in job->error = error; job->state = State::Done; aio.done.push_back(job); - aio.changed.notify_all(); + aio.changed.NotifyAll(); } std::optional ReadyError(const std::shared_ptr& job) { @@ -190,7 +190,7 @@ int Queue(GuestAiocb* user, int opcode) { aio.started = true; std::thread(Worker).detach(); } - aio.changed.notify_all(); + aio.changed.NotifyAll(); return 0; } @@ -202,7 +202,7 @@ void AioHoldWorkerForTest_nid_no_patch(bool hold) { auto& aio = Instance(); std::lock_guard lock(aio.lock); aio.held = hold; - aio.changed.notify_all(); + aio.changed.NotifyAll(); } int APS5_VABI aio_read_nid_postfix(GuestAiocb* aiocb) { @@ -241,10 +241,11 @@ 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) { if (count < 0 || count > static_cast(QueuePerProcess)) return Fail(GuestEinval); - std::optional wait; + std::optional deadline; if (timeout != nullptr) { - wait = WaitTime(*timeout); + const auto wait = WaitTime(*timeout); if (!wait) return Fail(GuestEinval); + deadline = TimedWait::NowNanos() + static_cast(wait->count()); } auto& aio = Instance(); std::unique_lock lock(aio.lock); @@ -260,20 +261,21 @@ int APS5_VABI aio_suspend_nid_postfix(const GuestAiocb* const* list, int count, } } 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); + if (!deadline) aio.changed.Wait(lock); + else if (!aio.changed.WaitUntil(lock, *deadline)) 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; + std::optional deadline; bool poll = false; if (timeout != nullptr) { poll = timeout->tv_sec == 0 && timeout->tv_nsec == 0; if (!poll) { - wait = WaitTime(*timeout); + const auto wait = WaitTime(*timeout); if (!wait) return Fail(GuestEinval); + deadline = TimedWait::NowNanos() + static_cast(wait->count()); } } auto& aio = Instance(); @@ -281,8 +283,8 @@ std::int64_t APS5_VABI aio_waitcomplete_nid_postfix(GuestAiocb** result, const K 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 (!deadline) aio.changed.Wait(lock); + else if (!aio.changed.WaitUntil(lock, *deadline) && aio.done.empty()) return Fail(GuestEagain); } if (result != nullptr) *result = aio.done.front()->user; return Consume(aio, aio.done.begin()); diff --git a/core/libs/prx/libkernel_sys/CMakeLists.txt b/core/libs/prx/libkernel_sys/CMakeLists.txt index ba729d0ae3..30f1c80ec7 100644 --- a/core/libs/prx/libkernel_sys/CMakeLists.txt +++ b/core/libs/prx/libkernel_sys/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(${LIB_NAME} SHARED EXCLUDE_FROM_ALL Export.cpp Export.Discovery.cpp Aio.cpp Cpuset.cpp ThreadQueries.cpp) +add_library(${LIB_NAME} SHARED EXCLUDE_FROM_ALL Export.cpp Export.Discovery.cpp Aio.cpp Cpuset.cpp ThreadQueries.cpp ../libkernel/Time/TimedWait.cpp) target_include_directories(${LIB_NAME} PRIVATE ${LIBS_INCLUDE_DIR}) target_sources(${LIB_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/AdditionalExports.cpp) configure_windows_unwind(${LIB_NAME}) diff --git a/core/libs/prx/libkernel_sys/Cpuset.cpp b/core/libs/prx/libkernel_sys/Cpuset.cpp index 45b3b79b81..ececefdc6f 100644 --- a/core/libs/prx/libkernel_sys/Cpuset.cpp +++ b/core/libs/prx/libkernel_sys/Cpuset.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -91,18 +92,40 @@ int APS5_VABI cpuset_getid_nid_postfix(int level, int which, std::int64_t id, in if (which < WhichThread || which > WhichDomain || which == WhichIrq || which == WhichDomain) { return Fail(GuestEinval); } - if (which != WhichProcess) { - NotImplemented_nid_no_patch("cpuset_getid other than process"); - } - if (id != -1 && id != getpid_nid_postfix()) { - return Fail(GuestEsrch); + if (which != WhichProcess && which != WhichCpuset) { + NotImplemented_nid_no_patch("cpuset_getid other than process or cpuset"); } int resultId = 0; - if (level == LevelRoot) { - resultId = RootSetId; + if (which == WhichProcess) { + if (id != -1 && id != getpid_nid_postfix()) { + return Fail(GuestEsrch); + } + if (level == LevelRoot) { + resultId = RootSetId; + } else { + std::lock_guard lock(setMutex); + resultId = currentProcessSetId; + } } else { - std::lock_guard lock(setMutex); - resultId = currentProcessSetId; + if (id == -1) { + if (level == LevelRoot) { + resultId = RootSetId; + } else { + std::lock_guard lock(setMutex); + resultId = currentProcessSetId; + } + } else { + const int lookupId = static_cast(id); + std::lock_guard lock(setMutex); + if (validSets.find(lookupId) == validSets.end()) { + return Fail(GuestEsrch); + } + if (level == LevelRoot) { + resultId = RootSetId; + } else { + resultId = lookupId; + } + } } if (!GuestCopyOut_nid_no_patch(setid, &resultId, sizeof(resultId))) { return Fail(GuestEfault); diff --git a/core/libs/prx/libkernel_sys/Export.Discovery.cpp b/core/libs/prx/libkernel_sys/Export.Discovery.cpp index 75ddb1a22a..d3f44c6f4c 100644 --- a/core/libs/prx/libkernel_sys/Export.Discovery.cpp +++ b/core/libs/prx/libkernel_sys/Export.Discovery.cpp @@ -139,16 +139,6 @@ int APS5_VABI ipmimgr_call_nid_postfix(void) { return 0; } -int APS5_VABI is_in_sandbox_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI kldload_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI kmq_notify_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -422,11 +412,6 @@ int APS5_VABI sceKernelAllocateDirectMemory2(void) { return 0; } -int APS5_VABI sceKernelAllocateDirectMemoryForApp(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelAllocateDirectMemoryForMiniApp(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1343,11 +1328,6 @@ int APS5_VABI sceKernelIsGenuineN(void) { return 1; } -int APS5_VABI sceKernelIsInSandbox(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelIsM2DeviceAttached(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1507,11 +1487,6 @@ int APS5_VABI sceKernelProtectDirectMemory(void) { return 0; } -int APS5_VABI sceKernelProtectDirectMemoryForPID(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceKernelQueryToolMemory(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -2043,11 +2018,6 @@ int APS5_VABI scePthreadMutexInitForInternalLibc(PthreadMutex* mutex, const Pthr return SceResult(pthread_mutex_init_nid_postfix(mutex, attr)); } -int APS5_VABI scePthreadMutexIsowned(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI scePthreadMutexSetprioceiling(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -2103,18 +2073,16 @@ int APS5_VABI scePthreadTimedjoin(void) { return 0; } -int APS5_VABI sched_setscheduler_nid_postfix(int pid, int policy, const KernelSchedParam* param) { +int APS5_VABI sched_setscheduler_nid_postfix(int pid, int, const KernelSchedParam* param) { + constexpr int GuestEperm = 1, GuestEsrch = 3, GuestEfault = 14; const auto fail = [](int error) { *__error_nid_postfix() = error; return -1; }; - if (param == nullptr) return fail(14); - if (pid != 0 && pid == getpid_nid_postfix()) NotImplemented_nid_no_patch("sched_setscheduler: the first thread of the process, which its own pid selects, is not tracked"); - if (pid != 0) return fail(3); - if (policy != 1 && policy != 2 && policy != 3) return fail(22); - if (scePthreadSetschedparam(scePthreadSelf(), policy, param) != 0) - NotImplemented_nid_no_patch("sched_setscheduler: a policy and priority scePthreadSetschedparam does not accept, where FreeBSD answers EPERM or EINVAL and the console's answer is unverified"); - return 0; + KernelSchedParam requested{}; + if (!GuestCopyIn_nid_no_patch(&requested, param, sizeof(requested))) return fail(GuestEfault); + if (pid != 0 && pid != getpid_nid_postfix()) return fail(GuestEsrch); + return fail(GuestEperm); } int APS5_VABI set_phys_fmem_limit_nid_postfix(void) { @@ -2161,26 +2129,11 @@ int APS5_VABI sysc_s00_nid_postfix(void) { return 0; } -int APS5_VABI tcgetsid_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI tcsetsid_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI unmount_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI utrace_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI vfork_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 cf8e68ddaa..2ed2882ec6 100644 --- a/core/libs/prx/libkernel_sys/Export.cpp +++ b/core/libs/prx/libkernel_sys/Export.cpp @@ -10,12 +10,10 @@ #include #include #include -#include #include #include #include #include -#include #include #include #include @@ -36,6 +34,7 @@ #include "prx/libkernel/File/include/DescriptorControl.hpp" #include "prx/libc/include/CreationMask.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" +#include "prx/libkernel/Socket/include/SocketPoll.hpp" #include "prx/libkernel/File/include/SharedMemory.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" @@ -85,6 +84,7 @@ constexpr int GuestEloop = 62; constexpr int GuestEnametoolong = 63; constexpr int GuestEnotempty = 66; constexpr int GuestEoverflow = 84; +constexpr std::uint64_t TerminalSetControlling = 0x20007461; constexpr int GuestAtFdcwd = -100; constexpr int GuestAtRemovedir = 0x800; constexpr std::size_t GuestPathMax = 1024; @@ -142,24 +142,6 @@ constexpr int KenvSet = 1; constexpr int KenvUnset = 2; constexpr int KenvDump = 3; constexpr std::size_t KenvNameLength = 128; -constexpr std::size_t KenvValueLength = 128; -constexpr std::size_t KenvEntries = 512; - -struct KernelEnvironment { - std::mutex mutex; - std::vector entries; -}; - -KernelEnvironment& KernelEnv() { - static auto* environment = new KernelEnvironment; - return *environment; -} - -std::vector::iterator FindKernelEnv(std::vector& entries, const std::string& name) { - return std::find_if(entries.begin(), entries.end(), [&](const std::string& entry) { - return entry.size() > name.size() && entry.compare(0, name.size(), name) == 0 && entry[name.size()] == '='; - }); -} int CopyKernelEnvString(const char* source, std::size_t limit, std::string& destination) { if (const int error = CopyGuestPath_nid_no_patch(source, destination)) return error; @@ -220,6 +202,8 @@ std::int64_t CopyLinkTarget(const std::string& target, char* buffer, std::size_t bool HasNonDirectoryPrefix(const std::filesystem::path& native) { std::error_code error; for (auto prefix = native.parent_path(); prefix.has_relative_path(); prefix = prefix.parent_path()) { + const auto link = LinkPrefixState_nid_no_patch(prefix); + if (link != LinkPrefix_nid_no_patch::Resolves) return link == LinkPrefix_nid_no_patch::BlockedByFile; const auto status = std::filesystem::status(prefix, error); if (std::filesystem::exists(status)) return !std::filesystem::is_directory(status); } @@ -242,6 +226,56 @@ bool IsSymbolicLink(const std::filesystem::path& native) { #endif } +bool ReadSymbolicLinkTarget(const std::filesystem::path& native, std::filesystem::path& target) { +#ifdef _WIN32 + const DWORD attributes = GetFileAttributesW(native.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) return false; + std::string text; + if (!File::WindowsSymbolicLink::Read(native, text, "link")) return false; + target = std::filesystem::path(reinterpret_cast(text.c_str())); + return true; +#else + std::error_code error; + if (!std::filesystem::is_symlink(std::filesystem::symlink_status(native, error))) return false; + target = std::filesystem::read_symlink(native, error); + if (error) throw std::system_error(error, "link: read_symlink"); + return true; +#endif +} + +int SymbolicLinkLimitError(const std::filesystem::path& native, bool followLast) { + constexpr int MaxSymbolicLinks = 32; + std::vector pending; + const auto push = [&pending](const std::filesystem::path& path) { + const std::vector parts(path.begin(), path.end()); + pending.insert(pending.end(), parts.rbegin(), parts.rend()); + }; + push(native.relative_path()); + std::filesystem::path current = native.root_path(); + int links = 0; + while (!pending.empty()) { + const auto part = pending.back(); + pending.pop_back(); + if (part.empty() || part == ".") continue; + if (part == "..") { + std::error_code error; + if (!std::filesystem::is_directory(current, error)) return 0; + current = current.parent_path(); + continue; + } + const auto next = current / part; + std::filesystem::path target; + if ((followLast || !pending.empty()) && ReadSymbolicLinkTarget(next, target)) { + if (++links > MaxSymbolicLinks) return GuestEloop; + if (target.has_root_path()) current = target.root_path(); + push(target.relative_path()); + continue; + } + current = next; + } + return 0; +} + 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()); @@ -279,6 +313,33 @@ bool HasLongComponent(std::string_view path) { return false; } +bool HostFollowsGuestTarget(const char* created, const char* target, const std::filesystem::path& native) { + auto directory = native.parent_path(); + for (const auto& part : std::filesystem::path(target)) { + if (part.empty() || part == ".") continue; + if (part != "..") { + directory /= part; + continue; + } + std::error_code error; + if (!std::filesystem::is_directory(directory, error) || IsSymbolicLink(directory)) return false; + directory = directory.parent_path(); + } + const std::string_view link(created); + const auto separator = link.find_last_of("/\\"); + std::string guest = separator == std::string_view::npos ? std::string(".") : std::string(link.substr(0, separator == 0 ? 1 : separator)); + guest += '/'; + guest += target; + std::filesystem::path resolved; + if (ResolvePathError_nid_no_patch(guest.c_str(), resolved)) return false; + const auto trimmed = [](std::filesystem::path path) { + path = path.lexically_normal(); + if (!path.has_filename() && path.has_relative_path()) path = path.parent_path(); + return path; + }; + return trimmed(resolved) == trimmed(directory); +} + bool IsDotOrDotDot(std::string_view path) { while (!path.empty() && (path.back() == '/' || path.back() == '\\')) path.remove_suffix(1); const auto separator = path.find_last_of("/\\"); @@ -354,6 +415,14 @@ int APS5_VABI sceKernelGetCompiledSdkVersion(std::int32_t* version) { return 0; } +int APS5_VABI is_in_sandbox_nid_postfix(void) { + return 1; +} + +int APS5_VABI sceKernelIsInSandbox(void) { + return is_in_sandbox_nid_postfix(); +} + int APS5_VABI fork_nid_postfix(void) { NotImplemented_nid_no_patch("fork: child processes"); return -1; @@ -467,58 +536,14 @@ int APS5_VABI fstatfs_nid_postfix(int d, KernelStatfs* buf) { } int APS5_VABI kenv_nid_postfix(int what, const char* name, char* value, int len) { - auto& environment = KernelEnv(); - if (what == KenvDump) { - std::size_t capacity = len > 0 ? static_cast(len) : 0; - if (capacity > KenvEntries * (KenvNameLength + KenvValueLength + 2)) capacity = KenvEntries * (KenvNameLength + KenvValueLength + 2); - const bool copy = len > 0 && value != nullptr; - std::string buffer; - std::size_t needed = 0; - { - const std::lock_guard lock(environment.mutex); - for (const auto& entry : environment.entries) { - const std::size_t size = entry.size() + 1; - needed += size; - if (copy) { - const std::size_t taken = std::min(size, capacity - buffer.size()); - buffer.append(entry.c_str(), taken); - } - } - } - if (copy && !GuestCopyOut_nid_no_patch(value, buffer.data(), buffer.size())) return static_cast(Fail(GuestEfault)); - return buffer.size() == needed ? 0 : static_cast(needed); - } + static_cast(value); + static_cast(len); + if (what == KenvDump) return 0; + if (what == KenvSet || what == KenvUnset) return static_cast(Fail(GuestEperm)); std::string key; if (const int error = CopyKernelEnvString(name, KenvNameLength + 1, key)) return static_cast(Fail(error)); - const std::lock_guard lock(environment.mutex); - auto& entries = environment.entries; - switch (what) { - case KenvGet: { - const auto found = FindKernelEnv(entries, key); - if (found == entries.end()) return static_cast(Fail(GuestEnoent)); - const std::size_t size = found->size() - key.size(); - const std::size_t limit = static_cast(static_cast(len)); - const std::size_t taken = std::min(size, limit); - if (!GuestCopyOut_nid_no_patch(value, found->c_str() + key.size() + 1, taken)) return static_cast(Fail(GuestEfault)); - return static_cast(taken); - } - case KenvSet: { - if (len == 0) return static_cast(Fail(GuestEinval)); - std::string text; - if (const int error = CopyKernelEnvString(value, std::min(len, KenvValueLength + 1), text)) return static_cast(Fail(error)); - const auto found = FindKernelEnv(entries, key); - if (found != entries.end()) *found = key + "=" + text; - else if (entries.size() < KenvEntries) entries.push_back(key + "=" + text); - return 0; - } - case KenvUnset: { - const auto found = FindKernelEnv(entries, key); - if (found == entries.end()) return static_cast(Fail(GuestEnoent)); - entries.erase(found); - return 0; - } - default: return static_cast(Fail(GuestEinval)); - } + if (what == KenvGet) return static_cast(Fail(GuestEnoent)); + return static_cast(Fail(GuestEinval)); } int GetModuleInfoExByHandle_nid_no_patch(KernelModule handle, ModuleInfoEx* info); @@ -591,6 +616,9 @@ std::int64_t APS5_VABI _fpathconf_nid_postfix(int fd, int name) { bool withoutVnode = false; #ifdef _WIN32 if (GuestFstat(fd, &withoutVnode) != 0) return Fail(GuestEbadf); + std::shared_ptr owner; + std::uintptr_t native = 0; + withoutVnode = withoutVnode || KernelLookupSocketNative_nid_no_patch(fd, &owner, &native); #else struct stat status{}; if (::fstat(fd, &status) != 0) return Fail(GuestEbadf); @@ -697,9 +725,11 @@ int APS5_VABI link_nid_postfix(const char* existing, const char* created) { 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 = SymbolicLinkLimitError(source, true)) 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)); + if (const int error = SymbolicLinkLimitError(destination, false)) return static_cast(Fail(error)); return LinkNative(source, destination); } @@ -719,11 +749,13 @@ int APS5_VABI linkat_nid_postfix(int fd1, const char* path1, int fd2, const char if (const int error = CopyPath(path1, copy1)) return static_cast(Fail(error)); std::filesystem::path source, destination; if (const int error = ResolveAtPath(fd1, path1, source)) return static_cast(Fail(error)); + if (const int error = SymbolicLinkLimitError(source, flag != 0)) return static_cast(Fail(error)); const bool linkItself = flag == 0 && IsSymbolicLink(source); if (!linkItself) if (const int error = LinkSourceError(source)) return static_cast(Fail(error)); if (const int error = CopyPath(path2, copy2)) return static_cast(Fail(error)); if (const int error = ResolveAtPath(fd2, path2, destination)) return static_cast(Fail(error)); + if (const int error = SymbolicLinkLimitError(destination, false)) return static_cast(Fail(error)); if (linkItself) return LinkSymbolicLinkItself(source, destination); return LinkNative(source, destination); } @@ -770,6 +802,12 @@ int APS5_VABI kldfind_nid_postfix(const char* file) { return static_cast(Fail(GuestEnoent)); } +int APS5_VABI kldload_nid_postfix(const char* file) { + std::string copy; + if (const int error = CopyGuestPath_nid_no_patch(file, copy)) return static_cast(Fail(error)); + return static_cast(Fail(GuestEperm)); +} + int APS5_VABI kldfirstmod_nid_postfix(int fileid) { return static_cast(Fail(GuestEnoent)); } @@ -832,12 +870,6 @@ int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { NotImplemented_nid_no_patch("symlink: absolute targets"); return -1; } - for (const auto& part : contents) { - if (part == "..") { - NotImplemented_nid_no_patch("symlink: parent-directory targets"); - return -1; - } - } std::filesystem::path native; if (const int error = ResolvePathError_nid_no_patch(created, native)) return static_cast(Fail(error)); std::error_code error; @@ -847,6 +879,11 @@ int APS5_VABI symlink_nid_postfix(const char* target, const char* created) { if (std::filesystem::exists(std::filesystem::symlink_status(native, error))) return static_cast(Fail(GuestEexist)); #endif if (!std::filesystem::is_directory(native.parent_path(), error)) return static_cast(Fail(HasNonDirectoryPrefix(native) ? GuestEnotdir : GuestEnoent)); + if (std::any_of(contents.begin(), contents.end(), [](const std::filesystem::path& part) { return part == ".."; }) && + !HostFollowsGuestTarget(created, target, native)) { + NotImplemented_nid_no_patch("symlink: a parent-directory target the host would not follow to the guest path it names"); + return -1; + } const bool directory = std::filesystem::is_directory(native.parent_path() / contents, error); #ifdef _WIN32 DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; @@ -1154,7 +1191,7 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) { 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"); + if (!clockId) throw std::invalid_argument("scePthreadCondattrGetclock: null clock id"); *clockId = (*attr)->_clockid; return 0; } @@ -1260,4 +1297,20 @@ int APS5_VABI sceKernelGetProcessType(int pid) { return 0; } +int APS5_VABI scePthreadMutexIsowned(PthreadMutex* mutex) { + const int owned = pthread_mutex_isowned_np_nid_postfix(mutex); + return owned == 0 ? 0 : SceKernelError(owned); +} + +int APS5_VABI utrace_nid_postfix(const void* address, std::size_t length) { + (void)address; + (void)length; + return 0; +} + +int APS5_VABI tcsetsid_nid_postfix(int fd, int pid) { + if (pid != getsid_nid_postfix(0)) return static_cast(Fail(GuestEinval)); + return ioctl_nid_postfix(fd, TerminalSetControlling, nullptr); +} + } diff --git a/core/libs/prx/libkernel_sys/Uuid.cpp b/core/libs/prx/libkernel_sys/Uuid.cpp index c3c0a0da72..929f77c1ca 100644 --- a/core/libs/prx/libkernel_sys/Uuid.cpp +++ b/core/libs/prx/libkernel_sys/Uuid.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/HostEntropy.hpp" @@ -68,15 +69,18 @@ int APS5_VABI uuidgen_nid_postfix(GuestUuid* store, int count) { uuidLastSequence = sequence; uuidLastTime = (time + static_cast(count) - 1) & UuidTimeMask; } - if (store == nullptr) return Fail(GuestEfault); + std::vector generated(static_cast(count)); const auto variantSequence = static_cast(sequence | 0x8000); for (int index = 0; index < count; ++index, ++time) { - store[index].timeLow = static_cast(time); - store[index].timeMid = static_cast(time >> 32); - store[index].timeHiAndVersion = static_cast(((time >> 48) & 0x0fff) | 0x1000); - store[index].clockSeqHiAndReserved = static_cast(variantSequence >> 8); - store[index].clockSeqLow = static_cast(variantSequence); - std::memcpy(store[index].node, node.data(), node.size()); + generated[index].timeLow = static_cast(time); + generated[index].timeMid = static_cast(time >> 32); + generated[index].timeHiAndVersion = static_cast(((time >> 48) & 0x0fff) | 0x1000); + generated[index].clockSeqHiAndReserved = static_cast(variantSequence >> 8); + generated[index].clockSeqLow = static_cast(variantSequence); + std::memcpy(generated[index].node, node.data(), node.size()); + } + if (!GuestCopyOut_nid_no_patch(store, generated.data(), sizeof(GuestUuid) * static_cast(count))) { + return Fail(GuestEfault); } return 0; } diff --git a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp index 75c2c5e523..5cee705589 100644 --- a/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp +++ b/core/libs/prx/libkernel_sys/include/AdditionalExports.hpp @@ -2,6 +2,8 @@ #include "SceTypes.hpp" +#include + extern "C" { int APS5_VABI __cfi_slowpath_diag_nid_postfix(); @@ -56,7 +58,7 @@ int APS5_VABI _execvpe_nid_postfix(); int APS5_VABI _wait4_nid_postfix(); int APS5_VABI adjtime_nid_postfix(); int APS5_VABI libkernel_sysUnknown_0CV40L3tznI_nid_no_patch(); -int APS5_VABI libkernel_sysUnknown_1WXQlxMpnNA_nid_no_patch(); +std::uint64_t APS5_VABI libkernel_sysUnknown_1WXQlxMpnNA_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_1v23w7Rnkic_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_1v6DjTPunjs_nid_no_patch(); int APS5_VABI sceKernelInternalCreateMapperSharedMemory(); @@ -93,7 +95,7 @@ int APS5_VABI libkernel_sysUnknown_BJvWKKfmj4w_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_BjmZn9pYSuk_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_C3ToW2CA_plus_hM_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_C3xgqN95IoE_nid_no_patch(); -int APS5_VABI libkernel_sysUnknown_C49jelxiaVE_nid_no_patch(); +std::uint64_t APS5_VABI libkernel_sysUnknown_C49jelxiaVE_nid_no_patch(); int APS5_VABI sceKernelSwitchToTrinityMode(); int APS5_VABI sceKernelIsGenuineCronosCex(); int APS5_VABI libkernel_sysUnknown_DWWduhQYA3I_nid_no_patch(); @@ -103,7 +105,7 @@ int APS5_VABI libkernel_sysUnknown_EIz_plus_fSlYCn8_nid_no_patch(); int APS5_VABI sceKernelTraceMarkerBookmark(); int APS5_VABI libkernel_sysUnknown_EaepepH_minus_LmI_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_FIUPjhnoI_plus_o_nid_no_patch(); -int APS5_VABI libkernel_sysUnknown_FR0odl35yQY_nid_no_patch(); +std::uint64_t APS5_VABI libkernel_sysUnknown_FR0odl35yQY_nid_no_patch(); int APS5_VABI sceKernelIccGetExtBDDriveStatus(); int APS5_VABI libkernel_sysUnknown_GTyjVeZcdRM_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_Gtncco_plus_mxio_nid_no_patch(); @@ -126,7 +128,7 @@ int APS5_VABI libkernel_sysUnknown_LVRydXoXt7U_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_LwGBaXgvFaY_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_MJ_minus_err8l_minus_XY_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_MKlp_minus_bW1bz8_nid_no_patch(); -int APS5_VABI libkernel_sysUnknown_N94a0T4F4_plus_U_nid_no_patch(); +std::uint64_t APS5_VABI libkernel_sysUnknown_N94a0T4F4_plus_U_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_NhFlyw_minus_77cY_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_OZBJnWxt7d4_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_PBq_minus_OUg5A5s_nid_no_patch(); @@ -189,7 +191,7 @@ int APS5_VABI sceKernelTraceMarkerPlot(); int APS5_VABI libkernel_sysUnknown_ftHBlCfIEQo_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_g7MGwZ5UxBE_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_gBiaAGKEvHM_nid_no_patch(); -int APS5_VABI libkernel_sysUnknown_gJ5gpW45mU0_nid_no_patch(); +std::uint64_t APS5_VABI libkernel_sysUnknown_gJ5gpW45mU0_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_gSIiye3ERqo_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_gpgi_minus_GwE2Is_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_guq_plus_otVpgj0_nid_no_patch(); @@ -216,7 +218,7 @@ int APS5_VABI libkernel_sysUnknown_nsq826qQ2EI_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_o141x2VDeVc_nid_no_patch(); int APS5_VABI sceKernelGetMainSocSubId(); int APS5_VABI sceKernelInternalUnmapMapperSharedMemory(); -int APS5_VABI libkernel_sysUnknown_pCbBddNl66Y_nid_no_patch(); +std::uint64_t APS5_VABI libkernel_sysUnknown_pCbBddNl66Y_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_pOClGbxrx94_nid_no_patch(); int APS5_VABI sceKernelSetAppLibcData(); int APS5_VABI libkernel_sysUnknown_pp_plus_G_plus_UZhiqY_nid_no_patch(); @@ -238,7 +240,7 @@ int APS5_VABI libkernel_sysUnknown_uV_plus_YilcHqAs_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_ulVjluL9TzM_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_v_plus_qTlNjF6kE_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_vfYaxkxnJXQ_nid_no_patch(); -int APS5_VABI libkernel_sysUnknown_vq2_plus_HyP5U5I_nid_no_patch(); +std::uint64_t APS5_VABI libkernel_sysUnknown_vq2_plus_HyP5U5I_nid_no_patch(); int APS5_VABI sceKernelSetCanvasHandle(); int APS5_VABI libkernel_sysUnknown_wn0XzOue4EM_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_wwWv_plus_0MzhGM_nid_no_patch(); @@ -248,8 +250,8 @@ int APS5_VABI libkernel_sysUnknown_yWOJkfd_minus_DhA_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_yn0b42DApAI_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_yzvrk3YrPlE_nid_no_patch(); int APS5_VABI libkernel_sysUnknown_z0qKN5kkVj0_nid_no_patch(); -int APS5_VABI libkernel_sysUnknown_zAloppPJxy0_nid_no_patch(); -int APS5_VABI libkernel_sysUnknown_z_plus_e0vV_minus_tnH4_nid_no_patch(); +std::uint64_t APS5_VABI libkernel_sysUnknown_zAloppPJxy0_nid_no_patch(); +std::uint64_t APS5_VABI libkernel_sysUnknown_z_plus_e0vV_minus_tnH4_nid_no_patch(); int APS5_VABI mmap2_nid_postfix(); int APS5_VABI pthread_setschedother_np_nid_postfix(); int APS5_VABI sceKernelAddDynlibEvent(); diff --git a/core/libs/prx/libkernel_sys/include/Api.hpp b/core/libs/prx/libkernel_sys/include/Api.hpp index c1fa1576d4..0d1da8b729 100644 --- a/core/libs/prx/libkernel_sys/include/Api.hpp +++ b/core/libs/prx/libkernel_sys/include/Api.hpp @@ -117,7 +117,7 @@ int APS5_VABI is_in_sandbox_nid_postfix(void); int APS5_VABI kenv_nid_postfix(int what, const char* name, char* value, int len); int APS5_VABI kldfind_nid_postfix(const char* file); int APS5_VABI kldfirstmod_nid_postfix(int fileid); -int APS5_VABI kldload_nid_postfix(void); +int APS5_VABI kldload_nid_postfix(const char* file); int APS5_VABI kldnext_nid_postfix(int fileid); int APS5_VABI kldstat_nid_postfix(int fileid, void* stat); int APS5_VABI kldsym_nid_postfix(int fileid, int cmd, void* data); @@ -178,7 +178,6 @@ 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); @@ -408,7 +407,6 @@ 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); @@ -522,7 +520,7 @@ int APS5_VABI scePthreadMain(void); int APS5_VABI scePthreadMulti(void); int APS5_VABI scePthreadMutexGetprioceiling(void); int APS5_VABI scePthreadMutexInitForInternalLibc(PthreadMutex* mutex, const PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexIsowned(void); +int APS5_VABI scePthreadMutexIsowned(PthreadMutex* mutex); int APS5_VABI scePthreadMutexSetprioceiling(void); int APS5_VABI scePthreadMutexattrGetgen(const PthreadMutexattr* attr, int* generation); int APS5_VABI scePthreadMutexattrInitForInternalLibc(PthreadMutexattr* attr); @@ -546,10 +544,9 @@ int APS5_VABI symlinkat_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 tcsetsid_nid_postfix(int fd, int pid); int APS5_VABI unmount_nid_postfix(void); -int APS5_VABI utrace_nid_postfix(void); +int APS5_VABI utrace_nid_postfix(const void* address, std::size_t length); int APS5_VABI uuidgen_nid_postfix(GuestUuid* store, int count); int APS5_VABI vfork_nid_postfix(void); int APS5_VABI wait3_nid_postfix(void); diff --git a/core/libs/prx/libwebrtc/CMakeLists.txt b/core/libs/prx/libwebrtc/CMakeLists.txt index 6f22151507..a0809c2959 100644 --- a/core/libs/prx/libwebrtc/CMakeLists.txt +++ b/core/libs/prx/libwebrtc/CMakeLists.txt @@ -2,11 +2,19 @@ add_library(libwebrtc SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export target_include_directories(libwebrtc PRIVATE ${LIBS_INCLUDE_DIR}) configure_windows_unwind(libwebrtc) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioDecoder.cpp) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioDeviceGeneric.cpp) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioEncoder.cpp) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioFrame.cpp) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/AudioFrameOperations.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/ExpFilter.cpp) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/IpAddress.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RtcpCommonHeader.cpp) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/CpuFeatures.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RmsLevel.cpp) target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/Opus.cpp) target_link_libraries(libwebrtc PRIVATE opus) +target_sources(libwebrtc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/TaskQueueBase.cpp) if(BUILD_TESTING) add_test_executable(guest_webrtc_fast_int_to_buffer_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/FastIntToBuffer.cpp) @@ -49,6 +57,16 @@ if(BUILD_TESTING) set_tests_properties(guest_webrtc_rtcp_common_header PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_webrtc_cpu_features_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/CpuFeatures.cpp) + target_include_directories(guest_webrtc_cpu_features_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_webrtc_cpu_features_tests PRIVATE libwebrtc libc) + configure_windows_unwind(guest_webrtc_cpu_features_tests) + add_test(NAME guest_webrtc_cpu_features COMMAND guest_webrtc_cpu_features_tests) + set_tests_properties(guest_webrtc_cpu_features PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_webrtc_cpu_features PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_webrtc_opus_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/Opus.cpp) target_include_directories(guest_webrtc_opus_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_webrtc_opus_tests PRIVATE libwebrtc libc opus) @@ -69,4 +87,64 @@ if(BUILD_TESTING) set_tests_properties(guest_webrtc_rms_level PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_webrtc_task_queue_base_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/TaskQueueBase.cpp) + target_include_directories(guest_webrtc_task_queue_base_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_webrtc_task_queue_base_tests PRIVATE libwebrtc libc) + configure_windows_unwind(guest_webrtc_task_queue_base_tests) + add_test(NAME guest_webrtc_task_queue_base COMMAND guest_webrtc_task_queue_base_tests) + set_tests_properties(guest_webrtc_task_queue_base PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_webrtc_task_queue_base PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_webrtc_ip_address_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/IpAddress.cpp) + target_include_directories(guest_webrtc_ip_address_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_webrtc_ip_address_tests PRIVATE libwebrtc libc) + configure_windows_unwind(guest_webrtc_ip_address_tests) + add_test(NAME guest_webrtc_ip_address COMMAND guest_webrtc_ip_address_tests) + set_tests_properties(guest_webrtc_ip_address PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_webrtc_ip_address PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_webrtc_audio_frame_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/AudioFrame.cpp) + target_include_directories(guest_webrtc_audio_frame_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_webrtc_audio_frame_tests PRIVATE libwebrtc libc) + configure_windows_unwind(guest_webrtc_audio_frame_tests) + add_test(NAME guest_webrtc_audio_frame COMMAND guest_webrtc_audio_frame_tests) + set_tests_properties(guest_webrtc_audio_frame PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_webrtc_audio_frame PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_webrtc_audio_encoder_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/AudioEncoder.cpp) + target_include_directories(guest_webrtc_audio_encoder_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_webrtc_audio_encoder_tests PRIVATE libwebrtc libc) + configure_windows_unwind(guest_webrtc_audio_encoder_tests) + add_test(NAME guest_webrtc_audio_encoder COMMAND guest_webrtc_audio_encoder_tests) + set_tests_properties(guest_webrtc_audio_encoder PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_webrtc_audio_encoder PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_webrtc_audio_device_generic_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/AudioDeviceGeneric.cpp) + target_include_directories(guest_webrtc_audio_device_generic_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_webrtc_audio_device_generic_tests PRIVATE libwebrtc libc) + configure_windows_unwind(guest_webrtc_audio_device_generic_tests) + add_test(NAME guest_webrtc_audio_device_generic COMMAND guest_webrtc_audio_device_generic_tests) + set_tests_properties(guest_webrtc_audio_device_generic PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_webrtc_audio_device_generic PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_test_executable(guest_webrtc_audio_decoder_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/AudioDecoder.cpp) + target_include_directories(guest_webrtc_audio_decoder_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_webrtc_audio_decoder_tests PRIVATE libwebrtc libc) + configure_windows_unwind(guest_webrtc_audio_decoder_tests) + add_test(NAME guest_webrtc_audio_decoder COMMAND guest_webrtc_audio_decoder_tests) + set_tests_properties(guest_webrtc_audio_decoder PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_webrtc_audio_decoder PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libwebrtc/Export.cpp b/core/libs/prx/libwebrtc/Export.cpp index bf714a5404..6eb865f27a 100644 --- a/core/libs/prx/libwebrtc/Export.cpp +++ b/core/libs/prx/libwebrtc/Export.cpp @@ -117,16 +117,6 @@ int APS5_VABI _ZNK3rtc13SocketAddress4portEv_nid_postfix(void) { return 0; } -int APS5_VABI _ZNK3rtc9IPAddress12ipv4_addressEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZNK3rtc9IPAddress12ipv6_addressEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc34CreateModularPeerConnectionFactoryENS_33PeerConnectionFactoryDependenciesE_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -212,11 +202,6 @@ int APS5_VABI _ZNK6webrtc20AudioSourceInterface7optionsEv_nid_postfix(void) { return 0; } -int APS5_VABI _ZNK3rtc9IPAddresseqERKS0__nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZNK6webrtc8RTCError7messageEv_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -294,11 +279,6 @@ int APS5_VABI _ZN3rtc17CopyOnWriteBufferC1Emm_nid_postfix(void) { return 0; } -int APS5_VABI _ZN6webrtc13TaskQueueBase7CurrentEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3rtc10PacketInfoD1Ev_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -445,16 +425,6 @@ int APS5_VABI _ZN3rtc15CurrentThreadIdEv_nid_postfix(void) { return 0; } -int APS5_VABI _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterC1EPS0__nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterD1Ev_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc18EncodedImageBuffer6CreateEPKhm_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -715,11 +685,6 @@ int APS5_VABI _ZNK3rtc13SocketAddress10IsCompleteEv_nid_postfix(void) { return 0; } -int APS5_VABI _ZNK3rtc9IPAddress27v4AddressAsHostOrderIntegerEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - APS5_EXPORT("z-b1v5cik7s", libwebrtcUnknown19); int APS5_VABI libwebrtcUnknown19(void) { NotImplemented_nid_no_patch("z-b1v5cik7s"); @@ -736,11 +701,6 @@ int APS5_VABI _ZN3rtc10TruncateIPERKNS_9IPAddressEi_nid_postfix(void) { return 0; } -int APS5_VABI _ZN3rtc15CountIPMaskBitsERKNS_9IPAddressE_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN3rtc17AsyncPacketSocketD2Ev_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1221,26 +1181,6 @@ int APS5_VABI _ZN6webrtc12AudioDecoder15DecodeRedundantEPKhmimPsPNS0_10SpeechTyp return 0; } -int APS5_VABI _ZNK6webrtc12AudioDecoder12HasDecodePlcEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc12AudioDecoder9DecodePlcEmPs_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc12AudioDecoder11GeneratePlcEmPN3rtc7BufferTIsLb0EEE_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc12AudioDecoder9ErrorCodeEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc12AudioDecoder11ParseResultC1EOS1__nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1267,16 +1207,6 @@ int APS5_VABI _ZN6webrtc12AudioEncoder6EncodeEjN3rtc9ArrayViewIKsLln4711EEEPNS1_ return 0; } -int APS5_VABI _ZNK6webrtc12AudioEncoder6GetDtxEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc12AudioEncoder16SetTargetBitrateEi_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - APS5_EXPORT("qli1Cjz9UIo", libwebrtcUnknown36); int APS5_VABI libwebrtcUnknown36(void) { NotImplemented_nid_no_patch("qli1Cjz9UIo"); @@ -1289,11 +1219,6 @@ int APS5_VABI libwebrtcUnknown37(void) { return 0; } -int APS5_VABI _ZN6webrtc12AudioEncoder26DisableAudioNetworkAdaptorEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc12AudioEncoder45OnReceivedUplinkRecoverablePacketLossFractionEf_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1309,31 +1234,6 @@ int APS5_VABI _ZN6webrtc12AudioEncoder26OnReceivedUplinkAllocationENS_23BitrateA return 0; } -int APS5_VABI _ZN6webrtc12AudioEncoder13OnReceivedRttEi_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc12AudioEncoder18OnReceivedOverheadEm_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc12AudioEncoder27SetReceiverFrameLengthRangeEii_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZNK6webrtc12AudioEncoder11GetANAStatsEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc8ANAStatsC1Ev_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc12AudioEncoder11EncodedInfoC1Ev_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1472,36 +1372,11 @@ int APS5_VABI _ZN6webrtc10AudioFrame8CopyFromERKS0__nid_postfix(void) { return 0; } -int APS5_VABI _ZNK6webrtc10AudioFrame5mutedEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZNK6webrtc10AudioFrame9data_viewEv_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; } -int APS5_VABI _ZNK6webrtc10AudioFrame4dataEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc10AudioFrame4MuteEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc10AudioFrameC1Ev_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc20EchoCanceller3ConfigaSERKS0__nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1593,41 +1468,6 @@ int APS5_VABI _ZN6webrtc17AudioDeviceBuffer22SetRecordingSampleRateEj_nid_postfi return 0; } -int APS5_VABI _ZNK6webrtc18AudioDeviceGeneric21BuiltInAECIsAvailableEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZNK6webrtc18AudioDeviceGeneric21BuiltInAGCIsAvailableEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZNK6webrtc18AudioDeviceGeneric20BuiltInNSIsAvailableEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAECEb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAGCEb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZN6webrtc18AudioDeviceGeneric15EnableBuiltInNSEb_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI _ZNK6webrtc18AudioDeviceGeneric23GetPlayoutUnderrunCountEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc17PushSincResamplerC1Emm_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1714,11 +1554,6 @@ int APS5_VABI _ZN6webrtc11AudioBuffer6CopyToERKNS_12StreamConfigEPs_nid_postfix( return 0; } -int APS5_VABI _ZN6webrtc23GetAvailableCpuFeaturesEv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI _ZN6webrtc7rnn_vad17FeaturesExtractorC1ERKNS_20AvailableCpuFeaturesE_nid_postfix(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -1750,11 +1585,6 @@ int APS5_VABI _ZN6webrtc7rnn_vad6RnnVad21ComputeVadProbabilityEN3rtc9ArrayViewIK return 0; } -int APS5_VABI _ZN6webrtc20AudioFrameOperations4MuteEPNS_10AudioFrameE_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - APS5_EXPORT("GMnT0UEJDZY", libwebrtcUnknown44); int APS5_VABI libwebrtcUnknown44(void) { NotImplemented_nid_no_patch("GMnT0UEJDZY"); diff --git a/core/libs/prx/libwebrtc/include/Api.hpp b/core/libs/prx/libwebrtc/include/Api.hpp index 8dabe99405..c7f068170a 100644 --- a/core/libs/prx/libwebrtc/include/Api.hpp +++ b/core/libs/prx/libwebrtc/include/Api.hpp @@ -1,14 +1,21 @@ #pragma once #include "SceTypes.hpp" +#include "prx/libwebrtc/include/AudioDecoder.hpp" +#include "prx/libwebrtc/include/AudioDeviceGeneric.hpp" +#include "prx/libwebrtc/include/AudioEncoder.hpp" +#include "prx/libwebrtc/include/AudioFrame.hpp" #include "prx/libwebrtc/include/ExpFilter.hpp" +#include "prx/libwebrtc/include/IpAddress.hpp" #include "prx/libwebrtc/include/RmsLevel.hpp" #include "prx/libwebrtc/include/RtcpCommonHeader.hpp" #include "prx/libwebrtc/include/Opus.hpp" +#include "prx/libwebrtc/include/CpuFeatures.hpp" #include #include struct WebRtcVadInstance; +#include "prx/libwebrtc/include/TaskQueueBase.hpp" extern "C" { @@ -28,8 +35,8 @@ int APS5_VABI _ZN3rtc13SocketAddressaSERKS0__nid_postfix(void); int APS5_VABI _ZNK3rtc13SocketAddress6ipaddrEv_nid_postfix(void); int APS5_VABI _ZN3rtc13SocketAddressC1ERKNS_9IPAddressEi_nid_postfix(void); int APS5_VABI _ZNK3rtc13SocketAddress4portEv_nid_postfix(void); -int APS5_VABI _ZNK3rtc9IPAddress12ipv4_addressEv_nid_postfix(void); -int APS5_VABI _ZNK3rtc9IPAddress12ipv6_addressEv_nid_postfix(void); +WebRtcInAddr APS5_VABI _ZNK3rtc9IPAddress12ipv4_addressEv_nid_postfix(const WebRtcIpAddress* address); +WebRtcIn6Addr APS5_VABI _ZNK3rtc9IPAddress12ipv6_addressEv_nid_postfix(const WebRtcIpAddress* address); void APS5_VABI _ZN6webrtc8RmsLevel7AnalyzeEN3rtc9ArrayViewIKsLln4711EEE_nid_postfix(WebRtcRmsLevel* level, const std::int16_t* data, std::size_t size); int APS5_VABI _ZN6webrtc8RmsLevel7AverageEv_nid_postfix(WebRtcRmsLevel* level); void APS5_VABI _ZN6webrtc8RmsLevelC1Ev_nid_postfix(WebRtcRmsLevel* level); @@ -51,7 +58,7 @@ int APS5_VABI _ZN6webrtc23PeerConnectionInterface9IceServerC1ERKS1__nid_postfix( int APS5_VABI _ZN6webrtc23PeerConnectionInterface9IceServerD1Ev_nid_postfix(void); int APS5_VABI _ZN3rtc14RTCCertificateD1Ev_nid_postfix(void); int APS5_VABI _ZNK6webrtc20AudioSourceInterface7optionsEv_nid_postfix(void); -int APS5_VABI _ZNK3rtc9IPAddresseqERKS0__nid_postfix(void); +bool APS5_VABI _ZNK3rtc9IPAddresseqERKS0__nid_postfix(const WebRtcIpAddress* address, const WebRtcIpAddress* other); int APS5_VABI _ZNK6webrtc8RTCError7messageEv_nid_postfix(void); int APS5_VABI _ZN6webrtc21PendingTaskSafetyFlag6CreateEv_nid_postfix(void); int APS5_VABI _ZN7cricket5CodecD1Ev_nid_postfix(void); @@ -67,7 +74,7 @@ int APS5_VABI _ZN6webrtc17RtpPacketReceivedC1ERKS0__nid_postfix(void); int APS5_VABI _ZN6webrtc17RtpPacketReceivedD1Ev_nid_postfix(void); int APS5_VABI _ZN3rtc17CopyOnWriteBuffer24UnshareAndEnsureCapacityEm_nid_postfix(void); int APS5_VABI _ZN3rtc17CopyOnWriteBufferC1Emm_nid_postfix(void); -int APS5_VABI _ZN6webrtc13TaskQueueBase7CurrentEv_nid_postfix(void); +WebRtcTaskQueueBase* APS5_VABI _ZN6webrtc13TaskQueueBase7CurrentEv_nid_postfix(void); int APS5_VABI _ZN3rtc10PacketInfoD1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc21RtpHeaderExtensionMapC1EN3rtc9ArrayViewIKNS_12RtpExtensionELln4711EEE_nid_postfix(void); int APS5_VABI _ZN6webrtc17RtpPacketReceivedC1EPKNS_21RtpHeaderExtensionMapENS_9TimestampE_nid_postfix(void); @@ -96,8 +103,8 @@ int APS5_VABI libwebrtcUnknown12(void); int APS5_VABI libwebrtcUnknown13(void); int APS5_VABI _ZN6webrtc32webrtc_sequence_checker_internal19SequenceCheckerImplC2Eb_nid_postfix(void); int APS5_VABI _ZN3rtc15CurrentThreadIdEv_nid_postfix(void); -int APS5_VABI _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterC1EPS0__nid_postfix(void); -int APS5_VABI _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterD1Ev_nid_postfix(void); +void APS5_VABI _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterC1EPS0__nid_postfix(WebRtcCurrentTaskQueueSetter* setter, WebRtcTaskQueueBase* taskQueue); +void APS5_VABI _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterD1Ev_nid_postfix(WebRtcCurrentTaskQueueSetter* setter); int APS5_VABI _ZN6webrtc18EncodedImageBuffer6CreateEPKhm_nid_postfix(void); int APS5_VABI _ZN6webrtc10ColorSpaceC1ERKS0__nid_postfix(void); int APS5_VABI _ZN6webrtc10ColorSpaceaSERKS0__nid_postfix(void); @@ -149,11 +156,11 @@ int APS5_VABI libwebrtcUnknown18(void); int APS5_VABI _ZN3rtc10LogMessageC1EPKciNS_15LoggingSeverityE_nid_postfix(void); int APS5_VABI _ZN3rtc10LogMessageD1Ev_nid_postfix(void); int APS5_VABI _ZNK3rtc13SocketAddress10IsCompleteEv_nid_postfix(void); -int APS5_VABI _ZNK3rtc9IPAddress27v4AddressAsHostOrderIntegerEv_nid_postfix(void); +std::uint32_t APS5_VABI _ZNK3rtc9IPAddress27v4AddressAsHostOrderIntegerEv_nid_postfix(const WebRtcIpAddress* address); int APS5_VABI libwebrtcUnknown19(void); int APS5_VABI _ZNK3rtc13SocketAddress7IsAnyIPEv_nid_postfix(void); int APS5_VABI _ZN3rtc10TruncateIPERKNS_9IPAddressEi_nid_postfix(void); -int APS5_VABI _ZN3rtc15CountIPMaskBitsERKNS_9IPAddressE_nid_postfix(void); +int APS5_VABI _ZN3rtc15CountIPMaskBitsERKNS_9IPAddressE_nid_postfix(const WebRtcIpAddress* mask); int APS5_VABI _ZN3rtc17AsyncPacketSocketD2Ev_nid_postfix(void); int APS5_VABI _ZN3rtc17AsyncPacketSocket20NotifyPacketReceivedERKNS_14ReceivedPacketE_nid_postfix(void); int APS5_VABI libwebrtcUnknown20(void); @@ -250,28 +257,28 @@ int APS5_VABI _ZN6webrtc4rtcp17TransportFeedbackD1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc9RtpPacket5ParseEPKhm_nid_postfix(void); int APS5_VABI _ZN6webrtc12AudioDecoder6DecodeEPKhmimPsPNS0_10SpeechTypeE_nid_postfix(void); int APS5_VABI _ZN6webrtc12AudioDecoder15DecodeRedundantEPKhmimPsPNS0_10SpeechTypeE_nid_postfix(void); -int APS5_VABI _ZNK6webrtc12AudioDecoder12HasDecodePlcEv_nid_postfix(void); -int APS5_VABI _ZN6webrtc12AudioDecoder9DecodePlcEmPs_nid_postfix(void); -int APS5_VABI _ZN6webrtc12AudioDecoder11GeneratePlcEmPN3rtc7BufferTIsLb0EEE_nid_postfix(void); -int APS5_VABI _ZN6webrtc12AudioDecoder9ErrorCodeEv_nid_postfix(void); +bool APS5_VABI _ZNK6webrtc12AudioDecoder12HasDecodePlcEv_nid_postfix(const WebRtcAudioDecoder* decoder); +std::size_t APS5_VABI _ZN6webrtc12AudioDecoder9DecodePlcEmPs_nid_postfix(WebRtcAudioDecoder* decoder, std::size_t numFrames, std::int16_t* decoded); +void APS5_VABI _ZN6webrtc12AudioDecoder11GeneratePlcEmPN3rtc7BufferTIsLb0EEE_nid_postfix(WebRtcAudioDecoder* decoder, std::size_t requestedSamplesPerChannel, WebRtcInt16Buffer* concealmentAudio); +int APS5_VABI _ZN6webrtc12AudioDecoder9ErrorCodeEv_nid_postfix(WebRtcAudioDecoder* decoder); int APS5_VABI _ZN6webrtc12AudioDecoder11ParseResultC1EOS1__nid_postfix(void); int APS5_VABI libwebrtcUnknown35(void); int APS5_VABI _ZN6webrtc12AudioDecoder11ParseResultD1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc12AudioEncoder11EncodedInfoaSERKS1__nid_postfix(void); int APS5_VABI _ZN6webrtc12AudioEncoder6EncodeEjN3rtc9ArrayViewIKsLln4711EEEPNS1_7BufferTIhLb0EEE_nid_postfix(void); -int APS5_VABI _ZNK6webrtc12AudioEncoder6GetDtxEv_nid_postfix(void); -int APS5_VABI _ZN6webrtc12AudioEncoder16SetTargetBitrateEi_nid_postfix(void); +bool APS5_VABI _ZNK6webrtc12AudioEncoder6GetDtxEv_nid_postfix(const WebRtcAudioEncoder* encoder); +void APS5_VABI _ZN6webrtc12AudioEncoder16SetTargetBitrateEi_nid_postfix(WebRtcAudioEncoder* encoder, int targetBps); int APS5_VABI libwebrtcUnknown36(void); int APS5_VABI libwebrtcUnknown37(void); -int APS5_VABI _ZN6webrtc12AudioEncoder26DisableAudioNetworkAdaptorEv_nid_postfix(void); +void APS5_VABI _ZN6webrtc12AudioEncoder26DisableAudioNetworkAdaptorEv_nid_postfix(WebRtcAudioEncoder* encoder); int APS5_VABI _ZN6webrtc12AudioEncoder45OnReceivedUplinkRecoverablePacketLossFractionEf_nid_postfix(void); int APS5_VABI _ZN6webrtc12AudioEncoder28OnReceivedTargetAudioBitrateEi_nid_postfix(void); int APS5_VABI _ZN6webrtc12AudioEncoder26OnReceivedUplinkAllocationENS_23BitrateAllocationUpdateE_nid_postfix(void); -int APS5_VABI _ZN6webrtc12AudioEncoder13OnReceivedRttEi_nid_postfix(void); -int APS5_VABI _ZN6webrtc12AudioEncoder18OnReceivedOverheadEm_nid_postfix(void); -int APS5_VABI _ZN6webrtc12AudioEncoder27SetReceiverFrameLengthRangeEii_nid_postfix(void); -int APS5_VABI _ZNK6webrtc12AudioEncoder11GetANAStatsEv_nid_postfix(void); -int APS5_VABI _ZN6webrtc8ANAStatsC1Ev_nid_postfix(void); +void APS5_VABI _ZN6webrtc12AudioEncoder13OnReceivedRttEi_nid_postfix(WebRtcAudioEncoder* encoder, int rttMs); +void APS5_VABI _ZN6webrtc12AudioEncoder18OnReceivedOverheadEm_nid_postfix(WebRtcAudioEncoder* encoder, std::size_t overheadBytesPerPacket); +void APS5_VABI _ZN6webrtc12AudioEncoder27SetReceiverFrameLengthRangeEii_nid_postfix(WebRtcAudioEncoder* encoder, int minFrameLengthMs, int maxFrameLengthMs); +WebRtcAnaStats* APS5_VABI _ZNK6webrtc12AudioEncoder11GetANAStatsEv_nid_postfix(WebRtcAnaStats* result, const WebRtcAudioEncoder* encoder); +void APS5_VABI _ZN6webrtc8ANAStatsC1Ev_nid_postfix(WebRtcAnaStats* stats); int APS5_VABI _ZN6webrtc12AudioEncoder11EncodedInfoC1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc12AudioEncoder11EncodedInfoC1ERKS1__nid_postfix(void); int APS5_VABI _ZN6webrtc12AudioEncoder11EncodedInfoD1Ev_nid_postfix(void); @@ -299,12 +306,12 @@ int APS5_VABI _ZN6webrtc15AudioSendStream6Config13SendCodecSpecC1EiRKNS_14SdpAud int APS5_VABI _ZN6webrtc15AudioSendStream6Config13SendCodecSpecD1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc10AudioFrame11UpdateFrameEjPKsmiNS0_10SpeechTypeENS0_11VADActivityEm_nid_postfix(void); int APS5_VABI _ZN6webrtc10AudioFrame8CopyFromERKS0__nid_postfix(void); -int APS5_VABI _ZNK6webrtc10AudioFrame5mutedEv_nid_postfix(void); +bool APS5_VABI _ZNK6webrtc10AudioFrame5mutedEv_nid_postfix(const WebRtcAudioFrame* frame); int APS5_VABI _ZNK6webrtc10AudioFrame9data_viewEv_nid_postfix(void); -int APS5_VABI _ZNK6webrtc10AudioFrame4dataEv_nid_postfix(void); -int APS5_VABI _ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(void); -int APS5_VABI _ZN6webrtc10AudioFrame4MuteEv_nid_postfix(void); -int APS5_VABI _ZN6webrtc10AudioFrameC1Ev_nid_postfix(void); +const std::int16_t* APS5_VABI _ZNK6webrtc10AudioFrame4dataEv_nid_postfix(const WebRtcAudioFrame* frame); +std::int16_t* APS5_VABI _ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(WebRtcAudioFrame* frame); +void APS5_VABI _ZN6webrtc10AudioFrame4MuteEv_nid_postfix(WebRtcAudioFrame* frame); +void APS5_VABI _ZN6webrtc10AudioFrameC1Ev_nid_postfix(WebRtcAudioFrame* frame); int APS5_VABI _ZN6webrtc20EchoCanceller3ConfigaSERKS0__nid_postfix(void); int APS5_VABI _ZN6webrtc20EchoCanceller3ConfigC1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc20EchoCanceller3ConfigC1ERKS0__nid_postfix(void); @@ -328,13 +335,13 @@ void APS5_VABI WebRtcVad_Free_nid_postfix(WebRtcVadInstance* handle); int APS5_VABI WebRtcVad_Init_nid_postfix(WebRtcVadInstance* handle); int APS5_VABI WebRtcVad_set_mode_nid_postfix(WebRtcVadInstance* handle, int mode); int APS5_VABI WebRtcVad_Process_nid_postfix(WebRtcVadInstance* handle, int sampleRate, const std::int16_t* audio, std::size_t length); -int APS5_VABI _ZNK6webrtc18AudioDeviceGeneric21BuiltInAECIsAvailableEv_nid_postfix(void); -int APS5_VABI _ZNK6webrtc18AudioDeviceGeneric21BuiltInAGCIsAvailableEv_nid_postfix(void); -int APS5_VABI _ZNK6webrtc18AudioDeviceGeneric20BuiltInNSIsAvailableEv_nid_postfix(void); -int APS5_VABI _ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAECEb_nid_postfix(void); -int APS5_VABI _ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAGCEb_nid_postfix(void); -int APS5_VABI _ZN6webrtc18AudioDeviceGeneric15EnableBuiltInNSEb_nid_postfix(void); -int APS5_VABI _ZNK6webrtc18AudioDeviceGeneric23GetPlayoutUnderrunCountEv_nid_postfix(void); +bool APS5_VABI _ZNK6webrtc18AudioDeviceGeneric21BuiltInAECIsAvailableEv_nid_postfix(const WebRtcAudioDeviceGeneric* device); +bool APS5_VABI _ZNK6webrtc18AudioDeviceGeneric21BuiltInAGCIsAvailableEv_nid_postfix(const WebRtcAudioDeviceGeneric* device); +bool APS5_VABI _ZNK6webrtc18AudioDeviceGeneric20BuiltInNSIsAvailableEv_nid_postfix(const WebRtcAudioDeviceGeneric* device); +std::int32_t APS5_VABI _ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAECEb_nid_postfix(WebRtcAudioDeviceGeneric* device, bool enable); +std::int32_t APS5_VABI _ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAGCEb_nid_postfix(WebRtcAudioDeviceGeneric* device, bool enable); +std::int32_t APS5_VABI _ZN6webrtc18AudioDeviceGeneric15EnableBuiltInNSEb_nid_postfix(WebRtcAudioDeviceGeneric* device, bool enable); +std::int32_t APS5_VABI _ZNK6webrtc18AudioDeviceGeneric23GetPlayoutUnderrunCountEv_nid_postfix(const WebRtcAudioDeviceGeneric* device); int APS5_VABI _ZN6webrtc17PushSincResamplerC1Emm_nid_postfix(void); int APS5_VABI _ZN6webrtc17PushSincResampler8ResampleEPKfmPfm_nid_postfix(void); int APS5_VABI _ZN6webrtc13PushResamplerIsEC1Ev_nid_postfix(void); @@ -352,14 +359,14 @@ int APS5_VABI libwebrtcUnknown42(void); int APS5_VABI _ZN6webrtc11AudioBufferC1Emmmmmm_nid_postfix(void); int APS5_VABI _ZN6webrtc11AudioBuffer8CopyFromEPKsRKNS_12StreamConfigE_nid_postfix(void); int APS5_VABI _ZN6webrtc11AudioBuffer6CopyToERKNS_12StreamConfigEPs_nid_postfix(void); -int APS5_VABI _ZN6webrtc23GetAvailableCpuFeaturesEv_nid_postfix(void); +WebRtcAvailableCpuFeatures APS5_VABI _ZN6webrtc23GetAvailableCpuFeaturesEv_nid_postfix(); int APS5_VABI _ZN6webrtc7rnn_vad17FeaturesExtractorC1ERKNS_20AvailableCpuFeaturesE_nid_postfix(void); int APS5_VABI _ZN6webrtc7rnn_vad6RnnVadC1ERKNS_20AvailableCpuFeaturesE_nid_postfix(void); int APS5_VABI _ZN6webrtc7rnn_vad6RnnVadD1Ev_nid_postfix(void); int APS5_VABI _ZN6webrtc7rnn_vad17FeaturesExtractorD1Ev_nid_postfix(void); int APS5_VABI libwebrtcUnknown43(void); int APS5_VABI _ZN6webrtc7rnn_vad6RnnVad21ComputeVadProbabilityEN3rtc9ArrayViewIKfLl42EEEb_nid_postfix(void); -int APS5_VABI _ZN6webrtc20AudioFrameOperations4MuteEPNS_10AudioFrameE_nid_postfix(void); +void APS5_VABI _ZN6webrtc20AudioFrameOperations4MuteEPNS_10AudioFrameE_nid_postfix(WebRtcAudioFrame* frame); int APS5_VABI libwebrtcUnknown44(void); int APS5_VABI libwebrtcUnknown45(void); int APS5_VABI _ZN3rtc11RateTrackerC1Elm_nid_postfix(void); diff --git a/core/libs/prx/libwebrtc/include/AudioDecoder.hpp b/core/libs/prx/libwebrtc/include/AudioDecoder.hpp new file mode 100644 index 0000000000..f058d27226 --- /dev/null +++ b/core/libs/prx/libwebrtc/include/AudioDecoder.hpp @@ -0,0 +1,4 @@ +#pragma once + +struct WebRtcAudioDecoder; +struct WebRtcInt16Buffer; diff --git a/core/libs/prx/libwebrtc/include/AudioDeviceGeneric.hpp b/core/libs/prx/libwebrtc/include/AudioDeviceGeneric.hpp new file mode 100644 index 0000000000..e508033696 --- /dev/null +++ b/core/libs/prx/libwebrtc/include/AudioDeviceGeneric.hpp @@ -0,0 +1,3 @@ +#pragma once + +struct WebRtcAudioDeviceGeneric; diff --git a/core/libs/prx/libwebrtc/include/AudioEncoder.hpp b/core/libs/prx/libwebrtc/include/AudioEncoder.hpp new file mode 100644 index 0000000000..4d207a9abe --- /dev/null +++ b/core/libs/prx/libwebrtc/include/AudioEncoder.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +struct WebRtcAudioEncoder; + +struct WebRtcOptionalUint32 { + union { + char nullState; + std::uint32_t value; + } storage; + bool engaged; +}; + +struct WebRtcOptionalFloat { + union { + char nullState; + float value; + } storage; + bool engaged; +}; + +struct WebRtcAnaStats { + WebRtcOptionalUint32 bitrateActionCounter; + WebRtcOptionalUint32 channelActionCounter; + WebRtcOptionalUint32 dtxActionCounter; + WebRtcOptionalUint32 fecActionCounter; + WebRtcOptionalUint32 frameLengthIncreaseCounter; + WebRtcOptionalUint32 frameLengthDecreaseCounter; + WebRtcOptionalFloat uplinkPacketLossFraction; +}; + +static_assert(sizeof(WebRtcOptionalUint32) == 8); +static_assert(offsetof(WebRtcOptionalUint32, engaged) == 4); +static_assert(sizeof(WebRtcOptionalFloat) == 8); +static_assert(sizeof(WebRtcAnaStats) == 56); +static_assert(offsetof(WebRtcAnaStats, uplinkPacketLossFraction) == 48); diff --git a/core/libs/prx/libwebrtc/include/AudioFrame.hpp b/core/libs/prx/libwebrtc/include/AudioFrame.hpp new file mode 100644 index 0000000000..e462d1bea1 --- /dev/null +++ b/core/libs/prx/libwebrtc/include/AudioFrame.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +constexpr std::size_t WebRtcAudioFrameMaxDataSizeSamples = 7680; +constexpr std::int32_t WebRtcAudioFrameSpeechUndefined = 4; +constexpr std::int32_t WebRtcAudioFrameVadUnknown = 2; +constexpr std::int32_t WebRtcChannelLayoutNone = 0; + +struct WebRtcAudioFrame { + std::uint32_t timestamp; + std::int64_t elapsedTimeMs; + std::int64_t ntpTimeMs; + std::size_t samplesPerChannel; + std::int32_t sampleRateHz; + std::size_t numChannels; + std::int32_t speechType; + std::int32_t vadActivity; + std::int64_t profileTimestampMs; + void* packetInfos; + std::int16_t data[WebRtcAudioFrameMaxDataSizeSamples]; + bool muted; + std::int32_t channelLayout; + union { + char nullState; + std::int64_t value; + } absoluteCaptureTimestampMs; + bool absoluteCaptureTimestampMsEngaged; +}; + +static_assert(sizeof(WebRtcAudioFrame) == 15456); +static_assert(offsetof(WebRtcAudioFrame, elapsedTimeMs) == 8); +static_assert(offsetof(WebRtcAudioFrame, ntpTimeMs) == 16); +static_assert(offsetof(WebRtcAudioFrame, samplesPerChannel) == 24); +static_assert(offsetof(WebRtcAudioFrame, sampleRateHz) == 32); +static_assert(offsetof(WebRtcAudioFrame, numChannels) == 40); +static_assert(offsetof(WebRtcAudioFrame, speechType) == 48); +static_assert(offsetof(WebRtcAudioFrame, vadActivity) == 52); +static_assert(offsetof(WebRtcAudioFrame, profileTimestampMs) == 56); +static_assert(offsetof(WebRtcAudioFrame, packetInfos) == 64); +static_assert(offsetof(WebRtcAudioFrame, data) == 72); +static_assert(offsetof(WebRtcAudioFrame, muted) == 15432); +static_assert(offsetof(WebRtcAudioFrame, channelLayout) == 15436); +static_assert(offsetof(WebRtcAudioFrame, absoluteCaptureTimestampMs) == 15440); +static_assert(offsetof(WebRtcAudioFrame, absoluteCaptureTimestampMsEngaged) == 15448); diff --git a/core/libs/prx/libwebrtc/include/CpuFeatures.hpp b/core/libs/prx/libwebrtc/include/CpuFeatures.hpp new file mode 100644 index 0000000000..4dd076d791 --- /dev/null +++ b/core/libs/prx/libwebrtc/include/CpuFeatures.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +struct WebRtcAvailableCpuFeatures { + bool sse2; + bool avx2; + bool neon; +}; + +static_assert(sizeof(WebRtcAvailableCpuFeatures) == 3); +static_assert(offsetof(WebRtcAvailableCpuFeatures, avx2) == 1); +static_assert(offsetof(WebRtcAvailableCpuFeatures, neon) == 2); diff --git a/core/libs/prx/libwebrtc/include/IpAddress.hpp b/core/libs/prx/libwebrtc/include/IpAddress.hpp new file mode 100644 index 0000000000..1142275a2a --- /dev/null +++ b/core/libs/prx/libwebrtc/include/IpAddress.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +constexpr std::int32_t WebRtcAfUnspec = 0; +constexpr std::int32_t WebRtcAfInet = 2; +constexpr std::int32_t WebRtcAfInet6 = 28; + +struct WebRtcInAddr { + std::uint32_t sAddr; +}; + +struct WebRtcIn6Addr { + std::uint8_t bytes[16]; +}; + +struct WebRtcIpAddress { + const void* vtable; + std::int32_t family; + union { + WebRtcInAddr ip4; + WebRtcIn6Addr ip6; + } address; +}; + +static_assert(sizeof(WebRtcInAddr) == 4); +static_assert(sizeof(WebRtcIn6Addr) == 16); +static_assert(sizeof(WebRtcIpAddress) == 32); +static_assert(offsetof(WebRtcIpAddress, family) == 8); +static_assert(offsetof(WebRtcIpAddress, address) == 12); diff --git a/core/libs/prx/libwebrtc/include/TaskQueueBase.hpp b/core/libs/prx/libwebrtc/include/TaskQueueBase.hpp new file mode 100644 index 0000000000..100d970a67 --- /dev/null +++ b/core/libs/prx/libwebrtc/include/TaskQueueBase.hpp @@ -0,0 +1,9 @@ +#pragma once + +struct WebRtcTaskQueueBase; + +struct WebRtcCurrentTaskQueueSetter { + WebRtcTaskQueueBase* previous; +}; + +static_assert(sizeof(WebRtcCurrentTaskQueueSetter) == 8); diff --git a/core/libs/prx/libwebrtc/src/AudioDecoder.cpp b/core/libs/prx/libwebrtc/src/AudioDecoder.cpp new file mode 100644 index 0000000000..c552105486 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/AudioDecoder.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include + +extern "C" { + +bool APS5_VABI _ZNK6webrtc12AudioDecoder12HasDecodePlcEv_nid_postfix(const WebRtcAudioDecoder*) { + return false; +} + +std::size_t APS5_VABI _ZN6webrtc12AudioDecoder9DecodePlcEmPs_nid_postfix(WebRtcAudioDecoder*, std::size_t, std::int16_t*) { + return 0; +} + +void APS5_VABI _ZN6webrtc12AudioDecoder11GeneratePlcEmPN3rtc7BufferTIsLb0EEE_nid_postfix(WebRtcAudioDecoder*, std::size_t, WebRtcInt16Buffer*) {} + +int APS5_VABI _ZN6webrtc12AudioDecoder9ErrorCodeEv_nid_postfix(WebRtcAudioDecoder*) { + return 0; +} + +} diff --git a/core/libs/prx/libwebrtc/src/AudioDeviceGeneric.cpp b/core/libs/prx/libwebrtc/src/AudioDeviceGeneric.cpp new file mode 100644 index 0000000000..039e0d6ac7 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/AudioDeviceGeneric.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" + +#include + +extern "C" { + +bool APS5_VABI _ZNK6webrtc18AudioDeviceGeneric21BuiltInAECIsAvailableEv_nid_postfix(const WebRtcAudioDeviceGeneric*) { + return false; +} + +std::int32_t APS5_VABI _ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAECEb_nid_postfix(WebRtcAudioDeviceGeneric*, bool) { + return -1; +} + +bool APS5_VABI _ZNK6webrtc18AudioDeviceGeneric21BuiltInAGCIsAvailableEv_nid_postfix(const WebRtcAudioDeviceGeneric*) { + return false; +} + +std::int32_t APS5_VABI _ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAGCEb_nid_postfix(WebRtcAudioDeviceGeneric*, bool) { + return -1; +} + +bool APS5_VABI _ZNK6webrtc18AudioDeviceGeneric20BuiltInNSIsAvailableEv_nid_postfix(const WebRtcAudioDeviceGeneric*) { + return false; +} + +std::int32_t APS5_VABI _ZN6webrtc18AudioDeviceGeneric15EnableBuiltInNSEb_nid_postfix(WebRtcAudioDeviceGeneric*, bool) { + return -1; +} + +std::int32_t APS5_VABI _ZNK6webrtc18AudioDeviceGeneric23GetPlayoutUnderrunCountEv_nid_postfix(const WebRtcAudioDeviceGeneric*) { + return -1; +} + +} diff --git a/core/libs/prx/libwebrtc/src/AudioEncoder.cpp b/core/libs/prx/libwebrtc/src/AudioEncoder.cpp new file mode 100644 index 0000000000..e745251521 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/AudioEncoder.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include + +namespace { + +void ClearOptional(WebRtcOptionalUint32& optional) { + optional.storage.nullState = 0; + optional.engaged = false; +} + +void ConstructAnaStats(WebRtcAnaStats& stats) { + ClearOptional(stats.bitrateActionCounter); + ClearOptional(stats.channelActionCounter); + ClearOptional(stats.dtxActionCounter); + ClearOptional(stats.fecActionCounter); + ClearOptional(stats.frameLengthIncreaseCounter); + ClearOptional(stats.frameLengthDecreaseCounter); + stats.uplinkPacketLossFraction.storage.nullState = 0; + stats.uplinkPacketLossFraction.engaged = false; +} + +} + +extern "C" { + +void APS5_VABI _ZN6webrtc8ANAStatsC1Ev_nid_postfix(WebRtcAnaStats* stats) { + if (!stats) + throw std::invalid_argument("ANAStats::ANAStats requires stats"); + ConstructAnaStats(*stats); +} + +WebRtcAnaStats* APS5_VABI _ZNK6webrtc12AudioEncoder11GetANAStatsEv_nid_postfix(WebRtcAnaStats* result, const WebRtcAudioEncoder*) { + if (!result) + throw std::invalid_argument("AudioEncoder::GetANAStats requires a result"); + ConstructAnaStats(*result); + return result; +} + +bool APS5_VABI _ZNK6webrtc12AudioEncoder6GetDtxEv_nid_postfix(const WebRtcAudioEncoder*) { + return false; +} + +void APS5_VABI _ZN6webrtc12AudioEncoder16SetTargetBitrateEi_nid_postfix(WebRtcAudioEncoder*, int) {} + +void APS5_VABI _ZN6webrtc12AudioEncoder26DisableAudioNetworkAdaptorEv_nid_postfix(WebRtcAudioEncoder*) {} + +void APS5_VABI _ZN6webrtc12AudioEncoder13OnReceivedRttEi_nid_postfix(WebRtcAudioEncoder*, int) {} + +void APS5_VABI _ZN6webrtc12AudioEncoder18OnReceivedOverheadEm_nid_postfix(WebRtcAudioEncoder*, std::size_t) {} + +void APS5_VABI _ZN6webrtc12AudioEncoder27SetReceiverFrameLengthRangeEii_nid_postfix(WebRtcAudioEncoder*, int, int) {} + +} diff --git a/core/libs/prx/libwebrtc/src/AudioFrame.cpp b/core/libs/prx/libwebrtc/src/AudioFrame.cpp new file mode 100644 index 0000000000..cbe7492c52 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/AudioFrame.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include + +namespace { + +const std::int16_t ZeroedData[WebRtcAudioFrameMaxDataSizeSamples] = {}; + +} + +extern "C" { + +void APS5_VABI _ZN6webrtc10AudioFrameC1Ev_nid_postfix(WebRtcAudioFrame* frame) { + if (!frame) + throw std::invalid_argument("AudioFrame::AudioFrame requires a frame"); + frame->timestamp = 0; + frame->elapsedTimeMs = -1; + frame->ntpTimeMs = -1; + frame->samplesPerChannel = 0; + frame->sampleRateHz = 0; + frame->numChannels = 0; + frame->speechType = WebRtcAudioFrameSpeechUndefined; + frame->vadActivity = WebRtcAudioFrameVadUnknown; + frame->profileTimestampMs = 0; + frame->packetInfos = nullptr; + frame->muted = true; + frame->channelLayout = WebRtcChannelLayoutNone; + frame->absoluteCaptureTimestampMs.nullState = 0; + frame->absoluteCaptureTimestampMsEngaged = false; +} + +void APS5_VABI _ZN6webrtc10AudioFrame4MuteEv_nid_postfix(WebRtcAudioFrame* frame) { + if (!frame) + throw std::invalid_argument("AudioFrame::Mute requires a frame"); + frame->muted = true; +} + +bool APS5_VABI _ZNK6webrtc10AudioFrame5mutedEv_nid_postfix(const WebRtcAudioFrame* frame) { + if (!frame) + throw std::invalid_argument("AudioFrame::muted requires a frame"); + return frame->muted; +} + +const std::int16_t* APS5_VABI _ZNK6webrtc10AudioFrame4dataEv_nid_postfix(const WebRtcAudioFrame* frame) { + if (!frame) + throw std::invalid_argument("AudioFrame::data requires a frame"); + return frame->muted ? ZeroedData : frame->data; +} + +std::int16_t* APS5_VABI _ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(WebRtcAudioFrame* frame) { + if (!frame) + throw std::invalid_argument("AudioFrame::mutable_data requires a frame"); + if (frame->muted) { + std::memset(frame->data, 0, sizeof(frame->data)); + frame->muted = false; + } + return frame->data; +} + +} diff --git a/core/libs/prx/libwebrtc/src/AudioFrameOperations.cpp b/core/libs/prx/libwebrtc/src/AudioFrameOperations.cpp new file mode 100644 index 0000000000..ebd4232b28 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/AudioFrameOperations.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" +#include "prx/libc/include/General.hpp" + +#include + +extern "C" { + +void APS5_VABI _ZN6webrtc20AudioFrameOperations4MuteEPNS_10AudioFrameE_nid_postfix(WebRtcAudioFrame* frame) { + if (!frame) + throw std::invalid_argument("AudioFrameOperations::Mute requires a frame"); + if (frame->numChannels != 0 && frame->samplesPerChannel > WebRtcAudioFrameMaxDataSizeSamples / frame->numChannels) + NotImplemented_nid_no_patch(__func__); + frame->muted = true; +} + +} diff --git a/core/libs/prx/libwebrtc/src/CpuFeatures.cpp b/core/libs/prx/libwebrtc/src/CpuFeatures.cpp new file mode 100644 index 0000000000..4033c443e6 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/CpuFeatures.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include + +namespace { + +std::uint64_t ExtendedControlRegister0() { + std::uint32_t low = 0; + std::uint32_t high = 0; + __asm__ volatile("xgetbv" : "=a"(low), "=d"(high) : "c"(0)); + return (static_cast(high) << 32) | low; +} + +bool HasAvx2(unsigned int leaf1Ecx) { + unsigned int eax = 0; + unsigned int ebx = 0; + unsigned int ecx = 0; + unsigned int edx = 0; + __cpuid_count(0, 0, eax, ebx, ecx, edx); + if (static_cast(eax) < 7) + return false; + if ((leaf1Ecx & 0x10000000u) == 0 || (leaf1Ecx & 0x04000000u) == 0 || (leaf1Ecx & 0x08000000u) == 0) + return false; + if ((ExtendedControlRegister0() & 0x6u) != 0x6u) + return false; + __cpuid_count(7, 0, eax, ebx, ecx, edx); + return (ebx & 0x00000020u) != 0 && (ebx & 0x00000100u) != 0; +} + +} + +extern "C" { + +WebRtcAvailableCpuFeatures APS5_VABI _ZN6webrtc23GetAvailableCpuFeaturesEv_nid_postfix() { + unsigned int eax = 0; + unsigned int ebx = 0; + unsigned int ecx = 0; + unsigned int edx = 0; + __cpuid_count(1, 0, eax, ebx, ecx, edx); + return {(edx & 0x04000000u) != 0, HasAvx2(ecx), false}; +} + +} diff --git a/core/libs/prx/libwebrtc/src/IpAddress.cpp b/core/libs/prx/libwebrtc/src/IpAddress.cpp new file mode 100644 index 0000000000..db3cf1ff07 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/IpAddress.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include +#include + +namespace { + +const WebRtcIpAddress& Checked(const WebRtcIpAddress* address, const char* function) { + if (!address) + throw std::invalid_argument(function); + return *address; +} + +std::uint32_t BigEndianWord(const std::uint8_t* bytes) { + return (static_cast(bytes[0]) << 24) | (static_cast(bytes[1]) << 16) | (static_cast(bytes[2]) << 8) | bytes[3]; +} + +std::uint32_t HostOrderIpv4(const WebRtcIpAddress& address) { + std::uint8_t bytes[4]; + std::memcpy(bytes, &address.address.ip4, sizeof(bytes)); + return BigEndianWord(bytes); +} + +} + +extern "C" { + +WebRtcInAddr APS5_VABI _ZNK3rtc9IPAddress12ipv4_addressEv_nid_postfix(const WebRtcIpAddress* address) { + return Checked(address, "IPAddress::ipv4_address requires an address").address.ip4; +} + +WebRtcIn6Addr APS5_VABI _ZNK3rtc9IPAddress12ipv6_addressEv_nid_postfix(const WebRtcIpAddress* address) { + return Checked(address, "IPAddress::ipv6_address requires an address").address.ip6; +} + +std::uint32_t APS5_VABI _ZNK3rtc9IPAddress27v4AddressAsHostOrderIntegerEv_nid_postfix(const WebRtcIpAddress* address) { + const WebRtcIpAddress& checked = Checked(address, "IPAddress::v4AddressAsHostOrderInteger requires an address"); + return checked.family == WebRtcAfInet ? HostOrderIpv4(checked) : 0; +} + +bool APS5_VABI _ZNK3rtc9IPAddresseqERKS0__nid_postfix(const WebRtcIpAddress* address, const WebRtcIpAddress* other) { + const WebRtcIpAddress& left = Checked(address, "IPAddress::operator== requires an address"); + const WebRtcIpAddress& right = Checked(other, "IPAddress::operator== requires another address"); + if (left.family != right.family) + return false; + if (left.family == WebRtcAfInet) + return std::memcmp(&left.address.ip4, &right.address.ip4, sizeof(WebRtcInAddr)) == 0; + if (left.family == WebRtcAfInet6) + return std::memcmp(&left.address.ip6, &right.address.ip6, sizeof(WebRtcIn6Addr)) == 0; + return left.family == WebRtcAfUnspec; +} + +int APS5_VABI _ZN3rtc15CountIPMaskBitsERKNS_9IPAddressE_nid_postfix(const WebRtcIpAddress* mask) { + const WebRtcIpAddress& checked = Checked(mask, "CountIPMaskBits requires a mask"); + std::uint32_t word = 0; + int bits = 0; + if (checked.family == WebRtcAfInet) { + word = HostOrderIpv4(checked); + } else if (checked.family == WebRtcAfInet6) { + const std::uint8_t* bytes = checked.address.ip6.bytes; + while (bits < 128 && BigEndianWord(bytes + bits / 8) == 0xFFFFFFFFu) + bits += 32; + if (bits < 128) + word = BigEndianWord(bytes + bits / 8); + } else { + return 0; + } + if (word == 0) + return bits; + return bits + 32 - std::countr_zero(word); +} + +} diff --git a/core/libs/prx/libwebrtc/src/TaskQueueBase.cpp b/core/libs/prx/libwebrtc/src/TaskQueueBase.cpp new file mode 100644 index 0000000000..f263fe3a35 --- /dev/null +++ b/core/libs/prx/libwebrtc/src/TaskQueueBase.cpp @@ -0,0 +1,72 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +/* + * Copyright (c) 2011, The WebRTC project authors. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Google nor the names of its contributors may + * be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "prx/libwebrtc/libwebrtc.hpp" + +#include + +namespace { + +thread_local WebRtcTaskQueueBase* currentTaskQueue = nullptr; + +} + +extern "C" { + +WebRtcTaskQueueBase* APS5_VABI _ZN6webrtc13TaskQueueBase7CurrentEv_nid_postfix(void) { + return currentTaskQueue; +} + +void APS5_VABI _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterC1EPS0__nid_postfix(WebRtcCurrentTaskQueueSetter* setter, WebRtcTaskQueueBase* taskQueue) { + if (!setter) + throw std::invalid_argument("CurrentTaskQueueSetter requires a setter"); + setter->previous = currentTaskQueue; + currentTaskQueue = taskQueue; +} + +void APS5_VABI _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterD1Ev_nid_postfix(WebRtcCurrentTaskQueueSetter* setter) { + if (!setter) + throw std::invalid_argument("~CurrentTaskQueueSetter requires a setter"); + currentTaskQueue = setter->previous; +} + +} diff --git a/core/libs/prx/libwebrtc/tests/AudioDecoder.cpp b/core/libs/prx/libwebrtc/tests/AudioDecoder.cpp new file mode 100644 index 0000000000..5ed74ba9f0 --- /dev/null +++ b/core/libs/prx/libwebrtc/tests/AudioDecoder.cpp @@ -0,0 +1,62 @@ +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "libwebrtc AudioDecoder check failed at line %u\n", location.line()); + std::abort(); +} + +unsigned char decoderStorage[64]; +unsigned char bufferStorage[32]; +std::int16_t samples[64]; + +WebRtcAudioDecoder* Decoder() { + std::memset(decoderStorage, 0xC3, sizeof(decoderStorage)); + std::memset(bufferStorage, 0xA5, sizeof(bufferStorage)); + for (std::int16_t& sample : samples) + sample = 0x1234; + return reinterpret_cast(decoderStorage); +} + +bool Unchanged() { + for (unsigned char byte : decoderStorage) + if (byte != 0xC3) return false; + for (unsigned char byte : bufferStorage) + if (byte != 0xA5) return false; + for (std::int16_t sample : samples) + if (sample != 0x1234) return false; + return true; +} + +void TestPlc() { + WebRtcAudioDecoder* decoder = Decoder(); + Require(!_ZNK6webrtc12AudioDecoder12HasDecodePlcEv_nid_postfix(decoder)); + Require(_ZN6webrtc12AudioDecoder9DecodePlcEmPs_nid_postfix(decoder, 0, samples) == 0); + Require(_ZN6webrtc12AudioDecoder9DecodePlcEmPs_nid_postfix(decoder, 3, samples) == 0); + Require(_ZN6webrtc12AudioDecoder9DecodePlcEmPs_nid_postfix(decoder, 1, nullptr) == 0); + _ZN6webrtc12AudioDecoder11GeneratePlcEmPN3rtc7BufferTIsLb0EEE_nid_postfix(decoder, 480, reinterpret_cast(bufferStorage)); + _ZN6webrtc12AudioDecoder11GeneratePlcEmPN3rtc7BufferTIsLb0EEE_nid_postfix(decoder, 480, nullptr); + Require(Unchanged()); +} + +void TestErrorCode() { + WebRtcAudioDecoder* decoder = Decoder(); + Require(_ZN6webrtc12AudioDecoder9ErrorCodeEv_nid_postfix(decoder) == 0); + Require(Unchanged()); +} + +} + +int main() { + TestPlc(); + TestErrorCode(); + return 0; +} diff --git a/core/libs/prx/libwebrtc/tests/AudioDeviceGeneric.cpp b/core/libs/prx/libwebrtc/tests/AudioDeviceGeneric.cpp new file mode 100644 index 0000000000..05c1f1e42f --- /dev/null +++ b/core/libs/prx/libwebrtc/tests/AudioDeviceGeneric.cpp @@ -0,0 +1,62 @@ +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "libwebrtc AudioDeviceGeneric check failed at line %u\n", location.line()); + std::abort(); +} + +unsigned char storage[64]; + +WebRtcAudioDeviceGeneric* Device() { + std::memset(storage, 0xC3, sizeof(storage)); + return reinterpret_cast(storage); +} + +bool DeviceUnchanged() { + for (unsigned char byte : storage) + if (byte != 0xC3) return false; + return true; +} + +void TestAvailability() { + WebRtcAudioDeviceGeneric* device = Device(); + Require(!_ZNK6webrtc18AudioDeviceGeneric21BuiltInAECIsAvailableEv_nid_postfix(device)); + Require(!_ZNK6webrtc18AudioDeviceGeneric21BuiltInAGCIsAvailableEv_nid_postfix(device)); + Require(!_ZNK6webrtc18AudioDeviceGeneric20BuiltInNSIsAvailableEv_nid_postfix(device)); + Require(DeviceUnchanged()); +} + +void TestEnable() { + WebRtcAudioDeviceGeneric* device = Device(); + for (bool enable : {true, false}) { + Require(_ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAECEb_nid_postfix(device, enable) == -1); + Require(_ZN6webrtc18AudioDeviceGeneric16EnableBuiltInAGCEb_nid_postfix(device, enable) == -1); + Require(_ZN6webrtc18AudioDeviceGeneric15EnableBuiltInNSEb_nid_postfix(device, enable) == -1); + } + Require(!_ZNK6webrtc18AudioDeviceGeneric21BuiltInAECIsAvailableEv_nid_postfix(device)); + Require(DeviceUnchanged()); +} + +void TestUnderrunCount() { + WebRtcAudioDeviceGeneric* device = Device(); + Require(_ZNK6webrtc18AudioDeviceGeneric23GetPlayoutUnderrunCountEv_nid_postfix(device) == -1); + Require(DeviceUnchanged()); +} + +} + +int main() { + TestAvailability(); + TestEnable(); + TestUnderrunCount(); + return 0; +} diff --git a/core/libs/prx/libwebrtc/tests/AudioEncoder.cpp b/core/libs/prx/libwebrtc/tests/AudioEncoder.cpp new file mode 100644 index 0000000000..2b1141885f --- /dev/null +++ b/core/libs/prx/libwebrtc/tests/AudioEncoder.cpp @@ -0,0 +1,94 @@ +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "libwebrtc AudioEncoder check failed at line %u\n", location.line()); + std::abort(); +} + +template void RequireInvalidArgument(TAction action, std::source_location location = std::source_location::current()) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, location); +} + +void Fill(WebRtcAnaStats& stats) { + std::memset(&stats, 0x5A, sizeof(stats)); +} + +bool Cleared(const WebRtcOptionalUint32& optional) { + const auto* bytes = reinterpret_cast(&optional); + return !optional.engaged && bytes[0] == 0 && bytes[1] == 0x5A && bytes[2] == 0x5A && bytes[3] == 0x5A && bytes[5] == 0x5A && bytes[6] == 0x5A && bytes[7] == 0x5A; +} + +bool AllCleared(const WebRtcAnaStats& stats) { + WebRtcOptionalUint32 loss; + std::memcpy(&loss, &stats.uplinkPacketLossFraction, sizeof(loss)); + return Cleared(stats.bitrateActionCounter) && Cleared(stats.channelActionCounter) && Cleared(stats.dtxActionCounter) && Cleared(stats.fecActionCounter) && + Cleared(stats.frameLengthIncreaseCounter) && Cleared(stats.frameLengthDecreaseCounter) && Cleared(loss); +} + +WebRtcAudioEncoder* Encoder() { + static unsigned char storage[64]; + std::memset(storage, 0xC3, sizeof(storage)); + return reinterpret_cast(storage); +} + +bool EncoderUnchanged(const WebRtcAudioEncoder* encoder) { + const auto* bytes = reinterpret_cast(encoder); + for (int i = 0; i < 64; ++i) + if (bytes[i] != 0xC3) return false; + return true; +} + +void TestAnaStats() { + WebRtcAnaStats stats; + Fill(stats); + _ZN6webrtc8ANAStatsC1Ev_nid_postfix(&stats); + Require(AllCleared(stats)); + + WebRtcAudioEncoder* encoder = Encoder(); + WebRtcAnaStats result; + Fill(result); + Require(_ZNK6webrtc12AudioEncoder11GetANAStatsEv_nid_postfix(&result, encoder) == &result); + Require(AllCleared(result)); + Require(EncoderUnchanged(encoder)); +} + +void TestDefaults() { + WebRtcAudioEncoder* encoder = Encoder(); + Require(!_ZNK6webrtc12AudioEncoder6GetDtxEv_nid_postfix(encoder)); + _ZN6webrtc12AudioEncoder16SetTargetBitrateEi_nid_postfix(encoder, 32000); + _ZN6webrtc12AudioEncoder26DisableAudioNetworkAdaptorEv_nid_postfix(encoder); + _ZN6webrtc12AudioEncoder13OnReceivedRttEi_nid_postfix(encoder, 120); + _ZN6webrtc12AudioEncoder18OnReceivedOverheadEm_nid_postfix(encoder, 40); + _ZN6webrtc12AudioEncoder27SetReceiverFrameLengthRangeEii_nid_postfix(encoder, 20, 120); + Require(!_ZNK6webrtc12AudioEncoder6GetDtxEv_nid_postfix(encoder)); + Require(EncoderUnchanged(encoder)); +} + +void TestNullPointers() { + RequireInvalidArgument([] { _ZN6webrtc8ANAStatsC1Ev_nid_postfix(nullptr); }); + RequireInvalidArgument([] { _ZNK6webrtc12AudioEncoder11GetANAStatsEv_nid_postfix(nullptr, Encoder()); }); +} + +} + +int main() { + TestAnaStats(); + TestDefaults(); + TestNullPointers(); + return 0; +} diff --git a/core/libs/prx/libwebrtc/tests/AudioFrame.cpp b/core/libs/prx/libwebrtc/tests/AudioFrame.cpp new file mode 100644 index 0000000000..b8e6489e7b --- /dev/null +++ b/core/libs/prx/libwebrtc/tests/AudioFrame.cpp @@ -0,0 +1,143 @@ +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "libwebrtc AudioFrame check failed at line %u\n", location.line()); + std::abort(); +} + +template void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { + action(); + } catch (const TException&) { + return; + } + Require(false, location); +} + +std::unique_ptr Filled() { + auto frame = std::make_unique(); + std::memset(frame.get(), 0x5A, sizeof(WebRtcAudioFrame)); + return frame; +} + +bool AllSamples(const std::int16_t* data, std::int16_t value) { + for (std::size_t i = 0; i < WebRtcAudioFrameMaxDataSizeSamples; ++i) + if (data[i] != value) return false; + return true; +} + +void TestConstructor() { + auto frame = Filled(); + _ZN6webrtc10AudioFrameC1Ev_nid_postfix(frame.get()); + Require(frame->timestamp == 0); + Require(frame->elapsedTimeMs == -1); + Require(frame->ntpTimeMs == -1); + Require(frame->samplesPerChannel == 0); + Require(frame->sampleRateHz == 0); + Require(frame->numChannels == 0); + Require(frame->speechType == 4); + Require(frame->vadActivity == 2); + Require(frame->profileTimestampMs == 0); + Require(frame->packetInfos == nullptr); + Require(frame->muted); + Require(frame->channelLayout == 0); + Require(!frame->absoluteCaptureTimestampMsEngaged); + Require(AllSamples(frame->data, 0x5A5A)); + Require(_ZNK6webrtc10AudioFrame5mutedEv_nid_postfix(frame.get())); +} + +void TestMutedData() { + auto frame = Filled(); + _ZN6webrtc10AudioFrameC1Ev_nid_postfix(frame.get()); + const std::int16_t* zeroed = _ZNK6webrtc10AudioFrame4dataEv_nid_postfix(frame.get()); + Require(zeroed != frame->data); + Require(AllSamples(zeroed, 0)); + Require(AllSamples(frame->data, 0x5A5A)); + + std::int16_t* samples = _ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(frame.get()); + Require(samples == frame->data); + Require(!frame->muted); + Require(AllSamples(frame->data, 0)); + Require(_ZNK6webrtc10AudioFrame4dataEv_nid_postfix(frame.get()) == frame->data); + + samples[0] = 1234; + samples[WebRtcAudioFrameMaxDataSizeSamples - 1] = -5; + Require(_ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(frame.get()) == frame->data); + Require(frame->data[0] == 1234); + Require(frame->data[WebRtcAudioFrameMaxDataSizeSamples - 1] == -5); +} + +void TestMute() { + auto frame = Filled(); + _ZN6webrtc10AudioFrameC1Ev_nid_postfix(frame.get()); + _ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(frame.get())[3] = 77; + Require(!_ZNK6webrtc10AudioFrame5mutedEv_nid_postfix(frame.get())); + _ZN6webrtc10AudioFrame4MuteEv_nid_postfix(frame.get()); + Require(_ZNK6webrtc10AudioFrame5mutedEv_nid_postfix(frame.get())); + Require(frame->data[3] == 77); + Require(AllSamples(_ZNK6webrtc10AudioFrame4dataEv_nid_postfix(frame.get()), 0)); + _ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(frame.get()); + Require(frame->data[3] == 0); +} + +void TestOperationsMute() { + auto frame = Filled(); + _ZN6webrtc10AudioFrameC1Ev_nid_postfix(frame.get()); + frame->samplesPerChannel = 480; + frame->numChannels = 2; + _ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(frame.get())[0] = 9; + _ZN6webrtc20AudioFrameOperations4MuteEPNS_10AudioFrameE_nid_postfix(frame.get()); + Require(frame->muted); + Require(frame->data[0] == 9); + Require(frame->samplesPerChannel == 480); + Require(frame->numChannels == 2); + + frame->muted = false; + frame->samplesPerChannel = 960; + frame->numChannels = 8; + _ZN6webrtc20AudioFrameOperations4MuteEPNS_10AudioFrameE_nid_postfix(frame.get()); + Require(frame->muted); + + frame->muted = false; + frame->samplesPerChannel = 7681; + frame->numChannels = 0; + _ZN6webrtc20AudioFrameOperations4MuteEPNS_10AudioFrameE_nid_postfix(frame.get()); + Require(frame->muted); + + frame->muted = false; + frame->samplesPerChannel = 961; + frame->numChannels = 8; + RequireThrows([&] { _ZN6webrtc20AudioFrameOperations4MuteEPNS_10AudioFrameE_nid_postfix(frame.get()); }); + Require(!frame->muted); +} + +void TestNullFrame() { + RequireThrows([] { _ZN6webrtc10AudioFrameC1Ev_nid_postfix(nullptr); }); + RequireThrows([] { _ZN6webrtc10AudioFrame4MuteEv_nid_postfix(nullptr); }); + RequireThrows([] { _ZNK6webrtc10AudioFrame5mutedEv_nid_postfix(nullptr); }); + RequireThrows([] { _ZNK6webrtc10AudioFrame4dataEv_nid_postfix(nullptr); }); + RequireThrows([] { _ZN6webrtc10AudioFrame12mutable_dataEv_nid_postfix(nullptr); }); + RequireThrows([] { _ZN6webrtc20AudioFrameOperations4MuteEPNS_10AudioFrameE_nid_postfix(nullptr); }); +} + +} + +int main() { + TestConstructor(); + TestMutedData(); + TestMute(); + TestOperationsMute(); + TestNullFrame(); + return 0; +} diff --git a/core/libs/prx/libwebrtc/tests/CpuFeatures.cpp b/core/libs/prx/libwebrtc/tests/CpuFeatures.cpp new file mode 100644 index 0000000000..b488e6ca66 --- /dev/null +++ b/core/libs/prx/libwebrtc/tests/CpuFeatures.cpp @@ -0,0 +1,28 @@ +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "libwebrtc GetAvailableCpuFeatures check failed at line %u\n", location.line()); + std::abort(); +} + +} + +int main() { + __builtin_cpu_init(); + WebRtcAvailableCpuFeatures features = _ZN6webrtc23GetAvailableCpuFeaturesEv_nid_postfix(); + Require(features.sse2); + Require(features.sse2 == (__builtin_cpu_supports("sse2") != 0)); + Require(features.avx2 == (__builtin_cpu_supports("avx2") != 0 && __builtin_cpu_supports("bmi2") != 0)); + Require(!features.neon); + WebRtcAvailableCpuFeatures again = _ZN6webrtc23GetAvailableCpuFeaturesEv_nid_postfix(); + Require(again.sse2 == features.sse2 && again.avx2 == features.avx2 && again.neon == features.neon); + std::printf("sse2=%d avx2=%d neon=%d\n", features.sse2, features.avx2, features.neon); + return 0; +} diff --git a/core/libs/prx/libwebrtc/tests/IpAddress.cpp b/core/libs/prx/libwebrtc/tests/IpAddress.cpp new file mode 100644 index 0000000000..237768f008 --- /dev/null +++ b/core/libs/prx/libwebrtc/tests/IpAddress.cpp @@ -0,0 +1,137 @@ +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "libwebrtc rtc::IPAddress check failed at line %u\n", location.line()); + std::abort(); +} + +template void RequireInvalidArgument(TAction action, std::source_location location = std::source_location::current()) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, location); +} + +WebRtcIpAddress Address(std::int32_t family, std::initializer_list bytes) { + WebRtcIpAddress address; + std::memset(&address, 0x5A, sizeof(address)); + address.vtable = &address; + address.family = family; + std::memset(&address.address, 0, sizeof(address.address)); + std::size_t index = 0; + for (std::uint8_t byte : bytes) + address.address.ip6.bytes[index++] = byte; + return address; +} + +WebRtcIpAddress V4(std::uint8_t a, std::uint8_t b, std::uint8_t c, std::uint8_t d) { + return Address(WebRtcAfInet, {a, b, c, d}); +} + +WebRtcIpAddress V6Prefix(int ones) { + WebRtcIpAddress address = Address(WebRtcAfInet6, {}); + for (int bit = 0; bit < ones; ++bit) + address.address.ip6.bytes[bit / 8] |= static_cast(0x80u >> (bit % 8)); + return address; +} + +bool Equal(const WebRtcIpAddress& left, const WebRtcIpAddress& right) { + return _ZNK3rtc9IPAddresseqERKS0__nid_postfix(&left, &right); +} + +int MaskBits(const WebRtcIpAddress& mask) { + return _ZN3rtc15CountIPMaskBitsERKNS_9IPAddressE_nid_postfix(&mask); +} + +void TestAccessors() { + WebRtcIpAddress v4 = V4(192, 168, 1, 10); + WebRtcInAddr ip4 = _ZNK3rtc9IPAddress12ipv4_addressEv_nid_postfix(&v4); + Require(std::memcmp(&ip4, v4.address.ip6.bytes, 4) == 0); + Require(_ZNK3rtc9IPAddress27v4AddressAsHostOrderIntegerEv_nid_postfix(&v4) == 0xC0A8010Au); + + WebRtcIpAddress v6 = Address(WebRtcAfInet6, {0x20, 0x01, 0x0D, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}); + WebRtcIn6Addr ip6 = _ZNK3rtc9IPAddress12ipv6_addressEv_nid_postfix(&v6); + Require(std::memcmp(ip6.bytes, v6.address.ip6.bytes, 16) == 0); + WebRtcInAddr v6AsV4 = _ZNK3rtc9IPAddress12ipv4_addressEv_nid_postfix(&v6); + Require(std::memcmp(&v6AsV4, v6.address.ip6.bytes, 4) == 0); + WebRtcIn6Addr v4AsV6 = _ZNK3rtc9IPAddress12ipv6_addressEv_nid_postfix(&v4); + Require(std::memcmp(v4AsV6.bytes, v4.address.ip6.bytes, 16) == 0); + Require(_ZNK3rtc9IPAddress27v4AddressAsHostOrderIntegerEv_nid_postfix(&v6) == 0); + + WebRtcIpAddress unspec = Address(WebRtcAfUnspec, {1, 2, 3, 4}); + Require(_ZNK3rtc9IPAddress27v4AddressAsHostOrderIntegerEv_nid_postfix(&unspec) == 0); +} + +void TestEquality() { + Require(Equal(V4(10, 0, 0, 1), V4(10, 0, 0, 1))); + Require(!Equal(V4(10, 0, 0, 1), V4(10, 0, 0, 2))); + + WebRtcIpAddress v4 = V4(10, 0, 0, 1); + WebRtcIpAddress v4Tail = V4(10, 0, 0, 1); + v4Tail.address.ip6.bytes[15] = 0xFF; + Require(Equal(v4, v4Tail)); + + WebRtcIpAddress v6 = Address(WebRtcAfInet6, {10, 0, 0, 1}); + Require(!Equal(v4, v6)); + Require(Equal(v6, Address(WebRtcAfInet6, {10, 0, 0, 1}))); + WebRtcIpAddress v6Tail = v6; + v6Tail.address.ip6.bytes[15] = 1; + Require(!Equal(v6, v6Tail)); + + Require(Equal(Address(WebRtcAfUnspec, {1}), Address(WebRtcAfUnspec, {2}))); + WebRtcIpAddress other = Address(1, {}); + Require(!Equal(other, other)); +} + +void TestMaskBits() { + Require(MaskBits(V4(255, 255, 255, 0)) == 24); + Require(MaskBits(V4(255, 255, 255, 255)) == 32); + Require(MaskBits(V4(128, 0, 0, 0)) == 1); + Require(MaskBits(V4(0, 0, 0, 0)) == 0); + Require(MaskBits(V4(255, 0, 255, 0)) == 24); + Require(MaskBits(V4(0, 0, 0, 1)) == 32); + + for (int ones : {0, 1, 31, 32, 33, 60, 64, 96, 127, 128}) + Require(MaskBits(V6Prefix(ones)) == ones); + WebRtcIpAddress gap = V6Prefix(64); + gap.address.ip6.bytes[9] = 0x01; + Require(MaskBits(gap) == 80); + WebRtcIpAddress lateBit = Address(WebRtcAfInet6, {0, 0, 0, 0, 0, 0, 0, 0x01}); + Require(MaskBits(lateBit) == 0); + + Require(MaskBits(Address(WebRtcAfUnspec, {255, 255, 255, 0})) == 0); + Require(MaskBits(Address(1, {255, 255, 255, 0})) == 0); +} + +void TestNullAddress() { + WebRtcIpAddress v4 = V4(10, 0, 0, 1); + RequireInvalidArgument([] { _ZNK3rtc9IPAddress12ipv4_addressEv_nid_postfix(nullptr); }); + RequireInvalidArgument([] { _ZNK3rtc9IPAddress12ipv6_addressEv_nid_postfix(nullptr); }); + RequireInvalidArgument([] { _ZNK3rtc9IPAddress27v4AddressAsHostOrderIntegerEv_nid_postfix(nullptr); }); + RequireInvalidArgument([&] { _ZNK3rtc9IPAddresseqERKS0__nid_postfix(nullptr, &v4); }); + RequireInvalidArgument([&] { _ZNK3rtc9IPAddresseqERKS0__nid_postfix(&v4, nullptr); }); + RequireInvalidArgument([] { _ZN3rtc15CountIPMaskBitsERKNS_9IPAddressE_nid_postfix(nullptr); }); +} + +} + +int main() { + TestAccessors(); + TestEquality(); + TestMaskBits(); + TestNullAddress(); + return 0; +} diff --git a/core/libs/prx/libwebrtc/tests/TaskQueueBase.cpp b/core/libs/prx/libwebrtc/tests/TaskQueueBase.cpp new file mode 100644 index 0000000000..dbad7cad58 --- /dev/null +++ b/core/libs/prx/libwebrtc/tests/TaskQueueBase.cpp @@ -0,0 +1,126 @@ +#include "prx/libwebrtc/libwebrtc.hpp" + +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "libwebrtc TaskQueueBase check failed at line %u\n", location.line()); + std::abort(); +} + +template void RequireInvalidArgument(TAction action, std::source_location location = std::source_location::current()) { + try { + action(); + } catch (const std::invalid_argument&) { + return; + } + Require(false, location); +} + +WebRtcTaskQueueBase* Queue(int index) { + static char storage[3]; + return reinterpret_cast(&storage[index]); +} + +WebRtcTaskQueueBase* Current() { + return _ZN6webrtc13TaskQueueBase7CurrentEv_nid_postfix(); +} + +void Set(WebRtcCurrentTaskQueueSetter& setter, WebRtcTaskQueueBase* queue) { + _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterC1EPS0__nid_postfix(&setter, queue); +} + +void Restore(WebRtcCurrentTaskQueueSetter& setter) { + _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterD1Ev_nid_postfix(&setter); +} + +void TestNoQueue() { + Require(Current() == nullptr); +} + +void TestSetAndRestore() { + WebRtcCurrentTaskQueueSetter setter {Queue(2)}; + Set(setter, Queue(0)); + Require(setter.previous == nullptr); + Require(Current() == Queue(0)); + Restore(setter); + Require(Current() == nullptr); +} + +void TestNested() { + WebRtcCurrentTaskQueueSetter outer {}; + WebRtcCurrentTaskQueueSetter inner {}; + WebRtcCurrentTaskQueueSetter cleared {}; + Set(outer, Queue(0)); + Set(inner, Queue(1)); + Require(inner.previous == Queue(0)); + Require(Current() == Queue(1)); + Set(cleared, nullptr); + Require(cleared.previous == Queue(1)); + Require(Current() == nullptr); + Restore(cleared); + Require(Current() == Queue(1)); + Restore(inner); + Require(Current() == Queue(0)); + Restore(outer); + Require(Current() == nullptr); +} + +void TestRestoreUsesTheSetter() { + WebRtcCurrentTaskQueueSetter setter {}; + Set(setter, Queue(0)); + setter.previous = Queue(2); + Restore(setter); + Require(Current() == Queue(2)); + WebRtcCurrentTaskQueueSetter reset {nullptr}; + Restore(reset); + Require(Current() == nullptr); +} + +void TestPerThread() { + WebRtcCurrentTaskQueueSetter setter {}; + Set(setter, Queue(0)); + WebRtcTaskQueueBase* seenBefore = Queue(2); + WebRtcTaskQueueBase* seenWhileSet = nullptr; + WebRtcTaskQueueBase* seenAfter = Queue(2); + std::thread worker([&] { + seenBefore = Current(); + WebRtcCurrentTaskQueueSetter workerSetter {}; + Set(workerSetter, Queue(1)); + seenWhileSet = Current(); + Restore(workerSetter); + seenAfter = Current(); + }); + worker.join(); + Require(seenBefore == nullptr); + Require(seenWhileSet == Queue(1)); + Require(seenAfter == nullptr); + Require(Current() == Queue(0)); + Restore(setter); + Require(Current() == nullptr); +} + +void TestNullSetter() { + RequireInvalidArgument([] { _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterC1EPS0__nid_postfix(nullptr, Queue(0)); }); + Require(Current() == nullptr); + RequireInvalidArgument([] { _ZN6webrtc13TaskQueueBase22CurrentTaskQueueSetterD1Ev_nid_postfix(nullptr); }); + Require(Current() == nullptr); +} + +} + +int main() { + TestNoQueue(); + TestSetAndRestore(); + TestNested(); + TestRestoreUsesTheSetter(); + TestPerThread(); + TestNullSetter(); + return 0; +} diff --git a/core/libs/tests/AvPlayerHttpModules.cmake b/core/libs/tests/AvPlayerHttpModules.cmake new file mode 100644 index 0000000000..2eea029e12 --- /dev/null +++ b/core/libs/tests/AvPlayerHttpModules.cmake @@ -0,0 +1,199 @@ +cmake_policy(SET CMP0054 NEW) + +set(positional) +set(afterSeparator FALSE) +math(EXPR lastArgument "${CMAKE_ARGC} - 1") +foreach(index RANGE 0 ${lastArgument}) + if(afterSeparator) + list(APPEND positional "${CMAKE_ARGV${index}}") + elseif("${CMAKE_ARGV${index}}" STREQUAL "--") + set(afterSeparator TRUE) + endif() +endforeach() +list(LENGTH positional positionalCount) +if(NOT positionalCount EQUAL 3) + message(FATAL_ERROR "Expected owner, HTTP client and AvPlayer test executables after --") +endif() +list(GET positional 0 OWNER_TEST) +list(GET positional 1 CLIENT_TEST) +list(GET positional 2 PLAYER_TEST) + +foreach(required PYTHON SERVER PATCHER OWNER_TEST CLIENT_TEST PLAYER_TEST LIBC KERNEL INTERNAL HTTP AVPLAYER NATIVE OBJDUMP WORK_DIR) + if(NOT DEFINED ${required} OR "${${required}}" STREQUAL "") + message(FATAL_ERROR "Missing ${required}") + endif() +endforeach() + +set(httpNids Xa86mkDT770 FcAd67QjrVw T+Un3eukNLs mh7PrvIiVtk WN6ypk6qEnU T8if7aGBDYs 7EfcZqAehgI M3Qx8MdigHk) +string(RANDOM LENGTH 16 ALPHABET abcdef0123456789 stageId) +set(stage "${WORK_DIR}/run-${stageId}") +file(MAKE_DIRECTORY "${stage}") +execute_process(COMMAND "${PATCHER}" --write-export-list "${LIBC}" "${stage}/libc.exports" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) +if(NOT status STREQUAL "0") + message(FATAL_ERROR "Normal libc export listing failed: ${output}${error}") +endif() +message("${output}${error}") +set(moduleNames libc libkernel libSceLibcInternal libSceHttp libSceAvPlayer libSceAvPlayer.native) +set(moduleVariables LIBC KERNEL INTERNAL HTTP AVPLAYER NATIVE) +foreach(index RANGE 0 5) + list(GET moduleNames ${index} module) + list(GET moduleVariables ${index} variable) + file(COPY_FILE "${${variable}}" "${stage}/${module}.prx") + if(module STREQUAL "libc") + execute_process(COMMAND "${PATCHER}" "${module}" "${stage}/${module}.prx" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) + else() + execute_process(COMMAND "${PATCHER}" "${module}" --preserve-export-list "${stage}/libc.exports" "${stage}/${module}.prx" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) + endif() + if(NOT status STREQUAL "0") + message(FATAL_ERROR "Normal ${module} patching failed: ${output}${error}") + endif() + message("${output}${error}") +endforeach() +if(WIN32 AND DEFINED ALLOCATOR_DIRECTORY) + foreach(allocator mimalloc.dll mimalloc-redirect.dll) + file(COPY_FILE "${ALLOCATOR_DIRECTORY}/${allocator}" "${stage}/${allocator}") + endforeach() + execute_process(COMMAND "${IMPORT_FIRST}" "${stage}/mimalloc.dll" mimalloc-redirect.dll + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) + if(NOT status STREQUAL "0") + message(FATAL_ERROR "Normal allocator import ordering failed: ${output}${error}") + endif() + message("${output}${error}") +endif() + +function(read_module file prefix) + execute_process(COMMAND "${OBJDUMP}" -p "${file}" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) + if(NOT status STREQUAL "0") + message(FATAL_ERROR "Module import inspection failed: ${output}${error}") + endif() + set(${prefix}_header "${output}" PARENT_SCOPE) + if(WIN32) + string(FIND "${output}" "[Ordinal/Name Pointer] Table" exportsAt) + if(exportsAt EQUAL -1) + message(FATAL_ERROR "Missing PE export table in ${file}") + endif() + string(SUBSTRING "${output}" ${exportsAt} -1 exports) + set(${prefix}_exports "${exports}" PARENT_SCOPE) + else() + execute_process(COMMAND "${OBJDUMP}" -T "${file}" + RESULT_VARIABLE status OUTPUT_VARIABLE symbols ERROR_VARIABLE error) + if(NOT status STREQUAL "0") + message(FATAL_ERROR "Module symbol inspection failed: ${symbols}${error}") + endif() + set(${prefix}_symbols "${symbols}" PARENT_SCOPE) + endif() +endfunction() + +read_module("${stage}/libSceHttp.prx" http) +foreach(module libSceAvPlayer libSceAvPlayer.native) + read_module("${stage}/${module}.prx" player) + if(WIN32) + set(httpImports) + set(inHttp FALSE) + string(REPLACE "\n" ";" importLines "${player_header}") + foreach(line IN LISTS importLines) + string(STRIP "${line}" line) + if(line MATCHES "^DLL Name: (.+)$") + if(CMAKE_MATCH_1 STREQUAL "libSceHttp.prx") + set(inHttp TRUE) + else() + set(inHttp FALSE) + endif() + elseif(line MATCHES "^The Export Tables") + set(inHttp FALSE) + elseif(inHttp) + string(APPEND httpImports "${line}\n") + endif() + endforeach() + foreach(nid IN LISTS httpNids) + string(FIND "${httpImports}" "${nid}" importAt) + string(FIND "${http_exports}" "${nid}" providerAt) + string(FIND "${player_exports}" "${nid}" playerAt) + if(importAt EQUAL -1 OR providerAt EQUAL -1 OR NOT playerAt EQUAL -1) + message(FATAL_ERROR "Incorrect patched HTTP import/provider ${nid} in ${module}") + endif() + endforeach() + else() + if(NOT player_header MATCHES "NEEDED[ \t]+libSceHttp\\.prx") + message(FATAL_ERROR "${module} has no dynamic libSceHttp dependency") + endif() + string(REPLACE "\n" ";" playerLines "${player_symbols}") + string(REPLACE "\n" ";" providerLines "${http_symbols}") + foreach(nid IN LISTS httpNids) + set(importFound FALSE) + set(providerFound FALSE) + foreach(line IN LISTS playerLines) + string(FIND "${line}" "${nid}" nameAt) + if(NOT nameAt EQUAL -1) + string(FIND "${line}" "*UND*" undefinedAt) + if(undefinedAt EQUAL -1) + message(FATAL_ERROR "HTTP helper ${nid} is defined in ${module}") + endif() + set(importFound TRUE) + endif() + endforeach() + foreach(line IN LISTS providerLines) + string(FIND "${line}" "${nid}" nameAt) + string(FIND "${line}" "*UND*" undefinedAt) + if(NOT nameAt EQUAL -1 AND undefinedAt EQUAL -1) + set(providerFound TRUE) + endif() + endforeach() + if(NOT importFound OR NOT providerFound) + message(FATAL_ERROR "Incorrect patched HTTP import/provider ${nid} in ${module}") + endif() + endforeach() + endif() +endforeach() + +foreach(kind OWNER CLIENT PLAYER) + get_filename_component(name "${${kind}_TEST}" NAME) + file(COPY_FILE "${${kind}_TEST}" "${stage}/${name}") + set(${kind}_COPY "${stage}/${name}") + if(NOT kind STREQUAL "OWNER") + execute_process(COMMAND "${PATCHER}" --exec --only-sce "${stage}/${name}" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) + if(NOT status STREQUAL "0") + message(FATAL_ERROR "Test import patching failed: ${output}${error}") + endif() + endif() +endforeach() + +if(WIN32) + set(libraryEnvironment "PATH=${stage};$ENV{PATH}") +else() + set(libraryEnvironment "LD_LIBRARY_PATH=${stage}:$ENV{LD_LIBRARY_PATH}") +endif() + +foreach(module libSceAvPlayer libSceAvPlayer.native) + execute_process(COMMAND "${CMAKE_COMMAND}" -E env "${libraryEnvironment}" + "${OWNER_COPY}" "${stage}" "${module}.prx" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) + if(NOT status STREQUAL "0") + message(FATAL_ERROR "Patched module ownership failed: ${output}${error}") + endif() + message("${output}${error}") +endforeach() + +file(MAKE_DIRECTORY "${stage}/client" "${stage}/player") +execute_process(COMMAND "${CMAKE_COMMAND}" -E env "${libraryEnvironment}" + "${PYTHON}" "${SERVER}" "${CLIENT_COPY}" client + WORKING_DIRECTORY "${stage}/client" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) +if(NOT status STREQUAL "0") + message(FATAL_ERROR "Patched HTTP client behavior failed: ${output}${error}") +endif() +message("${output}${error}") +execute_process(COMMAND "${CMAKE_COMMAND}" -E env "${libraryEnvironment}" + "${PYTHON}" "${SERVER}" "${PLAYER_COPY}" + WORKING_DIRECTORY "${stage}/player" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) +if(NOT status STREQUAL "0") + message(FATAL_ERROR "Patched AvPlayer HTTP behavior failed: ${output}${error}") +endif() +message("${output}${error}") +message(STATUS "Patched HTTP stage ${stage}") diff --git a/core/libs/tests/AvPlayerHttpModules.cpp b/core/libs/tests/AvPlayerHttpModules.cpp new file mode 100644 index 0000000000..9c4d1f99c6 --- /dev/null +++ b/core/libs/tests/AvPlayerHttpModules.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif + +namespace { + +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +struct Import { + const char* name; + const char* nid; +}; + +constexpr std::array imports{{ + {"Aps5HttpClient_Create", "Xa86mkDT770"}, + {"Aps5HttpClient_Destroy", "FcAd67QjrVw"}, + {"Aps5HttpClient_Open", "T+Un3eukNLs"}, + {"Aps5HttpClient_Read", "mh7PrvIiVtk"}, + {"Aps5HttpClient_Seek", "WN6ypk6qEnU"}, + {"Aps5HttpClient_GetSize", "T8if7aGBDYs"}, + {"Aps5HttpClient_Start", "7EfcZqAehgI"}, + {"Aps5HttpClient_Stop", "M3Qx8MdigHk"} +}}; + +std::string Utf8(const std::filesystem::path& path) { + const auto bytes = path.u8string(); + return {reinterpret_cast(bytes.data()), bytes.size()}; +} + +#ifdef _WIN32 +std::filesystem::path ModulePath(HMODULE module) { + std::wstring buffer(1024, L'\0'); + for (;;) { + const auto size = GetModuleFileNameW(module, buffer.data(), static_cast(buffer.size())); + Require(size != 0, "GetModuleFileNameW failed"); + if (size < buffer.size()) { + buffer.resize(size); + return std::filesystem::canonical(buffer); + } + Require(buffer.size() < 32768, "module path is too long"); + buffer.resize(buffer.size() * 2); + } +} +#endif + +} + +int main(int argc, char** argv) { + try { + Require(argc == 3, "expected module directory and AvPlayer module name"); + const auto directory = std::filesystem::canonical(std::filesystem::u8path(argv[1])); + const auto player = directory / std::filesystem::u8path(argv[2]); + const auto expected = std::filesystem::canonical(directory / "libSceHttp.prx"); +#ifdef _WIN32 + const auto consumer = LoadLibraryW(player.c_str()); + Require(consumer != nullptr, "loading patched AvPlayer failed"); + const auto provider = GetModuleHandleW(L"libSceHttp.prx"); + Require(provider != nullptr && ModulePath(provider) == expected, "AvPlayer did not load the staged HTTP provider"); + for (const auto& entry : imports) { + const auto address = GetProcAddress(provider, entry.nid); + Require(address != nullptr, "patched HTTP helper is missing"); + Require(GetProcAddress(consumer, entry.nid) == nullptr, "HTTP helper was defined in AvPlayer"); + HMODULE owner{}; + Require(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(address), &owner) != 0, "HTTP helper owner lookup failed"); + Require(owner == provider && ModulePath(owner) == expected, "HTTP helper resolved outside libSceHttp"); + std::printf("HTTP provider %s %s %s\n", entry.name, entry.nid, Utf8(expected).c_str()); + } +#else + const auto consumer = dlopen(player.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!consumer) throw std::runtime_error(dlerror()); + const auto provider = dlopen(expected.c_str(), RTLD_NOW | RTLD_NOLOAD); + Require(provider != nullptr, "AvPlayer did not load libSceHttp as a dependency"); + for (const auto& entry : imports) { + const auto address = dlsym(consumer, entry.nid); + Require(address != nullptr && address == dlsym(provider, entry.nid), "AvPlayer HTTP helper lookup failed"); + Dl_info owner{}; + Require(dladdr(address, &owner) != 0 && owner.dli_fname != nullptr, "HTTP helper owner lookup failed"); + Require(std::filesystem::canonical(owner.dli_fname) == expected, "HTTP helper resolved outside libSceHttp"); + std::printf("HTTP provider %s %s %s\n", entry.name, entry.nid, Utf8(expected).c_str()); + } +#endif + std::printf("patched %s imports all HTTP helpers from libSceHttp\n", argv[2]); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "patched HTTP modules: %s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/AvPlayerHttpServer.py b/core/libs/tests/AvPlayerHttpServer.py new file mode 100644 index 0000000000..6d91ad2a28 --- /dev/null +++ b/core/libs/tests/AvPlayerHttpServer.py @@ -0,0 +1,119 @@ +import http.server +import pathlib +import re +import subprocess +import sys +import threading +import time +import urllib.parse + + +requests = [] +targets = [] +failures = [] +stalled = {"read": threading.Event(), "open": threading.Event()} + + +class Server(http.server.ThreadingHTTPServer): + def handle_error(self, request, client_address): + failures.append(repr(sys.exc_info()[1])) + super().handle_error(request, client_address) + + +class Handler(http.server.BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + + def log_message(self, *args): + pass + + def do_GET(self): + try: + self.respond() + except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError): + pass + + def respond(self): + path = urllib.parse.urlsplit(self.path).path + requests.append((path, self.headers.get("Range"))) + targets.append(self.path) + if path.startswith("/sync/"): + ready = stalled[path.removeprefix("/sync/")].wait(2) + self.send_response(200 if ready else 500) + self.send_header("Content-Length", "5") + self.send_header("Connection", "close") + self.end_headers() + self.wfile.write(b"ready" if ready else b"error") + return + if path.startswith("/redirect/") or path.startswith("/client-redirect/"): + self.send_response(307) + name = "payload.bin" if path.startswith("/client-redirect/") else "avplayer.mp4" + self.send_header("Location", f"../range/{name}?redirected=1") + self.send_header("Content-Length", "0") + self.end_headers() + return + content = pathlib.Path("app0", pathlib.PurePosixPath(path).name).read_bytes() + match = re.fullmatch(r"bytes=(\d+)-(\d+)", self.headers.get("Range", "")) + assert match is not None + start, end = map(int, match.groups()) + if path.startswith("/stall/") and start: + stalled["read"].set() + time.sleep(2) + if path.startswith("/stall-open/"): + stalled["open"].set() + time.sleep(2) + full = path.startswith(("/ignore/", "/full/", "/full-chunk/", "/full-close/")) + if full: + start, end = 0, len(content) - 1 + self.send_response(200) + else: + self.send_response(206) + end = min(end, start + 511, len(content) - 1) + first = start + 1 if path.startswith("/bad/") or path.startswith("/bad-late/") and start else start + self.send_header("Content-Range", f"bytes {first}-{end}/{len(content)}") + if start: + expected = "Wed, 01 Jan 2020 00:00:00 GMT" if path.startswith("/modified/") else '"fixture-v1"' + assert self.headers.get("If-Range") == expected + if path.startswith("/modified/"): + self.send_header("Last-Modified", "Wed, 01 Jan 2020 00:00:00 GMT") + else: + self.send_header("ETag", '"fixture-v2"' if path.startswith("/changed/") and start else '"fixture-v1"') + body = content[start:end + 1] + chunked = path.startswith(("/chunk/", "/full-chunk/", "/chunk-bad/")) + if chunked: + self.send_header("Transfer-Encoding", "chunked") + elif not path.startswith("/full-close/"): + self.send_header("Content-Length", str(len(body) + int(path.startswith("/short/")))) + self.send_header("Connection", "close") + self.end_headers() + if path.startswith("/chunk-bad/"): + self.wfile.write(b"ZZ\r\n") + elif chunked: + for at in range(0, len(body), 19): + chunk = body[at:at + 19] + self.wfile.write(f"{len(chunk):X} ;test=1\r\n".encode() + chunk + b"\r\n") + self.wfile.write(b"0\r\nX-Trailer: yes\r\n\r\n") + else: + self.wfile.write(body) + + +with Server(("127.0.0.1", 0), Handler) as server: + thread = threading.Thread(target=server.serve_forever, daemon=True) + thread.start() + mode = sys.argv[2] if len(sys.argv) > 2 else "avplayer" + arguments = [sys.argv[1], f"http://127.0.0.1:{server.server_port}", *sys.argv[3:]] if mode != "avplayer" else [sys.argv[1], "--http", f"http://127.0.0.1:{server.server_port}"] + result = subprocess.run(arguments) + server.shutdown() + thread.join() + if result.returncode: + sys.exit(result.returncode) + assert not failures, failures + assert any(header is not None and not header.startswith("bytes=0-") for _, header in requests) + if mode == "avplayer": + assert {"/range/avplayer.mp4", "/chunk/avplayer.webm", "/ignore/avplayer.mp4", "/redirect/avplayer.mp4"}.issubset({path for path, _ in requests}) + if mode == "client": + expected = {"/range/payload.bin", "/range/alternate.bin", "/full/payload.bin", "/full-chunk/payload.bin", "/full-close/payload.bin", "/client-redirect/payload.bin", "/changed/payload.bin", "/modified/payload.bin", "/bad/payload.bin", "/short/payload.bin", "/chunk-bad/payload.bin", "/stall/payload.bin", "/stall-open/payload.bin", "/sync/read", "/sync/open"} + assert expected.issubset({path for path, _ in requests}) + assert sum(path == "/cache/payload.bin" for path, _ in requests) == 1 + assert any(target == "/range/payload.bin?redirected=1" for target in targets) + assert any(target == "/range/payload.bin?token=direct" for target in targets) + assert all("#" not in target for target in targets) diff --git a/core/libs/tests/AvPlayerHttpTransport.cpp b/core/libs/tests/AvPlayerHttpTransport.cpp new file mode 100644 index 0000000000..eec101edc7 --- /dev/null +++ b/core/libs/tests/AvPlayerHttpTransport.cpp @@ -0,0 +1,74 @@ +#include "prx/libSceAvPlayer/include/HttpStream.hpp" +#include "prx/libSceHttp/libSceHttp.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "AvPlayer HTTP transport: %s\n", message); + std::abort(); +} + +int main(int argc, char** argv) { + Require(argc == 2, "missing server URL"); + std::filesystem::create_directories("app0"); + { + std::ofstream file("app0/payload.bin", std::ios::binary); + for (int byte = 0; byte < 100000; ++byte) file.put(static_cast(byte % 251)); + } + { + AvPlayer::HttpStream invalid; + auto url = std::string(argv[1]) + "/range/payload.bin"; + url.push_back('\0'); + url += "suffix"; + Require(!invalid.Open(url), "embedded NUL URL was truncated into a valid source"); + } + AvPlayer::HttpStream stream; + Require(stream.Open(std::string(argv[1]) + "/stall/payload.bin"), "opening range source failed"); + Require(avio_size(stream.Context()) == 100000, "range source length is incorrect"); + Require(avio_seek(stream.Context(), 90000, SEEK_SET) == 90000, "seeking range source failed"); + std::array bytes{}; + auto read = std::async(std::launch::async, [&] { return avio_read(stream.Context(), bytes.data(), bytes.size()); }); + { + std::unique_ptr observer(Aps5HttpClient_Create_nid_postfix(), Aps5HttpClient_Destroy_nid_postfix); + Require(observer != nullptr && Aps5HttpClient_Open_nid_postfix(observer.get(), (std::string(argv[1]) + "/sync/read").c_str()) == 1, "server did not observe the stalled read"); + } + Require(read.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout, "stalled source did not block"); + stream.Stop(); + Require(read.wait_for(std::chrono::seconds(1)) == std::future_status::ready && read.get() < 0, "stop did not cancel a stalled read"); + bool cancelled = false; + try { stream.Rethrow(); } catch (const std::exception&) { cancelled = true; } + Require(cancelled, "cancelled callback failure was not retained"); + stream.Start(); + stream.Rethrow(); + Require(avio_seek(stream.Context(), 0, SEEK_SET) == 0, "restart seek failed"); + Require(avio_read(stream.Context(), bytes.data(), bytes.size()) == bytes.size(), "restart read failed"); + for (std::size_t byte = 0; byte < bytes.size(); ++byte) Require(bytes[byte] == byte, "restart returned incorrect bytes"); + AvPlayer::HttpStream changed; + Require(changed.Open(std::string(argv[1]) + "/changed/payload.bin"), "opening validator source failed"); + Require(avio_seek(changed.Context(), 90000, SEEK_SET) == 90000, "validator seek failed"); + Require(avio_read(changed.Context(), bytes.data(), bytes.size()) < 0, "changed representation was mixed into cache"); + bool threw = false; + try { changed.Rethrow(); } catch (...) { threw = true; } + Require(threw, "callback failure was not retained"); + AvPlayer::HttpStream modified; + Require(modified.Open(std::string(argv[1]) + "/modified/payload.bin"), "opening Last-Modified source failed"); + Require(avio_seek(modified.Context(), 90000, SEEK_SET) == 90000, "Last-Modified seek failed"); + Require(avio_read(modified.Context(), bytes.data(), bytes.size()) == bytes.size(), "Last-Modified If-Range read failed"); + for (std::size_t byte = 0; byte < bytes.size(); ++byte) Require(bytes[byte] == (90000 + byte) % 251, "Last-Modified range returned incorrect bytes"); + Require(modified.Open(std::string(argv[1]) + "/range/payload.bin"), "reopening the AVIO adapter failed"); + Require(avio_read(modified.Context(), bytes.data(), bytes.size()) == bytes.size(), "reopened adapter read failed"); + for (std::size_t byte = 0; byte < bytes.size(); ++byte) Require(bytes[byte] == byte, "reopened adapter retained its old position"); +} diff --git a/core/libs/tests/EdgeWatch.cpp b/core/libs/tests/EdgeWatch.cpp new file mode 100644 index 0000000000..d22f5f8a72 --- /dev/null +++ b/core/libs/tests/EdgeWatch.cpp @@ -0,0 +1,174 @@ +#include "prx/libkernel/Equeue/include/EdgeWatch.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" [[noreturn]] void NotImplemented_nid_no_patch(const char* name) { + throw std::runtime_error(name); +} + +namespace { + +void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +struct Pair { + explicit Pair(bool socket) { + Require((socket ? ::socketpair(AF_UNIX, SOCK_STREAM, 0, descriptors.data()) : ::pipe(descriptors.data())) == 0, "cannot create descriptor pair"); + } + ~Pair() { + for (int descriptor : descriptors) if (descriptor >= 0) ::close(descriptor); + } + std::array descriptors{-1, -1}; +}; + +bool Wait(int descriptor, int milliseconds) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(milliseconds); + for (;;) { + const auto remaining = std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()).count(); + if (remaining < 0) return false; + pollfd entry{descriptor, POLLIN, 0}; + const int result = ::poll(&entry, 1, static_cast(remaining + 1)); + if (result < 0 && errno == EINTR) continue; + Require(result >= 0, "poll failed"); + Require((entry.revents & (POLLERR | POLLNVAL | POLLHUP)) == 0, "watch descriptor failed"); + return (entry.revents & POLLIN) != 0; + } +} + +void Edge(const std::shared_ptr& watch) { + Require(Wait(watch->Descriptor(), 5000), "edge did not wake the watch descriptor"); + Require(watch->TakeEdge(), "readable watch did not yield an edge"); +} + +void Quiet(const std::shared_ptr& watch) { + Require(!watch->TakeEdge(), "edge repeated without new activity"); + Require(!Wait(watch->Descriptor(), 50), "idle watch descriptor remained readable"); +} + +void Send(int descriptor, std::size_t count) { + std::array bytes{}; + Require(::write(descriptor, bytes.data(), count) == static_cast(count), "write failed"); +} + +void Read(int descriptor, std::size_t count) { + std::array bytes{}; + Require(::read(descriptor, bytes.data(), count) == static_cast(count), "read failed"); +} + +void ReadEdges(bool socket) { + Pair pair(socket); + auto watch = EdgeWatch::Watch::Open(pair.descriptors[0], false); + Quiet(watch); + Send(pair.descriptors[1], 2); + Edge(watch); + Quiet(watch); + Send(pair.descriptors[1], 1); + Edge(watch); + Quiet(watch); + Read(pair.descriptors[0], 3); + Quiet(watch); + std::promise entered; + auto started = entered.get_future(); + auto waiter = std::async(std::launch::async, [&] { + entered.set_value(); + return Wait(watch->Descriptor(), 5000); + }); + started.wait(); + const bool blocked = waiter.wait_for(std::chrono::milliseconds(100)) == std::future_status::timeout; + Send(pair.descriptors[1], 1); + const bool woke = waiter.get(); + Require(blocked, "blocking wait returned before new activity"); + Require(woke, "blocking wait did not wake after new activity"); + Require(watch->TakeEdge(), "blocking wait lost the edge"); + Require(::close(pair.descriptors[1]) == 0, "close writer failed"); + pair.descriptors[1] = -1; + Edge(watch); + Quiet(watch); + Read(pair.descriptors[0], 1); + watch->TakeEdge(); + char byte; + Require(::read(pair.descriptors[0], &byte, 1) == 0, "read did not report EOF"); + watch->TakeEdge(); + Quiet(watch); +} + +void WriteEdges() { + Pair pair(true); + Require(::fcntl(pair.descriptors[1], F_SETFL, O_NONBLOCK) == 0, "cannot make socket nonblocking"); + auto watch = EdgeWatch::Watch::Open(pair.descriptors[1], true); + Edge(watch); + Quiet(watch); + std::array bytes{}; + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + ssize_t result; + do { + result = ::write(pair.descriptors[1], bytes.data(), bytes.size()); + Require(std::chrono::steady_clock::now() < deadline, "socket did not fill"); + } while (result >= 0 || errno == EINTR); + Require(errno == EAGAIN || errno == EWOULDBLOCK, "socket fill failed"); + while (watch->TakeEdge()) Require(std::chrono::steady_clock::now() < deadline, "write edge did not clear"); + Quiet(watch); + Require(::fcntl(pair.descriptors[0], F_SETFL, O_NONBLOCK) == 0, "cannot make reader nonblocking"); + do { + result = ::read(pair.descriptors[0], bytes.data(), bytes.size()); + Require(std::chrono::steady_clock::now() < deadline, "socket did not drain"); + } while (result > 0 || (result < 0 && errno == EINTR)); + Require(result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK), "socket drain failed"); + Edge(watch); + Quiet(watch); +} + +void Lifecycle() { + Pair pair(false); + int descriptor; + { + auto watch = EdgeWatch::Watch::Open(pair.descriptors[0], false); + descriptor = watch->Descriptor(); + const int flags = ::fcntl(descriptor, F_GETFD); + Require(flags >= 0 && (flags & FD_CLOEXEC) != 0, "watch lacks close-on-exec"); + } + Require(::fcntl(descriptor, F_GETFD) == -1 && errno == EBADF, "watch descriptor leaked"); + Send(pair.descriptors[1], 1); + Read(pair.descriptors[0], 1); +} + +void Errors() { + bool threw = false; + try { + EdgeWatch::Watch::Open(-1, false); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "invalid descriptor was accepted"); +} + +} + +int main(int argc, char** argv) { + try { + Require(argc == 2, "expected test mode"); + const std::string_view mode(argv[1]); + if (mode == "socket") ReadEdges(true); + else if (mode == "pipe") ReadEdges(false); + else if (mode == "write") WriteEdges(); + else if (mode == "lifecycle") Lifecycle(); + else if (mode == "errors") Errors(); + else throw std::runtime_error("unknown test mode"); + std::printf("PASS: %s\n", argv[1]); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "FAIL: %s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/ExportLookup.hpp b/core/libs/tests/ExportLookup.hpp new file mode 100644 index 0000000000..235b066e32 --- /dev/null +++ b/core/libs/tests/ExportLookup.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +inline bool LibraryExportsName(void* library, const char* name) { +#ifdef _WIN32 + return GetProcAddress(static_cast(library), name) != nullptr; +#else + return dlsym(library, name) != nullptr; +#endif +} + +template +bool ExportsResolveByName(const char* path, const Export (&exports)[Count], const char* absentName) { + if (path == nullptr) { + std::fprintf(stderr, "no library path was given\n"); + return false; + } +#ifdef _WIN32 + void* library = LoadLibraryA(path); +#else + void* library = dlopen(path, RTLD_NOW | RTLD_LOCAL); +#endif + if (library == nullptr) { + std::fprintf(stderr, "%s did not load\n", path); + return false; + } + for (const auto& entry : exports) { + auto* volatile function = entry.function; + if (function == nullptr || !LibraryExportsName(library, entry.name)) { + std::fprintf(stderr, "%s is not exported\n", entry.name); + return false; + } + } + if (LibraryExportsName(library, absentName)) { + std::fprintf(stderr, "%s resolved although nothing exports it\n", absentName); + return false; + } + return true; +} diff --git a/core/libs/tests/FilePosition.cpp b/core/libs/tests/FilePosition.cpp index 50fcb3006c..57268ea049 100644 --- a/core/libs/tests/FilePosition.cpp +++ b/core/libs/tests/FilePosition.cpp @@ -49,6 +49,23 @@ int main() { Require(fgetpos_nid_postfix(&file, &position) == 0 && position == 1); file.Close(); + FileStream reread(std::tmpfile()); + Require(std::fputs("position", reread.GetHandle()) >= 0); + Require(fsetpos_nid_postfix(&reread, &beginning) == 0); + for (const char expected : {'p', 'o', 's'}) Require(fgetc_nid_postfix(&reread) == expected); + Require(fgetpos_nid_postfix(&reread, &position) == 0 && position == 3); + const std::int64_t third = position; + while (fgetc_nid_postfix(&reread) != EOF) {} + Require(feof_nid_postfix(&reread) != 0); + Require(fsetpos_nid_postfix(&reread, &third) == 0); + Require(feof_nid_postfix(&reread) == 0); + Require(fgetc_nid_postfix(&reread) == 'i'); + const std::int64_t negative = -1; + errno = 0; + Require(fsetpos_nid_postfix(&reread, &negative) != 0); + Require(errno == EINVAL); + reread.Close(); + int descriptors[2]; #ifdef _WIN32 Require(_pipe(descriptors, 4096, _O_BINARY) == 0); diff --git a/core/libs/tests/FileStreams.cpp b/core/libs/tests/FileStreams.cpp index 8a0603b2e0..451e092682 100644 --- a/core/libs/tests/FileStreams.cpp +++ b/core/libs/tests/FileStreams.cpp @@ -400,6 +400,35 @@ static void CheckWriteAfterRead(const std::string& path, const char* mode, const Require(ReadWhole(path) == expected, "Write after read wrote other bytes"); } +static void CheckReadsOnWriteOnlyStreams(const std::string& path) { + for (const char* mode : {"wb", "ab"}) { + auto* stream = fopen_nid_postfix(path.c_str(), mode); + Require(stream != nullptr && fwrite_nid_postfix("abc", 1, 3, stream) == 3, "fwrite on a write-only stream"); + const auto failed = [stream](bool result, const char* message) { + Require(result && errno == EBADF && ferror_nid_postfix(stream) != 0, message); + clearerr_nid_postfix(stream); + errno = 0; + }; + std::array buffer{}; + errno = 0; + failed(fread_nid_postfix(buffer.data(), 1, buffer.size(), stream) == 0, "fread on a write-only stream"); + failed(fgetc_nid_postfix(stream) == EOF, "fgetc on a write-only stream"); + failed(getc_unlocked_nid_postfix(stream) == EOF, "getc_unlocked on a write-only stream"); + failed(fgets_nid_postfix(buffer.data(), static_cast(buffer.size()), stream) == nullptr, "fgets on a write-only stream"); + int value = 0; + Require(fscanf_nid_postfix(stream, "%d", &value) == EOF, "fscanf on a write-only stream"); +#ifdef _WIN32 + Require(errno == EBADF && ferror_nid_postfix(stream) != 0, "fscanf on a write-only stream left errno or the error indicator unset"); + clearerr_nid_postfix(stream); + errno = 0; + Require(ungetc_nid_postfix('q', stream) == EOF && errno == 0 && ferror_nid_postfix(stream) == 0, "ungetc on a write-only stream"); +#endif + Require(fclose_nid_postfix(stream) == 0, "fclose after reads on a write-only stream"); + Require(ReadWhole(path) == "abc", "Reads on a write-only stream changed the file"); + Require(std::remove(path.c_str()) == 0); + } +} + static void CheckStreamWritesAfterReads(const std::string& base) { CheckCharacterScanLengths(); const std::string path = base + ".written"; @@ -455,6 +484,7 @@ static void CheckStreamWritesAfterReads(const std::string& base) { errno = 0; Require(fprintf_nid_postfix(stream, "%s", "XY") == -1 && errno == EBADF && ferror_nid_postfix(stream) != 0, "fprintf after fgetc on a read-only stream"); }); + CheckReadsOnWriteOnlyStreams(base + ".write-only"); Require(std::remove(path.c_str()) == 0); } diff --git a/core/libs/tests/FontRenderSurface.cpp b/core/libs/tests/FontRenderSurface.cpp new file mode 100644 index 0000000000..6ce96ca853 --- /dev/null +++ b/core/libs/tests/FontRenderSurface.cpp @@ -0,0 +1,264 @@ +#include "prx/libSceFontFt/libSceFontFt.hpp" +#include "prx/libSceFont/libSceFont.hpp" +#include "prx/libSceFont/include/FontTypes.hpp" +#include "prx/libSceFont/include/FontDriver.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Font surface check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static int allocations = 0; +static void* APS5_VABI Allocate(void*, std::uint32_t size) { + ++allocations; + return std::malloc(size); +} +static void APS5_VABI Release(void*, void* pointer) { + if (pointer) --allocations; + std::free(pointer); +} + +static void Put16(std::vector& out, std::uint32_t value) { + out.push_back(static_cast(value >> 8)); + out.push_back(static_cast(value)); +} + +static void Put32(std::vector& out, std::uint32_t value) { + Put16(out, value >> 16); + Put16(out, value & 0xFFFFu); +} + +static void PutAll16(std::vector& out, std::initializer_list values) { + for (const int value : values) Put16(out, static_cast(static_cast(value))); +} + +static std::vector TestGlyphFont(std::uint32_t codepoint, int width) { + std::vector head; + Put32(head, 0x00010000u); + Put32(head, 0x00010000u); + Put32(head, 0); + Put32(head, 0x5F0F3CF5u); + PutAll16(head, {0x000B, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, width, 800, 0, 8, 2, 0, 0}); + std::vector hhea; + Put32(hhea, 0x00010000u); + PutAll16(hhea, {800, -200, 0, width, 0, 0, width, 1, 0, 0, 0, 0, 0, 0, 0, 2}); + std::vector maxp; + Put32(maxp, 0x00010000u); + PutAll16(maxp, {2, 4, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0}); + std::vector hmtx; + PutAll16(hmtx, {width, 0, width, 100}); + std::vector glyf; + PutAll16(glyf, {1, 100, 0, width - 100, 700, 3, 0}); + glyf.insert(glyf.end(), {1, 1, 1, 1}); + PutAll16(glyf, {100, 0, width - 200, -(width - 200) / 2, 0, 700, 0, -700}); + std::vector loca; + PutAll16(loca, {0, 0, static_cast(glyf.size() / 2)}); + std::vector cmap; + PutAll16(cmap, {0, 1, 3, 1}); + Put32(cmap, 12); + PutAll16(cmap, {4, 32, 0, 4, 4, 1, 0, static_cast(codepoint), 0xFFFF, 0, static_cast(codepoint), 0xFFFF, 1 - static_cast(codepoint), 1, 0, 0}); + const std::vector*>> tables = { + {"cmap", &cmap}, {"glyf", &glyf}, {"head", &head}, {"hhea", &hhea}, {"hmtx", &hmtx}, {"loca", &loca}, {"maxp", &maxp}}; + std::vector font; + Put32(font, 0x00010000u); + PutAll16(font, {static_cast(tables.size()), 64, 2, static_cast(tables.size()) * 16 - 64}); + std::uint32_t offset = 12 + static_cast(tables.size()) * 16; + for (const auto& [tag, data] : tables) { + font.insert(font.end(), tag, tag + 4); + Put32(font, 0); + Put32(font, offset); + Put32(font, static_cast(data->size())); + offset += (static_cast(data->size()) + 3u) & ~3u; + } + for (const auto& [tag, data] : tables) { + font.insert(font.end(), data->begin(), data->end()); + font.resize((font.size() + 3u) & ~std::size_t{3}); + } + return font; +} + +using Render = decltype(&sceFontRenderCharGlyphImage); + +static constexpr std::size_t guardSize = 64; + +static std::size_t SurfaceExtent(int width, int height, int stride, int bpp) { + if (width == 0 || height == 0) return 0; + return (static_cast(height) - 1) * static_cast(stride) + + static_cast(width) * static_cast(bpp); +} + +static void CheckLayout(Render render, FontHandle font, int width, int height, int stride, int bpp, bool clipped, unsigned char fill) { + const int referenceStride = width * bpp + 7; + std::vector reference(std::max(SurfaceExtent(width, height, referenceStride, bpp), width == 0 || height == 0 ? 8192 : 0) + 2 * guardSize, fill); + std::vector pixels(std::max(SurfaceExtent(width, height, stride, bpp), width == 0 || height == 0 ? 8192 : 0) + 2 * guardSize, fill); + auto expected = pixels; + FontRenderSurface surface{}; + FontRenderSurface referenceSurface{}; + sceFontRenderSurfaceInit(&surface, pixels.data() + guardSize, stride, bpp, width, height); + sceFontRenderSurfaceInit(&referenceSurface, reference.data() + guardSize, referenceStride, bpp, width, height); + if (clipped) { + sceFontRenderSurfaceSetScissor(&surface, 8, 8, 24, 24); + sceFontRenderSurfaceSetScissor(&referenceSurface, 8, 8, 24, 24); + } + FontGlyphMetrics referenceMetrics{}; + FontRenderOutput referenceOutput{}; + FontGlyphMetrics metrics{}; + FontRenderOutput output{}; + Require(render(font, 'A', &referenceSurface, 10.0f, 10.0f, &referenceMetrics, &referenceOutput) == SCE_FONT_OK); + const int rc = render(font, 'A', &surface, 10.0f, 10.0f, &metrics, &output); + if (rc != SCE_FONT_OK) { + std::fprintf(stderr, "Font surface render failed: width=%d height=%d stride=%d bpp=%d rc=0x%08X\n", + width, height, stride, bpp, static_cast(rc)); + } + Require(rc == SCE_FONT_OK); + Require(std::memcmp(&metrics, &referenceMetrics, sizeof(metrics)) == 0); + Require(std::memcmp(&output.UpdateRect, &referenceOutput.UpdateRect, sizeof(output.UpdateRect)) == 0); + Require(std::memcmp(&output.ImageMetrics, &referenceOutput.ImageMetrics, sizeof(output.ImageMetrics)) == 0); + Require(output.SurfaceImage.widthByte == static_cast(stride)); + Require(output.SurfaceImage.pixelSizeByte == bpp); + if (width > 0 && height == 64) Require(output.UpdateRect.w > 0 && output.UpdateRect.h > 0); + if (width == 0 || height == 0) { + Require(output.UpdateRect.w == 0 && output.UpdateRect.h == 0); + Require(metrics.width > 0.0f && metrics.height > 0.0f); + } else if (output.UpdateRect.w > 0 && output.UpdateRect.h > 0) { + Require(output.UpdateRect.x + output.UpdateRect.w <= static_cast(width)); + Require(output.UpdateRect.y + output.UpdateRect.h <= static_cast(height)); + for (std::uint32_t row = output.UpdateRect.y; row < output.UpdateRect.y + output.UpdateRect.h; ++row) { + for (std::uint32_t column = output.UpdateRect.x; column < output.UpdateRect.x + output.UpdateRect.w; ++column) { + for (int byte = 0; byte < bpp; ++byte) { + const auto destination = guardSize + static_cast(row) * stride + static_cast(column) * bpp + byte; + const auto source = guardSize + static_cast(row) * referenceStride + static_cast(column) * bpp + byte; + Require(destination < expected.size() - guardSize); + expected[destination] = reference[source]; + } + } + } + const auto address = reinterpret_cast(pixels.data() + guardSize) + + static_cast(output.UpdateRect.y) * stride + static_cast(output.UpdateRect.x) * bpp; + Require(reinterpret_cast(output.SurfaceImage.address) == address); + Require(std::any_of(expected.begin() + guardSize, expected.end() - guardSize, [fill](unsigned char value) { return value != fill; })); + } + if (pixels != expected) { + std::fprintf(stderr, "Font surface layout differs: width=%d height=%d stride=%d bpp=%d scissor=%d fill=%u\n", + width, height, stride, bpp, clipped, fill); + } + Require(pixels == expected); + Require(std::all_of(reference.begin(), reference.begin() + guardSize, [fill](unsigned char value) { return value == fill; })); + Require(std::all_of(reference.end() - guardSize, reference.end(), [fill](unsigned char value) { return value == fill; })); +} + +static void CheckCleared(const FontGlyphMetrics& metrics, const FontRenderOutput& output) { + const FontGlyphMetrics emptyMetrics{}; + const FontRenderOutput emptyOutput{}; + Require(std::memcmp(&metrics, &emptyMetrics, sizeof(metrics)) == 0); + Require(std::memcmp(&output, &emptyOutput, sizeof(output)) == 0); +} + +static void CheckErrors(Render render, FontHandle font) { + for (int invalid = 0; invalid < 5; ++invalid) { + std::vector pixels(8192, 0xA5); + FontRenderSurface surface{}; + sceFontRenderSurfaceInit(&surface, pixels.data() + guardSize, 16, 1, 80, 64); + if (invalid == 0) surface.buffer = nullptr; + if (invalid == 1) surface.height = -1; + if (invalid == 2) surface.width = -1; + if (invalid == 3) surface.widthByte = -16; + if (invalid == 4) surface.pixelSizeByte = 2; + FontGlyphMetrics metrics; + FontRenderOutput output; + std::memset(&metrics, 0x5A, sizeof(metrics)); + std::memset(&output, 0x5A, sizeof(output)); + Require(render(font, 'A', &surface, 10.0f, 10.0f, &metrics, &output) == SCE_FONT_ERROR_NO_SUPPORT_SURFACE); + CheckCleared(metrics, output); + Require(std::all_of(pixels.begin(), pixels.end(), [](unsigned char value) { return value == 0xA5; })); + CheckLayout(render, font, 80, 64, 16, 1, false, 0); + } + std::vector pixels(8192, 0xA5); + FontRenderSurface surface{}; + sceFontRenderSurfaceInit(&surface, pixels.data() + guardSize, 16, 1, 80, 64); + FontGlyphMetrics metrics{}; + FontRenderOutput output{}; + Require(render(nullptr, 'A', &surface, 10.0f, 10.0f, &metrics, &output) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(render(font, 0, &surface, 10.0f, 10.0f, &metrics, &output) == SCE_FONT_ERROR_NO_SUPPORT_CODE); + Require(render(font, 'A', &surface, 10.0f, 10.0f, nullptr, &output) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(std::all_of(pixels.begin(), pixels.end(), [](unsigned char value) { return value == 0xA5; })); + CheckLayout(render, font, 80, 64, 0, 1, false, 0); +} + +static void CheckStyleError(FontLibrary library, FontHandle font) { + FontStyleFrame frame{}; + Require(sceFontStyleFrameInit(&frame) == SCE_FONT_OK); + Require(sceFontStyleFrameSetScalePixel(&frame, 40.0f, 40.0f) == SCE_FONT_OK); + std::vector pixels(8192, 0xA5); + int referenceRc = SCE_FONT_OK; + for (const int stride : {128, 16, 0}) { + FontRenderSurface surface{}; + sceFontRenderSurfaceInit(&surface, pixels.data() + guardSize, stride, 1, 80, 64); + Require(sceFontRenderSurfaceSetStyleFrame(&surface, &frame) == SCE_FONT_OK); + FontGlyphMetrics metrics; + FontRenderOutput output; + std::memset(&metrics, 0x5A, sizeof(metrics)); + std::memset(&output, 0x5A, sizeof(output)); + auto* nativeLibrary = static_cast(library); + const auto* driver = nativeLibrary->sys_driver; + nativeLibrary->sys_driver = nullptr; + const int rc = sceFontRenderCharGlyphImage(font, 'A', &surface, 10.0f, 10.0f, &metrics, &output); + nativeLibrary->sys_driver = driver; + if (stride == 128) referenceRc = rc; + Require(referenceRc != SCE_FONT_OK && rc == referenceRc); + CheckCleared(metrics, output); + Require(std::all_of(pixels.begin(), pixels.end(), [](unsigned char value) { return value == 0xA5; })); + CheckLayout(sceFontRenderCharGlyphImage, font, 80, 64, 16, 1, false, 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, 1, 0x52) == SCE_FONT_OK); + const auto bytes = TestGlyphFont('A', 900); + FontHandle font = nullptr; + Require(sceFontOpenFontMemory(library, bytes.data(), static_cast(bytes.size()), nullptr, &font) == SCE_FONT_OK); + FontRenderer renderer = nullptr; + Require(sceFontCreateRenderer(&memory, sceFontSelectRendererFt(0), &renderer) == SCE_FONT_OK); + Require(sceFontBindRenderer(font, renderer) == SCE_FONT_OK); + Require(sceFontSetupRenderScalePixel(font, 40.0f, 40.0f) == SCE_FONT_OK); + CheckStyleError(library, font); + for (const auto render : {sceFontRenderCharGlyphImage, sceFontRenderCharGlyphImageHorizontal, sceFontRenderCharGlyphImageVertical}) { + for (const int bpp : {1, 4}) { + for (const auto dimensions : {std::array{128, 64}, {80, 64}, {80, 1}, {0, 64}, {80, 0}}) { + const int width = dimensions[0]; + const int height = dimensions[1]; + for (const int stride : {width * bpp, width * bpp + 7, 64, 16, 1, 0, std::max(0, width * bpp - 1), 130}) { + for (const bool clipped : {false, true}) { + for (const unsigned char fill : {0x00, 0xEE}) { + CheckLayout(render, font, width, height, stride, bpp, clipped, fill); + } + } + } + } + } + CheckErrors(render, font); + } + Require(sceFontUnbindRenderer(font) == SCE_FONT_OK); + Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_OK); + Require(sceFontCloseFont(font) == SCE_FONT_OK); + Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); + Require(allocations == 0); +} diff --git a/core/libs/tests/Formatting.cpp b/core/libs/tests/Formatting.cpp index 87e27c19cd..6687df8d06 100644 --- a/core/libs/tests/Formatting.cpp +++ b/core/libs/tests/Formatting.cpp @@ -421,9 +421,47 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(ScanList(" -0B11", "%i", &binary) == 1 && binary == 0); Require(ScanListS("0b1", "%i", &binary) == 1 && binary == 0); Require(sscanf_nid_postfix("0x1f", "%i", &binary) == 1 && binary == 31); + for (const char* format : {"%hls", "%lhs", "%llls", "%jls", "%lLs", "%5y", "%k"}) { + for (const char* input : {"ab", ""}) { + char bytes[8]; + std::fill(bytes, bytes + sizeof(bytes), '!'); + Require(ScanListS(input, format, bytes, 8u) == 0); + Require(std::all_of(bytes, bytes + sizeof(bytes), [](char value) { return value == '!'; })); + } + } #ifdef _WIN32 int scanned = -1; int position = -1; + { + const struct { + const char* input; + const char* format; + int result; + const char* stored; + } sets[] = { + {"za", "%[z-a]", 0, ""}, {"b", "%[a-a-c]", 0, ""}, {"a-c", "%[a-a-c]", 1, "a-c"}, {"-d", "%[a-c-e]", 1, "-"}, + {"+", "%[*--]", 1, "+"}, {"-", "%[a--]", 0, ""}, {"z", "%[-^]", 0, ""}, {"-^z", "%[-^]", 1, "-^"}, + {"b]", "%[]-c]", 1, "b]"}, {"-]a", "%[^]-]", 0, ""}, {"ab", "%[^]", 0, ""}, {"ab", "%lls", 1, "ab"}, + {"ab", "%hhs", 1, "ab"}, {"ab", "%ll[a-z]", 1, "ab"}, {"ab", "%y", 0, ""}, {"", "%y", 0, ""}, + {"ab", "%5y", 0, ""}, {"ab", "%k", 0, ""}, {"ab", "%hls", 0, ""}, {"ab", "%llls", 0, ""}, + {"ab", "%l", 0, ""}, {"", "%l", 0, ""}, {"ab", "%ll", 0, ""}, {"ab", "%h", 0, ""}, {"ab", "%hl", 0, ""}, + }; + for (const auto& item : sets) { + char bytes[8] = {'!', '!', '!', '!', '!', '!', '!', '!'}; + Require(sscanf_nid_postfix(item.input, item.format, bytes) == item.result); + Require(item.result == 0 ? bytes[0] == '!' : std::strcmp(bytes, item.stored) == 0); + std::memset(bytes, '!', sizeof(bytes)); + Require(ScanListS(item.input, item.format, bytes, 8u) == item.result); + Require(item.result == 0 ? bytes[0] == '!' : std::strcmp(bytes, item.stored) == 0); + } + char pair[4] = {'!', '!', '!', '!'}; + Require(sscanf_nid_postfix("abc", "%2llc", pair) == 1 && pair[0] == 'a' && pair[1] == 'b' && pair[2] == '!'); + Require(sscanf_nid_postfix("ab", "%s%y", pair) == 1 && std::strcmp(pair, "ab") == 0); + char16_t wide[3] = {u'!', u'!', u'!'}; + Require(sscanf_nid_postfix("ab", "%ls", wide) == 1 && wide[0] == u'a' && wide[1] == u'b' && wide[2] == 0); + std::fill(wide, wide + 3, u'!'); + Require(ScanListS("ab", "%ls", wide, 3u) == 1 && wide[0] == u'a' && wide[1] == u'b' && wide[2] == 0); + } Require(sscanf_nid_postfix("12", "%*d%d", &scanned) == 0 && scanned == -1); Require(sscanf_nid_postfix("12 ", "%*d %d", &scanned) == 0); Require(sscanf_nid_postfix("abc", "%*s%d", &scanned) == 0); @@ -436,25 +474,35 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { Require(sscanf_nid_postfix("", "%*d", &scanned) == EOF); Require(sscanf_nid_postfix(" ", "%*d%d", &scanned) == EOF); Require(sscanf_nid_postfix("", "%d", &scanned) == EOF); -#else +#endif { char16_t first[8] = {}; char16_t letter = u'!'; Require(sscanf_nid_postfix("ab cd", "%ls %lc", first, &letter) == 2); Require(std::u16string(first) == u"ab" && letter == u'c'); - Require(sscanf_nid_postfix(" xy z", "%S %C", first, &letter) == 2 && std::u16string(first) == u"xy" && letter == u'z'); + letter = u'!'; + Require(sscanf_nid_postfix(" xy z", "%S %C", first, &letter) == 1 && std::u16string(first) == u"xy" && letter == u'!'); + Require(sscanf_nid_postfix("abc", "%C", &letter) == 0 && sscanf_nid_postfix("abc", "%2C", &letter) == 0 && letter == u'!'); + int afterWide = -7; + int countAfterWide = -7; + Require(sscanf_nid_postfix("x5", "%C%d", &letter, &afterWide) == 0 && letter == u'!' && afterWide == -7); + Require(sscanf_nid_postfix("xy", "%C%n", &letter, &countAfterWide) == 0 && countAfterWide == -7); + Require(sscanf_nid_postfix("5 x", "%d %C%d", &afterWide, &letter, &countAfterWide) == 1 && afterWide == 5 && letter == u'!' && countAfterWide == -7); + afterWide = -7; + Require(sscanf_nid_postfix("", "%C", &letter) == 0 && sscanf_nid_postfix("x5", "%*C%d", &afterWide) == 0 && afterWide == -7); + Require(sscanf_nid_postfix("xy", "%lC", &letter) == 0 && sscanf_nid_postfix("xy", "%hC", &letter) == 0 && letter == u'!'); + afterWide = -7; + Require(ScanListS("x5", "%C%d", &letter, 4u, &afterWide) == 0 && letter == u'!' && afterWide == -7); + Require(ScanListS("xy", "%C%n", &letter, 4u, &countAfterWide) == 0 && countAfterWide == -7); + Require(ScanListS("", "%C", &letter, 4u) == 0); + Require(sscanf_nid_postfix("abc", "%hS", first) == 1 && std::u16string(first) == u"abc"); char16_t three[5] = {u'!', u'!', u'!', u'!', u'!'}; Require(sscanf_nid_postfix("abcdef", "%3lc", three) == 1); Require(three[0] == u'a' && three[1] == u'b' && three[2] == u'c' && three[3] == u'!'); Require(sscanf_nid_postfix("abcz", "%l[a-c]", first) == 1 && std::u16string(first) == u"abc"); Require(sscanf_nid_postfix("ab cd", "%l[^ ]", first) == 1 && std::u16string(first) == u"ab"); Require(sscanf_nid_postfix("]x", "%l[]x]", first) == 1 && std::u16string(first) == u"]x"); - char16_t second[8] = {}; - Require(sscanf_nid_postfix("\xc5\xbelu\xc5\xa5 k\xc5\xaf\xc5\x88", "%ls %ls", first, second) == 2); - Require(std::u16string(first) == u"\u017elu\u0165" && std::u16string(second) == u"k\u016f\u0148"); int number = 0; - Require(sscanf_nid_postfix("\xc5\xbelu\xc5\xa5 7", "%2ls%*ls %d", first, &number) == 2); - Require(std::u16string(first) == u"\u017el" && number == 7); char narrowWord[8] = {}; Require(sscanf_nid_postfix("12 ab cd", "%d %ls %7s", &number, first, narrowWord) == 3); Require(number == 12 && std::u16string(first) == u"ab" && std::strcmp(narrowWord, "cd") == 0); @@ -473,14 +521,40 @@ __attribute__((noinline)) static void APS5_VABI RunChecks() { small[0] = u'!'; Require(ScanListS("xyz", "%3lc", small, 2u) == 0 && small[0] == 0); Require(ScanListS("ab cd", "%*ls %l[a-z]", fits, 6u) == 1 && std::u16string(fits) == u"cd"); - char16_t untouched[4] = {u'!', u'!', u'!', u'!'}; - const auto rejects = [&](const char* input, const char* format) { - try { sscanf_nid_postfix(input, format, untouched); } catch (const std::runtime_error&) { return untouched[0] == u'!'; } - return false; + struct Case { + const char* input; + const char* format; + int result; + const char16_t* stored; + int error; }; - Require(rejects("\xff", "%ls") && rejects("a b", "%2$ls") && rejects("\xc5\xbe", "%l[\xc5\xbe]")); - Require(rejects("\xf0\x9f\x98\x80", "%ls")); + const Case cases[] = { + {"\xc3\xa9", "%ls", 0, u"", 86}, {"a\xc3\xa9", "%ls", 0, u"a", 86}, {"\x80", "%ls", EOF, u"", 86}, + {" \x80", "%ls", EOF, u"", 86}, {"\x7f\x80", "%ls", 0, u"\x7f", 86}, {"\xf0\x9f\x98\x80", "%ls", EOF, u"", 86}, + {"\xc3", "%ls", EOF, u"", 0}, {"\xc3 x", "%ls", EOF, u"", 0}, {"a\xc3" "b", "%2ls", EOF, u"a", 0}, + {"\xc3\xa9", "%lc", EOF, u"", 0}, {"\xc3" "a", "%2lc", 0, u"", 86}, {"\x80", "%lc", EOF, u"", 86}, + {"\xc3\xa9", "%S", 0, u"", 86}, {"a\xc3\xa9", "%l[^ ]", 0, u"a", 86}, {"\x80", "%l[^ ]", EOF, u"", 86}, + {"\xc3" "a", "%l[a-z]", 0, u"", 0}, {"a\xc3" "b", "%l[a-z]", 1, u"a", 0}, {"\xc3\xa9", "%l[\xc3\xa9]", 0, u"", 86}, + {"za", "%l[z-a]", 0, u"", 0}, {"b", "%l[a-a-c]", 0, u"", 0}, {"a-c", "%l[a-a-c]", 1, u"a-c", 0}, {"+", "%l[*--]", 1, u"+", 0}, + {"\x80", "%l[\x80-\xff]", EOF, u"", 86}, {"a\x80" "b", "%l[a\x80]", 0, u"a", 86}, {"a\xc3" "b", "%l[^\xc3]", 1, u"a", 0}, + }; + for (const auto& item : cases) { + char16_t units[8] = {u'!', u'!', u'!', u'!', u'!', u'!', u'!', u'!'}; + *__error_nid_postfix() = 0; + Require(sscanf_nid_postfix(item.input, item.format, units) == item.result && *__error_nid_postfix() == item.error); + const std::u16string stored(item.stored); + Require(std::u16string(units, stored.size()) == stored && units[stored.size()] == (item.result == 1 ? u'\0' : u'!')); + } + Require(sscanf_nid_postfix("5 \xc3\xa9", "%d %ls", &number, first) == 1 && number == 5); + Require(sscanf_nid_postfix("x \x80", "%ls %ls", first, fits) == 1 && std::u16string(first) == u"x"); + Require(sscanf_nid_postfix("\xc3\xa9", "%*ls") == 0 && sscanf_nid_postfix("\x80", "%*ls") == EOF); + Require(sscanf_nid_postfix("\xc3\xa9", "%*lc") == EOF && sscanf_nid_postfix("a\xc3", "%*ls") == EOF); + char16_t untouched[4] = {u'!', u'!', u'!', u'!'}; + bool positional = false; + try { sscanf_nid_postfix("a b", "%2$ls", untouched); } catch (const std::runtime_error&) { positional = untouched[0] == u'!'; } + Require(positional); } +#ifndef _WIN32 char narrow[8] = {}; Require(sscanf_nid_postfix("ab%", "%7[^%ls]", narrow) == 1 && std::strcmp(narrow, "ab") == 0); #endif diff --git a/core/libs/tests/GuestAio.cpp b/core/libs/tests/GuestAio.cpp index 83fc668a6e..dff172fd1a 100644 --- a/core/libs/tests/GuestAio.cpp +++ b/core/libs/tests/GuestAio.cpp @@ -19,7 +19,10 @@ #include #ifdef _WIN32 #include +#include extern "C" _invalid_parameter_handler _set_thread_local_invalid_parameter_handler(_invalid_parameter_handler); +#else +#include #endif static void Require(bool value, int line) { @@ -35,6 +38,25 @@ static void DeleteAio(std::int32_t id) { Check(sceKernelAioDeleteRequest(id, &removed) == 0 && removed == 0); } +static void* MapReadOnlyPage() { +#ifdef _WIN32 + void* page = VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READONLY); + Check(page != nullptr); +#else + void* page = mmap(nullptr, 4096, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Check(page != MAP_FAILED); +#endif + return page; +} + +static void UnmapReadOnlyPage(void* page) { +#ifdef _WIN32 + Check(VirtualFree(page, 0, MEM_RELEASE) != 0); +#else + Check(munmap(page, 4096) == 0); +#endif +} + #ifdef _WIN32 static int g_parameterReports = 0; static void CountParameterReport(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, std::uintptr_t) { @@ -400,6 +422,55 @@ int main(int argc, char** argv) { 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 int faultFd = sceKernelOpen(file.string().c_str(), SCE_KERNEL_O_RDONLY, 0); + Check(faultFd >= 0); + std::array faultBuffer{}; + KernelAioResult faultResult{-1, 0}; + KernelAioRwRequest faultRequest{0, faultBuffer.size(), faultBuffer.data(), &faultResult, faultFd}; + const auto finished = [&] { + std::int32_t requestId = 0; + Check(sceKernelAioSubmitReadCommands(&faultRequest, 1, 1, &requestId) == 0 && faultResult.state == 3); + return requestId; + }; + auto* unreadable = reinterpret_cast(0x10); + std::int32_t kept[2] = {7, 7}; + Check(sceKernelAioPollRequests(unreadable, 0, kept) == SCE_KERNEL_ERROR_EINVAL); + Check(sceKernelAioPollRequests(unreadable, 2, kept) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioCancelRequests(unreadable, 2, kept) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioDeleteRequests(unreadable, 2, kept) == SCE_KERNEL_ERROR_EFAULT); + Check(kept[0] == 7 && kept[1] == 7); + void* readOnly = MapReadOnlyPage(); + for (void* bad : {static_cast(unreadable), readOnly}) { + auto* badState = static_cast(bad); + const std::int32_t pollId = finished(); + Check(sceKernelAioPollRequest(pollId, badState) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioPollRequest(pollId, &polled) == 0 && polled == 0x10003); + const std::int32_t cancelId = finished(); + Check(sceKernelAioCancelRequest(cancelId, badState) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioPollRequest(cancelId, &polled) == 0 && polled == 3); + const std::int32_t deleteId = finished(); + Check(sceKernelAioDeleteRequest(deleteId, badState) == SCE_KERNEL_ERROR_EFAULT); + std::int32_t removed = 7; + Check(sceKernelAioDeleteRequest(deleteId, &removed) == 0 && removed == SCE_KERNEL_ERROR_ESRCH); + std::int32_t batchIds[2] = {finished(), finished()}; + std::int32_t batchStates[2] = {7, 7}; + Check(sceKernelAioPollRequests(batchIds, 2, badState) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioCancelRequests(batchIds, 2, badState) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioPollRequests(batchIds, 2, batchStates) == 0 && batchStates[0] == 0x10003 && batchStates[1] == 0x10003); + Check(sceKernelAioDeleteRequests(batchIds, 2, badState) == SCE_KERNEL_ERROR_EFAULT); + Check(sceKernelAioDeleteRequests(batchIds, 2, batchStates) == 0); + Check(batchStates[0] == SCE_KERNEL_ERROR_ESRCH && batchStates[1] == SCE_KERNEL_ERROR_ESRCH); + DeleteAio(pollId); + DeleteAio(cancelId); + } + Check(*static_cast(readOnly) == 0); + UnmapReadOnlyPage(readOnly); + const std::int32_t twiceId = finished(); + std::int32_t twice[2] = {twiceId, twiceId}; + std::int32_t twiceStates[2] = {7, 7}; + Check(sceKernelAioPollRequests(twice, 2, twiceStates) == 0 && twiceStates[0] == 3 && twiceStates[1] == 0x10003); + Check(sceKernelAioDeleteRequests(twice, 2, twiceStates) == 0 && twiceStates[0] == 0 && twiceStates[1] == SCE_KERNEL_ERROR_ESRCH); + Check(sceKernelClose(faultFd) == 0); const auto shared = root / "shared.bin"; std::string sharedContents; for (char letter : {'A', 'B', 'C', 'D'}) sharedContents.append(1024, letter); diff --git a/core/libs/tests/GuestAioRegistry.cpp b/core/libs/tests/GuestAioRegistry.cpp index 1b165ec7ca..e6e6324361 100644 --- a/core/libs/tests/GuestAioRegistry.cpp +++ b/core/libs/tests/GuestAioRegistry.cpp @@ -160,8 +160,10 @@ static std::condition_variable g_aioGateChanged; static bool g_aioGateEntered = false; static bool g_aioGateReleased = false; static bool g_aioGateFails = false; +static std::uintptr_t g_aioGateSource = 0; -static void HoldAioSource(std::uintptr_t, std::size_t) { +static void HoldAioSource(std::uintptr_t address, std::size_t) { + if (address != g_aioGateSource) return; std::unique_lock lock(g_aioGateMutex); g_aioGateEntered = true; g_aioGateChanged.notify_all(); @@ -180,6 +182,7 @@ static void CheckAioProcessingOwnership(bool fail) { KernelAioRwRequest request{0, 1, &byte, &result, -1}; std::int32_t submittedId = -7; bool submitFailed = false; + g_aioGateSource = reinterpret_cast(&byte); GuestArena::GuestArenaSetHostReadHook_nid_no_patch(HoldAioSource); std::thread submitter([&] { try { Check(sceKernelAioSubmitWriteCommands(&request, 1, 1, &submittedId) == 0); } diff --git a/core/libs/tests/GuestAjm.cpp b/core/libs/tests/GuestAjm.cpp index 35ad71e346..007a6beaf0 100644 --- a/core/libs/tests/GuestAjm.cpp +++ b/core/libs/tests/GuestAjm.cpp @@ -851,6 +851,23 @@ void TestGetInfo(std::uint32_t context) { Require(sceAjmInstanceDestroy(context, instance) == 0); } +void TestGetStatistics(std::uint32_t context) { + std::uint32_t instance = 0; + Require(sceAjmInstanceCreate(context, 1, 0, &instance) == 0); + std::vector batch(4096); + AjmBatchInfo info{}; + std::vector statistics(32, 0xaa); + FormatSideband format{-1, -1, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau, 0xaaaaaaaau}; + Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0); + Require(sceAjmBatchJobGetStatistics(&info, 1.0f, statistics.data()) == 0); + Require(sceAjmBatchJobGetInfo(&info, instance, &format) == 0); + Submit(context, info); + Require(std::all_of(statistics.begin(), statistics.begin() + 24, [](std::uint8_t value) { return value == 0; })); + Require(std::all_of(statistics.begin() + 24, statistics.end(), [](std::uint8_t value) { return value == 0xaa; })); + Require(format.result == 1 && format.numChannels == 0xaaaaaaaau); + Require(sceAjmInstanceDestroy(context, instance) == 0); +} + void TestBatchBounds(std::uint32_t context) { constexpr int invalidParameter = static_cast(0x80930005); std::vector storage(128); @@ -2587,6 +2604,7 @@ int main(int argc, char** argv) { TestGaplessDecode(context); TestCodecInfo(context); TestGetInfo(context); + TestGetStatistics(context); TestBatchBounds(context); TestBatchWaitRelease(context); TestInstanceHandleLifetime(context); diff --git a/core/libs/tests/GuestAmpr.cpp b/core/libs/tests/GuestAmpr.cpp index 05e617348e..308c01e66b 100644 --- a/core/libs/tests/GuestAmpr.cpp +++ b/core/libs/tests/GuestAmpr.cpp @@ -1457,6 +1457,50 @@ void TestAmmRemapAndProtect() { ReleaseRecorderBuffer(exact); } +struct ObservedMapping { + std::uintptr_t address = 0; + std::size_t bytes = 0; + std::uint64_t generation = 0; +}; + +std::vector observedMappings; + +void ObserveMapping(std::uintptr_t address, std::size_t bytes, std::uint64_t generation) { + observedMappings.push_back({address, bytes, generation}); +} + +void RequireZeroMappingObserved(std::uint64_t address, std::uint64_t size, std::uint64_t before) { +#ifdef _WIN32 + bool found = false; + for (const auto& mapping : observedMappings) { + if (mapping.address <= address && mapping.address + mapping.bytes >= address + size && mapping.generation > before) found = true; + } + Require(found); + Require(GuestArena::GuestArenaCommitGeneration_nid_postfix() > before); +#else + (void)address; + (void)size; + (void)before; +#endif +} + +void RequireReadOnlyZeroPages(std::uint64_t address, std::uint64_t size) { +#ifdef _WIN32 + for (std::uint64_t offset = 0; offset < size; offset += page) { + auto* pointer = reinterpret_cast(address + offset); + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(pointer, &info, sizeof(info)) == sizeof(info)); + Require(info.Type == MEM_MAPPED && info.AllocationProtect == PAGE_READONLY && info.Protect == PAGE_READONLY); + DWORD previous = 0; + Require(!VirtualProtect(pointer, page, PAGE_READWRITE, &previous)); + Require(At(address + offset) == 0); + } +#else + (void)address; + (void)size; +#endif +} + void TestAmmPrt() { std::uint64_t start = 0; std::uint64_t end = 0; @@ -1478,6 +1522,7 @@ void TestAmmPrt() { SubmitAmm(reserve); Require(Protection(prt) == 0x10 && Protection(prt + 3 * page) == 0x10); Require(At(prt) == 0 && At(prt + 4 * page - 8) == 0); + RequireReadOnlyZeroPages(prt, 4 * page); At(source) = 0x88; Recorder back; @@ -1498,8 +1543,20 @@ void TestAmmPrt() { commands = release.Commands(); Require(sceAmprAmmCommandBufferUnmapToPrt(&release.buffer, prt + page, 2 * page) == 0); RequireAppended(release, offset, commands, Apr::Opcode::AmmUnmapToPrt, sceAmprAmmMeasureAmmCommandSizeUnmap(prt + page, 2 * page)); +#ifdef _WIN32 + const auto beforeRelease = GuestArena::GuestArenaCommitGeneration_nid_postfix(); + observedMappings.clear(); + GuestArena::GuestArenaSetPrivateMappingObserver_nid_postfix(&ObserveMapping); +#else + const std::uint64_t beforeRelease = 0; +#endif SubmitAmm(release); +#ifdef _WIN32 + GuestArena::GuestArenaSetPrivateMappingObserver_nid_postfix(nullptr); +#endif + RequireZeroMappingObserved(prt + page, 2 * page, beforeRelease); Require(Protection(prt + page) == 0x10 && At(prt + page) == 0 && At(prt + 2 * page) == 0 && At(prt + 3 * page) == 0x88); + RequireReadOnlyZeroPages(prt + page, 2 * page); Recorder rebuild; Require(sceAmprAmmCommandBufferAllocatePaForPrt(&rebuild.buffer, prt, 3 * page, 0, cpuReadWrite) == 0); diff --git a/core/libs/tests/GuestAprRead.cpp b/core/libs/tests/GuestAprRead.cpp index da7ee7df7c..f086665bbd 100644 --- a/core/libs/tests/GuestAprRead.cpp +++ b/core/libs/tests/GuestAprRead.cpp @@ -14,6 +14,8 @@ #include #ifdef _WIN32 #include +#else +#include #endif namespace { @@ -176,6 +178,7 @@ void TestStreams(const Fixture& fixture) { void TestQueries() { constexpr int GuestEinval = 22; constexpr int GuestEnoent = 2; + constexpr int GuestEfault = 14; const char* names[] = {"generated.bin", "missing.bin"}; std::array ids{0, 0}; std::array sizes{1, 1}; @@ -193,6 +196,28 @@ void TestQueries() { FileStat stat{}; Require(sceKernelAprGetFileStat(id, nullptr) == -1 && *__error_nid_postfix() == GuestEinval, "APR accepted a null file stat"); Require(sceKernelAprGetFileStat(id, &stat) == 0 && stat.st_size == static_cast(FileBytes), "APR reported a wrong file stat size"); + for (const bool readable : {true, false}) { +#ifdef _WIN32 + void* page = VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, readable ? PAGE_READONLY : PAGE_NOACCESS); + Require(page != nullptr, "cannot map an unwritable APR destination"); +#else + void* page = mmap(nullptr, 4096, readable ? PROT_READ : PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(page != MAP_FAILED, "cannot map an unwritable APR destination"); +#endif + *__error_nid_postfix() = 0; + Require(sceKernelAprGetFileSize(id, static_cast(page)) == -1 && *__error_nid_postfix() == GuestEfault, "APR accepted an unwritable file size"); + *__error_nid_postfix() = 0; + Require(sceKernelAprGetFileStat(id, static_cast(page)) == -1 && *__error_nid_postfix() == GuestEfault, "APR accepted an unwritable file stat"); + if (readable) { + const auto* bytes = static_cast(page); + Require(std::all_of(bytes, bytes + 4096, [](std::uint8_t byte) { return byte == 0; }), "APR wrote into an unwritable destination"); + } +#ifdef _WIN32 + VirtualFree(page, 0, MEM_RELEASE); +#else + munmap(page, 4096); +#endif + } std::vector data(4096, 0xCC); const Apr::ReadFileCommand command{{Apr::Opcode::ReadFile, sizeof(Apr::ReadFileCommand)}, id, 0, reinterpret_cast(data.data()), data.size(), 100}; alignas(8) std::array memory{}; diff --git a/core/libs/tests/GuestAudioOut.cpp b/core/libs/tests/GuestAudioOut.cpp new file mode 100644 index 0000000000..b80d9741f9 --- /dev/null +++ b/core/libs/tests/GuestAudioOut.cpp @@ -0,0 +1,22 @@ +#include "prx/libSceAudioOut/include/AudioOut.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include + +static void Require(bool value) { + if (!value) { + std::abort(); + } +} + +int main() { + constexpr int invalidPointer = static_cast(0x80260004); + constexpr int invalidHandle = static_cast(0x80260001); + + Require(sceAudioOutInit() == 0); + Require(sceAudioOutGetPortState(invalidHandle, nullptr) == invalidPointer); + Require(sceAudioOutGetPortState(0, nullptr) == invalidPointer); + + LibcRunShutdown_nid_postfix(); + return 0; +} diff --git a/core/libs/tests/GuestAudiodec.cpp b/core/libs/tests/GuestAudiodec.cpp index 5a34bf4135..9d7053a504 100644 --- a/core/libs/tests/GuestAudiodec.cpp +++ b/core/libs/tests/GuestAudiodec.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include static void Require(bool value) { if (!value) std::abort(); } @@ -238,8 +239,8 @@ struct M4aacExtendedParam { std::uint32_t surroundChannelInterleaveOrder; }; -void TestAac(bool adts, std::uint32_t maxChannels = 2, std::uint32_t paramSize = sizeof(AudiodecParamM4aac), std::uint32_t enableNondelayOutput = 0) { - M4aacExtendedParam param{{paramSize, 1, adts ? 1u : 2u, 3, maxChannels, 0}, enableNondelayOutput, 0}; +void TestAac(bool adts, std::uint32_t maxChannels = 2, std::uint32_t paramSize = sizeof(AudiodecParamM4aac), std::uint32_t enableNondelayOutput = 0, std::uint32_t surroundChannelInterleaveOrder = 0) { + M4aacExtendedParam param{{paramSize, 1, adts ? 1u : 2u, 3, maxChannels, 0}, enableNondelayOutput, surroundChannelInterleaveOrder}; AudiodecM4aacInfo info{sizeof(info), 0, 0, 0, 0}; AudiodecCtrl ctrl{¶m, &info, nullptr, nullptr}; const std::int32_t handle = sceAudiodecCreateDecoder(&ctrl, 3); @@ -266,6 +267,25 @@ void TestAac(bool adts, std::uint32_t maxChannels = 2, std::uint32_t paramSize = Require(sceAudiodecDeleteDecoder(handle) == 0); } +void TestAacUnopenableLayout() { + AudiodecParamM4aac param{sizeof(param), 1, 1, 3, 2, 0}; + AudiodecM4aacInfo info{sizeof(info), 0, 0, 0, 0}; + AudiodecCtrl ctrl{¶m, &info, nullptr, nullptr}; + const std::int32_t handle = sceAudiodecCreateDecoder(&ctrl, 3); + Require(handle > 0); + Streams streams; + const std::size_t length = AdtsLength(AAC_ADTS); + std::vector reserved(AAC_ADTS, AAC_ADTS + length); + reserved[2] = static_cast((reserved[2] & 0xC3) | 13 << 2); + Require(Decode(handle, ctrl, streams, reserved.data(), reserved.size()) == errorApiFail && info.i_result == -4); + Require(streams.au.ui_au_size == 0 && streams.pcm.ui_pcm_size == 0); + Require(Decode(handle, ctrl, streams, reserved.data(), reserved.size()) == errorApiFail && info.i_result == -4); + Require(sceAudiodecClearContext(handle) == 0); + Require(Decode(handle, ctrl, streams, AAC_ADTS, length) == 0); + Require(streams.au.ui_au_size == length && streams.pcm.ui_pcm_size == 1024 * 2 * 2 && info.i_result == 0); + Require(sceAudiodecDeleteDecoder(handle) == 0); +} + void TestAacRaw51(std::uint32_t maxChannels) { AudiodecParamM4aac param{sizeof(param), 1, 2, 3, maxChannels, 0}; AudiodecM4aacInfo info{sizeof(info), 0, 0, 0, 0}; @@ -472,6 +492,8 @@ int main() { TestAac(true, 2, 28, 1); TestAac(false, 2, 32, 1); TestAac(false, 8, 28, 1); + TestAac(false, 8, 32, 0, 2); + TestAac(true, 2, 32, 1, 1); TestAacRaw51(6); TestAacRaw51(8); for (const bool adts : {true, false}) { @@ -480,6 +502,7 @@ int main() { TestHeAac(HEAAC_STEREO, sizeof(HEAAC_STEREO), 2, adts, highEfficiency); } } + TestAacUnopenableLayout(); TestMp3(); TestAt9(); for (std::uint32_t type = 1; type <= 3; ++type) Require(sceAudiodecTermLibrary(type) == 0); diff --git a/core/libs/tests/GuestAudiodecCpu.cpp b/core/libs/tests/GuestAudiodecCpu.cpp new file mode 100644 index 0000000000..178daf786a --- /dev/null +++ b/core/libs/tests/GuestAudiodecCpu.cpp @@ -0,0 +1,196 @@ +#include "prx/libSceAudiodecCpu/libSceAudiodecCpu.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +namespace { + +struct LpcmParam { + std::uint32_t size, channelConfig, rateIndex, bitsIndex, auBytes, reserved; + std::uint64_t header; + std::uint32_t ptsHigh, ptsLow; +}; + +struct LpcmBsiInfo { + std::uint32_t reserved, channelConfig, rateIndex, pcmBytes; + std::uint64_t header; + std::uint32_t ptsHigh, ptsLow; +}; + +} + +namespace { + +constexpr std::uint32_t LpcmBd = 0x1003; +constexpr std::uint32_t LpcmDvd = 0x1004; +constexpr std::uint32_t Trhd = 0x100b; +constexpr std::uint32_t None = 0xffffffffu; + +void Require(bool value) { if (!value) std::abort(); } + +int Error(std::uint32_t code) { return static_cast(0x80f70000u + code); } + +std::uint64_t Header(std::uint32_t channels, std::uint32_t rate, std::uint32_t bits, std::uint32_t frames) { + return static_cast(channels) << 32 | static_cast(rate) << 40 | static_cast(bits) << 48 | static_cast(frames) << 56; +} + +struct Decoder { + std::vector work = std::vector(0x190138); + std::vector input = std::vector(0x2000); + std::vector output = std::vector(0x1000); + LpcmParam param{0x28, 0, 0, 0, 0, 0, Header(2, 1, 1, 1), None, None}; + LpcmBsiInfo info{}; + AudiodecAuInfo au{0x18, input.data(), static_cast(input.size())}; + AudiodecPcmItem pcm{0x18, output.data(), static_cast(output.size() * 4)}; + AudiodecCpuMemory memory{0x18, work.data(), 0}; + AudiodecCtrl ctrl{¶m, &info, &au, &pcm}; + + int Decode(std::uint32_t high, std::uint32_t low) { + param.ptsHigh = high; + param.ptsLow = low; + au.ui_au_size = static_cast(input.size()); + pcm.ui_pcm_size = static_cast(output.size() * 4); + return sceAudiodecCpuDecode(&ctrl, &memory, LpcmBd); + } +}; + +void TestQueryMemSize() { + Decoder decoder; + Require(sceAudiodecCpuQueryMemSize(nullptr, &decoder.memory, LpcmBd) == Error(9)); + AudiodecCtrl noParam{}; + Require(sceAudiodecCpuQueryMemSize(&noParam, &decoder.memory, LpcmBd) == Error(0xa)); + Require(sceAudiodecCpuQueryMemSize(&decoder.ctrl, nullptr, LpcmBd) == Error(0xe)); + AudiodecCpuMemory badSize{0x10, nullptr, 0}; + Require(sceAudiodecCpuQueryMemSize(&decoder.ctrl, &badSize, LpcmBd) == Error(8)); + Require(sceAudiodecCpuQueryMemSize(&decoder.ctrl, &decoder.memory, 0x1009) == Error(1)); + Require(sceAudiodecCpuQueryMemSize(&decoder.ctrl, &decoder.memory, 0x1000) == Error(1)); + AudiodecCpuMemory empty{0x18, nullptr, 0}; + Require(sceAudiodecCpuQueryMemSize(&decoder.ctrl, &empty, LpcmBd) == 0); + Require(empty.ui_memory_size == 0x190138); + empty.ui_memory_size = 0; + Require(sceAudiodecCpuInternalQueryMemSize(&decoder.ctrl, &empty, LpcmDvd) == 0); + Require(empty.ui_memory_size == 0x190138); +} + +void TestInitAndClear() { + Decoder decoder; + AudiodecCtrl noBsi{&decoder.param, nullptr, &decoder.au, &decoder.pcm}; + Require(sceAudiodecCpuInitDecoder(&noBsi, &decoder.memory, LpcmBd) == Error(0xb)); + AudiodecCpuMemory noAddress{0x18, nullptr, 0}; + Require(sceAudiodecCpuInitDecoder(&decoder.ctrl, &noAddress, LpcmBd) == Error(0x11)); + AudiodecCpuMemory badSize{0x10, decoder.work.data(), 0}; + Require(sceAudiodecCpuInitDecoder(&decoder.ctrl, &badSize, LpcmBd) == Error(8)); + Require(sceAudiodecCpuInitDecoder(&decoder.ctrl, &decoder.memory, 0x100a) == Error(1)); + Require(sceAudiodecCpuInitDecoder(&decoder.ctrl, &decoder.memory, LpcmBd) == 0); + Require(sceAudiodecCpuInternalInitDecoder(&decoder.ctrl, &decoder.memory, LpcmDvd) == 0); + Require(sceAudiodecCpuClearContext(nullptr, LpcmBd) == Error(2)); + Require(sceAudiodecCpuClearContext(&noAddress, LpcmBd) == Error(0x11)); + Require(sceAudiodecCpuClearContext(&badSize, LpcmBd) == Error(8)); + Require(sceAudiodecCpuClearContext(&decoder.memory, 0x1009) == Error(1)); + Require(sceAudiodecCpuClearContext(&decoder.memory, LpcmBd) == 0); + Require(sceAudiodecCpuInternalClearContext(&decoder.memory, LpcmDvd) == 0); +} + +void TestDecodeArguments() { + Decoder decoder; + Require(sceAudiodecCpuInitDecoder(&decoder.ctrl, &decoder.memory, LpcmBd) == 0); + Require(sceAudiodecCpuDecode(nullptr, &decoder.memory, LpcmBd) == Error(9)); + AudiodecCtrl ctrl = decoder.ctrl; + ctrl.pBsiInfo = nullptr; + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, LpcmBd) == Error(0xb)); + ctrl = decoder.ctrl; + ctrl.pAuInfo = nullptr; + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, LpcmBd) == Error(0xc)); + AudiodecAuInfo au{0x18, nullptr, 0}; + ctrl = decoder.ctrl; + ctrl.pAuInfo = &au; + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, LpcmBd) == Error(0xf)); + ctrl.pPcmItem = nullptr; + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, Trhd) == Error(0xd)); + AudiodecPcmItem pcm{0x18, nullptr, 4}; + ctrl.pPcmItem = &pcm; + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, Trhd) == Error(0x10)); + au.p_au_addr = decoder.input.data(); + pcm.p_pcm_addr = decoder.output.data(); + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, LpcmBd) == Error(0x13)); + au.ui_au_size = 4; + pcm.ui_pcm_size = 0; + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, LpcmBd) == Error(0x14)); + pcm.ui_pcm_size = 4; + au.ui_size = 0x10; + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, LpcmBd) == Error(6)); + au.ui_size = 0x18; + pcm.ui_size = 0x10; + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, LpcmBd) == Error(7)); + pcm.ui_size = 0x18; + Require(sceAudiodecCpuDecode(&ctrl, nullptr, LpcmBd) == Error(0xe)); + Require(sceAudiodecCpuDecode(&ctrl, &decoder.memory, 0x1009) == Error(1)); + decoder.param.size = 0x20; + Require(decoder.Decode(None, None) == static_cast(0x80f70100u)); + decoder.param.size = 0x28; + for (const std::uint64_t header : {Header(0, 1, 1, 1), Header(11, 1, 1, 1), Header(2, 0, 1, 1), Header(2, 4, 1, 1), Header(2, 1, 0, 1), Header(2, 1, 4, 1)}) { + decoder.param.header = header; + Require(decoder.Decode(None, None) == static_cast(0x80f70100u)); + } +} + +void TestDecodeSamples() { + Decoder decoder; + Require(sceAudiodecCpuInitDecoder(&decoder.ctrl, &decoder.memory, LpcmBd) == 0); + std::memset(decoder.input.data(), 0, decoder.input.size()); + const std::uint8_t samples16[] = {0x80, 0x00, 0x40, 0x00, 0xff, 0xff, 0x7f, 0xff}; + std::memcpy(decoder.input.data(), samples16, sizeof(samples16)); + Require(decoder.Decode(None, None) == 0); + Require(decoder.au.ui_au_size == 960 && decoder.pcm.ui_pcm_size == 480 * 4); + Require(decoder.param.channelConfig == 2 && decoder.param.rateIndex == 1 && decoder.param.bitsIndex == 1 && decoder.param.auBytes == 960); + Require(decoder.output[0] == -1.0f && decoder.output[1] == 0.5f && decoder.output[2] == -0x1p-15f && decoder.output[3] == 0x7fff0000 * 0x1p-31f && decoder.output[4] == 0.0f); + Require(decoder.info.channelConfig == 2 && decoder.info.rateIndex == 1 && decoder.info.pcmBytes == 480 * 4 && decoder.info.header == Header(2, 1, 1, 1)); + decoder.param.header = Header(2, 2, 2, 2); + const std::uint8_t samples24[] = {0x00, 0x00, 0x01, 0xff, 0xff, 0xff}; + std::memcpy(decoder.input.data(), samples24, sizeof(samples24)); + decoder.au.ui_au_size = static_cast(decoder.input.size()); + decoder.pcm.ui_pcm_size = static_cast(decoder.output.size() * 4); + Require(sceAudiodecCpuInternalDecode(&decoder.ctrl, &decoder.memory, LpcmBd) == 0); + Require(decoder.param.auBytes == 5760 && decoder.au.ui_au_size == 5760 && decoder.pcm.ui_pcm_size == 1920 * 4); + Require(decoder.output[0] == 0x1p-23f && decoder.output[1] == -0x1p-23f); + Require(decoder.info.rateIndex == 2); +} + +void TestTimestamps() { + Decoder decoder; + Require(sceAudiodecCpuInitDecoder(&decoder.ctrl, &decoder.memory, LpcmBd) == 0); + Require(decoder.Decode(None, None) == 0); + Require(decoder.info.ptsHigh == None && decoder.info.ptsLow == None); + Require(decoder.Decode(0, 1000) == 0); + Require(decoder.info.ptsHigh == 0 && decoder.info.ptsLow == 1000); + Require(decoder.Decode(None, None) == 0); + Require(decoder.info.ptsHigh == 0 && decoder.info.ptsLow == 1450); + Require(decoder.Decode(None, None) == 0); + Require(decoder.info.ptsHigh == 0 && decoder.info.ptsLow == 1900); + Require(decoder.Decode(0, 0xffffff00u) == 0); + Require(decoder.Decode(None, None) == 0); + Require(decoder.info.ptsHigh == 1 && decoder.info.ptsLow == 0xc2); + Require(decoder.Decode(1, 0xffffff00u) == 0); + Require(decoder.Decode(None, None) == 0); + Require(decoder.info.ptsHigh == 0 && decoder.info.ptsLow == 0xc2); + Require(sceAudiodecCpuClearContext(&decoder.memory, LpcmBd) == 0); + Require(decoder.Decode(None, None) == 0); + Require(decoder.info.ptsHigh == None && decoder.info.ptsLow == None); + Require(decoder.Decode(7, 9) == 0); + Require(sceAudiodecCpuInitDecoder(&decoder.ctrl, &decoder.memory, LpcmBd) == 0); + Require(decoder.Decode(None, None) == 0); + Require(decoder.info.ptsHigh == None && decoder.info.ptsLow == None); +} + +} + +int main() { + TestQueryMemSize(); + TestInitAndClear(); + TestDecodeArguments(); + TestDecodeSamples(); + TestTimestamps(); + return 0; +} diff --git a/core/libs/tests/GuestAudiodecNative.cpp b/core/libs/tests/GuestAudiodecNative.cpp new file mode 100644 index 0000000000..3d16686ffd --- /dev/null +++ b/core/libs/tests/GuestAudiodecNative.cpp @@ -0,0 +1,31 @@ +#include "prx/libSceAudiodec.native/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "tests/ExportLookup.hpp" +#include + +int main(int argc, char** argv) { + struct Export { + int (APS5_VABI* function)(void); + const char* name; + }; + const Export exports[] = { + {sceAudiodecClearContextEx, "sceAudiodecClearContextEx"}, + {sceAudiodecCreateDecoderEx, "sceAudiodecCreateDecoderEx"}, + {sceAudiodecDecode2, "sceAudiodecDecode2"}, + {sceAudiodecDecode2Ex, "sceAudiodecDecode2Ex"}, + {sceAudiodecDecode2WithPriority, "sceAudiodecDecode2WithPriority"}, + {sceAudiodecDecode2WithPriorityEx, "sceAudiodecDecode2WithPriorityEx"}, + {sceAudiodecDecodeEx, "sceAudiodecDecodeEx"}, + {sceAudiodecDecodeWithPriority, "sceAudiodecDecodeWithPriority"}, + {sceAudiodecDecodeWithPriorityEx, "sceAudiodecDecodeWithPriorityEx"}, + {sceAudiodecDeleteDecoderEx, "sceAudiodecDeleteDecoderEx"}, + {sceAudiodecGetContext, "sceAudiodecGetContext"}, + {sceAudiodecInitialize, "sceAudiodecInitialize"}, + {sceAudiodecRegisterCodec, "sceAudiodecRegisterCodec"}, + {sceAudiodecTerminate, "sceAudiodecTerminate"}, + }; + if (!ExportsResolveByName(argc > 1 ? argv[1] : nullptr, exports, "sceAudiodecNotExported")) return 1; + std::puts("libSceAudiodec.native pending exports are exported"); + return 0; +} diff --git a/core/libs/tests/GuestAvPlayer.cpp b/core/libs/tests/GuestAvPlayer.cpp index 66cf4737fc..5457ccce01 100644 --- a/core/libs/tests/GuestAvPlayer.cpp +++ b/core/libs/tests/GuestAvPlayer.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -532,6 +533,108 @@ constexpr std::array WebmMovie{ 0xf7, 0x81, 0x01, 0xf1, 0x82, 0x02, 0x24, 0xf0, 0x81, 0x0c, }; +constexpr std::array AnamorphicMovie{ + 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, 0x00, 0x08, 0x66, 0x72, 0x65, 0x65, 0x00, 0x00, 0x02, 0xc3, 0x6d, 0x64, 0x61, 0x74, + 0x00, 0x00, 0x02, 0x6e, 0x06, 0x05, 0xff, 0xff, 0x6a, 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, 0x35, 0x20, 0x72, 0x33, 0x32, 0x32, 0x33, 0x20, 0x30, + 0x34, 0x38, 0x30, 0x63, 0x62, 0x30, 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, 0x35, 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, 0x31, 0x20, 0x72, 0x65, 0x66, 0x3d, + 0x31, 0x20, 0x64, 0x65, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3d, 0x31, 0x3a, 0x30, 0x3a, 0x30, 0x20, + 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x65, 0x3d, 0x30, 0x78, 0x33, 0x3a, 0x30, 0x78, 0x31, 0x31, + 0x33, 0x20, 0x6d, 0x65, 0x3d, 0x68, 0x65, 0x78, 0x20, 0x73, 0x75, 0x62, 0x6d, 0x65, 0x3d, 0x32, + 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, 0x31, 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, 0x31, 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, 0x34, 0x30, 0x20, 0x69, 0x6e, 0x74, 0x72, 0x61, + 0x5f, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x3d, 0x30, 0x20, 0x72, 0x63, 0x5f, 0x6c, 0x6f, + 0x6f, 0x6b, 0x61, 0x68, 0x65, 0x61, 0x64, 0x3d, 0x31, 0x30, 0x20, 0x72, 0x63, 0x3d, 0x63, 0x72, + 0x66, 0x20, 0x6d, 0x62, 0x74, 0x72, 0x65, 0x65, 0x3d, 0x31, 0x20, 0x63, 0x72, 0x66, 0x3d, 0x35, + 0x31, 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, 0x31, 0x3a, 0x31, 0x2e, 0x30, 0x30, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x2f, 0x65, 0x88, 0x84, 0x17, 0xff, 0x30, 0x37, 0x80, 0x85, 0x93, + 0x4f, 0x08, 0x26, 0x6f, 0xaa, 0x13, 0xe0, 0x1b, 0xfc, 0x85, 0xbe, 0x4c, 0x03, 0x8e, 0x75, 0x87, + 0x76, 0xfd, 0xb5, 0x8a, 0xee, 0x1c, 0xde, 0x2d, 0xe6, 0x2f, 0xaf, 0x27, 0x31, 0x33, 0x7f, 0x0b, + 0xd4, 0xc4, 0xf4, 0x72, 0x09, 0x00, 0x00, 0x00, 0x07, 0x41, 0x9a, 0x26, 0x22, 0xff, 0x3e, 0x60, + 0x00, 0x00, 0x00, 0x07, 0x41, 0x9a, 0x46, 0x22, 0x7f, 0x41, 0x41, 0x00, 0x00, 0x03, 0x38, 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, 0x00, 0x64, 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, 0x02, 0x00, + 0x00, 0x02, 0x63, 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, 0x00, 0x64, 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, 0x6a, 0xaa, 0xab, 0x00, + 0x3c, 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, 0x64, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0xdb, 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, 0x06, 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, 0x01, 0x86, + 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, 0x46, + 0x73, 0x74, 0x62, 0x6c, 0x00, 0x00, 0x00, 0xc2, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb2, 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, 0x64, 0x00, 0x3c, 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x14, 0x4c, 0x61, 0x76, 0x63, 0x36, 0x33, 0x2e, 0x31, 0x2e, + 0x31, 0x30, 0x30, 0x20, 0x6c, 0x69, 0x62, 0x78, 0x32, 0x36, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x38, 0x61, 0x76, + 0x63, 0x43, 0x01, 0x64, 0x00, 0x0a, 0xff, 0xe1, 0x00, 0x1c, 0x67, 0x64, 0x00, 0x0a, 0xac, 0xb4, + 0x39, 0x3c, 0xf7, 0xff, 0x80, 0x08, 0x00, 0x07, 0x88, 0x00, 0x00, 0x03, 0x00, 0x08, 0x00, 0x00, + 0x03, 0x01, 0xe0, 0x78, 0x91, 0x35, 0x01, 0x00, 0x05, 0x68, 0xef, 0x01, 0x97, 0x2c, 0xfd, 0xf8, + 0xf8, 0x00, 0x00, 0x00, 0x00, 0x10, 0x70, 0x61, 0x73, 0x70, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x14, 0x62, 0x74, 0x72, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xda, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 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, 0x1c, 0x73, 0x74, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x20, 0x73, 0x74, 0x73, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x00, 0x00, 0x02, 0xa5, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, + 0x00, 0x14, 0x73, 0x74, 0x63, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x00, 0x61, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x59, 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, 0x2c, 0x69, 0x6c, 0x73, 0x74, 0x00, + 0x00, 0x00, 0x24, 0xa9, 0x74, 0x6f, 0x6f, 0x00, 0x00, 0x00, 0x1c, 0x64, 0x61, 0x74, 0x61, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x61, 0x76, 0x66, 0x36, 0x33, 0x2e, 0x31, 0x2e, + 0x31, 0x30, 0x30 +}; + void Check(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } @@ -707,7 +810,7 @@ void CheckVideoPicture(const AvPlayerFrameInfoEx& info) { Check(video.width == 112 && video.height == 64 && video.pitch == Pitch, "unexpected frame geometry"); Check(video.crop_left_offset == 0 && video.crop_top_offset == 0 && video.crop_right_offset == Pitch - Width && video.crop_bottom_offset == 4, "unexpected crop offsets"); Check(video.luma_bit_depth == 8 && video.chroma_bit_depth == 8, "unexpected bit depth"); - Check(std::fabs(video.aspect_ratio - static_cast(Width) / Height) < 0.01f, "unexpected aspect ratio"); + Check(video.aspect_ratio == 0.0f, "unexpected aspect ratio"); Check(std::memcmp(video.language_code, "eng", 4) == 0, "missing video language"); const int index = FrameIndex(info.timestamp); Check(index >= 0 && index < FrameCount, "frame timestamp out of range"); @@ -781,7 +884,7 @@ void CheckStreamInfoEx(AvPlayerInternal* player) { Check(video.width == 112 && video.height == 64 && video.pitch == Pitch, "unexpected extended video geometry"); Check(video.crop_left_offset == 0 && video.crop_top_offset == 0 && video.crop_right_offset == Pitch - Width && video.crop_bottom_offset == 4, "unexpected extended crop offsets"); Check(video.luma_bit_depth == 8 && video.chroma_bit_depth == 8 && !video.video_full_range_flag, "unexpected extended video format"); - Check(std::fabs(video.aspect_ratio - static_cast(Width) / Height) < 0.01f, "unexpected extended aspect ratio"); + Check(video.aspect_ratio == 0.0f, "unexpected extended aspect ratio"); Check(std::memcmp(video.language_code, "eng", 4) == 0, "unexpected extended video language"); } else { const bool english = index == EnglishAudioStream; @@ -1070,6 +1173,35 @@ void TestExtendedExports() { Check(sceAvPlayerDisableStream(nullptr, 0) == InvalidParams, "null player disable stream rejected"); } +void CheckAspectRatios(std::span movie, bool videoOnly, float streamAspect, float frameAspect, const char* what) { + { + std::ofstream file("app0/aspect.mp4", std::ios::binary | std::ios::trunc); + file.write(reinterpret_cast(movie.data()), static_cast(movie.size())); + Check(static_cast(file), "cannot write the aspect movie"); + } + Events events; + AvPlayerInitData init = InitData(&events); + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "aspect init failed"); + Check(sceAvPlayerAddSource(player, "/app0/aspect.mp4") == 0, "aspect add source failed"); + Check(WaitFor([&] { return events.Seen(EventReady); }), "aspect ready event missing"); + AvPlayerStreamInfo info{}; + Check(sceAvPlayerGetStreamInfo(player, VideoStream, &info) == 0, "aspect stream info failed"); + Check(std::fabs(info.details.video.aspect_ratio - streamAspect) < 0.001f, std::string("unexpected stream aspect ratio for ") + what); + Check(sceAvPlayerEnableStream(player, VideoStream) == 0, "aspect enable video failed"); + if (videoOnly) Check(sceAvPlayerSetAvSyncMode(player, 1) == 0, "aspect no-sync mode rejected"); + Check(sceAvPlayerStart(player) == 0, "aspect start failed"); + AvPlayerFrameInfoEx frame{}; + Check(WaitFor([&] { return sceAvPlayerGetVideoDataEx(player, &frame) != 0; }), "aspect frame missing"); + Check(std::fabs(frame.details.video.aspect_ratio - frameAspect) < 0.001f, std::string("unexpected frame aspect ratio for ") + what); + Check(sceAvPlayerClose(player) == 0, "aspect close failed"); +} + +void TestSampleAspect() { + CheckAspectRatios(Movie, false, 0.0f, 0.0f, "a stream without a sample aspect ratio"); + CheckAspectRatios(AnamorphicMovie, true, 16.0f / 15.0f, 16.0f / 15.0f, "a 16:15 sample aspect ratio"); +} + void TestWithoutAllocators() { AvPlayerInitData init = InitData(nullptr); init.memory_replacement = {}; @@ -2049,8 +2181,78 @@ void TestTrailingVideoAfterShortAudio() { } +namespace { + +std::string replacedUrl; + +std::int32_t APS5_VABI OpenRemoteReplacement(void* object, const char* path) { + Check(path == replacedUrl, "file replacement did not receive the original URL"); + return OpenFile(object, "/app0/replaced.mp4"); +} + +void TestHttpSources(const std::string& base) { + WriteMovie(); + { + std::ofstream file(WebmPath, std::ios::binary | std::ios::trunc); + file.write(reinterpret_cast(WebmMovie.data()), static_cast(WebmMovie.size())); + } + for (const auto& [route, expected] : std::array, 4>{{ + {"/range/avplayer.mp4?token=one#fragment", FrameCount}, + {"/chunk/avplayer.webm", WebmVideoFrames}, + {"/redirect/avplayer.mp4", FrameCount}, + {"/ignore/avplayer.mp4", FrameCount}, + }}) { + Events events; + auto init = InitData(&events); + auto* player = sceAvPlayerInit(&init); + Check(player != nullptr, "HTTP init failed"); + Check(sceAvPlayerAddSource(player, (base + route).c_str()) == 0, "HTTP add source failed"); + Check(WaitFor([&] { return events.Seen(EventReady); }), "HTTP ready event missing"); + Check(sceAvPlayerEnableStream(player, 0) == 0, "HTTP video enable failed"); + Check(sceAvPlayerSetAvSyncMode(player, 1) == 0 && sceAvPlayerStart(player) == 0, "HTTP video start failed"); + int frames = 0; + AvPlayerFrameInfoEx info{}; + Check(WaitFor([&] { + if (sceAvPlayerGetVideoDataEx(player, &info)) { + CheckVideoFrame(info); + ++frames; + } + return !sceAvPlayerIsActive(player); + }), "HTTP playback did not finish"); + Check(frames == expected, "HTTP delivered " + std::to_string(frames) + " frames instead of " + std::to_string(expected)); + Check(sceAvPlayerClose(player) == 0, "HTTP close failed"); + } + Events events; + auto init = InitData(&events); + auto* player = sceAvPlayerInit(&init); + for (const auto& path : {base + "/bad/avplayer.mp4", base + "/range/no\r\nheader.mp4"}) { + Check(sceAvPlayerAddSource(player, path.c_str()) == OperationFailed, "invalid HTTP response or URL accepted"); + } + bool callbackFailure = false; + try { sceAvPlayerAddSource(player, (base + "/bad-late/avplayer.mp4").c_str()); } + catch (const std::runtime_error&) { callbackFailure = true; } + Check(callbackFailure, "late HTTP callback failure did not survive the C ABI boundary"); + Check(sceAvPlayerClose(player) == 0, "failed HTTP player close failed"); + HostFile file; + init = InitData(&events); + init.file_replacement = {&file, OpenRemoteReplacement, CloseFile, ReadFile, FileSize}; + replacedUrl = "https://no-network/replaced.mp4"; + player = sceAvPlayerInit(&init); + Check(sceAvPlayerAddSource(player, replacedUrl.c_str()) == 0 && sceAvPlayerStreamCount(player) == 3, "file replacement did not take priority over HTTPS transport"); + Check(sceAvPlayerClose(player) == 0, "replacement HTTP player close failed"); + std::lock_guard lock(allocations.mutex); + Check(allocations.blocks.empty(), "HTTP playback allocations leaked"); +} + +} + int main(int argc, char** argv) { try { + if (argc == 3 && std::strcmp(argv[1], "--http") == 0) { + TestHttpSources(argv[2]); + std::puts("AvPlayer HTTP source tests passed"); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--callback-close") == 0) { WriteMovie(); TestCloseFromEventCallback(EventReady); @@ -2074,6 +2276,7 @@ int main(int argc, char** argv) { TestVp9OpusPlayback(); TestChangeStream(); TestExtendedExports(); + TestSampleAspect(); TestWithoutAllocators(); TestOptionalVideoBuffersRespectMemoryLimit(); TestFileReplacementAutoStart(); diff --git a/core/libs/tests/GuestAvPlayerNative.cpp b/core/libs/tests/GuestAvPlayerNative.cpp new file mode 100644 index 0000000000..bf694893fa --- /dev/null +++ b/core/libs/tests/GuestAvPlayerNative.cpp @@ -0,0 +1,19 @@ +#include "prx/libSceAvPlayer.native/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "tests/ExportLookup.hpp" +#include + +int main(int argc, char** argv) { + struct Export { + int (APS5_VABI* function)(void); + const char* name; + }; + const Export exports[] = { + {sceAvPlayerPrintf, "sceAvPlayerPrintf"}, + {sceAvPlayerVprintf, "sceAvPlayerVprintf"}, + }; + if (!ExportsResolveByName(argc > 1 ? argv[1] : nullptr, exports, "sceAvPlayerNotExported")) return 1; + std::puts("libSceAvPlayer.native pending exports are exported"); + return 0; +} diff --git a/core/libs/tests/GuestAvSettingPredicates.cpp b/core/libs/tests/GuestAvSettingPredicates.cpp new file mode 100644 index 0000000000..1d2196a649 --- /dev/null +++ b/core/libs/tests/GuestAvSettingPredicates.cpp @@ -0,0 +1,111 @@ +#include "prx/libSceAvSetting/include/Api.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libc/include/GuestHeap.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "AVSETTING PREDICATES: %s\n", message); + std::abort(); +} + +void Write(void* destination, const void* source, std::size_t bytes) { + const GuestArena::HostWrite access(destination, bytes); + Require(access.Open(), "guest fixture memory was not writable"); + std::memcpy(destination, source, bytes); +} + +template +void WriteField(void* object, std::size_t offset, TValue value) { + Write(static_cast(object) + offset, &value, sizeof(value)); +} + +void* Allocate(std::size_t bytes) { + void* memory = GuestHeap::GuestHeapAllocate_nid_postfix(bytes); + Require(memory != nullptr, "guest fixture allocation failed"); + const std::array zeros{}; + auto* destination = static_cast(memory); + for (std::size_t offset = 0; offset < bytes; offset += zeros.size()) { + const auto count = bytes - offset < zeros.size() ? bytes - offset : zeros.size(); + Write(destination + offset, zeros.data(), count); + } + return memory; +} + +void TestColorimetry() { + Require(!sceAvControlIsColorimetryHdr(8), "colorimetry below the table range was accepted"); + for (std::uint32_t value = 9; value <= 20; ++value) { + Require(sceAvControlIsColorimetryHdr(value) == (value != 18), "colorimetry table result is wrong"); + } + Require(!sceAvControlIsColorimetryHdr(21), "colorimetry above the table range was accepted"); +} + +void TestDeepColor(void* monitorInfo) { + Require(!sceAvControlIsDeepColorSupportedByMonitorInfo(nullptr), "null monitor info was accepted"); + Require(!sceAvControlIsDeepColorSupportedByMonitorInfo(reinterpret_cast(std::uintptr_t{1})), + "unmapped monitor info was accepted"); + Require(!sceAvControlIsDeepColorSupportedByMonitorInfo(monitorInfo), "empty monitor info was accepted"); + + WriteField(monitorInfo, 0x74, std::uint8_t{0x02}); + Require(sceAvControlIsDeepColorSupportedByMonitorInfo(monitorInfo), "first deep-color mask was ignored"); + WriteField(monitorInfo, 0x74, std::uint8_t{0}); + WriteField(monitorInfo, 0x75, std::uint8_t{0x08}); + Require(sceAvControlIsDeepColorSupportedByMonitorInfo(monitorInfo), "second deep-color mask was ignored"); + WriteField(monitorInfo, 0x75, std::uint8_t{0}); + WriteField(monitorInfo, 0x352, std::uint8_t{0x02}); + Require(sceAvControlIsDeepColorSupportedByMonitorInfo(monitorInfo), "extended deep-color mask was ignored"); + WriteField(monitorInfo, 0x352, std::uint8_t{0x04}); + Require(!sceAvControlIsDeepColorSupportedByMonitorInfo(monitorInfo), "unrelated extended bits were accepted"); +} + +void TestHdr(void* monitorInfo) { + Require(!sceAvControlIsHdrSupportedByMonitorInfo(nullptr), "null monitor info was accepted"); + Require(!sceAvControlIsHdrSupportedByMonitorInfo(reinterpret_cast(std::uintptr_t{1})), + "unmapped monitor info was accepted"); + Require(!sceAvControlIsHdrSupportedByMonitorInfo(monitorInfo), "empty monitor info was accepted"); + + WriteField(monitorInfo, 0x78, std::uint8_t{0x10}); + Require(!sceAvControlIsHdrSupportedByMonitorInfo(monitorInfo), "HDR was accepted without the capability bit"); + WriteField(monitorInfo, 0x358, std::uint8_t{0x04}); + Require(sceAvControlIsHdrSupportedByMonitorInfo(monitorInfo), "HDR flags were not combined correctly"); + WriteField(monitorInfo, 0x78, std::uint8_t{0x40}); + Require(!sceAvControlIsHdrSupportedByMonitorInfo(monitorInfo), "unrelated HDR flags were accepted"); +} + +void TestModeFlags(void* videoOutMode) { + Require(!sceAvControlIsModeGen1Vr(nullptr), "null video mode was accepted by Gen1 VR query"); + Require(!sceAvControlIsModeSystemDefault(nullptr), "null video mode was accepted by default-mode query"); + Require(!sceAvControlIsModeGen1Vr(reinterpret_cast(std::uintptr_t{1})), + "unmapped video mode was accepted by Gen1 VR query"); + Require(!sceAvControlIsModeSystemDefault(reinterpret_cast(std::uintptr_t{1})), + "unmapped video mode was accepted by default-mode query"); + Require(sceAvControlIsModeSystemDefault(videoOutMode), "clear system-default bit was not recognized"); + Require(!sceAvControlIsModeGen1Vr(videoOutMode), "clear Gen1 VR bit was accepted"); + + WriteField(videoOutMode, 0x30, std::uint32_t{0x02}); + Require(sceAvControlIsModeGen1Vr(videoOutMode), "Gen1 VR bit was ignored"); + Require(sceAvControlIsModeSystemDefault(videoOutMode), "unrelated Gen1 VR bit changed default-mode result"); + WriteField(videoOutMode, 0x30, std::uint32_t{0x03}); + Require(!sceAvControlIsModeSystemDefault(videoOutMode), "set system-default bit was accepted"); +} + +} + +int main() { + auto* monitorInfo = Allocate(0x360); + auto* videoOutMode = Allocate(0x34); + TestColorimetry(); + TestDeepColor(monitorInfo); + TestHdr(monitorInfo); + TestModeFlags(videoOutMode); + std::puts("AvSetting predicate tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestAvailableCpumask.cpp b/core/libs/tests/GuestAvailableCpumask.cpp index 8dfead909d..9f2a6ea956 100644 --- a/core/libs/tests/GuestAvailableCpumask.cpp +++ b/core/libs/tests/GuestAvailableCpumask.cpp @@ -83,8 +83,8 @@ static void* APS5_VABI ReportCpuset(void* arg) { result[0] = cpuset_getaffinity_nid_postfix(3, 1, -1, 8, set); result[1] = set[0]; result[2] = set[1]; - result[3] = cpuset_getaffinity_nid_postfix(3, 1, -1, sizeof(set), set); - result[4] = set[0] | (set[1] != 0 || set[2] != 0 || set[3] != 0 ? 1ull << 63 : 0); + result[3] = cpuset_getaffinity_nid_postfix(3, 1, -1, 16, set); + result[4] = set[0] | (set[1] != 0 ? 1ull << 63 : 0); result[5] = cpuset_getaffinity_nid_postfix(3, 1, -1, 4, set) == -1 && *__error_nid_postfix() == 34; result[6] = cpuset_getaffinity_nid_postfix(3, 1, -1, 33, set) == -1 && *__error_nid_postfix() == 34; result[7] = cpuset_getaffinity_nid_postfix(3, 1, -1, 8, nullptr) == -1 && *__error_nid_postfix() == 14; @@ -107,7 +107,7 @@ static void* APS5_VABI SetCpuset(void* arg) { set[1] = 1; result->results[1] = cpuset_setaffinity_nid_postfix(3, 1, -1, 16, set) == -1 && *__error_nid_postfix() == 22; set[1] = 0; - result->results[2] = cpuset_setaffinity_nid_postfix(3, 1, -1, sizeof(set), set); + result->results[2] = cpuset_setaffinity_nid_postfix(3, 1, -1, 16, set); result->results[3] = cpuset_setaffinity_nid_postfix(3, 1, -1, 4, set) == -1 && *__error_nid_postfix() == 34; result->results[4] = cpuset_setaffinity_nid_postfix(3, 1, -1, 33, set) == -1 && *__error_nid_postfix() == 34; result->results[5] = cpuset_setaffinity_nid_postfix(3, 1, -1, 8, nullptr) == -1 && *__error_nid_postfix() == 14; @@ -220,8 +220,8 @@ int main() { } std::uint64_t processSet[4] = {~0ull, ~0ull, ~0ull, ~0ull}; - Require(cpuset_getaffinity_nid_postfix(3, 2, -1, sizeof(processSet), processSet) == 0); - Require(processSet[0] == available && processSet[1] == 0 && processSet[2] == 0 && processSet[3] == 0); + Require(cpuset_getaffinity_nid_postfix(3, 2, -1, 16, processSet) == 0); + Require(processSet[0] == available && processSet[1] == 0 && processSet[2] == ~0ull && processSet[3] == ~0ull); processSet[0] = 0; Require(cpuset_getaffinity_nid_postfix(3, 2, getpid_nid_postfix(), 8, processSet) == 0 && processSet[0] == available); Require(cpuset_getaffinity_nid_postfix(3, 2, -1, 4, processSet) == -1 && *__error_nid_postfix() == GUEST_ERANGE); @@ -280,11 +280,26 @@ int main() { Require(ReadCpuset(pthread_self_nid_postfix()) == available); std::uint8_t rawSet[GUEST_CPUSET_BYTES]{}; - for (const std::size_t size : {std::size_t{32}, std::size_t{33}, std::size_t{64}}) + for (const std::size_t size : {std::size_t{24}, std::size_t{32}, std::size_t{33}, std::size_t{64}}) Require(pthread_getaffinity_np_nid_postfix(pthread_self_nid_postfix(), size, rawSet) == GUEST_ERANGE); + for (const std::size_t size : {std::size_t{0}, std::size_t{1}, std::size_t{4}, std::size_t{7}}) + Require(pthread_getaffinity_np_nid_postfix(pthread_self_nid_postfix(), size, rawSet) == GUEST_ERANGE); + for (const std::size_t size : {std::size_t{9}, std::size_t{12}, std::size_t{15}}) { + std::memset(rawSet, 0xff, sizeof(rawSet)); + Require(pthread_getaffinity_np_nid_postfix(pthread_self_nid_postfix(), size, rawSet) == 0); + KernelCpumask narrow = 0; + std::memcpy(&narrow, rawSet, sizeof(narrow)); + Require(narrow == available); + for (std::size_t index = sizeof(narrow); index < sizeof(rawSet); ++index) Require(rawSet[index] == (index < size ? 0 : 0xff)); + } Require(pthread_getaffinity_np_nid_postfix(nullptr, GUEST_CPUSET_BYTES, rawSet) == GUEST_EINVAL); Require(pthread_getaffinity_np_nid_postfix(nullptr, 0, rawSet) == GUEST_EINVAL); Require(pthread_getaffinity_np_nid_postfix(pthread_self_nid_postfix(), 16, nullptr) == GUEST_EFAULT); + auto* const badSet = reinterpret_cast(0x10); + Require(pthread_getaffinity_np_nid_postfix(pthread_self_nid_postfix(), 16, badSet) == GUEST_EFAULT); + Require(pthread_setaffinity_np_nid_postfix(pthread_self_nid_postfix(), GUEST_CPUSET_BYTES, badSet) == GUEST_EFAULT); + Require(cpuset_getaffinity_nid_postfix(3, 1, -1, 8, badSet) == -1 && *__error_nid_postfix() == GUEST_EFAULT); + Require(cpuset_setaffinity_nid_postfix(3, 1, -1, 8, badSet) == -1 && *__error_nid_postfix() == GUEST_EFAULT); Require(cpuset_getaffinity_nid_postfix(3, 1, scePthreadGetthreadid(), 8, processSet) == 0 && processSet[0] == available); Require(cpuset_getaffinity_nid_postfix(3, 1, 999999, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_ESRCH); Require(cpuset_getaffinity_nid_postfix(3, 2, 999999, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_ESRCH); @@ -296,8 +311,8 @@ int main() { Require(cpuset_setaffinity_nid_postfix(3, 1, 999999, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_ESRCH); Require(cpuset_setaffinity_nid_postfix(3, 2, 999999, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_ESRCH); Require(cpuset_setaffinity_nid_postfix(99, 1, -1, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_EINVAL); - Require(cpuset_setaffinity_nid_postfix(99, 1, 999999, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_ESRCH); - Require(cpuset_setaffinity_nid_postfix(99, 2, 999999, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_ESRCH); + Require(cpuset_setaffinity_nid_postfix(99, 1, 999999, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + Require(cpuset_setaffinity_nid_postfix(99, 2, 999999, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_EINVAL); Require(cpuset_setaffinity_nid_postfix(3, 99, -1, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_EINVAL); const auto throws = [](auto&& call) { try { @@ -307,14 +322,71 @@ int main() { return true; } }; - Require(throws([&] { cpuset_getaffinity_nid_postfix(1, 1, -1, 8, processSet); })); - Require(throws([&] { cpuset_getaffinity_nid_postfix(2, 1, -1, 8, processSet); })); - Require(throws([&] { cpuset_getaffinity_nid_postfix(3, 3, -1, 8, processSet); })); Require(throws([&] { cpuset_getaffinity_nid_postfix(3, 4, -1, 8, processSet); })); + std::uint64_t pinnedSelf[4] = {available & (~available + 1), 0, 0, 0}; + Require(cpuset_setaffinity_nid_postfix(3, 1, -1, 8, pinnedSelf) == 0); + for (const int which : {1, 2}) { + for (const std::size_t size : {std::size_t{8}, std::size_t{16}}) { + std::uint64_t base[4] = {~0ull, ~0ull, ~0ull, ~0ull}; + Require(cpuset_getaffinity_nid_postfix(2, which, -1, size, base) == 0); + Require(base[0] == available && (size == 8 || base[1] == 0)); + Require(base[2] == ~0ull && base[3] == ~0ull); + } + for (const int level : {1, 2}) { + std::uint64_t set[4] = {0, ~0ull, 0, 0}; + Require(cpuset_getaffinity_nid_postfix(level, which, -1, 16, set) == 0); + Require(set[0] == (level == 1 ? 0xffffull : available) && set[1] == 0); + } + } + for (const std::size_t size : {std::size_t{0}, std::size_t{7}, std::size_t{24}, std::size_t{32}, std::size_t{64}}) { + std::uint64_t sized[8]{}; + *__error_nid_postfix() = 0; + Require(cpuset_getaffinity_nid_postfix(2, 1, -1, size, sized) == -1 && *__error_nid_postfix() == GUEST_ERANGE); + *__error_nid_postfix() = 0; + Require(cpuset_setaffinity_nid_postfix(3, 1, -1, size, sized) == -1 && *__error_nid_postfix() == GUEST_ERANGE); + } + for (const std::size_t size : {std::size_t{9}, std::size_t{12}}) { + std::uint64_t sized[4] = {0, ~0ull, ~0ull, ~0ull}; + Require(cpuset_getaffinity_nid_postfix(2, 1, -1, size, sized) == 0 && sized[0] == available && sized[2] == ~0ull); + } + { + std::uint64_t root = 0; + Require(cpuset_getaffinity_nid_postfix(1, 3, -1, 8, &root) == 0 && root == 0xffff); + root = 0; + Require(cpuset_getaffinity_nid_postfix(1, 1, scePthreadGetthreadid(), 8, &root) == 0 && root == 0xffff); + *__error_nid_postfix() = 0; + Require(cpuset_getaffinity_nid_postfix(1, 1, 999999, 8, &root) == -1 && *__error_nid_postfix() == GUEST_ESRCH); + } + std::uint64_t base[4]{}; + Require(cpuset_getaffinity_nid_postfix(2, 1, scePthreadGetthreadid(), 8, base) == 0 && base[0] == available); + Require(cpuset_getaffinity_nid_postfix(2, 3, -1, 8, base) == 0 && base[0] == available); + Require(cpuset_getaffinity_nid_postfix(3, 3, -1, 8, base) == 0 && base[0] == available); + Require(cpuset_getaffinity_nid_postfix(3, 1, -1, 8, base) == 0 && base[0] == pinnedSelf[0]); + Require(cpuset_getaffinity_nid_postfix(2, 1, 999999, 8, base) == -1 && *__error_nid_postfix() == GUEST_ESRCH); + for (const int level : {1, 2}) { + for (const int which : {4, 6}) { + *__error_nid_postfix() = 0; + Require(cpuset_getaffinity_nid_postfix(level, which, -1, 8, base) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + } + } + Require(cpuset_getaffinity_nid_postfix(3, 7, -1, 8, base) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + Require(cpuset_getaffinity_nid_postfix(3, 8, -1, 8, base) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + pinnedSelf[0] = available; + Require(cpuset_setaffinity_nid_postfix(3, 1, -1, 8, pinnedSelf) == 0); Require(throws([&] { cpuset_setaffinity_nid_postfix(1, 1, -1, 8, processSet); })); Require(throws([&] { cpuset_setaffinity_nid_postfix(2, 1, -1, 8, processSet); })); Require(throws([&] { cpuset_setaffinity_nid_postfix(3, 3, -1, 8, processSet); })); Require(throws([&] { cpuset_setaffinity_nid_postfix(3, 4, -1, 8, processSet); })); + for (const int level : {1, 2}) { + for (const int which : {4, 6, 7, 8, 99}) { + *__error_nid_postfix() = 0; + Require(cpuset_setaffinity_nid_postfix(level, which, -1, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_EINVAL); + } + *__error_nid_postfix() = 0; + Require(cpuset_setaffinity_nid_postfix(level, 1, 999999, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_ESRCH); + } + *__error_nid_postfix() = 0; + Require(cpuset_setaffinity_nid_postfix(3, 6, 0, 8, processSet) == -1 && *__error_nid_postfix() == GUEST_EINVAL); std::promise holdOther; auto heldOther = holdOther.get_future(); RemoteThread otherArg; @@ -324,14 +396,14 @@ int main() { while (otherArg.tid.load(std::memory_order_acquire) == 0) std::this_thread::yield(); const int otherTid = otherArg.tid.load(std::memory_order_acquire); std::uint64_t remoteSet[5] = {~0ull, ~0ull, ~0ull, ~0ull, ~0ull}; - Require(cpuset_getaffinity_nid_postfix(3, 1, otherTid, GUEST_CPUSET_BYTES, remoteSet) == 0); - Require(remoteSet[0] == available && remoteSet[1] == 0 && remoteSet[2] == 0 && remoteSet[3] == 0 && remoteSet[4] == ~0ull); + Require(cpuset_getaffinity_nid_postfix(3, 1, otherTid, 16, remoteSet) == 0); + Require(remoteSet[0] == available && remoteSet[1] == 0 && remoteSet[2] == ~0ull && remoteSet[3] == ~0ull && remoteSet[4] == ~0ull); const std::uint64_t remoteMask[4] = {last | (1ull << 40), 0, 0, 0}; - Require(cpuset_setaffinity_nid_postfix(3, 1, otherTid, sizeof(remoteMask), remoteMask) == 0); + Require(cpuset_setaffinity_nid_postfix(3, 1, otherTid, 16, remoteMask) == 0); Require(ReadCpuset(otherThread) == last); Require(ReadCpuset(pthread_self_nid_postfix()) == available); Require(cpuset_getaffinity_nid_postfix(3, 1, otherTid, 8, remoteSet) == 0 && remoteSet[0] == last); - const std::uint64_t invalidRemote[4] = {first, 0, 0, 1}; + const std::uint64_t invalidRemote[2] = {first, 1}; Require(cpuset_setaffinity_nid_postfix(3, 1, otherTid, sizeof(invalidRemote), invalidRemote) == -1 && *__error_nid_postfix() == GUEST_EINVAL); const std::uint64_t unavailableRemote[1] = {1ull << 40}; Require(cpuset_setaffinity_nid_postfix(3, 1, otherTid, sizeof(unavailableRemote), unavailableRemote) == -1 && *__error_nid_postfix() == GUEST_EDEADLK); diff --git a/core/libs/tests/GuestBadOptionalAccess.cpp b/core/libs/tests/GuestBadOptionalAccess.cpp index 0b9d8edfe9..07709bab83 100644 --- a/core/libs/tests/GuestBadOptionalAccess.cpp +++ b/core/libs/tests/GuestBadOptionalAccess.cpp @@ -3,40 +3,147 @@ #include #include #include +#include -struct TypeRecord { const void* const* vtable; const char* name; const TypeRecord* base; }; +struct Vtable { std::ptrdiff_t offset; const std::type_info* type; const void* destroy; const void* deleteObject; const char* (APS5_VABI *what)(const LibcException::ExceptionObject*); }; -struct Vtable { std::ptrdiff_t offset; const TypeRecord* type; const void* destroy; const void* deleteObject; const char* (APS5_VABI *what)(const LibcException::ExceptionObject*); }; +struct AbiRecord { const void* const* vtable; const char* name; const void* base; }; +struct PointerRecord { const void* const* vtable; const char* name; unsigned flags; const void* pointee; }; +struct AbiVtable { + std::ptrdiff_t offset; + const void* type; + const void* destroy; + const void* deleteObject; + const void* isPointer; + const void* isFunction; + bool (APS5_VABI *catchType)(const void*, const void*, void**, unsigned); +}; + +struct SiTypeRecord { const void* const* vtable; const char* name; const std::type_info* base; }; +struct PointerTypeRecord { const void* const* vtable; const char* name; unsigned flags; const std::type_info* pointee; }; extern "C" { -extern const TypeRecord _ZTISt9exception_nid_postfix; -extern const TypeRecord _ZTISt19bad_optional_access_nid_postfix; -extern const Vtable _ZTVSt19bad_optional_access_nid_postfix; - +extern __cxxabiv1::__class_type_info _ZTISt9exception_nid_postfix; +extern __cxxabiv1::__si_class_type_info _ZTISt19bad_optional_access_nid_postfix; +extern Vtable _ZTVSt19bad_optional_access_nid_postfix; +extern std::type_info _ZTIi_nid_postfix; +extern std::type_info _ZTIj_nid_postfix; +extern std::type_info _ZTIf_nid_postfix; +extern std::type_info _ZTIDn_nid_postfix; +extern void* const _ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix[]; +extern SiTypeRecord _ZTISt9type_info_nid_postfix; +extern SiTypeRecord _ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix; +extern SiTypeRecord _ZTIN10__cxxabiv117__class_type_infoE_nid_postfix; +extern SiTypeRecord _ZTIN10__cxxabiv117__pbase_type_infoE_nid_postfix; +extern SiTypeRecord _ZTIN10__cxxabiv119__pointer_type_infoE_nid_postfix; +extern SiTypeRecord _ZTIN10__cxxabiv120__si_class_type_infoE_nid_postfix; +extern PointerTypeRecord _ZTIPi_nid_postfix; +extern PointerTypeRecord _ZTIPKi_nid_postfix; +extern PointerTypeRecord _ZTIPf_nid_postfix; +extern PointerTypeRecord _ZTIPv_nid_postfix; +extern PointerTypeRecord _ZTIPKv_nid_postfix; +extern void* const _ZTVN10__cxxabiv117__class_type_infoE_nid_postfix[]; +extern void* const _ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix[]; +extern void* const _ZTVN10__cxxabiv119__pointer_type_infoE_nid_postfix[]; +extern void* const _ZTVN10__cxxabiv117__pbase_type_infoE_nid_postfix[]; } static void Require(bool value) { if (!value) std::abort(); } +static void* const* Vptr(const std::type_info& record) { + return *reinterpret_cast(&record); +} + +static void TestFundamentalRecords() { + void* const* const fundamental = &_ZTVN10__cxxabiv123__fundamental_type_infoE_nid_postfix[2]; + const std::type_info* records[] = {&_ZTIi_nid_postfix, &_ZTIj_nid_postfix, &_ZTIf_nid_postfix, &_ZTIDn_nid_postfix}; + for (const std::type_info* record : records) + Require(Vptr(*record) == fundamental); + Require(std::strcmp(_ZTIi_nid_postfix.name(), "i") == 0 && std::strcmp(_ZTIDn_nid_postfix.name(), "Dn") == 0); + const auto* recordType = static_cast(Vptr(_ZTIi_nid_postfix)[-1]); + Require(std::strcmp(recordType->name(), "N10__cxxabiv123__fundamental_type_infoE") == 0); + + using CatchType = bool (APS5_VABI*)(const std::type_info*, const std::type_info*, void**, unsigned); + const auto catchType = reinterpret_cast(Vptr(_ZTIi_nid_postfix)[4]); + int thrownValue = 0; + void* thrownObject = &thrownValue; + Require(catchType(&_ZTIi_nid_postfix, &_ZTIi_nid_postfix, &thrownObject, 0)); + Require(!catchType(&_ZTIi_nid_postfix, &_ZTIj_nid_postfix, &thrownObject, 0)); + Require(!catchType(&_ZTIf_nid_postfix, &_ZTIi_nid_postfix, &thrownObject, 0)); + Require(!catchType(&_ZTIDn_nid_postfix, &_ZTIi_nid_postfix, &thrownObject, 0)); + + void* exception = __cxa_allocate_exception_nid_postfix(sizeof(int)); + *static_cast(exception) = 42; + bool caught = false; + try { + __cxa_throw_nid_postfix(exception, &_ZTIi_nid_postfix, nullptr); + } catch (...) { + caught = __cxa_current_exception_type_nid_postfix() == &_ZTIi_nid_postfix; + } + Require(caught); +} + +static void TestAbiAndPointerRecords() { + const void* const* classAddressPoint = _ZTVN10__cxxabiv117__class_type_infoE_nid_postfix + 2; + const void* const* siAddressPoint = _ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix + 2; + const void* const* pointerAddressPoint = _ZTVN10__cxxabiv119__pointer_type_infoE_nid_postfix + 2; + Require(_ZTISt9type_info_nid_postfix.vtable == classAddressPoint && _ZTISt9type_info_nid_postfix.base == nullptr && + std::strcmp(_ZTISt9type_info_nid_postfix.name, "St9type_info") == 0); + Require(_ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix.vtable == siAddressPoint && + _ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix.base == + reinterpret_cast(&_ZTISt9type_info_nid_postfix)); + Require(_ZTIN10__cxxabiv117__class_type_infoE_nid_postfix.base == + reinterpret_cast(&_ZTIN10__cxxabiv116__shim_type_infoE_nid_postfix)); + Require(_ZTIN10__cxxabiv120__si_class_type_infoE_nid_postfix.base == + reinterpret_cast(&_ZTIN10__cxxabiv117__class_type_infoE_nid_postfix)); + Require(_ZTIN10__cxxabiv119__pointer_type_infoE_nid_postfix.base == + reinterpret_cast(&_ZTIN10__cxxabiv117__pbase_type_infoE_nid_postfix)); + Require(_ZTIPi_nid_postfix.vtable == pointerAddressPoint && _ZTIPKi_nid_postfix.vtable == pointerAddressPoint && + _ZTIPv_nid_postfix.vtable == pointerAddressPoint && _ZTIPKv_nid_postfix.vtable == pointerAddressPoint); + Require(std::strcmp(_ZTIPi_nid_postfix.name, "Pi") == 0 && std::strcmp(_ZTIPKi_nid_postfix.name, "PKi") == 0); + Require(_ZTIPi_nid_postfix.flags == 0 && _ZTIPKi_nid_postfix.flags == 1 && _ZTIPKv_nid_postfix.flags == 1); + Require(_ZTIPi_nid_postfix.pointee == &_ZTIi_nid_postfix && _ZTIPKi_nid_postfix.pointee == &_ZTIi_nid_postfix); + Require(_ZTIPi_nid_postfix.vtable[-1] == reinterpret_cast(&_ZTIN10__cxxabiv119__pointer_type_infoE_nid_postfix)); + Require(_ZTVN10__cxxabiv119__pointer_type_infoE_nid_postfix[1] == + reinterpret_cast(&_ZTIN10__cxxabiv119__pointer_type_infoE_nid_postfix)); + Require(_ZTVN10__cxxabiv117__pbase_type_infoE_nid_postfix[1] == + reinterpret_cast(&_ZTIN10__cxxabiv117__pbase_type_infoE_nid_postfix)); + + using CatchType = bool (APS5_VABI*)(const std::type_info*, const std::type_info*, void**, unsigned); + const auto catchType = reinterpret_cast(const_cast(_ZTIPi_nid_postfix.vtable[4])); + const auto* pi = reinterpret_cast(&_ZTIPi_nid_postfix); + const auto* pki = reinterpret_cast(&_ZTIPKi_nid_postfix); + const auto* pf = reinterpret_cast(&_ZTIPf_nid_postfix); + const auto* pv = reinterpret_cast(&_ZTIPv_nid_postfix); + const auto* pkv = reinterpret_cast(&_ZTIPKv_nid_postfix); + void* pointerObject = nullptr; + Require(catchType(pki, pi, &pointerObject, 0)); + Require(!catchType(pi, pki, &pointerObject, 0)); + Require(catchType(pkv, pi, &pointerObject, 0)); + Require(catchType(pv, pi, &pointerObject, 0)); + Require(!catchType(pi, pf, &pointerObject, 0)); + Require(catchType(pi, &_ZTIDn_nid_postfix, &pointerObject, 0)); +} + int main() { const auto& type = _ZTISt19bad_optional_access_nid_postfix; - Require(std::strcmp(type.name, "St19bad_optional_access") == 0); - Require(type.base == &_ZTISt9exception_nid_postfix); + Require(std::strcmp(type.name(), "St19bad_optional_access") == 0); + Require(type.__base_type == &_ZTISt9exception_nid_postfix); const auto& table = _ZTVSt19bad_optional_access_nid_postfix; Require(table.offset == 0 && table.type == &type && table.what != nullptr); 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; } Require(thrown); + TestFundamentalRecords(); + TestAbiAndPointerRecords(); return 0; } diff --git a/core/libs/tests/GuestBeisobmfBoxes.cpp b/core/libs/tests/GuestBeisobmfBoxes.cpp index 665b07248a..076d37acb8 100644 --- a/core/libs/tests/GuestBeisobmfBoxes.cpp +++ b/core/libs/tests/GuestBeisobmfBoxes.cpp @@ -125,18 +125,37 @@ int main() { "null member-type result is wrong"); Require(isobmf_box_getmember_ptr_nid_postfix(box, nullptr) == nullptr, "null member name result is wrong"); + Require(isobmf_box_getmember_length_nid_postfix(box, nullptr) == 0, + "null member name length result is wrong"); + Require(isobmf_box_getmember_type_nid_postfix(box, nullptr) == 0, + "null member name type result is wrong"); auto *badPointer = reinterpret_cast(std::uintptr_t{1}); Require(isobmf_box_gettype_nid_postfix(badPointer) == 0, "unmapped box result is wrong"); + Require(isobmf_box_getchildbox_nid_postfix(badPointer) == nullptr, "unmapped child box result is wrong"); + Require(isobmf_box_getdiskoffset_nid_postfix(badPointer) == 0, "unmapped disk-offset result is wrong"); + Require(isobmf_box_getnextbox_nid_postfix(badPointer) == nullptr, "unmapped next box result is wrong"); + Require(isobmf_box_getstate_nid_postfix(badPointer) == 0, "unmapped state result is wrong"); + Require(isobmf_box_getflags32b_nid_postfix(badPointer) == 0, "unmapped flags box result is wrong"); Require(isobmf_decoder_next_disk_offset_nid_postfix(badPointer) == 0xffffffff, "unmapped decoder did not return its safe sentinel"); auto *badName = reinterpret_cast(std::uintptr_t{1}); Require(isobmf_box_getmember_ptr_nid_postfix(box, badName) == nullptr, "unmapped member name result is wrong"); + Require(isobmf_box_getmember_length_nid_postfix(box, badName) == 0, + "unmapped member name length result is wrong"); + Require(isobmf_box_getmember_type_nid_postfix(box, badName) == 0, + "unmapped member name type result is wrong"); auto *badListBox = static_cast(Allocate(0xa0)); WriteField(badListBox, 0x40, std::uintptr_t{1}); Require(isobmf_box_getmember_ptr_nid_postfix(badListBox, "missing") == nullptr, "unmapped member list result is wrong"); + Require(isobmf_box_getmember_length_nid_postfix(badListBox, "missing") == 0, + "unmapped member list length result is wrong"); + Require(isobmf_box_getmember_type_nid_postfix(badListBox, "missing") == 0, + "unmapped member list type result is wrong"); + Require(isobmf_box_getflags32b_nid_postfix(badListBox) == 0, + "unmapped flags list result is wrong"); auto *cycleBox = static_cast(Allocate(0xa0)); auto *cycleMember = static_cast(Allocate(0x70)); @@ -144,6 +163,12 @@ int main() { WriteField(cycleMember, 0x10, cycleMember); Require(isobmf_box_getmember_ptr_nid_postfix(cycleBox, "missing") == nullptr, "cyclic member list did not fail safely"); + Require(isobmf_box_getmember_length_nid_postfix(cycleBox, "missing") == 0, + "cyclic member list length did not fail safely"); + Require(isobmf_box_getmember_type_nid_postfix(cycleBox, "missing") == 0, + "cyclic member list type did not fail safely"); + Require(isobmf_box_getflags32b_nid_postfix(cycleBox) == 0, + "cyclic flags list did not fail safely"); std::puts("BEISOBMF BOXES tests passed"); return 0; diff --git a/core/libs/tests/GuestBoundedSearchGuards.cpp b/core/libs/tests/GuestBoundedSearchGuards.cpp new file mode 100644 index 0000000000..366deab375 --- /dev/null +++ b/core/libs/tests/GuestBoundedSearchGuards.cpp @@ -0,0 +1,86 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "check failed at line %u\n", location.line()); + std::abort(); + } +} + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#include +#endif + +class Guarded { +public: + unsigned char* data = nullptr; + std::size_t size = 0; + Guarded() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + size = info.dwPageSize; + data = static_cast(VirtualAlloc(nullptr, size * 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + Require(data != nullptr); + DWORD previous = 0; + Require(VirtualProtect(data + size, size, PAGE_NOACCESS, &previous) != 0); +#else + const auto pageSize = sysconf(_SC_PAGESIZE); + Require(pageSize > 0); + size = static_cast(pageSize); + auto* mapping = mmap(nullptr, size * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(mapping != MAP_FAILED); + data = static_cast(mapping); + Require(mprotect(data + size, size, PROT_NONE) == 0); +#endif + } + ~Guarded() { +#ifdef _WIN32 + Require(VirtualFree(data, 0, MEM_RELEASE) != 0); +#else + Require(munmap(data, size * 2) == 0); +#endif + } + Guarded(const Guarded&) = delete; + Guarded& operator=(const Guarded&) = delete; +}; + +int main() { + Guarded memory; + for (const std::size_t length : {0, 1, 2, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 129, 255}) { + auto* input = memory.data + memory.size - std::max(length, 1); + std::fill_n(input, std::max(length, 1), static_cast(0x81)); + for (const bool matches : {false, true}) { + if (matches && length) { input[0] = 0xfe; input[length - 1] = 0xfe; } + const std::vector before(input, input + std::max(length, 1)); + const void* first = nullptr; + const void* last = nullptr; + for (std::size_t i = 0; i < length; ++i) { + if (input[i] == 0xfe) { if (!first) first = input + i; last = input + i; } + } + Require(memchr_nid_postfix(input, 0xfe, length) == first); + Require(memrchr_nid_postfix(input, 0xfe, length) == last); + Require(strnlen_nid_postfix(reinterpret_cast(input), length) == length); + Require(std::equal(before.begin(), before.end(), input)); + } + if (length) { + input[length - 1] = 0; + Require(strnlen_nid_postfix(reinterpret_cast(input), length) == length - 1); + input[0] = 0; + Require(strnlen_nid_postfix(reinterpret_cast(input), length) == 0); + } + } +} diff --git a/core/libs/tests/GuestBsearchPacked.cpp b/core/libs/tests/GuestBsearchPacked.cpp new file mode 100644 index 0000000000..a8a4ecb915 --- /dev/null +++ b/core/libs/tests/GuestBsearchPacked.cpp @@ -0,0 +1,41 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "check failed at line %u\n", location.line()); + std::abort(); + } +} + +static int APS5_VABI Compare(const void* key, const void* record) { + const auto left = *static_cast(key); + const auto right = *static_cast(record); + return (left > right) - (left < right); +} + +int main() { + const std::array keys{1, 3, 3, 7, 9, 11, 13, 17, 21}; + for (std::size_t stride : {1, 3, 5, 7, 17}) { + for (std::size_t offset : {0, 1, 2, 5}) { + std::vector records(offset + keys.size() * stride + 7, 0xa5); + auto* base = records.data() + offset; + for (std::size_t i = 0; i < keys.size(); ++i) base[i * stride] = keys[i]; + const auto before = records; + for (unsigned char key = 0; key < 24; ++key) { + auto* found = static_cast(bsearch_nid_postfix(&key, base, keys.size(), stride, Compare)); + const bool present = std::find(keys.begin(), keys.end(), key) != keys.end(); + Require((found != nullptr) == present); + if (found) Require(found >= base && found < base + keys.size() * stride && + (found - base) % stride == 0 && *found == key); + Require(records == before); + } + } + } +} diff --git a/core/libs/tests/GuestBzeroExtent.cpp b/core/libs/tests/GuestBzeroExtent.cpp new file mode 100644 index 0000000000..c53a4c8d41 --- /dev/null +++ b/core/libs/tests/GuestBzeroExtent.cpp @@ -0,0 +1,81 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestBzeroExtent check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +int main() { + Buffer output; + for (std::size_t length = 0; length <= 256; ++length) { + auto* destination = output.At(length); + const auto extent = (std::max)(length, std::size_t{1}); + std::memset(destination - 16, 0xa5, extent + 16); + bzero_nid_postfix(destination, length); + for (std::size_t i = 0; i < length; ++i) Require(destination[i] == 0); + if (!length) Require(destination[0] == 0xa5); + Prefix(destination); + } +} diff --git a/core/libs/tests/GuestC23Bits.cpp b/core/libs/tests/GuestC23Bits.cpp new file mode 100644 index 0000000000..7b6706b69b --- /dev/null +++ b/core/libs/tests/GuestC23Bits.cpp @@ -0,0 +1,140 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (condition) return; + std::abort(); +} + +static std::uint64_t Reference(std::uint64_t value, unsigned width, unsigned operation) { + if (operation < 4) { + const bool leading = operation < 2; + const unsigned wanted = operation & 1u; + for (unsigned position = 1; position <= width; ++position) { + const unsigned bit = leading ? width - position : position - 1; + if (((value >> bit) & 1u) == wanted) return position; + } + return 0; + } + if (operation == 4) { + unsigned ones = 0; + for (unsigned bit = 0; bit < width; ++bit) ones += (value >> bit) & 1u; + return ones == 1; + } + if (operation == 5) { + unsigned bits = 0; + for (auto remaining = value; remaining; remaining >>= 1) ++bits; + return bits; + } + if (operation == 6) { + if (!value) return 0; + std::uint64_t power = 1; + for (auto remaining = value; remaining > 1; remaining >>= 1) power *= 2; + return power; + } + const auto maximum = width == 64 ? UINT64_MAX : (std::uint64_t{1} << width) - 1; + std::uint64_t power = 1; + while (power < value) { + if (power > maximum / 2) return 0; + power *= 2; + } + return power; +} + +template +static void Check(TFunction function, unsigned operation, const char* name) { + constexpr unsigned width = std::numeric_limits::digits; + const auto maximum = std::numeric_limits::max(); + const auto check = [&](TValue value) { + const auto expected = Reference(value, width, operation); + const auto actual = static_cast(function(value)); + if (actual == expected) return; + std::fprintf(stderr, "%s input %llx expected %llx got %llx\n", name, + static_cast(value), static_cast(expected), + static_cast(actual)); + std::abort(); + }; + check(0); + check(1); + check(maximum); + check(static_cast(0xaaaaaaaaaaaaaaaaULL)); + check(static_cast(0x5555555555555555ULL)); + if constexpr (width <= 16) { + for (std::uint32_t value = 0; value <= maximum; ++value) check(static_cast(value)); + } + for (unsigned bit = 0; bit < width; ++bit) { + const auto one = static_cast(TValue{1} << bit); + check(one); + check(static_cast(one - 1)); + check(static_cast(one + 1)); + check(static_cast(~one)); + for (unsigned other = 0; other < width; ++other) { + const auto pair = static_cast(one | (TValue{1} << other)); + check(pair); + check(static_cast(~pair)); + } + } + if constexpr (width > 16) { + std::uint64_t state = 0x8c314fb296ea570dULL; + for (unsigned index = 0; index < 100000; ++index) { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + check(static_cast(state)); + check(static_cast(~state)); + } + } +} + +int main(int argc, char** argv) { + Require(argc == 2); + const int saved = *__error_nid_postfix(); + *__error_nid_postfix() = 87; + if (std::strcmp(argv[1], "first_leading_zero_uc") == 0) Check(stdc_first_leading_zero_uc_nid_postfix, 0, argv[1]); + else if (std::strcmp(argv[1], "first_leading_zero_us") == 0) Check(stdc_first_leading_zero_us_nid_postfix, 0, argv[1]); + else if (std::strcmp(argv[1], "first_leading_zero_ui") == 0) Check(stdc_first_leading_zero_ui_nid_postfix, 0, argv[1]); + else if (std::strcmp(argv[1], "first_leading_zero_ul") == 0) Check(stdc_first_leading_zero_ul_nid_postfix, 0, argv[1]); + else if (std::strcmp(argv[1], "first_leading_zero_ull") == 0) Check(stdc_first_leading_zero_ull_nid_postfix, 0, argv[1]); + else if (std::strcmp(argv[1], "first_leading_one_uc") == 0) Check(stdc_first_leading_one_uc_nid_postfix, 1, argv[1]); + else if (std::strcmp(argv[1], "first_leading_one_us") == 0) Check(stdc_first_leading_one_us_nid_postfix, 1, argv[1]); + else if (std::strcmp(argv[1], "first_leading_one_ui") == 0) Check(stdc_first_leading_one_ui_nid_postfix, 1, argv[1]); + else if (std::strcmp(argv[1], "first_leading_one_ul") == 0) Check(stdc_first_leading_one_ul_nid_postfix, 1, argv[1]); + else if (std::strcmp(argv[1], "first_leading_one_ull") == 0) Check(stdc_first_leading_one_ull_nid_postfix, 1, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_zero_uc") == 0) Check(stdc_first_trailing_zero_uc_nid_postfix, 2, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_zero_us") == 0) Check(stdc_first_trailing_zero_us_nid_postfix, 2, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_zero_ui") == 0) Check(stdc_first_trailing_zero_ui_nid_postfix, 2, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_zero_ul") == 0) Check(stdc_first_trailing_zero_ul_nid_postfix, 2, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_zero_ull") == 0) Check(stdc_first_trailing_zero_ull_nid_postfix, 2, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_one_uc") == 0) Check(stdc_first_trailing_one_uc_nid_postfix, 3, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_one_us") == 0) Check(stdc_first_trailing_one_us_nid_postfix, 3, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_one_ui") == 0) Check(stdc_first_trailing_one_ui_nid_postfix, 3, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_one_ul") == 0) Check(stdc_first_trailing_one_ul_nid_postfix, 3, argv[1]); + else if (std::strcmp(argv[1], "first_trailing_one_ull") == 0) Check(stdc_first_trailing_one_ull_nid_postfix, 3, argv[1]); + else if (std::strcmp(argv[1], "has_single_bit_uc") == 0) Check(stdc_has_single_bit_uc_nid_postfix, 4, argv[1]); + else if (std::strcmp(argv[1], "has_single_bit_us") == 0) Check(stdc_has_single_bit_us_nid_postfix, 4, argv[1]); + else if (std::strcmp(argv[1], "has_single_bit_ui") == 0) Check(stdc_has_single_bit_ui_nid_postfix, 4, argv[1]); + else if (std::strcmp(argv[1], "has_single_bit_ul") == 0) Check(stdc_has_single_bit_ul_nid_postfix, 4, argv[1]); + else if (std::strcmp(argv[1], "has_single_bit_ull") == 0) Check(stdc_has_single_bit_ull_nid_postfix, 4, argv[1]); + else if (std::strcmp(argv[1], "bit_width_uc") == 0) Check(stdc_bit_width_uc_nid_postfix, 5, argv[1]); + else if (std::strcmp(argv[1], "bit_width_us") == 0) Check(stdc_bit_width_us_nid_postfix, 5, argv[1]); + else if (std::strcmp(argv[1], "bit_width_ui") == 0) Check(stdc_bit_width_ui_nid_postfix, 5, argv[1]); + else if (std::strcmp(argv[1], "bit_width_ul") == 0) Check(stdc_bit_width_ul_nid_postfix, 5, argv[1]); + else if (std::strcmp(argv[1], "bit_width_ull") == 0) Check(stdc_bit_width_ull_nid_postfix, 5, argv[1]); + else if (std::strcmp(argv[1], "bit_floor_uc") == 0) Check(stdc_bit_floor_uc_nid_postfix, 6, argv[1]); + else if (std::strcmp(argv[1], "bit_floor_us") == 0) Check(stdc_bit_floor_us_nid_postfix, 6, argv[1]); + else if (std::strcmp(argv[1], "bit_floor_ui") == 0) Check(stdc_bit_floor_ui_nid_postfix, 6, argv[1]); + else if (std::strcmp(argv[1], "bit_floor_ul") == 0) Check(stdc_bit_floor_ul_nid_postfix, 6, argv[1]); + else if (std::strcmp(argv[1], "bit_floor_ull") == 0) Check(stdc_bit_floor_ull_nid_postfix, 6, argv[1]); + else if (std::strcmp(argv[1], "bit_ceil_uc") == 0) Check(stdc_bit_ceil_uc_nid_postfix, 7, argv[1]); + else if (std::strcmp(argv[1], "bit_ceil_us") == 0) Check(stdc_bit_ceil_us_nid_postfix, 7, argv[1]); + else if (std::strcmp(argv[1], "bit_ceil_ui") == 0) Check(stdc_bit_ceil_ui_nid_postfix, 7, argv[1]); + else if (std::strcmp(argv[1], "bit_ceil_ul") == 0) Check(stdc_bit_ceil_ul_nid_postfix, 7, argv[1]); + else if (std::strcmp(argv[1], "bit_ceil_ull") == 0) Check(stdc_bit_ceil_ull_nid_postfix, 7, argv[1]); + else Require(false); + Require(*__error_nid_postfix() == 87); + *__error_nid_postfix() = saved; +} diff --git a/core/libs/tests/GuestCairoMatrix.cpp b/core/libs/tests/GuestCairoMatrix.cpp new file mode 100644 index 0000000000..de02f4f39a --- /dev/null +++ b/core/libs/tests/GuestCairoMatrix.cpp @@ -0,0 +1,121 @@ +#include "prx/libcairo/include/Matrix.hpp" +#include +#include +#include +#include +#include +#include + +using MatrixInit = void (APS5_VABI*)(CairoMatrix*, double, double, double, double, double, double); +using MatrixMultiply = void (APS5_VABI*)(CairoMatrix*, const CairoMatrix*, const CairoMatrix*); +using MatrixInvert = int (APS5_VABI*)(CairoMatrix*); +static_assert(std::is_same_v); +static_assert(std::is_same_v); +static_assert(std::is_same_v); + +namespace { +void Check(bool ok, int line) { + if (!ok) { + std::fprintf(stderr, "Cairo matrix guest test failed at line %d\n", line); + std::abort(); + } +} +#define Require(condition) Check((condition), __LINE__) + +bool Close(double a, double b) { + return std::abs(a - b) <= 1e-11 * (1 + std::abs(a) + std::abs(b)); +} + +void Matches(const CairoMatrix& a, const std::array& expected) { + const std::array actual{a.xx, a.yx, a.xy, a.yy, a.x0, a.y0}; + for (std::size_t i = 0; i < 6; ++i) Require(Close(actual[i], expected[i])); +} + +void TestConstruction() { + CairoMatrix a{}; + cairo_matrix_init(&a, 2, 3, 4, 5, 6, 7); + Matches(a, {2, 3, 4, 5, 6, 7}); + cairo_matrix_init_identity(&a); + Matches(a, {1, 0, 0, 1, 0, 0}); + cairo_matrix_init_translate(&a, 8, -3); + Matches(a, {1, 0, 0, 1, 8, -3}); +} + +void TestMultiplicationAndAliasing() { + const CairoMatrix a{2, 3, 4, 5, 6, 7}; + const CairoMatrix b{11, 13, 17, 19, 23, 29}; + CairoMatrix out{}; + cairo_matrix_multiply(&out, &a, &b); + Matches(out, {73, 83, 129, 147, 208, 240}); + CairoMatrix inA = a; + cairo_matrix_multiply(&inA, &inA, &b); + Matches(inA, {73, 83, 129, 147, 208, 240}); + CairoMatrix inB = b; + cairo_matrix_multiply(&inB, &a, &inB); + Matches(inB, {73, 83, 129, 147, 208, 240}); + CairoMatrix square = a; + cairo_matrix_multiply(&square, &square, &square); + Matches(square, {16, 21, 28, 37, 46, 60}); +} + +void TestTransforms() { + CairoMatrix m{}; + cairo_matrix_init_identity(&m); + cairo_matrix_translate(&m, 5, -2); + cairo_matrix_scale(&m, 2, 3); + Matches(m, {2, 0, 0, 3, 5, -2}); + double x = 1, y = 2; + cairo_matrix_transform_point(&m, &x, &y); + Require(Close(x, 7) && Close(y, 4)); + x = 1; + y = 2; + cairo_matrix_transform_distance(&m, &x, &y); + Require(Close(x, 2) && Close(y, 6)); + + cairo_matrix_init_identity(&m); + cairo_matrix_scale(&m, 2, 3); + cairo_matrix_translate(&m, 5, -2); + Matches(m, {2, 0, 0, 3, 10, -6}); + + constexpr double pi = 3.1415926535897932384626433832795; + cairo_matrix_init_identity(&m); + cairo_matrix_rotate(&m, pi / 2); + Matches(m, {0, 1, -1, 0, 0, 0}); + x = 2; + y = 1; + cairo_matrix_transform_point(&m, &x, &y); + Require(Close(x, -1) && Close(y, 2)); +} + +void TestInverse() { + CairoMatrix m{2, 0, 0, 4, 6, 8}; + Require(cairo_matrix_invert(&m) == 0); + Matches(m, {0.5, 0, 0, 0.25, -3, -2}); + Require(cairo_matrix_invert(&m) == 0); + Matches(m, {2, 0, 0, 4, 6, 8}); + + const CairoMatrix shear{1, 2, 3, 7, 5, 11}; + m = shear; + Require(cairo_matrix_invert(&m) == 0); + CairoMatrix identity{}; + cairo_matrix_multiply(&identity, &shear, &m); + Matches(identity, {1, 0, 0, 1, 0, 0}); + + m = CairoMatrix{1, 2, 2, 4, 3, 4}; + Require(cairo_matrix_invert(&m) == 5); + Matches(m, {1, 2, 2, 4, 3, 4}); + m = CairoMatrix{0, 0, 0, 1, 3, 4}; + Require(cairo_matrix_invert(&m) == 5); + Matches(m, {0, 0, 0, 1, -3, -4}); + m = CairoMatrix{1, 1, 0, std::numeric_limits::infinity(), 1, 1}; + Require(cairo_matrix_invert(&m) == 5); +} +} + +int main() { + TestConstruction(); + TestMultiplicationAndAliasing(); + TestTransforms(); + TestInverse(); + std::puts("guest cairo matrix tests passed"); +} diff --git a/core/libs/tests/GuestCallTrace.cpp b/core/libs/tests/GuestCallTrace.cpp index 6721aaddac..658a7f5e08 100644 --- a/core/libs/tests/GuestCallTrace.cpp +++ b/core/libs/tests/GuestCallTrace.cpp @@ -60,6 +60,10 @@ static Block APS5_VABI Blocks(std::uint64_t seed) { return {{seed, seed + 1, seed + 2, seed + 3}}; } +static std::uint64_t APS5_VABI Unlabeled(std::uint64_t value) { + return value + 3; +} + static std::uint64_t CurrentThread() { #ifdef _WIN32 return GetCurrentThreadId(); @@ -215,4 +219,25 @@ int main(int argc, char** argv) { Require(!WriteCallCounts_nid_no_patch(reinterpret_cast(unwritable.c_str()))); Require(!WriteCallCounts_nid_no_patch(nullptr) && !WriteCallCounts_nid_no_patch("")); std::filesystem::remove(path); + + Require(Traced(&Unlabeled, "Unlabeled", "") == &Unlabeled); + const auto executable = std::filesystem::path(argv[0]).filename().string(); +#ifdef _WIN32 + Require(_putenv_s("APS5_TRACE_CALLS", executable.c_str()) == 0); +#else + Require(setenv("APS5_TRACE_CALLS", executable.c_str(), 1) == 0); +#endif + const auto unlabeled = Traced(&Unlabeled, "Unlabeled", ""); + Require(unlabeled != &Unlabeled && unlabeled(39) == 42); + const auto tracedCount = CopyCallCounts_nid_no_patch(counts, 8, &total); + Require(tracedCount == 6 && total == 9 + Workers * Calls); + bool inferred = false; + for (std::size_t index = 0; index < tracedCount; ++index) { + const auto& count = counts[index]; + if (count.name != nullptr && std::strcmp(count.name, "Unlabeled") == 0) { + Require(count.calls == 1 && executable == count.library); + inferred = true; + } + } + Require(inferred); } diff --git a/core/libs/tests/GuestCameraSettings.cpp b/core/libs/tests/GuestCameraSettings.cpp index 7779b2b6ee..7266b7bf49 100644 --- a/core/libs/tests/GuestCameraSettings.cpp +++ b/core/libs/tests/GuestCameraSettings.cpp @@ -59,6 +59,11 @@ void CheckAudioAfterOpen() { Require(sceCameraAudioClose() == 0, "camera audio close must answer 0"); } +void CheckInternalCalls() { + Require(sceCameraDeviceOpen() == 0, "the device open call must answer 0"); + Require(sceCameraSetAttributeInternal() == 0, "the internal attribute call must answer 0"); +} + CameraConfig SizedConfig() { CameraConfig config; std::memset(&config, 0x5a, sizeof(config)); @@ -238,7 +243,9 @@ void CheckDevice() { int main() { CheckBeforeOpen(); + CheckInternalCalls(); Require(sceCameraOpen(0xFF, 0, 0, nullptr) == 1, "the camera must open"); + CheckInternalCalls(); CheckGetters(); CheckAttribute(); CheckSetters(); diff --git a/core/libs/tests/GuestCes.cpp b/core/libs/tests/GuestCes.cpp index bb5862cb5e..551a39d001 100644 --- a/core/libs/tests/GuestCes.cpp +++ b/core/libs/tests/GuestCes.cpp @@ -359,6 +359,14 @@ static void RequireSbcCode(const std::uint8_t* profile, std::uint8_t sbc, std::u RequireSbcToUtf8(profile, sbc, expected); } +static bool MeasuredHole(const std::uint8_t* profile, int sbc) { + const std::initializer_list cp1254 = {0x81, 0x8D, 0x8E, 0x8F, 0x90, 0x9D, 0x9E}; + const std::initializer_list cp1258 = {0x81, 0x8A, 0x8D, 0x8E, 0x8F, 0x90, 0x9A, 0x9D, 0x9E}; + if (profile == sceCesRefersUcsProfileCp1254()) return std::find(cp1254.begin(), cp1254.end(), sbc) != cp1254.end(); + if (profile == sceCesRefersUcsProfileCp1258()) return std::find(cp1258.begin(), cp1258.end(), sbc) != cp1258.end(); + return false; +} + 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, sceCesRefersUcsProfileCp858}; @@ -378,6 +386,12 @@ static void CheckSingleByteProfiles() { std::uint8_t cp[4] = {}; std::uint32_t cpLength = 0; std::uint8_t back = 0; + if (MeasuredHole(profile, sbc)) { + std::uint32_t code = 0x5A5A5A5A; + Require(sceCesSbcToUtf32(profile, static_cast(sbc), &code) == unassignedCode && code == 0x5A5A5A5A); + Require(sceCesUtf32ToSbc(static_cast(sbc), profile, &back) == unassignedCode); + continue; + } 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); diff --git a/core/libs/tests/GuestCesChinese.cpp b/core/libs/tests/GuestCesChinese.cpp index 24e18b4458..b719d80ff5 100644 --- a/core/libs/tests/GuestCesChinese.cpp +++ b/core/libs/tests/GuestCesChinese.cpp @@ -235,22 +235,40 @@ static void CheckGb18030(const void* gb, bool revision2005, std::uint32_t twoByt const std::uint32_t units = revision2005 ? 6 : 4; std::uint8_t text[16] = {}; Require(sceCesUtf8StrToMbcsStr(context, Bytes(utf8Text), utf8Size, &consumed, text, sizeof(text), &produced) == 0 && consumed == utf8Size && produced == gbSize); - Require(std::memcmp(text, gbText, gbSize) == 0); + Require(std::memcmp(text, gbText, gbSize) == 0 && text[gbSize] == 0); std::uint8_t backUtf8[16] = {}; Require(sceCesMbcsStrToUtf8Str(context, text, gbSize, &consumed, backUtf8, sizeof(backUtf8), &produced) == 0 && consumed == gbSize && produced == utf8Size); - Require(std::memcmp(backUtf8, utf8Text, utf8Size) == 0); + Require(std::memcmp(backUtf8, utf8Text, utf8Size) == 0 && backUtf8[utf8Size] == 0); std::uint16_t backUtf16[8] = {}; Require(sceCesMbcsStrToUtf16Str(context, text, gbSize, &consumed, backUtf16, 8, &produced) == 0 && consumed == gbSize && produced == units); - Require(backUtf16[0] == 'A' && backUtf16[1] == 0x4E2D && backUtf16[2] == 0x80 && backUtf16[units - 1] == 0xFE10); + Require(backUtf16[0] == 'A' && backUtf16[1] == 0x4E2D && backUtf16[2] == 0x80 && backUtf16[units - 1] == 0xFE10 && backUtf16[units] == 0); if (revision2005) Require(backUtf16[3] == 0xD840 && backUtf16[4] == 0xDC00); - std::uint16_t shortUtf16[8] = {}; - Require(sceCesMbcsStrToUtf16Str(context, text, gbSize, &consumed, shortUtf16, units - 2, &produced) == dstBufferEnd); + std::uint16_t shortUtf16[6] = {0xBEEF, 0xBEEF, 0xBEEF, 0xBEEF, 0xBEEF, 0xBEEF}; + Require(sceCesMbcsStrToUtf16Str(context, text, gbSize, &consumed, shortUtf16, 4, &produced) == dstBufferEnd && consumed == 7 && produced == 3); + Require(shortUtf16[0] == 'A' && shortUtf16[1] == 0x4E2D && shortUtf16[2] == 0x80 && shortUtf16[3] == 0 && shortUtf16[4] == 0xBEEF && shortUtf16[5] == 0xBEEF); + if (revision2005) { + Require(sceCesMbcsStrToUtf16Str(context, text, gbSize, &consumed, shortUtf16, 5, &produced) == dstBufferEnd && consumed == 7 && produced == 3); + Require(shortUtf16[3] == 0 && shortUtf16[4] == 0xBEEF && shortUtf16[5] == 0xBEEF); + } else { + Require(sceCesMbcsStrToUtf16Str(context, text, gbSize, &consumed, shortUtf16, 5, &produced) == 0 && consumed == gbSize && produced == units); + Require(shortUtf16[3] == 0xFE10 && shortUtf16[4] == 0 && shortUtf16[5] == 0xBEEF); + } + Require(sceCesMbcsStrToUtf16Str(context, text, gbSize, &consumed, shortUtf16, units - 2, &produced) == dstBufferEnd && consumed == (revision2005 ? 7u : 1u) && produced == (revision2005 ? 3u : 1u)); + Require(shortUtf16[produced] == 0 && shortUtf16[5] == 0xBEEF); Require(sceCesMbcsStrGetUtf16Len(context, text, gbSize, &consumed, &produced) == 0 && consumed == gbSize && produced == units); std::uint8_t again[16] = {}; Require(sceCesUtf16StrToMbcsStr(context, backUtf16, units, &consumed, again, sizeof(again), &produced) == 0 && consumed == units && produced == gbSize); - Require(std::memcmp(again, gbText, gbSize) == 0); - std::uint16_t ucs2Text[8] = {}; - if (revision2005) Require(sceCesMbcsStrToUcs2Str(context, text, gbSize, &consumed, ucs2Text, 8, &produced) == unassignedCode); + Require(std::memcmp(again, gbText, gbSize) == 0 && again[gbSize] == 0); + std::uint16_t ucs2Text[8] = {0xBEEF, 0xBEEF, 0xBEEF, 0xBEEF, 0xBEEF, 0xBEEF, 0xBEEF, 0xBEEF}; + if (revision2005) { + Require(sceCesMbcsStrToUcs2Str(context, text, gbSize, &consumed, ucs2Text, 8, &produced) == unassignedCode && consumed == 7 && produced == 3); + Require(ucs2Text[0] == 'A' && ucs2Text[1] == 0x4E2D && ucs2Text[2] == 0x80 && ucs2Text[3] == 0 && ucs2Text[4] == 0xBEEF); + Require(sceCesMbcsStrGetUcs2Len(context, text, gbSize, &consumed, &produced) == unassignedCode && consumed == 7 && produced == 3); + } else { + Require(sceCesMbcsStrToUcs2Str(context, text, gbSize, &consumed, ucs2Text, 8, &produced) == 0 && consumed == gbSize && produced == units); + Require(ucs2Text[0] == 'A' && ucs2Text[1] == 0x4E2D && ucs2Text[2] == 0x80 && ucs2Text[3] == 0xFE10 && ucs2Text[4] == 0 && ucs2Text[5] == 0xBEEF); + Require(sceCesMbcsStrGetUcs2Len(context, text, gbSize, &consumed, &produced) == 0 && consumed == gbSize && produced == units); + } Require(sceCesUtf8StrToMbcsStr(context, Bytes("\xC2\x80"), 2, &consumed, text, 3, &produced) == dstBufferEnd); } diff --git a/core/libs/tests/GuestCesDbcs.cpp b/core/libs/tests/GuestCesDbcs.cpp index dda2ed96af..cf517d68ef 100644 --- a/core/libs/tests/GuestCesDbcs.cpp +++ b/core/libs/tests/GuestCesDbcs.cpp @@ -252,6 +252,7 @@ static void RequireEncoders(const Encoders& encoders, const void* profile, void* std::uint32_t read = 0; std::uint32_t written = 0; const int expected = sceCesUcs2StrToMbcsStr(context, &ucs2, 1, &read, bytes, sizeof(bytes), &written); + Require(expected == 0 ? read == 1 && written >= 1 && written <= 2 && bytes[written] == 0 : read == 0 && written == 0 && bytes[0] == 0); RequireEncode(encoders, profile, code, expected, bytes, written); } RequireEncode(encoders, profile, 0, 0, Bytes("\0"), 1); @@ -395,6 +396,32 @@ int main() { 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 struct { + FromUtf8 convert; + const void* profile; + } utf8Encoders[] = {{sceCesUtf8ToEucKr, eucKr}, {sceCesUtf8ToUhc, uhc}, {sceCesUtf8ToBig5, big5}}; + const struct { + const char* bytes; + std::uint32_t length; + int result; + } utf8Errors[] = { + {"\xC0\x80", 2, illegalCode}, + {"\xED\xA0\x80", 3, illegalCode}, + {"\xEA\xB0", 2, srcBufferEnd}, + {"\xEA\x41\x80", 3, invalidEncode}, + {"\x80", 1, invalidEncode}, + }; + for (const auto& encoder : utf8Encoders) { + for (const auto& error : utf8Errors) { + std::uint8_t output[] = {0x55, 0x55, 0x55, 0x55}; + std::uint32_t consumed = 77; + std::uint32_t produced = 88; + Require(encoder.convert(Bytes(error.bytes), error.length, &consumed, encoder.profile, output + 1, 2, &produced) == error.result); + Require(consumed == 77 && produced == 88 && std::memcmp(output, "\x55\x55\x55\x55", 4) == 0); + Require(encoder.convert(Bytes(error.bytes), error.length, nullptr, encoder.profile, output + 1, 2, nullptr) == error.result); + Require(std::memcmp(output, "\x55\x55\x55\x55", 4) == 0); + } + } 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); diff --git a/core/libs/tests/GuestCesSparseProfiles.cpp b/core/libs/tests/GuestCesSparseProfiles.cpp new file mode 100644 index 0000000000..2cf04f05bb --- /dev/null +++ b/core/libs/tests/GuestCesSparseProfiles.cpp @@ -0,0 +1,298 @@ +#include "prx/libSceCesCs/libSceCesCs.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int Unassigned = static_cast(0x805C0020); +constexpr int ShortOutput = static_cast(0x805C0031); +constexpr std::uint32_t Untouched = 0x12345678; +const char* currentProfile = ""; +unsigned currentByte = 0; + +void Require(bool value, std::source_location location = std::source_location::current()) { + if (value) return; + std::fprintf(stderr, "%s byte 0x%02X line %u\n", currentProfile, currentByte, location.line()); + std::abort(); +} + +struct Profile { + const char* name; + decltype(&sceCesRefersUcsProfileCp1252) get; + std::array undefined; + std::uint64_t checksum; +}; + +constexpr Profile Profiles[] = { + {"Iso8859_3", sceCesRefersUcsProfileIso8859_3, {0x4000402000000000ULL, 0x0001000800010008ULL}, 0x3A14FC6A7610759CULL}, + {"Iso8859_6", sceCesRefersUcsProfileIso8859_6, {0x77FFCFEE00000000ULL, 0xFFF80000F8000001ULL}, 0x9C2E4F6D1F8BAAC9ULL}, + {"Iso8859_7", sceCesRefersUcsProfileIso8859_7, {0x0000400000000000ULL, 0x8000000000040000ULL}, 0xA1EC3F5965B3F537ULL}, + {"Iso8859_8", sceCesRefersUcsProfileIso8859_8, {0x8000000200000000ULL, 0x980000007FFFFFFFULL}, 0x7ED25AFCF1761A52ULL}, + {"Iso8859_11", sceCesRefersUcsProfileIso8859_11, {0x0000000000000000ULL, 0xF000000078000000ULL}, 0x6902E282C2CE9744ULL}, + {"Cp857", sceCesRefersUcsProfileCp857, {0x0000000000000000ULL, 0x0004008000200000ULL}, 0x7025402330A56CADULL}, + {"Cp869", sceCesRefersUcsProfileCp869, {0x00000000001800BFULL, 0x0000000000000000ULL}, 0x7B0192F88690DE80ULL}, + {"Cp874", sceCesRefersUcsProfileCp874, {0x00000000FF01FFDEULL, 0xF000000078000000ULL}, 0xD383EE6DBF9B57B2ULL}, +}; + +void Hash(std::uint64_t& value, std::uint8_t byte) { + value = (value ^ byte) * 1099511628211ULL; +} + +void CheckMbcScalar(const std::uint8_t* profile, std::uint8_t byte, const std::uint8_t* expected, + std::uint32_t length, std::uint32_t code, bool undefined) { + const int status = undefined ? Unassigned : 0; + std::array output = {0xA5, 0xA5, 0xA5, 0xA5}; + std::uint32_t consumed = Untouched; + std::uint32_t produced = Untouched; + Require(sceCesMbcToUtf8(profile, &byte, 1, &consumed, output.data(), output.size(), &produced) == status); + Require(consumed == 1 && produced == (undefined ? 0 : length)); + if (!undefined) Require(std::memcmp(output.data(), expected, length) == 0); + for (std::size_t i = undefined ? 0 : length; i < output.size(); ++i) Require(output[i] == 0xA5); + Require(sceCesMbcToUtf8(profile, &byte, 1, nullptr, output.data(), output.size(), nullptr) == status); + output.fill(0xA5); + consumed = produced = Untouched; + Require(sceCesMbcToUtf8(profile, &byte, 1, &consumed, output.data(), 0, &produced) == (undefined ? Unassigned : ShortOutput)); + Require(consumed == (undefined ? 1 : Untouched) && produced == (undefined ? 0 : Untouched)); + for (auto value : output) Require(value == 0xA5); + std::uint32_t utf32[2] = {0xA5A5A5A5, 0xA5A5A5A5}; + Require(sceCesMbcToUtf32(profile, &byte, 1, &consumed, utf32) == status); + Require(consumed == 1); + Require(utf32[0] == (undefined ? 0 : code) && utf32[1] == 0xA5A5A5A5); + utf32[0] = 0xA5A5A5A5; + Require(sceCesMbcToUtf32(profile, &byte, 1, nullptr, utf32) == status); + Require(utf32[0] == (undefined ? 0 : code) && utf32[1] == 0xA5A5A5A5); + if (undefined) return; + output.fill(0xA5); + produced = Untouched; + Require(sceCesUtf32ToMbc(code, profile, output.data(), 1, &produced) == 0); + Require(produced == 1 && output[0] == byte && output[1] == 0xA5); + output.fill(0xA5); + produced = Untouched; + Require(sceCesUtf32ToMbc(code, profile, output.data(), 0, &produced) == ShortOutput); + Require(produced == 1); + for (auto value : output) Require(value == 0xA5); + consumed = produced = Untouched; + Require(sceCesUtf8ToMbc(expected, length, &consumed, profile, output.data(), 1, &produced) == 0); + Require(consumed == length && produced == 1 && output[0] == byte && output[1] == 0xA5); + output.fill(0xA5); + consumed = produced = Untouched; + Require(sceCesUtf8ToMbc(expected, length, &consumed, profile, output.data(), 0, &produced) == ShortOutput); + Require(consumed == Untouched && produced == Untouched); + for (auto value : output) Require(value == 0xA5); +} + +void CheckWideScalar(const std::uint8_t* profile, std::uint8_t byte, std::uint32_t code, bool undefined) { + const int status = undefined ? Unassigned : 0; + std::uint16_t ucs2[2] = {0xA5A5, 0xA5A5}; + Require(sceCesSbcToUcs2(profile, byte, ucs2) == status); + Require(ucs2[0] == (undefined ? 0xA5A5 : code) && ucs2[1] == 0xA5A5); + const decltype(&sceCesSbcToUtf16) to16[] = {sceCesSbcToUtf16, sceCesSbcToUtf16be, sceCesSbcToUtf16le}; + const decltype(&sceCesSbcToUtf32) to32[] = {sceCesSbcToUtf32, sceCesSbcToUtf32be, sceCesSbcToUtf32le}; + for (std::size_t i = 0; i < 3; ++i) { + const std::uint16_t expected16 = i == 1 ? static_cast(code << 8 | code >> 8) : code; + const std::uint32_t expected32 = i == 1 ? (code & 0xff) << 24 | (code & 0xff00) << 8 : code; + ucs2[0] = 0xA5A5; + std::uint32_t length = Untouched; + Require(to16[i](profile, byte, ucs2, 1, &length) == status); + Require(ucs2[0] == (undefined ? 0xA5A5 : expected16) && ucs2[1] == 0xA5A5); + Require(length == (undefined ? Untouched : 1)); + Require(to16[i](profile, byte, ucs2, 1, nullptr) == status); + ucs2[0] = 0xA5A5; + length = Untouched; + Require(to16[i](profile, byte, ucs2, 0, &length) == (undefined ? Unassigned : ShortOutput)); + Require(ucs2[0] == 0xA5A5 && ucs2[1] == 0xA5A5 && length == Untouched); + std::uint32_t utf32[2] = {0xA5A5A5A5, 0xA5A5A5A5}; + Require(to32[i](profile, byte, utf32) == status); + Require(utf32[0] == (undefined ? 0xA5A5A5A5 : expected32) && utf32[1] == 0xA5A5A5A5); + } +} + +template +void CheckWideString(void* context, std::uint8_t byte, TUnit code, bool undefined, + TConvert convert, TLength getLength, TReverse reverse, TReverseLength getReverseLength) { + const TUnit untouched = static_cast(0xA5A5A5A5); + TUnit output[3] = {untouched, untouched, untouched}; + const int status = undefined ? Unassigned : 0; + const std::uint32_t units = undefined || byte == 0 ? 0 : 1; + std::uint32_t consumed = Untouched; + std::uint32_t produced = Untouched; + Require(convert(context, &byte, 1, &consumed, output, 2, &produced) == status); + Require(consumed == units && produced == units); + Require(output[0] == (units == 0 ? 0 : code) && output[units] == 0 && output[2] == untouched); + if (units == 0) Require(output[1] == untouched); + consumed = produced = Untouched; + Require(getLength(context, &byte, 1, &consumed, &produced) == status); + Require(consumed == units && produced == units); + if (undefined) { + const std::uint8_t input[] = {'A', byte, 'B'}; + Require(convert(context, input, 3, &consumed, output, 3, &produced) == Unassigned); + Require(output[0] == 'A' && output[1] == 0 && output[2] == untouched); + Require(consumed == 1 && produced == 1); + Require(getLength(context, input, 3, &consumed, &produced) == Unassigned); + Require(consumed == 1 && produced == 1); + return; + } + std::uint8_t back[3] = {0xA5, 0xA5, 0xA5}; + Require(reverse(context, &code, 1, &consumed, back, 2, &produced) == 0); + Require(consumed == units && produced == units && back[0] == byte && back[units] == 0 && back[2] == 0xA5); + Require(getReverseLength(context, &code, 1, &consumed, &produced) == 0); + Require(consumed == units && produced == units); + code = 0xFFFF; + consumed = produced = Untouched; + back[0] = back[1] = 0xA5; + Require(reverse(context, &code, 1, &consumed, back, 2, &produced) == Unassigned); + Require(consumed == 0 && produced == 0 && back[0] == 0 && back[1] == 0xA5 && back[2] == 0xA5); + Require(getReverseLength(context, &code, 1, &consumed, &produced) == Unassigned); + Require(consumed == 0 && produced == 0); +} + +void CheckContext(void* context, std::uint8_t byte, const std::uint8_t* expected, std::uint32_t length, bool undefined) { + std::array output = {0xA5, 0xA5, 0xA5, 0xA5}; + std::uint32_t consumed = Untouched; + std::uint32_t produced = Untouched; + const int status = undefined ? Unassigned : 0; + const std::uint32_t units = undefined || byte == 0 ? 0 : 1; + const std::uint32_t bytes = undefined || byte == 0 ? 0 : length; + Require(sceCesMbcsStrToUtf8Str(context, &byte, 1, &consumed, output.data(), output.size(), &produced) == status); + Require(consumed == units && produced == bytes && output[bytes] == 0); + if (!undefined) Require(std::memcmp(output.data(), expected, bytes) == 0); + for (std::size_t i = bytes + 1; i < output.size(); ++i) Require(output[i] == 0xA5); + consumed = produced = Untouched; + Require(sceCesMbcsStrGetUtf8Len(context, &byte, 1, &consumed, &produced) == status); + Require(consumed == units && produced == bytes); + + std::uint16_t ucs2[3] = {0xA5A5, 0xA5A5, 0xA5A5}; + consumed = produced = Untouched; + Require(sceCesMbcsStrToUcs2Str(context, &byte, 1, &consumed, ucs2, 2, &produced) == status); + Require(consumed == units && produced == units && ucs2[units] == 0 && ucs2[2] == 0xA5A5); + if (units == 0) Require(ucs2[1] == 0xA5A5); + consumed = produced = Untouched; + Require(sceCesMbcsStrGetUcs2Len(context, &byte, 1, &consumed, &produced) == status); + Require(consumed == units && produced == units); + if (undefined) return; + + std::uint16_t code = expected[0]; + if (length == 2) code = (expected[0] & 0x1F) << 6 | (expected[1] & 0x3F); + if (length == 3) code = (expected[0] & 0x0F) << 12 | (expected[1] & 0x3F) << 6 | (expected[2] & 0x3F); + if (byte != 0) Require(ucs2[0] == code); + std::uint8_t back[3] = {0xA5, 0xA5, 0xA5}; + Require(sceCesUtf8StrToMbcsStr(context, expected, length, &consumed, back, 2, &produced) == 0); + Require(consumed == bytes && produced == units && back[0] == byte && back[units] == 0 && back[2] == 0xA5); + Require(sceCesUtf8StrGetMbcsLen(context, expected, length, &consumed, &produced) == 0); + Require(consumed == bytes && produced == units); + back[0] = 0xA5; + Require(sceCesUcs2StrToMbcsStr(context, &code, 1, &consumed, back, 2, &produced) == 0); + Require(consumed == units && produced == units && back[0] == byte && back[units] == 0 && back[2] == 0xA5); + Require(sceCesUcs2StrGetMbcsLen(context, &code, 1, &consumed, &produced) == 0); + Require(consumed == units && produced == units); + if (byte == 0) return; + output.fill(0xA5); + consumed = produced = Untouched; + Require(sceCesMbcsStrToUtf8Str(context, &byte, 1, &consumed, output.data(), length, &produced) == ShortOutput); + Require(consumed == 0 && produced == 0 && output[0] == 0); + for (std::size_t i = 1; i < output.size(); ++i) Require(output[i] == 0xA5); + output.fill(0xA5); + Require(sceCesUtf8StrToMbcsStr(context, expected, length, &consumed, output.data(), 0, &produced) == ShortOutput); + Require(consumed == 0 && produced == 0); + for (auto value : output) Require(value == 0xA5); +} + +void CheckUnmapped(const std::uint8_t* profile, void* context) { + const std::uint8_t utf8[] = {0xEF, 0xBF, 0xBF}; + const std::uint16_t ucs2 = 0xFFFF; + std::uint8_t output = 0xA5; + std::uint32_t consumed = Untouched; + std::uint32_t produced = Untouched; + Require(sceCesUtf8ToSbc(utf8, sizeof(utf8), &consumed, profile, &output) == Unassigned); + Require(consumed == sizeof(utf8) && output == 0); + Require(sceCesUtf8StrToMbcsStr(context, utf8, sizeof(utf8), &consumed, &output, 1, &produced) == Unassigned); + Require(consumed == 0 && produced == 0 && output == 0); + output = 0xA5; + Require(sceCesUcs2StrToMbcsStr(context, &ucs2, 1, &consumed, &output, 1, &produced) == Unassigned); + Require(consumed == 0 && produced == 0 && output == 0); + output = 0xA5; + produced = Untouched; + Require(sceCesUtf32ToMbc(ucs2, profile, &output, 1, &produced) == Unassigned); + Require(produced == 0 && output == 0xA5); + consumed = produced = Untouched; + Require(sceCesUtf8ToMbc(utf8, sizeof(utf8), &consumed, profile, &output, 1, &produced) == Unassigned); + Require(consumed == Untouched && produced == Untouched && output == 0xA5); +} + +void CheckPrefix(void* context, std::uint8_t hole) { + const std::uint8_t input[] = {'A', hole, 'B'}; + std::uint8_t output[4] = {0xA5, 0xA5, 0xA5, 0xA5}; + std::uint32_t consumed = Untouched; + std::uint32_t produced = Untouched; + Require(sceCesMbcsStrToUtf8Str(context, input, 3, &consumed, output, 4, &produced) == Unassigned); + Require(consumed == 1 && produced == 1); + Require(output[0] == 'A' && output[1] == 0 && output[2] == 0xA5 && output[3] == 0xA5); + Require(sceCesMbcsStrGetUtf8Len(context, input, 3, &consumed, &produced) == Unassigned); + Require(consumed == 1 && produced == 1); + const std::uint8_t terminated[] = {'A', 0, hole}; + Require(sceCesMbcsStrToUtf8Str(context, terminated, 3, &consumed, output, 4, &produced) == 0); + Require(consumed == 1 && produced == 1 && output[1] == 0); +} + +void CheckProfile(const Profile& fixture) { + currentProfile = fixture.name; + const auto* profile = fixture.get(); + Require(profile && fixture.get() == profile && profile != sceCesRefersUcsProfileCp1252()); + alignas(8) std::uint8_t context[256] = {}; + Require(sceCesMbcsUcsContextInit(context, profile) == 0); + std::uint64_t checksum = 14695981039346656037ULL; + for (currentByte = 0; currentByte < 256; ++currentByte) { + const auto byte = static_cast(currentByte); + const bool undefined = byte >= 128 && (fixture.undefined[(byte - 128) / 64] >> ((byte - 128) % 64) & 1) != 0; + std::array utf8 = {0xA5, 0xA5, 0xA5, 0xA5}; + std::uint32_t length = Untouched; + Require(sceCesSbcToUtf8(profile, byte, utf8.data(), utf8.size(), &length) == (undefined ? Unassigned : 0)); + std::uint32_t code = utf8[0]; + if (length == 2) code = (utf8[0] & 0x1F) << 6 | (utf8[1] & 0x3F); + if (length == 3) code = (utf8[0] & 0x0F) << 12 | (utf8[1] & 0x3F) << 6 | (utf8[2] & 0x3F); + CheckWideScalar(profile, byte, code, undefined); + CheckMbcScalar(profile, byte, utf8.data(), length, code, undefined); + CheckWideString(context, byte, static_cast(code), undefined, + sceCesMbcsStrToUtf16Str, sceCesMbcsStrGetUtf16Len, sceCesUtf16StrToMbcsStr, sceCesUtf16StrGetMbcsLen); + CheckWideString(context, byte, code, undefined, + sceCesMbcsStrToUtf32Str, sceCesMbcsStrGetUtf32Len, sceCesUtf32StrToMbcsStr, sceCesUtf32StrGetMbcsLen); + if (undefined) { + Require(length == 0); + for (auto value : utf8) Require(value == 0xA5); + CheckContext(context, byte, utf8.data(), 0, true); + CheckPrefix(context, byte); + continue; + } + Require(length > 0 && length <= 3); + Hash(checksum, static_cast(length)); + for (std::uint32_t i = 0; i < length; ++i) Hash(checksum, utf8[i]); + for (std::uint32_t i = length; i < utf8.size(); ++i) Require(utf8[i] == 0xA5); + std::uint8_t back = 0xA5; + std::uint32_t consumed = Untouched; + Require(sceCesUtf8ToSbc(utf8.data(), length, &consumed, profile, &back) == 0 && consumed == length && back == byte); + Require(sceCesUtf8ToSbc(utf8.data(), length, nullptr, profile, &back) == 0 && back == byte); + CheckContext(context, byte, utf8.data(), length, false); + std::array shortOutput = {0xA5, 0xA5, 0xA5, 0xA5}; + consumed = Untouched; + Require(sceCesSbcToUtf8(profile, byte, shortOutput.data(), length - 1, &consumed) == ShortOutput); + Require(consumed == length); + for (auto value : shortOutput) Require(value == 0xA5); + Require(sceCesSbcToUtf8(profile, byte, shortOutput.data(), length, nullptr) == 0); + Require(std::memcmp(shortOutput.data(), utf8.data(), length) == 0); + } + Require(checksum == fixture.checksum); + CheckUnmapped(profile, context); +} + +} // namespace + +int main() { + for (const auto& profile : Profiles) CheckProfile(profile); + for (std::size_t i = 0; i < std::size(Profiles); ++i) { + for (std::size_t j = 0; j < i; ++j) Require(Profiles[i].get() != Profiles[j].get()); + } +} diff --git a/core/libs/tests/GuestCesWide.cpp b/core/libs/tests/GuestCesWide.cpp index 6f5d68eeed..2b3a754d71 100644 --- a/core/libs/tests/GuestCesWide.cpp +++ b/core/libs/tests/GuestCesWide.cpp @@ -236,6 +236,7 @@ int main() { 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); @@ -283,6 +284,8 @@ int main() { Require(sceCesUtf16leToSbc(&euroLe, 1, &length, cp1252, &sbc) == 0 && sbc == 0x80); Require(sceCesUtf32ToSbc(0x0178, cp1252, &sbc) == 0 && sbc == 0x9F); Require(sceCesUtf32ToSbc(0x110000, cp1252, &sbc) == invalidEncode); + Require(sceCesUtf32ToSbc(0xD800, cp1252, &sbc) == illegalCode); + Require(sceCesUtf32ToSbc(0xDFFF, sceCesRefersUcsProfileCp1254(), &sbc) == illegalCode); std::uint32_t yBe = 0; std::memcpy(&yBe, "\x00\x00\x01\x78", 4); Require(sceCesUtf32beToSbc(&yBe, cp1252, &sbc) == 0 && sbc == 0x9F); diff --git a/core/libs/tests/GuestCndAtThreadExit.cpp b/core/libs/tests/GuestCndAtThreadExit.cpp new file mode 100644 index 0000000000..b01f02ab09 --- /dev/null +++ b/core/libs/tests/GuestCndAtThreadExit.cpp @@ -0,0 +1,176 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +static bool failAllocations = false; + +void* operator new(std::size_t size) { + if (failAllocations) throw std::bad_alloc(); + 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 { std::free(memory); } + +static void Require(bool value) { if (!value) std::abort(); } + +template +static void RequireThrows(TCall call) { + try { + call(); + } catch (const TException&) { + return; + } + std::abort(); +} + +constexpr int success = 0; +constexpr int plain = 0x01; + +static void RegisterRejectsNullHandles() { + void* condition = nullptr; + void* mutex = nullptr; + Require(_Cnd_init_nid_postfix(&condition) == success); + Require(_Mtx_init_nid_postfix(&mutex, plain) == success); + int ready = 0; + RequireThrows([&] { _Cnd_register_at_thread_exit_nid_postfix(nullptr, mutex, &ready); }); + RequireThrows([&] { _Cnd_register_at_thread_exit_nid_postfix(condition, nullptr, &ready); }); + RequireThrows([] { _Cnd_unregister_at_thread_exit_nid_postfix(nullptr); }); + _Cnd_register_at_thread_exit_nid_postfix(condition, mutex, nullptr); + _Cnd_unregister_at_thread_exit_nid_postfix(mutex); + _Mtx_destroy_nid_postfix(&mutex); + _Cnd_destroy_nid_postfix(&condition); +} + +static void UnregisterSkipsBroadcast() { + void* condition = nullptr; + void* mutex = nullptr; + Require(_Cnd_init_nid_postfix(&condition) == success); + Require(_Mtx_init_nid_postfix(&mutex, plain) == success); + Require(_Mtx_lock_nid_postfix(&mutex) == success); + int ready = 0; + _Cnd_register_at_thread_exit_nid_postfix(condition, mutex, &ready); + _Cnd_unregister_at_thread_exit_nid_postfix(mutex); + _Cnd_do_broadcast_at_thread_exit_nid_postfix(); + Require(ready == 0); + Require(_Mtx_unlock_nid_postfix(&mutex) == success); + _Mtx_destroy_nid_postfix(&mutex); + _Cnd_destroy_nid_postfix(&condition); +} + +static void BroadcastWakesWaitingThread() { + void* condition = nullptr; + void* mutex = nullptr; + Require(_Cnd_init_nid_postfix(&condition) == success); + Require(_Mtx_init_nid_postfix(&mutex, plain) == success); + Require(_Mtx_lock_nid_postfix(&mutex) == success); + int ready = 0; + std::thread worker([&] { + Require(_Mtx_lock_nid_postfix(&mutex) == success); + _Cnd_register_at_thread_exit_nid_postfix(condition, mutex, &ready); + _Cnd_do_broadcast_at_thread_exit_nid_postfix(); + }); + Require(_Cnd_wait_nid_postfix(&condition, &mutex) == success); + Require(ready == 1); + worker.join(); + Require(_Mtx_unlock_nid_postfix(&mutex) == success); + _Mtx_destroy_nid_postfix(&mutex); + _Cnd_destroy_nid_postfix(&condition); +} + +static void BroadcastDoesNotAllocate() { + void* condition = nullptr; + void* mutex = nullptr; + Require(_Cnd_init_nid_postfix(&condition) == success); + Require(_Mtx_init_nid_postfix(&mutex, plain) == success); + Require(_Mtx_lock_nid_postfix(&mutex) == success); + int ready = 0; + _Cnd_register_at_thread_exit_nid_postfix(condition, mutex, &ready); + failAllocations = true; + _Cnd_do_broadcast_at_thread_exit_nid_postfix(); + failAllocations = false; + Require(ready == 1); + _Mtx_destroy_nid_postfix(&mutex); + _Cnd_destroy_nid_postfix(&condition); +} + +struct HostPadThread { + std::thread worker; + void* result = nullptr; +}; + +static int APS5_VABI HostPthreadCreate(void** thread, const void*, void* (APS5_VABI* entry)(void*), void* argument) { + auto* handle = new HostPadThread{}; + handle->worker = std::thread([handle, entry, argument] { handle->result = entry(argument); }); + *thread = handle; + return 0; +} + +static int APS5_VABI HostPthreadJoin(void* thread, void** value) { + if (!thread) return 22; + auto* handle = static_cast(thread); + handle->worker.join(); + if (value) *value = handle->result; + delete handle; + return 0; +} + +struct ExitPad { + unsigned (APS5_VABI* const* vtable)(GuestPad*); + void* condition; + void* mutex; + bool started; + void* notifyCondition; + void* notifyMutex; + int* ready; +}; + +static unsigned APS5_VABI ExitPadGo(GuestPad* base) { + auto* pad = reinterpret_cast(base); + _ZNSt4_Pad8_ReleaseEv_nid_postfix(base); + Require(_Mtx_lock_nid_postfix(&pad->notifyMutex) == success); + _Cnd_register_at_thread_exit_nid_postfix(pad->notifyCondition, pad->notifyMutex, pad->ready); + return 7; +} + +static void PadLaunchBroadcastsAtThreadExit() { + RegisterPthreadCreate_nid_no_patch(HostPthreadCreate); + RegisterPthreadJoin_nid_no_patch(HostPthreadJoin); + unsigned (APS5_VABI* vtable[1])(GuestPad*) = {ExitPadGo}; + void* condition = nullptr; + void* mutex = nullptr; + Require(_Cnd_init_nid_postfix(&condition) == success); + Require(_Mtx_init_nid_postfix(&mutex, plain) == success); + int ready = 0; + ExitPad pad{}; + pad.vtable = vtable; + pad.notifyCondition = condition; + pad.notifyMutex = mutex; + pad.ready = &ready; + Require(_Mtx_lock_nid_postfix(&mutex) == success); + _ZNSt4_PadC2Ev_nid_postfix(reinterpret_cast(&pad)); + void* thread = nullptr; + _ZNSt4_Pad7_LaunchEPP7pthread_nid_postfix(reinterpret_cast(&pad), &thread); + Require(_Cnd_wait_nid_postfix(&condition, &mutex) == success); + Require(ready == 1); + _ZNSt4_PadD2Ev_nid_postfix(reinterpret_cast(&pad)); + int code = 0; + Require(_Thrd_join_nid_postfix(thread, &code) == success && code == 7); + Require(_Mtx_unlock_nid_postfix(&mutex) == success); + _Mtx_destroy_nid_postfix(&mutex); + _Cnd_destroy_nid_postfix(&condition); +} + +int main() { + RegisterRejectsNullHandles(); + UnregisterSkipsBroadcast(); + BroadcastWakesWaitingThread(); + BroadcastDoesNotAllocate(); + PadLaunchBroadcastsAtThreadExit(); + return 0; +} diff --git a/core/libs/tests/GuestCompanionHttpd.cpp b/core/libs/tests/GuestCompanionHttpd.cpp index 05aad8a7d1..7a712c7b14 100644 --- a/core/libs/tests/GuestCompanionHttpd.cpp +++ b/core/libs/tests/GuestCompanionHttpd.cpp @@ -87,6 +87,14 @@ void CheckStates() { 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"); + Require(sceCompanionHttpdGetEvent(nullptr) == InvalidParam, "a null event after the last termination"); + Require(sceCompanionHttpdGetEvent(event) == NotInitialized, "an event after the last termination"); + Require(sceCompanionHttpdStart() == NotInitialized, "a start after the last termination"); + Require(sceCompanionHttpdStop() == NotInitialized, "a stop after the last termination"); + Require(sceCompanionHttpdRegisterRequestCallback(nullptr, nullptr) == InvalidParam, "a null callback after the last termination"); + Require(sceCompanionHttpdRegisterRequestCallback(reinterpret_cast(callback), nullptr) == NotInitialized, "a callback after the last termination"); + Require(sceCompanionHttpdUnregisterRequestCallback() == NotInitialized, "an unregistration after the last termination"); + Require(sceCompanionHttpdInitialize(nullptr) == InvalidParam, "a null parameter on reinit after termination"); } void CheckResponseBuilders() { diff --git a/core/libs/tests/GuestContentExport.cpp b/core/libs/tests/GuestContentExport.cpp index 07036b1d9c..1936c153db 100644 --- a/core/libs/tests/GuestContentExport.cpp +++ b/core/libs/tests/GuestContentExport.cpp @@ -27,7 +27,21 @@ void Export(std::int32_t expId) { ContentExportCallbackParam content{}; ContentExportCallbackParam thumbnail{}; sceContentExportFromDataWithThumbnail(expId, ¶m, &content, "image/png", &thumbnail, path, sizeof(path)); +} +void ExportData(std::int32_t expId) { + ContentExportParam param{}; + sceContentExportFromData(expId, ¶m, 0, nullptr, nullptr, path, sizeof(path)); +} + +void ExportFile(std::int32_t expId) { + ContentExportParam param{}; + sceContentExportFromFile(expId, ¶m, "source", path, sizeof(path)); +} + +void ExportFileWithThumbnail(std::int32_t expId) { + ContentExportParam param{}; + sceContentExportFromFileWithThumbnail(expId, ¶m, "source", "thumb", path, sizeof(path)); } } @@ -38,6 +52,9 @@ int main() { Require(sceContentExportTerm() == static_cast(0x809D3004u)); Require(sceContentExportStart() == static_cast(0x809D3004u)); Require(Throws([] { sceContentExportFinish(1); })); + Require(Throws([] { ExportData(1); })); + Require(Throws([] { ExportFile(1); })); + Require(Throws([] { ExportFileWithThumbnail(1); })); ContentExportInitParam2 missing = param; missing.free_func = nullptr; Require(Throws([&] { sceContentExportInit2(&missing); })); @@ -65,6 +82,15 @@ int main() { Require(Throws([&] { sceContentExportGetProgress(first); })); Require(Throws([&] { sceContentExportCancel(first); })); Require(Throws([&] { Export(first); })); + Require(Throws([&] { ExportData(first); })); + Require(Throws([&] { ExportFile(first); })); + Require(Throws([&] { ExportFileWithThumbnail(first); })); + Require(Throws([] { ExportData(0); })); + Require(Throws([] { ExportFile(0); })); + Require(Throws([] { ExportFileWithThumbnail(0); })); + Require(Throws([] { ExportData(-1); })); + Require(Throws([] { ExportFile(-1); })); + Require(Throws([] { ExportFileWithThumbnail(-1); })); Require(Throws([] { sceContentExportFinish(0); })); Require(Throws([] { sceContentExportFinish(-1); })); sessions.erase(first); @@ -80,17 +106,8 @@ int main() { Require(sceContentExportTerm() == 0); Require(sceContentExportStart() == static_cast(0x809D3004u)); Require(Throws([&] { sceContentExportFinish(reopened); })); - Require(Throws([] { sceContentExportAddToFavorites(0); })); - Require(Throws([] { sceContentExportDataProvideFunction(); })); - Require(Throws([] { sceContentExportFree(); })); - Require(Throws([] { sceContentExportFrom(); })); - 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(0); })); - Require(Throws([] { sceContentExportRenameContent(0, nullptr); })); - Require(Throws([] { sceContentExportValidateContents(nullptr, 0); })); + Require(Throws([&] { ExportData(reopened); })); + Require(Throws([&] { ExportFile(reopened); })); + Require(Throws([&] { ExportFileWithThumbnail(reopened); })); } diff --git a/core/libs/tests/GuestConvertKeycode.cpp b/core/libs/tests/GuestConvertKeycode.cpp index fc100b607b..a68c5deedc 100644 --- a/core/libs/tests/GuestConvertKeycode.cpp +++ b/core/libs/tests/GuestConvertKeycode.cpp @@ -139,16 +139,45 @@ int main() { 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; + for (std::uint32_t layout : {2u, 3u, 12u}) { + Require(sceConvertKeycodeGetVirtualKeycode(28, layout, &virtualKey) == 0 && virtualKey == 90); + Require(sceConvertKeycodeGetVirtualKeycode(29, layout, &virtualKey) == 0 && virtualKey == 89); + } + for (std::uint32_t layout : {31u, 38u, 39u}) { + Require(sceConvertKeycodeGetVirtualKeycode(28, layout, &virtualKey) == 0 && virtualKey == 89); + } + { + constexpr std::uint32_t oldSdkVersion = 0x03000000u; + std::memcpy(process.data() + 16, &oldSdkVersion, sizeof(oldSdkVersion)); + for (std::uint32_t layout : {2u, 3u, 12u}) { + Require(sceConvertKeycodeGetVirtualKeycode(28, layout, &virtualKey) == 0 && virtualKey == 89); + Require(sceConvertKeycodeGetVirtualKeycode(29, layout, &virtualKey) == 0 && virtualKey == 90); } - Require(rejected && virtualKey == 0xABCD); + Require(sceConvertKeycodeGetVirtualKeycode(28, 31, &virtualKey) == 0 && virtualKey == 89); + char16_t rejectedCharacter = u'?'; + for (std::uint32_t layout : {38u, 39u}) { + virtualKey = 0xABCD; + Require(sceConvertKeycodeGetVirtualKeycode(28, layout, &virtualKey) == static_cast(0x80BC001Du) && virtualKey == 0xABCD); + Require(sceConvertKeycodeGetCharacter(28, 0, layout, &rejectedCharacter) == static_cast(0x80BC001Du) && rejectedCharacter == u'?'); + } + const auto setSdkVersion = [&](std::uint32_t version) { std::memcpy(process.data() + 16, &version, sizeof(version)); }; + setSdkVersion(0x03FFFFFFu); + Require(sceConvertKeycodeGetVirtualKeycode(28, 2, &virtualKey) == 0 && virtualKey == 89); + setSdkVersion(0x04000000u); + Require(sceConvertKeycodeGetVirtualKeycode(28, 2, &virtualKey) == 0 && virtualKey == 90); + setSdkVersion(0x05FFFFFFu); + KeyboardData oldKeyData{}; + oldKeyData.length = 1; + oldKeyData.key_code[0] = 4; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&oldKeyData, 0, 0, 38, &rejectedCharacter) == static_cast(0x80BC001Du) && rejectedCharacter == u'?'); + setSdkVersion(0x06000000u); + Require(sceConvertKeycodeGetVirtualKeycode(28, 38, &virtualKey) == 0 && virtualKey == 89); + Require(sceConvertKeycodeGetVirtualKeycode(28, 39, &virtualKey) == static_cast(0x80BC001Du)); + setSdkVersion(0x07FFFFFFu); + Require(sceConvertKeycodeGetVirtualKeycode(28, 39, &virtualKey) == static_cast(0x80BC001Du)); + setSdkVersion(0x08000000u); + Require(sceConvertKeycodeGetVirtualKeycode(28, 39, &virtualKey) == 0 && virtualKey == 89); + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); } // The exported function with the real UserService: unknown users fail, the initial user reports the US-English default (keyboard type 20 -> IME 4). @@ -166,6 +195,16 @@ int main() { } SetLanguage("30"); Require(sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == 4); +#if defined(__linux__) + for (const char* name : {"LC_MESSAGES", "LANG", "LANGUAGE"}) Require(::unsetenv(name) == 0); + Require(::setenv("LC_ALL", "de_DE.UTF-8", 1) == 0); + SetLanguage("auto"); + type = 0xFFFFFFFFu; + Require(sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == imeTypeByLanguage[4]); + Require(::setenv("LC_ALL", "ja_JP.UTF-8", 1) == 0); + type = 0xFFFFFFFFu; + Require(sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == imeTypeByLanguage[4]); +#endif SetLanguage(nullptr); Require(ImeKeyboardType::IsPs4TitleId("CUSA00000")); @@ -208,6 +247,8 @@ int main() { 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); + Require(sceConvertKeycodeGetCharacter(28, 0, 31, &character) == 0 && character == u'y'); + Require(sceConvertKeycodeGetCharacter(29, 0, 39, &character) == 0 && character == u'z'); 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); @@ -219,29 +260,69 @@ int main() { char16_t kbdChar = u'?'; Require(sceConvertKeycodeGetCharacterFromKeyboardData(nullptr, 0, 0, 4, &kbdChar) == static_cast(0x80BC0031u)); Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 4, nullptr) == static_cast(0x80BC0031u)); + constexpr int invalidParameter = static_cast(0x80BC0030u); for (std::int32_t badIndex : {-1, 1, 16}) { kbdChar = u'?'; - bool rejected = false; - try { - sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, badIndex, 0, 4, &kbdChar); - } catch (const std::runtime_error&) { - rejected = true; - } - Require(rejected && kbdChar == u'?'); + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, badIndex, 0, 4, &kbdChar) == invalidParameter && kbdChar == u'?'); } + for (std::uint32_t badState : {0x1u, 0x200u, 0x20000u}) { + kbdChar = u'?'; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, badState, 4, &kbdChar) == invalidParameter && kbdChar == u'?'); + } + for (std::int32_t badLength : {0, 17, -1}) { + KeyboardData badData = keyData; + badData.length = badLength; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&badData, 0, 0, 4, &kbdChar) == invalidParameter); + } + KeyboardData badLed = keyData; + badLed.led = 8; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&badLed, 0, 0, 4, &kbdChar) == invalidParameter); + KeyboardData badModifier = keyData; + badModifier.modifier_key = 0x100; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&badModifier, 0, 0, 40, &kbdChar) == invalidParameter); for (std::uint32_t layout : {31u, 39u}) { kbdChar = u'?'; - bool rejected = false; - try { - sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, layout, &kbdChar); - } catch (const std::runtime_error&) { - rejected = true; - } - Require(rejected && kbdChar == u'?'); + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, layout, &kbdChar) == 0 && kbdChar == u'a'); } Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 0, &kbdChar) == static_cast(0x80BC001Du)); Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 40, &kbdChar) == static_cast(0x80BC001Du)); Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 4, &kbdChar) == 0 && kbdChar == u'a'); - Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0x200, 4, &kbdChar) == 0 && kbdChar == u'A'); + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0x80, 4, &kbdChar) == 0 && kbdChar == u'a'); + KeyboardData shifted = keyData; + shifted.modifier_key = 0x02; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&shifted, 0, 0, 4, &kbdChar) == 0 && kbdChar == u'A'); + KeyboardData capsLock = keyData; + capsLock.led = 2; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&capsLock, 0, 0, 4, &kbdChar) == 0 && kbdChar == u'A'); + KeyboardData numLock = keyData; + numLock.led = 1; + numLock.key_code[0] = 89; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&numLock, 0, 0, 4, &kbdChar) == 0 && kbdChar == u'1'); + KeyboardData full = keyData; + full.length = 16; + full.key_code[15] = 5; + full.led = 7; + full.modifier_key = 0xFF; + char16_t expected = u'?'; + Require(sceConvertKeycodeGetCharacter(5, 0x7FF00, 4, &expected) == 0); + kbdChar = u'?'; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&full, 15, 0, 4, &kbdChar) == 0 && kbdChar == expected); + for (std::uint32_t goodState : {0x4000000u, 0x8000000u, 0x10000000u, 0x1C000080u}) { + for (std::uint32_t layout : {4u, 24u, 30u}) { + Require(sceConvertKeycodeGetCharacter(4, goodState, layout, &expected) == 0); + kbdChar = u'?'; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, goodState, layout, &kbdChar) == 0 && kbdChar == expected); + } + } + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0x4000000, 24, &kbdChar) == 0 && kbdChar == 0x3141); + for (std::uint32_t badState : {0x40u, 0x100u, 0x20000000u, 0x80000000u}) { + kbdChar = u'?'; + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, badState, 4, &kbdChar) == invalidParameter && kbdChar == u'?'); + } + const std::uint32_t oldSdkVersion = 0x03000000u; + std::memcpy(process.data() + 16, &oldSdkVersion, sizeof(oldSdkVersion)); + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 38, &kbdChar) == static_cast(0x80BC001Du)); + Require(sceConvertKeycodeGetCharacterFromKeyboardData(&badLed, 0, 0, 38, &kbdChar) == invalidParameter); + std::memcpy(process.data() + 16, &sdkVersion, sizeof(sdkVersion)); return 0; } diff --git a/core/libs/tests/GuestConvertKeycodeTitle.cpp b/core/libs/tests/GuestConvertKeycodeTitle.cpp index 0ee595de20..1c7ee9402b 100644 --- a/core/libs/tests/GuestConvertKeycodeTitle.cpp +++ b/core/libs/tests/GuestConvertKeycodeTitle.cpp @@ -7,7 +7,7 @@ #include #include -int main() { +int main(int argc, char** argv) { #ifdef _WIN32 if (_putenv_s("ANYPS5_LANGUAGE", "") != 0) return 2; #else @@ -27,5 +27,34 @@ int main() { if (sceConvertKeycodeGetCharacter(0, 0, 0, &character) != static_cast(0x80BC001Du)) return 1; if (sceConvertKeycodeGetCharacter(0, 0, 40, &character) != static_cast(0x80BC001Du)) return 1; if (sceConvertKeycodeGetImeKeyboardType(0x10000000, nullptr) != static_cast(0x80BC0031u)) return 1; + if (argc > 1 && std::strcmp(argv[1], "ps4") == 0) { + constexpr int rejected = static_cast(0x80BC001Du); + KeyboardData keyData{}; + keyData.length = 1; + keyData.key_code[0] = 4; + const auto setSdkVersion = [](std::uint32_t version) { std::memcpy(process.data() + 16, &version, sizeof(version)); }; + setSdkVersion(0x04FFFFFFu); + virtualKey = 0xABCD; + character = u'?'; + if (sceConvertKeycodeGetVirtualKeycode(28, 31, &virtualKey) != rejected || virtualKey != 0xABCD) return 1; + if (sceConvertKeycodeGetCharacter(28, 0, 31, &character) != rejected || character != u'?') return 1; + if (sceConvertKeycodeGetCharacterFromKeyboardData(&keyData, 0, 0, 31, &character) != rejected || character != u'?') return 1; + setSdkVersion(0x05000000u); + if (sceConvertKeycodeGetVirtualKeycode(28, 31, &virtualKey) != 0 || virtualKey != 89) return 1; + if (sceConvertKeycodeGetVirtualKeycode(28, 38, &virtualKey) != rejected) return 1; + setSdkVersion(0x08FFFFFFu); + if (sceConvertKeycodeGetVirtualKeycode(28, 2, &virtualKey) != 0 || virtualKey != 89) return 1; + setSdkVersion(0x09000000u); + if (sceConvertKeycodeGetVirtualKeycode(28, 2, &virtualKey) != 0 || virtualKey != 90) return 1; + setSdkVersion(0x0FFFFFFFu); + if (sceConvertKeycodeGetVirtualKeycode(28, 38, &virtualKey) != rejected) return 1; + setSdkVersion(0x10000000u); + if (sceConvertKeycodeGetVirtualKeycode(28, 38, &virtualKey) != 0 || virtualKey != 89) return 1; + setSdkVersion(0xFFFFFFFFu); + if (sceConvertKeycodeGetVirtualKeycode(28, 39, &virtualKey) != rejected) return 1; + if (sceConvertKeycodeGetCharacter(28, 0, 39, &character) != rejected) return 1; + setSdkVersion(sdkVersion); + } + if (sceConvertKeycodeGetImeKeyboardType(-1, &type) != static_cast(0x80BC0010u)) return 1; return sceConvertKeycodeGetImeKeyboardType(0x10000000, &type) == 0 && type == 4 ? 0 : 1; } diff --git a/core/libs/tests/GuestDecimalBoundary.cpp b/core/libs/tests/GuestDecimalBoundary.cpp new file mode 100644 index 0000000000..51490bbbd8 --- /dev/null +++ b/core/libs/tests/GuestDecimalBoundary.cpp @@ -0,0 +1,88 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestDecimalBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +int main() { + Buffer input; + for (long long value : {-2147483648LL, -9999LL, -1LL, 0LL, 1LL, 9999LL, 2147483647LL}) { + const auto number = std::to_string(value); + for (std::size_t spaces : {0u, 1u, 15u, 64u, 128u}) { + for (const std::string suffix : {std::string{}, std::string{"tail"}}) { + const auto text = std::string(spaces, ' ') + "\t" + (value >= 0 ? "+" : "") + number + suffix; + auto* source = input.At(text.size() + 1); + std::copy(text.begin(), text.end(), source); + source[text.size()] = 0; + const std::string saved(source, text.size() + 1); + Require(atoi_nid_postfix(source) == value); + Require(atol_nid_postfix(source) == value); + Require(atoll_nid_postfix(source) == value); + Require(std::equal(saved.begin(), saved.end(), source)); + } + } + } +} diff --git a/core/libs/tests/GuestDeviceName.cpp b/core/libs/tests/GuestDeviceName.cpp new file mode 100644 index 0000000000..e34dfcf57d --- /dev/null +++ b/core/libs/tests/GuestDeviceName.cpp @@ -0,0 +1,95 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "Device name check failed at line %d\n", line); + std::abort(); + } +} +#define Check(value) Require((value), __LINE__) + +namespace { +constexpr std::uint16_t Character = 0020000; +constexpr std::uint16_t Block = 0060000; +constexpr std::uint16_t Regular = 0100000; +constexpr std::uint16_t Directory = 0040000; +constexpr std::uint32_t NoDevice = 0xFFFFFFFFu; + +std::string Name(std::uint32_t device, std::uint16_t type) { + std::array buffer; + buffer.fill('#'); + char* result = devname_r_nid_postfix(device, type, buffer.data(), static_cast(buffer.size())); + Check(result == buffer.data()); + return result; +} +} + +int main() { + Check(Name(NoDevice, Character) == "#NODEV"); + Check(Name(NoDevice, Block) == "#NODEV"); + Check(Name(0x1234, Regular) == "#NODEV"); + Check(Name(0x1234, Directory) == "#NODEV"); + Check(Name(0x1234, 0) == "#NODEV"); + Check(Name(0x1234, Character | 0644) == "#C:0x1234"); + Check(Name(0, Character) == "#C:0"); + Check(Name(1, Character) == "#C:0x1"); + Check(Name(0xF, Character) == "#C:0xf"); + Check(Name(0x10, Character) == "#C:0x10"); + Check(Name(0xDEADBEEFu, Character) == "#C:0xdeadbeef"); + Check(Name(0xFFFFFFFEu, Character) == "#C:0xfffffffe"); + Check(Name(0x61006001u, Block) == "#B:0x61006001"); + Check(Name(0x6001u, Block) == "#B:0x6001"); + Check(Name(0x1234, Block) == "#NODEV"); + Check(Name(0x1234, Block | 0644) == "#NODEV"); + + for (std::size_t length = 0; length <= 16; ++length) { + std::array buffer; + buffer.fill('#'); + char* result = devname_r_nid_postfix(0xABCDu, Character, buffer.data() + 1, static_cast(length)); + Check(result == buffer.data() + 1); + const std::string expected = "#C:0xabcd"; + Check(buffer.front() == '#'); + for (std::size_t index = length + 1; index < buffer.size(); ++index) Check(buffer[index] == '#'); + if (length != 0) Check(std::string(buffer.data() + 1, std::min(length - 1, expected.size())) == expected.substr(0, length - 1)); + if (length != 0 && length <= expected.size()) Check(buffer[length] == '\0'); + if (length > expected.size()) Check(std::strcmp(buffer.data() + 1, expected.c_str()) == 0); + } + + for (std::size_t length = 0; length <= 8; ++length) { + std::array buffer; + buffer.fill('#'); + devname_r_nid_postfix(NoDevice, Character, buffer.data() + 1, static_cast(length)); + const std::string expected = "#NODEV"; + if (length == 0) Check(buffer[1] == '#'); + else { + Check(std::strncmp(buffer.data() + 1, expected.c_str(), std::min(length - 1, expected.size())) == 0); + Check(buffer[1 + std::min(length - 1, expected.size())] == '\0'); + } + } + + std::array negative; + negative.fill('#'); + *__error_nid_postfix() = 0; + Check(devname_r_nid_postfix(0x12, Character, negative.data(), -1) == negative.data()); + Check(*__error_nid_postfix() == 84 && negative[0] == '\0' && negative[1] == '#'); + *__error_nid_postfix() = 0; + Check(devname_r_nid_postfix(0x12, Character, negative.data(), std::numeric_limits::min()) == negative.data()); + Check(*__error_nid_postfix() == 84 && negative[0] == '\0'); + + char* first = devname_nid_postfix(0x77, Character); + Check(first != nullptr && std::strcmp(first, "#C:0x77") == 0); + char* second = devname_nid_postfix(NoDevice, Character); + Check(second == first && std::strcmp(second, "#NODEV") == 0); + char* third = devname_nid_postfix(0x6001u, Block); + Check(third == first && std::strcmp(third, "#B:0x6001") == 0); + Check(std::strlen(devname_nid_postfix(0xFFFFFFFEu, Character)) == 13); +} diff --git a/core/libs/tests/GuestDinkumwareDivision.cpp b/core/libs/tests/GuestDinkumwareDivision.cpp new file mode 100644 index 0000000000..946ab9926a --- /dev/null +++ b/core/libs/tests/GuestDinkumwareDivision.cpp @@ -0,0 +1,192 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct Case { + unsigned function; + unsigned rounding; + unsigned denormals; + std::uint64_t first; + std::uint64_t second; + std::uint64_t result; + unsigned flags; +}; + +constexpr Case Cases[] = { + {0, 0, 0, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 0, 0, 0x0000000000000000ull, 0x0000000000000000ull, 0xfff8000000000000ull, 1u}, + {0, 0, 1, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 0, 1, 0x3ff0000000000000ull, 0x0000000000000000ull, 0x7ff0000000000000ull, 4u}, + {0, 0, 2, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 0, 2, 0xbff0000000000000ull, 0x0000000000000000ull, 0xfff0000000000000ull, 4u}, + {0, 0, 3, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 0, 3, 0x0000000000000000ull, 0xbff0000000000000ull, 0x8000000000000000ull, 0u}, + {0, 1, 0, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 1, 0, 0x7ff0000000000001ull, 0x3ff0000000000000ull, 0x7ff8000000000001ull, 1u}, + {0, 1, 1, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 1, 1, 0x7ff8000000001234ull, 0xfff8000000005678ull, 0x7ff8000000001234ull, 0u}, + {0, 1, 2, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 1, 2, 0xfff8000000005678ull, 0x7ff8000000001234ull, 0xfff8000000005678ull, 0u}, + {0, 1, 3, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 1, 3, 0x0000000000000001ull, 0xbff0000000000000ull, 0x8000000000000000ull, 0u}, + {0, 2, 0, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555556ull, 32u}, + {0, 2, 0, 0x7fefffffffffffffull, 0x3fd0000000000000ull, 0x7ff0000000000000ull, 40u}, + {0, 2, 1, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555556ull, 32u}, + {0, 2, 1, 0x3fd0000000000000ull, 0x7fefffffffffffffull, 0x0001000000000001ull, 48u}, + {0, 2, 2, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555556ull, 32u}, + {0, 2, 2, 0x0000000000000000ull, 0x0000000000000000ull, 0xfff8000000000000ull, 1u}, + {0, 2, 3, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555556ull, 32u}, + {0, 2, 3, 0x3ff0000000000000ull, 0x0000000000000000ull, 0x7ff0000000000000ull, 4u}, + {0, 3, 0, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 3, 0, 0xbff0000000000000ull, 0x0000000000000000ull, 0xfff0000000000000ull, 4u}, + {0, 3, 1, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 3, 1, 0x0000000000000000ull, 0xbff0000000000000ull, 0x8000000000000000ull, 0u}, + {0, 3, 2, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 3, 2, 0x7ff0000000000001ull, 0x3ff0000000000000ull, 0x7ff8000000000001ull, 1u}, + {0, 3, 3, 0x3ff0000000000000ull, 0x4008000000000000ull, 0x3fd5555555555555ull, 32u}, + {0, 3, 3, 0x7ff8000000001234ull, 0xfff8000000005678ull, 0x7ff8000000001234ull, 0u}, + {1, 0, 0, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaabull, 32u}, + {1, 0, 0, 0x0000000000000000ull, 0x0000000000000000ull, 0x00000000ffc00000ull, 1u}, + {1, 0, 1, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaabull, 32u}, + {1, 0, 1, 0x000000003f800000ull, 0x0000000000000000ull, 0x000000007f800000ull, 4u}, + {1, 0, 2, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaabull, 32u}, + {1, 0, 2, 0x00000000bf800000ull, 0x0000000000000000ull, 0x00000000ff800000ull, 4u}, + {1, 0, 3, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaabull, 32u}, + {1, 0, 3, 0x0000000000000000ull, 0x00000000bf800000ull, 0x0000000080000000ull, 0u}, + {1, 1, 0, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaaaull, 32u}, + {1, 1, 0, 0x000000007f800001ull, 0x000000003f800000ull, 0x000000007fc00001ull, 1u}, + {1, 1, 1, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaaaull, 32u}, + {1, 1, 1, 0x000000007fc01234ull, 0x00000000ffc05678ull, 0x000000007fc01234ull, 0u}, + {1, 1, 2, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaaaull, 32u}, + {1, 1, 2, 0x00000000ffc05678ull, 0x000000007fc01234ull, 0x00000000ffc05678ull, 0u}, + {1, 1, 3, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaaaull, 32u}, + {1, 1, 3, 0x0000000000000001ull, 0x00000000bf800000ull, 0x0000000080000000ull, 0u}, + {1, 2, 0, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaabull, 32u}, + {1, 2, 0, 0x000000007f7fffffull, 0x000000003e800000ull, 0x000000007f800000ull, 40u}, + {1, 2, 1, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaabull, 32u}, + {1, 2, 1, 0x000000003e800000ull, 0x000000007f7fffffull, 0x0000000000080001ull, 48u}, + {1, 2, 2, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaabull, 32u}, + {1, 2, 2, 0x0000000000000000ull, 0x0000000000000000ull, 0x00000000ffc00000ull, 1u}, + {1, 2, 3, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaabull, 32u}, + {1, 2, 3, 0x000000003f800000ull, 0x0000000000000000ull, 0x000000007f800000ull, 4u}, + {1, 3, 0, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaaaull, 32u}, + {1, 3, 0, 0x00000000bf800000ull, 0x0000000000000000ull, 0x00000000ff800000ull, 4u}, + {1, 3, 1, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaaaull, 32u}, + {1, 3, 1, 0x0000000000000000ull, 0x00000000bf800000ull, 0x0000000080000000ull, 0u}, + {1, 3, 2, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaaaull, 32u}, + {1, 3, 2, 0x000000007f800001ull, 0x000000003f800000ull, 0x000000007fc00001ull, 1u}, + {1, 3, 3, 0x000000003f800000ull, 0x0000000040400000ull, 0x000000003eaaaaaaull, 32u}, + {1, 3, 3, 0x000000007fc01234ull, 0x00000000ffc05678ull, 0x000000007fc01234ull, 0u}, + {2, 0, 0, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 0, 0, 0x0000000000000000ull, 0x0000000000000000ull, 0x7ff0000000000000ull, 4u}, + {2, 0, 1, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 0, 1, 0x8000000000000000ull, 0x0000000000000000ull, 0xfff0000000000000ull, 4u}, + {2, 0, 2, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 0, 2, 0x0000000000000001ull, 0x0000000000000000ull, 0x7ff0000000000000ull, 42u}, + {2, 0, 3, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 0, 3, 0x000fffffffffffffull, 0x0000000000000000ull, 0x7ff0000000000000ull, 4u}, + {2, 1, 0, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 1, 0, 0x0010000000000000ull, 0x0000000000000000ull, 0x7fd0000000000000ull, 0u}, + {2, 1, 1, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 1, 1, 0x7fefffffffffffffull, 0x0000000000000000ull, 0x0004000000000000ull, 48u}, + {2, 1, 2, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 1, 2, 0x7ff0000000000000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0u}, + {2, 1, 3, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 1, 3, 0x7ff0000000000001ull, 0x0000000000000000ull, 0x7ff8000000000001ull, 1u}, + {2, 2, 0, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555556ull, 32u}, + {2, 2, 0, 0x7ff8000000001234ull, 0x0000000000000000ull, 0x7ff8000000001234ull, 0u}, + {2, 2, 1, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555556ull, 32u}, + {2, 2, 1, 0xfff8000000005678ull, 0x0000000000000000ull, 0xfff8000000005678ull, 0u}, + {2, 2, 2, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555556ull, 32u}, + {2, 2, 2, 0x0000000000000000ull, 0x0000000000000000ull, 0x7ff0000000000000ull, 4u}, + {2, 2, 3, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555556ull, 32u}, + {2, 2, 3, 0x8000000000000000ull, 0x0000000000000000ull, 0xfff0000000000000ull, 4u}, + {2, 3, 0, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 3, 0, 0x0000000000000001ull, 0x0000000000000000ull, 0x7fefffffffffffffull, 42u}, + {2, 3, 1, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 3, 1, 0x000fffffffffffffull, 0x0000000000000000ull, 0x7ff0000000000000ull, 4u}, + {2, 3, 2, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 3, 2, 0x0010000000000000ull, 0x0000000000000000ull, 0x7fd0000000000000ull, 0u}, + {2, 3, 3, 0x4008000000000000ull, 0x0000000000000000ull, 0x3fd5555555555555ull, 32u}, + {2, 3, 3, 0x7fefffffffffffffull, 0x0000000000000000ull, 0x0000000000000000ull, 48u}, + {3, 0, 0, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaabull, 32u}, + {3, 0, 0, 0x0000000000000000ull, 0x0000000000000000ull, 0x000000007f800000ull, 4u}, + {3, 0, 1, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaabull, 32u}, + {3, 0, 1, 0x0000000080000000ull, 0x0000000000000000ull, 0x00000000ff800000ull, 4u}, + {3, 0, 2, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaabull, 32u}, + {3, 0, 2, 0x0000000000000001ull, 0x0000000000000000ull, 0x000000007f800000ull, 42u}, + {3, 0, 3, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaabull, 32u}, + {3, 0, 3, 0x00000000007fffffull, 0x0000000000000000ull, 0x000000007f800000ull, 4u}, + {3, 1, 0, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaaaull, 32u}, + {3, 1, 0, 0x0000000000800000ull, 0x0000000000000000ull, 0x000000007e800000ull, 0u}, + {3, 1, 1, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaaaull, 32u}, + {3, 1, 1, 0x000000007f7fffffull, 0x0000000000000000ull, 0x0000000000200000ull, 48u}, + {3, 1, 2, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaaaull, 32u}, + {3, 1, 2, 0x000000007f800000ull, 0x0000000000000000ull, 0x0000000000000000ull, 0u}, + {3, 1, 3, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaaaull, 32u}, + {3, 1, 3, 0x000000007f800001ull, 0x0000000000000000ull, 0x000000007fc00001ull, 1u}, + {3, 2, 0, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaabull, 32u}, + {3, 2, 0, 0x000000007fc01234ull, 0x0000000000000000ull, 0x000000007fc01234ull, 0u}, + {3, 2, 1, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaabull, 32u}, + {3, 2, 1, 0x00000000ffc05678ull, 0x0000000000000000ull, 0x00000000ffc05678ull, 0u}, + {3, 2, 2, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaabull, 32u}, + {3, 2, 2, 0x0000000000000000ull, 0x0000000000000000ull, 0x000000007f800000ull, 4u}, + {3, 2, 3, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaabull, 32u}, + {3, 2, 3, 0x0000000080000000ull, 0x0000000000000000ull, 0x00000000ff800000ull, 4u}, + {3, 3, 0, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaaaull, 32u}, + {3, 3, 0, 0x0000000000000001ull, 0x0000000000000000ull, 0x000000007f7fffffull, 42u}, + {3, 3, 1, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaaaull, 32u}, + {3, 3, 1, 0x00000000007fffffull, 0x0000000000000000ull, 0x000000007f800000ull, 4u}, + {3, 3, 2, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaaaull, 32u}, + {3, 3, 2, 0x0000000000800000ull, 0x0000000000000000ull, 0x000000007e800000ull, 0u}, + {3, 3, 3, 0x0000000040400000ull, 0x0000000000000000ull, 0x000000003eaaaaaaull, 32u}, + {3, 3, 3, 0x000000007f7fffffull, 0x0000000000000000ull, 0x0000000000000000ull, 48u}, +}; + +int StandardFlags(unsigned flags) { + return ((flags & 1u) ? FE_INVALID : 0) | ((flags & 4u) ? FE_DIVBYZERO : 0) | + ((flags & 8u) ? FE_OVERFLOW : 0) | ((flags & 16u) ? FE_UNDERFLOW : 0) | + ((flags & 32u) ? FE_INEXACT : 0); +} + +} + +int main() { + std::fenv_t saved; + if (std::fegetenv(&saved) != 0) return 1; + const auto savedCsr = _mm_getcsr(); + const int savedError = *__error_nid_postfix(); + unsigned index = 0; + for (const auto& test : Cases) { + for (unsigned sticky : {0u, 5u, 32u}) { + if (std::fesetenv(FE_DFL_ENV) != 0) return 1; + const unsigned control = 0x1f80u | (test.rounding << 13u) | ((test.denormals & 1u) ? 0x40u : 0u) | ((test.denormals & 2u) ? 0x8000u : 0u); + *__error_nid_postfix() = 97; + _mm_setcsr(control | sticky); + std::uint64_t result = 0; + if (test.function == 0) result = std::bit_cast(_Divide_nid_postfix(std::bit_cast(test.first), std::bit_cast(test.second))); + else if (test.function == 1) result = std::bit_cast(_FDivide_nid_postfix(std::bit_cast(static_cast(test.first)), std::bit_cast(static_cast(test.second)))); + else if (test.function == 2) result = std::bit_cast(_Recip_nid_postfix(std::bit_cast(test.first))); + else result = std::bit_cast(_FRecip_nid_postfix(std::bit_cast(static_cast(test.first)))); + const unsigned csr = _mm_getcsr(); + const int flags = std::fetestexcept(FE_ALL_EXCEPT); + const int error = *__error_nid_postfix(); + if (result != test.result || csr != (control | test.flags | sticky) || flags != StandardFlags(test.flags | sticky) || error != 97) { + std::fprintf(stderr, "Dinkumware division case %u sticky %u: bits %016llx/%016llx csr %x/%x flags %x/%x errno %d\n", index, sticky, + static_cast(result), static_cast(test.result), csr, control | test.flags | sticky, flags, StandardFlags(test.flags | sticky), error); + return 1; + } + } + ++index; + } + if (std::fesetenv(&saved) != 0) return 1; + _mm_setcsr(savedCsr); + *__error_nid_postfix() = savedError; +} diff --git a/core/libs/tests/GuestDinkumwareFloatBits.cpp b/core/libs/tests/GuestDinkumwareFloatBits.cpp new file mode 100644 index 0000000000..6f2fd9dbac --- /dev/null +++ b/core/libs/tests/GuestDinkumwareFloatBits.cpp @@ -0,0 +1,149 @@ +#include "prx/libc/libc.hpp" +#include +#include +#include +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +static double D(std::uint64_t bits) { return std::bit_cast(bits); } + +static float F(std::uint32_t bits) { return std::bit_cast(bits); } + +static long double X(std::uint16_t signExponent, std::uint64_t mantissa) { + long double value = 0; + std::memcpy(&value, &mantissa, sizeof(mantissa)); + std::memcpy(reinterpret_cast(&value) + 8, &signExponent, sizeof(signExponent)); + return value; +} + +static bool SameBits(double left, double right) { return std::bit_cast(left) == std::bit_cast(right); } + +static bool SameBits(float left, float right) { return std::bit_cast(left) == std::bit_cast(right); } + +static bool SameBits(long double left, long double right) { return std::memcmp(&left, &right, 10) == 0; } + +static const double kPoly[] = {0.1, 0.7, 1.3, -2.9}; +static const float kFPoly[] = {0.1f, 0.7f, 1.3f, -2.9f}; +static const long double kLPoly[] = {0.1L, 0.7L, 1.3L, -2.9L}; + +template +static void CheckInt(short (APS5_VABI* function)(T*, short), T input, short scale, short code, T expected) { + T value = input; + Require(function(&value, scale) == code && SameBits(value, expected)); +} + +template +static void CheckScale(short (APS5_VABI* function)(T*, std::int64_t), T input, std::int64_t exponent, short code, T expected) { + T value = input; + Require(function(&value, exponent) == code && SameBits(value, expected)); +} + +template +static void CheckUnscale(short (APS5_VABI* function)(short*, T*), T input, short code, short exponent, T expected) { + T value = input; + short result = 77; + Require(function(&result, &value) == code && result == exponent && SameBits(value, expected)); +} + +template +static void CheckNorm(short (APS5_VABI* function)(std::uint16_t*), T input, short code, T expected) { + T value = input; + Require(function(reinterpret_cast(&value)) == code && SameBits(value, expected)); +} + +int main() { + CheckInt(_Dint_nid_postfix, D(0x4006000000000000ull), 0, -1, D(0x4000000000000000ull)); + CheckInt(_Dint_nid_postfix, D(0xc006000000000000ull), 1, -1, D(0xc004000000000000ull)); + CheckInt(_Dint_nid_postfix, D(0x4008000000000000ull), 0, 0, D(0x4008000000000000ull)); + CheckInt(_Dint_nid_postfix, D(0x3fd0000000000000ull), 0, -1, D(0x0000000000000000ull)); + CheckInt(_Dint_nid_postfix, D(0x8000000000000000ull), 5, 0, D(0x8000000000000000ull)); + CheckInt(_Dint_nid_postfix, D(0x7ff0000000000000ull), 0, 1, D(0x7ff0000000000000ull)); + CheckInt(_Dint_nid_postfix, D(0x7ff8000000000000ull), 0, 2, D(0x7ff8000000000000ull)); + CheckInt(_Dint_nid_postfix, D(0x7e37e43c8800759cull), 0, 0, D(0x7e37e43c8800759cull)); + CheckInt(_Dint_nid_postfix, D(0x0000000000000001ull), 1100, 0, D(0x0000000000000001ull)); + CheckInt(_Dint_nid_postfix, D(0x3ff8000000000000ull), -32000, -1, D(0x0000000000000000ull)); + CheckInt(_Dint_nid_postfix, D(0x3ff8000000000000ull), 32000, 0, D(0x3ff8000000000000ull)); + CheckInt(_Dint_nid_postfix, D(0x405edd2f1a9fbe77ull), 3, -1, D(0x405ed80000000000ull)); + CheckInt(_FDint_nid_postfix, F(0x40300000u), 0, -1, F(0x40000000u)); + CheckInt(_FDint_nid_postfix, F(0xc0300000u), 1, -1, F(0xc0200000u)); + CheckInt(_FDint_nid_postfix, F(0x3e800000u), 0, -1, F(0x00000000u)); + CheckInt(_FDint_nid_postfix, F(0x7f800000u), 0, 1, F(0x7f800000u)); + CheckInt(_FDint_nid_postfix, F(0x3fc00000u), -32700, -1, F(0x00000000u)); + CheckInt(_FDint_nid_postfix, F(0x42f6e979u), 3, -1, F(0x42f6c000u)); + CheckInt(_LDint_nid_postfix, X(0x4000, 0xb000000000000000ull), 0, -1, X(0x4000, 0x8000000000000000ull)); + CheckInt(_LDint_nid_postfix, X(0xc000, 0xb000000000000000ull), 1, -1, X(0xc000, 0xa000000000000000ull)); + CheckInt(_LDint_nid_postfix, X(0x3ffd, 0x8000000000000000ull), 0, -1, X(0x0000, 0x0000000000000000ull)); + CheckInt(_LDint_nid_postfix, X(0x7fff, 0x8000000000000000ull), 0, 1, X(0x7fff, 0x8000000000000000ull)); + CheckInt(_LDint_nid_postfix, X(0x7fff, 0xc000000000000000ull), 0, 2, X(0x7fff, 0xc000000000000000ull)); + CheckInt(_LDint_nid_postfix, X(0x1234, 0x0000000000000000ull), 3, 0, X(0x1234, 0x0000000000000000ull)); + CheckInt(_LDint_nid_postfix, X(0x0000, 0x8e5f1900ace5e5bfull), -16411, 0, X(0x0000, 0x8e5f1900ace5e5bfull)); + CheckInt(_LDint_nid_postfix, X(0x7f96, 0xbe8ad47f0a38a106ull), 16769, -1, X(0x0000, 0x0000000000000000ull)); + CheckInt(_LDint_nid_postfix, X(0x4000, 0x4000000000000001ull), 0, -1, X(0x4000, 0x4000000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0x3ff8000000000000ull), std::int64_t{3}, -1, D(0x4028000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0xbff8000000000000ull), std::int64_t{-1080}, 0, D(0x8000000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0x3ff8000000000000ull), std::int64_t{-1075}, -1, D(0x0000000000000001ull)); + CheckScale(_Dscale_nid_postfix, D(0x7fe1ccf385ebc8a0ull), std::int64_t{10}, 1, D(0x7ff0000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0xffe1ccf385ebc8a0ull), std::int64_t{10}, 1, D(0xfff0000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0x0000000000000001ull), std::int64_t{60}, -1, D(0x0090000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0x0000000000000000ull), std::int64_t{10}, 0, D(0x0000000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0x7ff8000000000000ull), std::int64_t{1}, 2, D(0x7ff8000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0x3ff0000000000000ull), std::int64_t{1} << 40, 1, D(0x7ff0000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0x3ff0000000000000ull), std::int64_t{1} << 31, 1, D(0x7ff0000000000000ull)); + CheckScale(_Dscale_nid_postfix, D(0x3ff0000000000000ull), -(std::int64_t{1} << 32) + 3, 0, D(0x0000000000000000ull)); + CheckScale(_FDscale_nid_postfix, F(0x3fc00000u), std::int64_t{3}, -1, F(0x41400000u)); + CheckScale(_FDscale_nid_postfix, F(0xbfc00000u), std::int64_t{-160}, 0, F(0x80000000u)); + CheckScale(_FDscale_nid_postfix, F(0x7f61b1e6u), std::int64_t{2}, 1, F(0x7f800000u)); + CheckScale(_FDscale_nid_postfix, F(0x00000001u), std::int64_t{30}, -1, F(0x04000000u)); + CheckScale(_LDscale_nid_postfix, X(0x3fff, 0xc000000000000000ull), std::int64_t{3}, -1, X(0x4002, 0xc000000000000000ull)); + CheckScale(_LDscale_nid_postfix, X(0xbfff, 0xc000000000000000ull), std::int64_t{-17000}, 0, X(0x0000, 0x0000000000000000ull)); + CheckScale(_LDscale_nid_postfix, X(0x8000, 0xc388414f03e224f9ull), std::int64_t{-17526}, 0, X(0x0000, 0x0000000000000000ull)); + CheckScale(_LDscale_nid_postfix, X(0x7f94, 0xae9204275937a4c1ull), std::int64_t{100}, -1, X(0x7ff8, 0xae9204275937a4c1ull)); + CheckScale(_LDscale_nid_postfix, X(0x7f94, 0xae9204275937a4c1ull), std::int64_t{200}, 1, X(0x7fff, 0x8000000000000000ull)); + CheckScale(_LDscale_nid_postfix, X(0x4000, 0x4000000000000000ull), std::int64_t{1}, -1, X(0x4000, 0x8000000000000000ull)); + CheckScale(_LDscale_nid_postfix, X(0xec64, 0x0000000000000000ull), std::int64_t{9362}, 0, X(0x8000, 0x0000000000000000ull)); + CheckScale(_LDscale_nid_postfix, X(0x0000, 0x8000000000000000ull), std::int64_t{1}, -1, X(0x0002, 0x8000000000000000ull)); + CheckUnscale(_Dunscale_nid_postfix, D(0x4028000000000000ull), -1, 4, D(0x3fe8000000000000ull)); + CheckUnscale(_Dunscale_nid_postfix, D(0xbfd8000000000000ull), -1, -1, D(0xbfe8000000000000ull)); + CheckUnscale(_Dunscale_nid_postfix, D(0x0000000000000001ull), -1, -1073, D(0x3fe0000000000000ull)); + CheckUnscale(_Dunscale_nid_postfix, D(0x0000000000000000ull), 0, 0, D(0x0000000000000000ull)); + CheckUnscale(_Dunscale_nid_postfix, D(0x7ff0000000000000ull), 1, 0, D(0x7ff0000000000000ull)); + CheckUnscale(_Dunscale_nid_postfix, D(0x7ff8000000000000ull), 2, 0, D(0x7ff8000000000000ull)); + CheckUnscale(_FDunscale_nid_postfix, F(0x41400000u), -1, 4, F(0x3f400000u)); + CheckUnscale(_FDunscale_nid_postfix, F(0x00000001u), -1, -148, F(0x3f000000u)); + CheckUnscale(_FDunscale_nid_postfix, F(0xff800000u), 1, 0, F(0xff800000u)); + CheckUnscale(_LDunscale_nid_postfix, X(0x4002, 0xc000000000000000ull), -1, 4, X(0x3ffe, 0xc000000000000000ull)); + CheckUnscale(_LDunscale_nid_postfix, X(0x4724, 0x4e343e19658fad25ull), -1, 1829, X(0x3ffe, 0x9c687c32cb1f5a4aull)); + CheckUnscale(_LDunscale_nid_postfix, X(0x0000, 0x0000000000000001ull), -1, -16444, X(0x3ffe, 0x8000000000000000ull)); + CheckUnscale(_LDunscale_nid_postfix, X(0x3fff, 0x0000000000000000ull), 0, 0, X(0x3fff, 0x0000000000000000ull)); + CheckNorm(_Dnorm_nid_postfix, D(0x0000000000000001ull), -51, D(0x0000000000000000ull)); + CheckNorm(_Dnorm_nid_postfix, D(0x800fffffffffffffull), 0, D(0x800ffffffffffffeull)); + CheckNorm(_Dnorm_nid_postfix, D(0x3ff0000000000000ull), 1, D(0x0000000000000000ull)); + CheckNorm(_Dnorm_nid_postfix, D(0x8000000000000000ull), 1, D(0x8000000000000000ull)); + CheckNorm(_FDnorm_nid_postfix, F(0x00000001u), -22, F(0x00000000u)); + CheckNorm(_FDnorm_nid_postfix, F(0x80400000u), 0, F(0x80000000u)); + CheckNorm(_LDnorm_nid_postfix, X(0x0000, 0x0000000000000001ull), -63, X(0x0000, 0x8000000000000000ull)); + CheckNorm(_LDnorm_nid_postfix, X(0xfcb6, 0xdf75917a3eb7b900ull), 0, X(0xfcb6, 0xdf75917a3eb7b900ull)); + CheckNorm(_LDnorm_nid_postfix, X(0x1234, 0x0000000100000000ull), -31, X(0x1234, 0x8000000000000000ull)); + Require(_Fpcomp_nid_postfix(1.0, 2.0) == 1 && _Fpcomp_nid_postfix(2.0, 1.0) == 4 && _Fpcomp_nid_postfix(1.0, 1.0) == 2 && _Fpcomp_nid_postfix(NAN, 1.0) == 0); + Require(_FFpcomp_nid_postfix(-1.0f, 2.0f) == 1 && _FFpcomp_nid_postfix(1.0f, NAN) == 0); + Require(_LFpcomp_nid_postfix(X(0x4000, 0x4000000000000000ull), 1.0L) == 2 && _LFpcomp_nid_postfix(X(0x7fff, 0x0000000000000000ull), 1.0L) == 2 && _LFpcomp_nid_postfix(X(0x7fff, 0x4000000000000000ull), 1.0L) == 0 && _LFpcomp_nid_postfix(3.0L, 1.0L) == 4); + Require(_LFpcomp_nid_postfix(X(0x4000, 0x4000000000000000ull), 3.0L) == 2 && _LFpcomp_nid_postfix(3.0L, X(0x4000, 0x4000000000000000ull)) == 2); + Require(_LFpcomp_nid_postfix(X(0x4001, 0x6000000000000000ull), 1.0L) == 2 && _LFpcomp_nid_postfix(1.0L, X(0x4001, 0x6000000000000000ull)) == 2); + Require(_LFpcomp_nid_postfix(X(0x4000, 0x4000000000000000ull), X(0x4001, 0x6000000000000000ull)) == 2 && _LFpcomp_nid_postfix(X(0x4001, 0x6000000000000000ull), X(0x4000, 0x4000000000000000ull)) == 2); + Require(_LFpcomp_nid_postfix(X(0xc000, 0x4000000000000000ull), 3.0L) == 2 && _LFpcomp_nid_postfix(X(0x4000, 0x0000000000000000ull), 1.0L) == 2 && _LFpcomp_nid_postfix(X(0x0000, 0x8000000000000000ull), 1.0L) == 1); + Require(SameBits(_Poly_nid_postfix(0.3, kPoly, 3), D(0xc0038ded288ce703ull)) && SameBits(_Poly_nid_postfix(0.3, kPoly, 0), 0.1) && SameBits(_Poly_nid_postfix(0.3, kPoly, -1), 0.1)); + Require(SameBits(_FPoly_nid_postfix(0.3f, kFPoly, 3), F(0xc01c6f6au))); + Require(SameBits(_LPoly_nid_postfix(0.3L, kLPoly, 3), X(0xc000, 0x9c6f694467381d7eull))); + long double zero = X(0x1234, 0); + bool threw = false; + try { + _LDnorm_nid_postfix(reinterpret_cast(&zero)); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw); + return 0; +} diff --git a/core/libs/tests/GuestDirectMemoryForApp.cpp b/core/libs/tests/GuestDirectMemoryForApp.cpp new file mode 100644 index 0000000000..94ac293efc --- /dev/null +++ b/core/libs/tests/GuestDirectMemoryForApp.cpp @@ -0,0 +1,38 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/DirectMemory/DirectMemory.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "Direct memory for-app check failed at %s:%u\n", location.file_name(), static_cast(location.line())); + std::abort(); + } +} + +int main() { + constexpr std::size_t page = PS5_PAGE_SIZE; + const auto size = static_cast(sceKernelGetDirectMemorySize()); + int64_t phys = 0; + + Require(sceKernelAllocateDirectMemoryForApp(0, size, 0, 0, 0, &phys) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelAllocateDirectMemoryForApp(0, size, page + 1, 0, 0, &phys) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelAllocateDirectMemoryForApp(0, size, page, 0, 0, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelAllocateDirectMemoryForApp(size, 0, page, 0, 0, &phys) == SCE_KERNEL_ERROR_EINVAL); + + phys = -1; + Require(sceKernelAllocateDirectMemoryForApp(0, size, page * 4, 0, 0, &phys) == 0); + Require(phys >= 0); + Require((phys % static_cast(page)) == 0); + Require(phys + static_cast(page * 4) <= size); + std::printf("allocated for-app phys=%lld len=%zu\n", static_cast(phys), page * 4); + Require(sceKernelReleaseDirectMemory(phys, page * 4) == 0); + + return 0; +} diff --git a/core/libs/tests/GuestDivisionPrxAbi.cpp b/core/libs/tests/GuestDivisionPrxAbi.cpp new file mode 100644 index 0000000000..c0f1a58936 --- /dev/null +++ b/core/libs/tests/GuestDivisionPrxAbi.cpp @@ -0,0 +1,159 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "check failed at line %u\n", location.line()); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#endif + +class Module { +#ifdef _WIN32 + HMODULE handle; +#else + void* handle; +#endif +public: + explicit Module(const std::filesystem::path& path) { +#ifdef _WIN32 + handle = LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + if (!handle) std::fprintf(stderr, "LoadLibraryExW failed: %lu\n", GetLastError()); +#else + handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!handle) std::fprintf(stderr, "%s\n", dlerror()); +#endif + Require(handle != nullptr); + } + ~Module() { +#ifdef _WIN32 + Require(FreeLibrary(handle) != 0); +#else + Require(dlclose(handle) == 0); +#endif + } + template TFunction Resolve(const std::string& name) { +#ifdef _WIN32 + const auto symbol = GetProcAddress(handle, name.c_str()); +#else + const auto symbol = dlsym(handle, name.c_str()); +#endif + Require(symbol != nullptr); + static_assert(sizeof(TFunction) == sizeof(symbol)); + TFunction function; + std::memcpy(&function, &symbol, sizeof(function)); + return function; + } +}; + +struct Answer { long long numerator, denominator, quotient, remainder; }; +static constexpr Answer small[] = { + {0LL, 1LL, 0LL, 0LL}, + {0LL, 2LL, 0LL, 0LL}, + {0LL, -3LL, 0LL, 0LL}, + {0LL, 17LL, 0LL, 0LL}, + {1LL, 1LL, 1LL, 0LL}, + {1LL, 2LL, 0LL, 1LL}, + {1LL, -3LL, 0LL, 1LL}, + {1LL, 17LL, 0LL, 1LL}, + {-1LL, 1LL, -1LL, 0LL}, + {-1LL, 2LL, 0LL, -1LL}, + {-1LL, -3LL, 0LL, -1LL}, + {-1LL, 17LL, 0LL, -1LL}, + {7LL, 1LL, 7LL, 0LL}, + {7LL, 2LL, 3LL, 1LL}, + {7LL, -3LL, -2LL, 1LL}, + {7LL, 17LL, 0LL, 7LL}, + {-7LL, 1LL, -7LL, 0LL}, + {-7LL, 2LL, -3LL, -1LL}, + {-7LL, -3LL, 2LL, -1LL}, + {-7LL, 17LL, 0LL, -7LL}, + {2147483647LL, 1LL, 2147483647LL, 0LL}, + {2147483647LL, 2LL, 1073741823LL, 1LL}, + {2147483647LL, -3LL, -715827882LL, 1LL}, + {2147483647LL, 17LL, 126322567LL, 8LL}, + {-2147483648LL, 1LL, -2147483648LL, 0LL}, + {-2147483648LL, 2LL, -1073741824LL, 0LL}, + {-2147483648LL, -3LL, 715827882LL, -2LL}, + {-2147483648LL, 17LL, -126322567LL, -9LL} +}; +static constexpr Answer wide[] = { + {0LL, 1LL, 0LL, 0LL}, + {0LL, 2LL, 0LL, 0LL}, + {0LL, -3LL, 0LL, 0LL}, + {0LL, 17LL, 0LL, 0LL}, + {1LL, 1LL, 1LL, 0LL}, + {1LL, 2LL, 0LL, 1LL}, + {1LL, -3LL, 0LL, 1LL}, + {1LL, 17LL, 0LL, 1LL}, + {-1LL, 1LL, -1LL, 0LL}, + {-1LL, 2LL, 0LL, -1LL}, + {-1LL, -3LL, 0LL, -1LL}, + {-1LL, 17LL, 0LL, -1LL}, + {7LL, 1LL, 7LL, 0LL}, + {7LL, 2LL, 3LL, 1LL}, + {7LL, -3LL, -2LL, 1LL}, + {7LL, 17LL, 0LL, 7LL}, + {-7LL, 1LL, -7LL, 0LL}, + {-7LL, 2LL, -3LL, -1LL}, + {-7LL, -3LL, 2LL, -1LL}, + {-7LL, 17LL, 0LL, -7LL}, + {2147483647LL, 1LL, 2147483647LL, 0LL}, + {2147483647LL, 2LL, 1073741823LL, 1LL}, + {2147483647LL, -3LL, -715827882LL, 1LL}, + {2147483647LL, 17LL, 126322567LL, 8LL}, + {-2147483648LL, 1LL, -2147483648LL, 0LL}, + {-2147483648LL, 2LL, -1073741824LL, 0LL}, + {-2147483648LL, -3LL, 715827882LL, -2LL}, + {-2147483648LL, 17LL, -126322567LL, -9LL}, + {1099511627809LL, 2LL, 549755813904LL, 1LL}, + {1099511627809LL, -7LL, -157073089687LL, 0LL}, + {1099511627809LL, 4294967297LL, 255LL, 4294967074LL}, + {-1099511627809LL, 2LL, -549755813904LL, -1LL}, + {-1099511627809LL, -7LL, 157073089687LL, 0LL}, + {-1099511627809LL, 4294967297LL, -255LL, -4294967074LL}, + {9223372036854775807LL, 2LL, 4611686018427387903LL, 1LL}, + {9223372036854775807LL, -7LL, -1317624576693539401LL, 0LL}, + {9223372036854775807LL, 4294967297LL, 2147483647LL, 2147483648LL}, + {-9223372036854775807LL, 2LL, -4611686018427387903LL, -1LL}, + {-9223372036854775807LL, -7LL, 1317624576693539401LL, 0LL}, + {-9223372036854775807LL, 4294967297LL, -2147483647LL, -2147483648LL} +}; + +template +static void Check(TFunction function, const Answer (&answers)[TSize]) { + for (const auto& answer : answers) { + using Result = decltype(function(answer.numerator, answer.denominator)); + struct GuardedResult { std::uint64_t before; Result value; std::uint64_t after; }; + GuardedResult result{0x123456789abcdef0ull, {}, 0xfedcba9876543210ull}; + result.value = function(answer.numerator, answer.denominator); + Require(result.value.quot == answer.quotient && result.value.rem == answer.remainder); + Require(result.before == 0x123456789abcdef0ull && result.after == 0xfedcba9876543210ull); + } +} + +int main(int argc, char** argv) { + Require(argc == 3); + for (int index = 1; index <= 2; ++index) { + Module module(std::filesystem::absolute(argv[index])); + const auto name = [&](const char* symbol) { return index == 1 ? std::string(symbol) + "_nid_postfix" : Nid::ComputeNid(symbol, "libc"); }; + Check(module.Resolve(name("div")), small); + Check(module.Resolve(name("ldiv")), wide); + Check(module.Resolve(name("lldiv")), wide); + Check(module.Resolve(name("imaxdiv")), wide); + } +} diff --git a/core/libs/tests/GuestDownloadDataMount.cpp b/core/libs/tests/GuestDownloadDataMount.cpp index 1c3abe8c03..9b8299d9f1 100644 --- a/core/libs/tests/GuestDownloadDataMount.cpp +++ b/core/libs/tests/GuestDownloadDataMount.cpp @@ -1,4 +1,5 @@ #include "prx/libkernel/AppMetadata/include/AppMetadata.hpp" +#include "prx/libc/include/General.hpp" #include #include #include @@ -51,6 +52,12 @@ int main(int argc, char** argv) { Require(rejected); return 0; } - Require(std::strcmp(GetAppTitleId_nid_postfix().value, mode == "sfo-child" ? "CUSA00000" : "PPSA00000") == 0); + const std::string titleId = GetAppTitleId_nid_postfix().value; + Require(std::strcmp(titleId.c_str(), mode == "sfo-child" ? "CUSA00000" : "PPSA00000") == 0); + const std::string sandboxApp0 = std::string("/mnt/sandbox/") + titleId + "_000/app0"; + Require(ResolvePath_nid_no_patch(sandboxApp0.c_str()) == (std::filesystem::current_path() / "app0").make_preferred()); + const std::string metadataFile = mode == "sfo-child" ? "param.sfo" : "param.json"; + Require(ResolvePath_nid_no_patch((sandboxApp0 + "/sce_sys/" + metadataFile).c_str()) == + (std::filesystem::current_path() / "app0" / "sce_sys" / metadataFile).make_preferred()); std::filesystem::remove_all("download0"); } diff --git a/core/libs/tests/GuestDsign.cpp b/core/libs/tests/GuestDsign.cpp new file mode 100644 index 0000000000..3fa2a95707 --- /dev/null +++ b/core/libs/tests/GuestDsign.cpp @@ -0,0 +1,30 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +int main() { + constexpr short Negative = static_cast(0x8000); + const float floatInf = std::numeric_limits::infinity(); + const float floatNaN = std::numeric_limits::quiet_NaN(); + Require(_FDsign_nid_postfix(1.0f) == 0); + Require(_FDsign_nid_postfix(0.0f) == 0); + Require(_FDsign_nid_postfix(floatInf) == 0); + Require(_FDsign_nid_postfix(-1.0f) == Negative); + Require(_FDsign_nid_postfix(-0.0f) == Negative); + Require(_FDsign_nid_postfix(-floatInf) == Negative); + Require(_FDsign_nid_postfix(-floatNaN) == Negative); + + const double doubleInf = std::numeric_limits::infinity(); + const double doubleNaN = std::numeric_limits::quiet_NaN(); + Require(_Dsign_nid_postfix(1.0) == 0); + Require(_Dsign_nid_postfix(0.0) == 0); + Require(_Dsign_nid_postfix(doubleInf) == 0); + Require(_Dsign_nid_postfix(-1.0) == Negative); + Require(_Dsign_nid_postfix(-0.0) == Negative); + Require(_Dsign_nid_postfix(-doubleInf) == Negative); + Require(_Dsign_nid_postfix(-doubleNaN) == Negative); + return 0; +} diff --git a/core/libs/tests/GuestDynamicCast.cpp b/core/libs/tests/GuestDynamicCast.cpp index 5a4212d578..ae3f708021 100644 --- a/core/libs/tests/GuestDynamicCast.cpp +++ b/core/libs/tests/GuestDynamicCast.cpp @@ -1,18 +1,27 @@ #include "prx/libc/include/Api.hpp" #include "prx/libc/include/exceptions/Runtime.hpp" +#include #include #include #include +#include #include struct TypeRecord { const void* vtable; const char* name; const TypeRecord* base; }; struct Vtable { std::intptr_t offsetToTop; const TypeRecord* type; const void* first; }; struct Object { const void* vtable; }; +struct Source { virtual ~Source() = default; }; +struct Side { virtual ~Side() = default; }; +struct Native : Side, Source {}; +struct PrivateNative : private Source { virtual ~PrivateNative() = default; }; +struct VirtualNative : virtual Source { virtual ~VirtualNative() = default; }; +struct BaseRecord { const TypeRecord* type; std::ptrdiff_t flags; }; +struct MultipleRecord { const void* vtable; const char* name; unsigned flags; unsigned count; BaseRecord bases[1]; }; 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[]; - +extern const unsigned char _ZTVN10__cxxabiv121__vmi_class_type_infoE_nid_postfix[]; } namespace { @@ -26,6 +35,21 @@ const TypeRecord DerivedType{_ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfi const TypeRecord OtherType{_ZTVN10__cxxabiv120__si_class_type_infoE_nid_postfix + 16, "5Other", &BaseType}; const Vtable BaseVtable{0, &BaseType, nullptr}; const Vtable DerivedVtable{0, &DerivedType, nullptr}; +const MultipleRecord MultipleType{_ZTVN10__cxxabiv121__vmi_class_type_infoE_nid_postfix + 16, "8Multiple", 0, 1, + {{&BaseType, (16 << 8) | 2}}}; +const MultipleRecord PrivateType{_ZTVN10__cxxabiv121__vmi_class_type_infoE_nid_postfix + 16, "7Private", 0, 1, + {{&BaseType, 16 << 8}}}; +const MultipleRecord VirtualType{_ZTVN10__cxxabiv121__vmi_class_type_infoE_nid_postfix + 16, "7Virtual", 0, 1, + {{&BaseType, (-3 * static_cast(sizeof(void*)) * 256) | 3}}}; + +int FindPublic(const TypeRecord* type, std::ptrdiff_t hint, const void* object, const TypeRecord* sourceType, + const void* source) { + using Function = int (APS5_VABI *)(const void*, std::ptrdiff_t, const void*, const void*, const void*); + const auto* table = static_cast(type->vtable); + Function function; + std::memcpy(&function, table + 8 * sizeof(void*), sizeof(function)); + return function(type, hint, object, sourceType, source); +} } // namespace @@ -36,11 +60,28 @@ int main() { 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); + std::array multipleObject{}; + Require(FindPublic(reinterpret_cast(&MultipleType), -1, multipleObject.data(), &BaseType, + multipleObject.data() + 16) == 6); + Require(FindPublic(reinterpret_cast(&MultipleType), -1, multipleObject.data(), &BaseType, + multipleObject.data()) == 1); + Require(FindPublic(reinterpret_cast(&PrivateType), -1, multipleObject.data(), &BaseType, + multipleObject.data() + 16) == 1); + std::array virtualTable{}; + virtualTable[0] = 16; + const auto* virtualAddressPoint = virtualTable.data() + 3; + alignas(std::intptr_t) std::array virtualObject{}; + *reinterpret_cast(virtualObject.data()) = virtualAddressPoint; + Require(FindPublic(reinterpret_cast(&VirtualType), -1, virtualObject.data(), &BaseType, + virtualObject.data() + 16) == 7); + Require(FindPublic(reinterpret_cast(&MultipleType), -3, multipleObject.data(), &BaseType, + multipleObject.data() + 16) == 6); + Require(FindPublic(reinterpret_cast(&VirtualType), -3, virtualObject.data(), &BaseType, + virtualObject.data() + 16) == 1); 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/GuestEntitlements.cpp b/core/libs/tests/GuestEntitlements.cpp index 2bf1b61c4e..d43bd30d05 100644 --- a/core/libs/tests/GuestEntitlements.cpp +++ b/core/libs/tests/GuestEntitlements.cpp @@ -12,9 +12,80 @@ #include #include #include +#ifdef _WIN32 +#include +#else +#include +#endif static void Require(bool value) { if (!value) std::abort(); } +static std::string CaptureStderr(void (*body)()) { + std::fflush(stderr); + std::FILE* file = std::tmpfile(); + Require(file != nullptr); +#ifdef _WIN32 + const int saved = _dup(_fileno(stderr)); + _dup2(_fileno(file), _fileno(stderr)); +#else + const int saved = dup(fileno(stderr)); + dup2(fileno(file), fileno(stderr)); +#endif + body(); + 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); + return text; +} + +static void QueryUnlistedTwiceAndListedOnce() { + NpEntitlementAccessAddcontEntitlementInfo info{}; + NpUnifiedEntitlementLabel unlisted{}; + std::memcpy(unlisted.data, "LOGGEDADDON", 11); + Require(sceNpEntitlementAccessGetAddcontEntitlementInfo(0, &unlisted, &info) == static_cast(0x80558007)); + Require(sceNpEntitlementAccessGetAddcontEntitlementInfo(0, &unlisted, &info) == static_cast(0x80558007)); + NpUnifiedEntitlementLabel listed{}; + std::memcpy(listed.data, "SECONDADDON", 11); + Require(sceNpEntitlementAccessGetAddcontEntitlementInfo(0, &listed, &info) == 0); +} + +static void QueryUnlisted(const char* text, std::size_t length) { + NpEntitlementAccessAddcontEntitlementInfo info{}; + NpUnifiedEntitlementLabel label{}; + std::memcpy(label.data, text, length); + Require(sceNpEntitlementAccessGetAddcontEntitlementInfo(0, &label, &info) == static_cast(0x80558007)); +} + +static void QueryUnprintable() { + QueryUnlisted("BAD\nNAME\x1b[1m", 13); + QueryUnlisted("SLASH\\ADDON", 11); +} + +static void QueryPastTheLimit() { + for (int i = 0; i < 70; ++i) { + char text[17]{}; + std::snprintf(text, sizeof(text), "CAPADDON%02d", i); + QueryUnlisted(text, std::strlen(text)); + } + QueryUnlisted("LOGGEDADDON", 11); +} + +static std::size_t Occurrences(const std::string& text, std::string_view needle) { + std::size_t count = 0; + for (auto at = text.find(needle); at != std::string::npos; at = text.find(needle, at + needle.size())) ++count; + return count; +} + static void SetEntitlements(const std::filesystem::path& file) { #ifdef _WIN32 Require(_wputenv_s(L"ANYPS5_ENTITLEMENTS", file.wstring().c_str()) == 0); @@ -69,6 +140,18 @@ int main(int argc, char** argv) { 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); + const auto log = CaptureStderr(QueryUnlistedTwiceAndListedOnce); + Require(Occurrences(log, "[entitlements] the title asked for the add-on LOGGEDADDON, which ") == 1); + Require(log.find(file.filename().string() + " does not list, so it is reported as not installed\n") != std::string::npos); + Require(Occurrences(log, "[entitlements]") == 1); + const auto escaped = CaptureStderr(QueryUnprintable); + Require(escaped.find("the add-on BAD\\x0aNAME\\x1b[1m, which ") != std::string::npos); + Require(escaped.find("the add-on SLASH\\x5cADDON, which ") != std::string::npos); + Require(Occurrences(escaped, "\n") == 2 && escaped.find('\x1b') == std::string::npos); + const auto capped = CaptureStderr(QueryPastTheLimit); + Require(Occurrences(capped, "the add-on CAPADDON") == 61); + Require(Occurrences(capped, "[entitlements] the title asked for more than 64 add-ons that ") == 1); + Require(Occurrences(capped, "[entitlements]") == 62); for (std::int32_t userId : {1, 0x10000000, std::numeric_limits::min()}) { NpEntitlementAccessAddcontEntitlementInfo individual{}; Require(sceNpEntitlementAccessGetAddcontEntitlementInfoIndividual(userId, 0, &label, &individual) == 0); diff --git a/core/libs/tests/GuestEqueueEvents.cpp b/core/libs/tests/GuestEqueueEvents.cpp index 56e10c1485..ec5b994361 100644 --- a/core/libs/tests/GuestEqueueEvents.cpp +++ b/core/libs/tests/GuestEqueueEvents.cpp @@ -106,6 +106,32 @@ static void VerifyPeriodicTimer() { Require(sceKernelDeleteEqueue(eq) == SCE_OK); } +static void VerifyHRTimerDelete() { + using namespace std::chrono_literals; + int udata = 0; + KernelEvent events[2]{}; + int count = 0; + const KernelUseconds poll = 0; + const KernelTimespec far{3600, 0}; + const KernelTimespec soon{0, 1000000}; + Require(sceKernelDeleteHRTimerEvent(0, 12) == SCE_KERNEL_ERROR_EBADF); + + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "hrtimer delete") == SCE_OK); + Require(sceKernelDeleteHRTimerEvent(eq, 12) == SCE_KERNEL_ERROR_ENOENT); + Require(sceKernelAddHRTimerEvent(eq, 12, &far, &udata) == SCE_OK); + Require(sceKernelAddTimerEvent(eq, 12, 3000000000u, &udata) == SCE_OK); + Require(sceKernelDeleteHRTimerEvent(eq, 12) == SCE_OK); + Require(sceKernelDeleteHRTimerEvent(eq, 12) == SCE_KERNEL_ERROR_ENOENT); + Require(sceKernelDeleteTimerEvent(eq, 12) == SCE_OK); + + Require(sceKernelAddHRTimerEvent(eq, 13, &soon, &udata) == SCE_OK); + SleepAtLeast(5ms); + Require(sceKernelDeleteHRTimerEvent(eq, 13) == SCE_OK); + Require(sceKernelWaitEqueue(eq, events, 2, &count, &poll) == SCE_KERNEL_ERROR_ETIMEDOUT); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + static void VerifyUserEventReportedOncePerWait() { KernelEqueue eq = 0; Require(sceKernelCreateEqueue(&eq, "user") == SCE_OK); @@ -220,6 +246,103 @@ static void VerifyRepeatedAmprCompletionsAreNotLost() { Require(sceKernelDeleteEqueue(eq) == SCE_OK); } +static void VerifyUndrainedTriggersStayBounded() { + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "pending-bound") == SCE_OK); + KernelEvent events[4]{}; + int count = 0; + const KernelUseconds poll = 0; + int udata = 0; + auto data = [](std::uintptr_t value) { return reinterpret_cast(value); }; + Require(sceKernelAddAmprEvent(eq, 9, &udata) == SCE_OK); + constexpr int kFlood = 100000; + for (int i = 1; i <= kFlood; ++i) { + Require(EqueueTriggerEvent_nid_postfix(eq, 9, EVFILT_AMPR, data(static_cast(i))) == SCE_OK); + } + const int kBound = static_cast(1 + KernelEqueuePrivate::kMaxPendingEvents); + int total = 0; + intptr_t last = 0; + for (;;) { + const int rc = sceKernelWaitEqueue(eq, events, 4, &count, &poll); + if (rc == SCE_KERNEL_ERROR_ETIMEDOUT) { + break; + } + Require(rc == SCE_OK); + for (int i = 0; i < count; ++i) { + ++total; + last = sceKernelGetEventData(&events[i]); + } + Require(total <= kBound); + } + Require(total == kBound); + Require(last == kFlood); + Require(sceKernelDeleteAmprEvent(eq, 9) == SCE_OK); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + +static void VerifyAddEventTruncatesPending() { + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(&eq, "add-bound") == SCE_OK); + int udata = 0; + KernelEqueueEvent seeded{}; + seeded.enabled = true; + seeded.triggered = true; + seeded.event.ident = 21; + seeded.event.filter = EVFILT_AMPR; + seeded.event.flags = EV_ADD | EV_CLEAR; + seeded.event.udata = &udata; + seeded.event.data = 1; + seeded.filter.triggerFunc = [](KernelEqueueEvent* e, void* data) { + KernelEvent triggered = e->event; + triggered.data = static_cast(reinterpret_cast(data)); + if (e->triggered) { + e->pendingEvents.push_back(triggered); + } else { + e->event = triggered; + e->triggered = true; + } + }; + seeded.filter.resetFunc = [](KernelEqueueEvent* e) { + if ((e->event.flags & EV_CLEAR) != 0) { + e->triggered = false; + e->event.fflags = 0; + e->event.data = 0; + } + }; + constexpr int kQueued = 20000; + for (int i = 2; i <= kQueued + 1; ++i) { + KernelEvent queued{}; + queued.ident = 21; + queued.filter = EVFILT_AMPR; + queued.flags = EV_ADD | EV_CLEAR; + queued.data = i; + queued.udata = &udata; + seeded.pendingEvents.push_back(queued); + } + Require(EqueueAddEvent_nid_postfix(eq, seeded) == SCE_OK); + const int kBound = static_cast(1 + KernelEqueuePrivate::kMaxPendingEvents); + KernelEvent events[4]{}; + int count = 0; + const KernelUseconds poll = 0; + int total = 0; + intptr_t last = 0; + for (;;) { + const int rc = sceKernelWaitEqueue(eq, events, 4, &count, &poll); + if (rc == SCE_KERNEL_ERROR_ETIMEDOUT) { + break; + } + Require(rc == SCE_OK); + for (int i = 0; i < count; ++i) { + ++total; + last = sceKernelGetEventData(&events[i]); + } + Require(total <= kBound); + } + Require(total == kBound); + Require(last == kQueued + 1); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + static void RequireReported(KernelEqueue eq, int num, std::initializer_list expected) { KernelEvent events[4]{}; int count = 0; @@ -271,12 +394,33 @@ static void VerifyReportedEventsMoveBehindWaitingOnes() { Require(sceKernelDeleteEqueue(eq) == SCE_OK); } +static void VerifyInvalidQueueAttributes() { + KernelEqueue eq = 0; + Require(sceKernelCreateEqueue(nullptr, "invalid") == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelCreateEqueue(&eq, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelCreateEqueue(nullptr, nullptr) == SCE_KERNEL_ERROR_EINVAL); + + Require(sceKernelCreateEqueue(&eq, "bounds") == SCE_OK); + KernelEvent event{}; + int count = -1; + const KernelUseconds timeout = 0; + Require(sceKernelWaitEqueue(eq, &event, 0, &count, &timeout) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelWaitEqueue(eq, &event, -1, &count, &timeout) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelWaitEqueue(eq, &event, 1, nullptr, &timeout) == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelWaitEqueue(eq, nullptr, 1, &count, &timeout) == SCE_KERNEL_ERROR_EFAULT); + Require(sceKernelDeleteEqueue(eq) == SCE_OK); +} + int main() { + VerifyInvalidQueueAttributes(); VerifyPeriodicTimer(); + VerifyHRTimerDelete(); VerifyUserEventReportedOncePerWait(); VerifyBlockingWaitReportsLevelEventOnce(); VerifyEdgeUserEventReportedOnce(); VerifyRepeatedAmprCompletionsAreNotLost(); + VerifyUndrainedTriggersStayBounded(); + VerifyAddEventTruncatesPending(); VerifyReportedEventsMoveBehindWaitingOnes(); KernelEqueue eq = 0; diff --git a/core/libs/tests/GuestExitOrder.cpp b/core/libs/tests/GuestExitOrder.cpp index af813531ef..3520fd68fb 100644 --- a/core/libs/tests/GuestExitOrder.cpp +++ b/core/libs/tests/GuestExitOrder.cpp @@ -14,7 +14,7 @@ void Record(char value) { static_cast(!write(events[1], &value, 1)); } -void APS5_VABI GuestHandler(void*) { Record('G'); } +void APS5_VABI RecordGuestExit(void*) { Record('G'); } void HostHandler() { Record('H'); } @@ -46,11 +46,11 @@ bool ExitOrder(Setup setup, const char* expected) { } int main() { - if (!ExitOrder([] { __cxa_atexit_nid_postfix(GuestHandler, nullptr, nullptr); std::atexit(HostHandler); }, "GH")) { + if (!ExitOrder([] { __cxa_atexit_nid_postfix(RecordGuestExit, nullptr, nullptr); std::atexit(HostHandler); }, "GH")) { std::fputs("guest exit handlers must run before the host exit handlers\n", stderr); return 1; } - if (!ExitOrder([] { __cxa_atexit_nid_postfix(GuestHandler, nullptr, nullptr); atexit_nid_postfix(AtexitHandler); }, "AG")) { + if (!ExitOrder([] { __cxa_atexit_nid_postfix(RecordGuestExit, nullptr, nullptr); atexit_nid_postfix(AtexitHandler); }, "AG")) { std::fputs("atexit and __cxa_atexit handlers must run in reverse order of registration\n", stderr); return 1; } diff --git a/core/libs/tests/GuestExplicitMemset.cpp b/core/libs/tests/GuestExplicitMemset.cpp new file mode 100644 index 0000000000..b54f1a441d --- /dev/null +++ b/core/libs/tests/GuestExplicitMemset.cpp @@ -0,0 +1,129 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#endif + +static void Check(bool condition, int line) { + if (condition) return; + std::fprintf(stderr, "memset_explicit check failed at line %d\n", line); + std::abort(); +} +#define Require(condition) Check((condition), __LINE__) + +class GuardedPage { + unsigned char* allocation; +public: + std::size_t size; + unsigned char* data; + + GuardedPage() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + size = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, size * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous = 0; + Require(VirtualProtect(allocation + size, size, PAGE_READWRITE, &previous) != 0); +#else + const long pageSize = sysconf(_SC_PAGESIZE); + Require(pageSize > 0); + size = static_cast(pageSize); + allocation = static_cast(mmap(nullptr, size * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + size, size, PROT_READ | PROT_WRITE) == 0); +#endif + data = allocation + size; + } + + GuardedPage(const GuardedPage&) = delete; + GuardedPage& operator=(const GuardedPage&) = delete; + + ~GuardedPage() { +#ifdef _WIN32 + VirtualFree(allocation, 0, MEM_RELEASE); +#else + munmap(allocation, size * 3); +#endif + } +}; + +static void Invoke(void* destination, int value, std::size_t count) { + *__error_nid_postfix() = 97; + Require(memset_explicit_nid_postfix(destination, value, count) == destination); + Require(*__error_nid_postfix() == 97); +} + +static void Verify(unsigned char* bytes, std::size_t size, std::size_t offset, int value, std::size_t count) { + Require(offset <= size && count <= size - offset); + for (std::size_t index = 0; index < size; ++index) bytes[index] = 0xA7; + Invoke(bytes + offset, value, count); + for (std::size_t index = 0; index < size; ++index) { + const auto expected = index >= offset && index - offset < count ? static_cast(value) : 0xA7; + Require(bytes[index] == expected); + } +} + +static void Empty() { + GuardedPage page; + unsigned char byte = 0x35; + for (const int value : {0, -1, INT_MIN, INT_MAX}) { + Invoke(nullptr, value, 0); + Invoke(&byte, value, 0); + Invoke(page.data - 1, value, 0); + Invoke(page.data + page.size, value, 0); + Require(byte == 0x35); + } +} + +static void Values() { + std::array bytes; + constexpr std::array lengths{0, 1, 2, 3, 7, 8, 15, 16, 17, 31, 32, 63, + 64, 65, 127, 128, 255, 256, 257, 511, 512, 1023, 1024, 1056}; + const auto checkValue = [&](int value) { + for (const auto count : lengths) { + for (std::size_t offset = 0; offset < 32; ++offset) Verify(bytes.data(), bytes.size(), offset, value, count); + } + }; + for (int value = 0; value <= 255; ++value) checkValue(value); + for (const int value : {-1025, -513, -256, -1, 256, 511, INT_MIN, INT_MAX}) checkValue(value); +} + +static void Ranges() { + std::array bytes; + for (std::size_t offset = 0; offset < 64; ++offset) { + for (std::size_t count = 0; count <= 256; ++count) { + for (const int value : {0, 0xA7, -1}) Verify(bytes.data(), bytes.size(), offset, value, count); + } + } +} + +static void GuardPages() { + GuardedPage page; + for (std::size_t count = 0; count <= page.size; ++count) { + for (const int value : {0, 0x35, -1}) { + Verify(page.data, page.size, 0, value, count); + Verify(page.data, page.size, page.size - count, value, count); + } + } +} + +int main(int argc, char** argv) { + Require(argc == 2); + if (std::strcmp(argv[1], "empty") == 0) Empty(); + else if (std::strcmp(argv[1], "values") == 0) Values(); + else if (std::strcmp(argv[1], "ranges") == 0) Ranges(); + else if (std::strcmp(argv[1], "guard_pages") == 0) GuardPages(); + else Require(false); + std::puts("memset_explicit return values, bytes, boundaries and errno passed"); +} diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp index 4c8ffa4dc0..ab3f86d9b3 100644 --- a/core/libs/tests/GuestFilesystem.cpp +++ b/core/libs/tests/GuestFilesystem.cpp @@ -5,10 +5,15 @@ #include "prx/libc/include/General.hpp" #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/RenameDot.hpp" +#include "prx/libc/include/WindowsVerbatimPath.hpp" #include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" #include "prx/libkernel/File/include/NativeStat.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "tests/TemporaryFile.hpp" +#ifndef _WIN32 +#include +#endif #include #include #include @@ -170,6 +175,10 @@ static void TestParentTraversal() { checkInvalid("/" + root.generic_string() + "/missing/..", 2); checkInvalid(root.generic_string() + "/missing/../../" + root.generic_string(), 2); AddPathAlias_nid_no_patch("parent-test-mount", std::filesystem::absolute(root).string().c_str()); + const char* missingCreateAlias = "/parent-test-mount/missing-create/"; + Require(sceKernelOpen(missingCreateAlias, SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT, 0600) == SCE_KERNEL_ERROR_ENOENT); + Require(open_nid_postfix(missingCreateAlias, SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT, 0600) == -1 && *__error_nid_postfix() == 2); + Require(!std::filesystem::exists(root / "missing-create")); checkInvalid("/PARENT-TEST-MOUNT/missing/..", 2); checkInvalid("/parent-test-mount/present.txt/..", 20); FileStat status{}; @@ -389,8 +398,324 @@ static void TestLongHostPathStat() { for (auto path = directories.rbegin(); path != directories.rend(); ++path) Require(::RemoveDirectoryW(ExtendedPath(*path).c_str()) != 0); } + +static void TestWindowsVerbatimPathLive() { + auto root = std::filesystem::absolute(std::filesystem::temp_directory_path()).lexically_normal(); + if (root.filename().empty()) root = root.parent_path(); + root /= L"anyps5-verbatim-" + std::to_wstring(std::chrono::steady_clock::now().time_since_epoch().count()); + Require(::CreateDirectoryW(root.c_str(), nullptr) != 0); + const auto host = root.string(); + AddPathAlias_nid_no_patch("/verbatim-test", host.c_str()); + + std::error_code error; + const auto dosPath = NativeVerbatimPath_nid_no_patch(root / L"nul.txt", error); + Require(!error); + Require(dosPath.native().starts_with(L"\\\\?\\")); + + const auto dotPath = NativeVerbatimPath_nid_no_patch(root / L"file.", error); + Require(!error); + Require(dotPath.native().starts_with(L"\\\\?\\")); + + const auto spacePath = NativeVerbatimPath_nid_no_patch(root / L"file ", error); + Require(!error); + Require(spacePath.native().starts_with(L"\\\\?\\")); + + const auto normalPath = NativeVerbatimPath_nid_no_patch(root / L"regular.txt", error); + Require(!error); + Require(!normalPath.native().starts_with(L"\\\\?\\")); + + const auto alreadyVerbatim = NativeVerbatimPath_nid_no_patch(dosPath, error); + Require(!error); + Require(alreadyVerbatim == dosPath); + + const int fdDot = sceKernelOpen("/verbatim-test/file.", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdDot >= 0); + sceKernelClose(fdDot); + + FileStat sbDot{}; + Require(sceKernelStat("/verbatim-test/file.", &sbDot) == 0); + Require(sceKernelUnlink("/verbatim-test/file.") == 0); + Require(sceKernelStat("/verbatim-test/file.", &sbDot) == static_cast(0x80020002u)); + + const int fdNul = sceKernelOpen("/verbatim-test/nul", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdNul >= 0); + sceKernelClose(fdNul); + + FileStat sbNul{}; + Require(sceKernelStat("/verbatim-test/nul", &sbNul) == 0); + Require(sceKernelUnlink("/verbatim-test/nul") == 0); + Require(sceKernelStat("/verbatim-test/nul", &sbNul) == static_cast(0x80020002u)); + + const int fdPlain = sceKernelOpen("/verbatim-test/file", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdPlain >= 0); + Require(sceKernelWrite(fdPlain, "A", 1) == 1); + sceKernelClose(fdPlain); + + const int fdSibling = sceKernelOpen("/verbatim-test/file.", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdSibling >= 0); + Require(sceKernelWrite(fdSibling, "BB", 2) == 2); + sceKernelClose(fdSibling); + + FileStat sbPlain{}, sbSibling{}; + Require(sceKernelStat("/verbatim-test/file", &sbPlain) == 0); + Require(sceKernelStat("/verbatim-test/file.", &sbSibling) == 0); + Require(sbPlain.st_size == 1); + Require(sbSibling.st_size == 2); + Require(sceKernelUnlink("/verbatim-test/file") == 0); + Require(sceKernelUnlink("/verbatim-test/file.") == 0); + + Require(sceKernelMkdir("/verbatim-test/d.", 0777) == 0); + const int fdSave = sceKernelOpen("/verbatim-test/d./save", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdSave >= 0); + sceKernelClose(fdSave); + FileStat sbSave{}; + Require(sceKernelStat("/verbatim-test/d./save", &sbSave) == 0); + Require(sceKernelUnlink("/verbatim-test/d./save") == 0); + Require(sceKernelRmdir("/verbatim-test/d.") == 0); + + Require(mkdir_nid_postfix("/verbatim-test/posix_d.", 0777) == 0); + const int fdPosixSave = open_nid_postfix("/verbatim-test/posix_d./save", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdPosixSave >= 0); + close_nid_postfix(fdPosixSave); + FileStat sbPosixSave{}; + Require(sceKernelStat("/verbatim-test/posix_d./save", &sbPosixSave) == 0); + Require(unlink_nid_postfix("/verbatim-test/posix_d./save") == 0); + Require(rmdir_nid_postfix("/verbatim-test/posix_d.") == 0); + + const int fdOrig = sceKernelOpen("/verbatim-test/orig.", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdOrig >= 0); + sceKernelClose(fdOrig); + Require(rename_nid_postfix("/verbatim-test/orig.", "/verbatim-test/renamed.") == 0); + FileStat sbRename{}; + Require(sceKernelStat("/verbatim-test/orig.", &sbRename) == static_cast(0x80020002u)); + Require(sceKernelStat("/verbatim-test/renamed.", &sbRename) == 0); + Require(sceKernelRename("/verbatim-test/renamed.", "/verbatim-test/kernel_renamed.") == 0); + Require(sceKernelStat("/verbatim-test/renamed.", &sbRename) == static_cast(0x80020002u)); + Require(sceKernelStat("/verbatim-test/kernel_renamed.", &sbRename) == 0); + Require(sceKernelUnlink("/verbatim-test/kernel_renamed.") == 0); + + const int fdReach = sceKernelOpen("/verbatim-test/reach.", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdReach >= 0); + sceKernelClose(fdReach); + Require(sceKernelCheckReachability("/verbatim-test/reach.") == 0); + Require(sceKernelUnlink("/verbatim-test/reach.") == 0); + Require(sceKernelCheckReachability("/verbatim-test/reach.") == static_cast(0x80020002u)); + + const int fdTrunc = sceKernelOpen("/verbatim-test/trunc.", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdTrunc >= 0); + Require(sceKernelWrite(fdTrunc, "12345678", 8) == 8); + sceKernelClose(fdTrunc); + Require(sceKernelTruncate_nid_postfix("/verbatim-test/trunc.", 4) == 0); + FileStat sbTrunc{}; + Require(sceKernelStat("/verbatim-test/trunc.", &sbTrunc) == 0); + Require(sbTrunc.st_size == 4); + Require(sceKernelChmod_nid_postfix("/verbatim-test/trunc.", 0644) == 0); + Require(sceKernelUnlink("/verbatim-test/trunc.") == 0); + + Require(sceKernelMkdir("/verbatim-test/aux.dir", 0777) == 0); + const int fdAux = sceKernelOpen("/verbatim-test/aux.dir/file", SCE_KERNEL_O_CREAT | SCE_KERNEL_O_RDWR, 0666); + Require(fdAux >= 0); + sceKernelClose(fdAux); + Require(sceKernelUnlink("/verbatim-test/aux.dir/file") == 0); + Require(sceKernelRmdir("/verbatim-test/aux.dir") == 0); + + const auto rootLength = root.native().size(); + if (rootLength + 2 <= 250) { + const std::string dir250Name(250 - rootLength - 1, 'd'); + const std::string dir250Guest = "/verbatim-test/" + dir250Name; + Require(sceKernelMkdir(dir250Guest.c_str(), 0777) == 0); + Require(sceKernelRmdir(dir250Guest.c_str()) == 0); + } + + RemovePathAlias_nid_no_patch("/verbatim-test"); + ::RemoveDirectoryW(root.c_str()); +} #endif +static void TestWindowsVerbatimPath() { + Require(IsDosDeviceStem_nid_no_patch(std::string_view("nul"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("NUL"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("con"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("CON"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("prn"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("PRN"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("aux"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("AUX"))); + for (char digit = '1'; digit <= '9'; ++digit) { + Require(IsDosDeviceStem_nid_no_patch(std::string("com") + digit)); + Require(IsDosDeviceStem_nid_no_patch(std::string("COM") + digit)); + Require(IsDosDeviceStem_nid_no_patch(std::string("lpt") + digit)); + Require(IsDosDeviceStem_nid_no_patch(std::string("LPT") + digit)); + } + Require(IsDosDeviceStem_nid_no_patch(std::string_view("com\xC2\xB9"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("COM\xC2\xB9"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("com\xC2\xB2"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("COM\xC2\xB2"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("com\xC2\xB3"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("COM\xC2\xB3"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("lpt\xC2\xB9"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("LPT\xC2\xB9"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("lpt\xC2\xB2"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("LPT\xC2\xB2"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("lpt\xC2\xB3"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("LPT\xC2\xB3"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"com\u00b9"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"COM\u00b9"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"com\u00b2"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"COM\u00b2"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"com\u00b3"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"COM\u00b3"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"lpt\u00b9"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"LPT\u00b9"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"lpt\u00b2"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"LPT\u00b2"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"lpt\u00b3"))); + Require(IsDosDeviceStem_nid_no_patch(std::wstring_view(L"LPT\u00b3"))); + Require(!IsDosDeviceStem_nid_no_patch(std::string_view("com\xC2\xB4"))); + Require(!IsDosDeviceStem_nid_no_patch(std::wstring_view(L"com\u00b4"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("conin$"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("CONIN$"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("conout$"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("CONOUT$"))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("con. "))); + Require(IsDosDeviceStem_nid_no_patch(std::string_view("nul "))); + Require(!IsDosDeviceStem_nid_no_patch(std::string_view(""))); + Require(!IsDosDeviceStem_nid_no_patch(std::string_view("null"))); + Require(!IsDosDeviceStem_nid_no_patch(std::string_view("conflict"))); + Require(!IsDosDeviceStem_nid_no_patch(std::string_view("auxiliary"))); + Require(!IsDosDeviceStem_nid_no_patch(std::string_view("com10"))); + Require(!IsDosDeviceStem_nid_no_patch(std::string_view("lpt10"))); + Require(!IsDosDeviceStem_nid_no_patch(std::string_view("regular"))); + Require(!IsDosDeviceStem_nid_no_patch(std::string_view("clock$"))); + + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("nul"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("nul.txt"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("NUL.TXT"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("con.dat"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("aux.bin"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("com1.log"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("lpt9.out"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("conin$.txt"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("CONOUT$.dat"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("con .txt"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("file."))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("file "))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("name.txt."))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("name.txt "))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("..."))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("com\xC2\xB9.txt"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("LPT\xC2\xB3.out"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::wstring_view(L"COM\u00b9.txt"))); + Require(ComponentNeedsVerbatimPrefix_nid_no_patch(std::wstring_view(L"LPT\u00b3.log"))); + + Require(!ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view(""))); + Require(!ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("."))); + Require(!ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view(".."))); + Require(!ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("regular.txt"))); + Require(!ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("file.nul"))); + Require(!ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view(".nul"))); + Require(!ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("null.txt"))); + Require(!ComponentNeedsVerbatimPrefix_nid_no_patch(std::string_view("conflict.dat"))); + + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(C:\games\title\nul)"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(C:\games\title\nul.txt)"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(C:\games\title\file.)"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(C:\games\title\file )"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(C:\games\nul\save.dat)"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(C:\games\dir.\save.dat)"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(C:\games\dir \save.dat)"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(\\server\share\nul)"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(\\server\share\file.)"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("C:/games/title/nul"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("C:/games/title/file."))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("//server/share/nul"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("C:nul"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("C:file."))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("C:file "))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::wstring_view(L"COM\u00b9.txt"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::wstring_view(L"LPT\u00b3.log"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("C:/games/title/com\xC2\xB9.txt"))); + Require(PathNeedsVerbatimPrefix_nid_no_patch(std::wstring_view(L"C:/games/title/COM\u00b9.txt"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(C:\games\title\file.txt)"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(\\server\share\file.txt)"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("C:/games/title/file.txt"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("//server/share/file.txt"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("C:file.txt"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("C:/games/title/com10.txt"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::wstring_view(L"C:/games/title/COM10.txt"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(\\?\C:\games\title\nul)"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::string_view(R"(\\.\C:\games\title\nul)"))); + Require(!PathNeedsVerbatimPrefix_nid_no_patch(std::string_view("//?/C:/games/title/nul"))); + + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string(R"(C:\games\title\nul)")) == R"(\\?\C:\games\title\nul)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string("C:/games/title/nul")) == R"(\\?\C:\games\title\nul)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string(R"(C:\games\title\file.)")) == R"(\\?\C:\games\title\file.)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string("C:/games/title/file.")) == R"(\\?\C:\games\title\file.)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string(R"(C:\games\title\file )")) == R"(\\?\C:\games\title\file )"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string(R"(\\server\share\nul)")) == R"(\\?\UNC\server\share\nul)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string("//server/share/nul")) == R"(\\?\UNC\server\share\nul)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string(R"(\\server\share\file.)")) == R"(\\?\UNC\server\share\file.)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string(R"(\\?\C:\games\title\nul)")) == R"(\\?\C:\games\title\nul)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string(R"(\\?\UNC\server\share\nul)")) == R"(\\?\UNC\server\share\nul)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::string("//?/C:/games/title/nul")) == R"(\\?\C:\games\title\nul)"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::wstring(L"C:\\games\\title\\nul")) == L"\\\\?\\C:\\games\\title\\nul"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::wstring(L"C:/games/title/nul")) == L"\\\\?\\C:\\games\\title\\nul"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::wstring(L"\\\\server\\share\\nul")) == L"\\\\?\\UNC\\server\\share\\nul"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::wstring(L"//server/share/nul")) == L"\\\\?\\UNC\\server\\share\\nul"); + Require(FormatVerbatimWindowsPath_nid_no_patch(std::wstring(L"\\\\?\\C:\\games\\title\\nul")) == L"\\\\?\\C:\\games\\title\\nul"); + + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\file.txt)")) == R"(C:\games\title\file.txt)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\nul)")) == R"(\\?\C:\games\title\nul)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view("C:/games/title/nul")) == R"(\\?\C:\games\title\nul)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\con.dat)")) == R"(\\?\C:\games\title\con.dat)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\aux.bin)")) == R"(\\?\C:\games\title\aux.bin)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\com1.log)")) == R"(\\?\C:\games\title\com1.log)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\lpt9.out)")) == R"(\\?\C:\games\title\lpt9.out)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\file.)")) == R"(\\?\C:\games\title\file.)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view("C:/games/title/file.")) == R"(\\?\C:\games\title\file.)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\file )")) == R"(\\?\C:\games\title\file )"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(\\server\share\nul)")) == R"(\\?\UNC\server\share\nul)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view("//server/share/nul")) == R"(\\?\UNC\server\share\nul)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(\\server\share\file.)")) == R"(\\?\UNC\server\share\file.)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view("C:nul")) == R"(\\?\C:nul)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view("C:file.")) == R"(\\?\C:file.)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::wstring_view(L"COM\u00b9.txt")) == L"\\\\?\\COM\u00b9.txt"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::wstring_view(L"LPT\u00b3.log")) == L"\\\\?\\LPT\u00b3.log"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view("C:/games/title/com\xC2\xB9.txt")) == R"(\\?\C:\games\title\com)" "\xC2\xB9.txt"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::wstring_view(L"C:/games/title/COM\u00b9.txt")) == L"\\\\?\\C:\\games\\title\\COM\u00b9.txt"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\null.txt)")) == R"(C:\games\title\null.txt)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\file.nul)")) == R"(C:\games\title\file.nul)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(C:\games\title\com10.txt)")) == R"(C:\games\title\com10.txt)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(\\?\C:\games\title\nul)")) == R"(\\?\C:\games\title\nul)"); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(R"(\\?\UNC\server\share\nul)")) == R"(\\?\UNC\server\share\nul)"); + const std::string path247 = "C:\\" + std::string(232, 'a') + "\\regular.txt"; + Require(path247.size() == 247); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(path247)) == path247); + const std::string path248 = "C:\\" + std::string(233, 'a') + "\\regular.txt"; + Require(path248.size() == 248); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(path248)) == R"(\\?\)" + path248); + const std::string path259 = "C:\\" + std::string(244, 'a') + "\\regular.txt"; + Require(path259.size() == 259); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(path259)) == R"(\\?\)" + path259); + const std::string path260 = "C:\\" + std::string(245, 'a') + "\\regular.txt"; + Require(path260.size() == 260); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(path260)) == R"(\\?\)" + path260); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(path248), WindowsMaxPathLength) == path248); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(path260), WindowsMaxPathLength) == R"(\\?\)" + path260); + const std::wstring wPath247 = L"C:\\" + std::wstring(232, L'a') + L"\\regular.txt"; + Require(wPath247.size() == 247); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::wstring_view(wPath247)) == wPath247); + const std::wstring wPath248 = L"C:\\" + std::wstring(233, L'a') + L"\\regular.txt"; + Require(wPath248.size() == 248); + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::wstring_view(wPath248)) == L"\\\\?\\" + wPath248); + const std::string longPath = "C:\\" + std::string(300, 'a') + "\\regular.txt"; + Require(MakeVerbatimWindowsPathForTest_nid_no_patch(std::string_view(longPath)) == R"(\\?\)" + longPath); + +#ifdef _WIN32 + TestWindowsVerbatimPathLive(); +#endif +} + #ifdef __linux__ static int DirectoryOpens(int watch) { alignas(inotify_event) char buffer[4096]; @@ -433,6 +758,7 @@ int main() { TestParentTraversal(); TestRelinkedModuleNames(); TestRelinkedModuleNameCase(); + TestWindowsVerbatimPath(); #ifdef _WIN32 TestCaseSensitiveHostDirectory(); TestLongHostPathStat(); @@ -675,7 +1001,11 @@ int main() { Require(chflags_nid_postfix(file.string().c_str(), ~0ULL) == -1 && *__error_nid_postfix() == 45); 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")); + for (const std::uint64_t system : {0x10000ULL, 0x20000ULL, 0x40000ULL, 0x100000ULL, 0x200000ULL, 0x370000ULL, 0x1000ULL | 0x40000ULL, 0x2ULL | 0x20000ULL}) + Require(chflags_nid_postfix(file.string().c_str(), system) == -1 && *__error_nid_postfix() == 1); + Require(chflags_nid_postfix(file.string().c_str(), 0x40000 | 0x20) == -1 && *__error_nid_postfix() == 45); + Require(chflags_nid_postfix(root.string().c_str(), 0x20000) == -1 && *__error_nid_postfix() == 1); + Require(chflags_nid_postfix((root / "missing").string().c_str(), 0x20000) == -1 && *__error_nid_postfix() == 2); const auto chflagsDangling = root / "dangling.link"; const auto chflagsPointing = root / "file.link"; #ifdef _WIN32 @@ -704,7 +1034,10 @@ int main() { Require(fchflags_nid_postfix(flagged, ~0ULL) == -1 && *__error_nid_postfix() == 45); Require(fchflags_nid_postfix(flagged, 0x200000) == -1 && *__error_nid_postfix() == 1); Require(FchflagsThrows(flagged, 0x2, "UF_IMMUTABLE")); - Require(FchflagsThrows(flagged, 0x1000 | 0x40000, "UF_READONLY|SF_APPEND")); + for (const std::uint64_t system : {0x10000ULL, 0x20000ULL, 0x40000ULL, 0x100000ULL, 0x200000ULL, 0x1000ULL | 0x40000ULL, 0x2ULL | 0x20000ULL}) + Require(fchflags_nid_postfix(flagged, system) == -1 && *__error_nid_postfix() == 1); + Require(fchflags_nid_postfix(flagged, 0x370000) == -1 && *__error_nid_postfix() == 1); + Require(fchflags_nid_postfix(flagged, 0x40000 | 0x20) == -1 && *__error_nid_postfix() == 45); Require(close_nid_postfix(flagged) == 0); Require(fchflags_nid_postfix(flagged, 0) == -1 && *__error_nid_postfix() == 9); Require(fchflags_nid_postfix(flagged, ~0ULL) == -1 && *__error_nid_postfix() == 9); @@ -779,6 +1112,76 @@ int main() { Require(rename_nid_postfix(file.string().c_str(), renamed.string().c_str()) == -1); Require(*__error_nid_postfix() == 2); Require(rename_nid_postfix(renamed.string().c_str(), file.string().c_str()) == 0); + { + const auto linkedDirectory = root / "rename-linked-directory"; + const auto emptyDirectory = root / "rename-empty-directory"; + const auto directoryLink = root / "rename-directory.link"; + const auto movingLink = root / "rename-moving.link"; + const auto movingFile = root / "rename-moving.txt"; + const auto occupiedFile = root / "rename-occupied.txt"; + Require(std::filesystem::create_directory(linkedDirectory) && std::filesystem::create_directory(emptyDirectory)); + { std::ofstream stream(linkedDirectory / "kept.txt"); stream << "kept"; } + { std::ofstream stream(movingFile); stream << "moved"; } + { std::ofstream stream(occupiedFile); stream << "occupied"; } +#ifdef _WIN32 + const DWORD directoryLinkFlags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE | SYMBOLIC_LINK_FLAG_DIRECTORY; + const bool renameLinked = CreateSymbolicLinkW(directoryLink.wstring().c_str(), L"rename-linked-directory", directoryLinkFlags) && + CreateSymbolicLinkW(movingLink.wstring().c_str(), L"rename-linked-directory", directoryLinkFlags); +#else + std::error_code renameLinkError; + std::filesystem::create_directory_symlink("rename-linked-directory", directoryLink, renameLinkError); + if (!renameLinkError) std::filesystem::create_directory_symlink("rename-linked-directory", movingLink, renameLinkError); + const bool renameLinked = !renameLinkError; +#endif + if (renameLinked) { + Require(rename_nid_postfix(movingLink.string().c_str(), emptyDirectory.string().c_str()) == -1 && *__error_nid_postfix() == 21); + Require(std::filesystem::is_directory(emptyDirectory) && std::filesystem::is_empty(emptyDirectory)); + Require(rename_nid_postfix(movingLink.string().c_str(), occupiedFile.string().c_str()) == 0); + Require(!std::filesystem::exists(movingLink) && std::filesystem::is_regular_file(occupiedFile / "kept.txt")); + Require(rename_nid_postfix(movingFile.string().c_str(), directoryLink.string().c_str()) == 0); + Require(!std::filesystem::exists(movingFile) && std::filesystem::is_regular_file(directoryLink)); + { std::ifstream stream(directoryLink); std::string contents; std::getline(stream, contents); Require(contents == "moved"); } + Require(std::filesystem::is_regular_file(linkedDirectory / "kept.txt")); + Require(unlink_nid_postfix(directoryLink.string().c_str()) == 0); +#ifdef _WIN32 + Require(RemoveDirectoryW(occupiedFile.wstring().c_str()) != 0); +#else + Require(unlink_nid_postfix(occupiedFile.string().c_str()) == 0); +#endif + } else { +#ifdef _WIN32 + RemoveDirectoryW(movingLink.wstring().c_str()); + RemoveDirectoryW(directoryLink.wstring().c_str()); +#else + unlink_nid_postfix(movingLink.string().c_str()); + unlink_nid_postfix(directoryLink.string().c_str()); +#endif + unlink_nid_postfix(occupiedFile.string().c_str()); + unlink_nid_postfix(movingFile.string().c_str()); + std::fprintf(stderr, "symbolic links unavailable: the rename link cases not tested\n"); + } +#ifdef _WIN32 + const auto directoryJunction = root / "rename-directory.junction"; + const auto movingJunction = root / "rename-moving.junction"; + const auto junctionFile = root / "rename-junction-moving.txt"; + const auto junctionOccupied = root / "rename-junction-occupied.txt"; + { std::ofstream stream(junctionFile); stream << "moved"; } + { std::ofstream stream(junctionOccupied); stream << "occupied"; } + const auto junctionTargetPath = std::filesystem::absolute(linkedDirectory).wstring(); + for (const auto& made : {directoryJunction, movingJunction}) + Require(_wsystem((L"cmd /c mklink /J \"" + made.wstring() + L"\" \"" + junctionTargetPath + L"\" >nul").c_str()) == 0); + Require(rename_nid_postfix(movingJunction.string().c_str(), emptyDirectory.string().c_str()) == -1 && *__error_nid_postfix() == 21); + Require(std::filesystem::is_directory(emptyDirectory) && std::filesystem::is_empty(emptyDirectory)); + Require(rename_nid_postfix(movingJunction.string().c_str(), junctionOccupied.string().c_str()) == 0); + Require(!std::filesystem::exists(movingJunction) && std::filesystem::is_regular_file(junctionOccupied / "kept.txt")); + Require(rename_nid_postfix(junctionFile.string().c_str(), directoryJunction.string().c_str()) == 0); + Require(!std::filesystem::exists(junctionFile) && std::filesystem::is_regular_file(directoryJunction)); + Require(std::filesystem::is_regular_file(linkedDirectory / "kept.txt")); + Require(RemoveDirectoryW(junctionOccupied.wstring().c_str()) && unlink_nid_postfix(directoryJunction.string().c_str()) == 0); +#endif + std::filesystem::remove_all(linkedDirectory); + std::filesystem::remove(emptyDirectory); + } { const auto area = root / "libc_rename"; const auto sourceFile = area / "source.txt"; @@ -1434,7 +1837,18 @@ int main() { Require(close_nid_postfix(plainFile) == 0); Require(close_nid_postfix(directoryDescriptor) == 0); const int flaggedDirectory = sceKernelOpen(rootName.c_str(), 0x20000, 0); - Require(flaggedDirectory >= 0 && sceKernelClose(flaggedDirectory) == 0); + FileStat directoryStatus{}; + Require(flaggedDirectory >= 0 && sceKernelFstat(flaggedDirectory, &directoryStatus) == 0 && directoryStatus.st_blksize == 0x10000); + Require(sceKernelStat(rootName.c_str(), &directoryStatus) == 0 && directoryStatus.st_blksize == 0x10000); + FileStat fileStatus{}; +#ifdef _WIN32 + Require(sceKernelStat(presentName.c_str(), &fileStatus) == 0 && fileStatus.st_blksize == 512); +#else + struct stat presentHostStat {}; + Require(::stat(presentName.c_str(), &presentHostStat) == 0); + Require(sceKernelStat(presentName.c_str(), &fileStatus) == 0 && fileStatus.st_blksize == static_cast(presentHostStat.st_blksize)); +#endif + Require(sceKernelClose(flaggedDirectory) == 0); std::vector entries(4096); for (const auto& directoryName : {std::string("/"), std::string("."), rootName + "/", rootName + "/.."}) { const int listed = sceKernelOpen(directoryName.c_str(), 0x20000, 0); @@ -1554,6 +1968,7 @@ int main() { Require(sceKernelRmdir(directoryLink.string().c_str()) == static_cast(0x80020014u)); Require(rmdir_nid_postfix(danglingDirectoryLink.string().c_str()) == -1 && *__error_nid_postfix() == 20); Require((GetFileAttributesW(directoryLink.c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && std::filesystem::is_directory(linkedDirectory)); + Require(unlink_nid_postfix((root / "directory-li*").string().c_str()) == -1 && *__error_nid_postfix() != 5 && (GetFileAttributesW(directoryLink.c_str()) & FILE_ATTRIBUTE_REPARSE_POINT) != 0); Require(unlink_nid_postfix(directoryLink.string().c_str()) == 0 && unlink_nid_postfix(danglingDirectoryLink.string().c_str()) == 0); } #else @@ -1682,6 +2097,17 @@ int main() { } Require(rejected); } + auto* reopened = fopen_nid_postfix(presentName.c_str(), "rb"); + Require(reopened != nullptr && freopen_nid_postfix(device, "r", reopened) == reopened); + std::memset(first, 0, sizeof(first)); + std::memset(second, 0, sizeof(second)); + Require(fread_nid_postfix(first, 1, sizeof(first), reopened) == sizeof(first)); + Require(fread_nid_postfix(second, 1, sizeof(second), reopened) == sizeof(second)); + Require(std::memcmp(first, second, sizeof(first)) != 0); + Require(feof_nid_postfix(reopened) == 0 && ferror_nid_postfix(reopened) == 0); + Require(freopen_nid_postfix(presentName.c_str(), "rb", reopened) == reopened); + Require(fread_nid_postfix(first, 1, sizeof(first), reopened) == std::filesystem::file_size(present) && fgetc_nid_postfix(reopened) == -1); + Require(freopen_nid_postfix(device, "rb", reopened) == reopened && fclose_nid_postfix(reopened) == 0); } Require(unlink_nid_postfix(presentName.c_str()) == 0 && !std::filesystem::exists(present)); const auto empty = root / "empty"; diff --git a/core/libs/tests/GuestFindLastTrivial.cpp b/core/libs/tests/GuestFindLastTrivial.cpp new file mode 100644 index 0000000000..4e11d2dd85 --- /dev/null +++ b/core/libs/tests/GuestFindLastTrivial.cpp @@ -0,0 +1,174 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#include +#endif + +static void Check(bool condition, int line) { + if (condition) return; + std::fprintf(stderr, "find-last check failed at line %d\n", line); + std::abort(); +} +#define Require(condition) Check((condition), __LINE__) + +class GuardedPage { + unsigned char* allocation; +public: + std::size_t size; + unsigned char* data; + + GuardedPage() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + size = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, size * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + size, size, PAGE_READWRITE, &previous) != 0); +#else + const long pageSize = sysconf(_SC_PAGESIZE); + Require(pageSize > 0); + size = static_cast(pageSize); + allocation = static_cast(mmap(nullptr, size * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + size, size, PROT_READ | PROT_WRITE) == 0); +#endif + data = allocation + size; + std::memset(data, 0xff, size); +#ifdef _WIN32 + Require(VirtualProtect(data, size, PAGE_READONLY, &previous) != 0); +#else + Require(mprotect(data, size, PROT_READ) == 0); +#endif + } + + GuardedPage(const GuardedPage&) = delete; + GuardedPage& operator=(const GuardedPage&) = delete; + + ~GuardedPage() { +#ifdef _WIN32 + VirtualFree(allocation, 0, MEM_RELEASE); +#else + munmap(allocation, size * 3); +#endif + } +}; + +template +static void Invoke(TFunction function, const void* first, const void* last, TValue value, const void* expected) { + *__error_nid_postfix() = 97; + Require(function(first, last, value) == expected); + Require(*__error_nid_postfix() == 97); +} + +template +static void Empty(TFunction function) { + GuardedPage page; + for (const void* address : {static_cast(nullptr), static_cast(page.data), + static_cast(page.data - 1), static_cast(page.data + page.size)}) { + for (const TValue value : {TValue{}, TValue{1}, std::numeric_limits::max()}) { + Invoke(function, address, address, value, address); + } + } +} + +template +static void Positions(TFunction function) { + std::array storage; + const TValue zero{}, maximum = std::numeric_limits::max(); + for (std::size_t alignment = 0; alignment < 32; ++alignment) { + auto* first = storage.data() + alignment; + for (std::size_t count = 1; count <= 129; ++count) { + auto* last = first + count * sizeof(TValue); + storage.fill(0xff); + Invoke(function, first, last, zero, last); + Invoke(function, first, last, maximum, last - sizeof(TValue)); + for (std::size_t index = 0; index < count; ++index) { + auto* match = first + index * sizeof(TValue); + std::memcpy(match, &zero, sizeof(zero)); + const auto before = storage; + Invoke(function, first, last, zero, match); + Require(storage == before); + } + Invoke(function, first, last, maximum, last); + } + } +} + +template +static void Values(TFunction function) { + std::array storage{}; + std::uint64_t state = 0xac431bd5708ef269ULL; + const auto checkValue = [&](TValue value) { + for (std::size_t alignment = 0; alignment < 32; ++alignment) { + auto* first = storage.data() + alignment; + auto* last = first + 64 * sizeof(TValue); + const TValue other = static_cast(~value); + for (std::size_t index = 0; index < 64; ++index) std::memcpy(first + index * sizeof(TValue), &other, sizeof(other)); + Invoke(function, first, last, value, last); + for (const std::size_t index : {std::size_t{0}, std::size_t{15}, std::size_t{31}, std::size_t{63}}) { + auto* match = first + index * sizeof(TValue); + std::memcpy(match, &value, sizeof(value)); + const auto before = storage; + Invoke(function, first, last, value, match); + Require(storage == before); + } + } + }; + if constexpr (sizeof(TValue) <= 2) { + for (std::uint32_t value = 0; value <= std::numeric_limits::max(); ++value) checkValue(static_cast(value)); + } else { + for (unsigned bit = 0; bit < sizeof(TValue) * 8; ++bit) { + const auto value = static_cast(TValue{1} << bit); + checkValue(value); + checkValue(static_cast(~value)); + } + for (unsigned index = 0; index < 1024; ++index) { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + checkValue(static_cast(state)); + } + } +} + +template +static void GuardPages(TFunction function) { + GuardedPage page; + for (std::size_t count = 1; count <= page.size / sizeof(TValue); ++count) { + for (const auto* first : {page.data, page.data + page.size - count * sizeof(TValue)}) { + const auto* last = first + count * sizeof(TValue); + Invoke(function, first, last, TValue{}, last); + Invoke(function, first, last, std::numeric_limits::max(), last - sizeof(TValue)); + } + } +} + +template +static void Run(TFunction function, const char* mode) { + if (std::strcmp(mode, "empty") == 0) Empty(function); + else if (std::strcmp(mode, "positions") == 0) Positions(function); + else if (std::strcmp(mode, "values") == 0) Values(function); + else if (std::strcmp(mode, "guard_pages") == 0) GuardPages(function); + else Require(false); +} + +int main(int argc, char** argv) { + Require(argc == 3); + if (std::strcmp(argv[1], "1") == 0) Run(__std_find_last_trivial_1_nid_postfix, argv[2]); + else if (std::strcmp(argv[1], "2") == 0) Run(__std_find_last_trivial_2_nid_postfix, argv[2]); + else if (std::strcmp(argv[1], "4") == 0) Run(__std_find_last_trivial_4_nid_postfix, argv[2]); + else if (std::strcmp(argv[1], "8") == 0) Run(__std_find_last_trivial_8_nid_postfix, argv[2]); + else Require(false); +} diff --git a/core/libs/tests/GuestFios2.cpp b/core/libs/tests/GuestFios2.cpp new file mode 100644 index 0000000000..96ab8caae3 --- /dev/null +++ b/core/libs/tests/GuestFios2.cpp @@ -0,0 +1,566 @@ +#include "prx/libSceFios2/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "guest_fios2 check failed at line %d\n", line); + std::abort(); + } +} +#define REQUIRE(condition) Require((condition), __LINE__) + +static std::atomic g_completed{0}; +static std::atomic g_deleted{0}; +static std::atomic g_lastError{0}; +static std::atomic g_lastOp{0}; + +static int APS5_VABI CountEvents(void* context, SceFiosOp op, SceFiosOpEvent event, int error) { + REQUIRE(context == reinterpret_cast(0x1234)); + g_lastOp = op; + if (event == SCE_FIOS_OPEVENT_COMPLETE) { + g_lastError = error; + ++g_completed; + } else if (event == SCE_FIOS_OPEVENT_DELETE) { + ++g_deleted; + } + return 0; +} + +static SceFiosOpAttr CountingAttr() { + SceFiosOpAttr attr{}; + attr.pCallback = CountEvents; + attr.pCallbackContext = reinterpret_cast(0x1234); + return attr; +} + +static void CheckInitialization() { + REQUIRE(!sceFiosIsInitialized(nullptr)); + std::uint32_t params[16] = {}; + params[0] = (sizeof(params) << 1) | 0u; + params[3] = 0xfeedbeef; + REQUIRE(sceFiosInitialize(reinterpret_cast(params)) == SCE_FIOS_OK); + REQUIRE(sceFiosIsInitialized(nullptr)); + std::uint32_t copy[16] = {}; + REQUIRE(sceFiosIsInitialized(reinterpret_cast(copy))); + REQUIRE(copy[3] == 0xfeedbeef); + params[3] = 0x1; + sceFiosUpdateParameters(reinterpret_cast(params)); + REQUIRE(sceFiosIsInitialized(reinterpret_cast(copy)) && copy[3] == 0x1); +} + +static void CheckQueries(const std::string& directory, const std::string& file) { + REQUIRE(sceFiosExistsSync(nullptr, file.c_str())); + REQUIRE(sceFiosExistsSync(nullptr, directory.c_str())); + REQUIRE(!sceFiosExistsSync(nullptr, (directory + "/missing").c_str())); + REQUIRE(sceFiosFileExistsSync(nullptr, file.c_str())); + REQUIRE(!sceFiosFileExistsSync(nullptr, directory.c_str())); + REQUIRE(sceFiosDirectoryExistsSync(nullptr, directory.c_str())); + REQUIRE(!sceFiosDirectoryExistsSync(nullptr, file.c_str())); + REQUIRE(!sceFiosExistsSync(nullptr, nullptr)); + REQUIRE(!sceFiosExistsSync(nullptr, "")); + + REQUIRE(sceFiosFileGetSizeSync(nullptr, file.c_str()) == 10); + REQUIRE(sceFiosFileGetSizeSync(nullptr, directory.c_str()) == SCE_FIOS_ERROR_NOT_A_FILE); + REQUIRE(sceFiosFileGetSizeSync(nullptr, (directory + "/missing").c_str()) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosFileGetSizeSync(nullptr, nullptr) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFileGetSizeSync(nullptr, "") == SCE_FIOS_ERROR_BAD_PATH); + const std::string tooLong(2000, 'a'); + REQUIRE(sceFiosFileGetSizeSync(nullptr, tooLong.c_str()) == SCE_FIOS_ERROR_PATH_TOO_LONG); + + SceFiosStat stat{}; + std::memset(&stat, 0xaa, sizeof(stat)); + REQUIRE(sceFiosStatSync(nullptr, file.c_str(), &stat) == SCE_FIOS_OK); + REQUIRE(stat.fileSize == 10 && (stat.statFlags & SCE_FIOS_STAT_DIRECTORY) == 0); + REQUIRE(stat.modificationDate > 1000000000ull * 1000000000ull); + REQUIRE((stat.mode & 0170000) == 0100000); + REQUIRE(sceFiosStatSync(nullptr, directory.c_str(), &stat) == SCE_FIOS_OK); + REQUIRE((stat.statFlags & SCE_FIOS_STAT_DIRECTORY) != 0); + REQUIRE(sceFiosStatSync(nullptr, (directory + "/missing").c_str(), &stat) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosStatSync(nullptr, file.c_str(), nullptr) == SCE_FIOS_ERROR_BAD_PTR); +} + +static void CheckOperationLifecycle(const std::string& file) { + const SceFiosOp op = sceFiosFileGetSize(nullptr, file.c_str()); + REQUIRE(op > 0); + REQUIRE(sceFiosIsValidHandle(op)); + REQUIRE(sceFiosOpIsDone(op)); + REQUIRE(sceFiosOpGetError(op) == SCE_FIOS_OK); + REQUIRE(sceFiosOpWait(op) == SCE_FIOS_OK); + REQUIRE(sceFiosOpGetActualCount(op) == 10); + REQUIRE(sceFiosOpGetPath(op) != nullptr && file == sceFiosOpGetPath(op)); + REQUIRE(sceFiosOpGetAttr(op) != nullptr && !sceFiosOpIsCancelled(op)); + REQUIRE(sceFiosOpGetOffset(op) == 0); + std::vector listed(8); + REQUIRE(sceFiosGetAllOps(listed.data(), static_cast(listed.size())) >= 1); + REQUIRE(listed[0] == op); + REQUIRE(sceFiosOpSyncWaitForIO(op) == 10); + REQUIRE(!sceFiosIsValidHandle(op)); + REQUIRE(sceFiosOpGetError(op) == SCE_FIOS_ERROR_BAD_OP); + REQUIRE(sceFiosOpWait(op) == SCE_FIOS_ERROR_BAD_OP); + REQUIRE(sceFiosOpSyncWait(op) == SCE_FIOS_ERROR_BAD_OP); + REQUIRE(sceFiosOpGetActualCount(op) == SCE_FIOS_ERROR_BAD_OP); + REQUIRE(sceFiosOpGetPath(op) == nullptr); + sceFiosOpDelete(op); + + bool exists = false; + const SceFiosOp existsOp = sceFiosExists(nullptr, file.c_str(), &exists); + REQUIRE(exists && sceFiosOpGetActualCount(existsOp) == 1); + REQUIRE(sceFiosOpSyncWait(existsOp) == 1); + const SceFiosOp missingOp = sceFiosExists(nullptr, "fios2_probe/none", &exists); + REQUIRE(!exists && sceFiosOpSyncWait(missingOp) == 0); + + const SceFiosOp failed = sceFiosFileGetSize(nullptr, "fios2_probe/none"); + REQUIRE(sceFiosOpGetError(failed) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosOpSyncWait(failed) == SCE_FIOS_ERROR_BAD_PATH); +} + +static void CheckCallbacks(const std::string& file) { + g_completed = 0; + g_deleted = 0; + const SceFiosOpAttr attr = CountingAttr(); + const SceFiosOp op = sceFiosFileExists(&attr, file.c_str(), nullptr); + REQUIRE(g_completed == 1 && g_lastOp == op && g_lastError == SCE_FIOS_OK); + REQUIRE(g_deleted == 0); + sceFiosOpDelete(op); + REQUIRE(g_deleted == 1); + REQUIRE(sceFiosFileExistsSync(&attr, file.c_str())); + REQUIRE(g_completed == 2 && g_deleted == 2); + + const SceFiosOp failed = sceFiosFileGetSize(&attr, "fios2_probe/none"); + REQUIRE(g_completed == 3 && g_lastError == SCE_FIOS_ERROR_BAD_PATH); + sceFiosOpDelete(failed); + + SceFiosOpAttr stored{}; + REQUIRE(!sceFiosGetGlobalDefaultOpAttr(&stored)); + SceFiosOpAttr global = CountingAttr(); + global.userTag = 77; + sceFiosSetGlobalDefaultOpAttr(&global); + REQUIRE(sceFiosGetGlobalDefaultOpAttr(&stored) && stored.userTag == 77); + SceFiosOpAttr effective{}; + sceFiosGetDefaultOpAttr(&effective); + REQUIRE(effective.userTag == 77); + const SceFiosOp usesGlobal = sceFiosFileExists(nullptr, file.c_str(), nullptr); + REQUIRE(sceFiosOpGetAttr(usesGlobal)->userTag == 77 && g_completed == 4); + sceFiosOpDelete(usesGlobal); + + SceFiosOpAttr thread = CountingAttr(); + thread.userTag = 88; + sceFiosSetThreadDefaultOpAttr(&thread); + REQUIRE(sceFiosGetThreadDefaultOpAttr(&stored) && stored.userTag == 88); + std::thread other([&] { + SceFiosOpAttr seen{}; + REQUIRE(!sceFiosGetThreadDefaultOpAttr(&seen)); + sceFiosGetDefaultOpAttr(&seen); + REQUIRE(seen.userTag == 77); + }); + other.join(); + const SceFiosOp usesThread = sceFiosFileExists(nullptr, file.c_str(), nullptr); + REQUIRE(sceFiosOpGetAttr(usesThread)->userTag == 88); + sceFiosOpDelete(usesThread); + sceFiosSetThreadDefaultOpAttr(nullptr); + REQUIRE(!sceFiosGetThreadDefaultOpAttr(&stored)); + sceFiosSetGlobalDefaultOpAttr(nullptr); + REQUIRE(!sceFiosGetGlobalDefaultOpAttr(&stored)); +} + +static void CheckSuspension(const std::string& file) { + REQUIRE(!sceFiosIsSuspended() && sceFiosGetSuspendCount() == 0 && sceFiosIsIdle()); + g_completed = 0; + const SceFiosOpAttr attr = CountingAttr(); + sceFiosSuspend(); + sceFiosSuspend(); + REQUIRE(sceFiosIsSuspended() && sceFiosGetSuspendCount() == 2); + const SceFiosOp first = sceFiosFileGetSize(&attr, file.c_str()); + const SceFiosOp second = sceFiosFileGetSize(&attr, file.c_str()); + REQUIRE(!sceFiosOpIsDone(first) && !sceFiosOpIsDone(second) && !sceFiosIsIdle()); + REQUIRE(sceFiosOpGetError(first) == 1 && g_completed == 0); + sceFiosResume(); + REQUIRE(!sceFiosOpIsDone(first) && sceFiosGetSuspendCount() == 1); + std::thread resumer([] { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + sceFiosResume(); + }); + REQUIRE(sceFiosOpWait(first) == SCE_FIOS_OK); + resumer.join(); + REQUIRE(sceFiosOpIsDone(second) && sceFiosIsIdle() && g_completed == 2); + REQUIRE(sceFiosOpGetActualCount(first) == 10 && sceFiosOpGetActualCount(second) == 10); + sceFiosOpDelete(first); + sceFiosOpDelete(second); + sceFiosResume(); + REQUIRE(sceFiosGetSuspendCount() == 0); + + sceFiosSuspend(); + const SceFiosOp cancelled = sceFiosFileGetSize(&attr, file.c_str()); + const SceFiosOp alsoCancelled = sceFiosFileGetSize(&attr, file.c_str()); + sceFiosOpCancel(cancelled); + REQUIRE(sceFiosOpIsDone(cancelled) && sceFiosOpIsCancelled(cancelled)); + REQUIRE(sceFiosOpGetError(cancelled) == SCE_FIOS_ERROR_CANCELLED); + REQUIRE(g_lastError == SCE_FIOS_ERROR_CANCELLED); + sceFiosCancelAllOps(); + REQUIRE(sceFiosOpGetError(alsoCancelled) == SCE_FIOS_ERROR_CANCELLED && sceFiosIsIdle()); + sceFiosResume(); + REQUIRE(sceFiosOpSyncWaitForIO(cancelled) == SCE_FIOS_ERROR_CANCELLED); + sceFiosOpDelete(alsoCancelled); + + sceFiosSuspend(); + const SceFiosOp abandoned = sceFiosFileGetSize(nullptr, file.c_str()); + sceFiosOpDelete(abandoned); + REQUIRE(!sceFiosIsValidHandle(abandoned)); + sceFiosResume(); + sceFiosResume(); + REQUIRE(sceFiosGetSuspendCount() == 0); +} + +static void CheckMutations(const std::string& directory) { + const std::string sub = directory + "/sub"; + const std::string target = sub + "/a.bin"; + REQUIRE(sceFiosDirectoryCreateSync(nullptr, sub.c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosDirectoryCreateSync(nullptr, sub.c_str()) == SCE_FIOS_ERROR_ALREADY_EXISTS); + REQUIRE(sceFiosDirectoryCreateSync(nullptr, (directory + "/none/deeper").c_str()) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosDirectoryCreateSync(nullptr, nullptr) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosDirectoryExistsSync(nullptr, sub.c_str())); + + REQUIRE(sceFiosFileWriteSync(nullptr, target.c_str(), "hello world", 11, 0) == 11); + char buffer[32] = {}; + REQUIRE(sceFiosFileReadSync(nullptr, target.c_str(), buffer, 11, 0) == 11 && std::memcmp(buffer, "hello world", 11) == 0); + std::memset(buffer, 0, sizeof(buffer)); + REQUIRE(sceFiosFileReadSync(nullptr, target.c_str(), buffer, 5, 6) == 5 && std::memcmp(buffer, "world", 5) == 0); + REQUIRE(sceFiosFileReadSync(nullptr, target.c_str(), buffer, 100, 6) == 5); + REQUIRE(sceFiosFileReadSync(nullptr, target.c_str(), buffer, 4, 500) == 0); + REQUIRE(sceFiosFileReadSync(nullptr, target.c_str(), buffer, 0, 0) == 0); + REQUIRE(sceFiosFileReadSync(nullptr, (sub + "/none").c_str(), buffer, 4, 0) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosFileReadSync(nullptr, target.c_str(), buffer, -1, 0) == SCE_FIOS_ERROR_BAD_SIZE); + REQUIRE(sceFiosFileReadSync(nullptr, target.c_str(), buffer, 4, -1) == SCE_FIOS_ERROR_BAD_OFFSET); + REQUIRE(sceFiosFileReadSync(nullptr, target.c_str(), nullptr, 4, 0) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFileReadSync(nullptr, sub.c_str(), buffer, 4, 0) < 0); + REQUIRE(sceFiosFileWriteSync(nullptr, target.c_str(), "xyzzy", 5, 20) == 5); + REQUIRE(sceFiosFileGetSizeSync(nullptr, target.c_str()) == 25); + REQUIRE(sceFiosFileWriteSync(nullptr, target.c_str(), nullptr, 5, 0) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFileWriteSync(nullptr, (sub + "/none/x").c_str(), "a", 1, 0) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosFileWriteSync(nullptr, (sub + "/created.bin").c_str(), "a", 1, 0) == 1); + REQUIRE(sceFiosFileDeleteSync(nullptr, (sub + "/created.bin").c_str()) == SCE_FIOS_OK); + + std::memset(buffer, 0, sizeof(buffer)); + const SceFiosOp read = sceFiosFileRead(nullptr, target.c_str(), buffer, 5, 6); + REQUIRE(sceFiosOpGetRequestCount(read) == 5 && sceFiosOpGetOffset(read) == 6 && sceFiosOpGetBuffer(read) == buffer); + REQUIRE(sceFiosOpGetActualCount(read) == 5 && sceFiosOpSyncWaitForIO(read) == 5 && std::memcmp(buffer, "world", 5) == 0); + + REQUIRE(sceFiosFileTruncateSync(nullptr, target.c_str(), 4) == SCE_FIOS_OK); + REQUIRE(sceFiosFileGetSizeSync(nullptr, target.c_str()) == 4); + REQUIRE(sceFiosFileTruncateSync(nullptr, target.c_str(), -1) == SCE_FIOS_ERROR_BAD_SIZE); + REQUIRE(sceFiosFileTruncateSync(nullptr, (sub + "/none").c_str(), 4) == SCE_FIOS_ERROR_BAD_PATH); + + const std::string renamed = sub + "/b.bin"; + REQUIRE(sceFiosRenameSync(nullptr, target.c_str(), renamed.c_str()) == SCE_FIOS_OK); + REQUIRE(!sceFiosExistsSync(nullptr, target.c_str()) && sceFiosFileExistsSync(nullptr, renamed.c_str())); + REQUIRE(sceFiosRenameSync(nullptr, target.c_str(), renamed.c_str()) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosRenameSync(nullptr, renamed.c_str(), nullptr) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosRenameSync(nullptr, nullptr, renamed.c_str()) == SCE_FIOS_ERROR_BAD_PTR); + + REQUIRE(sceFiosFileDeleteSync(nullptr, sub.c_str()) == SCE_FIOS_ERROR_NOT_A_FILE); + REQUIRE(sceFiosDirectoryDeleteSync(nullptr, renamed.c_str()) == SCE_FIOS_ERROR_NOT_A_DIRECTORY); + REQUIRE(sceFiosDirectoryDeleteSync(nullptr, sub.c_str()) == SCE_FIOS_ERROR_ACCESS); + REQUIRE(sceFiosFileDeleteSync(nullptr, renamed.c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosFileDeleteSync(nullptr, renamed.c_str()) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosDirectoryDeleteSync(nullptr, sub.c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosDirectoryCreateSync(nullptr, sub.c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosDeleteSync(nullptr, sub.c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosFileWriteSync(nullptr, renamed.c_str(), "a", 1, 0) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosFileWriteSync(nullptr, (directory + "/c.bin").c_str(), "a", 1, 0) == 1); + REQUIRE(sceFiosDeleteSync(nullptr, (directory + "/c.bin").c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosDeleteSync(nullptr, (directory + "/c.bin").c_str()) == SCE_FIOS_ERROR_BAD_PATH); + + g_completed = 0; + const SceFiosOpAttr attr = CountingAttr(); + const SceFiosOp created = sceFiosDirectoryCreate(&attr, sub.c_str()); + REQUIRE(g_completed == 1 && sceFiosOpGetError(created) == SCE_FIOS_OK); + sceFiosOpDelete(created); + const SceFiosOp removed = sceFiosDelete(&attr, sub.c_str()); + REQUIRE(g_completed == 2 && sceFiosOpSyncWait(removed) == SCE_FIOS_OK); +} + +static void CheckFileHandles(const std::string& directory) { + const std::string path = directory + "/handle.bin"; + SceFiosOpenParams writer{}; + writer.openFlags = (SCE_FIOS_O_READ | SCE_FIOS_O_WRITE | SCE_FIOS_O_CREAT); + SceFiosFH fh = 0; + REQUIRE(sceFiosFHOpenSync(nullptr, &fh, path.c_str(), nullptr) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosFHOpenSync(nullptr, nullptr, path.c_str(), &writer) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFHOpenSync(nullptr, &fh, nullptr, &writer) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFHOpenSync(nullptr, &fh, directory.c_str(), nullptr) == SCE_FIOS_ERROR_NOT_A_FILE); + REQUIRE(sceFiosFHOpenSync(nullptr, &fh, path.c_str(), &writer) == SCE_FIOS_OK); + REQUIRE(fh > 0 && sceFiosIsValidHandle(fh)); + REQUIRE(path == sceFiosFHGetPath(fh) && sceFiosFHGetOpenParams(fh)->openFlags == writer.openFlags); + REQUIRE(sceFiosFHGetSize(fh) == 0); + std::uint32_t listed[4] = {}; + REQUIRE(sceFiosGetAllFHs(reinterpret_cast(listed), 4) == 1 && listed[0] == static_cast(fh)); + + REQUIRE(sceFiosFHWriteSync(nullptr, fh, "abcdef", 6) == 6); + REQUIRE(sceFiosFHTell(fh) == 6 && sceFiosFHGetSize(fh) == 6); + REQUIRE(sceFiosFHSeek(fh, 1, SCE_FIOS_SEEK_SET) == 1); + char buffer[16] = {}; + REQUIRE(sceFiosFHReadSync(nullptr, fh, buffer, 3) == 3 && std::memcmp(buffer, "bcd", 3) == 0); + REQUIRE(sceFiosFHSeek(fh, -1, SCE_FIOS_SEEK_END) == 5 && sceFiosFHSeek(fh, 1, SCE_FIOS_SEEK_CUR) == 6); + REQUIRE(sceFiosFHReadSync(nullptr, fh, buffer, 3) == 0); + REQUIRE(sceFiosFHSeek(fh, 0, 7) == SCE_FIOS_ERROR_BAD_OFFSET); + REQUIRE(sceFiosFHSeek(fh, -100, SCE_FIOS_SEEK_SET) == SCE_FIOS_ERROR_BAD_OFFSET); + REQUIRE(sceFiosFHPwriteSync(nullptr, fh, "XY", 2, 1) == 2 && sceFiosFHTell(fh) == 6); + std::memset(buffer, 0, sizeof(buffer)); + REQUIRE(sceFiosFHPreadSync(nullptr, fh, buffer, 6, 0) == 6 && std::memcmp(buffer, "aXYdef", 6) == 0); + REQUIRE(sceFiosFHPreadSync(nullptr, fh, buffer, 4, -1) == SCE_FIOS_ERROR_BAD_OFFSET); + REQUIRE(sceFiosFHReadSync(nullptr, fh, buffer, -1) == SCE_FIOS_ERROR_BAD_SIZE); + REQUIRE(sceFiosFHReadSync(nullptr, fh, nullptr, 4) == SCE_FIOS_ERROR_BAD_PTR); + + char first[2] = {}; + char second[3] = {}; + const SceFiosBuffer reads[2] = {{first, sizeof(first)}, {second, sizeof(second)}}; + REQUIRE(sceFiosFHPreadvSync(nullptr, fh, reads, 2, 1) == 5 && std::memcmp(first, "XY", 2) == 0 && std::memcmp(second, "def", 3) == 0); + REQUIRE(sceFiosFHSeek(fh, 0, SCE_FIOS_SEEK_SET) == 0); + REQUIRE(sceFiosFHReadvSync(nullptr, fh, reads, 2) == 5 && std::memcmp(first, "aX", 2) == 0 && std::memcmp(second, "Yde", 3) == 0); + const SceFiosBuffer writes[2] = {{const_cast("12"), 2}, {const_cast("345"), 3}}; + REQUIRE(sceFiosFHPwritevSync(nullptr, fh, writes, 2, 6) == 5 && sceFiosFHGetSize(fh) == 11); + REQUIRE(sceFiosFHSeek(fh, 0, SCE_FIOS_SEEK_END) == 11); + REQUIRE(sceFiosFHWritevSync(nullptr, fh, writes, 2) == 5 && sceFiosFHGetSize(fh) == 16); + REQUIRE(sceFiosFHReadvSync(nullptr, fh, reads, 0) == SCE_FIOS_ERROR_BAD_IOVCNT); + REQUIRE(sceFiosFHReadvSync(nullptr, fh, reads, 5000) == SCE_FIOS_ERROR_BAD_IOVCNT); + REQUIRE(sceFiosFHReadvSync(nullptr, fh, nullptr, 1) == SCE_FIOS_ERROR_BAD_PTR); + + SceFiosStat stat{}; + REQUIRE(sceFiosFHStatSync(nullptr, fh, &stat) == SCE_FIOS_OK && stat.fileSize == 16); + REQUIRE(sceFiosFHStatSync(nullptr, fh, nullptr) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFHTruncateSync(nullptr, fh, 4) == SCE_FIOS_OK && sceFiosFHGetSize(fh) == 4); + REQUIRE(sceFiosFHTruncateSync(nullptr, fh, -1) == SCE_FIOS_ERROR_BAD_SIZE); + REQUIRE(sceFiosFHSyncSync(nullptr, fh) == SCE_FIOS_OK); + + const SceFiosOp op = sceFiosFHPread(nullptr, fh, buffer, 4, 0); + REQUIRE(sceFiosOpGetRequestCount(op) == 4 && sceFiosOpGetOffset(op) == 0 && path == sceFiosOpGetPath(op)); + REQUIRE(sceFiosOpSyncWaitForIO(op) == 4 && std::memcmp(buffer, "aXYd", 4) == 0); + + REQUIRE(sceFiosFHCloseSync(nullptr, fh) == SCE_FIOS_OK); + REQUIRE(!sceFiosIsValidHandle(fh) && sceFiosFHGetPath(fh) == nullptr && sceFiosFHGetOpenParams(fh) == nullptr); + REQUIRE(sceFiosFHCloseSync(nullptr, fh) == SCE_FIOS_ERROR_BAD_FH); + REQUIRE(sceFiosFHReadSync(nullptr, fh, buffer, 1) == SCE_FIOS_ERROR_BAD_FH); + REQUIRE(sceFiosFHSeek(fh, 0, 0) == SCE_FIOS_ERROR_BAD_FH && sceFiosFHTell(fh) == SCE_FIOS_ERROR_BAD_FH); + REQUIRE(sceFiosFHGetSize(fh) == SCE_FIOS_ERROR_BAD_FH); + REQUIRE(sceFiosFHStatSync(nullptr, fh, &stat) == SCE_FIOS_ERROR_BAD_FH); + REQUIRE(sceFiosFHSyncSync(nullptr, fh) == SCE_FIOS_ERROR_BAD_FH); + + SceFiosOpenParams reader{}; + reader.openFlags = SCE_FIOS_O_READ; + REQUIRE(sceFiosFHOpenSync(nullptr, &fh, path.c_str(), &reader) == SCE_FIOS_OK); + REQUIRE(sceFiosFHWriteSync(nullptr, fh, "z", 1) == SCE_FIOS_ERROR_READ_ONLY); + REQUIRE(sceFiosFHTruncateSync(nullptr, fh, 1) == SCE_FIOS_ERROR_READ_ONLY); + REQUIRE(sceFiosFHPreadSync(nullptr, fh, buffer, 4, 0) == 4); + sceFiosCloseAllFiles(); + REQUIRE(!sceFiosIsValidHandle(fh) && sceFiosGetAllFHs(nullptr, 0) == 0); + + SceFiosOpenParams writeOnly{}; + writeOnly.openFlags = SCE_FIOS_O_WRITE | SCE_FIOS_O_TRUNC; + REQUIRE(sceFiosFHOpenSync(nullptr, &fh, path.c_str(), &writeOnly) == SCE_FIOS_OK); + REQUIRE(sceFiosFHGetSize(fh) == 0 && sceFiosFHReadSync(nullptr, fh, buffer, 1) == SCE_FIOS_ERROR_WRITE_ONLY); + REQUIRE(sceFiosFHCloseSync(nullptr, fh) == SCE_FIOS_OK); + + SceFiosOpenParams appender{}; + appender.openFlags = SCE_FIOS_O_WRITE | SCE_FIOS_O_APPEND | SCE_FIOS_O_CREAT; + const std::string created = directory + "/appended.bin"; + REQUIRE(sceFiosFHOpenWithModeSync(nullptr, &fh, created.c_str(), &appender, 0644) == SCE_FIOS_OK); + REQUIRE(sceFiosFHWriteSync(nullptr, fh, "ab", 2) == 2 && sceFiosFHWriteSync(nullptr, fh, "cd", 2) == 2); + REQUIRE(sceFiosFHCloseSync(nullptr, fh) == SCE_FIOS_OK); + REQUIRE(sceFiosFileGetSizeSync(nullptr, created.c_str()) == 4); + + g_completed = 0; + const SceFiosOpAttr attr = CountingAttr(); + const SceFiosOp opened = sceFiosFHOpen(&attr, &fh, created.c_str(), &reader); + REQUIRE(g_completed == 1 && sceFiosOpGetError(opened) == SCE_FIOS_OK && sceFiosIsValidHandle(fh)); + sceFiosOpDelete(opened); + const SceFiosOp closed = sceFiosFHClose(&attr, fh); + REQUIRE(g_completed == 2 && sceFiosOpSyncWait(closed) == SCE_FIOS_OK); + + sceFiosSuspend(); + const SceFiosOp deferred = sceFiosFHOpen(nullptr, &fh, created.c_str(), &reader); + REQUIRE(!sceFiosOpIsDone(deferred)); + sceFiosResume(); + REQUIRE(sceFiosOpSyncWait(deferred) == SCE_FIOS_OK && sceFiosIsValidHandle(fh)); + sceFiosShutdownAndCancelOps(); + REQUIRE(!sceFiosIsValidHandle(fh)); + REQUIRE(sceFiosDeleteSync(nullptr, created.c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosDeleteSync(nullptr, path.c_str()) == SCE_FIOS_OK); +} + +static void CheckDirectories(const std::string& directory) { + const std::string root = directory + "/listing"; + REQUIRE(sceFiosDirectoryCreateSync(nullptr, root.c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosFileWriteSync(nullptr, (root + "/one.bin").c_str(), "12345", 5, 0) == 5); + REQUIRE(sceFiosFileWriteSync(nullptr, (root + "/two.bin").c_str(), "1", 1, 0) == 1); + REQUIRE(sceFiosDirectoryCreateSync(nullptr, (root + "/inner").c_str()) == SCE_FIOS_OK); + + SceFiosDH dh = 0; + char storage[256] = {}; + SceFiosBuffer small{storage, 1}; + SceFiosBuffer none{nullptr, 0}; + SceFiosBuffer big{storage, sizeof(storage)}; + REQUIRE(sceFiosDHOpenSync(nullptr, &dh, root.c_str(), none) == SCE_FIOS_ERROR_BAD_SIZE); + const SceFiosOp probe = sceFiosDHOpen(nullptr, &dh, root.c_str(), small); + REQUIRE(sceFiosOpWait(probe) == SCE_FIOS_ERROR_BAD_SIZE); + const std::int64_t required = sceFiosOpGetActualCount(probe); + REQUIRE(required > 0 && required <= static_cast(sizeof(storage))); + sceFiosOpDelete(probe); + REQUIRE(dh == 0 && sceFiosGetAllDHs(nullptr, 0) == 0); + REQUIRE(sceFiosDHOpenSync(nullptr, &dh, (root + "/missing").c_str(), big) == SCE_FIOS_ERROR_BAD_PATH); + REQUIRE(sceFiosDHOpenSync(nullptr, &dh, (root + "/one.bin").c_str(), big) == SCE_FIOS_ERROR_NOT_A_DIRECTORY); + REQUIRE(sceFiosDHOpenSync(nullptr, nullptr, root.c_str(), big) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosDHOpenSync(nullptr, &dh, nullptr, big) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosDHOpenSync(nullptr, &dh, root.c_str(), big) == SCE_FIOS_OK); + REQUIRE(sceFiosIsValidHandle(dh) && root == sceFiosDHGetPath(dh) && sceFiosGetAllDHs(nullptr, 0) == 1); + + int files = 0, directories = 0; + SceFiosDirEntry entry{}; + for (;;) { + std::memset(&entry, 0x7f, sizeof(entry)); + const int result = sceFiosDHReadSync(nullptr, dh, &entry); + if (result == SCE_FIOS_ERROR_EOF) break; + REQUIRE(result == SCE_FIOS_OK); + const std::string name(entry.fullPath + entry.offsetToName, entry.nameLength); + REQUIRE(std::string(entry.fullPath, entry.fullPathLength) == root + "/" + name && entry.fullPath[entry.fullPathLength] == 0); + if (name == "inner") { + ++directories; + REQUIRE((entry.statFlags & SCE_FIOS_STAT_DIRECTORY) != 0); + } else { + ++files; + REQUIRE(entry.statFlags == 0 && entry.fileSize == (name == "one.bin" ? 5 : 1)); + REQUIRE(name == "one.bin" || name == "two.bin"); + } + } + REQUIRE(files == 2 && directories == 1); + REQUIRE(sceFiosDHReadSync(nullptr, dh, &entry) == SCE_FIOS_ERROR_EOF); + REQUIRE(sceFiosDHReadSync(nullptr, dh, nullptr) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosDHCloseSync(nullptr, dh) == SCE_FIOS_OK); + REQUIRE(!sceFiosIsValidHandle(dh) && sceFiosDHGetPath(dh) == nullptr); + REQUIRE(sceFiosDHCloseSync(nullptr, dh) == SCE_FIOS_ERROR_BAD_DH); + REQUIRE(sceFiosDHReadSync(nullptr, dh, &entry) == SCE_FIOS_ERROR_BAD_DH); + + g_completed = 0; + const SceFiosOpAttr attr = CountingAttr(); + const SceFiosOp opened = sceFiosDHOpen(&attr, &dh, (root + "/").c_str(), big); + REQUIRE(g_completed == 1 && sceFiosOpGetError(opened) == SCE_FIOS_OK); + sceFiosOpDelete(opened); + const SceFiosOp read = sceFiosDHRead(nullptr, dh, &entry); + REQUIRE(sceFiosOpGetError(read) == SCE_FIOS_OK && entry.fullPathLength > root.size() + 1); + REQUIRE(entry.fullPath[root.size()] == '/' && entry.fullPath[root.size() + 1] != '/'); + sceFiosOpDelete(read); + REQUIRE(sceFiosDHCloseSync(nullptr, dh) == SCE_FIOS_OK); + REQUIRE(sceFiosDHOpenSync(nullptr, &dh, root.c_str(), big) == SCE_FIOS_OK); + sceFiosTerminate(); + REQUIRE(!sceFiosIsValidHandle(dh)); + REQUIRE(sceFiosDeleteSync(nullptr, (root + "/inner").c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosDeleteSync(nullptr, (root + "/one.bin").c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosDeleteSync(nullptr, (root + "/two.bin").c_str()) == SCE_FIOS_OK); + REQUIRE(sceFiosDeleteSync(nullptr, root.c_str()) == SCE_FIOS_OK); +} + +static void CheckDates() { + SceFiosTm tm{}; + tm.tm_year = 124; + tm.tm_mon = 1; + tm.tm_mday = 29; + tm.tm_hour = 13; + tm.tm_min = 14; + tm.tm_sec = 15; + const SceFiosDate date = sceFiosDateFromComponents(&tm); + REQUIRE(date == 1709212455ull * 1000000000ull); + SceFiosTm back{}; + REQUIRE(sceFiosDateToComponents(date + 999999999, &back) == &back); + REQUIRE(back.tm_year == 124 && back.tm_mon == 1 && back.tm_mday == 29 && back.tm_hour == 13 && back.tm_min == 14 && back.tm_sec == 15); + REQUIRE(back.tm_wday == 4 && back.tm_yday == 59 && back.tm_isdst == 0); + REQUIRE(sceFiosDateToComponents(0, &back) == &back && back.tm_year == 70 && back.tm_mon == 0 && back.tm_mday == 1 && back.tm_wday == 4 && back.tm_yday == 0); + REQUIRE(sceFiosDateToComponents(date, nullptr) == nullptr && sceFiosDateFromComponents(nullptr) == 0); + SceFiosTm overflow{}; + overflow.tm_year = 99; + overflow.tm_mon = 12; + overflow.tm_mday = 32; + overflow.tm_hour = 24; + overflow.tm_min = 60; + overflow.tm_sec = 60; + SceFiosTm normalized{}; + sceFiosDateToComponents(sceFiosDateFromComponents(&overflow), &normalized); + REQUIRE(normalized.tm_year == 100 && normalized.tm_mon == 1 && normalized.tm_mday == 2 && normalized.tm_hour == 1 && normalized.tm_min == 1 && normalized.tm_sec == 0); + SceFiosTm negative{}; + negative.tm_year = 70; + negative.tm_mon = -1; + negative.tm_mday = 1; + REQUIRE(sceFiosDateFromComponents(&negative) == 0); + const SceFiosDate now = sceFiosDateGetCurrent(); + REQUIRE(now > 1700000000ull * 1000000000ull && sceFiosDateGetCurrent() >= now); + REQUIRE(sceFiosTimeIntervalFromNanoseconds(-5) == -5 && sceFiosTimeIntervalToNanoseconds(123456789012) == 123456789012); +} + +static void CheckBadPointers(const std::string& file) { + const auto badAttr = reinterpret_cast(0x10); + REQUIRE(sceFiosFileGetSizeSync(badAttr, file.c_str()) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFileGetSize(badAttr, file.c_str()) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(!sceFiosFileExistsSync(badAttr, file.c_str())); + REQUIRE(sceFiosStatSync(badAttr, file.c_str(), nullptr) == SCE_FIOS_ERROR_BAD_PTR); + sceFiosSetGlobalDefaultOpAttr(badAttr); + SceFiosOpAttr stored{}; + REQUIRE(!sceFiosGetGlobalDefaultOpAttr(&stored)); + sceFiosSetThreadDefaultOpAttr(badAttr); + REQUIRE(!sceFiosGetThreadDefaultOpAttr(&stored)); + + const auto badParams = reinterpret_cast(0x10); + SceFiosFH fh = 0; + REQUIRE(sceFiosFHOpenSync(nullptr, &fh, file.c_str(), badParams) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFHOpenWithModeSync(nullptr, &fh, file.c_str(), badParams, -1) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(fh == 0 && sceFiosGetAllFHs(nullptr, 0) == 0); + + SceFiosOpenParams reader{}; + reader.openFlags = SCE_FIOS_O_READ; + REQUIRE(sceFiosFHOpenSync(nullptr, &fh, file.c_str(), &reader) == SCE_FIOS_OK); + const auto badVectors = reinterpret_cast(0x10); + REQUIRE(sceFiosFHReadvSync(nullptr, fh, badVectors, 2) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFHPreadvSync(nullptr, fh, badVectors, 2, 0) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFHWritevSync(nullptr, fh, badVectors, 2) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(sceFiosFHCloseSync(nullptr, fh) == SCE_FIOS_OK); + + REQUIRE(sceFiosDateFromComponents(reinterpret_cast(0x10)) == 0); + REQUIRE(sceFiosInitialize(reinterpret_cast(0x10)) == SCE_FIOS_ERROR_BAD_PTR); + REQUIRE(!sceFiosIsInitialized(nullptr)); + sceFiosUpdateParameters(reinterpret_cast(0x10)); +} + +int main() { + const std::filesystem::path directory = "fios2_probe"; + std::filesystem::remove_all(directory); + std::filesystem::create_directories(directory); + const std::string file = (directory / "data.bin").generic_string(); + { + std::ofstream out(file, std::ios::binary); + out << "0123456789"; + } + CheckInitialization(); + CheckQueries(directory.generic_string(), file); + CheckOperationLifecycle(file); + CheckCallbacks(file); + CheckSuspension(file); + CheckMutations(directory.generic_string()); + CheckFileHandles(directory.generic_string()); + CheckDirectories(directory.generic_string()); + CheckDates(); + CheckBadPointers(file); + sceFiosTerminate(); + REQUIRE(!sceFiosIsInitialized(nullptr)); + std::filesystem::remove_all(directory); + std::puts("guest_fios2 ok"); + return 0; +} diff --git a/core/libs/tests/GuestFirmwareConstantReturns.cpp b/core/libs/tests/GuestFirmwareConstantReturns.cpp index d63502eb3f..961bd458f3 100644 --- a/core/libs/tests/GuestFirmwareConstantReturns.cpp +++ b/core/libs/tests/GuestFirmwareConstantReturns.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace { @@ -52,6 +53,10 @@ void CheckComposite() { Require(sceCompositorSetIndirectCanvasCommand() == 0); Require(sceCompositorSetIndirectCompositionCommand() == 0); Require(sceCompositorSetIndirectRenderTargetConfigCommand() == 0); + const std::uint32_t virtualHandles[] = {0xff00, 0xff01, 0xffff, 0xffffff00, 0x1234ff78, 0xffffffff, 0x8000ff00}; + const std::uint32_t otherHandles[] = {0, 0xfe00, 0x7f00, 0x12345678, 0x100, 0xff}; + for (const std::uint32_t handle : virtualHandles) Require(sceCompositorIsVirtualCanvasHandle(handle)); + for (const std::uint32_t handle : otherHandles) Require(!sceCompositorIsVirtualCanvasHandle(handle)); } void CheckFsInternal() { @@ -77,6 +82,35 @@ void CheckSdma() { void CheckSsl() { Require(CRYPTO_uninitAsymmetricKey() == 0); Require(VLONG_freeVlongQueue() == 0); + + for (const int context : {0, 1, -1, 0x7fffffff, static_cast(0x80000000u)}) { + int moduleId = 0x5a5a5a5a; + Require(sceSslGetNanoSSLModuleId(context, &moduleId) == 0); + Require(moduleId == context); + Require(sceSslGetNanoSSLModuleId(context, nullptr) == 0); + } + + constexpr int InvalidArgument = static_cast(0x8095177a); + unsigned char key[24]; + const unsigned char blob[4] = {1, 2, 3, 4}; + std::memset(key, 0xa5, sizeof(key)); + Require(CA_MGMT_extractKeyBlobEx(0, nullptr, 4, key) == InvalidArgument); + Require(CA_MGMT_extractKeyBlobEx(0, blob, 4, nullptr) == InvalidArgument); + Require(CA_MGMT_extractKeyBlobEx(0, nullptr, 0, nullptr) == InvalidArgument); + for (unsigned char byte : key) Require(byte == 0xa5); + Require(CA_MGMT_extractKeyBlobEx(-1, blob, 0, key) == 0); + const void* stored = nullptr; + std::memcpy(&stored, key + 8, sizeof(stored)); + Require(stored == blob); + for (int index = 0; index < 8; ++index) Require(key[index] == 0xa5); + for (int index = 16; index < 24; ++index) Require(key[index] == 0xa5); + + std::memset(key, 0xa5, sizeof(key)); + Require(CRYPTO_initAsymmetricKey(key) == 0); + std::uint32_t initialized = 0; + std::memcpy(&initialized, key, sizeof(initialized)); + Require(initialized == 1); + for (int index = 4; index < 24; ++index) Require(key[index] == 0xa5); } void CheckVisionManager() { diff --git a/core/libs/tests/GuestFloatingPoint.cpp b/core/libs/tests/GuestFloatingPoint.cpp index 5d312a1460..0afa61f110 100644 --- a/core/libs/tests/GuestFloatingPoint.cpp +++ b/core/libs/tests/GuestFloatingPoint.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -37,8 +38,9 @@ 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; +bool SameEnvironment(const Environment& lhs, const Environment& rhs) { + const bool pointers = (lhs.x87[1] & 0x80u) != 0u && (rhs.x87[1] & 0x80u) != 0u; + return std::equal(lhs.x87.begin(), lhs.x87.begin() + (pointers ? 7 : 3), rhs.x87.begin()) && lhs.sse == rhs.sse; } void Require(bool value, const char* message) { @@ -252,13 +254,13 @@ void CheckHold(HoldExcept hold, Environment* saved) { *__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(SameEnvironment(*saved, before), "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(after.x87[2] == before.x87[2], "feholdexcept preserves x87 tags"); Require(*__error_nid_postfix() == 123, "feholdexcept preserves errno"); } @@ -292,7 +294,7 @@ void CheckHoldExcept(HoldExcept hold) { 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"); + Require(SameEnvironment(original, afterNull), "null feholdexcept preserves the environment"); #ifdef _WIN32 SYSTEM_INFO info{}; GetSystemInfo(&info); @@ -335,7 +337,7 @@ void CheckHoldExcept(HoldExcept hold) { first.join(); second.join(); const auto afterThreads = ReadEnvironment(); - Require(SameControlState(original, afterThreads), "feholdexcept workers preserve the main environment"); + Require(SameEnvironment(original, afterThreads), "feholdexcept workers preserve the main environment"); } int main(int argc, char** argv) { diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp index 8ddd747ee1..537afc95b5 100644 --- a/core/libs/tests/GuestFont.cpp +++ b/core/libs/tests/GuestFont.cpp @@ -193,6 +193,16 @@ static void CheckWritingDoesNotOwnCharacters(const FontMemory* memory, FontHandl FontString string = create(); FontWriting first{}; Require(sceFontWritingInit(&first, string, characters) == SCE_FONT_OK); + FontWritingMetrics metrics{1.0f, 2.0f, {3.0f, 4.0f, 5.0f, 6.0f}}; + const auto metricsAreZero = [&] { + return metrics.advanceX == 0.0f && metrics.advanceY == 0.0f && metrics.Extent.top == 0.0f && metrics.Extent.bottom == 0.0f && metrics.Extent.left == 0.0f && metrics.Extent.right == 0.0f; + }; + Require(sceFontWritingGetRenderMetrics(nullptr, &metrics) == SCE_FONT_ERROR_INVALID_PARAMETER && metricsAreZero()); + Require(sceFontWritingGetRenderMetrics(&first, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontWritingGetRenderMetrics(&first, &metrics) == SCE_FONT_OK); + Require(sceFontWritingSetMaskInvisible(&first, 1) == SCE_FONT_OK); + Require(sceFontWritingSetMaskInvisible(&first, 0) == SCE_FONT_OK); + Require(sceFontWritingSetMaskInvisible(&first, 2) == SCE_FONT_ERROR_INVALID_PARAMETER); FontWriting copy; std::memcpy(©, &first, sizeof(copy)); Require(steps(&first) == text.size() && steps(©) == text.size()); @@ -286,6 +296,17 @@ static void CheckTextCodes(const FontMemory* memory, FontHandle font) { std::uint32_t count = 0; FontTextCharacter* characters = sceFontStringRefersTextCharacters(string, &count); Require(characters != nullptr && count == 3); + FontHandle characterFont = font; + std::uint32_t characterCode = 'A'; + Require(sceFontCharacterGetTextFontCode(nullptr, &characterFont, &characterCode) == SCE_FONT_ERROR_INVALID_PARAMETER && characterFont == nullptr && characterCode == 0); + Require(sceFontCharacterGetTextFontCode(nullptr, nullptr, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontCharacterGetTextFontCode(characters, nullptr, &characterCode) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontCharacterGetTextFontCode(characters, &characterFont, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontCharacterGetTextFontCode(characters, &characterFont, &characterCode) == SCE_FONT_OK && characterFont == font && characterCode == 'A'); + void* textOrder = characters; + Require(sceFontCharacterGetTextOrder(characters, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontCharacterGetTextOrder(nullptr, &textOrder) == SCE_FONT_ERROR_INVALID_PARAMETER && textOrder == nullptr); + Require(sceFontCharacterGetTextOrder(characters, &textOrder) == SCE_FONT_OK && textOrder == characters->textOrder); Require(sceFontStringGetWritingForm(string) == 0x12); Require(sceFontStringGetTerminateCode(string) == 0 && sceFontStringGetTerminateOrder(string) == parsed.text + 3); ParsedText line{"AV\nA", 0, font}; @@ -633,6 +654,8 @@ int main() { Require(sceFontOpenFontSet(nullptr, SystemFontSet, 1, nullptr, &font) == SCE_FONT_ERROR_INVALID_LIBRARY); const unsigned char notAFont[64] = {1, 2, 3, 4}; Require(sceFontOpenFontMemory(library, notAFont, sizeof(notAFont), nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FUNCTION && font == nullptr); + Require(sceFontSupportExternalFonts(library, 0x04000000u, 0x52) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontSupportExternalFonts(library, 0xffffffffu, 0x52) == SCE_FONT_ERROR_INVALID_PARAMETER); Require(sceFontSupportExternalFonts(library, 4, 0x52) == SCE_FONT_OK); Require(sceFontSupportExternalFonts(library, 4, 0x52) == SCE_FONT_ERROR_ALREADY_SPECIFIED); font = reinterpret_cast(&memory); @@ -656,6 +679,14 @@ int main() { 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); + Require(sceFontGetLibrary(font, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + FontLibrary fontLibrary = nullptr; + Require(sceFontGetLibrary(font, &fontLibrary) == SCE_FONT_OK && fontLibrary == library); + Font::FontLibNative invalidLibrary{}; + Require(sceFontSetFontsOpenMode(nullptr, 0) == SCE_FONT_ERROR_INVALID_LIBRARY); + Require(sceFontSetFontsOpenMode(&invalidLibrary, 0) == SCE_FONT_ERROR_INVALID_LIBRARY); + Require(sceFontSetFontsOpenMode(library, 3) == SCE_FONT_ERROR_INVALID_PARAMETER); + for (const auto mode : {0, 1, 2}) Require(sceFontSetFontsOpenMode(library, mode) == SCE_FONT_OK); int setting = -1; Require(sceFontGetScriptLanguage(font, 3, &setting) == SCE_FONT_ERROR_INVALID_PARAMETER && setting == 0); Require(sceFontSetScriptLanguage(font, 3, 7) == SCE_FONT_ERROR_INVALID_PARAMETER); diff --git a/core/libs/tests/GuestFontGsAgc.cpp b/core/libs/tests/GuestFontGsAgc.cpp new file mode 100644 index 0000000000..1e74c58485 --- /dev/null +++ b/core/libs/tests/GuestFontGsAgc.cpp @@ -0,0 +1,331 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libSceFont/libSceFont.hpp" +#include "prx/libSceFontGs.native/libSceFontGs.native.hpp" +#include +#include +#include + +namespace { + +constexpr int Fatal = static_cast(0x80460001); +constexpr int InvalidParameter = static_cast(0x80460002); +constexpr int InvalidSequence = static_cast(0x8046009a); +constexpr int SequenceLocked = static_cast(0x804600b8); +constexpr int InvalidObject = static_cast(0x80460084); + +int g_errorCount = 0; +int g_lastError = 0; + +void APS5_VABI RecordError(int error) { + g_lastError = error; + ++g_errorCount; +} + +void Require(bool value) { if (!value) std::abort(); } + +struct TestDrawupRecord { + std::uint8_t type; + std::uint8_t sizeTag; + std::uint16_t flags; + std::uint32_t sequence; + TestDrawupRecord* next; + std::uint32_t state; + std::uint32_t mode; + void* payload; + std::uint32_t params[8]; + std::uint8_t reserved[0x40]; +}; + +struct TestDrawupEntry { + std::uint8_t* memory; + std::uint32_t reserved8; + std::uint32_t slotSize; + std::uint32_t total; + std::uint32_t used; + std::uint16_t* nodes; + std::uint16_t* scan; + std::uint64_t reserved28; +}; + +struct TestDrawupPool { + PthreadMutex* mutex; + std::uint32_t reserved8; + std::uint32_t entryCount; + std::uint32_t used; + std::uint32_t maximum; + TestDrawupEntry entries[1]; +}; + +struct TestDrawupOwner { + std::uint8_t reserved[0x160]; + TestDrawupPool* pool; +}; + +struct TestDrawupObject { + std::uint64_t reserved0; + TestDrawupOwner* owner; + void* resource; + std::uint64_t reserved18[2]; + TestDrawupRecord* head; + TestDrawupRecord* tail; +}; + +struct TestDrawupFrame { + std::uint16_t tag; + std::uint16_t reserved2; + int error; + TestDrawupObject* object; + std::uint32_t unitSize; + std::uint32_t unitUsed; + void* unitHead; + std::uint8_t* unit; + std::uint32_t sequence; +}; + +static_assert(sizeof(TestDrawupRecord) == 0x80); +static_assert(offsetof(TestDrawupRecord, state) == 0x10); +static_assert(offsetof(TestDrawupOwner, pool) == 0x160); +static_assert(offsetof(TestDrawupFrame, object) == 0x8 && offsetof(TestDrawupFrame, sequence) == 0x28); + +struct TestAgcSurface { + void* head; + std::uint8_t reserved8[5]; + std::uint8_t tag; + std::uint8_t reserved0e; + std::uint8_t mode; + std::uint8_t reserved10[0xc]; + std::uint32_t busy; + std::uint8_t reserved20[8]; + const void* fillObject; + std::uint8_t reserved30[8]; + std::uint32_t initialized; +}; + +static_assert(offsetof(TestAgcSurface, tag) == 0x0d); +static_assert(offsetof(TestAgcSurface, mode) == 0x0f); +static_assert(offsetof(TestAgcSurface, busy) == 0x1c); +static_assert(offsetof(TestAgcSurface, fillObject) == 0x28); +static_assert(offsetof(TestAgcSurface, initialized) == 0x38); + +struct TestAgcCommands { + std::uint8_t reserved[0x28]; + const void* first; + std::uint32_t firstSize; + std::uint32_t secondCount; + const void* second; +}; + +static_assert(offsetof(TestAgcCommands, first) == 0x28); +static_assert(offsetof(TestAgcCommands, firstSize) == 0x30); +static_assert(offsetof(TestAgcCommands, secondCount) == 0x34); +static_assert(offsetof(TestAgcCommands, second) == 0x38); + +struct TestAgcSequence { + std::uint16_t tag; + std::uint8_t reserved2[0x19]; + std::uint8_t lock; + std::uint8_t reserved1c[0xc]; + TestAgcCommands* commands; +}; + +static_assert(offsetof(TestAgcSequence, lock) == 0x1b); +static_assert(offsetof(TestAgcSequence, commands) == 0x28); + +constexpr std::uint32_t TotalSlots = 8; + +struct DrawupFixture { + PthreadMutex mutex{}; + std::uint8_t memory[TotalSlots * 0x80]{}; + std::uint16_t nodes[TotalSlots]{}; + TestDrawupPool pool{}; + TestDrawupOwner owner{}; + TestDrawupObject object{}; + TestDrawupFrame frame{}; + std::uint8_t descriptor[0x200]{}; + TestAgcSurface surface{}; + + DrawupFixture() { + scePthreadMutexInit(&mutex, nullptr, "agc-drawup"); + nodes[0] = TotalSlots << 1; + pool.mutex = &mutex; + pool.entryCount = 1; + pool.entries[0] = {memory, 0, 0x80, TotalSlots, 0, nodes, nodes, 0}; + owner.pool = &pool; + object.owner = &owner; + frame.tag = 0xf53; + frame.object = &object; + surface.head = &object; + surface.tag = 0x58; + surface.mode = 0x22; + surface.fillObject = descriptor; + surface.initialized = 1; + for (std::size_t i = 0; i < sizeof(descriptor); ++i) descriptor[i] = static_cast(i); + } + + ~DrawupFixture() { scePthreadMutexDestroy(&mutex); } +}; + +void AgcSelect() { + Require(sceFontSelectGraphicsAgc(1) == nullptr); + Require(sceFontSelectGraphicsAgc(0xffffffff) == nullptr); +} + +void AgcDrawupImage() { + { + void* out = reinterpret_cast(0xdead); + const int before = g_errorCount; + Require(sceFontGraphicsAgcDrawupFillTextureImage(nullptr, nullptr, &out, 0.0f, 0.0f, 8.0f, 8.0f) == InvalidParameter); + Require(out == nullptr); + Require(g_errorCount == before); + } + { + DrawupFixture f; + auto* frame = reinterpret_cast(&f.frame); + const int before = g_errorCount; + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, nullptr, 0.0f, 0.0f, 8.0f, 8.0f) == InvalidParameter); + Require(g_errorCount == before); + } + { + DrawupFixture f; + auto* frame = reinterpret_cast(&f.frame); + void* out = reinterpret_cast(0xdead); + const int before = g_errorCount; + f.surface.tag = 0x57; + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, 8.0f, 8.0f) == InvalidParameter); + Require(out == nullptr); + f.surface.tag = 0x58; + f.surface.head = nullptr; + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, 8.0f, 8.0f) == InvalidParameter); + f.surface.head = &f.object; + f.surface.initialized = 0; + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, 8.0f, 8.0f) == InvalidParameter); + f.surface.initialized = 1; + f.surface.busy = 1; + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, 8.0f, 8.0f) == InvalidParameter); + f.surface.busy = 0; + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, -1.0f, 0.0f, 8.0f, 8.0f) == InvalidParameter); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, -1.0f, 8.0f, 8.0f) == InvalidParameter); + const float nan = std::strtof("nan", nullptr); + Require(!(nan >= 0.0f)); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, nan, 0.0f, 8.0f, 8.0f) == InvalidParameter); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, nan, 8.0f, 8.0f) == InvalidParameter); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, nan, 8.0f) == InvalidParameter); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, 8.0f, nan) == InvalidParameter); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 16830.0f, 0.0f, 8.0f, 8.0f) == InvalidParameter); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, -4.0f, 8.0f) == InvalidParameter); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, 8.0f, -4.0f) == InvalidParameter); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, 16834.0f, 16834.0f) == 0); + Require(out != nullptr); + const auto* firstRecord = static_cast(out); + Require(f.object.head == firstRecord && f.object.tail == firstRecord); + out = reinterpret_cast(0xdead); + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, 8.0f, 8.0f) == 0); + Require(out != nullptr); + const auto* record = static_cast(out); + Require(record->state == 1); + Require(record->mode == 0x22); + Require(f.object.head == firstRecord && f.object.tail == record); + Require(g_errorCount == before); + } + { + DrawupFixture f; + auto* frame = reinterpret_cast(&f.frame); + void* out = reinterpret_cast(0xdead); + f.frame.tag = 0; + Require(sceFontGraphicsAgcDrawupFillTextureImage(frame, &f.surface, &out, 0.0f, 0.0f, 8.0f, 8.0f) == InvalidObject); + Require(out == nullptr); + Require(f.frame.error == InvalidObject); + Require(g_lastError == InvalidObject); + g_lastError = 0; + } +} + +void AgcDrawupPattern() { + { + DrawupFixture f; + auto* frame = reinterpret_cast(&f.frame); + const int before = g_errorCount; + Require(sceFontGraphicsAgcDrawupFillTexturePattern(frame, &f.surface, 1, 2, 3, 4, 5, 6, nullptr) == InvalidParameter); + f.surface.initialized = 0; + void* out = reinterpret_cast(0xdead); + Require(sceFontGraphicsAgcDrawupFillTexturePattern(frame, &f.surface, 1, 2, 3, 4, 5, 6, &out) == InvalidParameter); + Require(out == nullptr); + Require(g_errorCount == before); + f.surface.initialized = 1; + Require(sceFontGraphicsAgcDrawupFillTexturePattern(frame, &f.surface, 1, 2, 3, 4, 5, 6, &out) == 0); + Require(out != nullptr); + const auto* record = static_cast(out); + Require(record->state == 1); + Require(record->mode == 0x22); + } +} + +void AgcSequences() { + std::uint32_t firstData[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + std::uint32_t secondData[3] = {9, 10, 11}; + TestAgcCommands commands{}; + commands.first = firstData; + commands.firstSize = 0x20; + commands.secondCount = 3; + commands.second = secondData; + TestAgcSequence sequence{}; + sequence.tag = 0xf80; + sequence.commands = &commands; + auto* seq = reinterpret_cast(&sequence); + { + void* out = reinterpret_cast(0xdead); + void* out2 = reinterpret_cast(0xbeef); + std::uint32_t count = 0xcccc; + std::uint32_t count2 = 0xdddd; + const int before = g_errorCount; + Require(sceFontGraphicsSequenceGetAgcCommandsForJump(nullptr, &out, &count, &out2, &count2) == InvalidSequence); + Require(out == nullptr && count == 0 && out2 == nullptr && count2 == 0); + Require(sceFontGraphicsSequenceGetAgcCommandsForJump(seq, &out, nullptr, &out2, &count2) == InvalidParameter); + Require(out == nullptr && out2 == nullptr && count2 == 0); + Require(g_errorCount == before); + sequence.tag = 0xf81; + Require(sceFontGraphicsSequenceGetAgcCommandsForJump(seq, &out, &count, &out2, &count2) == InvalidSequence); + Require(sceFontGraphicsSequenceGetAgcCommandsForJump(seq, nullptr, nullptr, nullptr, nullptr) == InvalidSequence); + sequence.tag = 0xf80; + sequence.lock = 1; + Require(sceFontGraphicsSequenceGetAgcCommandsForJump(seq, &out, &count, &out2, &count2) == SequenceLocked); + sequence.lock = 0; + auto* saved = sequence.commands; + sequence.commands = nullptr; + Require(sceFontGraphicsSequenceGetAgcCommandsForJump(seq, &out, &count, &out2, &count2) == Fatal); + sequence.commands = saved; + Require(sceFontGraphicsSequenceGetAgcCommandsForJump(seq, &out, &count, &out2, &count2) == 0); + Require(out == firstData && count == 8 && out2 == secondData && count2 == 3); + Require(g_errorCount == before); + } + { + void* out = reinterpret_cast(0xdead); + std::uint32_t count = 0xcccc; + const int before = g_errorCount; + Require(sceFontGraphicsSequenceGetAgcCommandsForSubmit(nullptr, &out, &count) == InvalidSequence); + Require(out == nullptr && count == 0); + Require(sceFontGraphicsSequenceGetAgcCommandsForSubmit(seq, nullptr, &count) == InvalidParameter); + Require(count == 0); + sequence.tag = 0xf81; + Require(sceFontGraphicsSequenceGetAgcCommandsForSubmit(seq, nullptr, nullptr) == InvalidSequence); + sequence.tag = 0xf80; + sequence.lock = 1; + Require(sceFontGraphicsSequenceGetAgcCommandsForSubmit(seq, &out, &count) == SequenceLocked); + sequence.lock = 0; + Require(sceFontGraphicsSequenceGetAgcCommandsForSubmit(seq, &out, &count) == 0); + Require(out == firstData && count == 0x20); + Require(g_errorCount == before); + } +} + +} + +int main() { + sceFontSetGraphicsErrorCallback(RecordError); + AgcSelect(); + AgcDrawupImage(); + AgcDrawupPattern(); + AgcSequences(); + sceFontSetGraphicsErrorCallback(nullptr); +} diff --git a/core/libs/tests/GuestFontVerticalLayout.cpp b/core/libs/tests/GuestFontVerticalLayout.cpp new file mode 100644 index 0000000000..da7bf3a68b --- /dev/null +++ b/core/libs/tests/GuestFontVerticalLayout.cpp @@ -0,0 +1,122 @@ +#include "prx/libSceFontFt/include/Api.hpp" +#include "prx/libSceFont/include/Api.hpp" +#include "prx/libSceFont/include/FontTypes.hpp" +#include "tests/FontFixture.hpp" +#include +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Font vertical layout check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static int allocations = 0; +static void* APS5_VABI Allocate(void*, std::uint32_t size) { + ++allocations; + return std::malloc(size); +} +static void APS5_VABI Release(void*, void* pointer) { + if (pointer) --allocations; + std::free(pointer); +} + +static void Put16(std::vector& out, int value) { + out.push_back(static_cast((value >> 8) & 0xFF)); + out.push_back(static_cast(value & 0xFF)); +} + +static std::vector Words(std::initializer_list values) { + std::vector out; + for (const int value : values) Put16(out, value); + return out; +} + +static std::uint32_t ReadU32(const std::vector& font, std::size_t offset) { + return (static_cast(font[offset]) << 24) | (static_cast(font[offset + 1]) << 16) | + (static_cast(font[offset + 2]) << 8) | static_cast(font[offset + 3]); +} + +static void WriteI16(std::vector& font, std::size_t offset, int value) { + font[offset] = static_cast((value >> 8) & 0xFF); + font[offset + 1] = static_cast(value & 0xFF); +} + +static void SetHeadBounds(std::vector& font, int xMin, int yMin, int xMax, int yMax) { + const int tableCount = static_cast(font[4] << 8 | font[5]); + for (int table = 0; table < tableCount; ++table) { + const std::size_t record = 12 + static_cast(table) * 16; + if (std::memcmp(font.data() + record, "head", 4) != 0) continue; + const std::size_t head = ReadU32(font, record + 8); + WriteI16(font, head + 36, xMin); + WriteI16(font, head + 38, yMin); + WriteI16(font, head + 40, xMax); + WriteI16(font, head + 42, yMax); + return; + } + std::abort(); +} + +static std::vector VerticalFont() { + auto font = FontFixture::BuildFont(1, { + {"vhea", Words({1, 0, 800, -200, 0, 500, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1})}, + {"vmtx", Words({500, 0})}, + }); + SetHeadBounds(font, -100, -200, 900, 800); + return font; +} + +static void CheckLayout(FontHandle font, FontVerticalLayout& layout) { + Require(sceFontGetVerticalLayout(font, &layout) == SCE_FONT_OK); + Require(layout.columnAdvance > 0.0f); + Require(layout.baselineOffsetX < 0.0f); + Require(layout.decorationSpan == 0.0f); +} + +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, 1, 0x52) == SCE_FONT_OK); + const std::vector fontData = VerticalFont(); + FontHandle font = nullptr; + Require(sceFontOpenFontMemory(library, fontData.data(), static_cast(fontData.size()), nullptr, &font) == SCE_FONT_OK); + + FontVerticalLayout layout{}; + Require(sceFontGetVerticalLayout(font, nullptr) == SCE_FONT_ERROR_INVALID_PARAMETER); + Require(sceFontGetVerticalLayout(nullptr, &layout) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(layout.baselineOffsetX == 0.0f && layout.columnAdvance == 0.0f && layout.decorationSpan == 0.0f); + layout = {1.0f, 1.0f, 1.0f}; + FontHandleOpaque unopened{}; + Require(sceFontGetVerticalLayout(&unopened, &layout) == SCE_FONT_ERROR_INVALID_FONT_HANDLE); + Require(layout.baselineOffsetX == 0.0f && layout.columnAdvance == 0.0f && layout.decorationSpan == 0.0f); + + Require(sceFontSetScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK); + CheckLayout(font, layout); + const FontVerticalLayout pixelLayout = layout; + Require(sceFontSetResolutionDpi(font, 144, 144) == SCE_FONT_OK); + CheckLayout(font, layout); + Require(std::memcmp(&layout, &pixelLayout, sizeof(pixelLayout)) == 0); + + Require(sceFontSetScalePoint(font, 20.0f, 30.0f) == SCE_FONT_OK); + CheckLayout(font, layout); + const FontVerticalLayout pointLayout = layout; + FontHandle instance = nullptr; + Require(sceFontOpenFontInstance(font, nullptr, &instance) == SCE_FONT_OK); + FontVerticalLayout instanceLayout{}; + Require(sceFontGetVerticalLayout(instance, &instanceLayout) == SCE_FONT_OK); + Require(std::memcmp(&instanceLayout, &pointLayout, sizeof(pointLayout)) == 0); + Require(sceFontCloseFont(instance) == SCE_FONT_OK); + + Require(sceFontCloseFont(font) == SCE_FONT_OK); + Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK); + Require(allocations == 0); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); +} diff --git a/core/libs/tests/GuestFreeTypeBuildInfo.cpp b/core/libs/tests/GuestFreeTypeBuildInfo.cpp new file mode 100644 index 0000000000..93efe929c8 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeBuildInfo.cpp @@ -0,0 +1,67 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" + +#include FT_MODULE_H + +#include +#include +#include +#include +#include + +using FreeType::GuestLibrary; + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "FreeType build info check failed at line %u\n", location.line()); + std::abort(); + } +} + +void CheckVersion(GuestLibrary* guest, FT_Library native) { + std::array expected{}; + FT_Library_Version(native, &expected[0], &expected[1], &expected[2]); + for (unsigned mask = 0; mask < 8; ++mask) { + std::array values{0x12345678, -1, -2, -3, 0x76543210}; + const auto before = values; + FT_Library_Version_nid_postfix(guest, mask & 1 ? &values[1] : nullptr, + mask & 2 ? &values[2] : nullptr, mask & 4 ? &values[3] : nullptr); + Require(values[0] == before[0] && values[4] == before[4]); + for (unsigned index = 0; index < 3; ++index) + Require(values[index + 1] == (mask & (1u << index) ? expected[index] : before[index + 1])); + std::array alias{0x12345678, -1, 0x76543210}; + FT_Library_Version_nid_postfix(guest, mask & 1 ? &alias[1] : nullptr, + mask & 2 ? &alias[1] : nullptr, mask & 4 ? &alias[1] : nullptr); + const auto last = mask & 4 ? expected[2] : mask & 2 ? expected[1] : mask & 1 ? expected[0] : -1; + Require(alias[1] == last && alias[0] == 0x12345678 && alias[2] == 0x76543210); + } +} + +int main() { + CheckVersion(nullptr, nullptr); + Require(FT_Get_TrueType_Engine_Type_nid_postfix(nullptr) == FT_TRUETYPE_ENGINE_TYPE_NONE); + GuestLibrary* first = nullptr; + GuestLibrary* second = nullptr; + FT_Library native = nullptr; + Require(FT_Init_FreeType_nid_postfix(&first) == 0); + Require(FT_Init_FreeType_nid_postfix(&second) == 0); + Require(FT_Init_FreeType(&native) == 0); + CheckVersion(first, native); + CheckVersion(second, native); + const auto initialEngine = FT_Get_TrueType_Engine_Type(native); + Require(FT_Get_TrueType_Engine_Type_nid_postfix(first) == initialEngine); + Require(FT_Get_TrueType_Engine_Type_nid_postfix(second) == initialEngine); + if (const auto module = FT_Get_Module(native, "truetype")) { + Require(FT_Remove_Module(first->host, FT_Get_Module(first->host, "truetype")) == 0); + Require(FT_Remove_Module(native, module) == 0); + Require(FT_Get_TrueType_Engine_Type_nid_postfix(first) == FT_TRUETYPE_ENGINE_TYPE_NONE); + Require(FT_Get_TrueType_Engine_Type_nid_postfix(first) == FT_Get_TrueType_Engine_Type(native)); + Require(FT_Get_TrueType_Engine_Type_nid_postfix(second) == initialEngine); + CheckVersion(first, native); + } + Require(FT_Done_FreeType_nid_postfix(first) == 0); + CheckVersion(second, native); + Require(FT_Get_TrueType_Engine_Type_nid_postfix(second) == initialEngine); + Require(FT_Done_FreeType_nid_postfix(second) == 0); + Require(FT_Done_FreeType(native) == 0); +} diff --git a/core/libs/tests/GuestFreeTypeFaceCompat.cpp b/core/libs/tests/GuestFreeTypeFaceCompat.cpp new file mode 100644 index 0000000000..7f6a27eab6 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeFaceCompat.cpp @@ -0,0 +1,74 @@ +#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 compat check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +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 Query(FacePair& face, const char* format) { + const char* actual = FT_Get_Font_Format_nid_postfix(&face.guest.rec); + const char* expected = FT_Get_Font_Format(face.oracle); + Require(actual && expected && std::strcmp(actual, expected) == 0 && std::strcmp(actual, format) == 0); + Require(actual == FT_Get_Font_Format(face.guest.host)); + Require(FT_Get_Font_Format_nid_postfix(&face.guest.rec) == FT_Get_X11_Font_Format(face.guest.host)); + Require(FT_Face_CheckTrueTypePatents_nid_postfix(&face.guest.rec) == FT_Face_CheckTrueTypePatents(face.oracle)); + Require(FT_Face_CheckTrueTypePatents_nid_postfix(&face.guest.rec) == 0); + for (const std::uint8_t value : {0, 1, 0xFF}) { + Require(FT_Face_SetUnpatentedHinting_nid_postfix(&face.guest.rec, value) == FT_Face_SetUnpatentedHinting(face.oracle, value)); + Require(FT_Face_SetUnpatentedHinting_nid_postfix(&face.guest.rec, value) == 0); + } +} + +int main() { + GuestLibrary library{}; + FT_Library nativeLibrary = nullptr; + Require(FT_Init_FreeType(&library.host) == 0 && FT_Init_FreeType(&nativeLibrary) == 0); + { + FacePair bitmap(library, nativeLibrary, FontFixture::BitmapFont()); + Query(bitmap, "BDF"); + FacePair outline(library, nativeLibrary, FontFixture::BuildFont(3, {})); + Query(outline, "TrueType"); + GuestFaceRec empty{}; + for (GuestFaceRec* face : {static_cast(nullptr), &empty}) { + Require(FT_Get_Font_Format_nid_postfix(face) == FT_Get_Font_Format(nullptr)); + Require(FT_Face_CheckTrueTypePatents_nid_postfix(face) == 0); + Require(FT_Face_SetUnpatentedHinting_nid_postfix(face, 1) == 0); + } + } + Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(nativeLibrary) == 0); +} diff --git a/core/libs/tests/GuestFreeTypeGlyph.cpp b/core/libs/tests/GuestFreeTypeGlyph.cpp new file mode 100644 index 0000000000..0f303088c5 --- /dev/null +++ b/core/libs/tests/GuestFreeTypeGlyph.cpp @@ -0,0 +1,474 @@ +#include "FreeTypeFontFixture.hpp" +#include "prx/libSceFreeType/include/Api.hpp" + +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_OUTLINE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType glyph check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +template static bool Throws(TCall call, const char* function) { + try { + call(); + } catch (const std::runtime_error& error) { + return std::string(error.what()).find(function) != std::string::npos; + } + return false; +} + +static void Matches(const GuestGlyphRec* glyph, FT_Glyph native, void* library, bool isBitmap) { + Require(glyph && native && glyph->library == library && glyph->clazz); + Require(glyph->format == static_cast(native->format)); + Require(glyph->advance.x == native->advance.x && glyph->advance.y == native->advance.y); + if (isBitmap) { + const auto& image = *reinterpret_cast(glyph); + const auto host = reinterpret_cast(native); + Require(image.left == host->left && image.top == host->top); + const auto& a = image.bitmap; + const auto& b = host->bitmap; + Require(a.rows == b.rows && a.width == b.width && a.pitch == b.pitch); + Require(a.num_grays == b.num_grays && a.pixel_mode == b.pixel_mode && a.palette_mode == b.palette_mode && a.palette == b.palette); + Require((a.buffer == nullptr) == (b.buffer == nullptr)); + const std::size_t size = a.rows * static_cast(a.pitch < 0 ? -static_cast(a.pitch) : a.pitch); + if (size && a.buffer) Require(std::memcmp(a.buffer, b.buffer, size) == 0); + } else { + const auto& a = reinterpret_cast(glyph)->outline; + const auto& b = reinterpret_cast(native)->outline; + Require(a.n_points == b.n_points && a.n_contours == b.n_contours && a.flags == b.flags); + for (unsigned index = 0; index < a.n_points; ++index) { + Require(a.points[index].x == b.points[index].x && a.points[index].y == b.points[index].y && a.tags[index] == static_cast(b.tags[index])); + } + for (unsigned index = 0; index < a.n_contours; ++index) Require(a.contours[index] == static_cast(b.contours[index])); + } +} + +struct Fixture { + std::vector font = SquareGlyphFont('A', 700); + GuestLibrary* library = nullptr; + GuestFaceRec* face = nullptr; + FT_Library hostLibrary = nullptr; + FT_Face host = nullptr; + + Fixture() { + Require(FT_Init_FreeType_nid_postfix(&library) == 0); + Require(FT_Init_FreeType(&hostLibrary) == 0); + Require(FT_New_Memory_Face_nid_postfix(library, font.data(), static_cast(font.size()), 0, &face) == 0); + Require(FT_New_Memory_Face(hostLibrary, font.data(), static_cast(font.size()), 0, &host) == 0); + Require(FT_Set_Pixel_Sizes_nid_postfix(face, 0, 48) == FT_Set_Pixel_Sizes(host, 0, 48)); + } + + void Load(std::uint32_t index, std::int32_t flags) { + Require(FT_Load_Glyph_nid_postfix(face, index, flags) == 0); + Require(FT_Load_Glyph(host, index, flags) == 0); + } + + void CloseFaces() { + Require(FT_Done_Face_nid_postfix(face) == 0); + Require(FT_Done_Face(host) == 0); + face = nullptr; + host = nullptr; + } + + ~Fixture() { + if (face) CloseFaces(); + Require(FT_Done_FreeType_nid_postfix(library) == 0); + Require(FT_Done_FreeType(hostLibrary) == 0); + } +}; + +static void OutlineOwnership() { + Fixture fixture; + fixture.Load(1, FT_LOAD_NO_HINTING); + auto* slot = fixture.face->glyph; + auto hostSlot = fixture.host->glyph; + slot->advance = {-731, 2047}; + hostSlot->advance = {-731, 2047}; + slot->outline.points[0].x += 41; + hostSlot->outline.points[0].x += 41; + slot->outline.flags ^= FT_OUTLINE_EVEN_ODD_FILL; + hostSlot->outline.flags ^= FT_OUTLINE_EVEN_ODD_FILL; + slot->outline.tags[0] ^= FT_CURVE_TAG_TOUCH_X; + hostSlot->outline.tags[0] ^= FT_CURVE_TAG_TOUCH_X; + GuestGlyphRec* glyph = nullptr; + FT_Glyph native = nullptr; + Require(FT_Get_Glyph_nid_postfix(slot, &glyph) == FT_Get_Glyph(hostSlot, &native)); + Matches(glyph, native, fixture.library, false); + auto& image = reinterpret_cast(glyph)->outline; + auto& nativeImage = reinterpret_cast(native)->outline; + Require(image.points != slot->outline.points && image.tags != slot->outline.tags && image.contours != slot->outline.contours); + Require(glyph->advance.x == -731 * 1024 && glyph->advance.y == 2047 * 1024); + Require(slot->outline.points[0].x == image.points[0].x); + fixture.Load(0, FT_LOAD_NO_HINTING); + Matches(glyph, native, fixture.library, false); + fixture.CloseFaces(); + Matches(glyph, native, fixture.library, false); + image.points[1].y -= 19; + nativeImage.points[1].y -= 19; + image.tags[1] ^= FT_CURVE_TAG_TOUCH_Y; + nativeImage.tags[1] ^= FT_CURVE_TAG_TOUCH_Y; + image.flags &= ~FT_OUTLINE_OWNER; + nativeImage.flags &= ~FT_OUTLINE_OWNER; + glyph->format = static_cast(FT_GLYPH_FORMAT_NONE); + native->format = FT_GLYPH_FORMAT_NONE; + glyph->advance = {-1234567, 987654}; + native->advance = {-1234567, 987654}; + GuestGlyphRec* copy = nullptr; + FT_Glyph nativeCopy = nullptr; + Require(FT_Glyph_Copy_nid_postfix(glyph, ©) == FT_Glyph_Copy(native, &nativeCopy)); + Matches(copy, nativeCopy, fixture.library, false); + auto& copiedImage = reinterpret_cast(copy)->outline; + Require(copiedImage.points != image.points && copiedImage.tags != image.tags && copiedImage.contours != image.contours); + Require((copiedImage.flags & FT_OUTLINE_OWNER) != 0); + image.points[0].x += 100; + image.tags[0] ^= FT_CURVE_TAG_TOUCH_Y; + image.contours[0] = 0; + Matches(copy, nativeCopy, fixture.library, false); + image.flags |= FT_OUTLINE_OWNER; + nativeImage.flags |= FT_OUTLINE_OWNER; + FT_Done_Glyph_nid_postfix(glyph); + FT_Done_Glyph(native); + Matches(copy, nativeCopy, fixture.library, false); + auto* original = copy; + const FT_Glyph originalNative = nativeCopy; + Require(FT_Glyph_Copy_nid_postfix(copy, ©) == FT_Glyph_Copy(nativeCopy, &nativeCopy)); + Require(copy != original && nativeCopy != originalNative); + FT_Done_Glyph_nid_postfix(original); + FT_Done_Glyph(originalNative); + Matches(copy, nativeCopy, fixture.library, false); + FT_Done_Glyph_nid_postfix(copy); + FT_Done_Glyph(nativeCopy); +} + +static void BitmapOwnership() { + Fixture fixture; + fixture.Load(1, FT_LOAD_RENDER); + auto* slot = fixture.face->glyph; + const auto hostSlot = fixture.host->glyph; + slot->bitmap_left = hostSlot->bitmap_left = -7; + slot->bitmap_top = hostSlot->bitmap_top = 19; + slot->advance = {-27, 53}; + hostSlot->advance = {-27, 53}; + GuestGlyphRec* glyph = nullptr; + FT_Glyph native = nullptr; + Require(FT_Get_Glyph_nid_postfix(slot, &glyph) == FT_Get_Glyph(hostSlot, &native)); + auto& image = *reinterpret_cast(glyph); + const auto nativeImage = reinterpret_cast(native); + Require((image.bitmap.buffer == slot->bitmap.buffer) == (nativeImage->bitmap.buffer == hostSlot->bitmap.buffer)); + Matches(glyph, native, fixture.library, true); + GuestGlyphRec* second = nullptr; + FT_Glyph nativeSecond = nullptr; + Require(FT_Get_Glyph_nid_postfix(slot, &second) == FT_Get_Glyph(hostSlot, &nativeSecond)); + Require(reinterpret_cast(second)->bitmap.buffer != image.bitmap.buffer); + Require(reinterpret_cast(nativeSecond)->bitmap.buffer != nativeImage->bitmap.buffer); + Matches(second, nativeSecond, fixture.library, true); + fixture.Load(0, FT_LOAD_RENDER); + fixture.CloseFaces(); + Matches(glyph, native, fixture.library, true); + FT_Done_Glyph_nid_postfix(second); + FT_Done_Glyph(nativeSecond); + for (const auto mode : {FT_PIXEL_MODE_MONO, FT_PIXEL_MODE_GRAY2, FT_PIXEL_MODE_GRAY4, FT_PIXEL_MODE_GRAY, FT_PIXEL_MODE_LCD, FT_PIXEL_MODE_LCD_V, FT_PIXEL_MODE_BGRA}) { + for (const int pitch : {-24, 24}) { + image.left = nativeImage->left = -13; + image.top = nativeImage->top = 17; + image.bitmap.rows = nativeImage->bitmap.rows = 3; + image.bitmap.width = nativeImage->bitmap.width = 5; + image.bitmap.pitch = nativeImage->bitmap.pitch = pitch; + image.bitmap.pixel_mode = nativeImage->bitmap.pixel_mode = static_cast(mode); + GuestGlyphRec* copy = nullptr; + FT_Glyph nativeCopy = nullptr; + Require(FT_Glyph_Copy_nid_postfix(glyph, ©) == FT_Glyph_Copy(native, &nativeCopy)); + Matches(copy, nativeCopy, fixture.library, true); + const auto buffer = reinterpret_cast(copy)->bitmap.buffer; + Require(buffer != image.bitmap.buffer); + const auto oldPixel = buffer[0]; + image.bitmap.buffer[0] ^= 0xA5; + nativeImage->bitmap.buffer[0] ^= 0xA5; + Require(buffer[0] == oldPixel); + Matches(copy, nativeCopy, fixture.library, true); + FT_Done_Glyph_nid_postfix(copy); + FT_Done_Glyph(nativeCopy); + } + } + const auto savedBuffer = image.bitmap.buffer; + const auto savedNativeBuffer = nativeImage->bitmap.buffer; + image.bitmap.buffer = nullptr; + nativeImage->bitmap.buffer = nullptr; + GuestGlyphRec* empty = nullptr; + FT_Glyph nativeEmpty = nullptr; + Require(FT_Glyph_Copy_nid_postfix(glyph, &empty) == FT_Glyph_Copy(native, &nativeEmpty)); + Matches(empty, nativeEmpty, fixture.library, true); + FT_Done_Glyph_nid_postfix(empty); + FT_Done_Glyph(nativeEmpty); + image.bitmap.buffer = savedBuffer; + nativeImage->bitmap.buffer = savedNativeBuffer; + GuestGlyphRec* retained = nullptr; + FT_Glyph nativeRetained = nullptr; + Require(FT_Glyph_Copy_nid_postfix(glyph, &retained) == FT_Glyph_Copy(native, &nativeRetained)); + FT_Done_Glyph_nid_postfix(glyph); + FT_Done_Glyph(native); + Matches(retained, nativeRetained, fixture.library, true); + FT_Done_Glyph_nid_postfix(retained); + FT_Done_Glyph(nativeRetained); +} + +static void WideOutlineCoordinates() { + Fixture fixture; + fixture.Load(1, FT_LOAD_NO_HINTING); + const std::array expected{{ + {std::numeric_limits::min(), std::numeric_limits::max()}, + {std::numeric_limits::max(), std::numeric_limits::min()}, + {std::int64_t{1} << 40, -(std::int64_t{1} << 40)}, + {-2147483649LL, 2147483648LL}, + }}; + auto& slotOutline = fixture.face->glyph->outline; + Require(slotOutline.n_points == expected.size()); + for (std::size_t index = 0; index < expected.size(); ++index) { + slotOutline.points[index] = expected[index]; + fixture.host->glyph->outline.points[index] = {static_cast(expected[index].x), static_cast(expected[index].y)}; + } + GuestGlyphRec* glyph = nullptr; + FT_Glyph native = nullptr; + Require(FT_Get_Glyph_nid_postfix(fixture.face->glyph, &glyph) == FT_Get_Glyph(fixture.host->glyph, &native)); + auto& outline = reinterpret_cast(glyph)->outline; + Require(outline.n_points == expected.size() && outline.points != slotOutline.points); + for (std::size_t index = 0; index < expected.size(); ++index) { + Require(outline.points[index].x == expected[index].x && outline.points[index].y == expected[index].y); + } + if constexpr (sizeof(FT_Pos) == sizeof(std::int64_t)) Matches(glyph, native, fixture.library, false); + fixture.Load(0, FT_LOAD_NO_HINTING); + fixture.CloseFaces(); + GuestGlyphRec* copy = nullptr; + FT_Glyph nativeCopy = nullptr; + Require(FT_Glyph_Copy_nid_postfix(glyph, ©) == FT_Glyph_Copy(native, &nativeCopy)); + const auto& copiedOutline = reinterpret_cast(copy)->outline; + Require(copiedOutline.points != outline.points && copiedOutline.n_points == expected.size()); + outline.points[0] = {}; + FT_Done_Glyph_nid_postfix(glyph); + FT_Done_Glyph(native); + for (std::size_t index = 0; index < expected.size(); ++index) { + Require(copiedOutline.points[index].x == expected[index].x && copiedOutline.points[index].y == expected[index].y); + } + if constexpr (sizeof(FT_Pos) == sizeof(std::int64_t)) Matches(copy, nativeCopy, fixture.library, false); + FT_Done_Glyph_nid_postfix(copy); + FT_Done_Glyph(nativeCopy); +} + +static void ErrorsAndEmptyGlyphs() { + Fixture fixture; + fixture.Load(1, FT_LOAD_NO_HINTING); + auto* slot = fixture.face->glyph; + const auto hostSlot = fixture.host->glyph; + GuestGlyphRec sentinel{}; + FT_GlyphRec nativeSentinel{}; + GuestGlyphRec* glyph = &sentinel; + FT_Glyph native = &nativeSentinel; + Require(FT_Get_Glyph_nid_postfix(nullptr, &glyph) == FT_Get_Glyph(nullptr, &native)); + Require(glyph == &sentinel && native == &nativeSentinel); + Require(FT_Get_Glyph_nid_postfix(nullptr, nullptr) == FT_Get_Glyph(nullptr, nullptr)); + Require(FT_Get_Glyph_nid_postfix(slot, nullptr) == FT_Get_Glyph(hostSlot, nullptr)); + for (const auto format : {FT_GLYPH_FORMAT_NONE, FT_GLYPH_FORMAT_COMPOSITE, static_cast(0x12345678)}) { + slot->format = static_cast(format); + hostSlot->format = format; + Require(FT_Get_Glyph_nid_postfix(slot, &glyph) == FT_Get_Glyph(hostSlot, &native)); + Require(glyph == &sentinel && native == &nativeSentinel); + } + slot->format = static_cast(FT_GLYPH_FORMAT_OUTLINE); + hostSlot->format = FT_GLYPH_FORMAT_OUTLINE; + const auto library = slot->library; + const auto hostLibrary = hostSlot->library; + slot->library = nullptr; + hostSlot->library = nullptr; + Require(FT_Get_Glyph_nid_postfix(slot, &glyph) == FT_Get_Glyph(hostSlot, &native)); + Require(glyph == &sentinel && native == &nativeSentinel); + slot->library = library; + hostSlot->library = hostLibrary; + for (const std::int64_t value : std::array{-0x200000, 0x200000, -0x200001, 0x200001, std::numeric_limits::min(), std::numeric_limits::max()}) { + for (const bool vertical : {false, true}) { + slot->advance = vertical ? GuestVector{0, value} : GuestVector{value, 0}; + const auto bounded = static_cast(value < 0 ? -0x200000 : 0x200000); + hostSlot->advance = vertical ? FT_Vector{0, bounded} : FT_Vector{bounded, 0}; + glyph = &sentinel; + native = &nativeSentinel; + Require(FT_Get_Glyph_nid_postfix(slot, &glyph) == FT_Get_Glyph(hostSlot, &native)); + Require(glyph == nullptr && native == nullptr); + } + } + for (const std::int64_t value : {-0x1FFFFF, 0x1FFFFF}) { + slot->advance = {value, -value}; + hostSlot->advance = {static_cast(value), static_cast(-value)}; + Require(FT_Get_Glyph_nid_postfix(slot, &glyph) == FT_Get_Glyph(hostSlot, &native)); + Matches(glyph, native, fixture.library, false); + FT_Done_Glyph_nid_postfix(glyph); + FT_Done_Glyph(native); + } + slot->advance = {}; + hostSlot->advance = {}; + const auto savedOutline = slot->outline; + const auto savedNativeOutline = hostSlot->outline; + slot->outline = {1, 0, nullptr, nullptr, nullptr, 0}; + hostSlot->outline = {1, 0, nullptr, nullptr, nullptr, 0}; + glyph = &sentinel; + native = &nativeSentinel; + Require(FT_Get_Glyph_nid_postfix(slot, &glyph) == FT_Get_Glyph(hostSlot, &native)); + Require(glyph == nullptr && native == nullptr); + slot->outline = {}; + hostSlot->outline = {}; + Require(FT_Get_Glyph_nid_postfix(slot, &glyph) == FT_Get_Glyph(hostSlot, &native)); + Matches(glyph, native, fixture.library, false); + GuestGlyphRec* copy = &sentinel; + FT_Glyph nativeCopy = &nativeSentinel; + Require(FT_Glyph_Copy_nid_postfix(nullptr, ©) == FT_Glyph_Copy(nullptr, &nativeCopy)); + Require(copy == &sentinel && nativeCopy == &nativeSentinel); + Require(FT_Glyph_Copy_nid_postfix(glyph, nullptr) == FT_Glyph_Copy(native, nullptr)); + const auto clazz = glyph->clazz; + const auto nativeClazz = native->clazz; + glyph->clazz = nullptr; + native->clazz = nullptr; + Require(FT_Glyph_Copy_nid_postfix(glyph, ©) == FT_Glyph_Copy(native, &nativeCopy)); + Require(copy == &sentinel && nativeCopy == &nativeSentinel); + glyph->clazz = clazz; + native->clazz = nativeClazz; + Require(FT_Glyph_Copy_nid_postfix(glyph, ©) == FT_Glyph_Copy(native, &nativeCopy)); + Matches(copy, nativeCopy, fixture.library, false); + FT_Done_Glyph_nid_postfix(copy); + FT_Done_Glyph(nativeCopy); + auto& image = reinterpret_cast(glyph)->outline; + auto& nativeImage = reinterpret_cast(native)->outline; + image.n_contours = nativeImage.n_contours = 1; + copy = &sentinel; + nativeCopy = &nativeSentinel; + Require(FT_Glyph_Copy_nid_postfix(glyph, ©) == FT_Glyph_Copy(native, &nativeCopy)); + Require(copy == nullptr && nativeCopy == nullptr); + image.n_contours = nativeImage.n_contours = 0; + glyph->advance = {std::numeric_limits::min(), std::numeric_limits::max()}; + Require(FT_Glyph_Copy_nid_postfix(glyph, ©) == 0); + Require(copy->advance.x == glyph->advance.x && copy->advance.y == glyph->advance.y); + FT_Done_Glyph_nid_postfix(copy); + FT_Done_Glyph_nid_postfix(glyph); + FT_Done_Glyph(native); + slot->outline = savedOutline; + hostSlot->outline = savedNativeOutline; + FT_Done_Glyph_nid_postfix(nullptr); + FT_Done_Glyph(nullptr); +} + +static void OutlineCountLimits() { + Fixture fixture; + fixture.Load(1, FT_LOAD_NO_HINTING); + const auto pointCount = std::min(std::numeric_limits::max(), std::numeric_limits::max()); + std::vector points(pointCount); + std::vector nativePoints(pointCount); + std::vector tags(pointCount, 0xA5); + std::vector> nativeTags(pointCount, static_cast>(0xA5)); + std::uint16_t contour = static_cast(pointCount - 1); + auto nativeContour = static_cast>(contour); + for (std::size_t index = 0; index < pointCount; ++index) { + points[index] = {static_cast(index), -static_cast(index)}; + nativePoints[index] = {static_cast(index), -static_cast(index)}; + } + auto* slot = fixture.face->glyph; + const auto nativeSlot = fixture.host->glyph; + const auto savedOutline = slot->outline; + const auto savedNativeOutline = nativeSlot->outline; + slot->outline = {1, static_cast(pointCount), points.data(), tags.data(), &contour, 0}; + nativeSlot->outline = {1, static_cast(pointCount), nativePoints.data(), nativeTags.data(), &nativeContour, 0}; + GuestGlyphRec* glyph = nullptr; + FT_Glyph native = nullptr; + Require(FT_Get_Glyph_nid_postfix(slot, &glyph) == FT_Get_Glyph(nativeSlot, &native)); + Matches(glyph, native, fixture.library, false); + slot->outline = savedOutline; + nativeSlot->outline = savedNativeOutline; + reinterpret_cast(glyph)->outline.contours[0] = 0xFFFF; + reinterpret_cast(native)->outline.contours[0] = static_cast(0xFFFF); + GuestGlyphRec* copy = nullptr; + FT_Glyph nativeCopy = nullptr; + Require(FT_Glyph_Copy_nid_postfix(glyph, ©) == FT_Glyph_Copy(native, &nativeCopy)); + Matches(copy, nativeCopy, fixture.library, false); + FT_Done_Glyph_nid_postfix(copy); + FT_Done_Glyph(nativeCopy); + if (pointCount < std::numeric_limits::max()) { + slot->outline = {0, static_cast(pointCount + 1), nullptr, nullptr, nullptr, 0}; + Require(Throws([&] { FT_Get_Glyph_nid_postfix(slot, ©); }, "FT_Get_Glyph")); + slot->outline = {static_cast(pointCount + 1), 0, nullptr, nullptr, nullptr, 0}; + Require(Throws([&] { FT_Get_Glyph_nid_postfix(slot, ©); }, "FT_Get_Glyph")); + slot->outline = savedOutline; + auto& outline = reinterpret_cast(glyph)->outline; + const auto saved = outline; + outline.n_points = static_cast(pointCount + 1); + Require(Throws([&] { FT_Glyph_Copy_nid_postfix(glyph, ©); }, "FT_Glyph_Copy")); + outline = saved; + outline.n_contours = static_cast(pointCount + 1); + Require(Throws([&] { FT_Glyph_Copy_nid_postfix(glyph, ©); }, "FT_Glyph_Copy")); + outline = saved; + } + FT_Done_Glyph_nid_postfix(glyph); + FT_Done_Glyph(native); +} + +static void UnsupportedChanges() { + Fixture fixture; + fixture.Load(1, FT_LOAD_NO_HINTING); + GuestGlyphRec* glyph = nullptr; + Require(FT_Get_Glyph_nid_postfix(fixture.face->glyph, &glyph) == 0); + auto& outline = reinterpret_cast(glyph)->outline; + const auto points = outline.points; + std::array replacement{}; + outline.points = replacement.data(); + Require(Throws([&] { FT_Done_Glyph_nid_postfix(glyph); }, "FT_Done_Glyph")); + outline.points = points; + outline.flags ^= FT_OUTLINE_OWNER; + Require(Throws([&] { FT_Done_Glyph_nid_postfix(glyph); }, "FT_Done_Glyph")); + outline.flags ^= FT_OUTLINE_OWNER; + const auto library = glyph->library; + glyph->library = nullptr; + GuestGlyphRec* copy = nullptr; + Require(Throws([&] { FT_Glyph_Copy_nid_postfix(glyph, ©); }, "FT_Glyph_Copy")); + glyph->library = library; + const auto clazz = glyph->clazz; + const unsigned char foreignClass = 0; + glyph->clazz = &foreignClass; + Require(Throws([&] { FT_Glyph_Copy_nid_postfix(glyph, ©); }, "FT_Glyph_Copy")); + glyph->clazz = clazz; + FT_Done_Glyph_nid_postfix(glyph); + fixture.Load(1, FT_LOAD_RENDER); + const auto slotBuffer = fixture.face->glyph->bitmap.buffer; + fixture.face->glyph->bitmap.buffer = nullptr; + Require(Throws([&] { FT_Get_Glyph_nid_postfix(fixture.face->glyph, ©); }, "FT_Get_Glyph")); + fixture.face->glyph->bitmap.buffer = slotBuffer; + Require(FT_Get_Glyph_nid_postfix(fixture.face->glyph, &glyph) == 0); + auto& bitmap = reinterpret_cast(glyph)->bitmap; + const auto buffer = bitmap.buffer; + bitmap.buffer = nullptr; + Require(Throws([&] { FT_Done_Glyph_nid_postfix(glyph); }, "FT_Done_Glyph")); + bitmap.buffer = buffer; + FT_Done_Glyph_nid_postfix(glyph); +} + +int main() { + OutlineOwnership(); + BitmapOwnership(); + WideOutlineCoordinates(); + ErrorsAndEmptyGlyphs(); + OutlineCountLimits(); + UnsupportedChanges(); + std::puts("FreeType glyph ownership and reference comparisons passed"); +} diff --git a/core/libs/tests/GuestFreeTypeMemoryRecord.cpp b/core/libs/tests/GuestFreeTypeMemoryRecord.cpp new file mode 100644 index 0000000000..d862f2fe3a --- /dev/null +++ b/core/libs/tests/GuestFreeTypeMemoryRecord.cpp @@ -0,0 +1,78 @@ +#include "prx/libSceFreeType/include/MemoryRecord.hpp" +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +static void Require(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "FreeType memory record check failed at line %d\n", line); + std::exit(EXIT_FAILURE); + } +} +#define Check(value) Require((value), __LINE__) + +int main(int count, char** arguments) { + Check(count == 3); + const bool patched = std::strcmp(arguments[2], "patched") == 0; + Check(patched || std::strcmp(arguments[2], "unpatched") == 0); + const char* const newSymbol = patched ? "tCMhTh7h8D4" : "FT_New_Memory_nid_postfix"; + const char* const doneSymbol = patched ? "UTumVjeIQtg" : "FT_Done_Memory_nid_postfix"; +#ifdef _WIN32 + const auto module = LoadLibraryW(std::filesystem::path(arguments[1]).c_str()); + Check(module != nullptr); + const auto newAddress = GetProcAddress(module, newSymbol); + const auto doneAddress = GetProcAddress(module, doneSymbol); +#else + const auto module = dlopen(arguments[1], RTLD_NOW | RTLD_LOCAL); + if (module == nullptr) std::fprintf(stderr, "%s\n", dlerror()); + Check(module != nullptr); + const auto newAddress = dlsym(module, newSymbol); + const auto doneAddress = dlsym(module, doneSymbol); +#endif + if (newAddress == nullptr) std::fprintf(stderr, "Missing FreeType entry: %s\n", newSymbol); + if (doneAddress == nullptr) std::fprintf(stderr, "Missing FreeType entry: %s\n", doneSymbol); + Check(newAddress != nullptr); + Check(doneAddress != nullptr); + const auto create = std::bit_cast(newAddress); + const auto destroy = std::bit_cast(doneAddress); + + FreeType::GuestMemoryRec* memory = create(); + Check(memory != nullptr); + Check(memory->user == nullptr); + Check(memory->alloc != nullptr); + Check(memory->free != nullptr); + Check(memory->realloc != nullptr); + + unsigned char* block = static_cast(memory->alloc(memory, 64)); + Check(block != nullptr); + for (int index = 0; index < 64; ++index) block[index] = static_cast(index); + block = static_cast(memory->realloc(memory, 64, 128, block)); + Check(block != nullptr); + for (int index = 0; index < 64; ++index) Check(block[index] == static_cast(index)); + memory->free(memory, block); + + destroy(memory); + destroy(nullptr); + + for (int index = 0; index < 256; ++index) { + FreeType::GuestMemoryRec* record = create(); + Check(record != nullptr); + void* probe = record->alloc(record, 1024); + Check(probe != nullptr); + record->free(record, probe); + destroy(record); + } + +#ifdef _WIN32 + Check(FreeLibrary(module) != 0); +#else + Check(dlclose(module) == 0); +#endif +} diff --git a/core/libs/tests/GuestFreeTypeOutlineStorage.cpp b/core/libs/tests/GuestFreeTypeOutlineStorage.cpp new file mode 100644 index 0000000000..d1acca1b1e --- /dev/null +++ b/core/libs/tests/GuestFreeTypeOutlineStorage.cpp @@ -0,0 +1,67 @@ +#include "prx/libSceFreeType/include/Api.hpp" +#include "prx/libSceFreeType/include/GuestObjects.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" + +#include FT_OUTLINE_H + +#include +#include +#include + +using namespace FreeType; + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "FreeType outline storage check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static void Compare(GuestLibrary& library, FT_Library native, std::uint32_t points, std::int32_t contours) { + GuestOutline guest{1, 2, nullptr, nullptr, nullptr, 3}; + FT_Outline expected{1, 2, nullptr, nullptr, nullptr, 3}; + const int guestError = FT_Outline_New_nid_postfix(&library, points, contours, &guest); + const int nativeError = FT_Outline_New(native, points, contours, &expected); + Require(guestError == nativeError); + Require(guest.n_points == expected.n_points && guest.n_contours == expected.n_contours && guest.flags == expected.flags); + Require((guest.points == nullptr) == (expected.points == nullptr)); + Require((guest.tags == nullptr) == (expected.tags == nullptr)); + Require((guest.contours == nullptr) == (expected.contours == nullptr)); + for (std::uint32_t index = 0; nativeError == 0 && index < points; ++index) { + Require(guest.points[index].x == 0 && guest.points[index].y == 0 && guest.tags[index] == 0); + } + for (std::int32_t index = 0; nativeError == 0 && index < contours; ++index) Require(guest.contours[index] == 0); + Require(FT_Outline_Done_nid_postfix(&library, &guest) == FT_Outline_Done(native, &expected)); + Require(guest.n_points == 0 && guest.n_contours == 0 && guest.flags == 0); + Require(!guest.points && !guest.tags && !guest.contours); +} + +int main() { + GuestLibrary library{}; + FT_Library native = nullptr; + Require(FT_Init_FreeType(&library.host) == 0 && FT_Init_FreeType(&native) == 0); + for (const std::uint32_t points : {0u, 1u, 7u, 65535u, 65536u, 0xFFFFFFFFu}) { + for (const std::int32_t contours : {-1, 0, 1, 3, 7, 8, 65535}) Compare(library, native, points, contours); + } + GuestOutline outline{}; + Require(FT_Outline_New_nid_postfix(nullptr, 3, 1, &outline) == FT_Err_Invalid_Library_Handle); + Require(FT_Outline_New_nid_postfix(&library, 3, 1, nullptr) == FT_Err_Invalid_Argument); + Require(FT_Outline_Done_nid_postfix(nullptr, &outline) == FT_Err_Invalid_Library_Handle); + Require(FT_Outline_Done_nid_postfix(&library, nullptr) == FT_Err_Invalid_Outline); + + GuestLibrary uninitialized{}; + GuestOutline untouched{1, 2, nullptr, nullptr, nullptr, 3}; + Require(FT_Outline_New_nid_postfix(&uninitialized, 3, 1, &untouched) == FT_Err_Invalid_Argument); + Require(untouched.n_points == 2 && untouched.n_contours == 1 && untouched.flags == 3); + Require(FT_Outline_Done_nid_postfix(&uninitialized, &untouched) == FT_Err_Invalid_Argument); + Require(untouched.n_points == 2 && untouched.n_contours == 1 && untouched.flags == 3); + + GuestVector borrowedPoints[2]{}; + unsigned char borrowedTags[2]{}; + std::uint16_t borrowedContours[1]{1}; + GuestOutline borrowed{1, 2, borrowedPoints, borrowedTags, borrowedContours, 0}; + Require(FT_Outline_Done_nid_postfix(&library, &borrowed) == 0); + Require(borrowed.n_points == 0 && !borrowed.points && borrowedContours[0] == 1); + Require(FT_Done_FreeType(library.host) == 0 && FT_Done_FreeType(native) == 0); +} diff --git a/core/libs/tests/GuestFreebsdClocks.cpp b/core/libs/tests/GuestFreebsdClocks.cpp index 958fee2b22..2331ab57b2 100644 --- a/core/libs/tests/GuestFreebsdClocks.cpp +++ b/core/libs/tests/GuestFreebsdClocks.cpp @@ -17,7 +17,13 @@ static constexpr int GUEST_CLOCK_REALTIME = 0; static constexpr int GUEST_CLOCK_VIRTUAL = 1; static constexpr int GUEST_CLOCK_PROF = 2; static constexpr int GUEST_CLOCK_MONOTONIC = 4; +static constexpr int GUEST_CLOCK_UPTIME = 5; +static constexpr int GUEST_CLOCK_UPTIME_PRECISE = 7; +static constexpr int GUEST_CLOCK_UPTIME_FAST = 8; static constexpr int GUEST_CLOCK_REALTIME_PRECISE = 9; +static constexpr int GUEST_CLOCK_REALTIME_FAST = 10; +static constexpr int GUEST_CLOCK_MONOTONIC_PRECISE = 11; +static constexpr int GUEST_CLOCK_MONOTONIC_FAST = 12; static constexpr int GUEST_CLOCK_SECOND = 13; static constexpr int GUEST_CLOCK_THREAD_CPUTIME_ID = 14; static constexpr int GUEST_CLOCK_PROCESS_CPUTIME_ID = 15; @@ -85,6 +91,18 @@ static void OmittedTimeIsFaultAfterClockValidation() { Require(rejected); } +static void InvalidClockIdFailsGettime() { + KernelTimespec time{-1, -1}; + for (const int clockId : {-1, 999}) { + *__error_nid_postfix() = 0; + Require(clock_gettime_nid_postfix(clockId, &time) == -1 && *__error_nid_postfix() == 22); + Require(time.tv_sec == -1 && time.tv_nsec == -1); + *__error_nid_postfix() = 0; + Require(sceKernelClockGettime(clockId, &time) == static_cast(0x80020016u)); + Require(time.tv_sec == -1 && time.tv_nsec == -1); + } +} + static KernelTimespec Read(int clockId) { KernelTimespec time{-1, -1}; Require(clock_gettime_nid_postfix(clockId, &time) == SCE_OK); @@ -118,6 +136,13 @@ static void RequireNeverDecreases(int clockId) { } } +static void WallAndUptimeClocksReportSubsecondResolutions() { + const int clocks[] = {GUEST_CLOCK_REALTIME, GUEST_CLOCK_REALTIME_PRECISE, GUEST_CLOCK_REALTIME_FAST, GUEST_CLOCK_MONOTONIC, + GUEST_CLOCK_MONOTONIC_PRECISE, GUEST_CLOCK_MONOTONIC_FAST, GUEST_CLOCK_UPTIME, GUEST_CLOCK_UPTIME_PRECISE, + GUEST_CLOCK_UPTIME_FAST}; + for (const int clockId : clocks) ResolutionNanos(clockId); +} + static void SecondClockReportsWholeSeconds() { KernelTimespec resolution{-1, -1}; Require(clock_getres_nid_postfix(GUEST_CLOCK_SECOND, &resolution) == SCE_OK); @@ -225,6 +250,15 @@ static void CalendarConversionsInitializeTheirState() { } Require(sceKernelConvertLocaltimeToUtc(0, 0, nullptr, nullptr, nullptr) == SCE_OK); Require(sceKernelConvertUtcToLocaltime(0, nullptr, nullptr, nullptr) == SCE_OK); + auto* const badTime = reinterpret_cast(static_cast(0x10)); + auto* const badState = reinterpret_cast(static_cast(0x10)); + auto* const badDst = reinterpret_cast(static_cast(0x10)); + Require(sceKernelConvertLocaltimeToUtc(0, 0, badTime, nullptr, nullptr) == static_cast(0x8002000eu)); + Require(sceKernelConvertLocaltimeToUtc(0, 0, nullptr, badState, nullptr) == static_cast(0x8002000eu)); + Require(sceKernelConvertLocaltimeToUtc(0, 0, nullptr, nullptr, badDst) == static_cast(0x8002000eu)); + Require(sceKernelConvertUtcToLocaltime(0, badTime, nullptr, nullptr) == static_cast(0x8002000eu)); + Require(sceKernelConvertUtcToLocaltime(0, nullptr, badState, nullptr) == static_cast(0x8002000eu)); + Require(sceKernelConvertUtcToLocaltime(0, nullptr, nullptr, badDst) == static_cast(0x8002000eu)); } static void RequireSettimeError(int clockId, const KernelTimespec* time, int error) { @@ -232,16 +266,6 @@ static void RequireSettimeError(int clockId, const KernelTimespec* time, int err Require(clock_settime_nid_postfix(clockId, time) == -1 && *__error_nid_postfix() == error); } -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; - } - return false; -} - static void SettimeValidatesLikeFreebsd() { const KernelTimespec valid{1767225600, 5}; RequireSettimeError(GUEST_CLOCK_REALTIME, nullptr, 14); @@ -259,9 +283,6 @@ static void SettimeValidatesLikeFreebsd() { RequireSettimeError(GUEST_CLOCK_REALTIME, &wholeSecondOfNanoseconds, 22); RequireSettimeError(GUEST_CLOCK_REALTIME, &negativeSeconds, 22); RequireSettimeError(GUEST_CLOCK_MONOTONIC, &negativeSeconds, 22); - Require(SettimeThrows(valid, "1767225600.000000005")); - Require(SettimeThrows(KernelTimespec{0, 0}, "0.000000000")); - Require(SettimeThrows(KernelTimespec{1, NANOS_PER_SECOND - 1}, "1.999999999")); } static std::int32_t RuntimeDaylight(std::int32_t daylight) { @@ -333,11 +354,13 @@ int main() { KernelTimezoneIsUtc(); UnsupportedResolutionStillThrows(); OmittedTimeIsFaultAfterClockValidation(); + InvalidClockIdFailsGettime(); SettimeValidatesLikeFreebsd(); CalendarConversionsInitializeTheirState(); CalendarConversionsFollowTheHostZone(); TimezoneReportsTheStandardOffsetAndDaylightAmount(); SecondClockReportsWholeSeconds(); + WallAndUptimeClocksReportSubsecondResolutions(); KernelTimezone zone{-1, -1}; Require(gettimeofday_nid_postfix(nullptr, nullptr) == SCE_OK); Require(gettimeofday_nid_postfix(nullptr, &zone) == SCE_OK && zone.tz_minuteswest == 0 && zone.tz_dsttime == 0); diff --git a/core/libs/tests/GuestFstatvfs.cpp b/core/libs/tests/GuestFstatvfs.cpp index 9137793fbb..6f53575826 100644 --- a/core/libs/tests/GuestFstatvfs.cpp +++ b/core/libs/tests/GuestFstatvfs.cpp @@ -31,6 +31,9 @@ static void RequireFailure(int descriptor, int error) { Require(fstatvfs_nid_postfix(descriptor, reinterpret_cast(output.data())) == -1); Require(*__error_nid_postfix() == error); Require(output == saved); + *__error_nid_postfix() = 0; + Require(fstatvfs_nid_postfix(descriptor, nullptr) == -1); + Require(*__error_nid_postfix() == error); } static void RequireVolume(int descriptor, const std::filesystem::path& path) { @@ -50,6 +53,18 @@ static void RequireVolume(int descriptor, const std::filesystem::path& path) { Require(fields[9] == 0 && fields[10] >= 255); } +static void RequireDevice(int descriptor) { + std::array guarded; + guarded.fill(0xA5A5A5A5A5A5A5A5ull); + *__error_nid_postfix() = 12345; + 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; + const std::array expected{0, 0, 2, 0, 0, 0, 512, 0, 512, 0, 255}; + Require(std::memcmp(fields, expected.data(), sizeof(expected)) == 0); +} + int main() { const auto root = std::filesystem::temp_directory_path() / ("anyps5-fstatvfs-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); @@ -71,20 +86,46 @@ int main() { const int directory = sceKernelOpen("/anyps5-fstatvfs-test", SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); Require(directory >= 0); RequireVolume(directory, root); + Require(fstatvfs_nid_postfix(directory, nullptr) == -1 && *__error_nid_postfix() == 14); Require(close_nid_postfix(directory) == 0); + RequireFailure(directory, 9); + + const int random = sceKernelOpen("/dev/urandom", SCE_KERNEL_O_RDONLY, 0); + Require(random >= 0); + RequireDevice(random); + Require(fstatvfs_nid_postfix(random, nullptr) == -1 && *__error_nid_postfix() == 14); + Require(close_nid_postfix(random) == 0); + RequireFailure(random, 9); + + const int queue = kqueue_nid_postfix(); + Require(queue >= 0); + RequireFailure(queue, 22); + Require(close_nid_postfix(queue) == 0); + RequireFailure(queue, 9); int pipes[2]; Require(pipe_nid_postfix(pipes) == 0); RequireFailure(pipes[0], 22); RequireFailure(pipes[1], 22); for (int fd : pipes) Require(close_nid_postfix(fd) == 0); + RequireFailure(pipes[0], 9); + RequireFailure(pipes[1], 9); + int sockets[2]; Require(socketpair_nid_postfix(1, 1, 0, sockets) == 0); RequireFailure(sockets[0], 22); + RequireFailure(sockets[1], 22); for (int fd : sockets) Require(close_nid_postfix(fd) == 0); RequireFailure(sockets[0], 9); + RequireFailure(sockets[1], 9); + RequireFailure(-1, 9); + RequireFailure(-100, 9); + RequireFailure(1024, 9); + Require(close_nid_postfix(file) == 0); + RequireFailure(file, 9); + RemovePathAlias_nid_no_patch("/anyps5-fstatvfs-test"); Require(std::filesystem::remove(filePath)); Require(std::filesystem::remove(root)); diff --git a/core/libs/tests/GuestFwide.cpp b/core/libs/tests/GuestFwide.cpp new file mode 100644 index 0000000000..9c98ea5cc8 --- /dev/null +++ b/core/libs/tests/GuestFwide.cpp @@ -0,0 +1,231 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace { +using Open = void* (APS5_VABI*)(const char*, const char*); +using Close = int (APS5_VABI*)(void*); +using Wide = int (APS5_VABI*)(void*, int); +using PutByte = int (APS5_VABI*)(int, void*); +using PutWide = int (APS5_VABI*)(char16_t, void*); +using Seek = int (APS5_VABI*)(void*, std::int64_t, int); +using Reopen = void* (APS5_VABI*)(const char*, const char*, void*); +using Read = std::size_t (APS5_VABI*)(void*, std::size_t, std::size_t, void*); +using Write = std::size_t (APS5_VABI*)(const void*, std::size_t, std::size_t, void*); +using Flush = int (APS5_VABI*)(void*); +using Descriptor = int (APS5_VABI*)(void*); +using GetByte = int (APS5_VABI*)(void*); +using GetWide = std::int32_t (APS5_VABI*)(void*); +using UngetWide = std::int32_t (APS5_VABI*)(std::int32_t, void*); +using GetLine = char* (APS5_VABI*)(char*, int, void*); +using GetWideLine = char16_t* (APS5_VABI*)(char16_t*, int, void*); +using ScanList = int (APS5_VABI*)(void*, const char*, void*); +using WideList = int (APS5_VABI*)(void*, const char16_t*, void*); + +int APS5_VABI ScanThrough(ScanList scan, void* 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 = scan(stream, format, reinterpret_cast(args)); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} + +int APS5_VABI WideThrough(WideList print, void* stream, const char16_t* 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 = print(stream, format, reinterpret_cast(args)); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} + +void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +template +Function Symbol( +#ifdef _WIN32 + HMODULE library, +#else + void* library, +#endif + const char* name) { +#ifdef _WIN32 + return reinterpret_cast(GetProcAddress(library, name)); +#else + return reinterpret_cast(dlsym(library, name)); +#endif +} +} + +int main(int argc, char** argv) { + Require(argc == 3, "expected libc path and scratch filename"); +#ifdef _WIN32 + const auto library = LoadLibraryA(argv[1]); +#else + const auto library = dlopen(argv[1], RTLD_NOW | RTLD_LOCAL); +#endif + Require(library != nullptr, "could not load libc"); + const auto open = Symbol(library, "fopen_nid_postfix"); + const auto close = Symbol(library, "fclose_nid_postfix"); + const auto wide = Symbol(library, "fwide_nid_postfix"); + const auto putByte = Symbol(library, "fputc_nid_postfix"); + const auto putWide = Symbol(library, "fputwc_nid_postfix"); + const auto seek = Symbol(library, "fseek_nid_postfix"); + const auto read = Symbol(library, "fread_nid_postfix"); + const auto write = Symbol(library, "fwrite_nid_postfix"); + const auto flush = Symbol(library, "fflush_nid_postfix"); + const auto descriptor = Symbol(library, "fileno_nid_postfix"); + const auto getByte = Symbol(library, "fgetc_nid_postfix"); + const auto getWide = Symbol(library, "fgetwc_nid_postfix"); + const auto putByteUnlocked = Symbol(library, "putc_unlocked_nid_postfix"); + Require(open && close && wide && putByte && putWide && seek && read && write && flush && descriptor && getByte && getWide && putByteUnlocked, + "libc is missing a stdio orientation test symbol"); + + void* stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not open guest stream"); + Require(wide(stream, 0) == 0, "a new stream should be unoriented"); + Require(seek(stream, 0, SEEK_SET) == 0, "seeking should succeed"); + Require(flush(stream) == 0, "flushing should succeed"); + Require(descriptor(stream) >= 0, "fileno should succeed"); + Require(read(nullptr, 0, 0, stream) == 0, "zero-length fread should be a no-op"); + Require(write(nullptr, 0, 0, stream) == 0, "zero-length fwrite should be a no-op"); + Require(wide(stream, 0) == 0, "metadata operations must not orient a stream"); + Require(wide(stream, 1) == 1, "positive mode should establish wide orientation"); + Require(wide(stream, -1) == 1, "an established orientation must not change"); + Require(close(stream) == 0, "could not close query-oriented stream"); + + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(putByte('B', stream) == 'B', "byte output failed"); + Require(wide(stream, 0) == -1, "byte output should establish byte orientation"); + Require(seek(stream, 0, SEEK_SET) == 0, "could not rewind byte stream"); + Require(getByte(stream) == 'B', "byte stream contents did not round-trip"); + Require(wide(stream, 0) == -1, "byte reads must preserve byte orientation"); + Require(close(stream) == 0, "could not close byte-oriented stream"); + + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(putByteUnlocked('U', stream) == 'U', "unlocked byte output failed"); + Require(wide(stream, 0) == -1, "unlocked byte output should establish byte orientation"); + Require(close(stream) == 0, "could not close unlocked byte-oriented stream"); + + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(putWide(u'W', stream) == u'W', "wide output failed"); + Require(wide(stream, 0) == 1, "wide output should establish wide orientation"); + Require(close(stream) == 0, "could not close wide-oriented stream"); + + { + std::FILE* host = std::fopen(argv[2], "wb"); + Require(host != nullptr, "could not prepare wide-input fixture"); + Require(std::fputc('I', host) == 'I', "could not write wide-input fixture"); + Require(std::fclose(host) == 0, "could not close wide-input fixture"); + } + stream = open(argv[2], "rb"); + Require(stream != nullptr, "could not open wide-input fixture"); + Require(getWide(stream) == 'I', "wide input returned the wrong character"); + Require(wide(stream, 0) == 1, "wide input should establish wide orientation"); + Require(close(stream) == 0, "could not close wide-input stream"); + + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(putByte('R', stream) == 'R', "byte output before freopen failed"); + Require(wide(stream, 0) == -1, "precondition: byte orientation"); + const auto reopen = Symbol(library, "freopen_nid_postfix"); + Require(reopen != nullptr, "libc is missing freopen"); + stream = reopen(argv[2], "w+b", stream); + Require(stream != nullptr, "freopen failed"); + Require(wide(stream, 0) == 0, "successful freopen should reset orientation"); + Require(getWide(stream) == -1, "wide read at EOF should fail"); + Require(wide(stream, 0) == 1, "wide input should establish orientation even at EOF"); + Require(close(stream) == 0, "could not close reopened stream"); + + const auto ungetWide = Symbol(library, "ungetwc_nid_postfix"); + const auto getLine = Symbol(library, "fgets_nid_postfix"); + const auto getWideLine = Symbol(library, "fgetws_nid_postfix"); + Require(ungetWide && getLine && getWideLine, "libc is missing a line or unget symbol"); + char line[4]{}; + char16_t wideLine[4]{}; + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(ungetWide(-1, stream) == -1, "ungetwc of WEOF should fail"); + Require(wide(stream, 0) == 1, "ungetwc should orient the stream even for WEOF"); + Require(close(stream) == 0, "could not close stream after ungetwc"); + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(getLine(line, 0, stream) == nullptr, "fgets with size 0 should fail"); + Require(wide(stream, 0) == -1, "fgets should orient the stream even for size 0"); + Require(close(stream) == 0, "could not close stream after fgets"); + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(getWideLine(wideLine, 0, stream) == nullptr, "fgetws with size 0 should fail"); + Require(wide(stream, 0) == 1, "fgetws should orient the stream even for size 0"); + Require(close(stream) == 0, "could not close stream after fgetws"); + + const auto scanList = Symbol(library, "vfscanf_nid_postfix"); + Require(scanList != nullptr, "libc is missing vfscanf"); + int number = 0; + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(ScanThrough(scanList, stream, "%d", &number) == EOF, "vfscanf at end of file should fail"); + Require(wide(stream, 0) == -1, "vfscanf should orient the stream to bytes"); + Require(close(stream) == 0, "could not close stream after vfscanf"); + + const auto scanSecure = Symbol(library, "vfscanf_s_nid_postfix"); + const auto printSecure = Symbol(library, "vfprintf_s_nid_postfix"); + const auto printWideSecure = Symbol(library, "vfwprintf_s_nid_postfix"); + Require(scanSecure && printSecure && printWideSecure, "libc is missing a bounds-checked stdio symbol"); + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(ScanThrough(scanSecure, stream, "%d", &number) == EOF, "vfscanf_s at end of file should fail"); + Require(wide(stream, 0) == -1, "vfscanf_s should orient the stream to bytes"); + Require(close(stream) == 0, "could not close stream after vfscanf_s"); + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(ScanThrough(printSecure, stream, "%d", 7) == 1, "vfprintf_s should write one byte"); + Require(wide(stream, 0) == -1, "vfprintf_s should orient the stream to bytes"); + Require(close(stream) == 0, "could not close stream after vfprintf_s"); + stream = open(argv[2], "w+b"); + Require(stream != nullptr, "could not reopen guest stream"); + Require(WideThrough(printWideSecure, stream, u"%d", 7) == 1, "vfwprintf_s should write one character"); + Require(wide(stream, 0) == 1, "vfwprintf_s should orient the stream wide"); + Require(close(stream) == 0, "could not close stream after vfwprintf_s"); + + std::remove(argv[2]); +#ifdef _WIN32 + FreeLibrary(library); +#else + dlclose(library); +#endif + return 0; +} diff --git a/core/libs/tests/GuestGnmNeoContextStateInternal.cpp b/core/libs/tests/GuestGnmNeoContextStateInternal.cpp index efe6173f22..d297dbcb72 100644 --- a/core/libs/tests/GuestGnmNeoContextStateInternal.cpp +++ b/core/libs/tests/GuestGnmNeoContextStateInternal.cpp @@ -50,10 +50,12 @@ int main() { static std::array process{}; Require(ProcessParametersFixture::Install(process.data(), process.size()), "the process parameters could not be installed"); - for (const std::uint32_t sdkVersion : {0x03500000u, 0x03ffffffu}) { + for (const std::uint32_t sdkVersion : {0x00000000u, 0x03500000u, 0x03ffffffu}) { std::memcpy(process.data() + 0x10, &sdkVersion, sizeof(sdkVersion)); + const int internalSize = sceGnmDrawInitToDefaultContextStateInternalSize(); + Require(internalSize == 0x20, "internal context state size before SDK 0x04000000 is not 0x20"); Buffer words = Fresh(); - Require(sceGnmDrawInitToDefaultContextStateInternalCommand(words.data(), 0x20) == 0x20, "size 0x20 was not taken before SDK 0x04000000"); + Require(sceGnmDrawInitToDefaultContextStateInternalCommand(words.data(), static_cast(internalSize)) == 0x20, "size 0x20 was not taken before SDK 0x04000000"); for (std::size_t index = 0; index < BufferWords; ++index) Require(words[index] == (index < ContextStateBefore400.size() ? ContextStateBefore400[index] : Untouched), "the words before SDK 0x04000000 differ from firmware 13.42's"); for (const std::uint32_t size : {0u, 0x1fu, 0x21u, 0xffu, 0x100u, 0x101u}) { @@ -62,16 +64,18 @@ int main() { } } - for (const std::uint32_t sdkVersion : {0x04000000u, 0x09000000u}) { + for (const std::uint32_t sdkVersion : {0x04000000u, 0x05500000u, 0x09000000u}) { std::memcpy(process.data() + 0x10, &sdkVersion, sizeof(sdkVersion)); + const int internalSize = sceGnmDrawInitToDefaultContextStateInternalSize(); + Require(internalSize == 0x100, "internal context state size from SDK 0x04000000 is not 0x100"); Buffer words = Fresh(); - Require(sceGnmDrawInitToDefaultContextStateInternalCommand(words.data(), 0x100) == 0x100, "size 0x100 was not taken from SDK 0x04000000"); + Require(sceGnmDrawInitToDefaultContextStateInternalCommand(words.data(), static_cast(internalSize)) == 0x100, "size 0x100 was not taken from SDK 0x04000000"); std::size_t written = 0; while (written < BufferWords && words[written] != Untouched) ++written; Require(written == 102, "size 0x100 did not write 102 words from SDK 0x04000000"); for (const std::uint32_t size : {0u, 0x20u, 0xffu, 0x100u, 0x101u}) CheckSize(sdkVersion, sceGnmDrawInitToDefaultContextState400, size); } - std::puts("GnmNeo InternalCommand tests passed"); + std::puts("GnmNeo ContextStateInternal tests passed"); return 0; } diff --git a/core/libs/tests/GuestHmd2.cpp b/core/libs/tests/GuestHmd2.cpp index 06c0e4ac07..7ad8a6a0de 100644 --- a/core/libs/tests/GuestHmd2.cpp +++ b/core/libs/tests/GuestHmd2.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include namespace Hmd2 { @@ -276,18 +276,132 @@ void SizeQueriesReturnTheConsoleSizes() { Require(work.size == 0x4000 && work.align == 0x100, "mirroring work memory size"); } +template +bool AnswersNoSession(Call call) { + try { + return call() == Hmd2::ReprojectionNotInitialized; + } catch (const std::exception&) { + return false; + } +} + +void TestGatedCallsInsideReprojectionSession() { + std::array memory; + memory.fill(0xaa); + const Hmd2::MirroringParameter mirroring{memory.data(), memory.data() + 32}; + std::uint64_t time = 0xaaaaaaaaaaaaaaaa; + Require(!AnswersNoSession([&] { return sceHmd2ReprojectionEnableMirroring(&mirroring); }), "mirroring inside a reprojection session answered as without one"); + Require(!AnswersNoSession([&] { return sceHmd2ReprojectionGetPredictedDisplayTime(1, &time); }), "predicted display time inside a reprojection session answered as without one"); + Require(!AnswersNoSession([&] { return sceHmd2ReprojectionSetRenderConfig(1, 0, 0); }), "render config inside a reprojection session answered as without one"); + Require(!AnswersNoSession([&] { return sceHmd2ReprojectionSetRenderConfig(1ull << 32, 0, 0); }), "render config with the second accepted bit inside a reprojection session answered as without one"); + for (const std::uint64_t frameId : {0ull, 1ull, 0x123456789ull}) { + Require(sceHmd2ReprojectionGetPredictedDisplayTime(frameId, nullptr) == Hmd2::ParameterNull, "predicted display time into a null output inside a reprojection session"); + } + Require(sceHmd2ReprojectionSetRenderConfig(0, 1, 1) == Hmd2::ParameterInvalid, "render config with nonzero reserved arguments inside a reprojection session"); + Require(sceHmd2ReprojectionSetRenderConfig(2, 0, 0) == Hmd2::ParameterInvalid, "render config with a bit outside the accepted ones inside a reprojection session"); + Require(sceHmd2ReprojectionSetRenderConfig(1, 1, 0) == Hmd2::ParameterInvalid && sceHmd2ReprojectionSetRenderConfig(1, 0, 1) == Hmd2::ParameterInvalid, "render config with one nonzero reserved argument inside a reprojection session"); + Require(sceHmd2ReprojectionSetRenderConfig(~0ull, 0, 0) == Hmd2::ParameterInvalid && sceHmd2ReprojectionSetRenderConfig(3, 0, 0) == Hmd2::ParameterInvalid, "render config with several bits inside a reprojection session"); +} + +void TestReprojection() { + Require(sceHmd2ReprojectionSetAllowPositionalReprojection(2) == 0, + "the positional setting without a session did not succeed"); + Hmd2::SizeAlign early = sceHmd2ReprojectionQueryBufferSizeAlign(); + Require(early.size == Hmd2::ReprojectionPositionalBufferSize + 2 * Hmd2::ReprojectionPositionalBufferSizeStep + && early.align == Hmd2::ReprojectionPositionalBufferAlign, + "the positional setting without a session did not change the work-size answer"); + Require(sceHmd2ReprojectionSetAllowPositionalReprojection(6) == Hmd2::ParameterInvalid, + "a positional setting above the range did not report invalid parameter"); + early = sceHmd2ReprojectionQueryBufferSizeAlign(); + Require(early.size == Hmd2::ReprojectionPositionalBufferSize + 2 * Hmd2::ReprojectionPositionalBufferSizeStep + && early.align == Hmd2::ReprojectionPositionalBufferAlign, + "a rejected positional setting did not keep the previous non-zero setting"); + Require(sceHmd2ReprojectionSetAllowPositionalReprojection(0) == 0, + "clearing the positional setting without a session did not succeed"); + Require(sceHmd2ReprojectionEnableVrMode(Hmd2::OutputMode89_91Hz) == Hmd2::ReprojectionNotInitialized, + "enabling VR mode without a session"); + Require(sceHmd2ReprojectionSetTiming(3000) == Hmd2::ReprojectionNotInitialized, "setting the timing without a session"); + const Hmd2::SizeAlign sizeAlign = sceHmd2ReprojectionQueryBufferSizeAlign(); + Require(sizeAlign.size == Hmd2::ReprojectionBufferSize && sizeAlign.align == Hmd2::ReprojectionBufferAlign, + "the reprojection work size did not answer the console's requirement"); + alignas(Hmd2::ReprojectionBufferAlign) static std::uint8_t work[Hmd2::ReprojectionBufferSize]{}; + std::uint8_t display[64]{}; + Hmd2::ReprojectionInitializeParameter initParam{}; + Require(sceHmd2ReprojectionInitialize(nullptr, nullptr) == Hmd2::ParameterNull, "initialization without a param"); + Require(sceHmd2ReprojectionInitialize(&initParam, nullptr) == Hmd2::ParameterNull, "initialization without buffers"); + initParam.workMemory = work; + Require(sceHmd2ReprojectionInitialize(&initParam, nullptr) == Hmd2::ParameterNull, "initialization without a display memory"); + initParam.displayMemory = display; + initParam.timingInterval = 0; + Require(sceHmd2ReprojectionInitialize(&initParam, nullptr) == 0, "initialization did not succeed"); + const auto* state = reinterpret_cast(work); + Require(state->outputMode == 0 && state->timingUs == 3000, "the initial state did not carry the default timing"); + Require(sceHmd2ReprojectionInitialize(&initParam, nullptr) == Hmd2::ReprojectionAlreadyInitialized, "a second initialization"); + Require(sceHmd2ReprojectionSetTiming(2500) == 0, "setting the timing did not succeed"); + Require(state->timingUs == 2500, "the timing was not recorded in the work memory"); + Require(sceHmd2ReprojectionSetTiming(2001) == 0 && state->timingUs == 2100, "the timing was not rounded up to hundreds"); + Require(sceHmd2ReprojectionSetTiming(2100) == 0 && state->timingUs == 2100, + "a timing equal to the current setting did not answer zero at once"); + Require(sceHmd2ReprojectionSetTiming(1999) == Hmd2::ParameterInvalid, + "a timing below the range did not report invalid parameter"); + Require(state->timingUs == 2100, "a rejected timing changed the recorded setting"); + Require(sceHmd2ReprojectionSetTiming(7001) == Hmd2::ParameterInvalid, + "a timing above the range did not report invalid parameter"); + Require(state->timingUs == 2100, "a rejected upper timing changed the recorded setting"); + Require(sceHmd2ReprojectionSetTiming(2000) == 0 && state->timingUs == 2000, "the range's lower end did not succeed"); + Require(sceHmd2ReprojectionSetTiming(7000) == 0 && state->timingUs == 7000, "the range's upper end did not succeed"); + Require(sceHmd2ReprojectionEnableVrMode(0x2000C) == Hmd2::VideoOutNoDevice, "enabling VR mode did not report no device"); + Require(sceHmd2ReprojectionEnableVrMode(0x2000F) == Hmd2::VideoOutNoDevice, "enabling the second VR mode did not report no device"); + Require(sceHmd2ReprojectionEnableVrMode(0) == Hmd2::VideoOutInvalidValue, "an unknown output mode did not report invalid value"); + Require(state->outputMode == 0, "the output mode changed without a VR runtime"); + Require(sceHmd2ReprojectionSetAllowPositionalReprojection(6) == Hmd2::ParameterInvalid, + "a positional setting above the range did not report invalid parameter"); + Require(sceHmd2ReprojectionSetAllowPositionalReprojection(0xFF) == Hmd2::ParameterInvalid, + "an out-of-range positional setting did not report invalid parameter"); + Hmd2::SizeAlign positional = sceHmd2ReprojectionQueryBufferSizeAlign(); + Require(positional.size == Hmd2::ReprojectionBufferSize && positional.align == Hmd2::ReprojectionBufferAlign, + "a rejected positional setting changed the work-size answer"); + Require(sceHmd2ReprojectionSetAllowPositionalReprojection(2) == 0, + "the positional setting did not succeed"); + positional = sceHmd2ReprojectionQueryBufferSizeAlign(); + Require(positional.size == Hmd2::ReprojectionPositionalBufferSize + 2 * Hmd2::ReprojectionPositionalBufferSizeStep + && positional.align == Hmd2::ReprojectionPositionalBufferAlign, + "the accepted positional setting did not change the work-size answer"); + Require(sceHmd2ReprojectionSetAllowPositionalReprojection(1) == 0, + "the lowest positional setting did not succeed"); + positional = sceHmd2ReprojectionQueryBufferSizeAlign(); + Require(positional.size == Hmd2::ReprojectionPositionalBufferSize + Hmd2::ReprojectionPositionalBufferSizeStep + && positional.align == Hmd2::ReprojectionPositionalBufferAlign, + "the lowest positional work size did not answer the console's requirement"); + Require(sceHmd2ReprojectionSetAllowPositionalReprojection(5) == 0, + "the highest positional setting did not succeed"); + positional = sceHmd2ReprojectionQueryBufferSizeAlign(); + Require(positional.size == Hmd2::ReprojectionPositionalBufferSize + 5 * Hmd2::ReprojectionPositionalBufferSizeStep + && positional.align == Hmd2::ReprojectionPositionalBufferAlign, + "the highest positional work size did not answer the console's requirement"); + Require(sceHmd2ReprojectionSetAllowPositionalReprojection(0) == 0, + "clearing the positional setting did not succeed"); + const Hmd2::SizeAlign cleared = sceHmd2ReprojectionQueryBufferSizeAlign(); + Require(cleared.size == Hmd2::ReprojectionBufferSize && cleared.align == Hmd2::ReprojectionBufferAlign, + "clearing the positional setting did not restore the work-size answer"); + TestGatedCallsInsideReprojectionSession(); + Require(sceHmd2ReprojectionTerminate() == 0, "terminate did not succeed"); + TestCallsWithoutReprojectionSession(); + Require(sceHmd2ReprojectionSetTiming(3000) == Hmd2::ReprojectionNotInitialized, + "setting the timing after terminate"); + Require(sceHmd2ReprojectionEnableVrMode(Hmd2::OutputMode89_91Hz) == Hmd2::ReprojectionNotInitialized, + "enabling VR mode after terminate"); + Require(sceHmd2ReprojectionInitialize(&initParam, nullptr) == 0, "re-initialization after terminate"); + Require(state->outputMode == 0 && state->timingUs == Hmd2::ReprojectionTimingDefault, + "the re-initialized state carries the default timing"); +} + int main() { SizeQueriesReturnTheConsoleSizes(); 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"); + TestReprojection(); 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"); diff --git a/core/libs/tests/GuestHmdSetupDialog.cpp b/core/libs/tests/GuestHmdSetupDialog.cpp index af83eb7cc9..adab84d2f0 100644 --- a/core/libs/tests/GuestHmdSetupDialog.cpp +++ b/core/libs/tests/GuestHmdSetupDialog.cpp @@ -1,7 +1,9 @@ #include "prx/libSceHmdSetupDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include +#include namespace { @@ -15,6 +17,28 @@ constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); constexpr int kErrNotFinished = static_cast(0x80B80005); constexpr int kErrArgNull = static_cast(0x80B8000D); constexpr int kResultUserCanceled = 1; +constexpr int kRaceRounds = 200000; + +template +void RequireTerminateWins(Finish finish) { + std::atomic round{0}; + std::atomic done{0}; + std::thread terminator([&] { + for (int i = 1; i <= kRaceRounds; ++i) { + while (round.load() != i) std::this_thread::yield(); + sceHmdSetupDialogTerminate(); + done.fetch_add(1); + } + }); + for (int i = 1; i <= kRaceRounds; ++i) { + Require(sceHmdSetupDialogInitialize() == 0); + round.store(i); + finish(); + while (done.load() != i) std::this_thread::yield(); + Require(sceHmdSetupDialogGetStatus() == kStatusNone); + } + terminator.join(); +} } @@ -72,4 +96,7 @@ int main() { Require(sceHmdSetupDialogGetResult(result) == kErrNotInitialized); Require(sceHmdSetupDialogGetStatus() == kStatusNone); Require(sceHmdSetupDialogUpdateStatus() == kStatusNone); + + RequireTerminateWins([&] { sceHmdSetupDialogOpen(¶m); }); + RequireTerminateWins([] { sceHmdSetupDialogClose(); }); } diff --git a/core/libs/tests/GuestHttp2.cpp b/core/libs/tests/GuestHttp2.cpp index 69cf99710a..7cb1575525 100644 --- a/core/libs/tests/GuestHttp2.cpp +++ b/core/libs/tests/GuestHttp2.cpp @@ -16,6 +16,13 @@ static bool Throws(TCall call) { return false; } +static int callbackCalls = 0; + +static int APS5_VABI RejectRedirect(int, int, const char**, const char*, void*) { + ++callbackCalls; + return -1; +} + int main() { Require(sceHttp2CreateCookieBox(0xffff) == static_cast(0x817b121du)); const int box = sceHttp2CreateCookieBox(0x10000); @@ -29,6 +36,15 @@ int main() { Require(Throws([&] { sceHttp2CreateTemplate(context, "agent", 3, -1); })); const int request = sceHttp2CreateRequestWithURL(tmpl, "GET", "https://localhost/", 0); Require(box > 0 && context > 0 && tmpl > 0 && request > 0); + Require(sceHttp2SetRedirectCallback(-1, RejectRedirect, nullptr) == InvalidId); + for (const int id : {tmpl, request}) { + Require(sceHttp2SetRedirectCallback(id, RejectRedirect, &callbackCalls) == 0); + Require(sceHttp2SetRedirectCallback(id, nullptr, &callbackCalls) == 0); + Require(sceHttp2SetRedirectCallback(id, RejectRedirect, nullptr) == 0); + } + Require(callbackCalls == 0); + for (const int id : {context, box}) Require(Throws([&] { sceHttp2SetRedirectCallback(id, RejectRedirect, nullptr); })); + Require(sceHttp2SetCookieBox(tmpl, 0) == 0); Require(Throws([&] { sceHttp2SetCookieBox(tmpl, box); })); Require(sceHttp2CookieFlush(box) == 0); diff --git a/core/libs/tests/GuestHttp2Options.cpp b/core/libs/tests/GuestHttp2Options.cpp index f2bb31e254..3eeb3ffb8d 100644 --- a/core/libs/tests/GuestHttp2Options.cpp +++ b/core/libs/tests/GuestHttp2Options.cpp @@ -136,7 +136,14 @@ int main() { Require(Throws([&] { sceHttp2SetPreSendCallback(sent, nullptr, nullptr); })); Require(sceHttp2DeleteRequest(sent) == 0); + const int sized = sceHttp2CreateRequestWithURL(tmpl, "POST", "https://localhost/", 0); + Require(sized > 0); + Require(sceHttp2SetRequestContentLength(sized, 42) == 0); + Require(sceHttp2DeleteRequest(sized) == 0); + Require(sceHttp2SetRequestContentLength(sized, 42) == InvalidId); + Require(sceHttp2DeleteRequest(request) == 0); + Require(sceHttp2SetRequestContentLength(tmpl, 42) == 0); const int invalidIds[] = {0, -1, 0x7fffffff, request, context, box}; for (const int invalid : invalidIds) { int value = -2; @@ -149,6 +156,7 @@ int main() { Require(sceHttp2SetAuthInfoCallback(invalid, nullptr, nullptr) == InvalidId); Require(sceHttp2SetPreSendCallback(invalid, nullptr, nullptr) == InvalidId); if (invalid != context) Require(sceHttp2AuthCacheFlush(invalid) == InvalidId); + if (invalid != context && invalid != box) Require(sceHttp2SetRequestContentLength(invalid, 42) == InvalidId); Require(sceHttp2RedirectCacheFlush(invalid) == 0); } diff --git a/core/libs/tests/GuestHttp2Transport.cpp b/core/libs/tests/GuestHttp2Transport.cpp index 4aab31e278..ac1ffb12c6 100644 --- a/core/libs/tests/GuestHttp2Transport.cpp +++ b/core/libs/tests/GuestHttp2Transport.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include #include @@ -52,7 +54,7 @@ bool Throws(TCall call) { return false; } -enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, Gzip, GzipSplit, GzipTruncated, GzipChecksum, GzipConcat, GzipLarge, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete }; +enum class Mode { Normal, Large, NoLength, Head, NotFound, Redirect, Auth, Reset, Truncate, Gzip, GzipSplit, GzipTruncated, GzipChecksum, GzipConcat, GzipLarge, StallHandshake, StallHeaders, StallBody, NoAlpn, Informational, StallUpload, RedirectOnce, GzipRedirectEmpty, GzipRedirectBody, EarlyFinal, EarlyReset, EarlyEmpty, EarlyLarge, EarlyResetIncomplete }; const char* Protocols[] = {"h2", nullptr}; std::string GzipPayload() { @@ -89,7 +91,8 @@ std::string StoredGzip(const std::string& payload) { class Server { public: - explicit Server(Mode mode) : mode(mode) { + explicit Server(Mode mode, std::string location = "/another", int redirectStatus = 302) + : mode(mode), location(std::move(location)), redirectStatus(redirectStatus) { mbedtls_net_init(&listener); mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&random); @@ -150,6 +153,9 @@ public: bool IsEarly() const { return mode >= Mode::EarlyFinal; } private: const Mode mode; + const std::string location; + const int redirectStatus; + unsigned accepted = 0; mbedtls_net_context listener; mbedtls_entropy_context entropy; mbedtls_ctr_drbg_context random; @@ -228,12 +234,14 @@ private: if (mode == Mode::StallHeaders) { Stage(2); Stall(); return; } if (mode == Mode::Reset) { Require(nghttp2_submit_rst_stream(session, 0, stream, NGHTTP2_REFUSED_STREAM) == 0, "server reset"); return; } body = (mode == Mode::Large || mode == Mode::EarlyLarge) ? std::string(200000, 'x') : "response body"; - std::string status = IsEarly() ? "413" : mode == Mode::NotFound ? "404" : mode == Mode::Redirect ? "302" : mode == Mode::Auth ? "401" : "200"; + const bool redirect = mode == Mode::Redirect || mode == Mode::GzipRedirectEmpty || mode == Mode::GzipRedirectBody || (mode == Mode::RedirectOnce && accepted == 1); + std::string status = IsEarly() ? "413" : mode == Mode::NotFound ? "404" : redirect ? std::to_string(redirectStatus) : mode == Mode::Auth ? "401" : "200"; if (mode == Mode::EarlyEmpty) body.clear(); auto length = std::to_string(body.size()); - if (mode == Mode::Head) body.clear(); + const auto requestMethod = headers.find(":method"); + if (mode == Mode::Head || (requestMethod != headers.end() && requestMethod->second == "HEAD")) body.clear(); if (mode == Mode::Truncate) body = "short"; - if (mode == Mode::Gzip || mode == Mode::GzipSplit || mode == Mode::GzipTruncated || mode == Mode::GzipChecksum || mode == Mode::GzipConcat) { + if (mode == Mode::GzipRedirectEmpty || mode == Mode::GzipRedirectBody || mode == Mode::Gzip || mode == Mode::GzipSplit || mode == Mode::GzipTruncated || mode == Mode::GzipChecksum || mode == Mode::GzipConcat) { static const unsigned char compressed[] = { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x2b, 0x4a, 0x2d, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x55, 0x48, 0xca, 0x4f, 0xa9, 0x04, 0x00, 0x36, 0x76, 0x8b, 0x0c, 0x0d, 0x00, 0x00, 0x00, @@ -244,6 +252,7 @@ private: if (mode == Mode::GzipConcat) { const std::string member = body; body += member; } length = std::to_string(body.size()); } + if (mode == Mode::GzipRedirectEmpty) { body.clear(); length = "0"; } if (mode == Mode::GzipLarge) { body = StoredGzip(GzipPayload()); length = std::to_string(body.size()); @@ -254,8 +263,8 @@ private: } std::vector> fields{{":status", status}, {"x-loopback", "yes"}}; if (mode != Mode::NoLength) fields.emplace_back("content-length", length); - if (mode == Mode::Redirect) fields.emplace_back("location", "/another"); - if (mode == Mode::Gzip || mode == Mode::GzipSplit || mode == Mode::GzipTruncated || mode == Mode::GzipChecksum || mode == Mode::GzipConcat || mode == Mode::GzipLarge) fields.emplace_back("content-encoding", "gzip"); + if (redirect) fields.emplace_back("location", location); + if (mode == Mode::GzipRedirectEmpty || mode == Mode::GzipRedirectBody || mode == Mode::Gzip || mode == Mode::GzipSplit || mode == Mode::GzipTruncated || mode == Mode::GzipChecksum || mode == Mode::GzipConcat || mode == Mode::GzipLarge) fields.emplace_back("content-encoding", "gzip"); std::vector response; for (auto& [name, value] : fields) response.push_back({reinterpret_cast(name.data()), reinterpret_cast(value.data()), name.size(), value.size(), 0}); nghttp2_data_provider2 provider{}; @@ -264,6 +273,26 @@ private: } void Serve() { + const unsigned connections = mode == Mode::RedirectOnce ? 2 : 1; + while (accepted < connections) { + for (;;) { + const int ready = mbedtls_net_poll(&listener, MBEDTLS_NET_POLL_READ, 20); + Require(ready >= 0, "server accept poll"); + if (ready) break; + std::lock_guard lock(mutex); + if (released) return; + } + headers.clear(); + upload.clear(); + body.clear(); + offset = 0; + responded = ended = false; + ++accepted; + ServeConnection(); + } + } + + void ServeConnection() { mbedtls_net_context client; mbedtls_net_init(&client); mbedtls_ssl_context ssl; @@ -363,6 +392,188 @@ struct Client { ~Client() { if (context > 0) Require(sceHttp2Term(context) == 0, "client term"); } }; +struct RedirectObservation { + int calls = 0; + int request = 0; + int status = 0; + int result = 0; + std::string method, location; + const char* replacement = nullptr; + bool nullMethod = false; + bool removeRequest = false; + bool throwException = false; +}; + +int APS5_VABI RedirectCallback(int request, int status, const char** method, const char* location, void* argument) { + auto& observed = *static_cast(argument); + ++observed.calls; + observed.request = request; + observed.status = status; + observed.method = *method; + observed.location = location; + if (observed.replacement || observed.nullMethod) *method = observed.replacement; + if (observed.removeRequest) Require(sceHttp2DeleteRequest(request) == 0, "delete from redirect callback"); + if (observed.throwException) throw std::runtime_error("redirect callback failure"); + return observed.result; +} + +void Response(Client& client, int expectedStatus) { + int status = 0; + Require(sceHttp2GetStatusCode(client.request, &status) == 0 && status == expectedStatus, "redirect response status"); + std::string received; + std::array buffer; + for (;;) { + const int count = sceHttp2ReadData(client.request, buffer.data(), buffer.size()); + Require(count >= 0, "redirect response read"); + if (!count) break; + received.append(buffer.data(), static_cast(count)); + } + Require(received == "response body", "redirect preserves final response body"); +} + +void RedirectPolicies(int ssl) { + for (int policy = 0; policy < 6; ++policy) { + Server server(Mode::Redirect, policy == 4 ? "" : "/another"); + Client client(ssl); + RedirectObservation original, changed; + original.result = policy == 5 ? 7 : -1; + if (policy == 0) Require(sceHttp2SetRedirectCallback(client.tmpl, RedirectCallback, &original) == 0, "template redirect callback"); + client.Create(server.Url()); + if (policy == 0) Require(sceHttp2SetRedirectCallback(client.tmpl, RedirectCallback, &changed) == 0, "template callback mutation"); + else Require(sceHttp2SetRedirectCallback(client.request, RedirectCallback, &original) == 0, "request redirect callback"); + if (policy == 2) Require(sceHttp2SetAutoRedirect(client.request, 0) == 0, "manual redirects omit callback"); + if (policy == 3) { + Require(sceHttp2SetRedirectCallback(client.request, nullptr, &changed) == 0, "clear callback with argument"); + Require(sceHttp2SetAutoRedirect(client.request, 0) == 0, "manual cleared callback"); + } + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "redirect refusal response"); + Response(client, 302); + server.Join(); + const int expectedCalls = policy < 2 || policy == 5 ? 1 : 0; + Require(original.calls == expectedCalls && changed.calls == 0, "callback inheritance and policy"); + if (expectedCalls) Require(original.request == client.request && original.status == 302 && original.method == "GET" && original.location == "/another", "redirect callback arguments"); + } +} + +void RedirectFollow(int ssl, int status, const char* replacement = nullptr, bool clearCallback = false, bool schemeRelative = false) { + const bool head = replacement && std::string_view(replacement) == "HEAD"; + Server target(head ? Mode::Head : Mode::Normal); + std::string destination = target.Url(); + if (schemeRelative) destination.erase(0, 6); + Server redirect(Mode::Redirect, destination, status); + Client client(ssl, 1); + RedirectObservation observed; + observed.replacement = replacement; + Require(sceHttp2SetRedirectCallback(client.tmpl, RedirectCallback, &observed) == 0, "inherited follow callback"); + for (const auto* name : {"authorization", "proxy-authorization", "cookie", "origin", "referer"}) + Require(sceHttp2AddRequestHeader(client.tmpl, name, "origin-only", 0) == 0, "origin header"); + Require(sceHttp2AddRequestHeader(client.tmpl, "content-type", "application/octet-stream", 0) == 0, "body metadata"); + Require(sceHttp2AddRequestHeader(client.tmpl, "x-retained", "yes", 0) == 0, "retained header"); + const std::string body = "upload bytes"; + client.Create(redirect.Url(), "POST", body.size()); + if (clearCallback) Require(sceHttp2SetRedirectCallback(client.request, nullptr, &observed) == 0, "clear inherited callback"); + Require(sceHttp2SendRequest(client.request, body.data(), body.size()) == 0, "follow redirect"); + if (head) { + int result = 0; + Require(sceHttp2GetStatusCode(client.request, &result) == 0 && result == 200, "redirect HEAD status"); + char byte; + Require(sceHttp2ReadData(client.request, &byte, 1) == 0, "redirect HEAD has no response body"); + } else Response(client, 200); + redirect.Join(); + target.Join(); + const bool retrieve = status == 301 || status == 302 || status == 303; + const std::string method = replacement ? replacement : retrieve ? "GET" : "POST"; + Require(target.Headers().find(":method")->second == method, "redirect request method"); + Require(target.Headers().find(":path")->second == "/path?query=yes", "redirect fragment removed"); + Require(target.Upload() == (method == "GET" || method == "HEAD" ? "" : body), "redirect upload policy"); + Require(target.Headers().count("content-type") == (method == "GET" || method == "HEAD" ? 0u : 1u), "redirect body headers"); + for (const auto* name : {"authorization", "proxy-authorization", "cookie", "origin", "referer"}) + Require(target.Headers().count(name) == 0, "origin headers excluded at a new origin"); + Require(target.Headers().count("x-retained") == 1, "ordinary header retained"); + Require(target.Headers().find("content-length")->second == std::to_string(target.Upload().size()), "redirect wire content length"); + Require(observed.calls == (clearCallback ? 0 : 1), "follow callback count"); + if (!clearCallback) Require(observed.request == client.request && observed.status == status && observed.method == (retrieve ? "GET" : "POST") && observed.location == destination, "follow callback arguments"); +} + +void RelativeRedirects(int ssl) { + for (const auto& [reference, expected] : std::vector>{ + {"another", "/another"}, {"../another", "/another"}, {"/a/b/../c/.", "/a/c/"}, + {"?next=1", "/path?next=1"}, {"../a//b?x=/../z", "/a//b?x=/../z"}, + {"../../../../c", "/c"}, {".", "/"}, {"..", "/"}, {"./a/./b/../../c", "/c"}, + {"/a/%2e%2e/b", "/a/%2e%2e/b"}, {"g?y/./x", "/g?y/./x"}, {"#tag", "/path?query=yes"}}) { + Server server(Mode::RedirectOnce, reference, 307); + Client client(ssl, 1); + Require(sceHttp2AddRequestHeader(client.tmpl, "authorization", "same-origin", 0) == 0, "same origin authorization"); + client.Create(server.Url()); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "relative redirect"); + Response(client, 200); + server.Join(); + Require(server.Headers().find(":path")->second == expected, "resolved relative reference"); + Require(server.Headers().count("authorization") == 1, "same origin retains authorization"); + } +} + +void RedirectRequestMethods(int ssl) { + for (const auto& [method, status] : std::vector>{{"PUT", 301}, {"PUT", 303}, {"GET", 307}, {"HEAD", 303}}) { + const bool head = std::string_view(method) == "HEAD"; + Server target(head ? Mode::Head : Mode::Normal); + Server redirect(Mode::Redirect, target.Url(), status); + Client client(ssl, 1); + const std::string body = head ? "" : "retained data"; + client.Create(redirect.Url(), method, body.size()); + Require(sceHttp2SendRequest(client.request, body.data(), body.size()) == 0, "redirect method semantics"); + if (head) { + int result = 0; + char byte; + Require(sceHttp2GetStatusCode(client.request, &result) == 0 && result == 200, "HEAD redirected status"); + Require(sceHttp2ReadData(client.request, &byte, 1) == 0, "HEAD redirected body"); + } else Response(client, 200); + redirect.Join(); + target.Join(); + Require(target.Headers().find(":method")->second == (status == 303 && !head ? "GET" : method), "redirect preserves or rewrites method"); + Require(target.Upload() == (status == 303 && !head ? "" : body), "redirect preserves or drops data"); + } +} + +void GzipRedirect(int ssl) { + for (const auto mode : {Mode::Normal, Mode::Gzip}) { + Server target(mode); + Server redirect(mode == Mode::Normal ? Mode::GzipRedirectEmpty : Mode::GzipRedirectBody, target.Url()); + Client client(ssl, 1); + Require(sceHttp2SetInflateGZIPEnabled(client.tmpl, 1) == 0, "gzip redirect option"); + client.Create(redirect.Url()); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "gzip redirect send"); + Response(client, 200); + redirect.Join(); + target.Join(); + } +} + +void RedirectFailures(int ssl) { + for (int mode = 0; mode < 4; ++mode) { + Server server(Mode::Redirect, "/another"); + Client client(ssl); + RedirectObservation observed; + observed.nullMethod = mode == 0; + observed.replacement = mode == 1 ? "bad method" : nullptr; + observed.removeRequest = mode == 2; + observed.throwException = mode == 3; + client.Create(server.Url()); + Require(sceHttp2SetRedirectCallback(client.request, RedirectCallback, &observed) == 0, "failing redirect callback"); + Require(Throws([&] { sceHttp2SendRequest(client.request, nullptr, 0); }), "unsupported redirect remains explicit"); + server.Join(); + Require(observed.calls == 1, "failure callback delivered once"); + if (observed.removeRequest) Require(sceHttp2DeleteRequest(client.request) == static_cast(0x817b1100), "callback deletion persists"); + } + std::vector> chain; + for (int index = 0; index < 7; ++index) + chain.push_back(std::make_unique(Mode::Redirect, chain.empty() ? "/unused" : chain.back()->Url())); + Client client(ssl, 1); + client.Create(chain.back()->Url()); + Require(Throws([&] { sceHttp2SendRequest(client.request, nullptr, 0); }), "bounded redirects"); + for (auto& server : chain) server->Join(); +} + void Success(int ssl, Mode mode, bool disableCa = false) { std::fprintf(stderr, "HTTP2 success scenario %d\n", static_cast(mode)); Server server(mode); @@ -707,6 +918,22 @@ void Deadline(int ssl, Mode mode, bool overall) { } +void HostAuthority(int ssl) { + std::fprintf(stderr, "HTTP2 host header scenario\n"); + Server server(Mode::Normal); + Client client(ssl); + Require(sceHttp2AddRequestHeader(client.tmpl, "Host", "template.example", 0) == 0, "template host"); + client.Create(server.Url()); + Require(sceHttp2AddRequestHeader(client.request, "host", "request.example", 0) == 0, "request host"); + Require(sceHttp2SendRequest(client.request, nullptr, 0) == 0, "send host request"); + std::array buffer; + while (sceHttp2ReadData(client.request, buffer.data(), buffer.size()) > 0) {} + server.Join(); + const auto& headers = server.Headers(); + Require(headers.count(":authority") == 1 && headers.find(":authority")->second == "request.example", "host header becomes the authority"); + Require(headers.count("host") == 0, "host header is not sent"); +} + int main(int argc, char** argv) { #ifndef _WIN32 std::signal(SIGPIPE, SIG_IGN); @@ -732,6 +959,17 @@ int main(int argc, char** argv) { } else if (argc == 2 && std::string_view(argv[1]) == "--dns-first") { FirstNetworkRequest(ssl); #endif + } else if (argc == 2 && std::string_view(argv[1]) == "--redirects") { + GzipRedirect(ssl); + RedirectPolicies(ssl); + for (const int status : {301, 302, 303, 307, 308}) RedirectFollow(ssl, status); + RedirectFollow(ssl, 302, "PATCH"); + RedirectFollow(ssl, 303, "HEAD"); + RedirectFollow(ssl, 307, nullptr, true); + RedirectFollow(ssl, 308, nullptr, false, true); + RedirectRequestMethods(ssl); + RelativeRedirects(ssl); + RedirectFailures(ssl); } else { Require(argc == 1, "test arguments"); for (const auto mode : {Mode::Normal, Mode::Large, Mode::NoLength, Mode::Head, Mode::NotFound, Mode::Redirect, Mode::Auth, Mode::Informational}) Success(ssl, mode); @@ -741,7 +979,7 @@ int main(int argc, char** argv) { GzipFailure(ssl, Mode::GzipTruncated, "truncated gzip throws"); GzipFailure(ssl, Mode::GzipChecksum, "gzip checksum mismatch throws"); GzipFailure(ssl, Mode::GzipConcat, "concatenated gzip members throw"); - for (const auto mode : {Mode::NoAlpn, Mode::Reset, Mode::Truncate, Mode::Redirect, Mode::Auth}) Failure(ssl, mode); + for (const auto mode : {Mode::NoAlpn, Mode::Reset, Mode::Truncate, Mode::Auth}) Failure(ssl, mode); Failure(ssl, Mode::Normal, true); for (const auto mode : {Mode::StallHandshake, Mode::StallHeaders, Mode::StallBody, Mode::StallUpload}) { Deadline(ssl, mode, false); @@ -756,6 +994,7 @@ int main(int argc, char** argv) { Failure(untrusted, Mode::Normal); Success(untrusted, Mode::Normal, true); Require(sceSslTerm_nid_postfix(untrusted) == 0, "untrusted SSL term"); + HostAuthority(ssl); } Require(sceSslTerm_nid_postfix(ssl) == 0, "SSL term"); std::puts("HTTP2 loopback transport passed"); diff --git a/core/libs/tests/GuestHttpClient.cpp b/core/libs/tests/GuestHttpClient.cpp new file mode 100644 index 0000000000..f644307c22 --- /dev/null +++ b/core/libs/tests/GuestHttpClient.cpp @@ -0,0 +1,206 @@ +#include "prx/libSceHttp/libSceHttp.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Check(bool value, const char* message) { + if (!value) throw std::runtime_error(message); +} + +using Client = std::unique_ptr; + +Client Create() { + Client client(Aps5HttpClient_Create_nid_postfix(), Aps5HttpClient_Destroy_nid_postfix); + Check(client != nullptr, "client allocation failed"); + return client; +} + +template +void Invalid(TAction action) { + bool failed = false; + try { action(); } catch (const std::invalid_argument&) { failed = true; } + Check(failed, "invalid client argument accepted"); +} + +std::uint8_t Byte(std::size_t offset, bool alternate = false) { + return static_cast((alternate ? offset * 13 + 71 : offset) % 251); +} + +void Write(const char* name, std::size_t size, bool alternate = false) { + std::ofstream file(std::filesystem::path("app0") / name, std::ios::binary); + Check(static_cast(file), "payload creation failed"); + for (std::size_t offset = 0; offset < size; ++offset) file.put(static_cast(Byte(offset, alternate))); + Check(static_cast(file), "payload write failed"); +} + +void Read(Aps5HttpClient* client, std::size_t offset, std::size_t size, bool alternate = false) { + constexpr std::array capacities{1, 7, 113, 1023, 4096}; + std::array bytes; + std::size_t iteration = 0; + while (size) { + bytes.fill(0xa5); + const auto capacity = std::min(size, capacities[iteration++ % capacities.size()]); + const auto count = Aps5HttpClient_Read_nid_postfix(client, bytes.data() + 1, static_cast(capacity)); + Check(count > 0 && static_cast(count) <= capacity, "read returned an invalid byte count"); + Check(bytes.front() == 0xa5 && std::all_of(bytes.begin() + count + 1, bytes.end(), [](auto byte) { return byte == 0xa5; }), "read changed bytes outside its result"); + for (std::int32_t index = 0; index < count; ++index) Check(bytes[index + 1] == Byte(offset + index, alternate), "read returned incorrect payload bytes"); + offset += count; + size -= count; + } +} + +void End(Aps5HttpClient* client) { + std::array bytes{0xa5, 0xa5, 0xa5}; + Check(Aps5HttpClient_Read_nid_postfix(client, bytes.data() + 1, 1) == 0, "EOF returned data"); + Check(bytes == std::array{0xa5, 0xa5, 0xa5}, "EOF changed the destination"); +} + +void WaitForRequest(const std::string& base, const char* kind) { + auto observer = Create(); + Check(Aps5HttpClient_Open_nid_postfix(observer.get(), (base + "/sync/" + kind).c_str()) == 1, "server did not observe the stalled request"); + std::array ready{}; + Check(Aps5HttpClient_GetSize_nid_postfix(observer.get()) == 5, "server synchronization response length differs"); + Check(Aps5HttpClient_Read_nid_postfix(observer.get(), ready.data(), ready.size()) == 5 && ready == std::array{'r', 'e', 'a', 'd', 'y'}, "server synchronization response differs"); +} + +void CheckInputValidation(const std::string& base) { + Aps5HttpClient_Destroy_nid_postfix(nullptr); + Invalid([&] { Aps5HttpClient_Open_nid_postfix(nullptr, base.c_str()); }); + Invalid([] { Aps5HttpClient_Read_nid_postfix(nullptr, nullptr, 0); }); + Invalid([] { Aps5HttpClient_Seek_nid_postfix(nullptr, 0, 0); }); + Invalid([] { Aps5HttpClient_GetSize_nid_postfix(nullptr); }); + Invalid([] { Aps5HttpClient_Start_nid_postfix(nullptr); }); + Invalid([] { Aps5HttpClient_Stop_nid_postfix(nullptr); }); + auto client = Create(); + Check(Aps5HttpClient_GetSize_nid_postfix(client.get()) == -1 && Aps5HttpClient_Seek_nid_postfix(client.get(), 0, 0) == -1, "closed client exposes a body position or size"); + Check(Aps5HttpClient_Read_nid_postfix(client.get(), nullptr, 0) == 0, "closed zero-capacity read failed"); + std::uint8_t byte = 0xa5; + bool failed = false; + try { Aps5HttpClient_Read_nid_postfix(client.get(), &byte, 1); } catch (const std::exception&) { failed = true; } + Check(failed && byte == 0xa5, "closed read did not reject without writing"); + Check(Aps5HttpClient_Open_nid_postfix(client.get(), (base + "/range/payload.bin").c_str()) == 1, "validation source open failed"); + Read(client.get(), 0, 13); + Invalid([&] { Aps5HttpClient_Open_nid_postfix(client.get(), nullptr); }); + Invalid([&] { Aps5HttpClient_Read_nid_postfix(client.get(), &byte, -1); }); + Invalid([&] { Aps5HttpClient_Read_nid_postfix(client.get(), nullptr, 1); }); + Check(byte == 0xa5, "invalid read changed the destination"); + Read(client.get(), 13, 17); + Aps5HttpClient_Stop_nid_postfix(client.get()); + Check(Aps5HttpClient_Read_nid_postfix(client.get(), nullptr, 0) == 0, "stopped zero-capacity read failed"); + Aps5HttpClient_Start_nid_postfix(client.get()); + Read(client.get(), 30, 19); +} + +void CheckBodies(const std::string& base) { + for (const char* route : {"/range/", "/full/", "/full-chunk/", "/full-close/", "/client-redirect/"}) { + auto client = Create(); + Check(Aps5HttpClient_GetSize_nid_postfix(client.get()) == -1, "unopened client has a body size"); + Check(Aps5HttpClient_Open_nid_postfix(client.get(), (base + route + "payload.bin?token=direct#fragment").c_str()) == 1, "body open failed"); + Check(Aps5HttpClient_GetSize_nid_postfix(client.get()) == 100000, "open did not consume and size the response"); + Check(Aps5HttpClient_Read_nid_postfix(client.get(), nullptr, 0) == 0, "zero-capacity read failed"); + Read(client.get(), 0, 100000); + End(client.get()); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), -17, 2) == 99983, "end-relative seek failed"); + Read(client.get(), 99983, 17); + End(client.get()); + } +} + +void CheckCacheAndReopen(const std::string& base) { + auto client = Create(); + Check(Aps5HttpClient_Open_nid_postfix(client.get(), (base + "/cache/payload.bin").c_str()) == 1, "cache source open failed"); + Read(client.get(), 0, 64); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), 0, 0) == 0, "cached seek failed"); + Read(client.get(), 0, 64); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), -1, 0) == -1, "negative seek accepted"); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), 0, 3) == -1, "invalid seek origin accepted"); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), std::numeric_limits::max(), 1) == -1, "overflowing seek accepted"); + Read(client.get(), 64, 16); + Check(Aps5HttpClient_Open_nid_postfix(client.get(), (base + "/range/alternate.bin").c_str()) == 1, "reopening a client failed"); + Check(Aps5HttpClient_GetSize_nid_postfix(client.get()) == 777, "reopen retained the old body size"); + Read(client.get(), 0, 777, true); + End(client.get()); + { + auto other = Create(); + Check(Aps5HttpClient_Open_nid_postfix(other.get(), (base + "/range/payload.bin").c_str()) == 1, "second independent client failed"); + Read(other.get(), 0, 33); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), 20, 0) == 20, "independent client position changed"); + Read(client.get(), 20, 21, true); + Read(other.get(), 33, 19); + } + Read(client.get(), 41, 23, true); +} + +void CheckFailures(const std::string& base) { + auto client = Create(); + for (const char* route : {"/bad/", "/short/", "/chunk-bad/"}) Check(Aps5HttpClient_Open_nid_postfix(client.get(), (base + route + "payload.bin").c_str()) == 0, "malformed HTTP response accepted"); + Check(Aps5HttpClient_Open_nid_postfix(client.get(), (base + "/changed/payload.bin").c_str()) == 1, "validator source open failed"); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), 90000, 0) == 90000, "validator source seek failed"); + bool failed = false; + std::array bytes{0xa5, 0xa5, 0xa5}; + try { Aps5HttpClient_Read_nid_postfix(client.get(), bytes.data() + 1, 1); } catch (const std::exception&) { failed = true; } + Check(failed && bytes == std::array{0xa5, 0xa5, 0xa5}, "changed representation did not fail without writing"); + Check(Aps5HttpClient_Open_nid_postfix(client.get(), (base + "/modified/payload.bin").c_str()) == 1, "reopen after failed read failed"); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), 90000, 0) == 90000, "Last-Modified seek failed"); + Read(client.get(), 90000, 127); +} + +void CheckCancellation(const std::string& base) { + auto client = Create(); + Check(Aps5HttpClient_Open_nid_postfix(client.get(), (base + "/stall/payload.bin").c_str()) == 1, "stalled source open failed"); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), 90000, 0) == 90000, "stalled source seek failed"); + auto read = std::async(std::launch::async, [&] { + std::uint8_t byte = 0xa5; + try { Aps5HttpClient_Read_nid_postfix(client.get(), &byte, 1); } catch (const std::exception&) { return byte == 0xa5; } + return false; + }); + WaitForRequest(base, "read"); + Check(read.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout, "stalled read already finished"); + Aps5HttpClient_Stop_nid_postfix(client.get()); + Check(read.wait_for(std::chrono::seconds(1)) == std::future_status::ready && read.get(), "stop did not cancel the read without writing"); + Aps5HttpClient_Start_nid_postfix(client.get()); + Check(Aps5HttpClient_Seek_nid_postfix(client.get(), 0, 0) == 0, "restart seek failed"); + Read(client.get(), 0, 64); + auto opening = Create(); + auto open = std::async(std::launch::async, [&] { return Aps5HttpClient_Open_nid_postfix(opening.get(), (base + "/stall-open/payload.bin").c_str()); }); + WaitForRequest(base, "open"); + Check(open.wait_for(std::chrono::milliseconds(0)) == std::future_status::timeout, "stalled open already finished"); + Aps5HttpClient_Stop_nid_postfix(opening.get()); + Check(open.wait_for(std::chrono::seconds(1)) == std::future_status::ready && open.get() == 0, "stop did not cancel open"); + Aps5HttpClient_Start_nid_postfix(opening.get()); + Check(Aps5HttpClient_Open_nid_postfix(opening.get(), (base + "/range/alternate.bin").c_str()) == 1, "restart after interrupted open failed"); + Read(opening.get(), 0, 777, true); +} + +} + +int main(int argc, char** argv) { + try { + Check(argc == 2, "missing server URL"); + std::filesystem::create_directories("app0"); + Write("payload.bin", 100000); + Write("alternate.bin", 777, true); + const std::string base = argv[1]; + CheckInputValidation(base); + CheckBodies(base); + CheckCacheAndReopen(base); + CheckFailures(base); + CheckCancellation(base); + std::puts("Reusable HTTP client body, framing, range, cache, reopen, isolation and cancellation tests passed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "Reusable HTTP client: %s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/GuestIcuConstrainedFieldPosition.cpp b/core/libs/tests/GuestIcuConstrainedFieldPosition.cpp new file mode 100644 index 0000000000..55ab4380c3 --- /dev/null +++ b/core/libs/tests/GuestIcuConstrainedFieldPosition.cpp @@ -0,0 +1,103 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include + +namespace { + +constexpr std::int32_t Success = 0; +constexpr std::int32_t IllegalArgument = 1; +constexpr std::int32_t InvalidFormat = 3; +constexpr std::int32_t BufferOverflow = 15; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "ICU constrained field position: %s\n", message); + std::abort(); +} + +template +bool Rejects(F call) { + try { + call(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +void Defaults() { + std::int32_t error = Success; + auto* position = ucfpos_open_nid_postfix(&error); + Require(position && error == Success, "open"); + Require(ucfpos_getCategory_nid_postfix(position, &error) == 0 && ucfpos_getField_nid_postfix(position, &error) == 0, "undefined category and field"); + std::int32_t start = -1, limit = -1; + ucfpos_getIndexes_nid_postfix(position, &start, &limit, &error); + Require(start == 0 && limit == 0 && error == Success, "empty indexes"); + ucfpos_close_nid_postfix(position); + error = BufferOverflow; + position = ucfpos_open_nid_postfix(&error); + Require(position && error == BufferOverflow, "open ignores an incoming error"); + ucfpos_close_nid_postfix(position); + position = ucfpos_open_nid_postfix(nullptr); + Require(position != nullptr, "open accepts a null error pointer"); + ucfpos_close_nid_postfix(position); +} + +void Constraints() { + std::int32_t error = Success; + auto* position = ucfpos_open_nid_postfix(&error); + ucfpos_constrainCategory_nid_postfix(position, 5, &error); + Require(ucfpos_getCategory_nid_postfix(position, &error) == 5 && ucfpos_getField_nid_postfix(position, &error) == 0, "category constraint"); + ucfpos_constrainField_nid_postfix(position, 7, 3, &error); + Require(ucfpos_getCategory_nid_postfix(position, &error) == 7 && ucfpos_getField_nid_postfix(position, &error) == 3, "field constraint"); + ucfpos_constrainCategory_nid_postfix(position, -9, &error); + Require(ucfpos_getCategory_nid_postfix(position, &error) == -9 && ucfpos_getField_nid_postfix(position, &error) == 3, "a category constraint keeps the field"); + Require(error == Success, "constraints succeed"); + error = IllegalArgument; + ucfpos_constrainField_nid_postfix(position, 1, 1, &error); + ucfpos_constrainCategory_nid_postfix(position, 1, &error); + Require(ucfpos_getCategory_nid_postfix(position, &error) == 0 && ucfpos_getField_nid_postfix(position, &error) == 0, "a set error returns defaults"); + std::int32_t start = 11, limit = 12; + ucfpos_getIndexes_nid_postfix(position, &start, &limit, &error); + Require(start == 11 && limit == 12 && error == IllegalArgument, "a set error leaves indexes untouched"); + error = Success; + Require(ucfpos_getCategory_nid_postfix(position, &error) == -9 && ucfpos_getField_nid_postfix(position, &error) == 3, "a set error changes nothing"); + ucfpos_close_nid_postfix(position); +} + +void Handles() { + std::int32_t error = Success; + Require(ucfpos_getCategory_nid_postfix(nullptr, &error) == 0 && error == IllegalArgument, "null handle"); + error = Success; + ucfpos_constrainCategory_nid_postfix(nullptr, 4, &error); + Require(error == IllegalArgument, "null handle constraint"); + alignas(8) std::array foreign{}; + auto* forged = reinterpret_cast(foreign.data()); + error = Success; + Require(ucfpos_getField_nid_postfix(forged, &error) == 0 && error == InvalidFormat, "foreign memory is rejected"); + error = Success; + std::int32_t start = 21, limit = 22; + ucfpos_getIndexes_nid_postfix(forged, &start, &limit, &error); + Require(start == 21 && limit == 22 && error == InvalidFormat, "foreign memory indexes"); + ucfpos_close_nid_postfix(nullptr); + ucfpos_close_nid_postfix(forged); + auto* position = ucfpos_open_nid_postfix(&error); + Require(Rejects([&] { ucfpos_getCategory_nid_postfix(position, nullptr); }), "null error pointer"); + Require(Rejects([&] { ucfpos_constrainField_nid_postfix(position, 1, 2, nullptr); }), "null error pointer in a constraint"); + error = Success; + Require(Rejects([&] { ucfpos_getIndexes_nid_postfix(position, nullptr, &start, &error); }), "null start output"); + Require(Rejects([&] { ucfpos_getIndexes_nid_postfix(position, &start, nullptr, &error); }), "null limit output"); + ucfpos_close_nid_postfix(position); +} + +} + +int main() { + Defaults(); + Constraints(); + Handles(); + return 0; +} diff --git a/core/libs/tests/GuestIcuErrorName.cpp b/core/libs/tests/GuestIcuErrorName.cpp new file mode 100644 index 0000000000..2b1a7a558c --- /dev/null +++ b/core/libs/tests/GuestIcuErrorName.cpp @@ -0,0 +1,60 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr const char* Bogus = "[BOGUS UErrorCode]"; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "ICU error name: %s\n", message); + std::abort(); +} + +bool Named(std::int32_t code, const char* expected) { + const char* name = u_errorName_nid_postfix(code); + return name && std::strcmp(name, expected) == 0; +} + +void Boundaries() { + Require(Named(-129, Bogus) && Named(-128, "U_USING_FALLBACK_WARNING") && Named(-124, "U_STRING_NOT_TERMINATED_WARNING"), "warning start"); + Require(Named(-120, "U_PLUGIN_CHANGED_LEVEL_WARNING") && Named(-119, Bogus) && Named(-1, Bogus), "warning limit"); + Require(Named(0, "U_ZERO_ERROR") && Named(1, "U_ILLEGAL_ARGUMENT_ERROR") && Named(15, "U_BUFFER_OVERFLOW_ERROR"), "standard errors"); + Require(Named(31, "U_INPUT_TOO_LONG_ERROR") && Named(32, Bogus) && Named(0xffff, Bogus), "standard limit"); + Require(Named(0x10000, "U_BAD_VARIABLE_DEFINITION") && Named(0x10022, "U_INVALID_FUNCTION") && Named(0x10023, Bogus), "transliterator errors"); + Require(Named(0x10100, "U_UNEXPECTED_TOKEN") && Named(0x1010d, "U_DUPLICATE_KEYWORD") && Named(0x10120, "U_MF_BAD_OPTION") && Named(0x10121, Bogus), "format errors"); + Require(Named(0x10200, "U_BRK_INTERNAL_ERROR") && Named(0x10206, "U_BRK_VARIABLE_REDFINITION") && Named(0x1020d, "U_BRK_MALFORMED_RULE_TAG") && Named(0x1020e, Bogus), "break iterator errors"); + Require(Named(0x10300, "U_REGEX_INTERNAL_ERROR") && Named(0x10315, "U_REGEX_INVALID_CAPTURE_GROUP_NAME") && Named(0x10316, Bogus), "regular expression errors"); + Require(Named(0x10400, "U_STRINGPREP_PROHIBITED_ERROR") && Named(0x10408, "U_IDNA_DOMAIN_NAME_TOO_LONG_ERROR") && Named(0x10409, Bogus), "IDNA errors"); + Require(Named(0x10500, "U_PLUGIN_TOO_HIGH") && Named(0x10501, "U_PLUGIN_DIDNT_SET_LEVEL") && Named(0x10502, Bogus), "plugin errors"); + Require(Named(std::numeric_limits::min(), Bogus) && Named(std::numeric_limits::max(), Bogus), "extreme codes"); +} + +void Coverage() { + std::set names; + std::int32_t named = 0; + for (std::int32_t code = -0x200; code < 0x10600; ++code) { + const char* name = u_errorName_nid_postfix(code); + Require(name != nullptr, "every code has a name"); + Require(name == u_errorName_nid_postfix(code), "names are static strings"); + if (std::strcmp(name, Bogus) == 0) continue; + ++named; + Require(std::strncmp(name, "U_", 2) == 0, "names start with U_"); + names.insert(name); + } + Require(named == 156 && names.size() == 156, "156 distinct named codes"); +} + +} + +int main() { + Boundaries(); + Coverage(); + return 0; +} diff --git a/core/libs/tests/GuestIcuNumeric.cpp b/core/libs/tests/GuestIcuNumeric.cpp new file mode 100644 index 0000000000..4658595f1c --- /dev/null +++ b/core/libs/tests/GuestIcuNumeric.cpp @@ -0,0 +1,141 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint64_t NaNBits = 0x7ff8000000000000; +constexpr std::uint64_t InfinityBits = 0x7ff0000000000000; +constexpr std::uint64_t NegativeZeroBits = 0x8000000000000000; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "ICU numeric: %s\n", message); + std::abort(); +} + +double FromBits(std::uint64_t bits) { + return std::bit_cast(bits); +} + +std::uint64_t Bits(double value) { + return std::bit_cast(value); +} + +template +bool Rejects(F call) { + try { + call(); + } catch (const std::invalid_argument&) { + return true; + } + return false; +} + +void Classification() { + const double infinity = std::numeric_limits::infinity(); + for (const std::uint64_t bits : std::array{NaNBits, 0x7ff0000000000001ull, 0x7fffffffffffffffull, 0xfff8000000000000ull, 0xfff0000000000001ull}) { + Require(uprv_isNaN_nid_postfix(FromBits(bits)) == 1, "NaN patterns are NaN"); + Require(uprv_isInfinite_nid_postfix(FromBits(bits)) == 0, "NaN patterns are not infinite"); + Require(uprv_isPositiveInfinity_nid_postfix(FromBits(bits)) == 0 && uprv_isNegativeInfinity_nid_postfix(FromBits(bits)) == 0, "NaN has no infinite sign"); + } + for (const double value : {0.0, -0.0, 1.0, -1.0, std::numeric_limits::max(), std::numeric_limits::denorm_min(), -std::numeric_limits::max()}) { + Require(uprv_isNaN_nid_postfix(value) == 0, "finite values are not NaN"); + Require(uprv_isInfinite_nid_postfix(value) == 0, "finite values are not infinite"); + Require(uprv_isPositiveInfinity_nid_postfix(value) == 0 && uprv_isNegativeInfinity_nid_postfix(value) == 0, "finite values have no infinite sign"); + } + Require(uprv_isInfinite_nid_postfix(infinity) == 1 && uprv_isInfinite_nid_postfix(-infinity) == 1, "infinities are infinite"); + Require(uprv_isNaN_nid_postfix(infinity) == 0 && uprv_isNaN_nid_postfix(-infinity) == 0, "infinities are not NaN"); + Require(uprv_isPositiveInfinity_nid_postfix(infinity) == 1 && uprv_isPositiveInfinity_nid_postfix(-infinity) == 0, "positive infinity"); + Require(uprv_isNegativeInfinity_nid_postfix(-infinity) == 1 && uprv_isNegativeInfinity_nid_postfix(infinity) == 0, "negative infinity"); + Require(Bits(uprv_getNaN_nid_postfix()) == NaNBits, "NaN bit pattern"); + Require(Bits(uprv_getInfinity_nid_postfix()) == InfinityBits, "infinity bit pattern"); +} + +void LibraryMath() { + Require(uprv_floor_nid_postfix(-2.5) == -3.0 && uprv_floor_nid_postfix(2.5) == 2.0, "floor"); + Require(uprv_ceil_nid_postfix(-2.5) == -2.0 && uprv_ceil_nid_postfix(2.5) == 3.0, "ceil"); + Require(Bits(uprv_fabs_nid_postfix(-0.0)) == 0 && uprv_fabs_nid_postfix(-7.25) == 7.25, "fabs"); + double integral = 0; + Require(uprv_modf_nid_postfix(-3.25, &integral) == -0.25 && integral == -3.0, "modf"); + Require(Rejects([] { uprv_modf_nid_postfix(1.5, nullptr); }), "modf rejects a null integral pointer"); + Require(uprv_fmod_nid_postfix(7.5, 2.0) == 1.5 && uprv_fmod_nid_postfix(-7.5, 2.0) == -1.5, "fmod"); + Require(uprv_pow_nid_postfix(2.0, 10.0) == 1024.0 && uprv_pow_nid_postfix(-2.0, 3.0) == -8.0, "pow"); + Require(uprv_pow10_nid_postfix(3) == 1000.0 && uprv_pow10_nid_postfix(0) == 1.0, "pow10"); + Require(uprv_pow10_nid_postfix(-2) == std::pow(10.0, -2.0) && uprv_pow10_nid_postfix(400) == std::numeric_limits::infinity(), "pow10 of negative and large exponents"); + Require(uprv_log_nid_postfix(1.0) == 0.0 && uprv_log_nid_postfix(0.0) == -std::numeric_limits::infinity(), "log"); + Require(uprv_isNaN_nid_postfix(uprv_log_nid_postfix(-1.0)) == 1, "log of a negative number"); + Require(uprv_maxMantissa_nid_postfix() == 18014398509481983.0, "maximum mantissa is 2^54 - 1"); +} + +void Rounding() { + Require(uprv_round_nid_postfix(2.5) == 3.0 && uprv_round_nid_postfix(-2.5) == -2.0, "round half up"); + Require(uprv_round_nid_postfix(-2.6) == -3.0 && uprv_round_nid_postfix(2.4) == 2.0, "round to nearest"); + Require(uprv_round_nid_postfix(0.49999999999999994) == 1.0, "round adds one half before flooring"); + Require(Bits(uprv_round_nid_postfix(-0.5)) == 0, "round of minus one half is positive zero"); + Require(uprv_trunc_nid_postfix(3.7) == 3.0 && uprv_trunc_nid_postfix(-3.7) == -3.0, "trunc"); + Require(Bits(uprv_trunc_nid_postfix(-0.5)) == NegativeZeroBits && Bits(uprv_trunc_nid_postfix(-0.0)) == NegativeZeroBits, "trunc keeps the sign of zero"); + Require(Bits(uprv_trunc_nid_postfix(0.5)) == 0, "trunc of a positive fraction"); + Require(Bits(uprv_trunc_nid_postfix(std::numeric_limits::infinity())) == InfinityBits, "trunc of positive infinity"); + Require(Bits(uprv_trunc_nid_postfix(-std::numeric_limits::infinity())) == InfinityBits, "trunc of negative infinity returns positive infinity"); + Require(Bits(uprv_trunc_nid_postfix(FromBits(0xfff8000000000001ull))) == NaNBits, "trunc returns the canonical NaN"); +} + +void Extremes() { + const double nan = FromBits(0xfff8000000000001ull); + Require(Bits(uprv_fmax_nid_postfix(nan, 1.0)) == NaNBits && Bits(uprv_fmax_nid_postfix(1.0, nan)) == NaNBits, "fmax with NaN"); + Require(Bits(uprv_fmin_nid_postfix(nan, 1.0)) == NaNBits && Bits(uprv_fmin_nid_postfix(1.0, nan)) == NaNBits, "fmin with NaN"); + Require(uprv_fmax_nid_postfix(1.0, 2.0) == 2.0 && uprv_fmax_nid_postfix(2.0, 1.0) == 2.0, "fmax"); + Require(uprv_fmin_nid_postfix(1.0, 2.0) == 1.0 && uprv_fmin_nid_postfix(2.0, 1.0) == 1.0, "fmin"); + Require(uprv_fmax_nid_postfix(-std::numeric_limits::infinity(), 3.0) == 3.0, "fmax with negative infinity"); + Require(Bits(uprv_fmax_nid_postfix(-0.0, 0.0)) == 0 && Bits(uprv_fmax_nid_postfix(0.0, 0.0)) == 0, "fmax prefers positive zero after negative zero"); + Require(Bits(uprv_fmax_nid_postfix(0.0, -0.0)) == NegativeZeroBits && Bits(uprv_fmax_nid_postfix(-0.0, -0.0)) == NegativeZeroBits, "fmax returns its second zero"); + Require(Bits(uprv_fmin_nid_postfix(0.0, -0.0)) == NegativeZeroBits && Bits(uprv_fmin_nid_postfix(-0.0, 0.0)) == NegativeZeroBits, "fmin prefers negative zero"); + Require(Bits(uprv_fmin_nid_postfix(0.0, 0.0)) == 0, "fmin of positive zeros"); + constexpr std::int32_t low = std::numeric_limits::min(); + constexpr std::int32_t high = std::numeric_limits::max(); + Require(uprv_max_nid_postfix(low, high) == high && uprv_max_nid_postfix(high, low) == high && uprv_max_nid_postfix(-4, -4) == -4, "max"); + Require(uprv_min_nid_postfix(low, high) == low && uprv_min_nid_postfix(high, low) == low && uprv_min_nid_postfix(7, 7) == 7, "min"); +} + +void Overflow() { + constexpr std::int32_t low = std::numeric_limits::min(); + constexpr std::int32_t high = std::numeric_limits::max(); + std::int32_t result = 0; + Require(uprv_add32_overflow_nid_postfix(2, 3, &result) == 0 && result == 5, "addition"); + Require(uprv_add32_overflow_nid_postfix(high, 1, &result) == 1 && result == low, "addition overflow wraps"); + Require(uprv_add32_overflow_nid_postfix(low, -1, &result) == 1 && result == high, "addition underflow wraps"); + Require(uprv_add32_overflow_nid_postfix(high, low, &result) == 0 && result == -1, "addition of the extremes"); + Require(uprv_mul32_overflow_nid_postfix(-46340, 46340, &result) == 0 && result == -2147395600, "multiplication"); + Require(uprv_mul32_overflow_nid_postfix(65536, 65536, &result) == 1 && result == 0, "multiplication overflow wraps"); + Require(uprv_mul32_overflow_nid_postfix(-46341, 46341, &result) == 1 && result == 2147479015, "negative multiplication overflow wraps"); + Require(uprv_mul32_overflow_nid_postfix(low, -1, &result) == 1 && result == low, "negating the minimum overflows"); + Require(Rejects([] { uprv_add32_overflow_nid_postfix(1, 2, nullptr); }), "addition rejects a null result"); + Require(Rejects([] { uprv_mul32_overflow_nid_postfix(1, 2, nullptr); }), "multiplication rejects a null result"); +} + +void MaximumPointer() { + constexpr std::uintptr_t top = std::numeric_limits::max(); + const auto limit = [](std::uintptr_t base) { return reinterpret_cast(uprv_maximumPtr_nid_postfix(reinterpret_cast(base))); }; + Require(limit(0) == 0x7fffffff && limit(0x1000) == 0x80000fff, "maximum pointer adds 2^31 - 1"); + Require(limit(top - 0x7fffffff) == top, "maximum pointer reaching the top"); + Require(limit(top - 0x7ffffffe) == top && limit(top) == top, "maximum pointer saturates instead of wrapping"); +} + +} + +int main() { + Classification(); + LibraryMath(); + Rounding(); + Extremes(); + Overflow(); + MaximumPointer(); + return 0; +} diff --git a/core/libs/tests/GuestIcuTokenize.cpp b/core/libs/tests/GuestIcuTokenize.cpp new file mode 100644 index 0000000000..b0aab05bd2 --- /dev/null +++ b/core/libs/tests/GuestIcuTokenize.cpp @@ -0,0 +1,266 @@ +#include "prx/libSceIcu/libSceIcu.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include "prx/libkernel/libkernel.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "ICU tokenization check failed at line %u\n", location.line()); + std::abort(); +} + +template +void Throws(TCall call) { + try { call(); } + catch (const std::exception&) { return; } + Require(false); +} + +void SetSearches() { + const std::array text{'a', 0xd83d, 0xde00, 'b', 0xd800, 'c', 0xdc00, 'd', 0}; + const auto before = text; + const std::array set{0xd83d, 0xde00, 0xd800, 0}; + const std::array lead{0xd83d, 0}; + const std::array trail{0xdc00, 0}; + Require(u_strspn_nid_postfix(text.data(), set.data()) == 0); + Require(u_strspn_nid_postfix(text.data() + 1, set.data()) == 2); + Require(u_strcspn_nid_postfix(text.data(), set.data()) == 1); + Require(u_strpbrk_nid_postfix(text.data(), set.data()) == text.data() + 1); + Require(u_strcspn_nid_postfix(text.data(), lead.data()) == 8); + Require(u_strpbrk_nid_postfix(text.data(), lead.data()) == nullptr); + Require(u_strcspn_nid_postfix(text.data(), trail.data()) == 6); + Require(u_strpbrk_nid_postfix(text.data(), trail.data()) == text.data() + 6); + Require(u_strspn_nid_postfix(text.data(), u"abcd") == 1); + Require(u_strspn_nid_postfix(u"", set.data()) == 0); + Require(u_strcspn_nid_postfix(u"", set.data()) == 0); + Require(u_strspn_nid_postfix(text.data(), u"") == 0); + Require(u_strcspn_nid_postfix(text.data(), u"") == 8); + Require(u_strpbrk_nid_postfix(text.data(), u"") == nullptr); + Require(text == before); + Throws([&] { u_strspn_nid_postfix(nullptr, set.data()); }); + Throws([&] { u_strcspn_nid_postfix(text.data(), nullptr); }); + Throws([&] { u_strpbrk_nid_postfix(nullptr, nullptr); }); +} + +void ReverseSearch() { + const std::array text{'a', 0, 0xd800, 0xdc00, 'b', 0xd800, 0, 0xdc00, 'a'}; + const auto before = text; + Require(u_memrchr32_nid_postfix(text.data(), 'a', 9) == text.data() + 8); + Require(u_memrchr32_nid_postfix(text.data(), 0, 9) == text.data() + 6); + Require(u_memrchr32_nid_postfix(text.data(), 0x10000, 9) == text.data() + 2); + Require(u_memrchr32_nid_postfix(text.data(), 0xd800, 9) == text.data() + 5); + Require(u_memrchr32_nid_postfix(text.data(), 0xdc00, 9) == text.data() + 7); + Require(u_memrchr32_nid_postfix(text.data(), 0xd800, 3) == text.data() + 2); + Require(u_memrchr32_nid_postfix(text.data(), 0x10000, 3) == nullptr); + Require(u_memrchr32_nid_postfix(text.data() + 3, 0xdc00, 1) == text.data() + 3); + Require(u_memrchr32_nid_postfix(nullptr, 'a', 0) == nullptr); + Require(u_memrchr32_nid_postfix(nullptr, 'a', -1) == nullptr); + Require(u_memrchr32_nid_postfix(nullptr, 0x10000, 1) == nullptr); + Require(u_memrchr32_nid_postfix(nullptr, 0xd800, 7) == nullptr); + Require(u_memrchr32_nid_postfix(nullptr, 0xdfff, 7) == nullptr); + Require(u_memrchr32_nid_postfix(nullptr, -1, 7) == nullptr); + Require(u_memrchr32_nid_postfix(nullptr, 0x110000, 7) == nullptr); + Require(u_memrchr32_nid_postfix(nullptr, std::numeric_limits::max(), 7) == nullptr); + Throws([&] { u_memrchr32_nid_postfix(nullptr, 'a', 1); }); + Require(text == before); +} + +void Tokens() { + std::array text{0xa55a, ',', 'a', ':', 'b', ',', 'c', 0, 0xa55a, 0xa55a}; + char16_t* state = nullptr; + Require(u_strtok_r_nid_postfix(text.data() + 1, u",:", &state) == text.data() + 2); + Require(state == text.data() + 4 && text[3] == 0 && text[1] == ','); + Require(u_strtok_r_nid_postfix(nullptr, u",", &state) == text.data() + 4); + Require(state == text.data() + 6 && text[5] == 0); + Require(u_strtok_r_nid_postfix(nullptr, u"", &state) == text.data() + 6); + Require(state == nullptr); + Require(u_strtok_r_nid_postfix(nullptr, nullptr, &state) == nullptr); + Require(text.front() == 0xa55a && text[8] == 0xa55a && text[9] == 0xa55a); + std::array delimitersOnly{',', ',', ',', 0}; + Require(u_strtok_r_nid_postfix(delimitersOnly.data(), u",", &state) == nullptr && state == nullptr); + const std::array unchanged{',', ',', ',', 0}; + Require(delimitersOnly == unchanged); + Throws([&] { u_strtok_r_nid_postfix(text.data(), u",", nullptr); }); + const auto before = text; + Throws([&] { u_strtok_r_nid_postfix(text.data(), nullptr, &state); }); + Require(text == before && state == nullptr); +} + +void SupplementaryTokens() { + std::array text{0xa55a, 'a', 0xd83d, 0xde00, 'b', 0, 0xa55a}; + const char16_t delimiter[]{0xd83d, 0xde00, 0}; + char16_t* state = nullptr; + Require(u_strtok_r_nid_postfix(text.data() + 1, delimiter, &state) == text.data() + 1); + Require(text[2] == 0 && text[3] == 0xde00 && state == text.data() + 3); + Require(u_strtok_r_nid_postfix(nullptr, delimiter, &state) == text.data() + 3); + Require(state == nullptr && text.front() == 0xa55a && text.back() == 0xa55a); + std::array leading{0xd83d, 0xde00, 'a', 0}; + Require(u_strtok_r_nid_postfix(leading.data(), delimiter, &state) == leading.data() + 2 && state == nullptr); + Require(leading[0] == 0xd83d && leading[1] == 0xde00); + std::array pair{0xd83d, 0xde00, 'b', 0}; + const char16_t unpaired[]{0xd83d, 0}; + Require(u_strtok_r_nid_postfix(pair.data(), unpaired, &state) == pair.data() && state == nullptr); + Require(pair[0] == 0xd83d && pair[1] == 0xde00); +} + +void AliasedDelimiters() { + std::array text{'a', ',', 'b', ',', 'c', 0, 0xa55a}; + auto* delimiters = text.data() + 3; + char16_t* state = nullptr; + Require(u_strtok_r_nid_postfix(text.data(), delimiters, &state) == text.data()); + Require(state == text.data() + 2 && text[1] == 0 && delimiters[0] == ','); + Require(u_strtok_r_nid_postfix(nullptr, delimiters, &state) == text.data() + 2); + Require(state == text.data() + 4 && delimiters[0] == 0); + Require(u_strtok_r_nid_postfix(nullptr, delimiters, &state) == text.data() + 4); + Require(state == nullptr && text[4] == 'c' && text.back() == 0xa55a); +} + +class GuestStorage { +public: + static constexpr std::size_t Page = 0x4000; + + GuestStorage() { + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, Page * 3, 0, 0, &physical) == 0); + Require(sceKernelMapDirectMemory(&mapping, Page * 3, 3, 0, physical, 0) == 0); + Require(GuestArena::GuestArenaContains_nid_postfix(mapping, Page * 3)); + std::fill_n(static_cast(mapping), Page * 3, 0xa5); + } + + ~GuestStorage() { + Require(sceKernelMunmap(reinterpret_cast(mapping), Page * 3) == 0); + Require(sceKernelReleaseDirectMemory(physical, Page * 3) == 0); + } + + char16_t* Text() const { return reinterpret_cast(static_cast(mapping) + 16); } + char16_t** State() const { return reinterpret_cast(static_cast(mapping) + Page + 16); } + char16_t* Delimiters() const { return reinterpret_cast(static_cast(mapping) + Page * 2 + 16); } + + void Protect(std::size_t page, int protection) { + Require(sceKernelMprotect(static_cast(mapping) + Page * page, Page, protection) == 0); + } + + void CheckCanaries() const { + const auto* bytes = static_cast(mapping); + for (const auto offset : {std::size_t{15}, Page + 15, Page + 16 + sizeof(char16_t*), Page * 2 + 15, Page * 3 - 1}) + Require(bytes[offset] == 0xa5); + } + +private: + std::int64_t physical = 0; + void* mapping = nullptr; +}; + +void GuestMappedTokens() { + GuestStorage storage; + auto* text = storage.Text(); + auto** state = storage.State(); + auto* delimiters = storage.Delimiters(); + const std::array initial{0xa55a, ',', 'a', ':', 'b', ',', 'c', 0, 0xa55a}; + std::copy(initial.begin(), initial.end(), text); + const std::array separators{',', ':', 0}; + std::copy(separators.begin(), separators.end(), delimiters); + *state = nullptr; + storage.Protect(2, 1); + Require(u_strtok_r_nid_postfix(text + 1, delimiters, state) == text + 2); + Require(*state == text + 4 && text[3] == 0 && text[1] == ','); + Require(u_strtok_r_nid_postfix(nullptr, delimiters, state) == text + 4); + Require(*state == text + 6 && text[5] == 0); + Require(u_strtok_r_nid_postfix(nullptr, delimiters, state) == text + 6 && *state == nullptr); + Require(u_strtok_r_nid_postfix(nullptr, nullptr, state) == nullptr); + Require(text[0] == 0xa55a && text[8] == 0xa55a); + Require(std::equal(separators.begin(), separators.end(), delimiters)); + storage.CheckCanaries(); + storage.Protect(2, 3); + const std::array supplementary{0xa55a, 'a', 0xd83d, 0xde00, 'b', 0, 0xa55a}; + std::copy(supplementary.begin(), supplementary.end(), text); + const std::array pair{0xd83d, 0xde00, 0}; + std::copy(pair.begin(), pair.end(), delimiters); + storage.Protect(2, 1); + Require(u_strtok_r_nid_postfix(text + 1, delimiters, state) == text + 1); + Require(text[2] == 0 && text[3] == 0xde00 && *state == text + 3); + Require(u_strtok_r_nid_postfix(nullptr, delimiters, state) == text + 3 && *state == nullptr); + Require(text[0] == 0xa55a && text[6] == 0xa55a); + Require(std::equal(pair.begin(), pair.end(), delimiters)); + storage.CheckCanaries(); +} + +void ReadOnlyGuestTokens() { +#ifdef _WIN32 + GuestStorage storage; + auto* text = storage.Text(); + auto** state = storage.State(); + const std::array initial{0xa55a, 'a', ',', 'b', 0}; + std::copy(initial.begin(), initial.end(), text); + *state = nullptr; + storage.Protect(1, 1); + { + GuestArena::HostWrite rejected(state, sizeof(*state)); + Require(!rejected.Open()); + } + Require(u_strtok_r_nid_postfix(nullptr, nullptr, state) == nullptr); + Throws([&] { u_strtok_r_nid_postfix(text + 1, u",", state); }); + Require(*state == nullptr && std::equal(initial.begin(), initial.end(), text)); + storage.Protect(1, 3); + *state = text + 1; + storage.Protect(1, 1); + Throws([&] { u_strtok_r_nid_postfix(nullptr, u",", state); }); + Require(*state == text + 1 && std::equal(initial.begin(), initial.end(), text)); + storage.Protect(1, 3); + storage.Protect(0, 1); + { + GuestArena::HostWrite rejected(text + 2, sizeof(*text)); + Require(!rejected.Open()); + } + Throws([&] { u_strtok_r_nid_postfix(nullptr, u",", state); }); + Require(*state == text + 1 && std::equal(initial.begin(), initial.end(), text)); + *state = nullptr; + Throws([&] { u_strtok_r_nid_postfix(text + 1, u",", state); }); + Require(*state == text + 1 && std::equal(initial.begin(), initial.end(), text)); + Require(u_strtok_r_nid_postfix(nullptr, u"", state) == text + 1 && *state == nullptr); + Require(std::equal(initial.begin(), initial.end(), text)); + Require(u_strtok_r_nid_postfix(text + 2, u",b", state) == nullptr && *state == nullptr); + Require(u_strtok_r_nid_postfix(text + 4, u",", state) == nullptr && *state == nullptr); + Require(std::equal(initial.begin(), initial.end(), text)); + storage.CheckCanaries(); +#endif +} + +void IndependentState() { + const auto tokenize = [](char16_t separator) { + for (unsigned iteration = 0; iteration < 1000; ++iteration) { + std::array text{'a', separator, 'b', 0}; + const char16_t delimiters[]{separator, 0}; + char16_t* state = nullptr; + Require(u_strtok_r_nid_postfix(text.data(), delimiters, &state) == text.data()); + Require(u_strtok_r_nid_postfix(nullptr, delimiters, &state) == text.data() + 2); + Require(state == nullptr && text[1] == 0 && text[3] == 0); + } + }; + std::thread first(tokenize, ','); + std::thread second(tokenize, ':'); + first.join(); + second.join(); +} + +} // namespace + +int main() { + SetSearches(); + ReverseSearch(); + Tokens(); + SupplementaryTokens(); + AliasedDelimiters(); + GuestMappedTokens(); + ReadOnlyGuestTokens(); + IndependentState(); +} diff --git a/core/libs/tests/GuestIcuVersion.cpp b/core/libs/tests/GuestIcuVersion.cpp index 31c85b68c5..26caf49bbc 100644 --- a/core/libs/tests/GuestIcuVersion.cpp +++ b/core/libs/tests/GuestIcuVersion.cpp @@ -207,6 +207,14 @@ void VersionToString() { u_versionToString_nid_postfix(version.data(), nullptr); } +void GetVersion() { + Version version{9, 9, 9, 9}; + u_getVersion_nid_postfix(version.data()); + Require(version == Version{78, 1, 0, 0}, "library version is 78.1.0.0"); + Require(ToString(version) == "78.1", "library version string"); + u_getVersion_nid_postfix(nullptr); +} + void RoundTrip() { const auto written = [](std::uint32_t part) { return part < 100 || part % 100 >= 10; }; for (std::uint32_t part = 0; part < 256; ++part) { @@ -226,6 +234,7 @@ int main() { VersionFromString(); VersionFromUString(); VersionToString(); + GetVersion(); RoundTrip(); return 0; } diff --git a/core/libs/tests/GuestImeComposition.cpp b/core/libs/tests/GuestImeComposition.cpp new file mode 100644 index 0000000000..d1377020bb --- /dev/null +++ b/core/libs/tests/GuestImeComposition.cpp @@ -0,0 +1,213 @@ +#include "prx/libSceIme/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libSceIme/include/ImeInput.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr uint32_t EventUpdateText = 1; +constexpr uint32_t EventUpdateCaret = 2; +constexpr uint32_t EventPressClose = 4; +constexpr uint32_t EventPressEnter = 5; + +struct Recorded { + uint32_t id; + std::u16string text; +}; + +std::vector g_events; +int g_filterCalls = 0; + +void APS5_VABI Record(void*, const ImeEvent* event) { + Recorded recorded{event->id, {}}; + if (event->id == EventUpdateText || event->id == EventPressEnter || event->id == EventPressClose) recorded.text = event->param.text.str; + g_events.push_back(recorded); +} + +int32_t APS5_VABI CountingFilter(char16_t* outText, uint32_t* outTextLength, const char16_t* sourceText, uint32_t sourceTextLength) { + ++g_filterCalls; + for (uint32_t i = 0; i < sourceTextLength; ++i) outText[i] = sourceText[i]; + *outTextLength = sourceTextLength; + return 0; +} + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +struct Buffers { + char16_t text[2049] = {}; + alignas(16) unsigned char work[0x5000] = {}; + int arg = 0; +}; + +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; + param.posx = 100.0f; + param.posy = 200.0f; + param.work = buffers.work; + param.arg = &buffers.arg; + param.handler = Record; + return param; +} + +std::vector Drain() { + g_events.clear(); + Require(sceImeUpdate(Record) == 0, "update failed"); + return g_events; +} + +void Publish(ImeHostInputKind kind, const char* text = "", std::int32_t start = 0, std::int32_t length = 0) { + ImeHostInput input{}; + input.kind = kind; + std::strncpy(input.text, text, sizeof(input.text) - 1); + input.start = start; + input.length = length; + ImePublishInput_nid_postfix(input); +} + +const char16_t* Work(const Buffers& buffers) { + return reinterpret_cast(buffers.work); +} + +void CheckLifecycle() { + Buffers buffers; + std::memcpy(buffers.text, u"ab", sizeof(u"ab")); + Param param = MakeParam(buffers, 16); + param.filter = reinterpret_cast(&CountingFilter); + Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "open"); + Drain(); + + Publish(ImeHostInputKind::Preedit, "\xe3\x81\x93", 1, 0); + Require(Drain().empty(), "a preedit queued an event"); + Publish(ImeHostInputKind::Preedit, "\xe3\x81\x93\xe3\x82\x93", 2, 0); + Require(Drain().empty(), "a second preedit queued an event"); + Require(std::u16string(buffers.text) == u"ab" && std::u16string(Work(buffers)) == u"ab", "a preedit reached the guest buffers"); + Require(g_filterCalls == 0, "a preedit reached the text filter"); + + Publish(ImeHostInputKind::Preedit, ""); + Publish(ImeHostInputKind::Text, "\xe3\x81\x93\xe3\x82\x93"); + const auto committed = Drain(); + Require(committed.size() == 1 && committed[0].id == EventUpdateText && committed[0].text == u"ab\u3053\u3093", "the commit is one text update"); + Require(std::u16string(buffers.text) == u"ab\u3053\u3093", "the commit reached the input buffer"); + Require(g_filterCalls == 1, "the commit reached the text filter once"); + + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Publish(ImeHostInputKind::Text, "\xe4\xbb\x8a"); + Publish(ImeHostInputKind::Backspace); + const auto afterCommit = Drain(); + Require(afterCommit.size() == 2 && afterCommit[1].text == u"ab\u3053\u3093", "a commit ends the composition so Backspace edits again"); + Require(sceImeClose_nid_postfix() == 0, "close"); +} + +void CheckCancel() { + Buffers buffers; + std::memcpy(buffers.text, u"ab", sizeof(u"ab")); + Param param = MakeParam(buffers, 16); + Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "open for cancel"); + Drain(); + + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Publish(ImeHostInputKind::Escape); + Require(Drain().empty(), "Escape while composing closed the panel"); + Publish(ImeHostInputKind::Escape); + const auto closed = Drain(); + Require(closed.size() == 1 && closed[0].id == EventPressClose, "the next Escape reaches the title"); + + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Publish(ImeHostInputKind::Enter); + Require(Drain().empty(), "Enter while composing accepted the text"); + Publish(ImeHostInputKind::Enter); + const auto entered = Drain(); + Require(entered.size() == 1 && entered[0].id == EventPressEnter, "the next Enter reaches the title"); + + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Publish(ImeHostInputKind::Preedit, ""); + Publish(ImeHostInputKind::Escape); + const auto emptied = Drain(); + Require(emptied.size() == 1 && emptied[0].id == EventPressClose, "an emptied composition no longer swallows Escape"); + Require(std::u16string(buffers.text) == u"ab", "a discarded composition changed the buffer"); + Require(sceImeClose_nid_postfix() == 0, "close after cancel"); +} + +void CheckEditKeysBelongToTheInputMethod() { + Buffers buffers; + std::memcpy(buffers.text, u"abc", sizeof(u"abc")); + Param param = MakeParam(buffers, 16); + Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "open for edit keys"); + Drain(); + + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + for (const auto kind : {ImeHostInputKind::Backspace, ImeHostInputKind::Delete, ImeHostInputKind::Left, ImeHostInputKind::Right, ImeHostInputKind::Home, ImeHostInputKind::End}) Publish(kind); + Require(Drain().empty() && std::u16string(buffers.text) == u"abc", "an editing key acted on the text while composing"); + + Publish(ImeHostInputKind::Preedit, ""); + Publish(ImeHostInputKind::Left); + const auto moved = Drain(); + Require(moved.size() == 1 && moved[0].id == EventUpdateCaret, "Left works once the composition is gone"); + Publish(ImeHostInputKind::Backspace); + const auto erased = Drain(); + Require(erased.size() == 1 && erased[0].text == u"ac", "Backspace works once the composition is gone"); + Require(sceImeClose_nid_postfix() == 0, "close after edit keys"); +} + +void CheckCloseDiscardsTheComposition() { + Buffers buffers; + std::memcpy(buffers.text, u"ab", sizeof(u"ab")); + Param param = MakeParam(buffers, 16); + Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "first open"); + Drain(); + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Drain(); + Require(sceImeClose_nid_postfix() == 0, "close with a composition"); + Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "second open"); + Drain(); + Publish(ImeHostInputKind::Backspace); + const auto erased = Drain(); + Require(erased.size() == 1 && erased[0].text == u"a", "a composition survived the close"); + Require(sceImeClose_nid_postfix() == 0, "close the second session"); +} + +void CheckMalformedPreedit() { + Buffers buffers; + Param param = MakeParam(buffers, 16); + Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "open for malformed"); + Drain(); + char cut[sizeof(ImeHostInput::text)] = {}; + std::memset(cut, 'a', sizeof(cut)); + cut[30] = '\xe3'; + cut[31] = '\x81'; + ImeHostInput input{}; + input.kind = ImeHostInputKind::Preedit; + std::memcpy(input.text, cut, sizeof(cut)); + ImePublishInput_nid_postfix(input); + Publish(ImeHostInputKind::Preedit, "\xff\xfe\x80", 0, 0); + Require(Drain().empty() && buffers.text[0] == u'\0', "a malformed preedit changed the session"); + Publish(ImeHostInputKind::Preedit, ""); + Publish(ImeHostInputKind::Text, "z"); + const auto typed = Drain(); + Require(typed.size() == 1 && typed[0].text == u"z", "typing still works after a malformed preedit"); + Require(sceImeClose_nid_postfix() == 0, "close after malformed"); +} + +} + +int main() { + CheckLifecycle(); + CheckCancel(); + CheckEditKeysBelongToTheInputMethod(); + CheckCloseDiscardsTheComposition(); + CheckMalformedPreedit(); +} diff --git a/core/libs/tests/GuestImeDialog.cpp b/core/libs/tests/GuestImeDialog.cpp index 0dcba47a4a..229ca4cfe5 100644 --- a/core/libs/tests/GuestImeDialog.cpp +++ b/core/libs/tests/GuestImeDialog.cpp @@ -80,6 +80,8 @@ Param DialogParam(char16_t* buffer, std::uint32_t maxLength) { } std::uint32_t EndStatus() { + Require(sceImeDialogGetResult(nullptr) == ErrorNotOpened); + Require(sceImeDialogGetResult(reinterpret_cast(0x10)) == ErrorNotOpened); Result result{}; Require(sceImeDialogGetResult(&result) == 0); return result.endstatus; diff --git a/core/libs/tests/GuestImeDialogComposition.cpp b/core/libs/tests/GuestImeDialogComposition.cpp new file mode 100644 index 0000000000..e9eea007e9 --- /dev/null +++ b/core/libs/tests/GuestImeDialogComposition.cpp @@ -0,0 +1,191 @@ +#include "prx/libSceImeDialog/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libSceIme/include/ImeInput.hpp" +#include "prx/libSceIme/include/ImeKeyboardView.hpp" + +#include +#include +#include +#include +#include + +namespace { + +constexpr int StatusRunning = 1; +constexpr int StatusFinished = 2; +constexpr std::uint32_t EndStatusOk = 0; +constexpr std::uint32_t EndStatusUserCanceled = 1; + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +void Publish(ImeHostInputKind kind, const char* text = "", std::int32_t start = 0, std::int32_t length = 0) { + ImeHostInput input{}; + input.kind = kind; + std::strncpy(input.text, text, sizeof(input.text) - 1); + input.start = start; + input.length = length; + ImePublishInput_nid_postfix(input); +} + +Param DialogParam(char16_t* buffer, std::uint32_t maxLength) { + Param param{}; + param.max_text_length = maxLength; + param.input_text_buffer = buffer; + return param; +} + +ImeKeyboardView View() { + ImeKeyboardView view; + Require(ImeReadKeyboardView_nid_postfix(view), "the dialog shows no keyboard"); + return view; +} + +std::uint32_t EndStatus() { + Result result{}; + Require(sceImeDialogGetResult(&result) == 0, "no result"); + return result.endstatus; +} + +void Open(const Param& param) { + Require(sceImeDialogInit(¶m, nullptr) == 0, "init"); + Require(sceImeDialogGetStatus() == StatusRunning, "not running"); +} + +void Close() { + Require(sceImeDialogTerm() == 0, "term"); +} + +void CheckLifecycle() { + char16_t buffer[16] = u"ab"; + const Param param = DialogParam(buffer, 8); + Open(param); + + Require(View().preedit.empty(), "a fresh dialog shows a composition"); + Publish(ImeHostInputKind::Preedit, "\xe3\x81\x93", 1, 0); + auto view = View(); + Require(view.preedit == u"\u3053" && view.preeditCursor == 1 && view.preeditSelection == 0, "the first edit is shown"); + Require(view.text == u"ab" && view.caret == 2, "the preedit entered the committed text"); + Publish(ImeHostInputKind::Preedit, "\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab", 1, 2); + view = View(); + Require(view.preedit == u"\u3053\u3093\u306B" && view.preeditCursor == 1 && view.preeditSelection == 2, "the second edit replaces the first and keeps the selection"); + Require(view.text == u"ab", "a second edit entered the committed text"); + Require(std::u16string(buffer) == u"ab", "a preedit reached the guest buffer"); + + Publish(ImeHostInputKind::Text, "\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab"); + view = View(); + Require(view.preedit.empty() && view.preeditCursor == 0 && view.preeditSelection == 0, "a commit left the preedit shown"); + Require(view.text == u"ab\u3053\u3093\u306B" && view.caret == 5, "a commit is inserted at the caret"); + Require(std::u16string(buffer) == u"ab", "the title sees text before the dialog is accepted"); + + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Publish(ImeHostInputKind::Enter); + Require(sceImeDialogGetStatus() == StatusRunning, "Enter while composing accepted the dialog"); + Require(View().preedit.empty(), "Enter did not discard the composition"); + Publish(ImeHostInputKind::Enter); + Require(sceImeDialogGetStatus() == StatusFinished && EndStatus() == EndStatusOk, "the next Enter does not accept the dialog"); + Require(std::u16string(buffer) == u"ab\u3053\u3093\u306B", "a discarded preedit leaked into the accepted text"); + Close(); +} + +void CheckCancel() { + char16_t buffer[16] = u"keep"; + const Param param = DialogParam(buffer, 8); + Open(param); + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Publish(ImeHostInputKind::Escape); + Require(sceImeDialogGetStatus() == StatusRunning && View().preedit.empty(), "Escape while composing cancelled the dialog or kept the composition"); + Publish(ImeHostInputKind::Escape); + Require(sceImeDialogGetStatus() == StatusFinished && EndStatus() == EndStatusUserCanceled, "the next Escape does not cancel the dialog"); + Require(std::u16string(buffer) == u"keep", "a cancelled dialog changed the buffer"); + Close(); +} + +void CheckEditKeysAndPad() { + char16_t buffer[16] = u"abc"; + const Param param = DialogParam(buffer, 8); + Open(param); + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + for (const auto kind : {ImeHostInputKind::Backspace, ImeHostInputKind::Delete, ImeHostInputKind::Left, ImeHostInputKind::Right, ImeHostInputKind::Home, ImeHostInputKind::End}) Publish(kind); + auto view = View(); + Require(view.text == u"abc" && view.caret == 3 && view.preedit == u"ni", "an editing key acted while composing"); + Publish(ImeHostInputKind::Preedit, ""); + Publish(ImeHostInputKind::Left); + Publish(ImeHostInputKind::Backspace); + view = View(); + Require(view.text == u"ac" && view.caret == 1, "editing keys do not work after the composition ends"); + Close(); +} + +void CheckClosing() { + char16_t buffer[16] = u"ab"; + const Param param = DialogParam(buffer, 8); + Open(param); + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Require(sceImeDialogAbort() == 0, "abort"); + ImeKeyboardView view; + Require(!ImeReadKeyboardView_nid_postfix(view), "a finished dialog still shows a keyboard"); + Close(); + + Open(param); + Require(View().preedit.empty(), "a composition survived into the next dialog"); + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Require(sceImeDialogForceClose() == 0, "force close"); + Publish(ImeHostInputKind::Preedit, "late", 0, 0); + Open(param); + Require(View().preedit.empty(), "a preedit after the close reached the next dialog"); + Publish(ImeHostInputKind::Backspace); + Require(View().text == u"a", "a composition after the close froze the next dialog"); + Close(); + + Publish(ImeHostInputKind::Preedit, "late", 0, 0); + Open(param); + Require(View().preedit.empty(), "a preedit before init reached the dialog"); + Close(); +} + +void CheckSelectionAndCaretOffsets() { + char16_t buffer[16] = u""; + const Param param = DialogParam(buffer, 8); + Open(param); + Publish(ImeHostInputKind::Preedit, "a\xf0\x9f\x98\x80z", 2, 1); + auto view = View(); + Require(view.preedit == u"a\U0001F600z" && view.preeditCursor == 3 && view.preeditSelection == 1, "offsets count a surrogate pair as one character"); + Publish(ImeHostInputKind::Preedit, "abc", 40, 40); + view = View(); + Require(view.preeditCursor == 3 && view.preeditSelection == 0, "offsets past the end are clamped"); + Publish(ImeHostInputKind::Preedit, "abc", -2, -2); + view = View(); + Require(view.preeditCursor == 0 && view.preeditSelection == 0, "negative offsets are zero"); + Close(); +} + +void CheckMalformedPreedit() { + char16_t buffer[16] = u""; + const Param param = DialogParam(buffer, 8); + Open(param); + ImeHostInput input{}; + input.kind = ImeHostInputKind::Preedit; + std::memset(input.text, 'a', sizeof(input.text)); + input.text[30] = '\xe3'; + input.text[31] = '\x81'; + ImePublishInput_nid_postfix(input); + Require(View().preedit == std::u16string(30, u'a'), "a preedit cut inside a character shows half of it"); + Publish(ImeHostInputKind::Preedit, "\xff\x01" "b", 0, 0); + Require(View().preedit == u"\uFFFDb", "an invalid byte is not replaced and a control character is not removed"); + Close(); +} + +} + +int main() { + CheckLifecycle(); + CheckCancel(); + CheckEditKeysAndPad(); + CheckClosing(); + CheckSelectionAndCaretOffsets(); + CheckMalformedPreedit(); +} diff --git a/core/libs/tests/GuestImeDialogPaste.cpp b/core/libs/tests/GuestImeDialogPaste.cpp new file mode 100644 index 0000000000..7c62866e04 --- /dev/null +++ b/core/libs/tests/GuestImeDialogPaste.cpp @@ -0,0 +1,158 @@ +#include "prx/libSceImeDialog/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libSceIme/include/ImeInput.hpp" +#include "prx/libSceIme/include/ImeKeyboardView.hpp" + +#include +#include +#include +#include +#include + +namespace { + +constexpr int StatusRunning = 1; + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +void Paste(const std::string& utf8) { + ImePublishPaste_nid_postfix(utf8.data(), utf8.size()); +} + +void Publish(ImeHostInputKind kind, const char* text = "", std::int32_t start = 0, std::int32_t length = 0) { + ImeHostInput input{}; + input.kind = kind; + std::strncpy(input.text, text, sizeof(input.text) - 1); + input.start = start; + input.length = length; + ImePublishInput_nid_postfix(input); +} + +Param DialogParam(char16_t* buffer, std::uint32_t maxLength) { + Param param{}; + param.max_text_length = maxLength; + param.input_text_buffer = buffer; + return param; +} + +ImeKeyboardView View() { + ImeKeyboardView view; + Require(ImeReadKeyboardView_nid_postfix(view), "the dialog shows no keyboard"); + return view; +} + +void Open(const Param& param) { + Require(sceImeDialogInit(¶m, nullptr) == 0, "init"); + Require(sceImeDialogGetStatus() == StatusRunning, "not running"); +} + +void Close() { + Require(sceImeDialogTerm() == 0, "term"); +} + +void CheckInsertAtCaret() { + char16_t buffer[16] = u"ab"; + const Param param = DialogParam(buffer, 8); + Open(param); + Publish(ImeHostInputKind::Left); + Paste("XY"); + auto view = View(); + Require(view.text == u"aXYb" && view.caret == 3, "a paste lands at the caret and moves it"); + Require(std::u16string(buffer) == u"ab", "a paste reached the buffer before the dialog was accepted"); + Close(); +} + +void CheckLineBreaksAndControls() { + char16_t buffer[16] = u""; + const Param param = DialogParam(buffer, 16); + Open(param); + Paste(std::string("a\r\nb\nc\td\x01" "e\x7f" "f", 13)); + Require(View().text == u"abcdef", "line breaks and controls are not removed from a paste"); + Close(); +} + +void CheckMaxLength() { + char16_t buffer[16] = u"abc"; + const Param param = DialogParam(buffer, 5); + Open(param); + Paste("d\xf0\x9f\x98\x80"); + Require(View().text == u"abcd", "a paste split a surrogate pair at the limit"); + Paste("ef"); + Require(View().text == u"abcde", "a paste went past the maximum length"); + Paste("g"); + Require(View().text == u"abcde", "a paste into a full dialog changed it"); + Close(); + + char16_t wide[16] = u""; + const Param roomForPair = DialogParam(wide, 4); + Open(roomForPair); + Paste("x\xf0\x9f\x98\x80\xf0\x9f\x98\x80"); + Require(View().text == u"x\U0001F600", "a pair that fits is not kept whole"); + Close(); +} + +void CheckHugeClipboard() { + char16_t buffer[2049] = u""; + const Param param = DialogParam(buffer, 2048); + Open(param); + const std::string huge(8u * 1024u * 1024u, 'x'); + Paste(huge); + Require(View().text.size() == 2048, "a huge clipboard did not fill the dialog to its limit exactly"); + Close(); +} + +void CheckInvalidUtf8() { + char16_t buffer[16] = u""; + const Param param = DialogParam(buffer, 16); + Open(param); + Paste("a\xff" "b\xe3\x81"); + Require(View().text == u"a\uFFFDb\uFFFD", "an invalid clipboard is not replaced"); + Close(); +} + +void CheckNothingCaptured() { + Require(!ImeCapturesKeyboard_nid_postfix(), "something is capturing"); + Paste("ignored"); + char16_t buffer[16] = u""; + const Param param = DialogParam(buffer, 16); + Open(param); + Require(View().text.empty(), "a paste before init was kept"); + Close(); + Paste("ignored"); + Open(param); + Require(View().text.empty(), "a paste after term was kept"); + Require(sceImeDialogAbort() == 0, "abort"); + Paste("ignored"); + ImeKeyboardView view; + Require(!ImeReadKeyboardView_nid_postfix(view), "a finished dialog shows a keyboard"); + Close(); +} + +void CheckPasteWhileComposing() { + char16_t buffer[16] = u"ab"; + const Param param = DialogParam(buffer, 16); + Open(param); + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Paste("XY"); + Require(View().text == u"ab", "a paste went in while a composition was shown"); + Publish(ImeHostInputKind::Preedit, ""); + Paste("XY"); + Require(View().text == u"abXY", "a paste does not work once the composition ends"); + Close(); +} + +} + +int main() { + CheckNothingCaptured(); + CheckInsertAtCaret(); + CheckLineBreaksAndControls(); + CheckMaxLength(); + CheckHugeClipboard(); + CheckInvalidUtf8(); + CheckPasteWhileComposing(); +} diff --git a/core/libs/tests/GuestImeForTestFunction.cpp b/core/libs/tests/GuestImeForTestFunction.cpp new file mode 100644 index 0000000000..8601812367 --- /dev/null +++ b/core/libs/tests/GuestImeForTestFunction.cpp @@ -0,0 +1,22 @@ +#include "prx/libSceIme/include/Api.hpp" + +#include +#include + +namespace { + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "%s\n", what); + std::abort(); +} + +} // namespace + +int main() { + const int first = sceImeForTestFunction(); + const int second = sceImeForTestFunction(); + Require(first < 0, "sceImeForTestFunction did not answer an error"); + Require(first == second, "sceImeForTestFunction did not answer the same error on a repeated call"); + return 0; +} diff --git a/core/libs/tests/GuestImePaste.cpp b/core/libs/tests/GuestImePaste.cpp new file mode 100644 index 0000000000..a03d1385e9 --- /dev/null +++ b/core/libs/tests/GuestImePaste.cpp @@ -0,0 +1,231 @@ +#include "prx/libSceIme/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libSceIme/include/ImeInput.hpp" + +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr uint32_t EventUpdateText = 1; + +struct Recorded { + uint32_t id; + std::u16string text; + uint32_t index; + int32_t length; +}; + +std::vector g_events; +int g_filterCalls = 0; + +void APS5_VABI Record(void*, const ImeEvent* event) { + Recorded recorded{event->id, {}, 0, 0}; + if (event->id == EventUpdateText) { + recorded.text = event->param.text.str; + recorded.index = event->param.text.text_area[0].index; + recorded.length = event->param.text.text_area[0].length; + } + g_events.push_back(recorded); +} + +int32_t APS5_VABI DigitsOnlyFilter(char16_t* outText, uint32_t* outTextLength, const char16_t* sourceText, uint32_t sourceTextLength) { + ++g_filterCalls; + uint32_t length = 0; + for (uint32_t i = 0; i < sourceTextLength; ++i) { + if (sourceText[i] >= u'0' && sourceText[i] <= u'9') outText[length++] = sourceText[i]; + } + *outTextLength = length; + return 0; +} + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +struct Buffers { + char16_t text[2049] = {}; + alignas(16) unsigned char work[0x5000] = {}; + int arg = 0; +}; + +Param MakeParam(Buffers& buffers, uint32_t maxLength, uint32_t option = 0) { + Param param; + sceImeParamInit(¶m); + param.user_id = 1; + param.max_text_length = maxLength; + param.input_text_buffer = buffers.text; + param.option = option; + param.posx = 100.0f; + param.posy = 200.0f; + param.work = buffers.work; + param.arg = &buffers.arg; + param.handler = Record; + return param; +} + +std::vector Drain() { + g_events.clear(); + Require(sceImeUpdate(Record) == 0, "update failed"); + return g_events; +} + +void Paste(const std::string& utf8) { + ImePublishPaste_nid_postfix(utf8.data(), utf8.size()); +} + +void Publish(ImeHostInputKind kind, const char* text = "", std::int32_t start = 0, std::int32_t length = 0) { + ImeHostInput input{}; + input.kind = kind; + std::strncpy(input.text, text, sizeof(input.text) - 1); + input.start = start; + input.length = length; + ImePublishInput_nid_postfix(input); +} + +struct Session { + Buffers buffers; + explicit Session(const char16_t* initial, uint32_t maxLength, uint32_t option = 0, ImeTextFilter filter = nullptr) { + std::memcpy(buffers.text, initial, (std::char_traits::length(initial) + 1) * sizeof(char16_t)); + Param param = MakeParam(buffers, maxLength, option); + param.filter = filter; + Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "open"); + Drain(); + } + ~Session() { sceImeClose_nid_postfix(); } + std::u16string Text() const { return buffers.text; } +}; + +void CheckInsertAtCaret() { + Session session(u"ab", 16); + Publish(ImeHostInputKind::Left); + Drain(); + Paste("XY"); + const auto events = Drain(); + Require(events.size() == 1 && events[0].id == EventUpdateText, "a paste is one text update"); + Require(events[0].text == u"aXYb" && events[0].index == 1 && events[0].length == 2, "the paste lands at the caret"); + Require(session.Text() == u"aXYb", "the paste reached the input buffer"); + Publish(ImeHostInputKind::Text, "z"); + Require(Drain().at(0).text == u"aXYzb", "the caret sits after the pasted text"); +} + +void CheckSingleLineAndMultiline() { + { + Session session(u"", 32); + Paste("a\r\nb\nc\rd"); + Drain(); + Require(session.Text() == u"abcd", "a single-line session drops line breaks"); + } + { + Session session(u"", 32, 0x00000001); + Paste("a\r\nb\nc\rd"); + Drain(); + Require(session.Text() == u"a\nb\nc\nd", "a multi-line session keeps line breaks as LF"); + } +} + +void CheckControlCharacters() { + Session session(u"", 32); + Paste(std::string("a\tb\x01" "c\x1b" "d\x7f" "e\0f", 11)); + Drain(); + Require(session.Text() == u"abcdef", "controls are stripped from a paste"); +} + +void CheckMaxLength() { + { + Session session(u"abc", 5); + Paste("defgh"); + const auto events = Drain(); + Require(session.Text() == u"abcde" && events.size() == 1 && events[0].length == 2, "a paste stops at the maximum length"); + Paste("z"); + Require(Drain().empty() && session.Text() == u"abcde", "a paste into a full field does nothing"); + } + { + Session session(u"abc", 5); + Paste("d\xf0\x9f\x98\x80"); + Drain(); + Require(session.Text() == u"abcd", "a cut that would split a surrogate pair drops the whole character"); + } + { + Session session(u"abc", 6); + Paste("d\xf0\x9f\x98\x80"); + Drain(); + Require(session.Text() == u"abcd\U0001F600", "a pair that fits is kept whole"); + } + { + Session session(u"", 1); + Paste("\xf0\x9f\x98\x80"); + Require(Drain().empty() && session.Text().empty(), "a pair that cannot fit is not half inserted"); + } +} + +void CheckFilter() { + g_filterCalls = 0; + Session session(u"", 16, 0, reinterpret_cast(&DigitsOnlyFilter)); + Paste("1a2b3\xe3\x81\x82" "4"); + const auto events = Drain(); + Require(g_filterCalls == 1, "a paste runs the text filter once"); + Require(session.Text() == u"1234" && events.size() == 1 && events[0].text == u"1234", "the filter shapes the pasted text"); +} + +void CheckHugeClipboard() { + Session session(u"", 2048); + const std::string huge(8u * 1024u * 1024u, 'x'); + Paste(huge); + Drain(); + Require(session.Text().size() == 2048, "a huge clipboard fills the field and no more"); +} + +void CheckInvalidUtf8() { + Session session(u"", 16); + Paste("a\xff" "b\xe3\x81"); + Drain(); + Require(session.Text() == u"a\uFFFDb\uFFFD", "an invalid clipboard is replaced and never throws"); +} + +void CheckNothingCaptured() { + Require(!ImeCapturesKeyboard_nid_postfix(), "nothing should be capturing"); + Paste("ignored"); + ImePublishPaste_nid_postfix(nullptr, 5); + Buffers buffers; + Param param = MakeParam(buffers, 16); + Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "open after an ignored paste"); + const auto opened = Drain(); + Require(opened.size() == 1 && opened[0].id == 0 && buffers.text[0] == u'\0', "a paste with no session was kept for later"); + Paste(""); + Require(Drain().empty(), "an empty paste queued an update"); + ImePublishPaste_nid_postfix(nullptr, 5); + Require(Drain().empty(), "a null clipboard queued an update"); + Require(sceImeClose_nid_postfix() == 0, "close"); +} + +void CheckPasteWhileComposing() { + Session session(u"ab", 16); + Publish(ImeHostInputKind::Preedit, "ni", 2, 0); + Paste("XY"); + Require(Drain().empty() && session.Text() == u"ab", "a paste went in while the input method held a composition"); + Publish(ImeHostInputKind::Preedit, ""); + Paste("XY"); + Drain(); + Require(session.Text() == u"abXY", "a paste works once the composition ends"); +} + +} + +int main() { + CheckNothingCaptured(); + CheckInsertAtCaret(); + CheckSingleLineAndMultiline(); + CheckControlCharacters(); + CheckMaxLength(); + CheckFilter(); + CheckHugeClipboard(); + CheckInvalidUtf8(); + CheckPasteWhileComposing(); +} diff --git a/core/libs/tests/GuestImeTextInput.cpp b/core/libs/tests/GuestImeTextInput.cpp new file mode 100644 index 0000000000..c0d5cfebdf --- /dev/null +++ b/core/libs/tests/GuestImeTextInput.cpp @@ -0,0 +1,251 @@ +#include "prx/libSceImeDialog/include/Api.hpp" +#include "prx/libSceIme/include/Api.hpp" +#include "SceTypes.hpp" +#include "prx/libSceIme/include/ImeInput.hpp" +#include "prx/libSceIme/include/ImeKeyboardView.hpp" +#include "prx/libSceVideoOut/include/ImeTextInput.hpp" + +#include +#include +#include +#include +#include + +namespace { + +constexpr unsigned WindowId = 7; +constexpr int StatusRunning = 1; +#ifdef __APPLE__ +constexpr unsigned PasteModifier = KMOD_LGUI; +constexpr unsigned WrongModifier = KMOD_LCTRL; +#else +constexpr unsigned PasteModifier = KMOD_LCTRL; +constexpr unsigned WrongModifier = KMOD_LGUI; +#endif + +std::string g_clipboard; +bool g_clipboardAvailable = true; +int g_reads = 0; +ImeTextInput g_window; + +bool ReadClipboard(std::string& text) { + ++g_reads; + if (!g_clipboardAvailable) return false; + text = g_clipboard; + return true; +} + +void APS5_VABI Ignore(void*, const ImeEvent*) {} + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +void Edit(const char* utf8, std::int32_t start, std::int32_t length, unsigned windowId = WindowId) { + SDL_Event event{}; + event.type = SDL_TEXTEDITING; + event.edit.windowID = windowId; + std::strncpy(event.edit.text, utf8, sizeof(event.edit.text) - 1); + event.edit.start = start; + event.edit.length = length; + g_window.HandleEvent(event, WindowId); +} + +void Type(const char* utf8) { + SDL_Event event{}; + event.type = SDL_TEXTINPUT; + event.text.windowID = WindowId; + std::strncpy(event.text.text, utf8, sizeof(event.text.text) - 1); + g_window.HandleEvent(event, WindowId); +} + +void Key(SDL_Scancode scancode, SDL_Keycode sym, unsigned modifiers, bool repeat = false, unsigned type = SDL_KEYDOWN, unsigned windowId = WindowId) { + SDL_Event event{}; + event.type = type; + event.key.windowID = windowId; + event.key.keysym.scancode = scancode; + event.key.keysym.sym = sym; + event.key.keysym.mod = static_cast(modifiers); + event.key.repeat = repeat ? 1 : 0; + g_window.HandleEvent(event, WindowId); +} + +Param DialogParam(char16_t* buffer, std::uint32_t maxLength) { + Param param{}; + param.max_text_length = maxLength; + param.input_text_buffer = buffer; + return param; +} + +ImeKeyboardView View() { + ImeKeyboardView view; + Require(ImeReadKeyboardView_nid_postfix(view), "the dialog shows no keyboard"); + return view; +} + +void Open(const Param& param) { + Require(sceImeDialogInit(¶m, nullptr) == 0, "init"); + Require(sceImeDialogGetStatus() == StatusRunning, "not running"); +} + +void Close() { + Require(sceImeDialogTerm() == 0, "term"); +} + +void CheckEditingEvents() { + char16_t buffer[16] = u"x"; + const Param param = DialogParam(buffer, 8); + Open(param); + Edit("k", 1, 0); + Require(View().preedit == u"k" && View().preeditCursor == 1, "an SDL text editing event is not shown as a preedit"); + Edit("ka", 2, 0); + Require(View().preedit == u"ka" && View().preeditCursor == 2, "a second edit does not replace the first"); + Edit("\xe3\x81\x8b", 1, 1, WindowId + 1); + Require(View().preedit == u"ka", "an edit for another window was shown"); + Type("\xe3\x81\x8b"); + Edit("", 0, 0); + Require(View().preedit.empty() && View().text == u"x\u304B" && std::u16string(buffer) == u"x", "the commit is not the only text and the preedit is not gone"); + Edit("", 0, 0); + Require(View().text == u"x\u304B", "an empty edit changed the text"); + Edit("ni", 2, 0); + Edit("", 0, 0); + Require(View().preedit.empty(), "an empty edit does not clear the composition"); + Key(SDL_SCANCODE_ESCAPE, SDLK_ESCAPE, 0); + Require(sceImeDialogGetStatus() == 2, "Escape with no composition does not cancel"); + Close(); + + Open(param); + Edit("ni", 2, 0); + Key(SDL_SCANCODE_ESCAPE, SDLK_ESCAPE, 0); + Require(sceImeDialogGetStatus() == StatusRunning && View().preedit.empty(), "Escape from the keyboard with a composition cancels the dialog"); + Close(); + + Edit("late", 0, 0); + Open(param); + Require(View().preedit.empty(), "an edit with no dialog was kept"); + Close(); +} + +void CheckPasteShortcuts() { + char16_t buffer[64] = u""; + const Param param = DialogParam(buffer, 32); + g_window.SetClipboardProvider(&ReadClipboard); + g_clipboard = "ab"; + Open(param); + + g_reads = 0; + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Require(g_reads == 1 && View().text == u"ab", "Ctrl+V does not paste"); + Key(SDL_SCANCODE_INSERT, SDLK_INSERT, KMOD_LSHIFT); + Require(g_reads == 2 && View().text == u"abab", "Shift+Insert does not paste"); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier | KMOD_LSHIFT); + Require(g_reads == 3 && View().text == u"ababab", "Ctrl+Shift+V does not paste"); + + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier, true); + Key(SDL_SCANCODE_INSERT, SDLK_INSERT, KMOD_LSHIFT, true); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier | KMOD_LALT); + Key(SDL_SCANCODE_V, SDLK_v, 0); + Key(SDL_SCANCODE_V, SDLK_v, WrongModifier); + Key(SDL_SCANCODE_INSERT, SDLK_INSERT, 0); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier, false, SDL_KEYUP); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier, false, SDL_KEYDOWN, WindowId + 1); + Key(SDL_SCANCODE_C, SDLK_c, PasteModifier); + Key(SDL_SCANCODE_X, SDLK_x, PasteModifier); + Key(SDL_SCANCODE_A, SDLK_a, PasteModifier); + Require(g_reads == 3 && View().text == u"ababab", "a key that is not a paste read the clipboard or changed the text"); + + g_clipboardAvailable = false; + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Require(g_reads == 4 && View().text == u"ababab", "an unavailable clipboard changed the text"); + g_clipboardAvailable = true; + g_clipboard.clear(); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Require(g_reads == 5 && View().text == u"ababab", "an empty clipboard changed the text"); + g_clipboard = "cd"; + + Edit("ni", 2, 0); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Require(View().text == u"ababab", "Ctrl+V pasted while a composition was shown"); + Edit("", 0, 0); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Require(View().text == u"abababcd", "Ctrl+V does not paste once the composition ends"); + Require(std::u16string(buffer).empty(), "a paste reached the buffer before accept"); + Close(); +} + +void CheckNothingCaptured() { + g_window.SetClipboardProvider(&ReadClipboard); + g_clipboard = "secret"; + g_reads = 0; + Require(!ImeCapturesKeyboard_nid_postfix(), "something is capturing"); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Key(SDL_SCANCODE_INSERT, SDLK_INSERT, KMOD_LSHIFT); + Require(g_reads == 0, "the clipboard was read while no dialog captures the keyboard"); + + char16_t buffer[16] = u""; + const Param param = DialogParam(buffer, 16); + Open(param); + Require(sceImeDialogAbort() == 0, "abort"); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Require(g_reads == 0, "the clipboard was read after the dialog finished"); + Close(); + + ImeTextInput bare; + Open(param); + SDL_Event event{}; + event.type = SDL_KEYDOWN; + event.key.windowID = WindowId; + event.key.keysym.scancode = SDL_SCANCODE_V; + event.key.keysym.sym = SDLK_v; + event.key.keysym.mod = static_cast(PasteModifier); + bare.HandleEvent(event, WindowId); + Require(View().text.empty() && g_reads == 0, "a window with no clipboard provider pasted"); + Close(); +} + +void CheckHugeClipboard() { + char16_t buffer[2049] = u""; + const Param param = DialogParam(buffer, 2048); + g_window.SetClipboardProvider(&ReadClipboard); + g_clipboard.assign(4u * 1024u * 1024u, 'x'); + Open(param); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Require(View().text.size() == 2048, "a huge clipboard is not cut at the dialog limit"); + Close(); +} + +void CheckSession() { + struct Buffers { + char16_t text[2049] = {}; + alignas(16) unsigned char work[0x5000] = {}; + } buffers; + Param param; + sceImeParamInit(¶m); + param.user_id = 1; + param.max_text_length = 16; + param.input_text_buffer = buffers.text; + param.option = 0x00000001; + param.work = buffers.work; + param.handler = Ignore; + g_window.SetClipboardProvider(&ReadClipboard); + g_clipboard = "a\r\nb"; + Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "open"); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Edit("ni", 2, 0); + Key(SDL_SCANCODE_V, SDLK_v, PasteModifier); + Require(sceImeUpdate(param.handler) == 0, "update"); + Require(std::u16string(buffers.text) == u"a\nb", "the session text is not the single paste with a normalised break"); + Require(sceImeClose_nid_postfix() == 0, "close"); +} + +} + +int main() { + CheckEditingEvents(); + CheckPasteShortcuts(); + CheckNothingCaptured(); + CheckHugeClipboard(); + CheckSession(); +} diff --git a/core/libs/tests/GuestIndexedDraw.cpp b/core/libs/tests/GuestIndexedDraw.cpp index 14333e04f6..60957670e3 100644 --- a/core/libs/tests/GuestIndexedDraw.cpp +++ b/core/libs/tests/GuestIndexedDraw.cpp @@ -49,6 +49,8 @@ int main() { draw[1] = 3; draw[4] = 3; draw[4]=4; Reject([&]{AgcDriver::Pm4::Validate({draw,6},0);}); + draw[4]=0xffffffffu; + Reject([&]{AgcDriver::Pm4::Validate({draw,6},0);}); draw[4]=3; draw[5]=2; Reject([&]{AgcDriver::Pm4::Validate({draw,6},0);}); @@ -59,6 +61,17 @@ int main() { Reject([&]{AgcDriver::Pm4::Validate({draw,6},0);}); draw[2]=1; draw[3]=0; queue.indexType=1; Reject([&]{AgcDriver::Pm4::ResolveDraw({draw,6},queue);}); + draw[2]=0; draw[3]=0; + Reject([&]{AgcDriver::Pm4::ResolveDraw({draw,6},queue);}); + draw[2]=static_cast(reinterpret_cast(indices.data())); + draw[3]=static_cast(reinterpret_cast(indices.data())>>32u); + queue.indexBase=0; + Reject([&]{AgcDriver::Pm4::ResolveDraw(offsetDraw,queue);}); + queue.indexBase=reinterpret_cast(indices.data()); + const std::array invalidOffsetCount{0xc0033500u,2,1,3,0}; + Reject([&]{AgcDriver::Pm4::Validate(invalidOffsetCount,0);}); + const std::array maxOffsetCount{0xc0033500u,2,1,0xffffffffu,0}; + Reject([&]{AgcDriver::Pm4::Validate(maxOffsetCount,0);}); auto* saved=buffer.cursor_up; Reject([&]{sceAgcDcbDrawIndex(&buffer,3,nullptr,0);}); Reject([&]{sceAgcDcbDrawIndex(&buffer,3,indices.data(),1ull<<40);}); @@ -66,5 +79,15 @@ int main() { buffer.cursor_down=buffer.cursor_up+5; Reject([&]{sceAgcDcbDrawIndex(&buffer,3,indices.data(),0);}); Require(buffer.cursor_up==saved); + buffer.cursor_down=words.data()+words.size(); + Reject([&]{sceAgcDcbDrawIndex(nullptr,3,indices.data(),0);}); + Reject([&]{sceAgcDcbSetIndexCount(nullptr,3);}); + Reject([&]{sceAgcDcbSetIndexBuffer(nullptr,reinterpret_cast(indices.data()));}); + Reject([&]{sceAgcDcbDrawIndexAuto(nullptr,3,0);}); + Reject([&]{sceAgcDcbDrawIndexOffset(nullptr,0,3,0);}); + Reject([&]{sceAgcDcbDrawIndexIndirect(nullptr,0,0);}); + Reject([&]{sceAgcDcbDrawIndexIndirectMulti(nullptr,0,0,1,nullptr,20,0);}); + Reject([&]{sceAgcDcbDrawIndexMultiInstanced(nullptr,3,indices.data(),nullptr,1,0);}); + Require(buffer.cursor_up==saved); return 0; } diff --git a/core/libs/tests/GuestInterfaces.cpp b/core/libs/tests/GuestInterfaces.cpp index 94e0d0ffd8..913b414b08 100644 --- a/core/libs/tests/GuestInterfaces.cpp +++ b/core/libs/tests/GuestInterfaces.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #endif #include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" @@ -29,7 +28,9 @@ #include #include #include +#include #include +#include #include static_assert(sizeof(GuestInterfaceAddress) == 56); @@ -128,6 +129,146 @@ static void SendLoopbackDatagram() { } #endif +#ifdef _WIN32 +static void RequireWindowsLink(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "guest_interfaces: %s\n", message); + std::abort(); + } +} + +static void CheckWindowsLinks() { + GuestInterfaceAddress* first = nullptr; + GuestInterfaceAddress* second = nullptr; + const ULONG options = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; + ULONG size = 0; + std::vector storage; + ULONG result = GetAdaptersAddresses(AF_UNSPEC, options, nullptr, nullptr, &size); + for (unsigned calls = 1; result == ERROR_BUFFER_OVERFLOW && calls < 4; ++calls) { + storage.resize(size); + result = GetAdaptersAddresses(AF_UNSPEC, options, nullptr, reinterpret_cast(storage.data()), &size); + } + Require(result == NO_ERROR); + std::vector baseline; + for (auto* adapter = reinterpret_cast(storage.data()); adapter; adapter = adapter->Next) { + MIB_IF_ROW2 row{}; + row.InterfaceLuid = adapter->Luid; + Require(GetIfEntry2(&row) == NO_ERROR); + baseline.push_back(row); + } + Require(getifaddrs_nid_postfix(&first) == 0); + Require(getifaddrs_nid_postfix(&second) == 0); + bool loopback = false; + bool hardware = false; + std::size_t adapters = 0; + for (auto* adapter = reinterpret_cast(storage.data()); adapter; adapter = adapter->Next) { + ++adapters; + char nativeName[NDIS_IF_MAX_STRING_SIZE + 1] = {}; + Require(ConvertInterfaceLuidToNameA(&adapter->Luid, nativeName, sizeof(nativeName)) == NO_ERROR); + const auto index = adapter->IfIndex ? adapter->IfIndex : adapter->Ipv6IfIndex; + const std::string name = std::strlen(nativeName) < 16 ? nativeName : "if" + std::to_string(index); + const GuestInterfaceAddress* links[2] = {}; + const GuestInterfaceAddress* lists[] = {first, second}; + for (std::size_t snapshot = 0; snapshot < 2; ++snapshot) { + unsigned count = 0; + for (auto* item = lists[snapshot]; item; item = item->next) { + if (name != item->name) continue; + const auto* address = static_cast(item->address); + Require(address != nullptr); + if (address[1] == 18) { + RequireWindowsLink(count == 0, "duplicate AF_LINK entry"); + links[snapshot] = item; + ++count; + } else { + RequireWindowsLink(count == 1, "address entry has no preceding AF_LINK entry"); + RequireWindowsLink(item->flags == links[snapshot]->flags, "link and address flags disagree"); + bool nativeAddress = false; + for (auto* unicast = adapter->FirstUnicastAddress; unicast; unicast = unicast->Next) { + const auto* hostAddress = unicast->Address.lpSockaddr; + if (!hostAddress || (hostAddress->sa_family != AF_INET && hostAddress->sa_family != AF_INET6)) continue; + const auto expected = HostAddress(hostAddress); + const bool ipv4 = address[1] == 2; + nativeAddress = nativeAddress || (expected.family == (ipv4 ? AF_INET : AF_INET6) && + std::memcmp(address + (ipv4 ? 4 : 8), expected.address, ipv4 ? 4 : 16) == 0); + } + RequireWindowsLink(nativeAddress, "address belongs to a different native interface index"); + } + } + RequireWindowsLink(count == 1, "native adapter has no AF_LINK entry"); + const auto* link = static_cast(links[snapshot]->address); + const auto* data = static_cast(links[snapshot]->data); + RequireWindowsLink(data != nullptr, "AF_LINK entry has no if_data"); + std::uint16_t guestIndex = 0; + std::uint16_t dataLength = 0; + std::uint32_t mtu = 0; + std::memcpy(&guestIndex, link + 2, sizeof(guestIndex)); + std::memcpy(&dataLength, data + 6, sizeof(dataLength)); + std::memcpy(&mtu, data + 8, sizeof(mtu)); + RequireWindowsLink(guestIndex == index, "AF_LINK index differs from native adapter"); + const std::uint8_t type = adapter->IfType == IF_TYPE_ETHERNET_CSMACD ? 0x6 + : adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK ? 0x18 : adapter->IfType == IF_TYPE_PPP ? 0x17 : 0x1; + RequireWindowsLink(link[4] == type, "AF_LINK type differs from native adapter"); + Require(link[0] >= 54 && link[0] % 8 == 0 && link[5] == name.size() && link[7] == 0); + Require(std::memcmp(link + 8, name.data(), name.size()) == 0); + Require(links[snapshot]->netmask == nullptr && links[snapshot]->destination == nullptr); + Require(dataLength == 152 && data[0] == link[4] && data[2] == link[6]); + RequireWindowsLink(mtu == adapter->Mtu, "AF_LINK MTU differs from native adapter"); + if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK) { + RequireWindowsLink(link[4] == 0x18 && link[6] == 0 && data[3] == 0, "loopback link type or hardware address is incorrect"); + loopback = true; + } else { + RequireWindowsLink(link[6] == adapter->PhysicalAddressLength && + std::memcmp(link + 8 + name.size(), adapter->PhysicalAddress, link[6]) == 0, "AF_LINK MAC differs from native adapter"); + hardware = hardware || adapter->PhysicalAddressLength != 0; + } + } + const auto* before = static_cast(links[0]->data); + const auto* after = static_cast(links[1]->data); + const auto& initial = baseline[adapters - 1]; + MIB_IF_ROW2 final{}; + final.InterfaceLuid = adapter->Luid; + Require(GetIfEntry2(&final) == NO_ERROR); + RequireWindowsLink(initial.InterfaceIndex == index && final.InterfaceIndex == index, "adapter and counter indices disagree"); + const auto counters = [](const MIB_IF_ROW2& row) { + return std::array{row.InUcastPkts + row.InNUcastPkts, row.InErrors, + row.OutUcastPkts + row.OutNUcastPkts, row.OutErrors, 0, row.InOctets, row.OutOctets, + row.InNUcastPkts, row.OutNUcastPkts, row.InDiscards, row.OutDiscards, row.InUnknownProtos}; + }; + const auto lower = counters(initial); + const auto upper = counters(final); + for (std::size_t offset = 24; offset <= 112; offset += 8) { + std::uint64_t oldValue = 0; + std::uint64_t newValue = 0; + std::memcpy(&oldValue, before + offset, sizeof(oldValue)); + std::memcpy(&newValue, after + offset, sizeof(newValue)); + RequireWindowsLink(newValue >= oldValue, "interface counter decreased between snapshots"); + RequireWindowsLink(oldValue >= lower[(offset - 24) / 8] && newValue <= upper[(offset - 24) / 8], + "interface counter differs from native bounds"); + } + } + for (const auto* list : {first, second}) { + std::size_t links = 0; + for (auto* item = list; item; item = item->next) { + const auto* address = static_cast(item->address); + if (address[1] == 18) ++links; + else { + unsigned owners = 0; + for (auto* candidate = list; candidate; candidate = candidate->next) { + const auto* link = static_cast(candidate->address); + if (link[1] == 18 && std::strcmp(candidate->name, item->name) == 0) ++owners; + } + RequireWindowsLink(owners == 1, "address entry does not have exactly one same-name AF_LINK entry"); + } + } + RequireWindowsLink(links == adapters, "AF_LINK count differs from native adapter count"); + } + RequireWindowsLink(loopback, "no loopback AF_LINK checked"); + RequireWindowsLink(hardware, "no non-loopback MAC and MTU checked"); + freeifaddrs_nid_postfix(first); + freeifaddrs_nid_postfix(second); +} +#endif + static std::vector HostSnapshot() { std::vector entries; #ifdef _WIN32 @@ -220,6 +361,9 @@ static std::vector HostSnapshot() { int main() { std::array api{}; ApplicationHeapRegister_nid_no_patch(api.data()); +#ifdef _WIN32 + CheckWindowsLinks(); +#endif constexpr std::uint32_t knownFlags = 0x1 | 0x2 | 0x8 | 0x10 | 0x40 | 0x80 | 0x100 | 0x200 | 0x8000; const std::uint8_t loopbackAddress[] = {127, 0, 0, 1}; const std::uint8_t loopbackMask[] = {255, 0, 0, 0}; @@ -236,6 +380,10 @@ int main() { bool equal = true; bool loopback = false; for (auto* item = list; item; item = item->next) { +#ifdef _WIN32 + const auto* family = static_cast(item->address); + if (family && family[1] == 18) continue; +#endif Require(++entries < 100000); Require(item->name != nullptr); const std::size_t nameLength = std::strlen(item->name); diff --git a/core/libs/tests/GuestItimer.cpp b/core/libs/tests/GuestItimer.cpp index 7a688d67d0..2e263792b7 100644 --- a/core/libs/tests/GuestItimer.cpp +++ b/core/libs/tests/GuestItimer.cpp @@ -2,6 +2,8 @@ #include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include +#include #include #include #include @@ -93,8 +95,29 @@ static int UnblockPendingDefault() { return 1; } +static int ShortExpiries() { + const GuestSignalSet alarmSet{{1u << 13, 0, 0, 0}}; + const KernelTimespec patience{2, 0}; + const GuestItimerval shortOnce{{0, 0}, {0, 2000}}; + Require(_sigprocmask_nid_postfix(1, &alarmSet, nullptr) == 0, "blocking SIGALRM failed"); + std::array samples{}; + for (auto& sample : samples) { + const auto before = std::chrono::steady_clock::now(); + Require(setitimer_nid_postfix(0, &shortOnce, nullptr) == 0, "arming the 2 ms timer failed"); + Require(sigtimedwait_nid_postfix(&alarmSet, nullptr, &patience) == 14, "the 2 ms timer did not expire"); + sample = std::chrono::steady_clock::now() - before; + Require(sample >= std::chrono::milliseconds(2), "the 2 ms timer expired early"); + } + std::sort(samples.begin(), samples.end()); + const auto median = samples[samples.size() / 2]; + std::fprintf(stderr, "setitimer(2 ms) expiry median: %lld ns\n", static_cast(median.count())); + Require(median < std::chrono::milliseconds(12), "the 2 ms timer expired on a coarse tick"); + return 0; +} + int main(int argc, char** argv) { if (argc > 1 && std::strcmp(argv[1], "pending-default") == 0) return UnblockPendingDefault(); + if (argc > 1 && std::strcmp(argv[1], "short-expiry") == 0) return ShortExpiries(); const auto ignore = reinterpret_cast(std::uintptr_t{1}); Require(signal_nid_postfix(14, ignore) == nullptr, "SIGALRM was not initially defaulted"); const GuestItimerval valid{{0, 0}, {30, 0}}; diff --git a/core/libs/tests/GuestJpegDecInputControl.cpp b/core/libs/tests/GuestJpegDecInputControl.cpp new file mode 100644 index 0000000000..8f57f9f67c --- /dev/null +++ b/core/libs/tests/GuestJpegDecInputControl.cpp @@ -0,0 +1,277 @@ +#include "SceTypes.hpp" +#include "prx/libSceJpegDec/libSceJpegDec.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "Decoder/Jpeg.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +struct Source { + std::vector bytes; + std::size_t chunkSize = 0; + std::size_t offset = 0; + std::size_t calls = 0; + std::int32_t failure = 0; + void* deleteHandle = nullptr; +}; + +std::int32_t APS5_VABI ReadChunk(JpegDecInputChunk* chunk, void* argument) { + auto* source = static_cast(argument); + ++source->calls; + Require(chunk->data == nullptr && chunk->size == 0); + if (void* handle = std::exchange(source->deleteHandle, nullptr)) Require(sceJpegDecDelete(handle) == 0); + if (source->failure != 0) return source->failure; + if (source->offset == source->bytes.size()) return 0; + const std::size_t count = std::min(source->chunkSize, source->bytes.size() - source->offset); + chunk->data = source->bytes.data() + source->offset; + chunk->size = static_cast(count); + source->offset += count; + return 7; +} + +std::vector TestJpeg(std::uint32_t width, std::uint32_t height, std::uint32_t channels) { + std::vector pixels(static_cast(width) * height * channels); + for (std::size_t i = 0; i < pixels.size(); ++i) pixels[i] = static_cast((i * 7) ^ (i >> 5)); + return Decoder::Jpeg::Encode(pixels, width, height, channels, 90); +} + +JpegDecInputControlParam StreamParam(std::uint16_t operation, Source& source) { + JpegDecInputControlParam param{}; + param.input = ReadChunk; + param.input_argument = &source; + param.operation = operation; + param.down_scale = 1; + param.alpha_value = 0x80; + return param; +} + +struct JpegHandle { + std::vector memory; + void* handle = nullptr; + explicit JpegHandle(std::uint32_t maxWidth) { + const JpegDecCreateParam create{sizeof(JpegDecCreateParam), 2, maxWidth}; + memory.resize(static_cast(sceJpegDecQueryMemorySize(&create)) + 64); + Require(sceJpegDecCreate(&create, memory.data(), static_cast(memory.size()), &handle) == 0); + } +}; + +std::vector Reference(const std::vector& jpeg, std::uint32_t width, std::uint32_t height, std::uint16_t downScale, std::int32_t* result) { + JpegHandle decoder(width); + std::vector image(static_cast(width) * height * 4, 0xCD); + JpegDecDecodeParam param{}; + param.jpeg_mem_addr = jpeg.data(); + param.jpeg_mem_size = static_cast(jpeg.size()); + param.image_mem_addr = image.data(); + param.image_mem_size = static_cast(image.size()); + param.down_scale = downScale; + param.alpha_value = 0x80; + *result = sceJpegDecDecode(decoder.handle, ¶m, nullptr); + return image; +} + +void RequireStreamMatches(const std::vector& jpeg, std::uint32_t width, std::uint32_t height, std::uint16_t downScale, std::size_t chunkSize) { + std::int32_t expected = 0; + const std::vector reference = Reference(jpeg, width, height, downScale, &expected); + Require(expected > 0); + + JpegHandle decoder(width); + Source header{jpeg, chunkSize}; + JpegDecInputControlParam parse = StreamParam(0x10, header); + parse.down_scale = downScale; + JpegDecImageInfo info{}, expectedInfo{}; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &parse, &info) == 0); + const JpegDecParseParam parseHeader{jpeg.data(), static_cast(jpeg.size()), 0, downScale}; + Require(sceJpegDecParseHeader(&parseHeader, &expectedInfo) == 0); + Require(std::equal(reinterpret_cast(&info), reinterpret_cast(&info + 1), reinterpret_cast(&expectedInfo))); + + Source rest{std::vector(jpeg.begin() + static_cast(header.offset), jpeg.end()), chunkSize}; + std::vector image(reference.size(), 0xCD); + JpegDecInputControlParam decode = StreamParam(0x20, rest); + decode.down_scale = downScale; + decode.image_mem_addr = image.data(); + decode.image_mem_size = static_cast(image.size()); + JpegDecImageInfo untouched{}; + untouched.image_width = 0x1234; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &decode, &untouched) == expected); + Require(untouched.image_width == 0x1234); + Require(image == reference); + Require(sceJpegDecDelete(decoder.handle) == 0); +} + +void RequireStreamOutlivesHandle(const std::vector& jpeg, std::uint32_t width, std::uint32_t height) { + constexpr std::int32_t invalidHandle = static_cast(0x80650004); + std::int32_t expected = 0; + const std::vector reference = Reference(jpeg, width, height, 1, &expected); + + JpegHandle parsing(width); + Source deletedInParse{jpeg, 64}; + deletedInParse.deleteHandle = parsing.handle; + JpegDecInputControlParam parse = StreamParam(0x10, deletedInParse); + JpegDecImageInfo info{}; + Require(sceJpegDecDecodeWithInputControl(parsing.handle, &parse, &info) == 0); + Require(info.image_width == width && info.image_height == height); + std::vector image(reference.size(), 0xCD); + JpegDecInputControlParam decode = StreamParam(0x20, deletedInParse); + decode.image_mem_addr = image.data(); + decode.image_mem_size = static_cast(image.size()); + Require(sceJpegDecDecodeWithInputControl(parsing.handle, &decode, nullptr) == invalidHandle); + + JpegHandle decoding(width); + Source deletedInDecode{jpeg, 64}; + parse.input_argument = &deletedInDecode; + Require(sceJpegDecDecodeWithInputControl(decoding.handle, &parse, &info) == 0); + deletedInDecode.deleteHandle = decoding.handle; + decode.input_argument = &deletedInDecode; + Require(sceJpegDecDecodeWithInputControl(decoding.handle, &decode, nullptr) == expected); + Require(image == reference); + Require(sceJpegDecDecodeWithInputControl(decoding.handle, &decode, nullptr) == invalidHandle); + + JpegHandle completed(width); + Source outer{jpeg, 64}; + parse.input_argument = &outer; + Require(sceJpegDecDecodeWithInputControl(completed.handle, &parse, &info) == 0); + std::fill(image.begin(), image.end(), 0xCD); + decode.input_argument = &outer; + Require(sceJpegDecDecodeWithInputControl(completed.handle, &decode, nullptr) == expected); + Require(image == reference); + Require(sceJpegDecDelete(completed.handle) == 0); +} + +} // namespace + +int main() { + 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); + constexpr std::int32_t decodeError = static_cast(0x80650012); + + const std::vector jpeg = TestJpeg(96, 64, 3); + Require(jpeg.size() > 2 * 0x800); + JpegHandle decoder(96); + Source source{jpeg, 100}; + JpegDecImageInfo info{}; + JpegDecInputControlParam parse = StreamParam(0x10, source); + + Require(sceJpegDecDecodeWithInputControl(nullptr, &parse, &info) == invalidHandle); + Require(sceJpegDecDecodeWithInputControl(static_cast(decoder.handle) + 1, &parse, &info) == invalidHandle); + std::array notHandle{}; + Require(sceJpegDecDecodeWithInputControl(notHandle.data(), &parse, &info) == invalidHandle); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, nullptr, &info) == invalidAddr); + JpegDecInputControlParam noInput = parse; + noInput.input = nullptr; + noInput.operation = 0x30; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &noInput, &info) == invalidAddr); + for (std::uint16_t operation : {0x00, 0x01, 0x12, 0x22, 0x30, 0x31}) { + JpegDecInputControlParam unknown = parse; + unknown.operation = operation; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &unknown, nullptr) == invalidParam); + } + JpegDecInputControlParam reserved = parse; + reserved.reserved = 1; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &reserved, nullptr) == invalidParam); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &parse, nullptr) == invalidAddr); + Require(source.calls == 0); + + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &parse, &info) == 0); + Require(info.image_width == 96 && info.image_height == 64 && info.output_image_width == 96); + Require(source.offset < jpeg.size() && source.offset <= 0x800 + source.chunkSize); + const std::size_t parseCalls = source.calls; + + std::vector image(96 * 64 * 4); + JpegDecInputControlParam decode = StreamParam(0x20, source); + decode.image_mem_addr = image.data(); + decode.image_mem_size = static_cast(image.size()); + JpegDecInputControlParam bad = decode; + bad.image_mem_addr = nullptr; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &bad, nullptr) == invalidAddr); + bad = decode; + bad.image_mem_addr = image.data() + 1; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &bad, nullptr) == invalidAddr); + bad.pixel_format = 11; + bad.image_mem_size = 0; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &bad, nullptr) == invalidSize); + bad = decode; + bad.image_mem_size = 0; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &bad, nullptr) == invalidSize); + bad = decode; + bad.down_scale = 3; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &bad, nullptr) == invalidParam); + bad = decode; + bad.pixel_format = 2; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &bad, nullptr) == invalidParam); + bad = decode; + bad.alpha_value = 256; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &bad, nullptr) == invalidParam); + bad = decode; + bad.image_mem_size = 96 * 64 * 4 - 1; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &bad, nullptr) == invalidSize); + + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &decode, nullptr) == (96 << 16 | 64)); + Require(image[3] == 0x80); + std::int32_t expected = 0; + Require(image == Reference(jpeg, 96, 64, 1, &expected) && expected == (96 << 16 | 64)); + + Source failing{jpeg, 100}; + failing.failure = static_cast(0x8000BEEF); + JpegDecInputControlParam failingParse = StreamParam(0x10, failing); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &failingParse, &info) == static_cast(0x8000BEEF)); + Require(failing.calls == 1); + Source garbage{std::vector(5000, 0x11), 1000}; + JpegDecInputControlParam garbageParse = StreamParam(0x10, garbage); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &garbageParse, &info) == decodeError); + Require(garbage.calls == 3 && garbage.offset == 3000); + std::vector imageEnd{0xFF, 0xD8, 0xFF, 0xD9}; + imageEnd.resize(10000, 0x00); + Source ended{imageEnd, 1000}; + JpegDecInputControlParam endedParse = StreamParam(0x10, ended); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &endedParse, &info) == decodeError); + Require(ended.calls == 3); + std::vector broken{0xFF, 0xD8, 0x12, 0x34}; + broken.resize(10000, 0xFF); + Source brokenSource{broken, 1000}; + JpegDecInputControlParam brokenParse = StreamParam(0x10, brokenSource); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &brokenParse, &info) == decodeError); + Require(brokenSource.calls == 3); + + Source again{jpeg, 512}; + JpegDecInputControlParam againParse = StreamParam(0x10, again); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &againParse, &info) == 0); + again.failure = -2; + JpegDecInputControlParam againDecode = decode; + againDecode.input_argument = &again; + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &againDecode, nullptr) == -2); + + std::vector withApp(jpeg.begin(), jpeg.begin() + 2); + const std::array app{0xFF, 0xE1, 0x00, 0x0A, 0xFF, 0xD8, 0x00, 0xFF, 0xD9, 0x00, 0xFF, 0xD9}; + withApp.insert(withApp.end(), app.begin(), app.end()); + withApp.insert(withApp.end(), jpeg.begin() + 2, jpeg.end()); + const std::size_t realEnd = withApp.size(); + withApp.resize(realEnd + 0x10000, 0x5A); + Source appSource{withApp, 300}; + JpegDecInputControlParam appParse = StreamParam(0x10, appSource); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &appParse, &info) == 0); + JpegDecInputControlParam appDecode = decode; + appDecode.input_argument = &appSource; + std::fill(image.begin(), image.end(), 0); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &appDecode, nullptr) == (96 << 16 | 64)); + Require(appSource.offset >= realEnd && appSource.offset <= realEnd + 0x800 + appSource.chunkSize); + Require(image == Reference(jpeg, 96, 64, 1, &expected)); + + Require(sceJpegDecDelete(decoder.handle) == 0); + Require(sceJpegDecDecodeWithInputControl(decoder.handle, &decode, nullptr) == invalidHandle); + + RequireStreamOutlivesHandle(jpeg, 96, 64); + RequireStreamMatches(TestJpeg(40, 24, 1), 40, 24, 1, 1); + RequireStreamMatches(TestJpeg(64, 48, 3), 64, 48, 2, 37); + RequireStreamMatches(TestJpeg(64, 48, 3), 64, 48, 8, 0x10000); +} diff --git a/core/libs/tests/GuestJson.cpp b/core/libs/tests/GuestJson.cpp index 1cc2993f93..589895b3d4 100644 --- a/core/libs/tests/GuestJson.cpp +++ b/core/libs/tests/GuestJson.cpp @@ -1,5 +1,7 @@ #include "prx/libSceJson2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include #include #include #include @@ -18,9 +20,6 @@ using namespace SceJson2; using SetNullAccessCallback = int (APS5_VABI*)(void*, NullAccessCallback, void*); extern "C" { - -extern std::size_t _ZN3sce4Json6String4nposE; - } static void Check(bool value, int line) { @@ -76,7 +75,7 @@ static void ParseAndRoundTrip() { 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); + Require(*_ZNK3sce4Json5Value7getRealEv(&Member(root, "count")) == 0.0); Require(_ZNK3sce4Json5Value7getTypeEv(&Member(root, "big")) == TypeUInteger); Require(*_ZNK3sce4Json5Value11getUIntegerEv(&Member(root, "big")) == std::numeric_limits::max()); Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "neg")) == -5); @@ -120,9 +119,13 @@ static void ParseAndRoundTrip() { Require(Serialize(reparsed) == serialized); _ZN3sce4Json5ValueD1Ev(&reparsed); - for (const char* invalid : {"{\"a\":}", "[1,]", "\"open", "tru"}) { - Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, invalid, std::strlen(invalid)) == JsonErrorParse); - Require(_ZNK3sce4Json5Value7getTypeEv(&root) == TypeObject && _ZNK3sce4Json5Value5countEv(&root) == 8); + for (const auto& [invalid, expected] : std::vector>{ + {"{\"a\":}", "{\"a\":null}"}, {"[1,]", "[1,null]"}, {"\"open", "\"open\""}, {"tru", "null"}}) { + Value partial{}; + _ZN3sce4Json5ValueC1Ev(&partial); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&partial, invalid, std::strlen(invalid)) == JsonErrorParse); + Require(Serialize(partial) == expected); + _ZN3sce4Json5ValueD1Ev(&partial); } Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, nullptr, 0) == JsonErrorParse); bool nullTextRejected = false; @@ -135,16 +138,13 @@ static void ParseAndRoundTrip() { 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; - } - Require(unverified); - Require(_ZNK3sce4Json5Value7getTypeEv(&root) == TypeObject && _ZNK3sce4Json5Value5countEv(&root) == 8); + for (const auto& [overflow, expected] : std::vector>{ + {"1e309", "inf"}, {"[-1e309]", "[-inf]"}, {"{\"a\":[1,1e400]}", "{\"a\":[1,inf]}"}}) { + Value partial{}; + _ZN3sce4Json5ValueC1Ev(&partial); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&partial, overflow, std::strlen(overflow)) == 0); + Require(Serialize(partial) == expected); + _ZN3sce4Json5ValueD1Ev(&partial); } _ZN3sce4Json5ValueD1Ev(&root); @@ -216,8 +216,8 @@ static void ObjectsAndArrays() { Value* first = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &alpha); Require(!_ZNK3sce4Json6Object5emptyEv(&object)); Require(_ZNK3sce4Json5Value7getTypeEv(first) == TypeNull); - Require(_ZN3sce4Json5Value3setEl(first, 1) == 0); - Require(_ZN3sce4Json5Value3setEPKc(_ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &beta), "b") == 0); + _ZN3sce4Json5Value3setEl(first, 1); + _ZN3sce4Json5Value3setEPKc(_ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &beta), "b"); Require(_ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &alpha) == first); std::vector keys; @@ -265,22 +265,15 @@ static void ObjectsAndArrays() { for (; _ZNK3sce4Json5Array8iteratorneERKS2_(&arrayIt, &arrayEnd); _ZN3sce4Json5Array8iteratorppEv(&arrayIt)) ++visited; Require(visited == 2); Require(*_ZNK3sce4Json5Value10getBooleanEv(_ZNK3sce4Json5Array5frontEv(&array))); + _ZN3sce4Json5Array8iteratorD1Ev(&arrayIt); _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; - } - Require(threw); + Require(_ZNK3sce4Json5Array5frontEv(&array) == _ZNK3sce4Json5Array4backEv(&array)); Require(_ZNK3sce4Json5Array4sizeEv(©) == 2); Require(*_ZNK3sce4Json5Value10getBooleanEv(_ZNK3sce4Json5Array5frontEv(©))); Require(*_ZNK3sce4Json5Value7getRealEv(_ZNK3sce4Json5Array4backEv(©)) == 2.5); @@ -291,7 +284,7 @@ static void ObjectsAndArrays() { _ZN3sce4Json5Array9pop_frontEv(©); Require(_ZNK3sce4Json5Array5emptyEv(©)); - threw = false; + bool threw = false; try { _ZN3sce4Json5Array9pop_frontEv(©); @@ -299,6 +292,8 @@ static void ObjectsAndArrays() { threw = true; } Require(threw && _ZNK3sce4Json5Array5emptyEv(©)); + _ZN3sce4Json5Array8iteratorD1Ev(&arrayIt); + _ZN3sce4Json5Array8iteratorD1Ev(&arrayEnd); _ZN3sce4Json5ArrayD1Ev(©); _ZN3sce4Json5ArrayD1Ev(&array); @@ -310,28 +305,25 @@ static void ObjectPair() { Value value{}; _ZN3sce4Json5ValueC1Ev(&value); - - Require(_ZN3sce4Json5Value3setEPKc(&value, "forty-two") == 0); + _ZN3sce4Json5Value3setEPKc(&value, "forty-two"); 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(pair.reserved == UINT64_C(0x5a5a5a5a5a5a5a5a)); + Require(pair.key.text != key.text && _ZNK3sce4Json5Value9getStringEv(&pair.value) != _ZNK3sce4Json5Value9getStringEv(&value)); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair.key), "answer") == 0); Require(_ZNK3sce4Json5Value7getTypeEv(&pair.value) == TypeString); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&pair.value)), "forty-two") == 0); - Require(_ZN3sce4Json5Value3setEl(&value, 7) == 0); + _ZN3sce4Json5Value3setEl(&value, 7); _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); + Require(pair.key.text == nullptr && _ZNK3sce4Json5Value7getTypeEv(&pair.value) == TypeNull); } static void ArrayIteratorCopies() { @@ -341,8 +333,7 @@ static void ArrayIteratorCopies() { for (std::int64_t i = 1; i <= 3; ++i) { Value item{}; _ZN3sce4Json5ValueC1Ev(&item); - - Require(_ZN3sce4Json5Value3setEl(&item, i) == 0); + _ZN3sce4Json5Value3setEl(&item, i); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &item); _ZN3sce4Json5ValueD1Ev(&item); @@ -365,8 +356,8 @@ static void ArrayIteratorCopies() { ArrayIterator previous{}; while (_ZNK3sce4Json5Array8iteratorneERKS2_(&it, &end)) { _ZN3sce4Json5Array8iteratorD1Ev(&previous); - - Require(_ZN3sce4Json5Array8iteratorppEi(&previous, &it, 0) == &previous); + _ZN3sce4Json5Array8iteratorC1ERKS2_(&previous, &it); + Require(_ZN3sce4Json5Array8iteratorppEi(&it, 0) == &it); seen.push_back(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(&previous))); Require(_ZNK3sce4Json5Array8iteratorneERKS2_(&previous, &it)); } @@ -374,9 +365,14 @@ static void ArrayIteratorCopies() { Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(&it, &end)); Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(&previous)) == 3); Require(_ZN3sce4Json5Array8iteratoraSERKS2_(&it, &end) == &it); - Require(it.state == nullptr); + Require(it.state != nullptr && !_ZNK3sce4Json5Array8iteratorneERKS2_(&it, &end)); Require(_ZN3sce4Json5Array8iteratoraSERKS2_(&it, &begin) == &it); - Require(it.state == nullptr); + Require(it.state != nullptr); + ArrayIterator empty{}; + _ZN3sce4Json5Array8iteratorC1Ev(&empty); + Require(_ZN3sce4Json5Array8iteratoraSERKS2_(&it, &empty) == &it && it.state == nullptr); + Require(_ZN3sce4Json5Array8iteratoraSERKS2_(&it, &begin) == &it && it.state == nullptr); + _ZN3sce4Json5Array8iteratorD1Ev(&empty); Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(&it, &begin)); _ZN3sce4Json5Array8iteratorD1Ev(&previous); @@ -411,9 +407,9 @@ static void ObjectFind() { _ZN3sce4Json6Object8iteratorD1Ev(&found); _ZN3sce4Json6Object8iteratorD1Ev(&end); - Require(_ZN3sce4Json5Value3setEl(_ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &alpha), 1) == 0); + _ZN3sce4Json5Value3setEl(_ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &alpha), 1); Value* second = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &beta); - Require(_ZN3sce4Json5Value3setEPKc(second, "b") == 0); + _ZN3sce4Json5Value3setEPKc(second, "b"); _ZNK3sce4Json6Object3endEv(&end, &object); _ZN3sce4Json6Object8iteratorD1Ev(&found); @@ -464,9 +460,8 @@ static void ObjectIteratorCopies() { Value val1{}, val2{}; _ZN3sce4Json5ValueC1Ev(&val1); _ZN3sce4Json5ValueC1Ev(&val2); - - Require(_ZN3sce4Json5Value3setEl(&val1, 10) == 0); - Require(_ZN3sce4Json5Value3setEl(&val2, 20) == 0); + _ZN3sce4Json5Value3setEl(&val1, 10); + _ZN3sce4Json5Value3setEl(&val2, 20); Pair p1{}, p2{}; _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&p1, &key1, &val1); _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&p2, &key2, &val2); @@ -491,11 +486,8 @@ static void ObjectIteratorCopies() { Require(!_ZNK3sce4Json6Object8iteratoreqERKS2_(&begin, &end)); Require(_ZNK3sce4Json6Object8iteratorneERKS2_(&begin, &end)); - ObjectIterator post{}; - Require(_ZN3sce4Json6Object8iteratorppEi(&post, &begin, 0) == &post); - Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(&post)->key), "one") == 0); + Require(_ZN3sce4Json6Object8iteratorppEi(&begin, 0) == &begin); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(&begin)->key), "two") == 0); - _ZN3sce4Json6Object8iteratorD1Ev(&post); ObjectIterator assigned{}; _ZN3sce4Json6Object8iteratorC1Ev(&assigned); @@ -526,8 +518,7 @@ static void ObjectInsert() { Value value{}; _ZN3sce4Json5ValueC1Ev(&value); - - Require(_ZN3sce4Json5Value3setEl(&value, 100) == 0); + _ZN3sce4Json5Value3setEl(&value, 100); Pair pair{}; _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &value); @@ -541,7 +532,7 @@ static void ObjectInsert() { Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&p->key), "score") == 0); Require(*_ZNK3sce4Json5Value10getIntegerEv(&p->value) == 100); - Require(_ZN3sce4Json5Value3setEl(&pair.value, 200) == 0); + _ZN3sce4Json5Value3setEl(&pair.value, 200); ObjectIterator duplicate{}; Require(_ZN3sce4Json6Object6insertERKNS1_4PairE(&duplicate, &object, &pair) == &duplicate); Require(_ZNK3sce4Json6Object8iteratoreqERKS2_(&duplicate, &inserted)); @@ -554,8 +545,7 @@ static void ObjectInsert() { Value otherValue{}; _ZN3sce4Json5ValueC1Ev(&otherValue); - - Require(_ZN3sce4Json5Value3setEPKc(&otherValue, "str") == 0); + _ZN3sce4Json5Value3setEPKc(&otherValue, "str"); Pair otherPair{}; _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&otherPair, &otherKey, &otherValue); @@ -626,8 +616,7 @@ static void ObjectKeyOrder() { 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})"}) { + for (const char* text : {R"({"a":1,"a":2})", R"({"a":[1,2],"a":2})", R"({"a":{"old":true},"a":2})"}) { Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, std::strlen(text)) == 0); Require(_ZNK3sce4Json5Value5countEv(&root) == 1); Require(Serialize(root) == R"({"a":2})"); @@ -670,14 +659,7 @@ static void StringMethods() { 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(_ZNK3sce4Json6String2atEm(&base, 5) == 0); Require(_ZNK3sce4Json6String4findEPKcm(&base, "/b", 0) == 1); Require(_ZNK3sce4Json6String4findEPKcm(&base, "/", 2) == 3); Require(_ZNK3sce4Json6String4findEPKcm(&base, "", 5) == 5); @@ -698,15 +680,7 @@ static void StringMethods() { 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); + Require(_ZNK3sce4Json6String4findEPKcm(&base, nullptr, 0) == 0); String part{}; Require(_ZNK3sce4Json6String6substrEmm(&part, &base, 2, 3) == &part); Require(text(part) == "b/c"); @@ -760,14 +734,13 @@ static void ValueSwap() { Value v1{}, v2{}; _ZN3sce4Json5ValueC1Ev(&v1); _ZN3sce4Json5ValueC1Ev(&v2); - - Require(_ZN3sce4Json5Value3setEl(&v1, 123) == 0); - Require(_ZN3sce4Json5Value3setEPKc(&v2, "hello") == 0); + _ZN3sce4Json5Value3setEl(&v1, 123); + _ZN3sce4Json5Value3setEPKc(&v2, "hello"); _ZN3sce4Json5Value4swapERS1_(&v1, &v2); Require(*_ZNK3sce4Json5Value10getIntegerEv(&v2) == 123); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&v1)), "hello") == 0); - Require(_ZN3sce4Json5Value3setEl(&v1, -456) == 0); + _ZN3sce4Json5Value3setEl(&v1, -456); const auto* firstStorage = _ZNK3sce4Json5Value10getIntegerEv(&v1); const auto* secondStorage = _ZNK3sce4Json5Value10getIntegerEv(&v2); _ZN3sce4Json5Value4swapERS1_(&v1, &v2); @@ -799,17 +772,16 @@ static void ArrayIteratorAdvance() { ArrayIterator copy{}; _ZN3sce4Json5Array8iteratorC1ERKS2_(©, &begin); - - Require(_ZN3sce4Json5Array8iterator7advanceEm(©, 3) == ©); + _ZN3sce4Json5Array8iterator7advanceEm(©, 3); Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(©)) == 30); ArrayIterator end{}; _ZNK3sce4Json5Array3endEv(&end, &array); _ZN3sce4Json5Array8iterator7advanceEm(©, std::numeric_limits::max()); - + Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(©, &begin)); + _ZN3sce4Json5Array8iterator7advanceEm(©, 5); Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(©, &end)); _ZN3sce4Json5Array8iteratorppEv(©); - - Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(©, &end)); + Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(©, &begin)); Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(&begin)) == 0); _ZN3sce4Json5Array8iteratorD1Ev(&end); _ZN3sce4Json5Array8iteratorD1Ev(&begin); @@ -853,8 +825,7 @@ static void ObjectOperations() { ObjectIterator copy{}; _ZN3sce4Json6Object8iteratorC1ERKS2_(©, &begin); - - Require(_ZN3sce4Json6Object8iterator7advanceEm(©, 2) == ©); + _ZN3sce4Json6Object8iterator7advanceEm(©, 2); Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(©)->key), "gamma") == 0); _ZN3sce4Json6Object8iterator7advanceEm(©, 1); @@ -904,35 +875,35 @@ static void NullAccess(SetNullAccessCallback setCallback) { lastContext = nullptr; alignas(16) std::uint8_t initializer[16]{}; alignas(16) std::uint8_t parameter[64]{}; - Require(_ZN3sce4Json11InitializerC1Ev(initializer) == 0); + JsonTest::Allocator allocator; + void* allocatorPointer = &allocator; + std::memcpy(parameter, &allocatorPointer, sizeof(allocatorPointer)); + _ZN3sce4Json11InitializerC1Ev(initializer); Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(initializer, parameter) == 0); int context = 0; Require(setCallback(initializer, OnNullAccess, &context) == 0); _ZN3sce4Json5ValueC1Ev(&fallback); - - Require(_ZN3sce4Json5Value3setEl(&fallback, 99) == 0); - + _ZN3sce4Json5Value3setEl(&fallback, 99); Value root{}; _ZN3sce4Json5ValueC1Ev(&root); - - const char text[] = "{\"present\":\"text\"}"; + const char text[] = "{\"present\":\"text\",\"none\":null}"; Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); - Require(&Member(root, "missing") == &fallback); - Require(callbackCalls == 1 && lastRequested == TypeNull && lastParent == &root && lastContext == &context); - Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "present")) == 99); - Require(callbackCalls == 2 && lastRequested == TypeInteger && lastParent == &Member(root, "present")); - Require(!*_ZNK3sce4Json5Value10getBooleanEv(&Member(root, "present"))); - Require(callbackCalls == 3 && lastRequested == TypeBoolean); - Require(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5ValueixEm(&root, 0)) == TypeInteger); - - Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0); - const Value& unhandled = Member(root, "missing"); - Require(&unhandled != &fallback && _ZNK3sce4Json5Value7getTypeEv(&unhandled) == TypeNull); - Require(callbackCalls == 4); + Require(_ZNK3sce4Json5Value7getTypeEv(&Member(root, "missing")) == TypeNull && callbackCalls == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "none")) == 99); + Require(callbackCalls == 1 && lastRequested == TypeInteger && lastParent == &root && lastContext == &context); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "present")) == 0 && callbackCalls == 1); + Require(!*_ZNK3sce4Json5Value10getBooleanEv(&Member(root, "none"))); + Require(callbackCalls == 2 && lastRequested == TypeBoolean); + Require(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5ValueixEm(&root, 0)) == TypeNull); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "missing")) == 99 && callbackCalls == 3); _ZN3sce4Json5ValueD1Ev(&root); _ZN3sce4Json5ValueD1Ev(&fallback); - - Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); + Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0); + Value after{}; + _ZN3sce4Json5ValueC1Ev(&after); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&after) == 0 && callbackCalls == 3); + _ZN3sce4Json5ValueD1Ev(&after); + _ZN3sce4Json11InitializerD1Ev(initializer); } static void ValueAccess() { @@ -974,10 +945,10 @@ static void ValueAccess() { Value* mutableList = _ZN3sce4Json5Value10referValueERKNS0_6StringE(&root, &key); Value* element = _ZN3sce4Json5Value10referValueEm(mutableList, 0); Require(element != nullptr); - Require(_ZN3sce4Json5Value3setEl(element, 15) == 0); + _ZN3sce4Json5Value3setEl(element, 15); Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Value8getValueEm(list, 0)) == 15); Require(_ZN3sce4Json5Value10referValueEm(mutableList, 3) == nullptr); - Require(_ZN3sce4Json5Value10referValueEm(&root, 0) == nullptr); + Require(_ZN3sce4Json5Value10referValueEm(&root, 0) != nullptr); _ZN3sce4Json6StringD1Ev(&key); String missing{}; @@ -987,11 +958,13 @@ static void ValueAccess() { _ZN3sce4Json6StringD1Ev(&missing); _ZN3sce4Json5ValueD1Ev(&root); +} + +static void ValueAccessParameters() { alignas(16) std::uint8_t parameter[40]; std::memset(parameter, 0xff, sizeof(parameter)); - _ZN3sce4Json14InitParameter2C1Ev(reinterpret_cast(parameter)); - - int allocator = 0; + _ZN3sce4Json14InitParameter2C1Ev(reinterpret_cast(parameter)); + JsonTest::Allocator allocator; int userData = 0; _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(reinterpret_cast(parameter), &allocator, &userData); _ZN3sce4Json14InitParameter217setFileBufferSizeEm(reinterpret_cast(parameter), 4096); @@ -1000,10 +973,10 @@ static void ValueAccess() { 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, reinterpret_cast(parameter)) == 0); + _ZN3sce4Json11InitializerC1Ev(initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, reinterpret_cast(parameter)) == 0); Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0); - Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); + _ZN3sce4Json11InitializerD1Ev(initializer); } static void InitParameter2BaseConstructor() { @@ -1024,7 +997,7 @@ static void InitParameter2BaseConstructor() { std::memcpy(&defaultUserData, baseParameter + sizeof(void*), sizeof(defaultUserData)); std::memcpy(&defaultFileBufferSize, baseParameter + 2 * sizeof(void*), sizeof(defaultFileBufferSize)); Require(defaultAllocator == nullptr && defaultUserData == nullptr && defaultFileBufferSize == 0); - int allocator = 0; + JsonTest::Allocator allocator; int userData = 0; _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(reinterpret_cast(baseParameter), &allocator, &userData); _ZN3sce4Json14InitParameter217setFileBufferSizeEm(reinterpret_cast(baseParameter), 4096); @@ -1040,10 +1013,10 @@ static void InitParameter2BaseConstructor() { for (std::size_t i = 16 + sizeof(parameter); i < sizeof(baseStorage); ++i) Require(baseStorage[i] == 0xff); alignas(16) std::uint8_t initializer[16]{}; - Require(_ZN3sce4Json11InitializerC1Ev(initializer) == 0); - Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, reinterpret_cast(baseParameter)) == 0); + _ZN3sce4Json11InitializerC1Ev(initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, reinterpret_cast(baseParameter)) == 0); Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0); - Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); + _ZN3sce4Json11InitializerD1Ev(initializer); } static_assert(sizeof(InitParameter2) == 40); @@ -1054,12 +1027,6 @@ static InitParameter2 Stored(const std::uint8_t* parameter) { return stored; } -static bool Untouched(const std::uint8_t* parameter, std::size_t from) { - for (std::size_t i = from; i < 40; ++i) - if (parameter[i] != 0xff) return false; - return true; -} - static void InitParameters() { using Constructor = decltype(&_ZN3sce4Json14InitParameter2C1Ev); for (Constructor construct : {_ZN3sce4Json14InitParameter2C1Ev, _ZN3sce4Json14InitParameter2C2Ev}) { @@ -1071,12 +1038,11 @@ static void InitParameters() { Require(stored.specialFloatFormatType == 2); Require(stored.reserved[0] == 0 && stored.reserved[1] == 0 && stored.reserved[2] == 0); - int allocParam = 0; + JsonTest::Allocator allocParam; int userData = 0; - _ZN3sce4Json14InitParameter217setFileBufferSizeEm(reinterpret_cast(parameter), 8192); - _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(reinterpret_cast(parameter), 2); - _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(reinterpret_cast(parameter), &allocParam, &userData); - + _ZN3sce4Json14InitParameter217setFileBufferSizeEm(reinterpret_cast(parameter), 8192); + _ZN3sce4Json14InitParameter225setSpecialFloatFormatTypeENS0_22SpecialFloatFormatTypeE(reinterpret_cast(parameter), 2); + _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(reinterpret_cast(parameter), &allocParam, &userData); stored = Stored(parameter); Require(stored.allocator == &allocParam && stored.userData == &userData && stored.fileBufferSize == 8192); Require(stored.specialFloatFormatType == 2); @@ -1086,22 +1052,12 @@ static void InitParameters() { Require(Stored(parameter).specialFloatFormatType == 0 && Stored(parameter).fileBufferSize == 8192); alignas(16) std::uint8_t initializer[16]{}; - Require(_ZN3sce4Json11InitializerC1Ev(initializer) == 0); - Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, reinterpret_cast(parameter)) == 0); + _ZN3sce4Json11InitializerC1Ev(initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(initializer, reinterpret_cast(parameter)) == 0); Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0); - Require(_ZN3sce4Json11InitializerD1Ev(initializer) == 0); + _ZN3sce4Json11InitializerD1Ev(initializer); } - alignas(16) std::uint8_t rttiParameter[40]; - std::memset(rttiParameter, 0xff, sizeof(rttiParameter)); - int allocParam = 0; - int userData = 0; - _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); - Require(Untouched(rttiParameter, sizeof(rttiStored))); } static void ValueClear() { @@ -1113,13 +1069,13 @@ static void ValueClear() { Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeNull); - Require(_ZN3sce4Json5Value3setEl(&value, 12345) == 0); + _ZN3sce4Json5Value3setEl(&value, 12345); Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeInteger); _ZN3sce4Json5Value5clearEv(&value); Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeNull); - Require(_ZN3sce4Json5Value3setEPKc(&value, "test string") == 0); + _ZN3sce4Json5Value3setEPKc(&value, "test string"); Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeString); _ZN3sce4Json5Value5clearEv(&value); @@ -1143,7 +1099,7 @@ static void ValueClear() { _ZN3sce4Json6StringD1Ev(&key); _ZN3sce4Json6ObjectD1Ev(&obj); - Require(_ZN3sce4Json5Value3setEl(&value, 777) == 0); + _ZN3sce4Json5Value3setEl(&value, 777); Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeInteger); Require(*_ZNK3sce4Json5Value10getIntegerEv(&value) == 777); @@ -1180,7 +1136,7 @@ static void IntegerRepresentation() { } Require(*_ZNK3sce4Json5Value10getIntegerEv(&value) == -1); Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeUInteger); - Require(_ZN3sce4Json5Value3setEl(&value, -1) == 0); + _ZN3sce4Json5Value3setEl(&value, -1); Require(*_ZNK3sce4Json5Value11getUIntegerEv(&value) == std::numeric_limits::max()); Require(_ZNK3sce4Json5Value7getTypeEv(&value) == TypeInteger); for (const char* text : {"-0", "-1", "-9223372036854775808"}) { @@ -1224,31 +1180,27 @@ static void StreamParse() { } -static void StaticNullAccess() { +static void ValueNullAccess() { callbackCalls = 0; lastRequested = -1; lastParent = nullptr; lastContext = nullptr; int context = 0; - _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(OnNullAccess, &context); _ZN3sce4Json5ValueC1Ev(&fallback); - - Require(_ZN3sce4Json5Value3setEl(&fallback, 99) == 0); - + _ZN3sce4Json5Value3setEl(&fallback, 99); Value root{}; _ZN3sce4Json5ValueC1Ev(&root); - - const char text[] = "{\"present\":\"text\"}"; + const char text[] = "{\"present\":\"text\",\"none\":null}"; Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0); - Require(&Member(root, "missing") == &fallback); - Require(callbackCalls == 1 && lastRequested == TypeNull && lastParent == &root && lastContext == &context); - Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "present")) == 99); - Require(callbackCalls == 2 && lastRequested == TypeInteger); - - _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(nullptr, nullptr); - const Value& unhandled = Member(root, "missing"); - Require(&unhandled != &fallback && _ZNK3sce4Json5Value7getTypeEv(&unhandled) == TypeNull); - Require(callbackCalls == 2); + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&root, OnNullAccess, &context) == 0); + Require(_ZNK3sce4Json5Value7getTypeEv(&Member(root, "missing")) == TypeNull && callbackCalls == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "none")) == 99); + Require(callbackCalls == 1 && lastRequested == TypeInteger && lastParent == &root && lastContext == &context); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "present")) == 0 && callbackCalls == 1); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "missing")) == 99 && callbackCalls == 2); + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&root, nullptr, nullptr) == 0); + Require(_ZNK3sce4Json5Value7getTypeEv(&Member(root, "missing")) == TypeNull); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "none")) == 0 && callbackCalls == 2); _ZN3sce4Json5ValueD1Ev(&root); _ZN3sce4Json5ValueD1Ev(&fallback); @@ -1272,13 +1224,12 @@ static const Value& APS5_VABI OnTypeMismatch(ValueType requested, const Value* v static void RequireMismatch(int calls, ValueType requested, const Value* value, void* context) { Require(mismatchCalls == calls && mismatchRequested == requested && mismatchValue == value); - Require(mismatchSecond == nullptr && mismatchContext == context); + Require(mismatchSecond == value->parent && mismatchContext == context); } static void ValueTypeMismatchHandler() { _ZN3sce4Json5ValueC1Ev(&replacement); - - Require(_ZN3sce4Json5Value3setEl(&replacement, 42) == 0); + _ZN3sce4Json5Value3setEl(&replacement, 42); int context = 0; Value root{}; _ZN3sce4Json5ValueC1Ev(&root); @@ -1297,49 +1248,49 @@ static void ValueTypeMismatchHandler() { Require(_ZNK3sce4Json5Array4sizeEv(_ZNK3sce4Json5Value8getArrayEv(&root)) == 0); RequireMismatch(3, TypeArray, &root, &context); const Value* element = _ZNK3sce4Json5ValueixEm(&Member(root, "list"), 0); - Require(*_ZNK3sce4Json5Value7getRealEv(element) == 0.0); + const double* missingReal = _ZNK3sce4Json5Value7getRealEv(element); + Require(missingReal == &_ZN3sce4Json5Value10s_nullrealE && *missingReal == 0.0); RequireMismatch(4, TypeReal, element, &context); - Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "none")) == 0); - Require(mismatchCalls == 4); + Require(_ZNK3sce4Json5Value10getIntegerEv(&Member(root, "none")) == &replacement.integer); + Require(replacement.integer == 42); + Require(mismatchCalls == 5); Value later{}; _ZN3sce4Json5ValueC1Ev(&later); Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&later, text, sizeof(text) - 1) == 0); Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(later, "name")) == 0); - Require(mismatchCalls == 4); + Require(mismatchCalls == 5); int laterContext = 0; Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&later, OnTypeMismatch, &laterContext) == 0); Require(*_ZNK3sce4Json5Value10getIntegerEv(&Member(later, "name")) == 42); - RequireMismatch(5, TypeInteger, &Member(later, "name"), &laterContext); + RequireMismatch(6, TypeInteger, &Member(later, "name"), &laterContext); Value other{}; _ZN3sce4Json5ValueC1Ev(&other); - - Require(_ZN3sce4Json5Value3setEPKc(&other, "other") == 0); + _ZN3sce4Json5Value3setEPKc(&other, "other"); Require(_ZN3sce4Json5ValueaSERKS1_(&root, &other) == &root); Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); - RequireMismatch(6, TypeInteger, &root, &context); - Require(*_ZNK3sce4Json5Value10getIntegerEv(&other) == 0); - Require(mismatchCalls == 6); - Require(_ZN3sce4Json5Value3setEd(&other, 1.5) == 0); - Require(_ZN3sce4Json5Value3setERKS1_(&root, &other) == 0); - Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); RequireMismatch(7, TypeInteger, &root, &context); - _ZN3sce4Json5Value5clearEv(&root); - - Require(_ZN3sce4Json5Value3setEPKc(&root, "cleared") == 0); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&other) == 0); + Require(mismatchCalls == 7); + _ZN3sce4Json5Value3setEd(&other, 1.5); + _ZN3sce4Json5Value3setERKS1_(&root, &other); Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); RequireMismatch(8, TypeInteger, &root, &context); + _ZN3sce4Json5Value5clearEv(&root); + _ZN3sce4Json5Value3setEPKc(&root, "cleared"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 42); + RequireMismatch(9, TypeInteger, &root, &context); Value copy{}; _ZN3sce4Json5ValueC1ERKS1_(©, &root); Require(*_ZNK3sce4Json5Value10getIntegerEv(©) == 0); - Require(mismatchCalls == 8); + Require(mismatchCalls == 9); Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, nullptr, nullptr) == 0); Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 0); - Require(mismatchCalls == 8); + Require(mismatchCalls == 9); _ZN3sce4Json5ValueD1Ev(©); _ZN3sce4Json5ValueD1Ev(&other); _ZN3sce4Json5ValueD1Ev(&later); @@ -1350,20 +1301,10 @@ static void ValueTypeMismatchHandler() { static int failureCalls = 0; -static void APS5_VABI OnElementAccessFailure(std::int32_t, const String*, const std::size_t*, const Value*, const Value*, void*) { +static void APS5_VABI OnElementAccessFailure(int, const String*, const std::size_t*, const Value*, const Value*, void*) { ++failureCalls; } -template -static bool ThrowsUnverified(const TAccess& access) { - try { - access(); - } catch (const std::runtime_error& error) { - return std::string(error.what()).find("unverified") != std::string::npos; - } - return false; -} - static void ValueElementAccessFailureHandler() { Value root{}; _ZN3sce4Json5ValueC1Ev(&root); @@ -1378,12 +1319,11 @@ 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); })); - Require(ThrowsUnverified([&] { _ZNK3sce4Json5Value8getValueEm(&list, 1); })); - Require(failureCalls == 0); + Require(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5ValueixEPKc(&root, "missing")) == TypeNull); + Require(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5Value8getValueERKNS0_6StringE(&root, &missing)) == TypeNull); + Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5ValueixEm(&root, 0)) == 2); + Require(_ZNK3sce4Json5Value7getTypeEv(_ZNK3sce4Json5Value8getValueEm(&list, 1)) == TypeNull); + Require(failureCalls == 3); Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, nullptr, nullptr) == 0); Require(_ZNK3sce4Json5Value7getTypeEv(&Member(root, "missing")) == TypeNull); @@ -1410,15 +1350,13 @@ static void ValueSpecialFloatHandler() { 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); })); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&real, &out) == 0); + _ZN3sce4Json5Value3setEd(&real, std::numeric_limits::infinity()); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&real, &out) == 0); _ZN3sce4Json6StringD1Ev(&out); - - Require(_ZN3sce4Json5Value3setEd(&real, 2.5) == 0); + _ZN3sce4Json5Value3setEd(&real, 2.5); Require(Serialize(real) == "2.5"); - Require(specialFloatCalls == 0); + Require(specialFloatCalls == 2); _ZN3sce4Json5ValueD1Ev(&real); } @@ -1431,7 +1369,7 @@ static void ValueOperatorBool() { _ZN3sce4Json5ValueD1Ev(&nullVal); Value typedNull{}; - _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&typedNull, TypeNull); + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&typedNull, static_cast(TypeNull)); Require(!_ZNK3sce4Json5ValuecvbEv(&typedNull)); _ZN3sce4Json5ValueD1Ev(&typedNull); @@ -1452,19 +1390,19 @@ static void ValueOperatorBool() { _ZN3sce4Json5ValueC1Ev(&scalar); for (std::int64_t value : {std::int64_t{0}, std::int64_t{1}, std::int64_t{-1}}) { - Require(_ZN3sce4Json5Value3setEl(&scalar, value) == 0); + _ZN3sce4Json5Value3setEl(&scalar, value); 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); + _ZN3sce4Json5Value3setEm(&scalar, value); 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); + _ZN3sce4Json5Value3setEd(&scalar, value); Require(_ZNK3sce4Json5ValuecvbEv(&scalar) == (value != 0.0)); } for (const char* text : {"", "0", "false", " "}) { - Require(_ZN3sce4Json5Value3setEPKc(&scalar, text) == 0); + _ZN3sce4Json5Value3setEPKc(&scalar, text); Require(_ZNK3sce4Json5ValuecvbEv(&scalar) == (*text != 0)); } _ZN3sce4Json5ValueD1Ev(&scalar); @@ -1489,7 +1427,21 @@ static void ValueOperatorBool() { } +struct ActiveModule { + JsonTest::Allocator allocator; + alignas(8) std::uint8_t parameter[40]{}; + std::uint8_t initializer = 0; + ActiveModule() { + _ZN3sce4Json14InitParameter2C1Ev(reinterpret_cast(parameter)); + _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(reinterpret_cast(parameter), &allocator, nullptr); + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(parameter)) == 0); + } + ~ActiveModule() { Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0); } +}; + static int NumericLocale() { + ActiveModule module; 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"}) { @@ -1511,8 +1463,12 @@ static int NumericLocale() { ++failures; } }; - for (const auto number : {1.5, -0.125, 1.25e30, std::numeric_limits::max(), - std::numeric_limits::min(), std::numeric_limits::denorm_min()}) { + for (const auto [number, expected] : std::vector>{ + {1.5, UINT64_C(0x3ff8000000000000)}, {-0.125, UINT64_C(0xbfc0000000000000)}, + {1.25e30, UINT64_C(0x462f8def8808b023)}, + {std::numeric_limits::max(), UINT64_C(0x7feffffffffffff9)}, + {std::numeric_limits::min(), UINT64_C(0x0010000000000003)}, + {std::numeric_limits::denorm_min(), UINT64_C(1)}}) { Value value{}; _ZN3sce4Json5ValueC1Ed(&value, number); @@ -1522,23 +1478,23 @@ static int NumericLocale() { _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()); + check(result == 0 && std::bit_cast(*_ZNK3sce4Json5Value7getRealEv(&parsed)) == expected, "Reference decimal conversion", 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}}) { + for (const auto [text, expected] : std::vector>{ + {"1.5", UINT64_C(0x3ff8000000000000)}, {"-1.25e2", UINT64_C(0xc05f400000000000)}, + {"4.9406564584124654e-324", UINT64_C(1)}, {"2.4703282292062327e-324", UINT64_C(0)}, + {"2.4703282292062328e-324", UINT64_C(0)}, + {"18446744073709551616", UINT64_C(0x43f0000000000000)}, + {"1e-999", UINT64_C(0)}, {"-1e-999", UINT64_C(0x8000000000000000)}}) { 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); + check(result == 0 && std::bit_cast(parsed) == expected, "Decimal-point parsing", text); _ZN3sce4Json5ValueD1Ev(&value); } @@ -1552,14 +1508,8 @@ static int NumericLocale() { 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"); + check(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, "1e309", 5) == 0 && + std::isinf(*_ZNK3sce4Json5Value7getRealEv(&root)), "Reference overflow admission", "1e309"); _ZN3sce4Json5ValueD1Ev(&root); check(std::strcmp(std::localeconv()->decimal_point, ",") == 0, "Preserve host locale", selected); @@ -1567,36 +1517,68 @@ static int NumericLocale() { return failures ? 1 : 0; } +static void ScalarDefaultIdentity() { + Value empty{}; + _ZN3sce4Json5ValueC1Ev(&empty); + Require(_ZNK3sce4Json5Value10getBooleanEv(&empty) == &_ZN3sce4Json5Value10s_nullboolE); + Require(_ZNK3sce4Json5Value10getIntegerEv(&empty) == &_ZN3sce4Json5Value13s_nullintegerE); + Require(_ZNK3sce4Json5Value11getUIntegerEv(&empty) == &_ZN3sce4Json5Value14s_nulluintegerE); + Require(_ZNK3sce4Json5Value7getRealEv(&empty) == &_ZN3sce4Json5Value10s_nullrealE); + _ZN3sce4Json5ValueD1Ev(&empty); + + Value actual{}; + _ZN3sce4Json5ValueC1Eb(&actual, true); + Require(_ZNK3sce4Json5Value10getBooleanEv(&actual) == &actual.boolean); + Require(*_ZNK3sce4Json5Value10getBooleanEv(&actual)); + _ZN3sce4Json5ValueD1Ev(&actual); +} + int main(int argc, char** argv) { if (argc == 2 && std::strcmp(argv[1], "--numeric-locale") == 0) return NumericLocale(); - ParseAndRoundTrip(); - NestingDepth(); - ObjectsAndArrays(); + { + ActiveModule module; + ScalarDefaultIdentity(); + ParseAndRoundTrip(); + NestingDepth(); + ObjectsAndArrays(); ArrayAssignment(); - ObjectFind(); - ObjectPair(); - ObjectInsert(); - ObjectKeyOrder(); - ArrayIteratorCopies(); - ObjectIteratorCopies(); - AllocatorNotifyError(); - StringMethods(); - StringComparison(); - ParseDuplicateKeys(); - ValueSwap(); - ArrayIteratorAdvance(); - ObjectOperations(); + ObjectFind(); + ObjectPair(); + ObjectInsert(); + ObjectKeyOrder(); + ArrayIteratorCopies(); + ObjectIteratorCopies(); + AllocatorNotifyError(); + StringMethods(); + StringComparison(); + ParseDuplicateKeys(); + ValueSwap(); + ArrayIteratorAdvance(); + ObjectOperations(); + } NullAccess(_ZN3sce4Json11Initializer27setGlobalNullAccessCallBackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_); NullAccess(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_); - StaticNullAccess(); + JsonTest::Allocator recoveryAllocator; + alignas(8) std::uint8_t recoveryParameter[40]{}; + _ZN3sce4Json14InitParameter2C1Ev(reinterpret_cast(recoveryParameter)); + _ZN3sce4Json14InitParameter212setAllocatorEPNS0_12MemAllocatorEPv(reinterpret_cast(recoveryParameter), &recoveryAllocator, nullptr); + std::uint8_t recoveryInitializer = 0; + _ZN3sce4Json11InitializerC1Ev(&recoveryInitializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&recoveryInitializer, reinterpret_cast(recoveryParameter)) == 0); + ValueNullAccess(); ValueTypeMismatchHandler(); ValueElementAccessFailureHandler(); ValueSpecialFloatHandler(); - ValueAccess(); + Require(_ZN3sce4Json11Initializer9terminateEv(&recoveryInitializer) == 0); InitParameter2BaseConstructor(); InitParameters(); - ValueClear(); - ValueOperatorBool(); - IntegerRepresentation(); - StreamParse(); + ValueAccessParameters(); + { + ActiveModule module; + ValueAccess(); + ValueClear(); + ValueOperatorBool(); + IntegerRepresentation(); + StreamParse(); + } } diff --git a/core/libs/tests/GuestJsonAliases.cpp b/core/libs/tests/GuestJsonAliases.cpp new file mode 100644 index 0000000000..fb5a3151cd --- /dev/null +++ b/core/libs/tests/GuestJsonAliases.cpp @@ -0,0 +1,153 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; }; +using AliasAllocateCallback = void* (APS5_VABI*)(std::size_t, void*); +using AliasFreeCallback = void (APS5_VABI*)(void*, void*); +using AliasNotifyCallback = void (APS5_VABI*)(int, std::size_t, void*); +struct Callbacks { AliasAllocateCallback allocate; AliasFreeCallback free; AliasNotifyCallback notify; }; +static_assert(sizeof(Value) == 32 && sizeof(Parameter) == 24 && sizeof(Pair) == 48); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct AliasTrace { + std::map live; + int budget = -1; + bool once = false; + std::size_t requests = 0; + unsigned notifications = 0; +}; +static void* APS5_VABI AllocateAlias(std::size_t size, void* context) { + auto& trace = *static_cast(context); + ++trace.requests; + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(size); + Require(memory != nullptr, "Alias tracking allocator requires host memory"); + std::memset(memory, 0xa5, size); trace.live.emplace(memory, size); return memory; +} +static void APS5_VABI FreeAlias(void* memory, void* context) { + if (!memory) return; + Require(static_cast(context)->live.erase(memory) == 1, "Alias destruction must free each allocation through its owning context exactly once"); + std::free(memory); +} +static void APS5_VABI NotifyAlias(int error, std::size_t, void* context) { + Require(static_cast(error) == 0x80848102, "Alias allocation failures must retain the Json2 error"); + ++static_cast(context)->notifications; +} +static const String* StringOf(const Value& value) { return reinterpret_cast(value.payload); } +static void CheckText(const Value& value, const char* expected) { + Require(value.type == 5 && value.payload && std::strcmp(_ZNK3sce4Json6String5c_strEv(StringOf(value)), expected) == 0, "Value constructor variants must preserve string contents"); +} +int main() { + AliasTrace trace; + Callbacks callbacks{AllocateAlias, FreeAlias, NotifyAlias}; + alignas(8) std::array settings; settings.fill(std::byte{0xa5}); + auto* parameter = reinterpret_cast(settings.data()); + _ZN3sce4Json17InitParameterRttiC2EPNS0_14AllocParamRttiEPvm(reinterpret_cast(parameter), &callbacks, &trace, 8192); + Require(parameter->allocator && parameter->context == &trace && parameter->bufferSize == 8192, "RTTI constructor variant must retain its callbacks, context and buffer setting"); + for (std::size_t i = sizeof(Parameter); i < settings.size(); ++i) Require(settings[i] == std::byte{0xa5}, "RTTI construction must preserve bytes beyond its parameter block"); + std::array initializer; initializer.fill(0xa5); + _ZN3sce4Json11InitializerC2Ev(initializer.data()); + Require(initializer[0] == 0, "Initializer constructor variant must clear its active byte"); + for (std::size_t i = 1; i < initializer.size(); ++i) Require(initializer[i] == 0xa5, "Initializer construction must preserve every adjacent byte"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(initializer.data(), parameter) == 0, "Initialize through the constructor variant and RTTI allocator"); + const auto baseline = trace.live.size(); + for (std::int32_t type = 0; type < 8; ++type) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + _ZN3sce4Json5ValueC2ENS0_9ValueTypeE(&value, static_cast(type)); + Require(value.type == type && !value.parent && !value.auxiliary && value.reserved == 0xa5a5a5a5, "Typed value constructor variant must retain its type and inactive metadata"); + _ZN3sce4Json5ValueD2Ev(&value); + Require(trace.live.size() == baseline, "Typed constructor/destructor variants must reclaim their handles and storage"); + } + Value value{}; + _ZN3sce4Json5ValueC2Ev(&value); Require(value.type == 0, "Default value constructor variant must construct null"); _ZN3sce4Json5ValueD2Ev(&value); + _ZN3sce4Json5ValueC2Eb(&value, true); Require(value.type == 1 && (value.payload & 255) == 1, "Boolean constructor variant must preserve its payload"); _ZN3sce4Json5ValueD2Ev(&value); + _ZN3sce4Json5ValueC2El(&value, -77); Require(value.type == 2 && value.payload == std::bit_cast(std::int64_t{-77}), "Signed constructor variant must preserve all payload bits"); _ZN3sce4Json5ValueD2Ev(&value); + _ZN3sce4Json5ValueC2Em(&value, UINT64_MAX); Require(value.type == 3 && value.payload == UINT64_MAX, "Unsigned constructor variant must preserve all payload bits"); _ZN3sce4Json5ValueD2Ev(&value); + _ZN3sce4Json5ValueC2Ed(&value, 1.25); Require(value.type == 4 && value.payload == std::bit_cast(1.25), "Real constructor variant must preserve its payload"); _ZN3sce4Json5ValueD2Ev(&value); + String empty{}, text{}, copiedText{}; + _ZN3sce4Json6StringC2Ev(&empty); Require(empty.text && !*_ZNK3sce4Json6String5c_strEv(&empty), "Default string constructor variant must own empty storage"); + _ZN3sce4Json6StringC2EPKc(&text, "abc"); _ZN3sce4Json6StringC2ERKS1_(&copiedText, &text); + Require(text.text != copiedText.text && std::strcmp(_ZNK3sce4Json6String5c_strEv(&copiedText), "abc") == 0, "String copy constructor variant must own independent storage"); + _ZN3sce4Json5ValueC2EPKc(&value, "abc"); CheckText(value, "abc"); + Value copiedValue{}; _ZN3sce4Json5ValueC2ERKS1_(&copiedValue, &value); CheckText(copiedValue, "abc"); + Require(copiedValue.payload != value.payload, "Value copy constructor variant must own an independent handle"); + _ZN3sce4Json5ValueD2Ev(&copiedValue); _ZN3sce4Json5ValueD2Ev(&value); + _ZN3sce4Json5ValueC2ERKNS0_6StringE(&value, &text); CheckText(value, "abc"); + Require(StringOf(value)->text != text.text, "String-to-value constructor variant must copy storage"); _ZN3sce4Json5ValueD2Ev(&value); + Array array{}, copiedArray{}; _ZN3sce4Json5ArrayC2Ev(&array); + _ZN3sce4Json5ValueC2El(&value, 42); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &value); + _ZN3sce4Json5ArrayC2ERKS1_(&copiedArray, &array); + Require(array.items != copiedArray.items && _ZNK3sce4Json5Array4sizeEv(&copiedArray) == 1 && _ZNK3sce4Json5Array5frontEv(&copiedArray)->payload == 42, "Array copy constructor variant must copy its entries into independent storage"); + Value wrapped{}; _ZN3sce4Json5ValueC2ERKNS0_5ArrayE(&wrapped, &array); + const auto* child = _ZNK3sce4Json5Array5frontEv(reinterpret_cast(wrapped.payload)); + Require(wrapped.type == 6 && child->parent == &wrapped && child->payload == 42, "Array-to-value constructor variant must bind copied children to their owner"); _ZN3sce4Json5ValueD2Ev(&wrapped); + Object object{}, copiedObject{}; _ZN3sce4Json6ObjectC2Ev(&object); + _ZN3sce4Json5Value3setEl(_ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &text), 42); + _ZN3sce4Json6ObjectC2ERKS1_(&copiedObject, &object); + Require(object.items != copiedObject.items && _ZNK3sce4Json6Object4sizeEv(&copiedObject) == 1, "Object copy constructor variant must own independent tree storage"); + _ZN3sce4Json5ValueC2ERKNS0_6ObjectE(&wrapped, &object); + child = _ZNK3sce4Json5ValueixEPKc(&wrapped, "abc"); + Require(wrapped.type == 7 && child->parent == &wrapped && child->payload == 42, "Object-to-value constructor variant must bind copied members to their owner"); _ZN3sce4Json5ValueD2Ev(&wrapped); + Pair pair{}; _ZN3sce4Json6Object4PairC2Ev(&pair); + Require(pair.key.text && pair.value.type == 0, "Default pair constructor variant must construct its key and null value"); _ZN3sce4Json6Object4PairD2Ev(&pair); + _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(&pair, &text, &value); + Require(pair.key.text != text.text && pair.value.type == 2 && pair.value.payload == 42, "Pair constructor variant must copy its key and value"); _ZN3sce4Json6Object4PairD2Ev(&pair); + ArrayIterator iterator{}, copy{}; + _ZN3sce4Json5Array8iteratorC2Ev(&iterator); _ZN3sce4Json5Array8iteratorC2ERKS2_(©, &iterator); + Require(iterator.state && copy.state && iterator.state != copy.state && !*reinterpret_cast(copy.state), "Default array iterator variants must own independent unbound states"); + _ZN3sce4Json5Array8iteratorD2Ev(©); _ZN3sce4Json5Array8iteratorD2Ev(&iterator); + _ZNK3sce4Json5Array5beginEv(&iterator, &array); _ZN3sce4Json5Array8iteratorC2ERKS2_(©, &iterator); + Require(copy.state != iterator.state && _ZNK3sce4Json5Array8iteratordeEv(©) == _ZNK3sce4Json5Array5frontEv(&array), "Array iterator copy variant must preserve the actual element identity"); + _ZN3sce4Json5Array8iteratorD2Ev(©); _ZN3sce4Json5Array8iteratorD2Ev(&iterator); + ObjectIterator objectIterator{}, objectCopy{}; + _ZN3sce4Json6Object8iteratorC2Ev(&objectIterator); _ZN3sce4Json6Object8iteratorC2ERKS2_(&objectCopy, &objectIterator); + Require(objectIterator.state && objectCopy.state && objectIterator.state != objectCopy.state && !*reinterpret_cast(objectCopy.state), "Default object objectIterator variants must own independent unbound states"); + _ZN3sce4Json6Object8iteratorD2Ev(&objectCopy); _ZN3sce4Json6Object8iteratorD2Ev(&objectIterator); + _ZNK3sce4Json6Object5beginEv(&objectIterator, &object); _ZN3sce4Json6Object8iteratorC2ERKS2_(&objectCopy, &objectIterator); + Require(objectCopy.state != objectIterator.state && _ZNK3sce4Json6Object8iteratordeEv(&objectCopy) == _ZNK3sce4Json6Object8iteratordeEv(&objectIterator), "Object objectIterator objectCopy variant must preserve the actual member identity"); + _ZN3sce4Json6Object8iteratorD2Ev(&objectCopy); _ZN3sce4Json6Object8iteratorD2Ev(&objectIterator); + _ZN3sce4Json6ObjectD2Ev(&copiedObject); _ZN3sce4Json6ObjectD2Ev(&object); + _ZN3sce4Json5ArrayD2Ev(&copiedArray); _ZN3sce4Json5ArrayD2Ev(&array); _ZN3sce4Json5ValueD2Ev(&value); + _ZN3sce4Json6StringD2Ev(&copiedText); _ZN3sce4Json6StringD2Ev(&text); _ZN3sce4Json6StringD2Ev(&empty); + Require(trace.live.size() == baseline, "All constructor/destructor variants must reclaim their owned resources"); + String absent{}; + const auto requests = trace.requests; + _ZN3sce4Json5ValueC2ERKNS0_6StringE(&value, &absent); + Require(value.type == 0 && trace.requests == requests, "Missing string storage must leave a null value without allocating"); _ZN3sce4Json5ValueD2Ev(&value); + _ZN3sce4Json5ValueC2EPKc(&value, nullptr); CheckText(value, ""); _ZN3sce4Json5ValueD2Ev(&value); + struct Failure { bool once; int budget; int type; unsigned notifications; std::size_t requests; const char* text; }; + const Failure failures[]{ + {false, 0, 0, 1, 1, ""}, {false, 1, 0, 2, 3, ""}, + {false, 2, 0, 1, 3, ""}, {false, 3, 0, 1, 4, ""}, + {false, 4, 5, 1, 5, ""}, {false, 5, 5, 0, 5, "abc"}, + {true, 0, 0, 1, 1, ""}, {true, 1, 5, 1, 4, ""}, + {true, 2, 0, 1, 3, ""}, {true, 3, 0, 1, 4, ""}, + {true, 4, 5, 1, 5, ""}, {true, 5, 5, 0, 5, "abc"}, + }; + for (const auto& failure : failures) { + const auto notifications = trace.notifications; + const auto before = trace.requests; + trace.budget = failure.budget; trace.once = failure.once; + _ZN3sce4Json5ValueC2EPKc(&value, "abc"); trace.budget = -1; trace.once = false; + Require(value.type == failure.type && trace.notifications - notifications == failure.notifications && trace.requests - before == failure.requests, "Text constructor variant must preserve temporary string allocation and failure admission"); + if (value.type == 5) CheckText(value, failure.text); + _ZN3sce4Json5ValueD2Ev(&value); + Require(trace.live.size() == baseline, "Failed constructor variants must reclaim their admitted prefix"); + } + _ZN3sce4Json11InitializerD2Ev(initializer.data()); + Require(initializer[0] == 0 && trace.live.empty(), "Initializer destructor variant must terminate its resources"); + for (std::size_t i = 1; i < initializer.size(); ++i) Require(initializer[i] == 0xa5, "Initializer destruction must preserve every adjacent byte"); + return 0; +} diff --git a/core/libs/tests/GuestJsonAllocationEntry.cpp b/core/libs/tests/GuestJsonAllocationEntry.cpp new file mode 100644 index 0000000000..637a51e898 --- /dev/null +++ b/core/libs/tests/GuestJsonAllocationEntry.cpp @@ -0,0 +1,96 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t fileBuffer; }; +using InfoCallback = void (APS5_VABI*)(int, std::int32_t, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct EntryTrace { + std::map live; + std::vector requests; + std::vector frees; + void* expectedAllocator = nullptr; + bool reject = false; + unsigned notices = 0; + std::size_t failedSize = 0; + int notifiedState = 0; + int firstRequestState = 0; + int laterRequestState = 0; +}; +static void APS5_VABI SdkInfo(int, ValueType, void*) { Require(false, "The reference allocator-info setter must not install or invoke the supplied callback"); } +static int InfoState() { return _ZN3sce4Json11Initializer24setAllocatorInfoCallBackEPFviNS0_9ValueTypeEPvES3_(nullptr, SdkInfo, nullptr); } +static void* APS5_VABI EntryAllocate(void* self, std::size_t size, void* context) { + auto& trace = *static_cast(context); + Require(self == trace.expectedAllocator, "Allocation dispatch must preserve the allocator receiver and user context"); + if (trace.requests.empty()) trace.firstRequestState = InfoState(); + else trace.laterRequestState = InfoState(); + trace.requests.push_back(size); + if (trace.reject) return nullptr; + void* pointer = std::malloc(size ? size : 1); + Require(pointer != nullptr && trace.live.emplace(pointer, size).second, "Tracking allocator must provide independently owned host storage"); + return pointer; +} +static void APS5_VABI EntryFree(void* self, void* memory, void* context) { + auto& trace = *static_cast(context); + Require(self == trace.expectedAllocator, "Free dispatch must preserve the allocator receiver and user context"); + trace.frees.push_back(memory); + if (memory) { Require(trace.live.erase(memory) == 1, "Every allocation must be released exactly once through its allocator"); std::free(memory); } +} +static void APS5_VABI EntryNotify(void* self, int error, std::size_t size, void* context) { + auto& trace = *static_cast(context); + Require(self == trace.expectedAllocator && static_cast(error) == 0x80848102, "Allocation failure must notify the owning allocator with the Json2 error"); + ++trace.notices; trace.failedSize = size; trace.notifiedState = InfoState(); +} +int main() { + std::uint64_t allocatorBytes[]{UINT64_C(0x1122334455667788), UINT64_C(0xaabbccddeeff0099)}; + const auto first = allocatorBytes[0], second = allocatorBytes[1]; + _ZN3sce4Json12MemAllocatorD2Ev(allocatorBytes); + Require(allocatorBytes[0] == first && allocatorBytes[1] == second, "Base allocator destruction must not rewrite the vtable or adjacent bytes"); + _ZN3sce4Json12MemAllocatorD1Ev(allocatorBytes); + Require(allocatorBytes[0] == first && allocatorBytes[1] == second, "Both non-deleting allocator destructors must leave the complete object unchanged"); + constexpr int notInitialized = static_cast(0x80848110); + Require(InfoState() == notInitialized && _ZN3sce4Json6MallocEm(17) == nullptr, "Allocation entry points must expose missing context before initialization"); + _ZN3sce4Json4FreeEPv(nullptr); + void* table[]{nullptr, nullptr, reinterpret_cast(&EntryAllocate), reinterpret_cast(&EntryFree), reinterpret_cast(&EntryNotify)}; + void** allocator = table; + EntryTrace trace; trace.expectedAllocator = &allocator; + Parameter parameter{&allocator, &trace, 8192}; + std::uint8_t initializer = 0xa5; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&initializer, ¶meter) == 0, "Entry-point tests require successful production initialization"); + Require(trace.firstRequestState == notInitialized && trace.laterRequestState == 0 && InfoState() == 0, "Allocator-info admission must follow context publication, including during resource initialization"); + std::uint8_t unrelated = 0; + Require(_ZN3sce4Json11Initializer24setAllocatorInfoCallBackEPFviNS0_9ValueTypeEPvES3_(&unrelated, nullptr, &trace) == 0, "Allocator-info admission must ignore the receiver, callback and context arguments"); + for (std::size_t size : {std::size_t{0}, std::size_t{1}, std::size_t{257}}) { + const auto requests = trace.requests.size(), frees = trace.frees.size(), live = trace.live.size(); + void* memory = _ZN3sce4Json6MallocEm(size); + Require(memory && trace.requests.size() == requests + 1 && trace.requests.back() == size && trace.live.at(memory) == size, "Malloc must forward exact size and return the allocator's unmodified pointer"); + _ZN3sce4Json4FreeEPv(memory); + Require(trace.frees.size() == frees + 1 && trace.frees.back() == memory && trace.live.size() == live, "Free must forward the exact allocation without retaining ownership"); + } + const auto frees = trace.frees.size(); + _ZN3sce4Json4FreeEPv(nullptr); + Require(trace.frees.size() == frees + 1 && trace.frees.back() == nullptr, "An active allocator must receive even a null Free argument"); + trace.reject = true; + Require(_ZN3sce4Json6MallocEm(4097) == nullptr && trace.notices == 1 && trace.failedSize == 4097 && trace.notifiedState == 0, "Failed Malloc must notify once with the original size while keeping the allocation context active"); + trace.reject = false; + void* retry = _ZN3sce4Json6MallocEm(9); + Require(retry != nullptr && trace.notices == 1, "A failed direct allocation must permit a later successful retry"); + _ZN3sce4Json4FreeEPv(retry); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Termination must release all required resources through the same allocator"); + const auto finalRequests = trace.requests.size(), finalFrees = trace.frees.size(); + Require(InfoState() == notInitialized && _ZN3sce4Json6MallocEm(13) == nullptr, "Termination must withdraw the allocation context"); + _ZN3sce4Json4FreeEPv(nullptr); + Require(trace.requests.size() == finalRequests && trace.frees.size() == finalFrees && trace.notices == 1, "Missing-context calls must not dispatch stale allocator callbacks"); + return 0; +} diff --git a/core/libs/tests/GuestJsonAllocator.cpp b/core/libs/tests/GuestJsonAllocator.cpp new file mode 100644 index 0000000000..b1240022ba --- /dev/null +++ b/core/libs/tests/GuestJsonAllocator.cpp @@ -0,0 +1,283 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct AllocationTrace { + int budget = -1; + bool failOnce = false; + Parameter* changeInput = nullptr; + void* replacementContext = nullptr; + std::size_t clearStringAt = 0; + String* displacedString = nullptr; + std::map live; + std::vector requests; + std::vector notifications; + std::vector publishedDefaults; +}; +static void* APS5_VABI AllocateTracked(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + trace.requests.push_back(bytes); + trace.publishedDefaults.push_back(unsigned(_ZN3sce4Json5Value12s_nullstringE != nullptr) | + (unsigned(_ZN3sce4Json5Value11s_nullarrayE != nullptr) << 1) | + (unsigned(_ZN3sce4Json5Value12s_nullobjectE != nullptr) << 2)); + if (trace.requests.size() == trace.clearStringAt) { + trace.displacedString = _ZN3sce4Json5Value12s_nullstringE; + _ZN3sce4Json5Value12s_nullstringE = nullptr; + } + if (trace.changeInput && trace.requests.size() == 1) { + trace.changeInput->context = trace.replacementContext; + trace.changeInput->bufferSize = 32768; + } + if (trace.budget == 0) { + if (trace.failOnce) trace.budget = -1; + return nullptr; + } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes ? bytes : 1); + Require(memory != nullptr, "The tracking allocator must have host memory"); + std::memset(memory, 0xa5, bytes); + trace.live.emplace(memory, bytes); + return memory; +} +static void APS5_VABI FreeTracked(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + Require(trace.live.erase(memory) == 1, "Free must use the allocator and context that own the resource"); + std::free(memory); +} +static void APS5_VABI NotifyTracked(void*, int error, std::size_t bytes, void* context) { + Require(static_cast(error) == 0x80848102, "Allocation notification must carry the Json2 out-of-memory error"); + auto& trace = *static_cast(context); + Require(!trace.requests.empty() && trace.requests.back() == bytes, "Notification size must match the failed allocation request"); + trace.notifications.push_back(bytes); +} +static void* const Vtable[]{nullptr, nullptr, reinterpret_cast(AllocateTracked), reinterpret_cast(FreeTracked), reinterpret_cast(NotifyTracked)}; +struct TrackingAllocator { void* const* vtable = Vtable; }; +static bool Owned(const AllocationTrace& trace, const void* memory, std::size_t bytes) { + const auto found = trace.live.find(const_cast(memory)); + return found != trace.live.end() && found->second == bytes; +} +static void CheckResources(AllocationTrace& trace) { + Require(trace.live.size() == 12, "SDK 6 initialization must own its actual twelve allocated resources"); + const std::vector requests{24, 8, 24, 8, 32, 48, 8, 32, 104, 32, 8, 8}; + Require(trace.requests.size() >= requests.size() && std::equal(requests.begin(), requests.end(), trace.requests.end() - requests.size()), + "Initialization must allocate its actual context, defaults, fallback and synchronization handles in reference order"); + const std::vector publication{0, 0, 0, 1, 1, 1, 3, 3, 3, 7, 7, 7}; + Require(trace.publishedDefaults.size() >= publication.size() && + std::equal(publication.begin(), publication.end(), trace.publishedDefaults.end() - publication.size()), + "Allocator callbacks must observe each default pointer only after its construction completes"); + bool contextFound = false; + for (const auto& [memory, bytes] : trace.live) { + if (bytes != 24) continue; + std::uintptr_t words[3]; std::memcpy(words, memory, sizeof(words)); + if (words[1] == reinterpret_cast(&trace) && words[2] == 16384) contextFound = true; + } + Require(contextFound, "Initialization must retain the supplied context and buffer setting in its owned context record"); + Value null; + _ZN3sce4Json5ValueC1Ev(&null); + const auto* string = _ZNK3sce4Json5Value9getStringEv(&null); + const auto* array = _ZNK3sce4Json5Value8getArrayEv(&null); + const auto* object = _ZNK3sce4Json5Value9getObjectEv(&null); + Require(string == _ZN3sce4Json5Value12s_nullstringE && array == _ZN3sce4Json5Value11s_nullarrayE && + object == _ZN3sce4Json5Value12s_nullobjectE, "Getters must return the exported default pointers"); + auto* savedString = _ZN3sce4Json5Value12s_nullstringE; + auto* savedArray = _ZN3sce4Json5Value11s_nullarrayE; + auto* savedObject = _ZN3sce4Json5Value12s_nullobjectE; + _ZN3sce4Json5Value12s_nullstringE = nullptr; + _ZN3sce4Json5Value11s_nullarrayE = nullptr; + _ZN3sce4Json5Value12s_nullobjectE = nullptr; + Require(!_ZNK3sce4Json5Value9getStringEv(&null) && !_ZNK3sce4Json5Value8getArrayEv(&null) && + !_ZNK3sce4Json5Value9getObjectEv(&null), "Getters must observe writes to the exported pointers"); + _ZN3sce4Json5Value12s_nullstringE = savedString; + _ZN3sce4Json5Value11s_nullarrayE = savedArray; + _ZN3sce4Json5Value12s_nullobjectE = savedObject; + Require(Owned(trace, string, 8) && Owned(trace, string->text, 24), "The default string must use the configured allocator"); + Require(Owned(trace, array, 8) && Owned(trace, array->items, 32) && Owned(trace, *reinterpret_cast(array->items), 48), + "The default array must own its allocator-backed header and sentinel"); + Require(Owned(trace, object, 8) && Owned(trace, object->items, 32) && Owned(trace, *reinterpret_cast(object->items), 104), + "The default object must own its allocator-backed header and tree sentinel"); + Require(Owned(trace, _ZNK3sce4Json5ValueixEPKc(&null, "missing"), 32), "Missing access must return the allocated module fallback"); + _ZN3sce4Json5ValueD1Ev(&null); + String text{}; + _ZN3sce4Json6StringC1EPKc(&text, "abc"); + Require(Owned(trace, text.text, 24) && Owned(trace, *reinterpret_cast(text.text), 4) && + _ZNK3sce4Json6String4sizeEv(&text) == 3 && _ZNK3sce4Json6String8capacityEv(&text) == 4, + "String construction must allocate the native header and exact initial character capacity"); + _ZN3sce4Json6String7reserveEm(&text, 17); + Require(Owned(trace, *reinterpret_cast(text.text), 17) && _ZNK3sce4Json6String8capacityEv(&text) == 17, + "Reserve must apply the SDK-dependent buffer growth rule"); + _ZN3sce4Json6String6resizeEm(&text, 18); + const char* data = _ZNK3sce4Json6String5c_strEv(&text); + Require(Owned(trace, data, 25) && _ZNK3sce4Json6String4sizeEv(&text) == 18 && std::memcmp(data, "abc", 3) == 0, + "Resize must retain the prefix and allocate the configured character capacity"); + for (unsigned index = 3; index <= 18; ++index) + Require(data[index] == 0, "Growing a string must zero the added bytes and terminator"); + _ZN3sce4Json6String5clearEv(&text); + Require(Owned(trace, text.text, 24) && _ZNK3sce4Json6String4sizeEv(&text) == 0 && _ZNK3sce4Json6String8capacityEv(&text) == 0, + "Clear must release characters while retaining the string header"); + _ZN3sce4Json6StringD1Ev(&text); + Require(trace.live.size() == 12, "Temporary string operations must leave only initialization resources alive"); +} +static void CheckSingleFailures() { + for (int point = 0; point < 12; ++point) { + AllocationTrace trace; + trace.budget = point; + trace.failOnce = true; + TrackingAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + const int result = _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)); + const bool optional = point == 2 || point == 4 || point == 5 || point == 7 || point == 8; + Require(result == (optional ? 0 : static_cast(0x80848102)) && initializer == (optional ? 1 : 0), + "Single inner-default allocation failures must retain the observed admission result"); + Require(trace.notifications.size() == (point == 0 ? 0 : 1), "Each failed request must produce its observed notification count"); + if (optional) { + Value null; + _ZN3sce4Json5ValueC1Ev(&null); + if (point == 2) { + const auto* string = _ZNK3sce4Json5Value9getStringEv(&null); + Require(Owned(trace, string, 8) && !string->text && _ZNK3sce4Json6String4sizeEv(string) == 0 && + _ZNK3sce4Json6String8capacityEv(string) == 0 && _ZNK3sce4Json6String5c_strEv(string)[0] == 0, + "A failed default string header must retain its usable empty outer handle"); + } else if (point == 4 || point == 5) { + const auto* array = _ZNK3sce4Json5Value8getArrayEv(&null); + Require(Owned(trace, array, 8) && _ZNK3sce4Json5Array4sizeEv(array) == 0, + "A failed default array resource must retain an empty outer handle"); + Require(point == 4 ? !array->items : Owned(trace, array->items, 32), + "Array admission must preserve the observed partial header ownership"); + } else { + const auto* object = _ZNK3sce4Json5Value9getObjectEv(&null); + Require(Owned(trace, object, 8) && !object->items && _ZNK3sce4Json6Object4sizeEv(object) == 0, + "A failed default object resource must retain its empty outer handle and reclaim partial tree storage"); + } + _ZN3sce4Json5ValueD1Ev(&null); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate partial-default initialization"); + } + Require(trace.live.empty(), "Both admitted partial defaults and refused initialization must release all allocated resources"); + trace.budget = -1; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, + "A subsequent full initialization must work after every single failure schedule"); + CheckResources(trace); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Release resources after retry"); + } +} + +static void CheckContextSnapshot() { + AllocationTrace trace, replacement; + TrackingAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + trace.changeInput = ¶meter; + trace.replacementContext = &replacement; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize with a reconfigured input during context allocation"); + bool found = false; + for (const auto& [memory, bytes] : trace.live) { + if (bytes != 24) continue; + std::uintptr_t words[3]; std::memcpy(words, memory, sizeof(words)); + if (words[0] == reinterpret_cast(&allocator) && words[1] == reinterpret_cast(&trace) && words[2] == 32768) found = true; + } + Require(found && trace.live.size() == 12 && replacement.requests.empty(), "Context admission must cache allocator and user data before allocation and read the buffer setting afterward"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty() && replacement.live.empty(), "Termination must release the admitted context snapshot"); +} +static void CheckReplacedDefaults() { + AllocationTrace trace; + TrackingAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, + "Initialize before replacing exported defaults"); + auto* oldString = _ZN3sce4Json5Value12s_nullstringE; + auto* oldArray = _ZN3sce4Json5Value11s_nullarrayE; + auto* oldObject = _ZN3sce4Json5Value12s_nullobjectE; + auto* replacementString = static_cast(AllocateTracked(&allocator, sizeof(String), &trace)); + auto* replacementArray = static_cast(AllocateTracked(&allocator, sizeof(Array), &trace)); + auto* replacementObject = static_cast(AllocateTracked(&allocator, sizeof(Object), &trace)); + _ZN3sce4Json6StringC1Ev(replacementString); + _ZN3sce4Json5ArrayC1Ev(replacementArray); + _ZN3sce4Json6ObjectC1Ev(replacementObject); + _ZN3sce4Json6StringD1Ev(oldString); + _ZN3sce4Json5ArrayD1Ev(oldArray); + _ZN3sce4Json6ObjectD1Ev(oldObject); + FreeTracked(&allocator, oldString, &trace); + FreeTracked(&allocator, oldArray, &trace); + FreeTracked(&allocator, oldObject, &trace); + _ZN3sce4Json5Value12s_nullstringE = replacementString; + _ZN3sce4Json5Value11s_nullarrayE = replacementArray; + _ZN3sce4Json5Value12s_nullobjectE = replacementObject; + Value null; + _ZN3sce4Json5ValueC1Ev(&null); + Require(_ZNK3sce4Json5Value9getStringEv(&null) == replacementString && + _ZNK3sce4Json5Value8getArrayEv(&null) == replacementArray && + _ZNK3sce4Json5Value9getObjectEv(&null) == replacementObject, + "Getters must observe replacement pointers"); + _ZN3sce4Json5ValueD1Ev(&null); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), + "Termination must destroy and release the replacement pointers"); +} +static void CheckAdmissionReadsPublishedPointer() { + AllocationTrace trace; + trace.clearStringAt = 10; + TrackingAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(static_cast(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter))) == 0x80848102 && !initializer, + "Admission must read the exported pointer after allocator callbacks"); + Require(trace.displacedString && trace.live.size() == 2 && Owned(trace, trace.displacedString, sizeof(String)) && + Owned(trace, trace.displacedString->text, 24), "The displaced default must remain owned by its allocator"); + FreeTracked(&allocator, trace.displacedString->text, &trace); + FreeTracked(&allocator, trace.displacedString, &trace); + Require(trace.live.empty(), "Fixture cleanup must release the displaced allocation"); +} +int main() { + for (int budget = 0; budget <= 12; ++budget) { + AllocationTrace trace; + trace.budget = budget; + TrackingAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + const int result = _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)); + if (budget < 12) { + Require(static_cast(result) == 0x80848102 && initializer == 0 && trace.live.empty(), + "Every initialization allocation failure must report OOM, roll back ownership and leave retry legal"); + Require(!_ZN3sce4Json5Value12s_nullstringE && !_ZN3sce4Json5Value11s_nullarrayE && !_ZN3sce4Json5Value12s_nullobjectE, + "Initialization rollback must clear the exported default pointers"); + Require((budget == 0) == trace.notifications.empty(), "Only the first context-allocation failure omits notification"); + trace.budget = -1; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, + "Initialization must succeed after failure rollback without reconstructing the initializer"); + } else { + Require(result == 0 && initializer == 1, "Initialization must succeed after its final resource allocation"); + trace.budget = -1; + } + CheckResources(trace); + AllocationTrace unrelated; + parameter.context = &unrelated; + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && initializer == 0 && trace.live.empty() && unrelated.live.empty(), + "Termination must release the snapshotted allocation context and every resource"); + Require(!_ZN3sce4Json5Value12s_nullstringE && !_ZN3sce4Json5Value11s_nullarrayE && !_ZN3sce4Json5Value12s_nullobjectE, + "Termination must clear the exported default pointers"); + } + CheckSingleFailures(); + CheckContextSnapshot(); + CheckReplacedDefaults(); + CheckAdmissionReadsPublishedPointer(); + return 0; +} diff --git a/core/libs/tests/GuestJsonAllocatorSettings.cpp b/core/libs/tests/GuestJsonAllocatorSettings.cpp new file mode 100644 index 0000000000..5e6e0d130c --- /dev/null +++ b/core/libs/tests/GuestJsonAllocatorSettings.cpp @@ -0,0 +1,127 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +using Allocate = void* (APS5_VABI*)(std::size_t, void*); +using Free = void (APS5_VABI*)(void*, void*); +using Notify = void (APS5_VABI*)(int, std::size_t, void*); +struct Callbacks { Allocate allocate; Free free; Notify notify; }; +struct Parameter { + void* allocator; + void* context; + std::size_t bufferSize; + std::int32_t mode; + std::uint32_t reserved[3]; +}; +static_assert(sizeof(Parameter) == 40); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct Context { std::size_t size = 0; int error = 0; void* memory = nullptr; int calls = 0; }; +static void* APS5_VABI AllocateMemory(std::size_t size, void* context) { + auto& trace = *static_cast(context); + trace.size = size; + ++trace.calls; + trace.memory = std::malloc(size); + return trace.memory; +} +static void APS5_VABI FreeMemory(void* memory, void* context) { + auto& trace = *static_cast(context); + Require(memory == trace.memory, "The adapter must forward the allocation to the matching context"); + ++trace.calls; + std::free(memory); + trace.memory = nullptr; +} +static void APS5_VABI NotifyError(int error, std::size_t size, void* context) { + auto& trace = *static_cast(context); + trace.error = error; + trace.size = size; + ++trace.calls; +} +static void* APS5_VABI AllocateReplacement(std::size_t size, void* context) { + return AllocateMemory(size + 1, context); +} +static void Exercise(Parameter& parameter, Context& trace, bool replacement) { + auto** table = *reinterpret_cast(parameter.allocator); + const auto allocate = reinterpret_cast(table[2]); + const auto free = reinterpret_cast(table[3]); + const auto notify = reinterpret_cast(table[4]); + const int before = trace.calls; + void* memory = allocate(parameter.allocator, 127, parameter.context); + Require(memory && trace.size == (replacement ? 128 : 127), "The RTTI adapter must dispatch allocation with the supplied size and context"); + free(parameter.allocator, memory, parameter.context); + notify(parameter.allocator, static_cast(0x80848102), 8193, parameter.context); + Require(trace.calls == before + 3 && trace.error == static_cast(0x80848102) && trace.size == 8193, + "The RTTI adapter must forward free and notification exactly once"); +} +int main() { + Context first, second; + Callbacks callbacks{AllocateMemory, FreeMemory, NotifyError}; + Parameter parameter{nullptr, nullptr, 8192, 1, {11, 12, 13}}; + _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(reinterpret_cast(¶meter), &callbacks, &first); + Require(parameter.allocator && parameter.allocator != &callbacks && parameter.context == &first && + parameter.bufferSize == 8192 && parameter.mode == 1 && parameter.reserved[2] == 13, + "The setter must install an adapter and preserve unrelated settings"); + callbacks = {}; + Exercise(parameter, first, false); + const Parameter saved = parameter; + for (Callbacks invalid : {Callbacks{}, Callbacks{AllocateMemory, nullptr, NotifyError}, + Callbacks{nullptr, FreeMemory, NotifyError}}) { + _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(reinterpret_cast(¶meter), &invalid, &second); + Require(std::memcmp(¶meter, &saved, sizeof(saved)) == 0, "Invalid callbacks must preserve the parameter block"); + alignas(8) std::array legacy; + legacy.fill(std::byte{0xa5}); + _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(reinterpret_cast(legacy.data()), &invalid, &second, 4096); + for (std::size_t i = 0; i < legacy.size(); ++i) + Require(legacy[i] == (i < 24 ? std::byte{} : std::byte{0xa5}), "Rejected legacy construction must clear only its 24-byte base"); + } + _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(reinterpret_cast(¶meter), nullptr, &second); + Require(std::memcmp(¶meter, &saved, sizeof(saved)) == 0, "A null RTTI setter argument must preserve settings"); + Callbacks replacement{AllocateReplacement, FreeMemory, NotifyError}; + Parameter newer{}; + _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(reinterpret_cast(&newer), &replacement, &second); + Require(newer.allocator == parameter.allocator, "RTTI parameter blocks must share the module adapter"); + Exercise(parameter, first, true); + Exercise(newer, second, true); + JsonTest::Allocator allocator; + Parameter regular{&allocator, nullptr, 0, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(®ular)) == 0, "Initialize the module"); + callbacks = {AllocateMemory, FreeMemory, NotifyError}; + _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(reinterpret_cast(¶meter), &callbacks, &second); + Require(std::memcmp(¶meter, &saved, sizeof(saved)) == 0, "An active module must refuse RTTI reconfiguration"); + alignas(8) std::array refused; + refused.fill(std::byte{0xa5}); + _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(reinterpret_cast(refused.data()), reinterpret_cast(std::uintptr_t{1}), &second, 4096); + for (std::size_t i = 0; i < refused.size(); ++i) + Require(refused[i] == (i < 24 ? std::byte{} : std::byte{0xa5}), "An active module must reject legacy configuration before reading callbacks"); + Exercise(parameter, first, true); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate the module"); + alignas(8) std::array legacy; + legacy.fill(std::byte{0xa5}); + _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(reinterpret_cast(legacy.data()), &callbacks, &first, 4096); + Parameter stored{}; + std::memcpy(&stored, legacy.data(), 24); + Require(stored.allocator == parameter.allocator && stored.context == &first && stored.bufferSize == 4096, + "Legacy construction must install the adapter, context and buffer size"); + for (std::size_t i = 24; i < legacy.size(); ++i) + Require(legacy[i] == std::byte{0xa5}, "Legacy construction must preserve trailing memory"); + Exercise(stored, first, false); + callbacks.notify = nullptr; + _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(reinterpret_cast(¶meter), &callbacks, &first); + auto** table = *reinterpret_cast(parameter.allocator); + const auto notify = reinterpret_cast(table[4]); + const int before = first.calls; + notify(parameter.allocator, static_cast(0x80848102), 512, parameter.context); + Require(first.calls == before, "An optional null notifier must not dispatch a callback"); + return 0; +} diff --git a/core/libs/tests/GuestJsonArrayAllocator.cpp b/core/libs/tests/GuestJsonArrayAllocator.cpp new file mode 100644 index 0000000000..c7c3408bfb --- /dev/null +++ b/core/libs/tests/GuestJsonArrayAllocator.cpp @@ -0,0 +1,206 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ArrayTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::set headers; +}; +static void* APS5_VABI AllocateArray(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI FreeArray(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + if (trace.headers.erase(memory)) { + const auto* words = reinterpret_cast(memory); + Require(words[0] == 0xffffffff && words[1] == UINT64_MAX, "Array teardown must expose the native header poison before allocator release"); + } + Require(trace.live.erase(memory) == 1, "Array must release through its owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyArray(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Array notification must identify the failed allocation"); ++trace.notifications; +} +static void* const ArrayVtable[]{nullptr, nullptr, reinterpret_cast(AllocateArray), reinterpret_cast(FreeArray), reinterpret_cast(NotifyArray)}; +struct ArrayAllocator { void* const* vtable = ArrayVtable; }; +static void CheckLinks(const Array& array, std::size_t count, std::initializer_list values = {}) { + Require(values.size() == 0 || values.size() == count, "Expected array values must cover every live node"); + const auto* header = reinterpret_cast(array.items); + Require(header && header[1] == count, "Array header must expose its native element count"); + const auto* head = reinterpret_cast(header[0]); + const auto* previous = head; + std::size_t seen = 0; + for (auto* node = reinterpret_cast(head[1]); node != head; node = reinterpret_cast(node[1])) { + Require(node[0] == reinterpret_cast(previous), "Every array link must retain its predecessor"); + const auto* value = reinterpret_cast(node + 2); + Require(++seen <= count, "Array links must close at the sentinel"); + const auto expected = values.size() ? *(values.begin() + seen - 1) : 10 * seen; + Require(!value->parent && !value->auxiliary && value->type == 2 && value->payload == expected, "Array nodes must expose their inline values and ownership"); + previous = node; + } + Require(seen == count && head[0] == reinterpret_cast(previous), "Array sentinel must close both list directions"); +} +static void CheckAssignments(ArrayTrace& trace) { + const auto baseline = trace.live.size(); + for (bool once : {false, true}) for (int budget = 0; budget <= 5; ++budget) { + Array source{}, destination{}; + _ZN3sce4Json5ArrayC1Ev(&source); _ZN3sce4Json5ArrayC1Ev(&destination); + Value value{}; + for (int number : {10, 20, 30}) { _ZN3sce4Json5ValueC1El(&value, number); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&source, &value); _ZN3sce4Json5ValueD1Ev(&value); } + _ZN3sce4Json5ValueC1El(&value, 99); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&destination, &value); _ZN3sce4Json5ValueD1Ev(&value); + Value owner{}; + reinterpret_cast(destination.items)[3] = reinterpret_cast(&owner); + auto* previous = destination.items; + trace.headers.insert(previous); + const auto notifications = trace.notifications; + trace.budget = budget; trace.once = once; + Require(_ZN3sce4Json5ArrayaSERKS1_(&destination, &source) == &destination, "Array assignment must return its destination"); + trace.budget = -1; trace.once = false; + auto* header = reinterpret_cast(destination.items); + Require((destination.items == previous) == (budget < 2) && header[3] == reinterpret_cast(&owner), "Array assignment must preserve the original on initial failure and carry its owner into a replacement"); + const auto expected = budget < 2 ? 1 : budget == 5 ? 3 : once ? 2 : budget - 2; + Require(header[1] == static_cast(expected), "Array assignment must retain the observed partial list"); + Require(trace.notifications - notifications == (budget < 2 ? 1u : budget == 5 ? 0u : once ? 1u : static_cast(5 - budget)), "Array assignment must report failed node admission while continuing through the source"); + auto* head = reinterpret_cast(header[0]); + auto* node = reinterpret_cast(head[1]); + for (int index = 0, sourceIndex = 0; index < expected; ++index, ++sourceIndex) { + if (once && budget >= 2 && budget < 5 && sourceIndex == budget - 2) ++sourceIndex; + const auto* entry = reinterpret_cast(node + 2); + Require(entry->type == 2 && entry->payload == static_cast(budget < 2 ? 99 : (sourceIndex + 1) * 10), "Array assignment must preserve retained entry values"); + if (budget >= 2) Require(entry->parent == &owner && !entry->auxiliary, "Replacement array entries must bind to the destination owner"); + node = reinterpret_cast(node[1]); + } + Require(node == head, "Replacement array traversal must close at its sentinel"); + CheckLinks(source, 3); + trace.headers.insert(destination.items); + _ZN3sce4Json5ArrayD1Ev(&destination); _ZN3sce4Json5ArrayD1Ev(&source); + Require(trace.live.size() == baseline, "Assignment must release failed replacement resources and its previous list"); + } + Array array{}; _ZN3sce4Json5ArrayC1Ev(&array); + Value value{}; + for (int number : {10, 20, 30}) { _ZN3sce4Json5ValueC1El(&value, number); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &value); _ZN3sce4Json5ValueD1Ev(&value); } + auto* previous = array.items; trace.headers.insert(previous); + _ZN3sce4Json5ArrayaSERKS1_(&array, &array); + Require(array.items != previous, "Modern array self-assignment must replace its header"); + CheckLinks(array, 3); + trace.headers.insert(array.items); _ZN3sce4Json5ArrayD1Ev(&array); + Require(trace.live.size() == baseline, "Array self-assignment must release its old list"); +} +int main() { + ArrayTrace trace; ArrayAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize array allocation contract"); + const auto baseline = trace.live.size(); + CheckAssignments(trace); + for (int budget : {0, 1}) { + trace.budget = budget; Array partial{}; _ZN3sce4Json5ArrayC1Ev(&partial); + Require(budget ? partial.items && trace.live.at(partial.items) == 32 && !*reinterpret_cast(partial.items) : !partial.items, + "Header and sentinel failures retain distinct partial array ownership"); + Require(_ZNK3sce4Json5Array4sizeEv(&partial) == 0, "Partial arrays remain empty"); + if (partial.items) trace.headers.insert(partial.items); + trace.budget = -1; _ZN3sce4Json5ArrayD1Ev(&partial); + Require(!partial.items && trace.live.size() == baseline, "Partial arrays must be destructible without leaks"); + } + Array array{}; _ZN3sce4Json5ArrayC1Ev(&array); + trace.headers.insert(array.items); + auto* header = reinterpret_cast(array.items); + Require(trace.live.at(array.items) == 32 && trace.live.at(reinterpret_cast(header[0])) == 48 && !header[3], "Actual array header and sentinel must use the configured allocator"); + Require(_ZNK3sce4Json5Array5frontEv(&array) == reinterpret_cast(header[0] + 16) && _ZNK3sce4Json5Array4backEv(&array) == reinterpret_cast(header[0] + 16), "Empty array endpoints expose the sentinel payload address"); + Value value{}; + for (int number : {10, 20, 30}) { _ZN3sce4Json5ValueC1El(&value, number); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &value); _ZN3sce4Json5ValueD1Ev(&value); } + CheckLinks(array, 3); + ArrayIterator begin{}, end{}, copy{}; + _ZNK3sce4Json5Array5beginEv(&begin, &array); _ZNK3sce4Json5Array3endEv(&end, &array); + Require(trace.live.at(begin.state) == 8 && trace.live.at(end.state) == 8 && *reinterpret_cast(end.state) == reinterpret_cast(header[0]), "ArrayIterator state must own one allocator-backed native node pointer"); + Require(_ZN3sce4Json5Array8iteratorppEi(&begin, 0) == &begin && *_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(&begin)) == 20, "Postfix increment must update this iterator without a hidden result object"); + _ZN3sce4Json5Array8iteratorC1ERKS2_(©, &begin); + Require(trace.live.at(copy.state) == 8 && copy.state != begin.state, "ArrayIterator copy must own independent state"); + _ZN3sce4Json5Array8iterator7advanceEm(&begin, 2); + Require(!_ZNK3sce4Json5Array8iteratorneERKS2_(&begin, &end), "Advance must reach the sentinel"); + _ZN3sce4Json5Array8iteratorppEv(&begin); + Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(&begin)) == 10, "Array increment must follow the circular link after the sentinel"); + _ZN3sce4Json5Array8iterator7advanceEm(&begin, 6); + Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array8iteratordeEv(&begin)) == 30, "Advance must preserve circular traversal"); + trace.budget = 0; ArrayIterator failed{}; _ZNK3sce4Json5Array5beginEv(&failed, &array); + Require(!failed.state, "ArrayIterator allocation failure must leave an empty outer handle"); + const auto fallbackRequests = trace.requests.size(); const auto fallbackNotices = trace.notifications; + const auto* fallback = _ZNK3sce4Json5Array8iteratordeEv(&failed); + Require(fallback && trace.live.contains(const_cast(fallback)) && trace.live.at(const_cast(fallback)) == 32 && + fallback->type == 0 && !fallback->parent && !fallback->auxiliary, + "Missing iterator state must return the allocator-owned initialized module fallback"); + const Value fallbackValue = *fallback; + ArrayIterator missing{}; Value lookup{}; _ZN3sce4Json5ValueC1El(&lookup, 17); + Require(_ZNK3sce4Json5Array8iteratordeEv(&missing) == fallback && _ZNK3sce4Json5Array8iteratorptEv(&failed) == fallback && + _ZNK3sce4Json5Array8iteratorptEv(&missing) == fallback && + trace.requests.size() == fallbackRequests && trace.notifications == fallbackNotices && std::memcmp(fallback, &fallbackValue, sizeof(fallbackValue)) == 0, + "Independent missing iterators must share the exact fallback without allocations, notices or mutation"); + Require(_ZNK3sce4Json5ValueixEPKc(&lookup, "missing") == fallback && std::memcmp(fallback, &fallbackValue, sizeof(fallbackValue)) == 0, + "Failed keyed lookup must return the same module fallback without mutating its bytes"); + _ZN3sce4Json5ValueD1Ev(&lookup); + trace.budget = -1; + _ZN3sce4Json5Array8iteratorD1Ev(©); _ZN3sce4Json5Array8iteratorD1Ev(&begin); _ZN3sce4Json5Array8iteratorD1Ev(&end); + const auto beforeCopies = trace.live.size(); + for (int budget = 0; budget <= 6; ++budget) { + trace.budget = budget; Array duplicate{}; _ZN3sce4Json5ArrayC1ERKS1_(&duplicate, &array); + const auto expected = budget < 4 ? 0 : static_cast(budget - 3); + Require(_ZNK3sce4Json5Array4sizeEv(&duplicate) == expected, "Array copy allocation failures retain the observed copied prefix"); + if (expected) CheckLinks(duplicate, expected); + if (duplicate.items) trace.headers.insert(duplicate.items); + trace.budget = -1; _ZN3sce4Json5ArrayD1Ev(&duplicate); + Require(trace.live.size() == beforeCopies, "Partial array copies must release all resources"); + } + trace.budget = 0; _ZN3sce4Json5ValueC1El(&value, 40); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &value); trace.budget = -1; + Require(_ZNK3sce4Json5Array4sizeEv(&array) == 2 && *_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array4backEv(&array)) == 20, "Failed append must retain the reference module's removal of the existing tail"); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &value); + Require(_ZNK3sce4Json5Array4sizeEv(&array) == 3 && *_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5Array4backEv(&array)) == 40, "Append remains usable after failure"); + ArrayIterator retained{}; _ZNK3sce4Json5Array5beginEv(&retained, &array); _ZN3sce4Json5Array8iteratorppEv(&retained); + const auto* survivor = _ZNK3sce4Json5Array8iteratordeEv(&retained); + const auto requests = trace.requests.size(); + const auto notifications = trace.notifications; + trace.budget = 0; + _ZN3sce4Json5Array9pop_frontEv(&array); + Require(_ZNK3sce4Json5Array4sizeEv(&array) == 2 && _ZNK3sce4Json5Array5frontEv(&array) == survivor, "Removing the front must preserve the next element and its iterator identity"); + CheckLinks(array, 2, {20, 40}); + _ZN3sce4Json5Array9pop_frontEv(&array); _ZN3sce4Json5Array9pop_frontEv(&array); + Require(trace.requests.size() == requests && trace.notifications == notifications, "Front removal must reclaim allocator-owned nodes without allocating"); + CheckLinks(array, 0); + trace.budget = -1; + _ZN3sce4Json5Array8iteratorD1Ev(&retained); + _ZN3sce4Json5ValueD1Ev(&value); _ZN3sce4Json5Array5clearEv(&array); + Require(_ZNK3sce4Json5Array4sizeEv(&array) == 0 && trace.live.size() == baseline + 2, "Clear must retain only the array header and sentinel"); + _ZN3sce4Json5ArrayD1Ev(&array); + Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + for (const char* key : {"a", "b"}) { String string{}; _ZN3sce4Json6StringC1EPKc(&string, key); _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &string); _ZN3sce4Json6StringD1Ev(&string); } + ObjectIterator member{}; _ZNK3sce4Json6Object5beginEv(&member, &object); + Require(_ZN3sce4Json6Object8iteratorppEi(&member, 0) == &member && std::strcmp(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(&member)->key), "b") == 0, + "Object postfix increment must also update this iterator without a hidden result object"); + _ZN3sce4Json6Object8iteratorD1Ev(&member); _ZN3sce4Json6ObjectD1Ev(&object); + Require(trace.live.size() == baseline, "Array and iterator operations must release their allocated resources"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Terminate array allocation contract"); + return 0; +} diff --git a/core/libs/tests/GuestJsonArrayInsertion.cpp b/core/libs/tests/GuestJsonArrayInsertion.cpp new file mode 100644 index 0000000000..0b06a5d4a8 --- /dev/null +++ b/core/libs/tests/GuestJsonArrayInsertion.cpp @@ -0,0 +1,250 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ArrayTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::set headers; + std::map generations; + std::size_t generation = 0; +}; +static void* APS5_VABI AllocateArray(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); + Require(trace.generations.emplace(memory, ++trace.generation).second, "Each insertion allocation must own a fresh live generation"); + return memory; +} +static void APS5_VABI FreeArray(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + if (trace.headers.erase(memory)) { + const auto* words = reinterpret_cast(memory); + Require(words[0] == 0xffffffff && words[1] == UINT64_MAX, "Array teardown must expose the native header poison before allocator release"); + } + Require(trace.live.erase(memory) == 1 && trace.generations.erase(memory) == 1, "Array must release through its owning allocator exactly once"); std::free(memory); +} +static void APS5_VABI NotifyArray(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Array notification must identify the failed allocation"); ++trace.notifications; +} +static void* const ArrayVtable[]{nullptr, nullptr, reinterpret_cast(AllocateArray), reinterpret_cast(FreeArray), reinterpret_cast(NotifyArray)}; +struct ArrayAllocator { void* const* vtable = ArrayVtable; }; +static void Fill(Array& array) { + _ZN3sce4Json5ArrayC1Ev(&array); + for (int number : {10, 20, 30}) { + Value value{}; _ZN3sce4Json5ValueC1El(&value, number); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &value); + _ZN3sce4Json5ValueD1Ev(&value); + } +} +static void CheckSequence(const Array& array, bool front, bool once, int budget) { + const bool inserted = front ? budget >= 4 : (once ? budget != 4 : budget >= 5); + const bool lostFirst = front && !inserted && (!once || budget == 3); + const auto* head = *reinterpret_cast(array.items); + auto* node = reinterpret_cast(head[1]); + unsigned seen = 0; + for (int slot = 0; slot < 4; ++slot) { + const bool newSlot = slot == (front ? 0 : 1); + if (newSlot && !inserted) continue; + const int original = front ? slot : (slot == 0 ? 1 : slot); + if (!newSlot && lostFirst && original == 1) continue; + Require(node != head, "Insertion must preserve the expected list length"); + const auto* value = reinterpret_cast(node + 2); + if (newSlot) { + const bool emptyNull = !front && once && (budget == 1 || budget == 2); + Require(value->type == (emptyNull ? 0 : 5), "Insertion must retain the native partial-copy type after one-shot failure"); + if (!emptyNull) { + const char* expected = !front && once && budget == 3 ? "" : "abc"; + Require(value->payload && std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(value->payload)), expected) == 0, "Insertion must retain the native partial string payload"); + } + } else Require(value->type == 2 && value->payload == static_cast(10 * original), "Insertion failure must preserve the reference's surviving original values"); + node = reinterpret_cast(node[1]); ++seen; + } + Require(node == head && _ZNK3sce4Json5Array4sizeEv(&array) == seen, "Insertion links and element count must agree"); +} +struct TransferSnapshot { + void* pointer; + std::size_t generation; + std::vector bytes; +}; +static void CheckNestedTransferFailures(ArrayTrace& trace) { + const auto baseline = trace.generations; + for (bool object : {false, true}) for (bool front : {false, true}) for (bool once : {false, true}) for (int failure = 0; failure <= 5; ++failure) { + Array inner{}; Object innerObject{}; + if (object) { + _ZN3sce4Json6ObjectC1Ev(&innerObject); + int number = 10; + for (const char* text : {"a", "b", "c"}) { + String key{}; _ZN3sce4Json6StringC1EPKc(&key, text); + _ZN3sce4Json5ValueC1El(_ZN3sce4Json6ObjectixERKNS0_6StringE(&innerObject, &key), number); + number += 10; _ZN3sce4Json6StringD1Ev(&key); + } + } else Fill(inner); + const auto beforeSource = trace.generations; + Value source{}; + if (object) _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&source, &innerObject); + else _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&source, &inner); + std::vector sourceAllocations; + for (const auto& [pointer, generation] : trace.generations) { + if (beforeSource.contains(pointer)) continue; + const auto* bytes = reinterpret_cast(pointer); + sourceAllocations.push_back({pointer, generation, {bytes, bytes + trace.live.at(pointer)}}); + } + Array destination{}; Fill(destination); + ArrayIterator position{}, result{}; _ZNK3sce4Json5Array5beginEv(&position, &destination); + _ZN3sce4Json5Array8iterator7advanceEm(&position, 1); + const auto* survivor = _ZNK3sce4Json5Array8iteratordeEv(&position); + const auto sourceValue = source; + std::vector copyRequests = object + ? std::vector{8, 32, 104, 32, 104, 24, 2, 24, 2, 2, 2, 24, 2, 104, 2, 24, 2, 24, 2, 24, 2, 2, 2, 24, 2, 104, 2, 24, 2, 24, 2, 24, 2, 2, 2, 24, 2, 104, 2, 24, 2, 48} + : std::vector{8, 32, 48, 32, 48, 48, 48, 48, 48}; + const auto requests = trace.requests.size(); const auto notices = trace.notifications; + trace.budget = static_cast(copyRequests.size()) + (front ? 0 : 1) + failure; trace.once = once; + if (front) _ZN3sce4Json5Array10push_frontERKNS0_5ValueE(&destination, &source); + else Require(_ZN3sce4Json5Array6insertERKNS1_8iteratorERKNS0_5ValueE(&result, &destination, &position, &source) == &result, "Nested insertion must return its actual caller-owned iterator"); + trace.budget = -1; trace.once = false; + std::vector expected; + if (!front) expected.push_back(8); + expected.insert(expected.end(), copyRequests.begin(), copyRequests.end()); + expected.insert(expected.end(), std::max(2, std::min(failure + 1, 5)), 8); + Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected, "Nested transfer must retain the measured copy, node and interrupted iterator allocation sequence"); + const unsigned failures = failure == 5 ? 0 : failure == 0 && !once ? 2 : 1; + Require(trace.notifications == notices + failures, "Nested transfer must notify every rejected iterator allocation without retrying the interrupted traversal"); + const bool rolledBack = front && failure < 5; + Require(_ZNK3sce4Json5Array4sizeEv(&destination) == (rolledBack ? 3u : 4u), "Prepending must remove its new entry after transfer failure while insertion retains it"); + Require(_ZNK3sce4Json5Array8iteratordeEv(&position) == survivor && survivor->type == 2 && survivor->payload == 20, "Interrupted nested transfer must preserve surviving iterator and element identity"); + auto* head = *reinterpret_cast(destination.items); + auto* node = reinterpret_cast(head[1]); + const Value* inserted = nullptr; + for (int original = 0; original < 3; ++original) { + if (!rolledBack && original == (front ? 0 : 1)) { + inserted = reinterpret_cast(node + 2); node = reinterpret_cast(node[1]); + } + Require(node != head && reinterpret_cast(node + 2)->type == 2 && reinterpret_cast(node + 2)->payload == static_cast((original + 1) * 10), "Transfer failure must preserve the full order of all existing destination entries"); + node = reinterpret_cast(node[1]); + } + Require(node == head, "Interrupted nested transfer must preserve list closure"); + if (!rolledBack) { + Require(inserted && !inserted->parent && !inserted->auxiliary && inserted->type == (object ? 7 : 6) && inserted->payload && inserted->payload != source.payload, "Transferred nested collections must own an independent root payload"); + if (!front) Require(_ZNK3sce4Json5Array8iteratordeEv(&result) == inserted, "Returning iterator must retain the new node after an interrupted child transfer"); + const auto rebound = std::max(0, std::min(failure - 1, 3)); + const auto checkChild = [&](const Value* value, int index) { + Require(value->type == 2 && value->payload == static_cast((index + 1) * 10) && (value->parent == inserted) == (index < rebound), "Transfer must rebind only the child prefix admitted before iterator failure"); + }; + if (object) { + const auto* nested = reinterpret_cast(inserted->payload); + ObjectIterator current{}, end{}; _ZNK3sce4Json6Object5beginEv(¤t, nested); _ZNK3sce4Json6Object3endEv(&end, nested); + for (int index = 0; index < 3; ++index) { + Require(_ZNK3sce4Json6Object8iteratorneERKS2_(¤t, &end), "Interrupted object transfer must retain every copied tree node"); + const auto* pair = _ZNK3sce4Json6Object8iteratordeEv(¤t); + const char key[]{static_cast('a' + index), 0}; + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair->key), key) == 0, "Object transfer must preserve ordered public keys"); + checkChild(&pair->value, index); _ZN3sce4Json6Object8iteratorppEv(¤t); + } + Require(!_ZNK3sce4Json6Object8iteratorneERKS2_(¤t, &end) && _ZNK3sce4Json6Object4sizeEv(nested) == 3, "Interrupted object transfer must preserve tree endpoints and count"); + _ZN3sce4Json6Object8iteratorD1Ev(¤t); _ZN3sce4Json6Object8iteratorD1Ev(&end); + } else { + const auto* nested = reinterpret_cast(inserted->payload); + auto* nestedHead = *reinterpret_cast(nested->items); + auto* child = reinterpret_cast(nestedHead[1]); + for (int index = 0; index < 3; ++index) { + Require(child != nestedHead, "Interrupted child rebinding must preserve every copied node"); + checkChild(reinterpret_cast(child + 2), index); child = reinterpret_cast(child[1]); + } + Require(child == nestedHead && _ZNK3sce4Json5Array4sizeEv(nested) == 3, "Nested payload count and circular links must survive interrupted transfer"); + } + } + Require(std::memcmp(&source, &sourceValue, sizeof(Value)) == 0, "Interrupted transfer must preserve the source Value bytes"); + for (const auto& snapshot : sourceAllocations) Require(trace.generations.at(snapshot.pointer) == snapshot.generation && std::memcmp(snapshot.pointer, snapshot.bytes.data(), snapshot.bytes.size()) == 0, "Interrupted transfer must preserve all source payload allocation generations and bytes"); + _ZN3sce4Json5Array8iteratorD1Ev(&result); _ZN3sce4Json5Array8iteratorD1Ev(&position); + _ZN3sce4Json5ArrayD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ArrayD1Ev(&inner); _ZN3sce4Json6ObjectD1Ev(&innerObject); + Require(trace.generations == baseline, "Both retained and rolled-back nested transfers must reclaim every newly admitted allocation generation"); + } +} +int main() { + ArrayTrace trace; ArrayAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize insertion allocation contracts"); + const auto baseline = trace.live.size(); + for (bool front : {false, true}) for (bool once : {false, true}) for (int budget = 0; budget <= 5; ++budget) { + Array array{}; Fill(array); + Value source{}; _ZN3sce4Json5ValueC1EPKc(&source, "abc"); + ArrayIterator position{}, result{}; _ZNK3sce4Json5Array5beginEv(&position, &array); _ZN3sce4Json5Array8iterator7advanceEm(&position, 1); + const auto* survivor = _ZNK3sce4Json5Array8iteratordeEv(&position); + const auto requests = trace.requests.size(); const auto notices = trace.notifications; + trace.budget = budget; trace.once = once; + if (front) _ZN3sce4Json5Array10push_frontERKNS0_5ValueE(&array, &source); + else Require(_ZN3sce4Json5Array6insertERKNS1_8iteratorERKNS0_5ValueE(&result, &array, &position, &source) == &result, "Insert must return the caller's hidden iterator object"); + trace.budget = -1; trace.once = false; + CheckSequence(array, front, once, budget); + Require(_ZNK3sce4Json5Array8iteratordeEv(&position) == survivor && survivor->payload == 20, "Insertion must preserve surviving node and iterator identity"); + std::vector expected; + if (!front) expected.push_back(8); + expected.push_back(8); + const int copied = budget - (front ? 0 : 1); + if (copied >= 1 || (!front && once && budget == 0)) expected.push_back(24); + if (copied >= 2 || (!front && once && budget == 0)) expected.push_back(4); + expected.push_back(48); + Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected, "Insertion must preserve allocation order across result, copied storage and list-node admission"); + const unsigned failed = once ? (budget < (front ? 4 : 5) ? 1 : 0) : (front ? (budget < 3 ? 2 : budget == 3 ? 1 : 0) : (budget == 0 ? 3 : budget < 4 ? 2 : budget == 4 ? 1 : 0)); + Require(trace.notifications == notices + failed, "Every rejected insertion allocation must notify exactly once"); + if (!front) { + Require((result.state != nullptr) == (budget != 0), "Result allocation failure must not suppress later insertion work"); + if (result.state) { + const bool nodeFailed = once ? budget == 4 : budget < 5; + auto* head = *reinterpret_cast(array.items); + auto* returned = *reinterpret_cast(result.state); + Require(nodeFailed ? returned == head : returned != head && reinterpret_cast(returned)[1] == *reinterpret_cast(position.state), "Insert must return the admitted node or the sentinel when node allocation fails"); + } + } + Require(source.type == 5 && std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(source.payload)), "abc") == 0, "Insertion must preserve its independently owned source"); + _ZN3sce4Json5Array8iteratorD1Ev(&result); _ZN3sce4Json5Array8iteratorD1Ev(&position); + _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ArrayD1Ev(&array); + Require(trace.live.size() == baseline, "Every insertion failure must remain destructible without leaked ownership"); + } + for (bool front : {false, true}) { + Array inner{}; Fill(inner); + Value source{}; _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&source, &inner); + Array destination{}; _ZN3sce4Json5ArrayC1Ev(&destination); + ArrayIterator position{}, result{}; _ZNK3sce4Json5Array3endEv(&position, &destination); + const auto requests = trace.requests.size(); + if (front) _ZN3sce4Json5Array10push_frontERKNS0_5ValueE(&destination, &source); + else _ZN3sce4Json5Array6insertERKNS1_8iteratorERKNS0_5ValueE(&result, &destination, &position, &source); + const auto* inserted = _ZNK3sce4Json5Array5frontEv(&destination); + Require(inserted->type == 6 && inserted->payload != source.payload && inserted->parent == nullptr, "Nested insertion must own an independent array payload with the destination's parent"); + auto* nested = reinterpret_cast(inserted->payload); + auto* head = *reinterpret_cast(nested->items); + for (auto* node = reinterpret_cast(head[1]); node != head; node = reinterpret_cast(node[1])) Require(reinterpret_cast(node + 2)->parent == inserted, "Payload transfer must rebind every child to the actual inserted value"); + Require(trace.requests.size() >= requests + 5 && std::vector(trace.requests.end() - 5, trace.requests.end()) == std::vector({8, 8, 8, 8, 8}), "Nested payload transfer must allocate its begin and repeated end iterator states through the supplied allocator"); + _ZN3sce4Json5Array8iteratorD1Ev(&result); _ZN3sce4Json5Array8iteratorD1Ev(&position); + _ZN3sce4Json5ArrayD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ArrayD1Ev(&inner); + Require(trace.live.size() == baseline, "Nested insertion and iterator traversal must release every admitted allocation"); + } + CheckNestedTransferFailures(trace); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty() && trace.generations.empty(), "Terminate insertion allocation contracts"); + return 0; +} diff --git a/core/libs/tests/GuestJsonArrayMutation.cpp b/core/libs/tests/GuestJsonArrayMutation.cpp new file mode 100644 index 0000000000..4ab41be386 --- /dev/null +++ b/core/libs/tests/GuestJsonArrayMutation.cpp @@ -0,0 +1,147 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ArrayTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::set headers; +}; +static void* APS5_VABI AllocateArray(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI FreeArray(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + if (trace.headers.erase(memory)) { + const auto* words = reinterpret_cast(memory); + Require(words[0] == 0xffffffff && words[1] == UINT64_MAX, "Array teardown must expose the native header poison before allocator release"); + } + Require(trace.live.erase(memory) == 1, "Array must release through its owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyArray(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Array notification must identify the failed allocation"); ++trace.notifications; +} +static void* const ArrayVtable[]{nullptr, nullptr, reinterpret_cast(AllocateArray), reinterpret_cast(FreeArray), reinterpret_cast(NotifyArray)}; +struct ArrayAllocator { void* const* vtable = ArrayVtable; }; +static void CheckLinks(const Array& array, std::size_t count, std::initializer_list values = {}) { + Require(values.size() == 0 || values.size() == count, "Expected array values must cover every live node"); + const auto* header = reinterpret_cast(array.items); + Require(header && header[1] == count, "Array header must expose its native element count"); + const auto* head = reinterpret_cast(header[0]); + const auto* previous = head; + std::size_t seen = 0; + for (auto* node = reinterpret_cast(head[1]); node != head; node = reinterpret_cast(node[1])) { + Require(node[0] == reinterpret_cast(previous), "Every array link must retain its predecessor"); + const auto* value = reinterpret_cast(node + 2); + Require(++seen <= count, "Array links must close at the sentinel"); + const auto expected = values.size() ? *(values.begin() + seen - 1) : 10 * seen; + Require(!value->parent && !value->auxiliary && value->type == 2 && value->payload == expected, "Array nodes must expose their inline values and ownership"); + previous = node; + } + Require(seen == count && head[0] == reinterpret_cast(previous), "Array sentinel must close both list directions"); +} +int main() { + ArrayTrace trace; ArrayAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize array mutation contract"); + const auto baseline = trace.live.size(); + Array absent{}; + const auto beforeMissing = trace.requests.size(); + _ZN3sce4Json5Array8pop_backEv(&absent); + Require(trace.requests.size() == beforeMissing, "Popping missing storage must do nothing without allocating"); + ArrayIterator missingResult{}; + Require(_ZN3sce4Json5Array5eraseERKNS1_8iteratorE(&missingResult, &absent, nullptr) == &missingResult && missingResult.state && *reinterpret_cast(missingResult.state) == nullptr, "Erase with missing array storage must return an allocated unbound iterator without reading the input iterator"); + _ZN3sce4Json5Array8iteratorD1Ev(&missingResult); + for (bool rejectResult : {false, true}) { + for (unsigned index : {0u, 1u, 2u}) { + Array array{}; _ZN3sce4Json5ArrayC1Ev(&array); + Value value{}; + for (int number : {10, 20, 30}) { + _ZN3sce4Json5ValueC1El(&value, number); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &value); + _ZN3sce4Json5ValueD1Ev(&value); + } + ArrayIterator selected{}, survivor{}, end{}; + _ZNK3sce4Json5Array5beginEv(&selected, &array); + _ZN3sce4Json5Array8iterator7advanceEm(&selected, index); + _ZNK3sce4Json5Array5beginEv(&survivor, &array); + _ZN3sce4Json5Array8iterator7advanceEm(&survivor, index == 0 ? 1 : 0); + _ZNK3sce4Json5Array3endEv(&end, &array); + const auto* survivorAddress = _ZNK3sce4Json5Array8iteratordeEv(&survivor); + const auto* removed = _ZNK3sce4Json5Array8iteratordeEv(&selected); + void* selectedNode = *reinterpret_cast(selected.state); + void* nextNode = reinterpret_cast(selectedNode)[1]; + const auto requests = trace.requests.size(); + const auto notices = trace.notifications; + trace.budget = rejectResult ? 0 : -1; + ArrayIterator result{}; + Require(_ZN3sce4Json5Array5eraseERKNS1_8iteratorE(&result, &array, &selected) == &result, "Erase must return the caller's hidden result object"); + trace.budget = -1; + Require(trace.requests.size() == requests + 1 && trace.requests.back() == 8 && trace.notifications == notices + (rejectResult ? 1 : 0), "Erase must request exactly one iterator state and notify only when that allocation fails"); + Require(!trace.live.count(selectedNode) && _ZNK3sce4Json5Array4sizeEv(&array) == 2, "Erase must remove the requested node even when its result-state allocation fails"); + Require(result.state ? *reinterpret_cast(result.state) == nextNode : rejectResult, "The returned iterator must identify the successor or remain absent after allocation failure"); + Require(_ZNK3sce4Json5Array8iteratordeEv(&survivor) == survivorAddress && survivorAddress != removed, "Erasure must preserve every surviving inline value and iterator identity"); + if (index == 0) CheckLinks(array, 2, {20, 30}); + else if (index == 1) CheckLinks(array, 2, {10, 30}); + else CheckLinks(array, 2, {10, 20}); + _ZN3sce4Json5Array8iteratorD1Ev(&result); + _ZN3sce4Json5Array8iteratorD1Ev(&selected); + _ZN3sce4Json5Array8iteratorD1Ev(&end); + const auto popRequests = trace.requests.size(); + _ZN3sce4Json5Array8pop_backEv(&array); + Require(trace.requests.size() == popRequests && _ZNK3sce4Json5Array4sizeEv(&array) == 1, "Pop back must destroy the final node without allocating"); + _ZN3sce4Json5Array8pop_backEv(&array); + _ZN3sce4Json5Array8pop_backEv(&array); + CheckLinks(array, 0); + _ZN3sce4Json5Array8iteratorD1Ev(&survivor); + _ZN3sce4Json5ArrayD1Ev(&array); + Require(trace.live.size() == baseline, "Mutation and iterator teardown must restore allocator ownership without leaks"); + } + } + for (bool erase : {false, true}) { + Array array{}; _ZN3sce4Json5ArrayC1Ev(&array); + Value source{}; _ZN3sce4Json5ValueC1EPKc(&source, "owned payload"); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &source); + const auto sourceAllocations = trace.live.size(); + if (erase) { + ArrayIterator selected{}, result{}; _ZNK3sce4Json5Array5beginEv(&selected, &array); + trace.budget = 0; + _ZN3sce4Json5Array5eraseERKNS1_8iteratorE(&result, &array, &selected); + trace.budget = -1; + Require(!result.state, "Rejected erase result allocation must stay absent while destroying its owned payload"); + _ZN3sce4Json5Array8iteratorD1Ev(&selected); + } else _ZN3sce4Json5Array8pop_backEv(&array); + Require(trace.live.size() + 4 == sourceAllocations && source.type == 5 && source.payload, "Removal must release the string node, handle, header and characters while preserving the independent source"); + _ZN3sce4Json5ValueD1Ev(&source); + _ZN3sce4Json5ArrayD1Ev(&array); + Require(trace.live.size() == baseline, "Owned-payload removal must leave no allocator resources behind"); + } + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Terminate the mutation allocation context without outstanding resources"); + return 0; +} diff --git a/core/libs/tests/GuestJsonCallbackCopies.cpp b/core/libs/tests/GuestJsonCallbackCopies.cpp new file mode 100644 index 0000000000..57ab1365f0 --- /dev/null +++ b/core/libs/tests/GuestJsonCallbackCopies.cpp @@ -0,0 +1,154 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using NullHandler = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +using MismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +using FloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +using ElementHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct CopyAllocations { std::map live; std::size_t generation = 0; }; +static void* APS5_VABI AllocateCopy(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); + Require(trace.live.emplace(memory, ++trace.generation).second, "Callback copy allocations must have independent live generations"); + return memory; +} +static void APS5_VABI FreeCopy(void*, void* memory, void* context) { + if (!memory) return; + Require(static_cast(context)->live.erase(memory) == 1, "Callback copy teardown must release each allocation through its owner exactly once"); + std::free(memory); +} +static void APS5_VABI NotifyCopy(void*, int, std::size_t, void*) { Require(false, "Healthy callback copies must not fail allocation"); } +static void* const CopyVtable[]{nullptr, nullptr, reinterpret_cast(AllocateCopy), reinterpret_cast(FreeCopy), reinterpret_cast(NotifyCopy)}; +struct CopyAllocator { void* const* vtable = CopyVtable; }; +struct CopyContext { + Value replacement{}; + const Value* receiver = nullptr; + unsigned nulls = 0, mismatches = 0, floats = 0, elements = 0; +}; +static const Value& APS5_VABI NullCopy(ValueType type, const Value* parent, void* context) { + Require(type == 2 && !parent, "Copied null recovery must preserve requested type and parent ABI"); + auto& state = *static_cast(context); ++state.nulls; + return state.replacement; +} +static const Value& APS5_VABI MismatchCopy(ValueType type, const Value* self, const Value* parent, void* context) { + auto& state = *static_cast(context); + Require(type == 2 && self == state.receiver && !parent, "Copied mismatch recovery must preserve receiver, type and parent ABI"); + ++state.mismatches; + return state.replacement; +} +static Value* APS5_VABI FloatCopy(Value* result, std::int32_t function, double number, const Value* parent, void* context, bool* error) { + Require(function == 1 && number == std::numeric_limits::infinity() && !parent && !error, "Copied float recovery must preserve its assignment ABI"); + auto& state = *static_cast(context); ++state.floats; + _ZN3sce4Json5ValueC1El(result, static_cast(state.replacement.payload)); + return result; +} +static void APS5_VABI ElementCopy(int error, const String* key, const std::size_t* index, const Value* parent, const Value* self, void* context) { + auto& state = *static_cast(context); + Require(static_cast(error) == 0x80848115 && key && !index && !parent && self == state.receiver && std::strcmp(_ZNK3sce4Json6String5c_strEv(key), "missing") == 0, "Copied element recovery must retain its exact key, receiver and parent ABI"); + ++state.elements; +} +static void Register(Value& value, CopyContext& context) { + context.receiver = &value; + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&value, NullCopy, &context) == 0, "Register source null recovery"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&value, MismatchCopy, &context) == 0, "Register source mismatch recovery"); + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, FloatCopy, &context) == 0, "Register source float recovery"); + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&value, ElementCopy, &context) == 0, "Register source element recovery"); +} +struct MetadataSnapshot { + std::array bytes; + std::array allocations; + std::array generations; +}; +static MetadataSnapshot Capture(const Value& value, const CopyAllocations& trace) { + MetadataSnapshot snapshot{}; + const auto* words = reinterpret_cast(value.auxiliary); + snapshot.allocations = {value.auxiliary, words[2], words[3], words[4], words[5]}; + std::memcpy(snapshot.bytes.data(), value.auxiliary, snapshot.bytes.size()); + for (unsigned i = 0; i < snapshot.allocations.size(); ++i) snapshot.generations[i] = trace.live.at(snapshot.allocations[i]); + return snapshot; +} +static void Verify(const Value& value, const MetadataSnapshot& snapshot, const CopyAllocations& trace) { + Require(value.auxiliary == snapshot.allocations[0] && std::memcmp(value.auxiliary, snapshot.bytes.data(), snapshot.bytes.size()) == 0, "Copying must preserve complete source/destination auxiliary bytes and handler/context identities"); + for (unsigned i = 0; i < snapshot.allocations.size(); ++i) Require(trace.live.at(snapshot.allocations[i]) == snapshot.generations[i], "Copying must preserve auxiliary, fallback and all three callback-record allocation generations"); + Require(reinterpret_cast(snapshot.allocations[1])->parent == &value, "Copied callback metadata must retain its original fallback owner"); +} +static std::string Serialize(Value& value) { + String output{}; _ZN3sce4Json6StringC1EPKc(&output, ""); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0, "Callback copy source must remain serializable"); + std::string text = _ZNK3sce4Json6String5c_strEv(&output); + _ZN3sce4Json6StringD1Ev(&output); + return text; +} +int main() { + CopyAllocations allocations; CopyAllocator allocator; Parameter parameter{&allocator, &allocations, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize callback copy contracts"); + const auto baseline = allocations.live; + for (const char* document : {"null", "1.25", "{\"n\":null,\"s\":\"text\"}"}) for (unsigned mode = 0; mode < 5; ++mode) { + Value source{}, copy{}; Pair pair{}; String key{}; Array array{}; + _ZN3sce4Json5ValueC1Ev(&source); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&source, document, std::strlen(document)) == 0, "Construct callback-bearing source"); + CopyContext sourceContext, destinationContext; + _ZN3sce4Json5ValueC1El(&sourceContext.replacement, 77); _ZN3sce4Json5ValueC1El(&destinationContext.replacement, 99); + Register(source, sourceContext); + const auto sourceMetadata = Capture(source, allocations); + Value* target = © + MetadataSnapshot destinationMetadata{}; + if (mode == 0) _ZN3sce4Json5ValueC1ERKS1_(©, &source); + else if (mode == 1 || mode == 2) { + _ZN3sce4Json5ValueC1Ev(©); + if (mode == 2) { Register(copy, destinationContext); destinationMetadata = Capture(copy, allocations); } + _ZN3sce4Json5Value3setERKS1_(©, &source); + } else if (mode == 3) { + _ZN3sce4Json6StringC1EPKc(&key, "key"); + _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); target = &pair.value; + } else { + _ZN3sce4Json5ArrayC1Ev(&array); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &source); target = const_cast(_ZNK3sce4Json5Array5frontEv(&array)); + } + Require(!target->parent && target->type == source.type && Serialize(*target) == document, "Copies must retain source contents with independent root ownership"); + if (mode == 2) Verify(*target, destinationMetadata, allocations); + else Require(!target->auxiliary, "Modern constructors and unregistered destinations must not inherit source-local callback metadata"); + const auto* sourceInteger = _ZNK3sce4Json5Value10getIntegerEv(&source); + const auto* targetInteger = _ZNK3sce4Json5Value10getIntegerEv(target); + Require(sourceInteger == reinterpret_cast(&sourceContext.replacement.payload) && *sourceInteger == 77, "Source recovery must retain its exact borrowed replacement identity"); + Require(mode == 2 ? targetInteger == reinterpret_cast(&destinationContext.replacement.payload) && *targetInteger == 99 : *targetInteger == 0, "Copies must use destination handlers or typed defaults rather than source callbacks"); + if (source.type == 7) { _ZNK3sce4Json5ValueixEPKc(&source, "missing"); _ZNK3sce4Json5ValueixEPKc(target, "missing"); } + _ZN3sce4Json5Value3setEd(target, std::numeric_limits::infinity()); + Require(target->type == (mode == 2 ? 2 : 4) && target->payload == (mode == 2 ? UINT64_C(99) : std::bit_cast(std::numeric_limits::infinity())), "Copies must retain destination float recovery without dispatching source handlers"); + Require(sourceContext.nulls == unsigned(source.type == 0) && sourceContext.mismatches == unsigned(source.type != 0) && !sourceContext.floats && sourceContext.elements == unsigned(source.type == 7), "Copy consumers must not invoke source callbacks except explicit source operations"); + Require(destinationContext.nulls == unsigned(mode == 2 && source.type == 0) && destinationContext.mismatches == unsigned(mode == 2 && source.type != 0) && destinationContext.floats == unsigned(mode == 2) && destinationContext.elements == unsigned(mode == 2 && source.type == 7), "Assignment must preserve each destination callback and context independently"); + Verify(source, sourceMetadata, allocations); + Require(Serialize(source) == document, "Copied value mutation must preserve its independent source contents"); + if (mode == 2) Verify(*target, destinationMetadata, allocations); + if (mode == 3) { _ZN3sce4Json6Object4PairD1Ev(&pair); _ZN3sce4Json6StringD1Ev(&key); } + else if (mode == 4) _ZN3sce4Json5ArrayD1Ev(&array); + else _ZN3sce4Json5ValueD1Ev(©); + Verify(source, sourceMetadata, allocations); + _ZN3sce4Json5Value3setEd(&source, std::numeric_limits::infinity()); + Require(sourceContext.floats == 1 && source.type == 2 && source.payload == 77, "Source callbacks must remain usable after copied storage and destination callbacks are destroyed"); + Verify(source, sourceMetadata, allocations); + _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ValueD1Ev(&sourceContext.replacement); _ZN3sce4Json5ValueD1Ev(&destinationContext.replacement); + Require(allocations.live == baseline, "Callback copy cleanup must reclaim every temporary generation without replacing module resources"); + } + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && allocations.live.empty(), "Release callback copy resources"); + return 0; +} diff --git a/core/libs/tests/GuestJsonCallbackLifecycle.cpp b/core/libs/tests/GuestJsonCallbackLifecycle.cpp new file mode 100644 index 0000000000..1575445c3b --- /dev/null +++ b/core/libs/tests/GuestJsonCallbackLifecycle.cpp @@ -0,0 +1,133 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +enum class NoticeAction { None, Terminate }; +enum class AllocatorAction { None, TerminateFree }; +struct LifecycleTrace { + std::map live; + std::size_t generation = 0; + bool failNext = false; + std::uint8_t* owner = nullptr; + NoticeAction noticeAction = NoticeAction::None; + AllocatorAction allocatorAction = AllocatorAction::None; + unsigned notices = 0; + unsigned freeCalls = 0; +}; +static void* APS5_VABI AllocateLifecycle(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + if (trace.failNext) { trace.failNext = false; return nullptr; } + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); + Require(trace.live.emplace(memory, ++trace.generation).second, "Lifecycle resources must have independently live allocation generations"); + return memory; +} +static void APS5_VABI FreeLifecycle(void*, void* memory, void* context) { + auto& trace = *static_cast(context); + ++trace.freeCalls; + if (trace.allocatorAction == AllocatorAction::TerminateFree) { + trace.allocatorAction = AllocatorAction::None; + const auto identity = trace.live.at(memory); + try { + Require(_ZN3sce4Json11Initializer9terminateEv(trace.owner) == 0 && *trace.owner == 0 && trace.live.size() == 1 && trace.live.at(memory) == identity, + "Standalone free-callback termination must release module resources while retaining the current allocation for its original free callback"); + } catch (const std::runtime_error&) { Require(false, "Reference-supported standalone free-callback termination must not be rejected"); } + } + if (!memory) return; + Require(trace.live.erase(memory) == 1, "Lifecycle cleanup must release each allocation through its owner exactly once"); + std::free(memory); +} +static void APS5_VABI NotifyLifecycle(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && bytes == (trace.noticeAction == NoticeAction::Terminate ? 8u : 72u), "Lifecycle notifier must receive the actual failed allocation size and module error"); + ++trace.notices; + if (trace.noticeAction == NoticeAction::Terminate) { + try { + Require(_ZN3sce4Json11Initializer9terminateEv(trace.owner) == 0 && *trace.owner == 0 && trace.live.empty(), "A standalone Malloc notifier must retain reference-supported module termination"); + } catch (const std::runtime_error&) { Require(false, "Reference-supported standalone notifier termination must not be rejected"); } + } else Require(false, "Healthy lifecycle operations must not unexpectedly fail allocation"); +} +static void* const LifecycleVtable[]{nullptr, nullptr, reinterpret_cast(AllocateLifecycle), reinterpret_cast(FreeLifecycle), reinterpret_cast(NotifyLifecycle)}; +struct LifecycleAllocator { void* const* vtable = LifecycleVtable; }; +struct RecoveryContext { LifecycleTrace* trace; unsigned calls = 0; }; +static Value* APS5_VABI RecoverLifecycle(Value* result, std::int32_t function, double number, const Value* parent, void* context, bool* error) { + auto& state = *static_cast(context); + Require(function == 1 && number == std::numeric_limits::infinity() && !parent && !error, "Lifecycle recovery must preserve its assignment callback ABI"); + ++state.calls; + std::uint8_t inactive = 0; + Require(static_cast(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&inactive, nullptr)) == 0x80848111 && inactive == 0, "Recovery reentry must reject a competing initializer before parameter validation"); + Require(static_cast(_ZN3sce4Json11Initializer9terminateEv(&inactive)) == 0x80848110 && *state.trace->owner == 1, "Inactive termination must preserve active recovery and module ownership"); + _ZN3sce4Json5ValueC1El(result, 7); + return result; +} +static void CheckRecovery() { + LifecycleTrace trace; LifecycleAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; trace.owner = &initializer; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize recovery lifecycle contracts"); + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + RecoveryContext context{&trace}; + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, RecoverLifecycle, &context) == 0, "Register lifecycle recovery"); + const auto baseline = trace.live; + void* auxiliary = value.auxiliary; + for (unsigned attempt = 1; attempt <= 2; ++attempt) { + _ZN3sce4Json5Value3setEd(&value, std::numeric_limits::infinity()); + Require(value.type == 2 && value.payload == 7 && value.auxiliary == auxiliary && context.calls == attempt && initializer == 1 && trace.live == baseline, + "Handled callback lifecycle operations must preserve recovery metadata, repeatability and complete module ownership"); + } + _ZN3sce4Json5ValueD1Ev(&value); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && initializer == 0 && trace.live.empty(), "Ordinary termination must work after recovery callback reentry"); +} +static void CheckNotice() { + LifecycleTrace trace; LifecycleAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; trace.owner = &initializer; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize notifier lifecycle contracts"); + trace.noticeAction = NoticeAction::Terminate; + trace.failNext = true; + Require(!_ZN3sce4Json6MallocEm(8) && initializer == 0 && trace.live.empty() && trace.notices == 1, + "Standalone allocation failure must preserve successful notifier termination and its null allocation result"); + Require(initializer == 0 && trace.live.empty(), "Notifier lifecycle cases must fully release the original module generation"); + const auto generation = trace.generation; + trace.noticeAction = NoticeAction::None; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0 && initializer == 1 && trace.live.size() == 12, "The same initializer must admit a fresh module generation after notifier-driven termination"); + for (const auto& [memory, identity] : trace.live) Require(memory && identity > generation, "Lifecycle retry must never retain stale allocation identities"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && initializer == 0 && trace.live.empty(), "Release retried notifier lifecycle generation"); +} +static void CheckStandaloneFree() { + LifecycleTrace trace; LifecycleAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; trace.owner = &initializer; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize standalone free lifecycle contract"); + void* memory = _ZN3sce4Json6MallocEm(8); + Require(memory != nullptr, "Standalone lifecycle preservation requires a real allocated buffer"); + trace.allocatorAction = AllocatorAction::TerminateFree; + _ZN3sce4Json4FreeEPv(memory); + Require(initializer == 0 && trace.live.empty() && trace.allocatorAction == AllocatorAction::None, + "Standalone free must complete callback-driven termination and release the original buffer"); + const auto generation = trace.generation; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0 && initializer == 1 && trace.live.size() == 12, + "Initialization must remain usable after standalone free-callback termination"); + for (const auto& [pointer, identity] : trace.live) Require(pointer && identity > generation, "Reinitialization must own fresh resource generations after free-callback termination"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && initializer == 0 && trace.live.empty(), "Release standalone free lifecycle retry"); +} +int main() { + CheckRecovery(); + CheckNotice(); + CheckStandaloneFree(); + return 0; +} diff --git a/core/libs/tests/GuestJsonCallbackThreads.cpp b/core/libs/tests/GuestJsonCallbackThreads.cpp new file mode 100644 index 0000000000..3ed4d25394 --- /dev/null +++ b/core/libs/tests/GuestJsonCallbackThreads.cpp @@ -0,0 +1,147 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + static const std::uint32_t words[]{0, 0, 0, 0, 0x05000000}; + return words; +} + +static void Require(bool value, const char* message) { + if (!value) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} + +static std::atomic heapAllocations{0}; + +extern "C" void* APS5_VABI _Znwm_nid_postfix(std::size_t bytes) { + void* memory = std::malloc(bytes == 0 ? 1 : bytes); + if (!memory) throw std::bad_alloc(); + heapAllocations.fetch_add(1, std::memory_order_relaxed); + return memory; +} +extern "C" void APS5_VABI _ZdlPv_nid_postfix(void* memory) { + if (!memory) return; + Require(heapAllocations.fetch_sub(1, std::memory_order_relaxed) != 0, "Legacy heap releases must balance live allocations"); + std::free(memory); +} + +struct Schedule { + std::mutex mutex; + std::condition_variable changed; + bool callbackEntered = false; + bool parserEntered = false; + std::thread::id parser; +}; +static Schedule schedule; + +static void ParserBoundary() { + std::lock_guard lock(schedule.mutex); + if (std::this_thread::get_id() != schedule.parser) return; + schedule.parserEntered = true; + schedule.changed.notify_all(); +} + +extern "C" int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attribute) { + *attribute = reinterpret_cast(new int(0)); + return 0; +} +extern "C" int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attribute, int type) { + Require(type == 2, "Json2 callback mutex must be recursive"); + *reinterpret_cast(*attribute) = type; + return 0; +} +extern "C" int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attribute) { + delete reinterpret_cast(*attribute); + return 0; +} +extern "C" int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr*, const char*) { + *mutex = reinterpret_cast(new std::recursive_mutex); + return 0; +} +extern "C" int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) { + delete reinterpret_cast(*mutex); + return 0; +} +extern "C" int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) { + ParserBoundary(); + reinterpret_cast(*mutex)->lock(); + return 0; +} +extern "C" int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) { + reinterpret_cast(*mutex)->unlock(); + return 0; +} + +static void* APS5_VABI Allocate(void*, std::size_t bytes, void*) { + ParserBoundary(); + return std::malloc(bytes); +} +static void APS5_VABI Free(void*, void* memory, void*) { std::free(memory); } +static void APS5_VABI Notify(void*, int, std::size_t, void*) { Require(false, "Thread regression must not fail allocation"); } +static void* const table[]{nullptr, nullptr, reinterpret_cast(Allocate), reinterpret_cast(Free), reinterpret_cast(Notify)}; +struct Allocator { void* const* vtable = table; }; +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static Value replacement{}; + +static const Value& APS5_VABI Mismatch(ValueType requested, const Value*, const Value*, void*) { + Require(requested == TypeInteger, "Exercise a real legacy mismatch callback"); + { + std::unique_lock lock(schedule.mutex); + schedule.callbackEntered = true; + schedule.changed.notify_all(); + schedule.changed.wait(lock, [] { return schedule.parserEntered; }); + } + Value missing{}; + _ZN3sce4Json5ValueC1Ev(&missing); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&missing) == 0, + "Nested mismatch must reach shared defaults while another thread starts parsing"); + _ZN3sce4Json5ValueD1Ev(&missing); + return replacement; +} + +int main() { + Allocator allocator; + Parameter parameter{&allocator, nullptr, 8192, 2, {}}; + std::uint8_t initializer = 0; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, + "Initialize legacy callback thread regression"); + Value source{}, parsed{}; + _ZN3sce4Json5ValueC1Ev(&source); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&source, "true", 4) == 0, "Create an initialized legacy recovery root"); + _ZN3sce4Json5ValueC1Ev(&parsed); + _ZN3sce4Json5ValueC1El(&replacement, 42); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, Mismatch, nullptr) == 0, + "Install legacy recovery callback"); + std::thread callback([&] { + Require(*_ZNK3sce4Json5Value10getIntegerEv(&source) == 42, "Mismatch recovery must complete with its callback value"); + }); + std::thread parser([&] { + { + std::unique_lock lock(schedule.mutex); + schedule.parser = std::this_thread::get_id(); + schedule.changed.wait(lock, [] { return schedule.callbackEntered; }); + } + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&parsed, "[1,2]", 5) == 0, + "Independent parsing must complete after callback recovery"); + }); + callback.join(); + parser.join(); + Require(parsed.type == TypeArray && _ZNK3sce4Json5Array4sizeEv(_ZNK3sce4Json5Value8getArrayEv(&parsed)) == 2, + "Concurrent callback recovery must preserve parser output"); + _ZN3sce4Json5ValueD1Ev(&source); + _ZN3sce4Json5ValueD1Ev(&parsed); + _ZN3sce4Json5ValueD1Ev(&replacement); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Release legacy callback resources"); + Require(heapAllocations.load(std::memory_order_relaxed) == 0, "Legacy callback threads must release every mocked application-heap allocation"); +} diff --git a/core/libs/tests/GuestJsonCompositeDestruction.cpp b/core/libs/tests/GuestJsonCompositeDestruction.cpp new file mode 100644 index 0000000000..d1484726ff --- /dev/null +++ b/core/libs/tests/GuestJsonCompositeDestruction.cpp @@ -0,0 +1,192 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using NullHandler = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +using MismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +using FloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +using ElementHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); +#if defined(JSON2_DESTRUCTION_LEGACY_SDK) +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + static const std::uint32_t words[]{0, 0, 0, 0, 0x05000000}; + return words; +} +static constexpr bool LegacySdk = true; +static constexpr unsigned ModuleResources = 11; +static constexpr unsigned AuxiliaryBytes = 24; +static constexpr unsigned ShapeCount = 3; +#else +static constexpr bool LegacySdk = false; +static constexpr unsigned ModuleResources = 12; +static constexpr unsigned AuxiliaryBytes = 72; +static constexpr unsigned ShapeCount = 4; +#endif +static constexpr unsigned ModuleFreeCalls = 12; +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct DestructionTrace; +struct DestructionDomain { DestructionTrace* trace; void* receiver; unsigned index; }; +struct DestructionTrace { + std::map live; + std::map module; + std::map source; + std::size_t generation = 0; + std::size_t sourceBase = 0; + std::uint8_t* owner = nullptr; + unsigned target = 0; + unsigned calls = 0; + unsigned terminations = 0; + unsigned nullCalls = 0; + bool reinitialize = false; + bool allocatingReplacement = false; + unsigned replacementDomain = 0; + const Parameter* parameter = nullptr; + std::map replacementModule; + bool observing = false; + std::vector releases; + std::vector releaseDomains; + std::vector moduleReleases; +}; +static void* APS5_VABI AllocateDestruction(void* receiver, std::size_t bytes, void* context) { + const auto& domain = *static_cast(context); + auto& trace = *domain.trace; + Require(receiver == domain.receiver && domain.index == (trace.allocatingReplacement ? trace.replacementDomain : 0u), "Composite allocations must retain their actual allocator receiver and context"); + void* memory = std::malloc(bytes); Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); + Require(trace.live.emplace(memory, ++trace.generation).second, "Composite resources must own distinct live allocation generations"); + return memory; +} +static void ReleaseStorage(DestructionTrace& trace, void* memory) { + Require(trace.live.erase(memory) == 1, "Every composite resource must be reclaimed exactly once by the shared tracking backend"); + std::free(memory); +} +static void APS5_VABI FreeDestruction(void* receiver, void* memory, void* context) { + const auto& domain = *static_cast(context); + auto& trace = *domain.trace; + Require(receiver == domain.receiver, "Composite cleanup must preserve the receiver paired with its supplied callback context"); + if (trace.observing) { + ++trace.calls; + if (!memory) { Require(LegacySdk && domain.index == 0, "Only the measured legacy module cleanup may forward a null free"); ++trace.nullCalls; return; } + const auto identity = trace.live.at(memory); + if (trace.module.contains(memory)) { + Require(trace.module.at(memory) == identity && domain.index == 0, "Nested module termination must retain its original allocation generations"); + trace.moduleReleases.push_back(identity); + } else { + Require(trace.source.contains(memory) && trace.source.at(memory) == identity, "Composite release callbacks must identify their exact original payload generation"); + trace.releases.push_back(identity - trace.sourceBase); trace.releaseDomains.push_back(domain.index); + if (trace.target && trace.releases.size() == trace.target) { + trace.target = 0; + auto expected = trace.live; + for (const auto& [pointer, generation] : trace.module) Require(expected.erase(pointer) == 1, "Callback termination must begin with every original module resource still live"); + Require(_ZN3sce4Json11Initializer9terminateEv(trace.owner) == 0 && *trace.owner == 0 && trace.live == expected, + "Composite free callbacks must retain reference-supported module termination while preserving all remaining source allocations including the current release"); + ++trace.terminations; + if (trace.reinitialize) { + const auto generation = trace.generation; + trace.observing = false; trace.allocatingReplacement = true; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(trace.owner, reinterpret_cast(trace.parameter)) == 0 && *trace.owner == 1, + "The allocator callback may initialize a fresh module before returning its original payload release"); + trace.observing = true; trace.allocatingReplacement = false; + for (const auto& [pointer, identity] : trace.live) if (!expected.contains(pointer) || expected.at(pointer) != identity) { + Require(identity > generation, "Callback-time reinitialization must own a new module resource generation"); + trace.replacementModule.emplace(pointer, identity); + } + Require(trace.replacementModule.size() == ModuleResources, "Callback-time reinitialization must publish the complete SDK-specific module epoch"); + for (const auto& [pointer, identity] : expected) Require(trace.live.at(pointer) == identity, "Reinitialization must preserve every old payload allocation still awaiting release"); + } + } + } + } + if (memory) ReleaseStorage(trace, memory); +} +static void APS5_VABI NotifyDestruction(void*, int, std::size_t, void*) { Require(false, "Healthy composite destruction must not fail allocation"); } +static void* const DestructionVtable[]{nullptr, nullptr, reinterpret_cast(AllocateDestruction), reinterpret_cast(FreeDestruction), reinterpret_cast(NotifyDestruction)}; +struct DestructionAllocator { void* const* vtable = DestructionVtable; }; +static const std::vector ReleaseOrders[]{ + {5, 4, 3}, + {31, 30, 29, 28, 27, 26, 25, 24, 18, 17, 16, 15, 14, 13, 12, 3, 2, 1, 5, 4}, + {9, 8, 7, 6, 26, 25, 24, 23, 22, 21, 20, 13, 12, 11, 10, 3, 2, 1, 5, 4}, + {5, 4, 3, 8, 9, 10, 7, 6}, +}; +static void DestroyAlias(Value& value, unsigned alias) { + if (alias) _ZN3sce4Json5ValueD2Ev(&value); + else _ZN3sce4Json5ValueD1Ev(&value); +} +static void Check(unsigned shape, unsigned target, unsigned alias, bool reinitialize, unsigned domain = 0) { + if (LegacySdk && shape != 0 && target > 18 && !reinitialize) return; + DestructionTrace trace; DestructionAllocator allocator, replacementAllocator; + DestructionDomain first{&trace, &allocator, 0}, second{&trace, domain == 2 ? &replacementAllocator : &allocator, 1}; + Parameter parameter{&allocator, &first, 8192, 2, {}}, replacement{second.receiver, &second, 4096, 2, {}}; + std::uint8_t initializer = 0; trace.owner = &initializer; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0 && initializer == 1 && trace.live.size() == ModuleResources, "Initialize the actual composite-destruction resource epoch"); + trace.module = trace.live; trace.sourceBase = trace.generation; + Value value; std::memset(&value, 0xa5, sizeof(value)); + if (shape == 0 || shape == 3) { + _ZN3sce4Json5ValueC1EPKc(&value, "text"); + if (shape == 3) Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&value, nullptr, nullptr) == 0 && + _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&value, nullptr, nullptr) == 0 && + _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, nullptr, nullptr) == 0 && + _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&value, nullptr, nullptr) == 0, "Admit all modern auxiliary callback records through their real allocation paths"); + } else { + _ZN3sce4Json5ValueC1Ev(&value); + const char* document = shape == 1 ? R"({"s":"text","a":[1]})" : R"(["text",{"s":"more"}])"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, document, std::strlen(document)) == 0, "Construct actual nested object/array ownership before callback destruction"); + } + for (const auto& [pointer, generation] : trace.live) if (!trace.module.contains(pointer)) trace.source.emplace(pointer, generation); + const auto& order = ReleaseOrders[shape]; + Require(trace.source.size() == order.size(), "Each composite payload must own its complete measured source resource epoch"); + void* auxiliary = value.auxiliary; + std::array auxiliaryBytes{}; + if (auxiliary) std::memcpy(auxiliaryBytes.data(), auxiliary, AuxiliaryBytes); + trace.reinitialize = reinitialize; trace.parameter = domain ? &replacement : ¶meter; trace.replacementDomain = unsigned(domain != 0); + const bool terminated = target != 0; + trace.target = target; trace.observing = true; + DestroyAlias(value, alias); + const unsigned released = terminated && !reinitialize ? target : static_cast(order.size()); + Require(trace.releases == std::vector(order.begin(), order.begin() + released), "Composite destruction must preserve the exact native release-generation prefix before callback termination"); + std::vector expectedDomains(released, 0); + if (reinitialize) for (unsigned index = target; index < released; ++index) expectedDomains[index] = trace.replacementDomain; + Require(trace.releaseDomains == expectedDomains, "The already-entered free retains its original domain; subsequent releases must use the replacement allocator/context even for old source allocations"); + auto remaining = trace.source; + for (unsigned index = 0; index < released; ++index) { + bool found = false; + for (auto iterator = remaining.begin(); iterator != remaining.end(); ++iterator) if (iterator->second == trace.sourceBase + order[index]) { remaining.erase(iterator); found = true; break; } + Require(found, "Every observed release must remove exactly its expected original payload generation"); + } + const bool retainedAuxiliary = terminated && !reinitialize && ((shape == 1 || shape == 2) ? target <= 18 : shape == 3 && target <= 3); + Require(value.type == 0 && !value.parent && !value.payload && value.reserved == UINT32_C(0xa5a5a5a5) && value.auxiliary == (retainedAuxiliary ? auxiliary : nullptr), + "Destruction must skip auxiliary cleanup after payload callbacks terminate the module, retaining its original pointer rather than falsely clearing ownership"); + if (retainedAuxiliary) Require(std::memcmp(auxiliary, auxiliaryBytes.data(), AuxiliaryBytes) == 0, "Skipped auxiliary cleanup must preserve the complete original auxiliary bytes and callback/fallback identities"); + Require(trace.terminations == unsigned(terminated) && !trace.target && trace.calls == released + (terminated ? ModuleFreeCalls : 0u) && trace.moduleReleases.size() == (terminated ? ModuleResources : 0u) && trace.nullCalls == (terminated ? ModuleFreeCalls - ModuleResources : 0u) && initializer == unsigned(!terminated || reinitialize) && trace.live == (reinitialize ? trace.replacementModule : terminated ? remaining : trace.module), + "Callback termination must reclaim only the module epoch and released source prefix while preserving the reference's orphaned allocations"); + const auto beforeRepeat = trace.live; const auto calls = trace.calls; + DestroyAlias(value, 1 - alias); + Require(trace.live == beforeRepeat && trace.calls == calls && value.auxiliary == (retainedAuxiliary ? auxiliary : nullptr), "A repeated destructor must preserve inactive-module auxiliary state without attempting another release"); + trace.observing = false; + if (!terminated || reinitialize) Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Ordinary composite destruction must remain fully reclaimable"); + else { + Require(trace.live == remaining, "Fixture reclamation may begin only after the exact orphaned source epoch has been observed"); + for (const auto& [pointer, generation] : remaining) { Require(trace.live.at(pointer) == generation, "Caller-owned fixture cleanup must retain each orphan allocation generation"); ReleaseStorage(trace, pointer); } + } + const auto generation = trace.generation; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0 && initializer == 1 && trace.live.size() == ModuleResources, "Initialization must admit a fresh module after every measured composite-destruction outcome"); + for (const auto& [pointer, identity] : trace.live) Require(pointer && identity > generation, "Composite lifecycle retry must retain no stale module or source resource generations"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && initializer == 0 && trace.live.empty(), "Release the fresh composite lifecycle generation"); +} +int main() { + for (unsigned shape = 0; shape < ShapeCount; ++shape) for (unsigned target = 0; target <= ReleaseOrders[shape].size(); ++target) for (unsigned alias = 0; alias < 2; ++alias) Check(shape, target, alias, false); + for (unsigned shape = 0; shape < ShapeCount; ++shape) for (unsigned target = 1; target <= ReleaseOrders[shape].size(); ++target) for (unsigned alias = 0; alias < 2; ++alias) for (unsigned domain = 0; domain < 3; ++domain) Check(shape, target, alias, true, domain); + return 0; +} diff --git a/core/libs/tests/GuestJsonDecimalRange.cpp b/core/libs/tests/GuestJsonDecimalRange.cpp new file mode 100644 index 0000000000..43a4af5fbf --- /dev/null +++ b/core/libs/tests/GuestJsonDecimalRange.cpp @@ -0,0 +1,101 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using LookupCallback = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ArrayTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::set headers; +}; +static void* APS5_VABI AllocateArray(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI FreeArray(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + if (trace.headers.erase(memory)) { + const auto* words = reinterpret_cast(memory); + Require(words[0] == 0xffffffff && words[1] == UINT64_MAX, "Array teardown must expose the native header poison before allocator release"); + } + Require(trace.live.erase(memory) == 1, "Array must release through its owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyArray(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Array notification must identify the failed allocation"); ++trace.notifications; +} +static void* const ArrayVtable[]{nullptr, nullptr, reinterpret_cast(AllocateArray), reinterpret_cast(FreeArray), reinterpret_cast(NotifyArray)}; +struct ArrayAllocator { void* const* vtable = ArrayVtable; }; +int main() { + ArrayTrace trace; ArrayAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize decimal underflow contracts"); + const auto baseline = trace.live.size(); + struct Case { const char* text; std::uint64_t bits; }; + const Case cases[]{ + {"1e309", UINT64_C(0x7ff0000000000000)}, {"-1e309", UINT64_C(0xfff0000000000000)}, + {"1.25e30", UINT64_C(0x462f8def8808b023)}, {"18446744073709551616", UINT64_C(0x43f0000000000000)}, + {"1e-999", 0}, {"-1e-999", UINT64_C(0x8000000000000000)}, + {"2.4703282292062327e-324", 0}, {"-2.4703282292062327e-324", UINT64_C(0x8000000000000000)}, + {"2.4703282292062328e-324", 0}, {"-2.4703282292062328e-324", UINT64_C(0x8000000000000000)}, + {"4.9406564584124654e-324", 1}, {"-4.9406564584124654e-324", UINT64_C(0x8000000000000001)}, + {"2.2250738585072014e-308", UINT64_C(0x0010000000000003)}, + {"1.7976931348623157e308", UINT64_C(0x7feffffffffffff9)}, + }; + for (const auto& test : cases) { + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, test.text, std::strlen(test.text)) == 0, "Decimal range conversion must preserve native admission"); + Require(value.type == 4 && value.payload == test.bits, "Decimal conversion must preserve the reference arithmetic, subnormal bits and the sign of underflowed zero"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Underflow parsing must release token, value and auxiliary ownership"); + } + for (const auto& test : {std::pair{"[-1e309]", "[-inf]"}, std::pair{"{\"a\":[1,1e400]}", "{\"a\":[1,inf]}"}}) { + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + String output{}; _ZN3sce4Json6StringC1EPKc(&output, ""); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, test.first, std::strlen(test.first)) == 0 && _ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0 && std::strcmp(_ZNK3sce4Json6String5c_strEv(&output), test.second) == 0, "Nested decimal overflow must preserve the reference collection and real-conversion behavior"); + _ZN3sce4Json6StringD1Ev(&output); _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Nested overflow must release all token, collection and auxiliary storage"); + } + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Terminate decimal range allocation context"); + for (int mode : {0, 1}) { + parameter.mode = mode; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize overflow conversion settings"); + const auto resources = trace.live.size(); + for (const char* text : {"1e309", "-1e309"}) { + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, text, std::strlen(text)) == 0, "Overflow conversion must honor the configured special-float mode"); + if (mode == 0) Require(value.type == 5 && value.payload && std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(value.payload)), text[0] == '-' ? "-inf" : "inf") == 0, "String mode must convert overflowing decimal values through the normal real setter"); + else Require(value.type == 0, "Null mode must convert overflowing decimal values to null"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == resources, "Overflow conversion must release admitted value and auxiliary storage"); + } + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Terminate overflow conversion settings"); + } + return 0; +} diff --git a/core/libs/tests/GuestJsonElementRecovery.cpp b/core/libs/tests/GuestJsonElementRecovery.cpp new file mode 100644 index 0000000000..1977bcf2fb --- /dev/null +++ b/core/libs/tests/GuestJsonElementRecovery.cpp @@ -0,0 +1,456 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using Handler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct Context { + int calls = 0; + unsigned error = 0; + std::string key; + const String* keyPointer = nullptr; + std::size_t index = 0; + bool indexed = false; + const Value* parent = nullptr; + const Value* value = nullptr; + bool reenter = false; + const Value* fallback = nullptr; +}; +static void APS5_VABI Recover(int error, const String* key, const std::size_t* index, + const Value* parent, const Value* value, void* context) { + auto& trace = *static_cast(context); + ++trace.calls; + trace.error = static_cast(error); + trace.key = key ? _ZNK3sce4Json6String5c_strEv(key) : ""; + trace.keyPointer = key; + trace.indexed = index != nullptr; + trace.index = index ? *index : 0; + trace.parent = parent; + trace.value = value; + if (trace.reenter) { + trace.fallback = _ZNK3sce4Json5ValueixEPKc(value, "retry"); + _ZN3sce4Json5Value3setEl(const_cast(trace.fallback), 77); + } +} +static void Event(const Context& trace, int calls, unsigned error, const Value* value, const Value* parent) { + Require(trace.calls == calls && trace.error == error && trace.value == value && trace.parent == parent, + "Recovery must receive the exact error, source value, parent and context"); +} +struct PartialElementTrace { + std::map live; + std::map generations; + std::vector requests; + unsigned nextGeneration = 0; + int budget = -1; + unsigned notifications = 0; + unsigned calls = 0; + const Value* expectedValue = nullptr; + const Value* expectedParent = nullptr; + const Value* expectedFallback = nullptr; + bool reenter = false; + bool assignString = false; + const Value* collectionSource = nullptr; + bool once = false; + unsigned expectedError = 0x80848118; +}; +static void* APS5_VABI AllocatePartialElement(void*, std::size_t size, void* data) { + auto& trace = *static_cast(data); trace.requests.push_back(size); + if (!trace.budget) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(size); + Require(memory && trace.live.emplace(memory, size).second && trace.generations.emplace(memory, ++trace.nextGeneration).second, "Partial metadata requires independent allocation generations"); + std::memset(memory, 0xa5, size); return memory; +} +static void APS5_VABI FreePartialElement(void*, void* memory, void* data) { + if (!memory) return; + auto& trace = *static_cast(data); + Require(trace.live.erase(memory) == 1 && trace.generations.erase(memory) == 1, "Partial recovery metadata must release through its owning allocator exactly once"); + std::free(memory); +} +static void APS5_VABI NotifyPartialElement(void*, int error, std::size_t size, void* data) { + auto& trace = *static_cast(data); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == size, "Failure notices must identify the rejected metadata allocation"); + ++trace.notifications; +} +static void APS5_VABI RecoverPartialElement(int error, const String* key, const std::size_t* index, const Value* parent, const Value* value, void* data) { + auto& trace = *static_cast(data); ++trace.calls; + Require(static_cast(error) == trace.expectedError && !key && index && *index == std::numeric_limits::max() && value == trace.expectedValue && parent == trace.expectedParent, + "Partial recovery must preserve error, all index bits, source, immediate parent and context"); + if (trace.reenter || trace.assignString || trace.collectionSource) { + const auto calls = trace.calls; + Require(_ZNK3sce4Json5Value8getValueEm(value, *index) == trace.expectedFallback && trace.calls == calls, "Reentry must share the fallback while suppressing recursive callbacks"); + if (trace.collectionSource) { + if (trace.collectionSource->type == 6) _ZN3sce4Json5Value3setERKNS0_5ArrayE(const_cast(trace.expectedFallback), reinterpret_cast(trace.collectionSource->payload)); + else _ZN3sce4Json5Value3setERKNS0_6ObjectE(const_cast(trace.expectedFallback), reinterpret_cast(trace.collectionSource->payload)); + } else if (trace.assignString) _ZN3sce4Json5Value3setEPKc(const_cast(trace.expectedFallback), "recovery"); + else _ZN3sce4Json5Value3setEl(const_cast(trace.expectedFallback), 77); + } +} +static void* const PartialElementTable[]{nullptr, nullptr, reinterpret_cast(AllocatePartialElement), reinterpret_cast(FreePartialElement), reinterpret_cast(NotifyPartialElement)}; +struct PartialElementAllocator { void* const* table = PartialElementTable; }; +static void CheckPartialElementFallbacks() { + for (unsigned budget = 0; budget <= 3; ++budget) for (bool reenter : {false, true}) { + PartialElementTrace trace; PartialElementAllocator allocator; Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == 0, "Initialize partial element recovery"); + Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + const auto* moduleFallback = _ZNK3sce4Json5Value8getValueEm(&root, std::numeric_limits::max()); + Require(moduleFallback && trace.live.at(const_cast(moduleFallback)) == 32, "Initial recovery must expose the initialized module fallback"); + const auto requests = trace.requests.size(); trace.budget = static_cast(budget); + const int status = _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, RecoverPartialElement, &trace); + std::vector expected{72}; if (budget) expected.push_back(32); if (budget >= 2) expected.push_back(16); + Require(status == (budget < 3 ? static_cast(0x80848102) : 0) && trace.notifications == unsigned(budget < 3) && + std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected, + "Partial handler admission must preserve exact auxiliary, fallback and record request order"); + void* originalAuxiliary = root.auxiliary; const auto retryRequests = trace.requests.size(); trace.budget = -1; + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, RecoverPartialElement, &trace) == 0, "Independent registration retry must succeed"); + const auto retryExpected = budget == 0 ? std::vector{72, 32, 16} : budget < 3 ? std::vector{16} : std::vector{}; + Require(std::vector(trace.requests.begin() + retryRequests, trace.requests.end()) == retryExpected && (!originalAuxiliary || root.auxiliary == originalAuxiliary), + "Registration retry must reuse admitted metadata without recreating a missing fallback"); + auto* auxiliary = reinterpret_cast(root.auxiliary); auto* fallback = reinterpret_cast(auxiliary[2]); + Require(bool(fallback) == (budget != 1) && auxiliary[5], "Failed fallback admission must remain missing after successful handler retry"); + const auto auxiliaryGeneration = trace.generations.at(auxiliary), recordGeneration = trace.generations.at(auxiliary[5]); + const auto fallbackGeneration = fallback ? trace.generations.at(fallback) : 0; + trace.expectedFallback = fallback ? fallback : moduleFallback; trace.reenter = reenter; + const auto dispatch = [&](const Value* source, const Value* parent, unsigned calls) { + trace.expectedValue = source; trace.expectedParent = parent; + const auto before = trace.requests.size(); const auto notices = trace.notifications; + trace.budget = 0; + Require(_ZNK3sce4Json5Value8getValueEm(source, std::numeric_limits::max()) == trace.expectedFallback && trace.calls == calls && + trace.requests.size() == before && trace.notifications == notices, + "Partial fallback dispatch and callback reentry must preserve identity without allocation or notifications"); + trace.budget = -1; + Require(!reenter || (trace.expectedFallback->type == 2 && trace.expectedFallback->payload == 77), "Callback mutation must remain visible through the returned fallback"); + }; + dispatch(&root, nullptr, 1); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, "[1]", 3) == 0, "Parsing must retain partial root recovery metadata"); + const auto* child = _ZNK3sce4Json5Value8getValueEm(&root, 0); + Require(child && child->parent == &root, "The parsed child must retain its actual parent"); dispatch(child, &root, 2); + Require(static_cast(root.auxiliary) == auxiliary && auxiliary[2] == fallback && trace.generations.at(auxiliary) == auxiliaryGeneration && trace.generations.at(auxiliary[5]) == recordGeneration && + (!fallback || (trace.generations.at(fallback) == fallbackGeneration && fallback->parent == &root)) && + reinterpret_cast(auxiliary[5])[0] == reinterpret_cast(&RecoverPartialElement) && reinterpret_cast(auxiliary[5])[1] == &trace, + "Parsing and dispatch must preserve auxiliary, fallback and handler generations, callback ABI and ownership"); + _ZN3sce4Json5ValueD1Ev(&root); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.live.empty() && trace.generations.empty(), "Every partial admission and retry must reclaim real allocation ownership"); + } +} + +static void CheckElementStringFailures() { + for (bool missingFallback : {false, true}) for (bool nested : {false, true}) for (unsigned budget = 0; budget <= 5; ++budget) { + PartialElementTrace trace; PartialElementAllocator allocator; Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == 0, "Initialize fallback String failure resources"); + Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + const auto* moduleFallback = _ZNK3sce4Json5Value8getValueEm(&root, std::numeric_limits::max()); + if (missingFallback) { + trace.budget = 1; + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, RecoverPartialElement, &trace) == static_cast(0x80848102), "Prepare a root without its auxiliary fallback"); + trace.budget = -1; + } + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, RecoverPartialElement, &trace) == 0 && + _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, "[1]", 3) == 0, "Prepare nested recovery with admitted callback metadata"); + auto* auxiliary = reinterpret_cast(root.auxiliary); + auto* fallback = reinterpret_cast(auxiliary[2]); + Require(bool(fallback) != missingFallback, "Parsing must preserve the intended fallback admission state"); + trace.expectedFallback = fallback ? fallback : moduleFallback; + trace.expectedValue = nested ? _ZNK3sce4Json5Value8getValueEm(&root, 0) : &root; + trace.expectedParent = nested ? &root : nullptr; trace.expectedError = nested ? 0x80848118 : 0x80848116; trace.assignString = true; + const auto generations = trace.generations; + std::map> snapshots; + for (const auto& [pointer, size] : trace.live) { + if (pointer == trace.expectedFallback) continue; + const auto* bytes = reinterpret_cast(pointer); snapshots.emplace(pointer, std::vector{bytes, bytes + size}); + } + const auto requests = trace.requests.size(); const auto notices = trace.notifications; + trace.budget = static_cast(budget); + Require(_ZNK3sce4Json5Value8getValueEm(trace.expectedValue, std::numeric_limits::max()) == trace.expectedFallback && trace.calls == 1, + "Callback String allocation failure must preserve the exact returned fallback and suppress reentry"); + std::vector expected{24}; if (budget) { expected.push_back(9); expected.push_back(8); } if (budget >= 3) expected.push_back(24); if (budget >= 4) expected.push_back(9); + Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected && trace.notifications == notices + (budget == 1 ? 2u : unsigned(budget < 5)), + "Callback String assignment must preserve temporary and destination handle/header/character request order and actual failure notices"); + Require(trace.expectedFallback->type == (budget < 4 ? 0 : 5) && trace.expectedFallback->parent == (missingFallback ? nullptr : &root), + "Failed String assignment must retain the admitted partial payload and fallback owner"); + if (budget >= 4) Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(trace.expectedFallback->payload)), budget == 4 ? "" : "recovery") == 0, + "Character failure must retain an empty String while successful admission exposes complete text"); + trace.budget = -1; const auto retryRequests = trace.requests.size(); + _ZN3sce4Json5Value3setEPKc(const_cast(trace.expectedFallback), "recovery"); + const auto retryExpected = budget < 4 ? std::vector{24, 9, 8, 24, 9} : std::vector{24, 9, 9}; + Require(std::vector(trace.requests.begin() + retryRequests, trace.requests.end()) == retryExpected && trace.expectedFallback->type == 5 && + std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(trace.expectedFallback->payload)), "recovery") == 0, + "Independent retry must reuse admitted String storage and complete only missing payload allocations"); + const auto retryGenerations = trace.generations; const auto repeatedRequests = trace.requests.size(); const auto repeatedNotices = trace.notifications; + trace.budget = 0; + Require(_ZNK3sce4Json5Value8getValueEm(trace.expectedValue, std::numeric_limits::max()) == trace.expectedFallback && trace.calls == 2 && + trace.generations == retryGenerations && trace.requests.size() == repeatedRequests + 1 && trace.requests.back() == 24 && trace.notifications == repeatedNotices + 1, + "Recovery must resume after allocation failure and preserve the completed payload when temporary admission fails"); + trace.budget = -1; + Require(static_cast(root.auxiliary) == auxiliary && auxiliary[2] == fallback && auxiliary[5], "Payload failure and retry must not replace callback metadata or repair a missing fallback"); + for (const auto& [pointer, generation] : generations) Require(trace.generations.at(pointer) == generation, "Payload recovery must preserve all preexisting allocation identities"); + for (const auto& [pointer, bytes] : snapshots) Require(std::memcmp(pointer, bytes.data(), bytes.size()) == 0, "Payload recovery must preserve source, module and callback-record bytes"); + _ZN3sce4Json5ValueD1Ev(&root); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.live.empty() && trace.generations.empty(), "Root and module fallbacks must release their real String payload ownership on teardown"); + } +} + +struct ElementListLink { ElementListLink* previous; ElementListLink* next; Value value; }; +struct ElementTreeLink { + ElementTreeLink* parent; ElementTreeLink* left; ElementTreeLink* right; + const char* key; unsigned length; unsigned capacity; std::uint64_t reserved; + void* publicKey; std::uint64_t padding; Value value; unsigned char color; unsigned char nil; unsigned char tail[6]; +}; +static_assert(sizeof(ElementListLink) == 48 && sizeof(ElementTreeLink) == 104); +static std::string ElementCollectionState(const Value& value, const Value* parent); +static void ElementTreeState(const ElementTreeLink* node, const Value* owner, std::string& output, unsigned depth = 0) { + Require(depth < 8, "Fallback tree traversal must remain bounded"); + if (node->nil) return; + ElementTreeState(node->left, owner, output, depth + 1); + output += std::string(node->key) + "/" + (node->publicKey ? *reinterpret_cast(node->publicKey) : "?") + "=" + ElementCollectionState(node->value, owner) + ";"; + ElementTreeState(node->right, owner, output, depth + 1); +} +static std::string ElementCollectionState(const Value& value, const Value* parent) { + std::string output = std::to_string(value.type) + (value.parent == parent ? "p" : value.parent ? "x" : "n"); + if (!value.type) return output; + if (value.type == 2 || value.type == 3) return output + std::to_string(value.payload); + Require(value.type == 6 || value.type == 7, "Fallback contents must have their admitted scalar or collection type"); + if (!value.payload) return output + "!"; + auto* words = *reinterpret_cast(value.payload); + if (!words) return output + "?"; + output += words[3] == reinterpret_cast(&value) ? "o" : "x"; + output += std::to_string(words[1]) + "["; + if (!words[0]) return output + "missing]"; + if (value.type == 7) { + auto* head = reinterpret_cast(words[0]); ElementTreeState(head->parent, &value, output); + } else { + auto* head = reinterpret_cast(words[0]); unsigned count = 0; + for (auto* node = head->next; node != head; node = node->next) { + Require(++count < 8, "Fallback list traversal must remain bounded"); output += ElementCollectionState(node->value, &value) + ";"; + } + } + return output + "]"; +} +static std::uint64_t ElementRequestOrder(const std::vector& requests, std::size_t begin) { + std::uint64_t hash = UINT64_C(14695981039346656037); + for (auto i = begin; i < requests.size(); ++i) { hash ^= requests[i]; hash *= UINT64_C(1099511628211); } + return hash; +} +struct ElementCollectionCase { + bool object; bool once; unsigned budget; std::size_t requests; std::uint64_t requestOrder; unsigned notices; const char* state; + std::size_t retryRequests; std::uint64_t retryRequestOrder; const char* retryState; +}; +static const ElementCollectionCase ElementCollectionCases[]{ + {0, 0, 0, 1, UINT64_C(12638161911788193143), 1, "0p", 7, UINT64_C(8729915328826252127), "6po2[3p11;3p22;]"}, + {0, 0, 1, 2, UINT64_C(598301484379165397), 1, "0p", 7, UINT64_C(8729915328826252127), "6po2[3p11;3p22;]"}, + {0, 0, 2, 4, UINT64_C(4035778052887317517), 2, "6po0[missing]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 0, 3, 4, UINT64_C(4035778052887317517), 1, "6po0[]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 0, 4, 5, UINT64_C(15412030556972272551), 1, "6po0[]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 0, 5, 7, UINT64_C(8729915328826252127), 2, "6po0[]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 0, 6, 7, UINT64_C(8729915328826252127), 1, "6po1[3p11;]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 0, 7, 7, UINT64_C(8729915328826252127), 0, "6po2[3p11;3p22;]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 1, 0, 1, UINT64_C(12638161911788193143), 1, "0p", 7, UINT64_C(8729915328826252127), "6po2[3p11;3p22;]"}, + {0, 1, 1, 2, UINT64_C(598301484379165397), 1, "0p", 7, UINT64_C(8729915328826252127), "6po2[3p11;3p22;]"}, + {0, 1, 2, 7, UINT64_C(8729915328826252127), 1, "6po2[3p11;3p22;]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 1, 3, 4, UINT64_C(4035778052887317517), 1, "6po0[]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 1, 4, 5, UINT64_C(15412030556972272551), 1, "6po0[]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 1, 5, 7, UINT64_C(8729915328826252127), 1, "6po1[3p22;]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 1, 6, 7, UINT64_C(8729915328826252127), 1, "6po1[3p11;]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {0, 1, 7, 7, UINT64_C(8729915328826252127), 0, "6po2[3p11;3p22;]", 4, UINT64_C(12494627435050714437), "6po2[3p11;3p22;]"}, + {1, 0, 0, 1, UINT64_C(12638161911788193143), 1, "0p", 29, UINT64_C(16556326738605743255), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 1, 2, UINT64_C(598301484379165397), 1, "0p", 29, UINT64_C(16556326738605743255), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 2, 3, UINT64_C(2174132616866993959), 1, "0p", 29, UINT64_C(16556326738605743255), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 3, 4, UINT64_C(3997444679488938213), 1, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 4, 5, UINT64_C(8830793754451562391), 1, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 5, 11, UINT64_C(1847692082414021751), 6, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 6, 13, UINT64_C(11795418786358859533), 7, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 7, 13, UINT64_C(11795418786358859533), 6, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 8, 15, UINT64_C(8198587246079178223), 7, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 9, 15, UINT64_C(8198587246079178223), 6, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 10, 16, UINT64_C(18047407352626022791), 6, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 11, 16, UINT64_C(18047407352626022791), 5, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 12, 17, UINT64_C(72191830074401783), 5, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 13, 17, UINT64_C(72191830074401783), 4, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 14, 18, UINT64_C(8609619750561420695), 4, "7po1[/?=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 15, 19, UINT64_C(7887639270042689613), 4, "7po1[a/?=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 16, 20, UINT64_C(15090866843038583229), 4, "7po1[a/=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 17, 20, UINT64_C(15090866843038583229), 3, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 18, 22, UINT64_C(16504299194552701231), 4, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 19, 22, UINT64_C(16504299194552701231), 3, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 20, 24, UINT64_C(3929529969927096549), 4, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 21, 24, UINT64_C(3929529969927096549), 3, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 22, 25, UINT64_C(4075476546177097205), 3, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 23, 25, UINT64_C(4075476546177097205), 2, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 24, 26, UINT64_C(7916499978844114973), 2, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 25, 26, UINT64_C(7916499978844114973), 1, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 26, 28, UINT64_C(14474737658796973967), 2, "7po2[a/a=3p11;/?=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 27, 28, UINT64_C(14474737658796973967), 1, "7po2[a/a=3p11;b/?=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 28, 29, UINT64_C(16556326738605743255), 1, "7po2[a/a=3p11;b/=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 0, 29, 29, UINT64_C(16556326738605743255), 0, "7po2[a/a=3p11;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 0, 1, UINT64_C(12638161911788193143), 1, "0p", 29, UINT64_C(16556326738605743255), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 1, 2, UINT64_C(598301484379165397), 1, "0p", 29, UINT64_C(16556326738605743255), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 2, 3, UINT64_C(2174132616866993959), 1, "0p", 29, UINT64_C(16556326738605743255), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 3, 4, UINT64_C(3997444679488938213), 1, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 4, 5, UINT64_C(8830793754451562391), 1, "7po0[]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 5, 22, UINT64_C(16085087199939238845), 1, "7po2[a/?=3p11;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 6, 26, UINT64_C(12187800176058535535), 1, "7po2[a/=3p11;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 7, 24, UINT64_C(14200822062155788271), 1, "7po2[a/?=3p11;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 8, 27, UINT64_C(16994698599719519703), 1, "7po2[a/=3p11;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 9, 27, UINT64_C(7401576235132465975), 1, "7po2[/a=3n0;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 10, 28, UINT64_C(11183815068046375183), 1, "7po2[/a=3n0;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 11, 26, UINT64_C(6368825009497694205), 1, "7po2[a/?=3n0;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 12, 28, UINT64_C(9933439070053794599), 1, "7po2[a/=3n0;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 13, 26, UINT64_C(18330066241628227221), 1, "7po1[b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 14, 29, UINT64_C(16556326738605743255), 1, "7po2[/a=3n0;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 15, 28, UINT64_C(9933439070053794599), 1, "7po2[a/?=3n0;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 16, 29, UINT64_C(16556326738605743255), 1, "7po2[a/=3n0;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 17, 22, UINT64_C(235942740017013557), 1, "7po2[a/a=3p11;b/?=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 18, 26, UINT64_C(8706565262351133383), 1, "7po2[a/a=3p11;b/=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 19, 24, UINT64_C(3883726514528165511), 1, "7po2[a/a=3p11;b/?=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 20, 27, UINT64_C(14424272583777125367), 1, "7po2[a/a=3p11;b/=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 21, 27, UINT64_C(9861833381400848727), 1, "7po2[/b=3n0;a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 22, 28, UINT64_C(14449855710655478887), 1, "7po2[/b=3n0;a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 23, 26, UINT64_C(7880037974236572341), 1, "7po2[a/a=3p11;b/?=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 24, 28, UINT64_C(14474737658796973967), 1, "7po2[a/a=3p11;b/=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 25, 26, UINT64_C(7916499978844114973), 1, "7po1[a/a=3p11;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 26, 29, UINT64_C(16556326738605743255), 1, "7po2[a/a=3p11;/b=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 27, 28, UINT64_C(14474737658796973967), 1, "7po2[a/a=3p11;b/?=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 28, 29, UINT64_C(16556326738605743255), 1, "7po2[a/a=3p11;b/=3n0;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, + {1, 1, 29, 29, UINT64_C(16556326738605743255), 0, "7po2[a/a=3p11;b/b=3p22;]", 26, UINT64_C(3004510956633589525), "7po2[a/a=3p11;b/b=3p22;]"}, +}; + +static void AssignElementCollection(PartialElementTrace& trace) { + if (trace.collectionSource->type == 6) _ZN3sce4Json5Value3setERKNS0_5ArrayE(const_cast(trace.expectedFallback), reinterpret_cast(trace.collectionSource->payload)); + else _ZN3sce4Json5Value3setERKNS0_6ObjectE(const_cast(trace.expectedFallback), reinterpret_cast(trace.collectionSource->payload)); +} +static void CheckElementCollectionFailures() { + for (const auto& expected : ElementCollectionCases) for (bool missingFallback : {false, true}) { + PartialElementTrace trace; PartialElementAllocator allocator; Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == 0, "Initialize collection fallback recovery"); + Value root{}, replacement{}; _ZN3sce4Json5ValueC1Ev(&root); _ZN3sce4Json5ValueC1Ev(&replacement); + const auto* moduleFallback = _ZNK3sce4Json5Value8getValueEm(&root, std::numeric_limits::max()); + if (missingFallback) { + trace.budget = 1; + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, RecoverPartialElement, &trace) == static_cast(0x80848102), "Prepare a missing root-owned fallback"); trace.budget = -1; + } + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, RecoverPartialElement, &trace) == 0 && + _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, "[1]", 3) == 0, "Prepare nested callback dispatch"); + const char* text = expected.object ? "{\"a\":11,\"b\":22}" : "[11,22]"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&replacement, text, std::strlen(text)) == 0, "Prepare the independent callback replacement source"); + const Value originalSource = replacement; + auto* auxiliary = reinterpret_cast(root.auxiliary); + trace.expectedFallback = auxiliary[2] ? reinterpret_cast(auxiliary[2]) : moduleFallback; + trace.expectedValue = _ZNK3sce4Json5Value8getValueEm(&root, 0); trace.expectedParent = &root; trace.collectionSource = &replacement; + Require(bool(auxiliary[2]) != missingFallback, "Parsing must retain the intended fallback admission state"); + const Value* fallbackParent = missingFallback ? nullptr : &root; + const auto generations = trace.generations; + std::map> snapshots; + for (const auto& [pointer, size] : trace.live) { + if (pointer == trace.expectedFallback) continue; + const auto* bytes = reinterpret_cast(pointer); snapshots.emplace(pointer, std::vector{bytes, bytes + size}); + } + const auto requests = trace.requests.size(); const auto notices = trace.notifications; + trace.budget = static_cast(expected.budget); trace.once = expected.once; + Require(_ZNK3sce4Json5Value8getValueEm(trace.expectedValue, std::numeric_limits::max()) == trace.expectedFallback && trace.calls == 1, + "Callback collection mutation must preserve fallback identity and suppress recursive recovery"); + trace.budget = -1; trace.once = false; + Require(trace.requests.size() == requests + expected.requests && ElementRequestOrder(trace.requests, requests) == expected.requestOrder && trace.notifications == notices + expected.notices && + ElementCollectionState(*trace.expectedFallback, fallbackParent) == expected.state, + "Fallback collection failures must preserve exact requests/notices, partial values, keys, counts, parent links and header ownership"); + const auto retryRequests = trace.requests.size(); const auto retryNotices = trace.notifications; + AssignElementCollection(trace); + Require(trace.requests.size() == retryRequests + expected.retryRequests && ElementRequestOrder(trace.requests, retryRequests) == expected.retryRequestOrder && trace.notifications == retryNotices && + ElementCollectionState(*trace.expectedFallback, fallbackParent) == expected.retryState, + "Independent fallback retry must retain metadata and publish the complete replacement with the reference allocation protocol"); + const auto handle = trace.expectedFallback->payload; + Require(_ZNK3sce4Json5Value8getValueEm(trace.expectedValue, std::numeric_limits::max()) == trace.expectedFallback && trace.calls == 2 && + trace.expectedFallback->payload == handle && ElementCollectionState(*trace.expectedFallback, fallbackParent) == expected.retryState, + "Recovery must resume after failed allocation and retain its completed collection handle"); + Require(static_cast(root.auxiliary) == auxiliary && bool(auxiliary[2]) != missingFallback && reinterpret_cast(auxiliary[5])[0] == reinterpret_cast(&RecoverPartialElement) && + reinterpret_cast(auxiliary[5])[1] == &trace && std::memcmp(&replacement, &originalSource, sizeof(replacement)) == 0, + "Collection recovery must preserve the registered callback, context, source Value and missing-fallback state"); + for (const auto& [pointer, generation] : generations) Require(trace.generations.at(pointer) == generation, "Collection recovery must retain every preexisting allocation generation"); + for (const auto& [pointer, bytes] : snapshots) Require(std::memcmp(pointer, bytes.data(), bytes.size()) == 0, "Collection recovery must preserve source, module and callback metadata bytes"); + _ZN3sce4Json5ValueD1Ev(&replacement); _ZN3sce4Json5ValueD1Ev(&root); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.live.empty() && trace.generations.empty(), "All source, partial/retried fallback and module resources must be reclaimed"); + } +} + +int main() { + Value root{}; + _ZN3sce4Json5ValueC1Ev(&root); + Context trace; + Require(static_cast(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, Recover, &trace)) == 0x80848110, + "Registration before initialization must report not initialized"); + JsonTest::Allocator allocator; + Parameter parameter{&allocator, nullptr, 0, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize element recovery"); + const char text[] = "{\"list\":[1],\"number\":2}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0, "Parse recovery tree"); + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, Recover, &trace) == 0, + "Register root recovery"); + const auto* list = _ZNK3sce4Json5ValueixEPKc(&root, "list"); + const auto* number = _ZNK3sce4Json5ValueixEPKc(&root, "number"); + Require(trace.calls == 0 && list->parent == &root && number->parent == &root, "Successful lookups must bypass failure recovery"); + const auto* fallback = _ZNK3sce4Json5ValueixEPKc(&root, "missing"); + Event(trace, 1, 0x80848115, &root, nullptr); + Require(trace.key == "missing" && !trace.indexed && fallback->type == 0 && fallback->parent == &root, + "A missing object key must return its root-owned fallback value"); + String missing{}; + _ZN3sce4Json6StringC1EPKc(&missing, "missing"); + Require(_ZNK3sce4Json5Value8getValueERKNS0_6StringE(&root, &missing) == fallback, + "String and C-string lookup must share the root fallback"); + Event(trace, 2, 0x80848115, &root, nullptr); + Require(trace.keyPointer == &missing, "The String overload must pass the supplied key object"); + _ZNK3sce4Json5Value8getValueERKNS0_6StringE(list, &missing); + Event(trace, 3, 0x80848117, list, &root); + Require(trace.key == "missing" && !trace.indexed, "Wrong-type key lookup must pass the key and no index"); + Require(_ZNK3sce4Json5Value8getValueEm(list, 1) == fallback, "Nested refusal must use the same root fallback"); + Event(trace, 4, 0x80848116, list, &root); + Require(trace.indexed && trace.index == 1 && !trace.keyPointer, "Bounds failure must pass the complete index and no key"); + _ZNK3sce4Json5Value8getValueEm(number, static_cast(-1)); + Event(trace, 5, 0x80848118, number, &root); + Require(trace.indexed && trace.index == static_cast(-1), "Wrong-type numeric lookup must preserve all index bits"); + Require(_ZNK3sce4Json5Value8getValueEm(&root, 0) == list && _ZN3sce4Json5Value10referValueEm(&root, 1) == number && trace.calls == 5, + "Numeric object lookup must use ordered entries without reporting failure"); + Require(_ZN3sce4Json5Value10referValueEm(&root, 2) == nullptr && trace.calls == 5, + "The nullable mutable lookup must not dispatch failure recovery"); + trace.reenter = true; + Require(_ZNK3sce4Json5ValueixEPKc(list, "missing") == fallback, "Reentrant refusal must keep fallback identity"); + Event(trace, 6, 0x80848117, list, &root); + Require(trace.fallback == fallback && fallback->type == 2 && fallback->payload == 77, + "The callback must be able to update the root fallback without recursively dispatching itself"); + trace.reenter = false; + Require(_ZNK3sce4Json5Value8getValueEm(fallback, 9) == fallback && trace.calls == 6, + "A refusal on the fallback itself must suppress recovery"); + _ZN3sce4Json5Value5clearEv(const_cast(fallback)); + Require(_ZNK3sce4Json5ValueixEPKc(&root, "missing") == fallback && trace.calls == 7 && fallback->type == 0, + "Recovery must resume after reentry and retain fallback ownership"); + Require(static_cast(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(const_cast(list), Recover, &trace)) == 0x80848104, + "An owned child must reject handler registration"); + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, nullptr, nullptr) == 0, + "Unregister element recovery"); + Require(_ZNK3sce4Json5ValueixEPKc(&root, "missing") == fallback && trace.calls == 7, + "Unregistration must preserve fallback identity and stop dispatch"); + _ZN3sce4Json6StringD1Ev(&missing); + _ZN3sce4Json5ValueD1Ev(&root); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate element recovery"); + CheckPartialElementFallbacks(); + CheckElementStringFailures(); + CheckElementCollectionFailures(); + return 0; +} diff --git a/core/libs/tests/GuestJsonFile.cpp b/core/libs/tests/GuestJsonFile.cpp new file mode 100644 index 0000000000..5e007ce546 --- /dev/null +++ b/core/libs/tests/GuestJsonFile.cpp @@ -0,0 +1,127 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct FileTrace { + std::string_view input; + std::size_t position = 0; + std::size_t chunk = 1; + std::size_t bufferSize = 997; + int descriptor = 99; + std::int64_t readError = 0; + int opens = 0; + int reads = 0; + int closes = 0; + int bufferAllocations = 0; + int bufferFrees = 0; + int notifications = 0; + void* buffer = nullptr; + bool failBuffer = false; +}; +static FileTrace* activeFile = nullptr; +extern "C" int APS5_VABI sceKernelOpen(const char* path, int flags, std::uint16_t mode) { + auto& trace = *activeFile; + Require(std::strcmp(path, "input.json") == 0 && flags == 0 && mode == 0555, "File parser must pass its filename and native open arguments"); + Require(trace.buffer != nullptr, "Open must follow successful file-buffer allocation"); + ++trace.opens; + return trace.descriptor; +} +extern "C" std::int64_t APS5_VABI sceKernelRead(int descriptor, void* output, std::size_t bytes) { + auto& trace = *activeFile; + Require(descriptor == trace.descriptor && output == trace.buffer && bytes == trace.bufferSize, "Read must use the configured buffer and descriptor"); + ++trace.reads; + if (trace.readError) return trace.readError; + const auto count = std::min({bytes, trace.chunk, trace.input.size() - trace.position}); + std::memcpy(output, trace.input.data() + trace.position, count); + trace.position += count; + return static_cast(count); +} +extern "C" int APS5_VABI sceKernelClose(int descriptor) { + auto& trace = *activeFile; + Require(descriptor == trace.descriptor && trace.buffer != nullptr, "Close must precede release of the file buffer"); + ++trace.closes; + return 0; +} +static void* APS5_VABI AllocateFile(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + if (bytes == trace.bufferSize) { + ++trace.bufferAllocations; + if (trace.failBuffer) return nullptr; + Require(!trace.buffer, "Only one live file buffer per parse"); + trace.buffer = std::malloc(bytes); + return trace.buffer; + } + return std::malloc(bytes); +} +static void APS5_VABI FreeFile(void*, void* memory, void* context) { + auto& trace = *static_cast(context); + if (memory && memory == trace.buffer) { ++trace.bufferFrees; trace.buffer = nullptr; } + std::free(memory); +} +static void APS5_VABI NotifyFile(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && bytes == trace.bufferSize, "File allocation failure must notify its exact size and OOM status"); + ++trace.notifications; +} +static void* const FileVtable[]{nullptr, nullptr, reinterpret_cast(AllocateFile), reinterpret_cast(FreeFile), reinterpret_cast(NotifyFile)}; +struct FileAllocator { void* const* vtable = FileVtable; }; +static void CheckParse(FileTrace& trace, std::string_view input, int descriptor, std::int64_t error, int expected) { + trace.input = input; trace.position = 0; trace.descriptor = descriptor; trace.readError = error; + const int opens = trace.opens, closes = trace.closes, frees = trace.bufferFrees; + Value value{}; + _ZN3sce4Json5ValueC1Ev(&value); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKc(&value, "input.json") == expected, "File parse must preserve success, syntax, open and read results"); + Require(trace.opens == opens + 1 && trace.closes == closes + (descriptor >= 0 ? 1 : 0) && trace.bufferFrees == frees + 1 && !trace.buffer, + "Every completed file parse must close its owned descriptor and release its buffer exactly once"); + if (!expected) Require(*_ZNK3sce4Json5Value10getIntegerEv(_ZNK3sce4Json5ValueixEPKc(&value, "answer")) == 42, "Short reads must assemble the parsed result"); + _ZN3sce4Json5ValueD1Ev(&value); +} +int main() { + FileTrace trace; activeFile = &trace; + FileAllocator allocator; + Parameter parameter{&allocator, &trace, trace.bufferSize, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + Require(static_cast(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKc(&value, "input.json")) == 0x80848110 && !trace.opens && !trace.bufferAllocations, "Inactive file parser must refuse before allocation or open"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize file parser"); + parameter.bufferSize = 3; + for (std::size_t chunk : {std::size_t{1}, std::size_t{7}, trace.bufferSize}) { + trace.chunk = chunk; + CheckParse(trace, "{\"answer\":42}", 99, 0, 0); + } + CheckParse(trace, "", 99, 0, static_cast(0x80848101)); + CheckParse(trace, "{\"answer\":", 99, 0, static_cast(0x80848101)); + CheckParse(trace, "", static_cast(0x80020002), 0, static_cast(0x80848103)); + CheckParse(trace, "", static_cast(0x8002000d), 0, static_cast(0x8002000d)); + CheckParse(trace, "", -1, 0, static_cast(0x80848110)); + CheckParse(trace, "", 99, static_cast(0x80020005), static_cast(0x80020005)); + trace.failBuffer = true; + const int opens = trace.opens, closes = trace.closes, frees = trace.bufferFrees; + Require(static_cast(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKc(&value, "input.json")) == 0x80848110 && trace.notifications == 1 && trace.opens == opens && trace.closes == closes && trace.bufferFrees == frees, + "Failed file-buffer allocation must notify, avoid open and preserve the native parser status"); + trace.failBuffer = false; + CheckParse(trace, "{\"answer\":42}", 99, 0, 0); + _ZN3sce4Json5ValueD1Ev(&value); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate file parser"); + parameter.bufferSize = 0; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize without a file buffer"); + _ZN3sce4Json5ValueC1Ev(&value); + Require(static_cast(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKc(&value, "input.json")) == 0x80848105 && trace.opens == opens + 1, "Zero configured file-buffer size must refuse before open"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate zero-buffer configuration"); + return 0; +} diff --git a/core/libs/tests/GuestJsonFloatFailure.cpp b/core/libs/tests/GuestJsonFloatFailure.cpp new file mode 100644 index 0000000000..7c43867503 --- /dev/null +++ b/core/libs/tests/GuestJsonFloatFailure.cpp @@ -0,0 +1,195 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +using Handler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +struct Parameter { + void* allocator; + void* userData; + std::size_t bufferSize; + std::int32_t mode; + std::uint32_t reserved[3]; +}; + +#ifndef _WIN32 +extern "C" [[noreturn]] void NotImplemented_nid_no_patch(const char* name) { + std::fprintf(stderr, "Unexpected unsupported Json2 path: %s\n", name); + std::abort(); +} +#endif + +static int allocationBudget = -1; +static std::size_t liveAllocations = 0; + +void* operator new(std::size_t bytes) { + if (allocationBudget == 0) throw std::bad_alloc(); + if (allocationBudget > 0) --allocationBudget; + if (void* pointer = std::malloc(bytes ? bytes : 1)) { + ++liveAllocations; + return pointer; + } + throw std::bad_alloc(); +} + +void operator delete(void* pointer) noexcept { + if (pointer) { + --liveAllocations; + std::free(pointer); + } +} + +void operator delete(void* pointer, std::size_t) noexcept { ::operator delete(pointer); } +void* operator new[](std::size_t bytes) { return ::operator new(bytes); } +void operator delete[](void* pointer) noexcept { ::operator delete(pointer); } +void operator delete[](void* pointer, std::size_t) noexcept { ::operator delete(pointer); } + +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +struct CallbackFailure {}; +struct Context { bool throwAfterConstruction = false; int calls = 0; }; +static constexpr const char* Replacement = "A callback replacement long enough to require separately allocated character storage"; + +static Value* APS5_VABI Recover(Value* result, std::int32_t, double, const Value*, void* context, bool*) { + auto& trace = *static_cast(context); + ++trace.calls; + _ZN3sce4Json5ValueC1EPKc(result, Replacement); + if (trace.throwAfterConstruction) throw CallbackFailure{}; + return result; +} + +static void CheckRecoveryFailures() { + const auto baseline = liveAllocations; + Context context; + bool succeeded = false; + unsigned failures = 0; + for (int budget = 0; budget < 64 && !succeeded; ++budget) { + Value value; + _ZN3sce4Json5ValueC1Ed(&value, 0.125); + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, Recover, &context) == 0, + "Register allocation-failure recovery"); + const auto before = liveAllocations; + allocationBudget = budget; + try { + _ZN3sce4Json5Value3setEd(&value, std::numeric_limits::infinity()); + succeeded = true; + } catch (const std::bad_alloc&) { + ++failures; + } + allocationBudget = -1; + if (succeeded) { + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == 5 && + std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&value)), Replacement) == 0, + "Recovery must remain usable after every earlier allocation failure"); + } else { + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == 4 && + *_ZNK3sce4Json5Value7getRealEv(&value) == 0.125 && liveAllocations == before, + "Failed callback construction or cloning must retain the destination and reclaim temporary ownership"); + } + _ZN3sce4Json5ValueD1Ev(&value); + Require(liveAllocations == baseline, "Recovery teardown must reclaim metadata, replacement and temporary allocations"); + } + Require(succeeded && failures == 0, "Recovery value storage must use the guest allocator without host allocations"); + Value value; + _ZN3sce4Json5ValueC1Ed(&value, 0.125); + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, Recover, &context) == 0, + "Register throwing recovery"); + const auto before = liveAllocations; + context.throwAfterConstruction = true; + bool threw = false; + try { + _ZN3sce4Json5Value3setEd(&value, std::numeric_limits::infinity()); + } catch (const CallbackFailure&) { + threw = true; + } + Require(threw && liveAllocations == before && _ZNK3sce4Json5Value7getTypeEv(&value) == 4 && + *_ZNK3sce4Json5Value7getRealEv(&value) == 0.125, + "A throwing host callback must release its constructed return object and preserve the destination"); + context.throwAfterConstruction = false; + const int calls = context.calls; + _ZN3sce4Json5Value3setEd(&value, std::numeric_limits::infinity()); + Require(context.calls == calls + 1 && _ZNK3sce4Json5Value7getTypeEv(&value) == 5, + "Callback exceptions must release the reentry guard before retry"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(liveAllocations == baseline, "A retried callback must not leak"); +} + +int main() { + JsonTest::Allocator allocator; + Parameter parameter{&allocator, nullptr, 0, 0, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, + "Initialize string-format conversion"); + const auto baseline = liveAllocations; + Value value; + _ZN3sce4Json5ValueC1Ed(&value, 0.125); + const auto before = liveAllocations; + allocationBudget = 0; + bool failed = false; + try { + _ZN3sce4Json5Value3setEd(&value, std::numeric_limits::infinity()); + } catch (const std::bad_alloc&) { + failed = true; + } + allocationBudget = -1; + Require(!failed, "String conversion must use the guest allocator without host allocations"); + Require(liveAllocations == before && _ZNK3sce4Json5Value7getTypeEv(&value) == 5 && + std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&value)), "inf") == 0, + "Conversion must own its string through the guest allocator"); + allocationBudget = 0; + _ZN3sce4Json5Value3setEd(&value, 0.5); + allocationBudget = -1; + Require(*_ZNK3sce4Json5Value7getRealEv(&value) == 0.5, + "Finite assignment must not require a conversion allocation"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(liveAllocations == baseline, "Destroy must reclaim the retained value"); + bool constructed = false; + unsigned constructorFailures = 0; + for (int budget = 0; budget < 64 && !constructed; ++budget) { + Value destination; + std::memset(&destination, 0xa5, sizeof(destination)); + std::array original; + std::memcpy(original.data(), &destination, sizeof(destination)); + allocationBudget = budget; + failed = false; + try { + _ZN3sce4Json5ValueC1Ed(&destination, std::numeric_limits::infinity()); + } catch (const std::bad_alloc&) { + failed = true; + } + allocationBudget = -1; + if (failed) { + ++constructorFailures; + Require(std::memcmp(&destination, original.data(), sizeof(destination)) == 0 && liveAllocations == baseline, + "Failed construction must leave destination storage unchanged and reclaim partial state"); + } else { + constructed = true; + Require(_ZNK3sce4Json5Value7getTypeEv(&destination) == 5, + "Construction must remain usable after earlier host allocation failures"); + _ZN3sce4Json5ValueD1Ev(&destination); + Require(liveAllocations == baseline, "Successful construction must release its host ownership"); + } + } + Require(constructed && constructorFailures == 0, "Float construction must not depend on host allocation"); + _ZN3sce4Json5ValueC1Ed(&value, std::numeric_limits::infinity()); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == 5, "Conversion must remain usable after allocation failure"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(liveAllocations == baseline, "Successful conversion must not leak"); + CheckRecoveryFailures(); + _ZN3sce4Json11InitializerD1Ev(&initializer); +} diff --git a/core/libs/tests/GuestJsonFloatSettings.cpp b/core/libs/tests/GuestJsonFloatSettings.cpp new file mode 100644 index 0000000000..4b5460fa25 --- /dev/null +++ b/core/libs/tests/GuestJsonFloatSettings.cpp @@ -0,0 +1,158 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { + void* allocator; + void* userData; + std::size_t bufferSize; + std::int32_t mode; + std::uint32_t reserved[3]; +}; +static_assert(sizeof(Parameter) == 40); + +static void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static std::string Serialize(Value& value, bool toString = false) { + String output; + _ZN3sce4Json6StringC1Ev(&output); + if (toString) _ZNK3sce4Json5Value8toStringERNS0_6StringE(&value, &output); + else Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0, + "Float serialization must succeed"); + const std::string text = _ZNK3sce4Json6String5c_strEv(&output); + _ZN3sce4Json6StringD1Ev(&output); + return text; +} + +static void CheckMode(int mode) { + JsonTest::Allocator allocator; + int userData = 0; + Parameter parameter{&allocator, &userData, 16384, mode, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, + "Initialize a valid float configuration"); + parameter.mode = (mode + 1) % 3; + const std::array numbers{std::numeric_limits::infinity(), + -std::numeric_limits::infinity(), + std::numeric_limits::quiet_NaN(), + std::bit_cast(std::uint64_t{0x7ff80000cafef00d}), + std::bit_cast(std::uint64_t{0xfff80000a5a55a5a})}; + const std::array tokens{"inf", "-inf", "nan", "nan", "nan"}; + for (unsigned i = 0; i < numbers.size(); ++i) { + Value value; + _ZN3sce4Json5ValueC1Ed(&value, numbers[i]); + const int type = mode == 0 ? 5 : mode == 1 ? 0 : 4; + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == type, + "Non-finite construction must honor the initialization mode"); + const std::string expected = mode == 0 ? '"' + std::string(tokens[i]) + '"' + : mode == 1 ? "null" : tokens[i]; + Require(Serialize(value) == expected, "Non-finite serialization must honor the copied mode"); + Require(Serialize(value, true) == (mode == 0 ? tokens[i] : expected), + "toString must return unquoted string values and serialize other types"); + if (mode == 2) + Require(std::bit_cast(*_ZNK3sce4Json5Value7getRealEv(&value)) == + std::bit_cast(numbers[i]), + "Mode 2 construction and formatting must preserve the real payload"); + _ZN3sce4Json5Value3setEd(&value, 0.125); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == 4 && + *_ZNK3sce4Json5Value7getRealEv(&value) == 0.125 && Serialize(value) == "0.125", + "Finite assignment and formatting must remain unchanged"); + _ZN3sce4Json5Value3setEd(&value, numbers[i]); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == type && Serialize(value) == expected, + "Non-finite assignment must use the same conversion as construction"); + if (mode == 2) + Require(std::bit_cast(*_ZNK3sce4Json5Value7getRealEv(&value)) == + std::bit_cast(numbers[i]), + "Mode 2 assignment must preserve the real payload"); + Array array; + _ZN3sce4Json5ArrayC1Ev(&array); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &value); + Value nested; + _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&nested, &array); + Require(Serialize(nested) == '[' + expected + ']', "Nested values must retain their converted type"); + _ZN3sce4Json5ValueD1Ev(&nested); + _ZN3sce4Json5ArrayD1Ev(&array); + _ZN3sce4Json5ValueD1Ev(&value); + } + Value zero; + _ZN3sce4Json5ValueC1Ed(&zero, -0.0); + Require(std::signbit(*_ZNK3sce4Json5Value7getRealEv(&zero)), "All modes must preserve finite signed zero"); + _ZN3sce4Json5ValueD1Ev(&zero); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && initializer == 0, + "Termination must reset initializer state"); + _ZN3sce4Json11InitializerD1Ev(&initializer); +} + +static void CheckLifecycle() { + JsonTest::Allocator allocator; + Parameter parameter{&allocator, nullptr, 0, 0, {}}; + std::array storage; + storage.fill(0xa5); + _ZN3sce4Json11InitializerC1Ev(storage.data()); + Require(storage[0] == 0, "The initializer constructor must clear its active byte"); + for (unsigned i = 1; i < storage.size(); ++i) + Require(storage[i] == 0xa5, "The initializer constructor must preserve trailing storage"); + Require(static_cast(_ZN3sce4Json11Initializer9terminateEv(storage.data())) == 0x80848110, + "Termination before initialization must report not initialized"); + Require(static_cast(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(storage.data(), nullptr)) == 0x80848120, + "Null InitParameter2 must report invalid argument"); + Require(static_cast(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(storage.data(), nullptr)) == 0x80848120, + "Null legacy parameters must report invalid argument"); + Parameter noAllocator{}; + Require(static_cast(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(storage.data(), reinterpret_cast(&noAllocator))) == 0x80848120, + "InitParameter2 must reject a null allocator"); + Require(static_cast(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(storage.data(), &noAllocator)) == 0x80848102, + "Legacy initialization must report its observed null-allocator error"); + for (int mode : {-1, 3, std::numeric_limits::max()}) { + parameter.mode = mode; + Require(static_cast(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(storage.data(), reinterpret_cast(¶meter))) == 0x80848120, + "Invalid float modes must be rejected"); + Require(storage[0] == 0, "Rejected initialization must preserve the inactive byte"); + } + parameter.mode = 0; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(storage.data(), reinterpret_cast(¶meter)) == 0, + "A valid initialization must work after invalid modes"); + std::uint8_t other = 0; + Require(static_cast(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&other, reinterpret_cast(std::uintptr_t{1}))) == 0x80848111, + "Already initialized must precede parameter access"); + Require(static_cast(_ZN3sce4Json11Initializer9terminateEv(&other)) == 0x80848110, + "An inactive initializer must not terminate active configuration"); + Value value; + _ZN3sce4Json5ValueC1Ed(&value, std::numeric_limits::infinity()); + Require(Serialize(value) == "\"inf\"", "Rejected lifecycle calls must preserve the active mode"); + _ZN3sce4Json5ValueD1Ev(&value); + _ZN3sce4Json11InitializerD1Ev(storage.data()); + Require(storage[0] == 0, "The destructor must terminate an active initializer"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&other, ¶meter) == 0, + "Legacy initialization must work after destructor cleanup"); + _ZN3sce4Json5ValueC1Ed(&value, std::numeric_limits::infinity()); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == 4 && + std::isinf(*_ZNK3sce4Json5Value7getRealEv(&value)), + "Legacy initialization must not apply InitParameter2 conversion rules"); + _ZN3sce4Json5ValueD1Ev(&value); + _ZN3sce4Json11InitializerD1Ev(&other); +} + +int main() { + for (int mode = 0; mode < 3; ++mode) CheckMode(mode); + CheckLifecycle(); + return 0; +} diff --git a/core/libs/tests/GuestJsonGlobalRecovery.cpp b/core/libs/tests/GuestJsonGlobalRecovery.cpp new file mode 100644 index 0000000000..48daf8b55a --- /dev/null +++ b/core/libs/tests/GuestJsonGlobalRecovery.cpp @@ -0,0 +1,103 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using Mismatch = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +using Null = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +using Special = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +using Element = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct CallbackTrace { int calls = 0; const Value* source = nullptr; const Value* parent = nullptr; const Value* replacement = nullptr; bool reenter = false; int function = -1; }; +static const Value& APS5_VABI RecoverMismatch(ValueType requested, const Value* source, const Value* parent, void* context) { + auto& trace = *static_cast(context); + ++trace.calls; trace.source = source; trace.parent = parent; + if (trace.reenter) Require(*_ZNK3sce4Json5Value10getIntegerEv(source) == 0, "Global mismatch reentry must use the typed default"); + Require(requested == 1 || requested == 2, "Preserve requested type"); + return *trace.replacement; +} +static const Value& APS5_VABI RecoverNull(ValueType, const Value*, void* context) { + auto& trace = *static_cast(context); ++trace.calls; return *trace.replacement; +} +static Value* APS5_VABI RecoverFloat(Value* result, std::int32_t function, double, const Value* parent, void* context, bool* error) { + auto& trace = *static_cast(context); + ++trace.calls; trace.function = function; trace.parent = parent; + Require(function == 1 && !error, "Assignment passes its function kind and no error output"); + _ZN3sce4Json5ValueC1El(result, trace.calls + 100); + return result; +} +static void APS5_VABI RecoverElement(int error, const String* key, const std::size_t* index, const Value* parent, const Value* source, void* context) { + auto& trace = *static_cast(context); + ++trace.calls; trace.source = source; trace.parent = parent; + Require(static_cast(error) == 0x80848118 && !key && index && *index == 9, "Global element recovery receives lookup details"); + if (trace.reenter) _ZNK3sce4Json5ValueixEm(source, 9); +} +int main() { + JsonTest::Allocator allocator; + Parameter parameter{&allocator, nullptr, 16384, 2, {}}; + std::uint8_t initializer = 0, inactive = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Value root{}, replacement{}, falseValue{}; + _ZN3sce4Json5ValueC1Ev(&root); + _ZN3sce4Json5ValueC1El(&replacement, 77); + _ZN3sce4Json5ValueC1Ev(&falseValue); + CallbackTrace mismatch{0, nullptr, nullptr, &replacement, true}; + CallbackTrace local{0, nullptr, nullptr, &falseValue, false}; + CallbackTrace null{0, nullptr, nullptr, &replacement, false}; + CallbackTrace special{}, element{}; element.reenter = true; + auto registerMismatch = [&](void* self, Mismatch callback, void* context) { return _ZN3sce4Json11Initializer28setGlobalTypeMismatchHandlerEPFRKNS0_5ValueENS0_9ValueTypeEPS3_S6_PvES7_(self, callback, context); }; + auto registerSpecial = [&](void* self, Special callback, void* context) { return _ZN3sce4Json11Initializer28setGlobalSpecialFloatHandlerEPFKNS0_5ValueENS0_12FunctionTypeEdPS3_PvPbES6_(self, callback, context); }; + auto registerElement = [&](void* self, Element callback, void* context) { return _ZN3sce4Json11Initializer36setGlobalElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKNS0_5ValueES9_PvESA_(self, callback, context); }; + Require(static_cast(registerMismatch(&initializer, RecoverMismatch, &mismatch)) == 0x80848110 && + static_cast(registerSpecial(&initializer, RecoverFloat, &special)) == 0x80848110 && + static_cast(registerElement(&initializer, RecoverElement, &element)) == 0x80848110, "Global handlers require initialization"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize global recovery"); + Require(static_cast(registerMismatch(&inactive, RecoverMismatch, &mismatch)) == 0x80848110 && + static_cast(registerSpecial(&inactive, RecoverFloat, &special)) == 0x80848110 && + static_cast(registerElement(&inactive, RecoverElement, &element)) == 0x80848110, "Inactive initializer cannot register handlers"); + Require(static_cast(registerMismatch(&initializer, nullptr, nullptr)) == 0x80848120 && + static_cast(registerSpecial(&initializer, nullptr, nullptr)) == 0x80848120 && + static_cast(registerElement(&initializer, nullptr, nullptr)) == 0x80848120, "Global handlers reject null callbacks"); + Require(registerMismatch(&initializer, RecoverMismatch, &mismatch) == 0 && registerSpecial(&initializer, RecoverFloat, &special) == 0 && registerElement(&initializer, RecoverElement, &element) == 0, "Register all global handlers"); + Require(static_cast(registerMismatch(&initializer, RecoverMismatch, &local)) == 0x80848112 && + static_cast(registerSpecial(&initializer, RecoverFloat, &local)) == 0x80848112 && + static_cast(registerElement(&initializer, RecoverElement, &local)) == 0x80848112, "Duplicate registration preserves original contexts"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 77 && mismatch.calls == 1 && mismatch.source == &root && !mismatch.parent, "Global mismatch recovers an unowned null value"); + Require(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(&initializer, RecoverNull, &null) == 0, "Register global null callback"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 77 && null.calls == 1 && mismatch.calls == 1, "Global null callback precedes global mismatch for null getters"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, RecoverMismatch, &local) == 0, "Register local mismatch"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&root) == 0 && local.calls == 1 && null.calls == 1 && mismatch.calls == 1, "Local mismatch overrides both global callbacks"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, nullptr, nullptr) == 0, "Unregister local mismatch"); + _ZN3sce4Json5Value3setEd(&root, std::numeric_limits::infinity()); + Require(special.calls == 1 && * _ZNK3sce4Json5Value10getIntegerEv(&root) == 101, "Global float handler supplies assignment replacement"); + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&root, RecoverFloat, &local) == 0, "Register local float handler"); + _ZN3sce4Json5Value3setEd(&root, std::numeric_limits::infinity()); + Require(special.calls == 1 && local.calls == 2, "Local float handler overrides global recovery"); + const auto* fallback = _ZNK3sce4Json5ValueixEm(&root, 9); + Require(element.calls == 1 && element.source == &root && !element.parent && fallback->type == 0, "Global element handler receives failed access and suppresses reentry"); + _ZNK3sce4Json5ValueixEm(fallback, 9); + Require(element.calls == 1, "Fallback access must not recursively invoke global element recovery"); + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, RecoverElement, &local) == 0, "Register local element handler"); + _ZNK3sce4Json5ValueixEm(&root, 9); + Require(element.calls == 1 && local.calls == 3, "Local element handler overrides global recovery"); + _ZN3sce4Json5ValueD1Ev(&root); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate global recovery"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Reinitialize after global recovery"); + Require(registerMismatch(&initializer, RecoverMismatch, &mismatch) == 0 && registerSpecial(&initializer, RecoverFloat, &special) == 0 && registerElement(&initializer, RecoverElement, &element) == 0, "Termination clears all global registration slots"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate second initialization"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&initializer, ¶meter) == 0, "Legacy initialization"); + Require(static_cast(registerSpecial(&initializer, nullptr, nullptr)) == 0x80848113 && static_cast(registerSpecial(&initializer, RecoverFloat, &special)) == 0x80848113, "Legacy initialization rejects global float registration before callback validation"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate legacy initialization"); + _ZN3sce4Json5ValueD1Ev(&replacement); + _ZN3sce4Json5ValueD1Ev(&falseValue); + return 0; +} diff --git a/core/libs/tests/GuestJsonInheritedRecovery.cpp b/core/libs/tests/GuestJsonInheritedRecovery.cpp new file mode 100644 index 0000000000..700e349be6 --- /dev/null +++ b/core/libs/tests/GuestJsonInheritedRecovery.cpp @@ -0,0 +1,202 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static_assert(sizeof(Value) == 32 && sizeof(Parameter) == 40); +using ReadCallback = int (APS5_VABI*)(char&, void*); +using FloatCallback = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct RecoveryAllocationTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::map ids; + std::size_t nextId = 0; + void (*onNotify)(RecoveryAllocationTrace&) = nullptr; + void* notificationContext = nullptr; + bool notifying = false; +}; +static void* APS5_VABI AllocateRecovery(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); trace.ids[memory] = ++trace.nextId; return memory; +} +static void APS5_VABI FreeRecovery(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + Require(trace.ids.count(memory) == 1, "Freed recovery storage must identify a currently live allocation generation"); + trace.ids.erase(memory); + Require(trace.live.erase(memory) == 1, "Recovery storage must release through its owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyRecovery(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Recovery notification must identify the failed allocation"); ++trace.notifications; + if (trace.onNotify && !trace.notifying) { + trace.notifying = true; + trace.onNotify(trace); + trace.notifying = false; + } +} +static void* const RecoveryVtable[]{nullptr, nullptr, reinterpret_cast(AllocateRecovery), reinterpret_cast(FreeRecovery), reinterpret_cast(NotifyRecovery)}; +struct RecoveryAllocator { void* const* vtable = RecoveryVtable; }; +struct RecoveryContext { const Value* replacement; bool object; unsigned calls = 0; int function = -1; bool errorPointer = false; }; +static Value* APS5_VABI RecoverAllocated(Value* result, std::int32_t function, double number, const Value* parent, void* context, bool* error) { + auto& trace = *static_cast(context); + ++trace.calls; trace.function = function; trace.errorPointer = error != nullptr; + Require(number == std::numeric_limits::infinity() && !parent, "Recovery must receive the original non-finite value and immediate parent"); + if (trace.object) _ZN3sce4Json5ValueC1ERKS1_(result, trace.replacement); + else _ZN3sce4Json5ValueC1EPKc(result, "recovery"); + if (error) *error = false; + return result; +} +static std::string Contents(Value& value) { + if (value.type == 0) return "null"; + if (value.type == 4) { Require(value.payload == std::bit_cast(std::numeric_limits::infinity()), "Serialization recovery must preserve the source real payload bits"); return "inf"; } + if (value.type == 5) return _ZNK3sce4Json6String5c_strEv(reinterpret_cast(value.payload)); + String output{}; _ZN3sce4Json6StringC1EPKc(&output, ""); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0, "Partial callback-return objects must remain serializable"); + std::string result = _ZNK3sce4Json6String5c_strEv(&output); + _ZN3sce4Json6StringD1Ev(&output); + return result; +} +struct ReadContext { + RecoveryAllocationTrace* allocations; + bool fail; + Value* assignment; + Value* format; + Value* nested; + int nestedStatus = 0; + int formatStatus = 0; + String* output; + unsigned reads = 0; + unsigned nestedReads = 0; + unsigned hooks = 0; +}; +static int APS5_VABI ReadTrue(char& character, void* data) { + auto& position = *static_cast(data); + if (position >= 4) return static_cast(0x80848101); + character = "true"[position++]; + return 0; +} +static int APS5_VABI ReadNested(char& character, void* data) { + auto& context = *static_cast(data); + if (!context.reads++) { + if (context.fail) { + String failed{}; + context.allocations->budget = 0; context.allocations->once = true; + _ZN3sce4Json6StringC1EPKc(&failed, "x"); + context.allocations->budget = -1; context.allocations->once = false; + _ZN3sce4Json6StringD1Ev(&failed); + } + context.nestedStatus = _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(context.nested, ReadTrue, &context.nestedReads); + _ZN3sce4Json5Value3setEd(context.assignment, std::numeric_limits::infinity()); + context.formatStatus = _ZN3sce4Json5Value9serializeERNS0_6StringE(context.format, context.output); + } + if (context.reads > 4) return static_cast(0x80848101); + character = "true"[context.reads - 1]; + return 0; +} +static void RunNotification(RecoveryAllocationTrace& allocations) { + auto& context = *reinterpret_cast(allocations.notificationContext); + ++context.hooks; + if (context.fail) { allocations.budget = 0; allocations.once = true; } + context.nestedStatus = _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(context.nested, ReadTrue, &context.nestedReads); + allocations.budget = -1; allocations.once = false; + _ZN3sce4Json5Value3setEd(context.assignment, std::numeric_limits::infinity()); + context.formatStatus = _ZN3sce4Json5Value9serializeERNS0_6StringE(context.format, context.output); +} +struct Expected { + const char* document; + bool fail; + int type; + const char* assignment; + const char* output; + std::size_t requests; + std::uint64_t order; + bool notification = false; +}; +static const Expected Cases[]{ + {"\"recovery\"", 0, 5, "recovery", "p\"recovery\"", 22, UINT64_C(0x512fbd7009730792)}, + {"\"recovery\"", 1, 5, "recovery", "p\"recovery\"", 21, UINT64_C(0xb7c45554dbd6d058)}, + {"{\"k\":\"recovery\"}", 0, 7, "{\"k\":\"recovery\"}", "p{\"k\":\"recovery\"}", 143, UINT64_C(0x105ecd3d3b0befa3)}, + {"{\"k\":\"recovery\"}", 1, 7, "{\"k\":\"\"}", "p{\"k\":\"\"}", 137, UINT64_C(0x1c0f8b9f8e134cd2)}, + {"[[\"recovery\"],{\"k\":1}]", 0, 6, "[[\"recovery\"],{\"k\":1}]", "p[[\"recovery\"],{\"k\":1}]", 254, UINT64_C(0xd1da135cb823d2ad)}, + {"[[\"recovery\"],{\"k\":1}]", 1, 6, "[[],{}]", "p[[],{}]", 136, UINT64_C(0xe4d4124493072e0a)}, + {"\"recovery\"", 0, 5, "recovery", "p\"recovery\"", 21, UINT64_C(0x1e6ba634008a1e40), true}, + {"\"recovery\"", 1, 5, "recovery", "p\"recovery\"", 20, UINT64_C(0x4f5af7c5c1ffdcb2), true}, + {"{\"k\":\"recovery\"}", 0, 7, "{\"k\":\"recovery\"}", "p{\"k\":\"recovery\"}", 142, UINT64_C(0x1b1dd3ba293b3ae5), true}, + {"{\"k\":\"recovery\"}", 1, 7, "{\"k\":\"\"}", "p{\"k\":\"\"}", 136, UINT64_C(0x2c958fa5f20dbd84), true}, + {"[[\"recovery\"],{\"k\":1}]", 0, 6, "[[\"recovery\"],{\"k\":1}]", "p[[\"recovery\"],{\"k\":1}]", 253, UINT64_C(0x1e7b03d5c679de77), true}, + {"[[\"recovery\"],{\"k\":1}]", 1, 6, "[[],{}]", "p[[],{}]", 135, UINT64_C(0x2d2754b3359f2448), true}, +}; +int main() { + RecoveryAllocationTrace allocations; RecoveryAllocator allocator; + Parameter parameter{&allocator, &allocations, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize inherited callback recovery contracts"); + const auto baseline = allocations.live; + const auto baselineIds = allocations.ids; + for (const auto& expected : Cases) { + Value replacement{}, assignment{}, format{}, nested{}, outer{}; + _ZN3sce4Json5ValueC1Ev(&replacement); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&replacement, expected.document, std::strlen(expected.document)) == 0, "Construct independent replacement template"); + _ZN3sce4Json5ValueC1Ed(&assignment, 0.125); + _ZN3sce4Json5ValueC1Ed(&format, std::numeric_limits::infinity()); + _ZN3sce4Json5ValueC1Ed(&nested, 0.125); _ZN3sce4Json5ValueC1Ed(&outer, 0.125); + RecoveryContext assignCallback{&replacement, true}, formatCallback{&replacement, true}; + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&assignment, RecoverAllocated, &assignCallback) == 0 && + _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&format, RecoverAllocated, &formatCallback) == 0, "Register assignment and formatting recovery"); + void* assignmentAuxiliary = assignment.auxiliary; + void* formatAuxiliary = format.auxiliary; + const auto assignmentId = allocations.ids.at(assignmentAuxiliary), formatId = allocations.ids.at(formatAuxiliary); + String output{}; _ZN3sce4Json6StringC1EPKc(&output, "p"); + ReadContext context{&allocations, expected.fail, &assignment, &format, &nested, 0, 0, &output}; + const auto first = allocations.requests.size(); const auto notifications = allocations.notifications; + int status; + if (expected.notification) { + allocations.onNotify = RunNotification; allocations.notificationContext = &context; + allocations.budget = 0; allocations.once = true; + status = _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&outer, "true", 4); + allocations.onNotify = nullptr; allocations.notificationContext = nullptr; + allocations.budget = -1; allocations.once = false; + } else status = _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&outer, ReadNested, &context); + const auto end = allocations.requests.size(); + Require(status == (expected.fail || expected.notification ? static_cast(0x80848102) : 0) && outer.type == 1 && (outer.payload & 0xff) == 1 && context.reads == (expected.notification ? 0u : 4u) && context.hooks == (expected.notification ? 1u : 0u), + "A failure raised by the reader must preserve literal completion while reporting the outer operation failure"); + Require(context.nestedStatus == (expected.fail ? static_cast(0x80848102) : 0) && nested.type == (expected.fail && !expected.notification ? 4 : 1) && context.nestedReads == (expected.fail && !expected.notification ? 0u : 4u), "A nested parse must observe the inherited failure without consuming the outer failure state"); + Require(expected.fail && !expected.notification ? nested.payload == std::bit_cast(0.125) : (nested.payload & 0xff) == 1, "Refused nested parsing must preserve its original scalar bits"); + Require(assignment.type == expected.type && Contents(assignment) == expected.assignment && context.formatStatus == 0 && + std::strcmp(_ZNK3sce4Json6String5c_strEv(&output), expected.output) == 0, "Inherited failures must preserve reference callback-return partial collections and serialization"); + std::uint64_t order = UINT64_C(14695981039346656037); + for (auto i = first; i < end; ++i) { order ^= allocations.requests[i]; order *= UINT64_C(1099511628211); } + Require(end - first == expected.requests && order == expected.order && allocations.notifications - notifications == unsigned(expected.fail) + unsigned(expected.notification), "Nested callback recovery must preserve exact allocation request order and notifications"); + Require(assignCallback.calls == 1 && assignCallback.function == 1 && !assignCallback.errorPointer && formatCallback.calls == 1 && formatCallback.function == 0 && formatCallback.errorPointer, "Nested assignment and serialization must preserve their distinct callback ABIs"); + Require(assignment.auxiliary == assignmentAuxiliary && format.auxiliary == formatAuxiliary && allocations.ids.at(assignmentAuxiliary) == assignmentId && allocations.ids.at(formatAuxiliary) == formatId, "Inherited failures must preserve destination metadata identities and generations"); + Require(Contents(replacement) == (replacement.type == 5 ? "recovery" : expected.document), "Nested recovery must preserve its independent source template"); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&nested, "true", 4) == 0 && nested.type == 1 && (nested.payload & 0xff) == 1, "Outermost completion must clear the inherited failure and permit the next operation"); + _ZN3sce4Json6StringD1Ev(&output); + for (auto* value : {&replacement, &assignment, &format, &nested, &outer}) _ZN3sce4Json5ValueD1Ev(value); + Require(allocations.live == baseline && allocations.ids == baselineIds, "Nested operations must release all temporary ownership without replacing module resources"); + } + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && allocations.live.empty() && allocations.ids.empty(), "Release inherited recovery resources"); + return 0; +} diff --git a/core/libs/tests/GuestJsonInitializationExceptions.cpp b/core/libs/tests/GuestJsonInitializationExceptions.cpp new file mode 100644 index 0000000000..133b450829 --- /dev/null +++ b/core/libs/tests/GuestJsonInitializationExceptions.cpp @@ -0,0 +1,110 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +#if defined(JSON2_EXCEPTION_LEGACY_SDK) +static constexpr unsigned ResourceCount = 11; +static constexpr std::size_t HeaderSize = 24; +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + static const std::uint32_t words[]{0, 0, 0, 0, 0x05000000}; + return words; +} +#else +static constexpr unsigned ResourceCount = 12; +static constexpr std::size_t HeaderSize = 32; +#endif +struct Marker { const void* identity; }; +struct InitializationExceptionTrace { + void* allocator = nullptr; + unsigned target = 0; + bool notifier = false; + unsigned notices = 0; + std::vector requests; + std::vector admitted; + std::map live; + unsigned releases = 0; +}; +static void* APS5_VABI AllocateExceptionTracked(void* self, std::size_t size, void* data) { + auto& trace = *static_cast(data); + Require(self == trace.allocator, "Initialization must preserve allocator receiver and context"); + trace.requests.push_back(size); + if (trace.requests.size() == trace.target + 1) { + if (trace.notifier) return nullptr; + throw Marker{&trace}; + } + void* memory = std::malloc(size); + Require(memory && trace.live.emplace(memory, trace.admitted.size()).second, "Every admitted allocation requires independent ownership"); + std::memset(memory, 0xa5, size); + trace.admitted.push_back(memory); + return memory; +} +static void APS5_VABI ReleaseExceptionTracked(void* self, void* memory, void* data) { + auto& trace = *static_cast(data); + Require(self == trace.allocator && (!memory || trace.live.erase(memory) == 1), "Normal termination must release each admitted generation exactly once"); + ++trace.releases; + std::free(memory); +} +static void APS5_VABI NotifyExceptionTracked(void* self, int error, std::size_t size, void* data) { + auto& trace = *static_cast(data); + Require(trace.notifier && self == trace.allocator && error == static_cast(0x80848102) && size == trace.requests.back(), + "Notifier exceptions must retain receiver, context and the rejected allocation request"); + ++trace.notices; + throw Marker{&trace}; +} +static void* const Table[]{nullptr, nullptr, reinterpret_cast(AllocateExceptionTracked), reinterpret_cast(ReleaseExceptionTracked), reinterpret_cast(NotifyExceptionTracked)}; +struct ExceptionAllocator { void* const* table = Table; }; +int main(int argc, char** argv) { + Require(argc == 3, "Each exception schedule requires an independent module process"); + const auto target = static_cast(std::strtoul(argv[1], nullptr, 10)); + Require(target <= ResourceCount, "Allocation schedule must include every initialization position and success"); + ExceptionAllocator allocator; InitializationExceptionTrace trace; trace.allocator = &allocator; trace.target = target; trace.notifier = std::strcmp(argv[2], "notify") == 0; + Require(trace.notifier || std::strcmp(argv[2], "allocate") == 0, "Each schedule requires an allocation or notifier exception"); + Parameter parameter{&allocator, &trace, 16384, 0, {}}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + bool caught = false; + try { + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == (trace.notifier && !target ? static_cast(0x80848102) : 0), "Only an unpublished null context may return OOM without notifying"); + } catch (const Marker& marker) { + Require(marker.identity == &trace, "Allocator exceptions must propagate with their original identity"); + caught = true; + } + Require(caught == (target < ResourceCount && (!trace.notifier || target)) && owner == unsigned(target == ResourceCount) && trace.releases == 0 && trace.live.size() == target && trace.notices == unsigned(trace.notifier && target && target < ResourceCount), + "Thrown initialization callbacks must retain every prior allocation without publishing an active owner or rolling back"); + const std::array sizes{24, 8, 24, 8, HeaderSize, 48, 8, HeaderSize, 104, 32, 8, 8}; + const auto count = target < ResourceCount ? target + 1 : ResourceCount; + Require(trace.requests == std::vector(sizes.begin(), sizes.begin() + count), "Exception schedules must cross the exact initialization resource sequence"); + const auto live = trace.live; + const auto requests = trace.requests; + const int notInitialized = static_cast(0x80848110), alreadyInitialized = static_cast(0x80848111); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == (target < ResourceCount ? notInitialized : 0), "An inactive owner must not reclaim the published partial context"); + trace.target = 1000; + if (target && caught) { + std::uint8_t other = 0; _ZN3sce4Json11InitializerC1Ev(&other); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == alreadyInitialized && + _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&other, reinterpret_cast(¶meter)) == alreadyInitialized && + _ZN3sce4Json11Initializer9terminateEv(&other) == notInitialized && !owner && !other && trace.live == live && trace.requests == requests && !trace.releases, + "A published partial context must reject retries from both owners without changing allocations"); + if (target > 10) Require(scePthreadMutexDestroy(reinterpret_cast(trace.admitted[10])) == 0, "The fixture must reclaim its orphaned host mutex separately"); + for (const auto& [pointer, generation] : trace.live) std::free(pointer); + trace.live.clear(); + } else { + Require(trace.live.empty() && trace.releases == (target == ResourceCount ? 12u : 0u), "Normal termination must reclaim all twelve resources"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == 0 && owner && trace.live.size() == ResourceCount, + "Unpublished exceptions and completed lifecycles must allow a fresh initialization"); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.live.empty(), "Fresh initialization must retain complete teardown"); + } + return 0; +} diff --git a/core/libs/tests/GuestJsonInitializationPublication.cpp b/core/libs/tests/GuestJsonInitializationPublication.cpp new file mode 100644 index 0000000000..b95db7a6c8 --- /dev/null +++ b/core/libs/tests/GuestJsonInitializationPublication.cpp @@ -0,0 +1,230 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using RttiAllocate = void* (APS5_VABI*)(std::size_t, void*); +using RttiFree = void (APS5_VABI*)(void*, void*); +using RttiNotify = void (APS5_VABI*)(int, std::size_t, void*); +struct RttiCallbacks { RttiAllocate allocate; RttiFree free; RttiNotify notify; }; +#if defined(JSON2_PUBLICATION_LEGACY_SDK) +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + static const std::uint32_t words[]{0, 0, 0, 0, 0x05000000}; + return words; +} +static constexpr std::size_t ModuleResources = 11; +static constexpr unsigned KernelResources = 1; +static constexpr std::size_t ModuleFreeCalls = ModuleResources + 1; +#else +static constexpr std::size_t ModuleResources = 12; +static constexpr unsigned KernelResources = 2; +static constexpr std::size_t ModuleFreeCalls = ModuleResources; +#endif +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +static int Initialize(bool legacy, void* self, const Parameter* parameter) { + return legacy ? _ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(self, parameter) : + _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(self, reinterpret_cast(parameter)); +} +enum class Publication { Handoff, CompleteNested, Overwrite }; +struct AllocationIdentity { + std::size_t size; + std::size_t generation; + bool operator==(const AllocationIdentity&) const = default; +}; +struct PublicationTrace { + std::map live; + std::map nested; + std::vector releases; + std::size_t generation = 0; + std::size_t nestedGeneration = 0; + Publication action = Publication::Handoff; + bool armed = false; + bool nestedLegacy = false; + std::uint8_t* nestedOwner = nullptr; + const Parameter* nestedParameter = nullptr; + PublicationTrace* nestedTrace = nullptr; + void* expectedAllocator = nullptr; + std::size_t rttiAllocations[2]{}; + std::size_t rttiReleases[2]{}; + bool deferredConfiguration = false; + Parameter* pendingParameter = nullptr; + const RttiCallbacks* pendingCallbacks = nullptr; + void* pendingContext = nullptr; + void* adapter = nullptr; +}; +struct ContextRecord { void* allocator; void* userData; std::size_t bufferSize; }; +static void CheckContext(const PublicationTrace& trace, std::size_t generation, const Parameter& parameter) { + unsigned found = 0; + for (const auto& [pointer, identity] : trace.live) { + if (identity.generation != generation) continue; + Require(identity.size == sizeof(ContextRecord), "Publication must allocate its actual context record in the owning domain"); + ContextRecord context{}; std::memcpy(&context, pointer, sizeof(context)); + Require(context.allocator == parameter.allocator && context.userData == parameter.context && context.bufferSize == parameter.bufferSize, + "Publication must snapshot the correct allocator receiver, user context and file-buffer setting for each domain"); + ++found; + } + Require(found == 1, "The published allocation context must retain its exact allocation generation"); +} +static void ConfigureRtti(bool legacy, Parameter& parameter, const RttiCallbacks& callbacks, void* context); +static void* AllocateStorage(PublicationTrace& trace, std::size_t size) { + if (trace.armed) { + trace.armed = false; + if (trace.deferredConfiguration) { + ConfigureRtti(trace.nestedLegacy, *trace.pendingParameter, *trace.pendingCallbacks, trace.pendingContext); + Require(trace.pendingParameter->allocator == trace.adapter, "Reentrant RTTI configuration must update the shared adapter before context publication"); + } + Require(Initialize(trace.nestedLegacy, trace.nestedOwner, trace.nestedParameter) == 0 && *trace.nestedOwner == 1 && trace.nestedTrace->live.size() == ModuleResources, + "Nested initialization in the unpublished-context callback must publish its actual complete resource generation"); + CheckContext(*trace.nestedTrace, trace.nestedTrace->generation - (ModuleResources - 1), *trace.nestedParameter); + trace.nested = trace.nestedTrace->live; trace.nestedGeneration = trace.nestedTrace->generation; + if (trace.action == Publication::CompleteNested) + Require(_ZN3sce4Json11Initializer9terminateEv(trace.nestedOwner) == 0 && trace.nestedTrace->live.empty() && *trace.nestedOwner == 0, "The initial allocation callback may complete a nested module lifecycle before returning its allocation"); + if (trace.action == Publication::Handoff) return nullptr; + } + void* memory = std::malloc(size); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, size); + Require(trace.live.emplace(memory, AllocationIdentity{size, ++trace.generation}).second, "Publication resources must have independently owned live generations"); + return memory; +} +static void FreeStorage(PublicationTrace& trace, void* memory) { + if (!memory) return; + trace.releases.push_back(trace.live.at(memory).generation); + Require(trace.live.erase(memory) == 1, "Publication cleanup must release each allocation through its owning allocator exactly once"); + std::free(memory); +} +static void* APS5_VABI AllocatePublication(void* self, std::size_t size, void* context) { + auto& trace = *static_cast(context); + Require(self == trace.expectedAllocator, "Publication allocation must preserve its exact allocator receiver and user context"); + return AllocateStorage(trace, size); +} +static void APS5_VABI FreePublication(void* self, void* memory, void* context) { + auto& trace = *static_cast(context); + Require(self == trace.expectedAllocator, "Publication release must preserve its exact allocator receiver and user context"); + FreeStorage(trace, memory); +} +static void* APS5_VABI AllocateRttiFirst(std::size_t size, void* context) { + auto& trace = *static_cast(context); ++trace.rttiAllocations[0]; + return AllocateStorage(trace, size); +} +static void* APS5_VABI AllocateRttiSecond(std::size_t size, void* context) { + auto& trace = *static_cast(context); ++trace.rttiAllocations[1]; + return AllocateStorage(trace, size); +} +static void APS5_VABI FreeRttiFirst(void* memory, void* context) { + auto& trace = *static_cast(context); ++trace.rttiReleases[0]; + FreeStorage(trace, memory); +} +static void APS5_VABI FreeRttiSecond(void* memory, void* context) { + auto& trace = *static_cast(context); ++trace.rttiReleases[1]; + FreeStorage(trace, memory); +} +static void APS5_VABI NotifyRtti(int, std::size_t, void*) { Require(false, "Failure of the unpublished RTTI context allocation must omit notification"); } +static void ConfigureRtti(bool legacy, Parameter& parameter, const RttiCallbacks& callbacks, void* context) { + if (legacy) _ZN3sce4Json17InitParameterRttiC1EPNS0_14AllocParamRttiEPvm(reinterpret_cast(¶meter), const_cast(&callbacks), context, parameter.bufferSize); + else _ZN3sce4Json18InitParameterRtti216setAllocatorRttiEPNS0_14AllocParamRttiEPv(reinterpret_cast(¶meter), const_cast(&callbacks), context); +} +static void APS5_VABI NotifyPublication(void*, int, std::size_t, void*) { Require(false, "Failure of the unpublished context allocation must omit allocator notification"); } +static void* const PublicationVtable[]{nullptr, nullptr, reinterpret_cast(AllocatePublication), reinterpret_cast(FreePublication), reinterpret_cast(NotifyPublication)}; +struct PublicationAllocator { void* const* vtable = PublicationVtable; }; +static void ReclaimOrphanedFixtureEpoch(PublicationTrace& trace, const std::map& nested, std::size_t generation) { + Require(trace.live == nested && trace.live.size() == ModuleResources, "The fixture may reclaim only the exact orphaned epoch already observed after library teardown"); + unsigned mutexes = 0; + for (const auto& [pointer, identity] : nested) { + if (identity.generation + KernelResources > generation) { + Require(identity.size == sizeof(PthreadMutex) && scePthreadMutexDestroy(reinterpret_cast(pointer)) == 0, + "Fixture cleanup must destroy the actual orphaned kernel mutex resources before reclaiming their handle storage"); + ++mutexes; + } + } + Require(mutexes == KernelResources, "The orphaned epoch must retain its SDK-specific count of actual mutex handles"); + for (const auto& [pointer, identity] : nested) { + Require(trace.live.at(pointer) == identity, "Fixture reclamation must retain the observed orphan allocation generation"); + FreeStorage(trace, pointer); + } + Require(trace.live.empty(), "Caller-owned allocator fixture cleanup must reclaim the measured orphaned epoch"); +} +static void Check(Publication action, bool sharedOwner, bool outerLegacy, bool nestedLegacy, unsigned domain, bool rtti, bool deferredConfiguration) { + PublicationTrace trace, separateTrace; + PublicationAllocator allocator, separateAllocator; + const bool separate = domain != 0; + trace.expectedAllocator = &allocator; + separateTrace.expectedAllocator = domain == 2 ? &separateAllocator : &allocator; + Parameter parameter{&allocator, &trace, 8192, 2, {}}; + Parameter separateParameter{separateTrace.expectedAllocator, &separateTrace, 16384, 2, {}}; + const RttiCallbacks first{AllocateRttiFirst, FreeRttiFirst, NotifyRtti}, second{AllocateRttiSecond, FreeRttiSecond, NotifyRtti}; + if (rtti) { + ConfigureRtti(outerLegacy, parameter, first, &trace); + if (!deferredConfiguration) ConfigureRtti(nestedLegacy, separateParameter, second, &separateTrace); + Require(parameter.allocator && (deferredConfiguration || parameter.allocator == separateParameter.allocator) && parameter.context == &trace && separateParameter.context == &separateTrace, + "Both RTTI parameter families must share the adapter while retaining independent user contexts"); + trace.expectedAllocator = parameter.allocator; separateTrace.expectedAllocator = parameter.allocator; + trace.deferredConfiguration = deferredConfiguration; + trace.pendingParameter = &separateParameter; trace.pendingCallbacks = &second; trace.pendingContext = &separateTrace; trace.adapter = parameter.allocator; + } + std::uint8_t outer = 0, nested = 0; + _ZN3sce4Json11InitializerC1Ev(&outer); _ZN3sce4Json11InitializerC1Ev(&nested); + trace.action = action; trace.armed = true; trace.nestedLegacy = nestedLegacy; + trace.nestedOwner = sharedOwner ? &outer : &nested; + trace.nestedParameter = separate ? &separateParameter : ¶meter; + trace.nestedTrace = separate ? &separateTrace : &trace; + const int status = Initialize(outerLegacy, &outer, ¶meter); + Require(!trace.armed && trace.nested.size() == ModuleResources && trace.nestedGeneration == ModuleResources, "Every publication case must exercise actual first-allocation callback reentry"); + if (action == Publication::Handoff) { + Require(static_cast(status) == 0x80848102 && *trace.nestedOwner == 1 && outer == unsigned(sharedOwner) && trace.nestedTrace->live == trace.nested && trace.releases.empty() && trace.nestedTrace->releases.empty(), + "A failed outer context allocation must retain the nested initializer's ownership, active byte and untouched resource generation"); + Require(trace.live.size() == (separate ? 0u : ModuleResources), "Outer context failure must allocate no resources in a separate outer allocator domain"); + Require(_ZN3sce4Json11Initializer9terminateEv(trace.nestedOwner) == 0 && *trace.nestedOwner == 0 && trace.live.empty() && separateTrace.live.empty(), + "The nested owner must terminate through its original allocator after outer initialization reports OOM"); + } else { + Require(status == 0 && outer == 1 && trace.live.size() == (action == Publication::Overwrite && !separate ? 2 * ModuleResources : ModuleResources), + "Successful outer initialization must preserve the reference resource count in each allocator domain"); + CheckContext(trace, separate ? 1u : trace.nestedGeneration + 1, parameter); + if (action == Publication::CompleteNested) { + Require(nested == 0 && trace.nestedTrace->releases.size() == ModuleResources, "Completed nested teardown must reclaim exactly its original resource generation"); + Require(!separate || (separateTrace.live.empty() && trace.releases.empty()), "A completed nested lifecycle must release only its owning allocator domain"); + for (const auto& [pointer, identity] : trace.live) Require(pointer && identity.generation > (separate ? 0u : trace.nestedGeneration), "Outer initialization after a nested lifecycle must own a fresh generation in its own domain"); + } else if (separate) { + Require(separateTrace.live == trace.nested && separateTrace.releases.empty(), "Outer publication must not reclaim or replace the nested allocator's orphaned generation"); + } + Require(_ZN3sce4Json11Initializer9terminateEv(&outer) == 0 && outer == 0, "Terminate the final published outer module generation"); + if (action == Publication::Overwrite) { + Require(trace.nestedTrace->live == trace.nested && nested == unsigned(!sharedOwner) && trace.releases.size() == ModuleResources && (!separate || (trace.live.empty() && separateTrace.releases.empty())), + "Double successful publication must retain the reference's orphaned first epoch in its original allocator domain"); + const auto released = trace.releases, nestedReleased = trace.nestedTrace->releases; + Require(static_cast(_ZN3sce4Json11Initializer9terminateEv(trace.nestedOwner)) == 0x80848110 && trace.nestedTrace->live == trace.nested && trace.releases == released && trace.nestedTrace->releases == nestedReleased, + "An overwritten nested owner must report missing module state without releasing resources through either allocator domain"); + ReclaimOrphanedFixtureEpoch(*trace.nestedTrace, trace.nested, trace.nestedGeneration); + } else Require(trace.live.empty() && separateTrace.live.empty(), "A completed nested lifecycle must not orphan module resources in either allocator domain"); + } + std::uint8_t retry = 0; _ZN3sce4Json11InitializerC1Ev(&retry); + const auto generation = trace.generation, separateGeneration = separateTrace.generation; + const auto separateReleases = separateTrace.releases; + Require(Initialize(outerLegacy, &retry, ¶meter) == 0 && retry == 1 && trace.live.size() == ModuleResources, "A fresh initializer must work after every measured publication outcome"); + for (const auto& [pointer, identity] : trace.live) Require(pointer && identity.generation > generation, "Publication retry must retain no old fixture allocation generations"); + Require(_ZN3sce4Json11Initializer9terminateEv(&retry) == 0 && retry == 0 && trace.live.empty() && separateTrace.live.empty() && separateTrace.generation == separateGeneration && separateTrace.releases == separateReleases, + "A fresh outer generation must not call into a stale nested allocator domain"); + if (rtti) { + Require(trace.rttiAllocations[0] == unsigned(deferredConfiguration) && !trace.rttiReleases[0] && !separateTrace.rttiAllocations[0] && !separateTrace.rttiReleases[0] && trace.rttiAllocations[1] && trace.rttiReleases[1], + "RTTI module allocation and release must use the most recently configured callback table even through an older parameter object"); + Require(!separate || (separateTrace.rttiAllocations[1] == ModuleResources && separateTrace.rttiReleases[1] == (action == Publication::Overwrite ? 0u : ModuleFreeCalls)), "RTTI callbacks must preserve the nested user context and leave an orphaned epoch unreleased by the library"); + } +} +int main() { + for (auto action : {Publication::Handoff, Publication::CompleteNested, Publication::Overwrite}) + for (bool sharedOwner : {false, true}) for (bool outerLegacy : {false, true}) for (bool nestedLegacy : {false, true}) + for (bool rtti : {false, true}) for (unsigned domain = 0; domain < (rtti ? 2u : 3u); ++domain) + for (unsigned deferred = 0; deferred < (rtti ? 2u : 1u); ++deferred) + Check(action, sharedOwner, outerLegacy, nestedLegacy, domain, rtti, deferred != 0); + return 0; +} diff --git a/core/libs/tests/GuestJsonInitializationRelease.cpp b/core/libs/tests/GuestJsonInitializationRelease.cpp new file mode 100644 index 0000000000..d5cc161267 --- /dev/null +++ b/core/libs/tests/GuestJsonInitializationRelease.cpp @@ -0,0 +1,128 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +#if defined(JSON2_LEGACY_TEST) +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + static const std::uint32_t words[]{0, 0, 0, 0, 0x05000000}; + return words; +} +#endif +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct InitializationTrace { + std::map ids; + std::vector releases; + std::size_t requests = 0; + int budget = -1; + bool once = false; +}; +static void* APS5_VABI AllocateTracked(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + ++trace.requests; + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); + Require(trace.ids.emplace(memory, trace.requests).second, "Each allocation must have an independent live identity"); + return memory; +} +static void APS5_VABI FreeTracked(void*, void* memory, void* context) { + auto& trace = *static_cast(context); + trace.releases.push_back(memory ? trace.ids.at(memory) : 0); + if (!memory) return; + Require(trace.ids.erase(memory) == 1, "Cleanup must release each admitted allocation through its owner exactly once"); + std::free(memory); +} +static void APS5_VABI NotifyTracked(void*, int error, std::size_t, void*) { + Require(static_cast(error) == 0x80848102, "Initialization allocation failures must report the observed error"); +} +static void* const Vtable[]{nullptr, nullptr, reinterpret_cast(AllocateTracked), reinterpret_cast(FreeTracked), reinterpret_cast(NotifyTracked)}; +struct InitializationAllocator { void* const* vtable = Vtable; }; +struct Expected { bool once; int budget; bool admitted; std::vector releases; }; +static const Expected Cases[]{ +#if defined(JSON2_LEGACY_TEST) + {0, 0, false, {}}, + {0, 1, false, {0,0,0,0,0,0,1}}, + {0, 2, false, {0,0,0,2,0,0,1}}, + {0, 3, false, {0,0,0,3,2,0,0,1}}, + {0, 4, false, {0,0,0,3,2,4,0,1}}, + {0, 5, false, {0,0,0,3,2,5,4,0,1}}, + {0, 6, false, {0,0,0,3,2,6,5,4,0,1}}, + {0, 7, false, {0,0,0,3,2,6,5,4,7,1}}, + {0, 8, false, {8,0,0,0,3,2,6,5,4,7,1}}, + {0, 9, false, {0,0,0,3,2,6,5,4,9,8,7,1}}, + {0, 10, false, {0,10,0,3,2,6,5,4,9,8,7,1}}, + {0, 11, true, {0,10,11,3,2,6,5,4,9,8,7,1}}, + {0, 12, true, {0,10,11,3,2,6,5,4,9,8,7,1}}, + {1, 0, false, {}}, + {1, 1, false, {0,9,10,0,5,4,3,8,7,6,1}}, + {1, 2, true, {0,10,11,2,6,5,4,9,8,7,1}}, + {1, 3, false, {0,8,9,3,2,0,7,6,5,1}}, + {1, 4, true, {0,9,10,3,2,4,8,7,6,1}}, + {1, 5, true, {0,10,11,3,2,5,4,9,8,7,1}}, + {1, 6, false, {0,8,9,3,2,6,5,4,0,1}}, + {1, 7, true, {0,9,10,3,2,6,5,4,7,1}}, + {1, 8, true, {8,0,10,11,3,2,6,5,4,7,1}}, + {1, 9, false, {0,0,11,3,2,6,5,4,9,8,7,1}}, + {1, 10, false, {0,10,0,3,2,6,5,4,9,8,7,1}}, + {1, 11, true, {0,10,11,3,2,6,5,4,9,8,7,1}}, + {1, 12, true, {0,10,11,3,2,6,5,4,9,8,7,1}}, +#else + {0, 0, false, {}}, + {0, 1, false, {0,0,0,0,0,0,1}}, + {0, 2, false, {0,0,0,2,0,0,1}}, + {0, 3, false, {0,0,0,3,2,0,0,1}}, + {0, 4, false, {0,0,0,3,2,4,0,1}}, + {0, 5, false, {0,0,0,3,2,5,4,0,1}}, + {0, 6, false, {0,0,0,3,2,6,5,4,0,1}}, + {0, 7, false, {0,0,0,3,2,6,5,4,7,1}}, + {0, 8, false, {8,0,0,0,3,2,6,5,4,7,1}}, + {0, 9, false, {0,0,0,3,2,6,5,4,9,8,7,1}}, + {0, 10, false, {10,0,0,3,2,6,5,4,9,8,7,1}}, + {0, 11, false, {10,0,11,3,2,6,5,4,9,8,7,1}}, + {0, 12, true, {10,12,11,3,2,6,5,4,9,8,7,1}}, + {1, 0, false, {}}, + {1, 1, false, {9,0,10,0,5,4,3,8,7,6,1}}, + {1, 2, true, {10,12,11,2,6,5,4,9,8,7,1}}, + {1, 3, false, {8,0,9,3,2,0,7,6,5,1}}, + {1, 4, true, {9,11,10,3,2,4,8,7,6,1}}, + {1, 5, true, {10,12,11,3,2,5,4,9,8,7,1}}, + {1, 6, false, {8,0,9,3,2,6,5,4,0,1}}, + {1, 7, true, {9,11,10,3,2,6,5,4,7,1}}, + {1, 8, true, {8,10,12,11,3,2,6,5,4,7,1}}, + {1, 9, false, {0,0,11,3,2,6,5,4,9,8,7,1}}, + {1, 10, false, {10,0,0,3,2,6,5,4,9,8,7,1}}, + {1, 11, false, {10,0,11,3,2,6,5,4,9,8,7,1}}, + {1, 12, true, {10,12,11,3,2,6,5,4,9,8,7,1}}, +#endif +}; +int main() { + for (const auto& expected : Cases) { + InitializationTrace trace; trace.budget = expected.budget; trace.once = expected.once; + InitializationAllocator allocator; Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + const int status = _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)); + Require(status == (expected.admitted ? 0 : static_cast(0x80848102)) && initializer == expected.admitted, "Failure admission must preserve the reference status and initializer active byte"); + if (expected.admitted) Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate admitted partial resources"); + Require(trace.ids.empty() && initializer == 0, "Rollback and termination must reclaim every admitted resource"); + Require(trace.releases == expected.releases, "Cleanup must preserve exact allocation-identity order including observable null frees"); + Require(static_cast(_ZN3sce4Json11Initializer9terminateEv(&initializer)) == 0x80848110 && trace.releases == expected.releases, "Repeated termination must report inactive state without allocator effects"); + trace.budget = -1; trace.once = false; trace.requests = 0; trace.releases.clear(); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0 && initializer == 1, "Retry must work on the same initializer after every failure schedule"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && initializer == 0 && trace.ids.empty(), "Retry termination must reclaim complete resource ownership"); + Require(trace.releases == Cases[12].releases, "Retry must restore the full healthy teardown protocol"); + } + return 0; +} diff --git a/core/libs/tests/GuestJsonInternalInitializer.cpp b/core/libs/tests/GuestJsonInternalInitializer.cpp new file mode 100644 index 0000000000..1242f414d9 --- /dev/null +++ b/core/libs/tests/GuestJsonInternalInitializer.cpp @@ -0,0 +1,138 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t fileBuffer; }; +using InfoCallback = void (APS5_VABI*)(int, std::int32_t, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct EntryTrace { + std::map live; + std::vector requests; + std::vector frees; + void* expectedAllocator = nullptr; + bool reject = false; + int budget = -1; + unsigned notices = 0; + std::size_t failedSize = 0; + int notifiedState = 0; + int firstRequestState = 0; + int laterRequestState = 0; + void* owner = nullptr; + unsigned allocationReentries = 0; + unsigned notificationReentries = 0; + unsigned releaseReentries = 0; +}; +static void CheckReentry(EntryTrace& trace) { + std::uint8_t other[]{0, 0x19, 0x28, 0x37}; + const auto active = *reinterpret_cast(trace.owner); + constexpr auto already = static_cast(0x80848111); + constexpr auto missing = static_cast(0x80848110); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(trace.owner, nullptr) == already && + _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(trace.owner, nullptr) == already, + "Allocator callback reentry must reject both duplicate initialization forms before parameter validation"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(other, nullptr) == already && + _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(other, nullptr) == already, + "An allocator callback must not publish a competing initializer while the module context is owned"); + Require(_ZN3sce4Json11Initializer9terminateEv(other) == missing && other[0] == 0 && other[1] == 0x19 && other[2] == 0x28 && other[3] == 0x37, + "An inactive callback-local initializer must not terminate another initializer's resources"); + Require(*reinterpret_cast(trace.owner) == active, "Rejected callback lifecycle operations must preserve the owning initializer active byte"); +} +static void APS5_VABI SdkInfo(int, ValueType, void*) { Require(false, "The reference allocator-info setter must not install or invoke the supplied callback"); } +static int InfoState() { return _ZN3sce4Json11Initializer24setAllocatorInfoCallBackEPFviNS0_9ValueTypeEPvES3_(nullptr, SdkInfo, nullptr); } +static void* APS5_VABI EntryAllocate(void* self, std::size_t size, void* context) { + auto& trace = *static_cast(context); + Require(self == trace.expectedAllocator, "Allocation dispatch must preserve the allocator receiver and user context"); + const int admission = InfoState(); + if (trace.requests.empty()) trace.firstRequestState = admission; + else trace.laterRequestState = admission; + trace.requests.push_back(size); + if (trace.owner && admission == 0) { CheckReentry(trace); ++trace.allocationReentries; } + if (trace.reject || trace.budget == 0) return nullptr; + if (trace.budget > 0) --trace.budget; + void* pointer = std::malloc(size ? size : 1); + Require(pointer != nullptr && trace.live.emplace(pointer, size).second, "Tracking allocator must provide independently owned host storage"); + return pointer; +} +static void APS5_VABI EntryFree(void* self, void* memory, void* context) { + auto& trace = *static_cast(context); + Require(self == trace.expectedAllocator, "Free dispatch must preserve the allocator receiver and user context"); + trace.frees.push_back(memory); + if (trace.owner) { CheckReentry(trace); ++trace.releaseReentries; } + if (memory) { Require(trace.live.erase(memory) == 1, "Every allocation must be released exactly once through its allocator"); std::free(memory); } +} +static void APS5_VABI EntryNotify(void* self, int error, std::size_t size, void* context) { + auto& trace = *static_cast(context); + Require(self == trace.expectedAllocator && static_cast(error) == 0x80848102, "Allocation failure must notify the owning allocator with the Json2 error"); + ++trace.notices; trace.failedSize = size; trace.notifiedState = InfoState(); + if (trace.owner) { CheckReentry(trace); ++trace.notificationReentries; } +} +int main() { + constexpr auto already = static_cast(0x80848111); + constexpr auto missing = static_cast(0x80848110); + constexpr auto invalid = static_cast(0x80848120); + constexpr auto noMemory = static_cast(0x80848102); + void* table[]{nullptr, nullptr, reinterpret_cast(&EntryAllocate), reinterpret_cast(&EntryFree), reinterpret_cast(&EntryNotify)}; + void** allocator = table; + unsigned allocationReentries = 0, notificationReentries = 0, releaseReentries = 0; + for (std::int32_t option : {0, 1, 2, -1}) { + for (int budget = 0; budget <= 12; ++budget) { + EntryTrace trace; trace.expectedAllocator = &allocator; trace.budget = budget; + Parameter parameter{&allocator, &trace, 8192}; + std::uint8_t object[]{0xa5, 0x19, 0x28, 0x37}; + if (budget % 2) _ZN3sce4Json19InternalInitializerC1Ev(object); + else _ZN3sce4Json19InternalInitializerC2Ev(object); + Require(object[0] == 0 && object[1] == 0x19 && object[2] == 0x28 && object[3] == 0x37, "Internal constructors must initialize only the active byte"); + Require(_ZN3sce4Json19InternalInitializer19terminate_staticlibEv(object) == missing, "Inactive internal termination must return not-initialized"); + Require(_ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE(object, nullptr, option) == invalid, "Internal initialization must reject a missing parameter"); + trace.owner = object; + Parameter empty{}; + Require(_ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE(object, &empty, option) == noMemory, "Legacy internal initialization must report a missing allocator as out-of-memory"); + const int result = _ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE(object, ¶meter, option); + if (budget < 12) { + Require(result == noMemory && object[0] == 0 && trace.live.empty(), "Every required-resource allocation failure must roll back ownership and clear the active byte"); + Require((budget == 0) == (trace.notices == 0), "The initial context failure must omit notification while resource failures notify"); + trace.budget = -1; + Require(_ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE(object, ¶meter, option) == 0, "Retry must succeed without reconstructing the failed internal initializer"); + } else Require(result == 0, "Internal initialization must admit every option value after allocating all resources"); + trace.budget = -1; + Require(object[0] == 1 && trace.live.size() == 12, "Internal initialization must own the same twelve required resources as legacy public initialization"); + String text{}; + _ZN3sce4Json6StringC1EPKc(&text, "abc"); + _ZN3sce4Json6String7reserveEm(&text, 5); + Require(_ZNK3sce4Json6String8capacityEv(&text) == 21, "Successful initialization must replace the requested growth option with the compiled SDK policy"); + _ZN3sce4Json6StringD1Ev(&text); + for (std::int32_t rejectedOption : {0, 1, 2, -1}) { + std::uint8_t other = 0; + Require(_ZN3sce4Json19InternalInitializer20initialize_staticlibEPKNS0_13InitParameterENS1_10InitOptionE(&other, nullptr, rejectedOption) == already && other == 0 && object[0] == 1, "Global-context admission must precede parameter validation without taking ownership in another initializer"); + _ZN3sce4Json6StringC1EPKc(&text, "abc"); + _ZN3sce4Json6String7reserveEm(&text, 5); + Require(_ZNK3sce4Json6String8capacityEv(&text) == (rejectedOption == 1 ? 21 : 5), "Even a rejected internal initialization must update the string-growth option"); + _ZN3sce4Json6StringD1Ev(&text); + Require(_ZN3sce4Json19InternalInitializer19terminate_staticlibEv(&other) == missing && trace.live.size() == 12, "An inactive initializer must not terminate another initializer's resources"); + } + if (budget % 3 == 0) _ZN3sce4Json19InternalInitializerD1Ev(object); + else if (budget % 3 == 1) _ZN3sce4Json19InternalInitializerD2Ev(object); + else Require(_ZN3sce4Json19InternalInitializer19terminate_staticlibEv(object) == 0, "Explicit internal termination must succeed for its owning initializer"); + Require(object[0] == 0 && object[1] == 0x19 && object[2] == 0x28 && object[3] == 0x37 && trace.live.empty(), "Every internal teardown entry point must release all resources exactly once and preserve adjacent bytes"); + const auto frees = trace.frees.size(); + _ZN3sce4Json19InternalInitializerD1Ev(object); + _ZN3sce4Json19InternalInitializerD2Ev(object); + Require(trace.frees.size() == frees && InfoState() == missing, "Repeated internal destruction must not dispatch stale frees"); + allocationReentries += trace.allocationReentries; + notificationReentries += trace.notificationReentries; + releaseReentries += trace.releaseReentries; + } + } + Require(allocationReentries && notificationReentries && releaseReentries, "Lifecycle rejection must be exercised through actual allocate, notify and free callbacks"); + return 0; +} diff --git a/core/libs/tests/GuestJsonKernelInitialization.cpp b/core/libs/tests/GuestJsonKernelInitialization.cpp new file mode 100644 index 0000000000..e69dfa63bf --- /dev/null +++ b/core/libs/tests/GuestJsonKernelInitialization.cpp @@ -0,0 +1,140 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +static std::uint32_t Sdk = 0x06000000; +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + static std::uint32_t words[]{0, 0, 0, 0, 0}; + words[4] = Sdk; + return words; +} +struct GuestTrace { std::map live; std::size_t generation = 0; }; +static void* APS5_VABI AllocateTracked(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); + Require(trace.live.emplace(memory, ++trace.generation).second, "Guest allocation identities must be independently live"); + return memory; +} +static void APS5_VABI FreeTracked(void*, void* memory, void* context) { + if (!memory) return; + Require(static_cast(context)->live.erase(memory) == 1, "Each guest resource must return to its allocator exactly once"); + std::free(memory); +} +static void APS5_VABI NotifyTracked(void*, int, std::size_t, void*) { + Require(false, "Host kernel failures must not fabricate guest allocator failures"); +} +static void* const Vtable[]{nullptr, nullptr, reinterpret_cast(AllocateTracked), reinterpret_cast(FreeTracked), reinterpret_cast(NotifyTracked)}; +struct Allocator { void* const* vtable = Vtable; }; +enum class Failure { None, AttributeException, MutexException }; +struct KernelTrace { + Failure failure = Failure::None; + unsigned target = 0; + unsigned attempts = 0; + unsigned attributesCreated = 0; + unsigned attributesDestroyed = 0; + unsigned mutexesCreated = 0; + unsigned mutexesDestroyed = 0; + std::map attributes; + std::map mutexes; +}; +static KernelTrace Kernel; +static bool At(Failure failure) { return Kernel.failure == failure && Kernel.attempts == Kernel.target; } +extern "C" int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attribute) { + ++Kernel.attempts; + if (At(Failure::AttributeException)) throw std::bad_alloc(); + *attribute = reinterpret_cast(new int(0)); + Require(Kernel.attributes.emplace(*attribute, 0).second, "Kernel attributes must have independent live identities"); + ++Kernel.attributesCreated; + return 0; +} +extern "C" int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attribute, int type) { + Require(type == 2 && Kernel.attributes.count(*attribute), "Json2 mutexes must request recursive attributes"); + Kernel.attributes.at(*attribute) = type; + return 0; +} +extern "C" int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attribute) { + Require(Kernel.attributes.erase(*attribute) == 1, "Kernel attributes must be destroyed exactly once"); + delete reinterpret_cast(*attribute); + *attribute = nullptr; + ++Kernel.attributesDestroyed; + return 0; +} +extern "C" int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attribute, const char* name) { + Require(Kernel.attributes.at(*attribute) == 2 && std::strcmp(name, "SceJson_internal") == 0 && !*mutex, "Mutex creation must receive recursive attributes and initialized handle storage"); + if (At(Failure::MutexException)) throw std::bad_alloc(); + *mutex = reinterpret_cast(new std::recursive_mutex); + Require(Kernel.mutexes.emplace(*mutex, 0).second, "Kernel mutexes must have independent live identities"); + ++Kernel.mutexesCreated; + return 0; +} +extern "C" int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) { + Require(Kernel.mutexes.count(*mutex) && Kernel.mutexes.at(*mutex) == 0, "Only a live unlocked kernel mutex may be destroyed"); + Kernel.mutexes.erase(*mutex); + delete reinterpret_cast(*mutex); + *mutex = reinterpret_cast(std::uintptr_t{2}); + ++Kernel.mutexesDestroyed; + return 0; +} +extern "C" int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) { + Require(Kernel.mutexes.count(*mutex), "Only a live kernel mutex may be locked"); + reinterpret_cast(*mutex)->lock(); + ++Kernel.mutexes.at(*mutex); + return 0; +} +extern "C" int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) { + Require(Kernel.mutexes.count(*mutex) && Kernel.mutexes.at(*mutex), "Kernel unlocking must balance an owned lock"); + --Kernel.mutexes.at(*mutex); + reinterpret_cast(*mutex)->unlock(); + return 0; +} +static void Check(Failure failure, unsigned target) { + Kernel = {}; Kernel.failure = failure; Kernel.target = target; + GuestTrace guest; Allocator allocator; Parameter parameter{&allocator, &guest, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + bool failed = false; + try { + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0 && failure == Failure::None, "A failed kernel setup must not report successful initialization"); + } catch (const std::bad_alloc&) { + Require(failure == Failure::AttributeException || failure == Failure::MutexException, "Host allocation exceptions must retain their actual type"); + failed = true; + } + const unsigned mutexCount = Sdk < 0x06000000 ? 1u : 2u; + if (failure == Failure::None) { + Require(!failed && Kernel.mutexes.size() == mutexCount && Kernel.attributes.empty(), "Healthy initialization must own the SDK-specific recursive mutex resources"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate healthy resources"); + } else { + Require(failed && initializer == 0 && Kernel.attempts == target, "Kernel setup failure must leave the initializer inactive at the requested attempt"); + } + Require(guest.live.empty() && Kernel.attributes.empty() && Kernel.mutexes.empty(), "Every kernel setup failure must roll back guest handles, attributes and previously created mutexes"); + Require(Kernel.attributesCreated == Kernel.attributesDestroyed && Kernel.mutexesCreated == Kernel.mutexesDestroyed, "Rollback must balance each kernel resource exactly once"); + const auto generation = guest.generation; + Kernel.failure = Failure::None; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0 && initializer == 1 && guest.generation > generation && Kernel.mutexes.size() == mutexCount, "Retry must admit a fresh resource generation using the same initializer"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && initializer == 0 && guest.live.empty() && Kernel.attributes.empty() && Kernel.mutexes.empty(), "Retry teardown must reclaim complete guest and kernel ownership"); + Require(Kernel.attributesCreated == Kernel.attributesDestroyed && Kernel.mutexesCreated == Kernel.mutexesDestroyed, "Retry must preserve balanced kernel ownership"); +} +int main() { + for (auto sdk : {0x05000000u, 0x06000000u}) { + Sdk = sdk; + Check(Failure::None, 0); + for (auto failure : {Failure::AttributeException, Failure::MutexException}) + for (unsigned target = 1; target <= (sdk < 0x06000000 ? 1u : 2u); ++target) Check(failure, target); + } + return 0; +} diff --git a/core/libs/tests/GuestJsonKeyLookup.cpp b/core/libs/tests/GuestJsonKeyLookup.cpp new file mode 100644 index 0000000000..f556a1eba6 --- /dev/null +++ b/core/libs/tests/GuestJsonKeyLookup.cpp @@ -0,0 +1,99 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using LookupCallback = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ArrayTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::set headers; +}; +static void* APS5_VABI AllocateArray(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI FreeArray(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + if (trace.headers.erase(memory)) { + const auto* words = reinterpret_cast(memory); + Require(words[0] == 0xffffffff && words[1] == UINT64_MAX, "Array teardown must expose the native header poison before allocator release"); + } + Require(trace.live.erase(memory) == 1, "Array must release through its owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyArray(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Array notification must identify the failed allocation"); ++trace.notifications; +} +static void* const ArrayVtable[]{nullptr, nullptr, reinterpret_cast(AllocateArray), reinterpret_cast(FreeArray), reinterpret_cast(NotifyArray)}; +struct ArrayAllocator { void* const* vtable = ArrayVtable; }; +struct LookupTrace { unsigned calls = 0; const String* key = nullptr; const Value* source = nullptr; }; +static void APS5_VABI LookupFailure(int error, const String* key, const std::size_t* index, const Value* parent, const Value* value, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848115 && !index && !parent, "Rejected object lookup must preserve its error, key form and parent"); + ++trace.calls; trace.key = key; trace.source = value; +} +int main() { + ArrayTrace trace; ArrayAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + String missingHeader{}; + Require(_ZNK3sce4Json5ValueixERKNS0_6StringE(&root, &missingHeader) == nullptr, "Before initialization a headerless key must return the absent module fallback without allocation"); + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize keyed lookup contract"); + const char json[] = "{\"a\":7,\"\":9}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, json, sizeof(json) - 1) == 0, "Parse lookup values including an empty key"); + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "a"); + String absent{}; _ZN3sce4Json6StringC1EPKc(&absent, "absent"); + const auto* moduleFallback = _ZNK3sce4Json5ValueixERKNS0_6StringE(&root, &missingHeader); + Require(moduleFallback && moduleFallback->type == 0, "Headerless keys must select the module fallback"); + LookupTrace lookup; + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&root, LookupFailure, &lookup) == 0, "Register object lookup recovery"); + const auto* rootFallback = _ZNK3sce4Json5ValueixERKNS0_6StringE(&root, &absent); + Require(rootFallback && rootFallback != moduleFallback && rootFallback->parent == &root && lookup.calls == 1, "Missing keys must retain root-local fallback identity"); + const auto* actual = _ZNK3sce4Json5ValueixERKNS0_6StringE(&root, &key); + Require(actual && actual->type == 3 && actual->payload == 7 && actual->parent == &root, "Successful const lookup must return the actual owned element"); + const auto baseline = trace.live.size(); + for (bool getter : {false, true}) for (bool once : {false, true}) for (int budget : {0, 1, 2}) { + const auto requests = trace.requests.size(); const auto notices = trace.notifications; const auto calls = lookup.calls; + trace.budget = budget; trace.once = once; + const auto* result = getter ? _ZNK3sce4Json5Value8getValueERKNS0_6StringE(&root, &key) : _ZNK3sce4Json5ValueixERKNS0_6StringE(&root, &key); + trace.budget = -1; trace.once = false; + Require(result == (budget < 2 ? rootFallback : actual) && trace.live.size() == baseline, "Both keyed exports must preserve fallback identity and release temporary iterators"); + Require(trace.requests.size() == requests + 2 && trace.requests[requests] == 8 && trace.requests[requests + 1] == 8, "Every object lookup must allocate find and end iterator states in order"); + const auto failed = budget == 2 ? 0u : once ? 1u : budget == 0 ? 2u : 1u; + Require(trace.notifications == notices + failed && lookup.calls == calls + (budget < 2 ? 1 : 0), "Iterator allocation failure must notify and dispatch missing-element recovery even for an existing key"); + if (budget < 2) Require(lookup.key == &key && lookup.source == &root, "Failure recovery must borrow the exact supplied key and source objects"); + } + const auto requests = trace.requests.size(); const auto calls = lookup.calls; + Require(_ZNK3sce4Json5ValueixERKNS0_6StringE(&root, &missingHeader) == moduleFallback && _ZNK3sce4Json5Value8getValueERKNS0_6StringE(&root, &missingHeader) == moduleFallback && lookup.calls == calls && trace.requests.size() == requests, "A missing string header must bypass iterator allocation, recovery and the root fallback"); + trace.budget = 0; + Require(_ZNK3sce4Json5ValueixEPKc(&root, "a") == moduleFallback && lookup.calls == calls && trace.requests.size() == requests + 1 && trace.requests.back() == 24, "C-string key-header failure must bypass lookup and recovery"); + trace.budget = 1; trace.once = true; + const auto* emptyKey = _ZNK3sce4Json5ValueixEPKc(&root, "a"); + trace.budget = -1; trace.once = false; + Require(emptyKey && emptyKey->type == 3 && emptyKey->payload == 9 && lookup.calls == calls && trace.live.size() == baseline, "One-shot character-allocation failure must admit the constructed empty key and look up that key faithfully"); + _ZN3sce4Json6StringD1Ev(&key); _ZN3sce4Json6StringD1Ev(&absent); _ZN3sce4Json5ValueD1Ev(&root); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Destroy all lookup resources and callbacks"); + return 0; +} diff --git a/core/libs/tests/GuestJsonLegacyHandlers.cpp b/core/libs/tests/GuestJsonLegacyHandlers.cpp new file mode 100644 index 0000000000..2310da6c7c --- /dev/null +++ b/core/libs/tests/GuestJsonLegacyHandlers.cpp @@ -0,0 +1,833 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; }; +using Handler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +using ReadCallback = int (APS5_VABI*)(char&, void*); +using NullHandler = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct LegacyTrace { + std::map guest; + std::map heap; + int budget = -1; + int heapBudget = -1; + bool once = false; + std::vector requests; + std::map guestGenerations; + std::map heapGenerations; + std::size_t generation = 0; + unsigned notifications = 0; + void (*onNotify)(void*) = nullptr; + void* noticeContext = nullptr; +}; +static LegacyTrace* HeapTrace = nullptr; +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + alignas(8) static const std::uint32_t parameters[]{0, 0, 0, 0, 0x05000000, 0}; + return parameters; +} +extern "C" void* APS5_VABI _Znwm_nid_postfix(std::size_t bytes) { + Require(HeapTrace != nullptr, "Heap allocation requires its owning trace"); + if (HeapTrace->heapBudget == 0) throw std::bad_alloc(); + if (HeapTrace->heapBudget > 0) --HeapTrace->heapBudget; + void* memory = std::malloc(bytes); + if (!memory) throw std::bad_alloc(); + std::memset(memory, 0xa5, bytes); + Require(HeapTrace && bytes == 56, "Legacy map nodes must use the actual libc new allocation boundary"); + Require(HeapTrace->heap.emplace(memory, bytes).second && HeapTrace->heapGenerations.emplace(memory, ++HeapTrace->generation).second, "Legacy libc allocations must own distinct live generations"); + return memory; +} +extern "C" void APS5_VABI _ZdlPv_nid_postfix(void* memory) { + Require(HeapTrace && HeapTrace->heap.erase(memory) == 1 && HeapTrace->heapGenerations.erase(memory) == 1, "Legacy map nodes must return through libc delete"); + std::free(memory); +} +static void* APS5_VABI AllocateLegacy(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); + Require(trace.guest.emplace(memory, bytes).second && trace.guestGenerations.emplace(memory, ++trace.generation).second, "Legacy guest allocations must own distinct live generations"); + return memory; +} +static void APS5_VABI FreeLegacy(void*, void* memory, void* context) { + if (!memory) return; + Require(static_cast(context)->guest.erase(memory) == 1 && static_cast(context)->guestGenerations.erase(memory) == 1, "Legacy metadata must return through its owning allocator"); + std::free(memory); +} +static void APS5_VABI NotifyLegacy(void*, int error, std::size_t bytes, void* context) { + Require(static_cast(error) == 0x80848102 && bytes == static_cast(context)->requests.back(), "Legacy allocation failure must identify its actual rejected request"); + auto& trace = *static_cast(context); + ++trace.notifications; + if (const auto action = trace.onNotify) { trace.onNotify = nullptr; action(trace.noticeContext); } +} +static void* const LegacyVtable[]{nullptr, nullptr, reinterpret_cast(AllocateLegacy), reinterpret_cast(FreeLegacy), reinterpret_cast(NotifyLegacy)}; +struct LegacyAllocator { void* const* vtable = LegacyVtable; }; +struct LegacyNode { LegacyNode* parent; LegacyNode* left; LegacyNode* right; const Value* key; Handler callback; void* context; std::uint8_t color; std::uint8_t nil; std::uint8_t reserved[6]; }; +struct LegacyHeader { LegacyNode* head; std::size_t count; std::uint64_t reserved; }; +static_assert(sizeof(LegacyNode) == 56 && sizeof(LegacyHeader) == 24); +struct Context { Value replacement{}; unsigned calls = 0; const Value* source = nullptr; const Value* parent = nullptr; }; +static const Value& APS5_VABI RecoverLegacy(ValueType type, const Value* source, const Value* parent, void* context) { + auto& result = *static_cast(context); + Require(type == 1, "Legacy mismatch dispatch must identify the requested type"); + ++result.calls; result.source = source; result.parent = parent; return result.replacement; +} +static void ParseLegacy(Value& value, const char* text = "9") { + _ZN3sce4Json5ValueC1Ev(&value); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, text, std::strlen(text)) == 0, "Prepare legacy root auxiliary metadata"); +} +static void CheckMap(const LegacyTrace& trace, const std::map& entries) { + const LegacyHeader* header = nullptr; + for (const auto& [memory, size] : trace.guest) { + if (size != 24) continue; + void* head = nullptr; std::memcpy(&head, memory, sizeof(head)); + if (trace.heap.contains(head)) { Require(!header, "Legacy registration must use one shared map header"); header = reinterpret_cast(memory); } + } + Require(header && header->count == entries.size() && header->reserved == UINT64_C(0xa5a5a5a5a5a5a5a5), "Legacy map header must preserve count and reserved bytes"); + auto* head = header->head; + Require(head->nil == 1 && head->color == 0 && head->parent->color == 0, "Legacy sentinel and root must be black"); + auto expected = entries.begin(); + std::function visit = [&](const LegacyNode* node, const LegacyNode* parent) -> unsigned { + if (node == head) return 1; + Require(trace.heap.contains(const_cast(node)) && node->parent == parent && !node->nil && node->color <= 1, "Legacy nodes must retain their libc ownership and parent link"); + Require(!node->color || (!node->left->color && !node->right->color), "Legacy red nodes must have black children"); + const auto left = visit(node->left, node); + Require(expected != entries.end() && node->key == expected->first && node->callback == (expected->second ? RecoverLegacy : nullptr) && node->context == expected->second, "Legacy map entries must preserve sorted keys and callback identity"); + ++expected; + const auto right = visit(node->right, node); + Require(left == right, "Legacy tree paths must preserve black height"); + return left + !node->color; + }; + visit(head->parent, head); + Require(expected == entries.end() && trace.heap.size() == entries.size() + 1, "Legacy map must retain only its actual sentinel and entry nodes"); +} +struct NullContext { const Value* other; Context* replacement; }; +static const Value& APS5_VABI RecoverLegacyNull(ValueType requested, const Value* parent, void* context) { + auto& value = *static_cast(context); + Require(requested == 1 && !parent && *_ZNK3sce4Json5Value10getBooleanEv(value.other), "Legacy null recovery must allow independent mismatch recovery before the reentry guard"); + return value.replacement->replacement; +} +struct LegacySnapshot { void* pointer; std::size_t generation; std::vector bytes; }; +static void CheckPairCopies(LegacyTrace& trace, Context& context) { + const auto guestBaseline = trace.guestGenerations, heapBaseline = trace.heapGenerations; + for (bool text : {false, true}) for (bool once : {false, true}) for (unsigned alias = 0; alias < 2; ++alias) for (int budget = 0; budget <= (text ? 5 : 2); ++budget) { + Value source{}; ParseLegacy(source, text ? "\"abc\"" : "9"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &context) == 0, "Register the actual legacy Pair source map record"); + std::vector sourceAllocations; + for (const auto& [pointer, generation] : trace.guestGenerations) { + if (guestBaseline.contains(pointer)) continue; + const auto* bytes = reinterpret_cast(pointer); + sourceAllocations.push_back({pointer, generation, {bytes, bytes + trace.guest.at(pointer)}}); + } + const LegacyNode* sourceNode = nullptr; + for (const auto& [pointer, size] : trace.heap) { + const auto* node = reinterpret_cast(pointer); + if (!node->nil && node->key == &source) sourceNode = node; + } + Require(sourceNode && sourceNode->callback == RecoverLegacy && sourceNode->context == &context, "Pair copying requires an independently owned source callback record"); + const auto sourceGeneration = trace.heapGenerations.at(const_cast(sourceNode)); + const Value sourceValue = source; + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "key"); + Pair pair; std::memset(&pair, 0xa5, sizeof(pair)); + const auto requests = trace.requests.size(); const auto notices = trace.notifications; + trace.budget = budget; trace.once = once; + if (alias) _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); + else _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); + trace.budget = -1; trace.once = false; + std::vector expected{24}; + if (budget > 0) expected.push_back(4); + if (text) { + expected.push_back(8); + if ((once && budget < 2) || budget >= 3) expected.push_back(24); + if ((once && budget < 2) || budget >= 4) expected.push_back(4); + } + Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected, "Legacy Pair failure must preserve the measured key, Value handle, text header and character request order"); + const unsigned failed = text ? (once ? unsigned(budget < 5) : budget < 2 ? 2u : unsigned(budget < 5)) : unsigned(budget < 2); + Require(trace.notifications == notices + failed, "Legacy Pair copies must notify every admitted allocation failure"); + const int expectedType = text ? ((once && budget < 2) || budget >= 4 ? 5 : 0) : 3; + Require(pair.reserved == UINT64_C(0xa5a5a5a5a5a5a5a5) && !pair.value.parent && !pair.value.auxiliary && pair.value.type == expectedType, "Legacy Pair copies must preserve inactive Pair bytes and retain independent Value ownership"); + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair.key), budget < 2 ? "" : "key") == 0, "Legacy Pair key failures must preserve the measured empty or completed key"); + if (expectedType == 5) Require(pair.value.payload && std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(pair.value.payload)), budget == 4 ? "" : "abc") == 0, "Legacy Pair string failures must preserve the admitted partial text"); + if (!text) Require(pair.value.payload == 9, "Failed Pair key allocation must not suppress scalar copying"); + CheckMap(trace, {{&source, &context}, {&pair.value, &context}}); + const auto calls = context.calls; + Require(_ZNK3sce4Json5Value10getBooleanEv(&pair.value) == reinterpret_cast(&context.replacement.payload) && context.calls == calls + 1 && context.source == &pair.value && !context.parent, + "Every partial legacy Pair must retain its independently copied map callback and exact borrowed replacement identity"); + if (alias) _ZN3sce4Json6Object4PairD2Ev(&pair); + else _ZN3sce4Json6Object4PairD1Ev(&pair); + CheckMap(trace, {{&source, &context}}); + Require(std::memcmp(&source, &sourceValue, sizeof(source)) == 0 && trace.heapGenerations.at(const_cast(sourceNode)) == sourceGeneration && sourceNode->callback == RecoverLegacy && sourceNode->context == &context, + "Pair cleanup must preserve source bytes and its original callback-record generation"); + for (const auto& snapshot : sourceAllocations) Require(trace.guestGenerations.at(snapshot.pointer) == snapshot.generation && std::memcmp(snapshot.pointer, snapshot.bytes.data(), snapshot.bytes.size()) == 0, + "Failed Pair copies must preserve all source guest allocation generations and bytes"); + Require(_ZNK3sce4Json5Value10getBooleanEv(&source) == reinterpret_cast(&context.replacement.payload) && context.source == &source && !context.parent, + "Source callbacks must remain usable after failed Pair copy teardown"); + _ZN3sce4Json6StringD1Ev(&key); _ZN3sce4Json5ValueD1Ev(&source); CheckMap(trace, {}); + Require(trace.guestGenerations == guestBaseline && trace.heapGenerations == heapBaseline, "Legacy Pair copies must reclaim every new generation in both allocator domains"); + } +} +struct LegacyArrayLink { LegacyArrayLink* previous; LegacyArrayLink* next; Value value; }; +struct LegacyArrayHeader { LegacyArrayLink* head; std::size_t count; std::uint64_t reserved; }; +struct LegacyTreeLink { LegacyTreeLink* parent; LegacyTreeLink* left; LegacyTreeLink* right; unsigned char key[24]; Pair pair; unsigned char color; unsigned char nil; unsigned char reserved[6]; }; +struct LegacyTreeHeader { LegacyTreeLink* head; std::size_t count; std::uint64_t reserved; }; +static_assert(sizeof(LegacyArrayHeader) == 24 && sizeof(LegacyTreeHeader) == 24 && sizeof(LegacyTreeLink) == 104); +static void CollectionShape(const Value& value, const Value* parent, std::string& output, std::vector& values); +static void CollectionTreeShape(const LegacyTreeLink* node, const Value* parent, std::string& output, std::vector& values) { + if (node->nil) return; + CollectionTreeShape(node->left, parent, output, values); + output += "k=" + std::string(*reinterpret_cast(node->key)) + "/q=" + _ZNK3sce4Json6String5c_strEv(&node->pair.key) + "/"; + CollectionShape(node->pair.value, parent, output, values); + CollectionTreeShape(node->right, parent, output, values); +} +static void CollectionShape(const Value& value, const Value* parent, std::string& output, std::vector& values) { + values.push_back(&value); + output += std::to_string(value.type) + ":" + std::to_string(value.parent ? value.parent == parent ? 1 : 2 : 0); + if (value.type > 0 && value.type < 5) output += "=" + std::to_string(value.payload); + if (value.type == 5) output += "s=" + std::string(value.payload ? _ZNK3sce4Json6String5c_strEv(reinterpret_cast(value.payload)) : ""); + if (value.type == 6) { + auto* header = value.payload ? *reinterpret_cast(value.payload) : nullptr; + if (!header) { output += "x;"; return; } + output += "a" + std::to_string(header->count) + "["; + if (!header->head) output += "missing"; + else for (auto* node = header->head->next; node != header->head; node = node->next) CollectionShape(node->value, &value, output, values); + output += "]"; + } + if (value.type == 7) { + auto* header = value.payload ? *reinterpret_cast(value.payload) : nullptr; + if (!header) { output += "x;"; return; } + output += "o" + std::to_string(header->count) + "{"; + if (!header->head) output += "missing"; + else CollectionTreeShape(header->head->parent, &value, output, values); + output += "}"; + } + output += ";"; +} +struct LegacyCollectionCase { + unsigned source; + bool once; + int budget; + unsigned notifications; + std::size_t requests; + std::uint64_t requestOrder; + const char* key; + const char* shape; +}; +static const LegacyCollectionCase CollectionCases[]{ + {0, 0, 0, 2, 2, UINT64_C(0x08842007b530c95d), "", "0:0;"}, + {0, 0, 1, 2, 3, UINT64_C(0xa922fa18e1c37583), "", "0:0;"}, + {0, 0, 2, 1, 3, UINT64_C(0xa922fa18e1c37583), "key", "0:0;"}, + {0, 0, 3, 1, 4, UINT64_C(0x29e493479f20d661), "key", "0:0;"}, + {0, 0, 4, 2, 6, UINT64_C(0x231a8cd446ee0ac9), "key", "6:0a0[missing];"}, + {0, 0, 5, 1, 6, UINT64_C(0x231a8cd446ee0ac9), "key", "6:0a0[missing];"}, + {0, 0, 6, 1, 7, UINT64_C(0x942845b4867ca51b), "key", "6:0a0[];"}, + {0, 0, 7, 1, 8, UINT64_C(0x3d1384c085cc7f49), "key", "6:0a1[0:0;];"}, + {0, 0, 8, 1, 9, UINT64_C(0x94a9e4235a7c56a3), "key", "6:0a1[0:0;];"}, + {0, 0, 9, 1, 10, UINT64_C(0x19057312c1477af1), "key", "6:0a1[0:0;];"}, + {0, 0, 10, 1, 11, UINT64_C(0xcbbd21de6c7551fb), "key", "6:0a1[7:0o0{missing};];"}, + {0, 0, 11, 1, 12, UINT64_C(0xa7b21ff24b599cc9), "key", "6:0a1[7:0o0{};];"}, + {0, 0, 12, 3, 15, UINT64_C(0x44ba72eae582feb9), "key", "6:0a1[7:0o1{k=/q=/0:0;};];"}, + {0, 0, 13, 2, 15, UINT64_C(0x44ba72eae582feb9), "key", "6:0a1[7:0o1{k=s/q=/0:0;};];"}, + {0, 0, 14, 2, 16, UINT64_C(0x4be45a23fda819b9), "key", "6:0a1[7:0o1{k=s/q=/0:0;};];"}, + {0, 0, 15, 1, 16, UINT64_C(0x4be45a23fda819b9), "key", "6:0a1[7:0o1{k=s/q=s/0:0;};];"}, + {0, 0, 16, 1, 17, UINT64_C(0x9d1ecc2804a38c93), "key", "6:0a1[7:0o1{k=s/q=s/0:0;};];"}, + {0, 0, 17, 1, 18, UINT64_C(0x9ee178ffe1e7da63), "key", "6:0a1[7:0o1{k=s/q=s/5:0s=;};];"}, + {0, 0, 18, 0, 18, UINT64_C(0x9ee178ffe1e7da63), "key", "6:0a1[7:0o1{k=s/q=s/5:0s=t;};];"}, + {0, 1, 0, 1, 17, UINT64_C(0xd9fd6582200984bd), "", "6:0a1[7:0o1{k=s/q=s/5:0s=t;};];"}, + {0, 1, 1, 1, 18, UINT64_C(0x9ee178ffe1e7da63), "", "6:0a1[7:0o1{k=s/q=s/5:0s=t;};];"}, + {0, 1, 2, 1, 3, UINT64_C(0xa922fa18e1c37583), "key", "0:0;"}, + {0, 1, 3, 1, 4, UINT64_C(0x29e493479f20d661), "key", "0:0;"}, + {0, 1, 4, 1, 18, UINT64_C(0x9ee178ffe1e7da63), "key", "6:0a1[7:0o1{k=s/q=s/5:0s=t;};];"}, + {0, 1, 5, 1, 6, UINT64_C(0x231a8cd446ee0ac9), "key", "6:0a0[missing];"}, + {0, 1, 6, 1, 7, UINT64_C(0x942845b4867ca51b), "key", "6:0a0[];"}, + {0, 1, 7, 1, 8, UINT64_C(0x3d1384c085cc7f49), "key", "6:0a1[0:0;];"}, + {0, 1, 8, 1, 9, UINT64_C(0x94a9e4235a7c56a3), "key", "6:0a1[0:0;];"}, + {0, 1, 9, 1, 10, UINT64_C(0x19057312c1477af1), "key", "6:0a1[0:0;];"}, + {0, 1, 10, 1, 11, UINT64_C(0xcbbd21de6c7551fb), "key", "6:0a1[7:0o0{missing};];"}, + {0, 1, 11, 1, 12, UINT64_C(0xa7b21ff24b599cc9), "key", "6:0a1[7:0o0{};];"}, + {0, 1, 12, 1, 18, UINT64_C(0x9ee178ffe1e7da63), "key", "6:0a1[7:0o1{k=/q=s/5:0s=t;};];"}, + {0, 1, 13, 1, 17, UINT64_C(0x68efa027e7058763), "key", "6:0a1[7:0o1{k=s/q=/5:0s=t;};];"}, + {0, 1, 14, 1, 18, UINT64_C(0x9ee178ffe1e7da63), "key", "6:0a1[7:0o1{k=s/q=/5:0s=t;};];"}, + {0, 1, 15, 1, 16, UINT64_C(0x4be45a23fda819b9), "key", "6:0a1[7:0o1{k=s/q=s/0:0;};];"}, + {0, 1, 16, 1, 17, UINT64_C(0x9d1ecc2804a38c93), "key", "6:0a1[7:0o1{k=s/q=s/0:0;};];"}, + {0, 1, 17, 1, 18, UINT64_C(0x9ee178ffe1e7da63), "key", "6:0a1[7:0o1{k=s/q=s/5:0s=;};];"}, + {0, 1, 18, 0, 18, UINT64_C(0x9ee178ffe1e7da63), "key", "6:0a1[7:0o1{k=s/q=s/5:0s=t;};];"}, + {1, 0, 0, 2, 2, UINT64_C(0x08842007b530c95d), "", "0:0;"}, + {1, 0, 1, 2, 3, UINT64_C(0xa922fa18e1c37583), "", "0:0;"}, + {1, 0, 2, 1, 3, UINT64_C(0xa922fa18e1c37583), "key", "0:0;"}, + {1, 0, 3, 1, 4, UINT64_C(0x29e493479f20d661), "key", "0:0;"}, + {1, 0, 4, 1, 5, UINT64_C(0x503c4bb364cbb14b), "key", "0:0;"}, + {1, 0, 5, 1, 6, UINT64_C(0x2225c4d4461dfe79), "key", "7:0o0{missing};"}, + {1, 0, 6, 1, 7, UINT64_C(0x242b85b324f6b6e3), "key", "7:0o0{};"}, + {1, 0, 7, 3, 10, UINT64_C(0x154035d1032f5a9b), "key", "7:0o1{k=/q=/0:0;};"}, + {1, 0, 8, 2, 10, UINT64_C(0x154035d1032f5a9b), "key", "7:0o1{k=a/q=/0:0;};"}, + {1, 0, 9, 2, 11, UINT64_C(0x4b61a128696594d3), "key", "7:0o1{k=a/q=/0:0;};"}, + {1, 0, 10, 1, 11, UINT64_C(0x4b61a128696594d3), "key", "7:0o1{k=a/q=a/0:0;};"}, + {1, 0, 11, 1, 12, UINT64_C(0x7c79a2ab179bd4f1), "key", "7:0o1{k=a/q=a/0:0;};"}, + {1, 0, 12, 2, 14, UINT64_C(0xa516a88d9f1de459), "key", "7:0o1{k=a/q=a/6:0a0[missing];};"}, + {1, 0, 13, 1, 14, UINT64_C(0xa516a88d9f1de459), "key", "7:0o1{k=a/q=a/6:0a0[missing];};"}, + {1, 0, 14, 1, 15, UINT64_C(0xa364d1a55fcb1e6b), "key", "7:0o1{k=a/q=a/6:0a0[];};"}, + {1, 0, 15, 1, 16, UINT64_C(0x6f6e9f01c624a239), "key", "7:0o1{k=a/q=a/6:0a1[0:0;];};"}, + {1, 0, 16, 1, 17, UINT64_C(0x7d9a5103b03f7e13), "key", "7:0o1{k=a/q=a/6:0a1[0:0;];};"}, + {1, 0, 17, 1, 18, UINT64_C(0xacb5ba447be336e3), "key", "7:0o1{k=a/q=a/6:0a1[5:0s=;];};"}, + {1, 0, 18, 0, 18, UINT64_C(0xacb5ba447be336e3), "key", "7:0o1{k=a/q=a/6:0a1[5:0s=t;];};"}, + {1, 1, 0, 1, 17, UINT64_C(0xc5a67b07339990bd), "", "7:0o1{k=a/q=a/6:0a1[5:0s=t;];};"}, + {1, 1, 1, 1, 18, UINT64_C(0xacb5ba447be336e3), "", "7:0o1{k=a/q=a/6:0a1[5:0s=t;];};"}, + {1, 1, 2, 1, 3, UINT64_C(0xa922fa18e1c37583), "key", "0:0;"}, + {1, 1, 3, 1, 4, UINT64_C(0x29e493479f20d661), "key", "0:0;"}, + {1, 1, 4, 1, 5, UINT64_C(0x503c4bb364cbb14b), "key", "0:0;"}, + {1, 1, 5, 1, 6, UINT64_C(0x2225c4d4461dfe79), "key", "7:0o0{missing};"}, + {1, 1, 6, 1, 7, UINT64_C(0x242b85b324f6b6e3), "key", "7:0o0{};"}, + {1, 1, 7, 1, 18, UINT64_C(0xacb5ba447be336e3), "key", "7:0o1{k=/q=a/6:0a1[5:0s=t;];};"}, + {1, 1, 8, 1, 17, UINT64_C(0xe052cbb56cadb10b), "key", "7:0o1{k=a/q=/6:0a1[5:0s=t;];};"}, + {1, 1, 9, 1, 18, UINT64_C(0xacb5ba447be336e3), "key", "7:0o1{k=a/q=/6:0a1[5:0s=t;];};"}, + {1, 1, 10, 1, 11, UINT64_C(0x4b61a128696594d3), "key", "7:0o1{k=a/q=a/0:0;};"}, + {1, 1, 11, 1, 12, UINT64_C(0x7c79a2ab179bd4f1), "key", "7:0o1{k=a/q=a/0:0;};"}, + {1, 1, 12, 1, 18, UINT64_C(0xacb5ba447be336e3), "key", "7:0o1{k=a/q=a/6:0a1[5:0s=t;];};"}, + {1, 1, 13, 1, 14, UINT64_C(0xa516a88d9f1de459), "key", "7:0o1{k=a/q=a/6:0a0[missing];};"}, + {1, 1, 14, 1, 15, UINT64_C(0xa364d1a55fcb1e6b), "key", "7:0o1{k=a/q=a/6:0a0[];};"}, + {1, 1, 15, 1, 16, UINT64_C(0x6f6e9f01c624a239), "key", "7:0o1{k=a/q=a/6:0a1[0:0;];};"}, + {1, 1, 16, 1, 17, UINT64_C(0x7d9a5103b03f7e13), "key", "7:0o1{k=a/q=a/6:0a1[0:0;];};"}, + {1, 1, 17, 1, 18, UINT64_C(0xacb5ba447be336e3), "key", "7:0o1{k=a/q=a/6:0a1[5:0s=;];};"}, + {1, 1, 18, 0, 18, UINT64_C(0xacb5ba447be336e3), "key", "7:0o1{k=a/q=a/6:0a1[5:0s=t;];};"}, +}; +static void CheckCollectionCopies(LegacyTrace& trace, Context& context) { + const auto guestBaseline = trace.guestGenerations, heapBaseline = trace.heapGenerations; + for (const auto& test : CollectionCases) for (unsigned alias = 0; alias < 2; ++alias) { + Value source{}; ParseLegacy(source, test.source ? R"({"a":["t"]})" : R"([{"s":"t"}])"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &context) == 0, "Register nested legacy Pair source recovery"); + std::vector sourceAllocations; + for (const auto& [pointer, generation] : trace.guestGenerations) { + if (guestBaseline.contains(pointer)) continue; + const auto* bytes = reinterpret_cast(pointer); + sourceAllocations.push_back({pointer, generation, {bytes, bytes + trace.guest.at(pointer)}}); + } + const LegacyNode* sourceNode = nullptr; + for (const auto& [pointer, size] : trace.heap) { + const auto* node = reinterpret_cast(pointer); + if (!node->nil && node->key == &source) sourceNode = node; + } + Require(sourceNode != nullptr, "Nested Pair copies require the actual source callback record"); + const auto sourceGeneration = trace.heapGenerations.at(const_cast(sourceNode)); + const Value sourceValue = source; + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "key"); + Pair pair; std::memset(&pair, 0xa5, sizeof(pair)); + const auto requests = trace.requests.size(); const auto notices = trace.notifications; + trace.budget = test.budget; trace.once = test.once; + if (alias) _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); + else _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); + trace.budget = -1; trace.once = false; + std::uint64_t requestOrder = UINT64_C(14695981039346656037); + for (auto i = requests; i < trace.requests.size(); ++i) { + requestOrder ^= trace.requests[i]; requestOrder *= UINT64_C(1099511628211); + } + Require(trace.notifications == notices + test.notifications && trace.requests.size() == requests + test.requests && requestOrder == test.requestOrder, + "Nested legacy Pair failures must preserve every request, notification and allocation-order fingerprint"); + Require(pair.reserved == UINT64_C(0xa5a5a5a5a5a5a5a5) && pair.value.reserved == UINT32_C(0xa5a5a5a5) && !pair.value.auxiliary && std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair.key), test.key) == 0, + "Nested legacy copies must preserve inactive bytes, independent metadata and partial key admission"); + std::string shape; std::vector values; + CollectionShape(pair.value, nullptr, shape, values); + Require(shape == test.shape, "Nested legacy copies must preserve partial counts, missing sentinels, both object keys, scalar/string contents and child parent identities"); + CheckMap(trace, {{&source, &context}, {&pair.value, &context}}); + const auto calls = context.calls; + Require(_ZNK3sce4Json5Value10getBooleanEv(&pair.value) == reinterpret_cast(&context.replacement.payload) && context.calls == calls + 1 && context.source == &pair.value && !context.parent, + "Even an incomplete nested Pair must retain its independently copied callback and borrowed result identity"); + for (const auto* value : values) { + if (value == &pair.value) continue; + Require(!value->parent && !value->auxiliary && !*_ZNK3sce4Json5Value10getBooleanEv(value) && context.calls == calls + 1, + "Unbound legacy descendants must use typed defaults without inheriting the copied root callback"); + } + if (alias) _ZN3sce4Json6Object4PairD2Ev(&pair); + else _ZN3sce4Json6Object4PairD1Ev(&pair); + CheckMap(trace, {{&source, &context}}); + Require(std::memcmp(&source, &sourceValue, sizeof(source)) == 0 && trace.heapGenerations.at(const_cast(sourceNode)) == sourceGeneration && sourceNode->callback == RecoverLegacy && sourceNode->context == &context, + "Nested Pair teardown must preserve the source Value and callback-record generation"); + for (const auto& snapshot : sourceAllocations) Require(trace.guestGenerations.at(snapshot.pointer) == snapshot.generation && std::memcmp(snapshot.pointer, snapshot.bytes.data(), snapshot.bytes.size()) == 0, + "Nested Pair failure and cleanup must preserve all source allocation generations and bytes"); + Require(_ZNK3sce4Json5Value10getBooleanEv(&source) == reinterpret_cast(&context.replacement.payload) && context.source == &source && !context.parent, + "Nested source recovery must remain usable after copied storage is destroyed"); + _ZN3sce4Json6StringD1Ev(&key); _ZN3sce4Json5ValueD1Ev(&source); CheckMap(trace, {}); + Require(trace.guestGenerations == guestBaseline && trace.heapGenerations == heapBaseline, "Nested legacy copies must restore every original generation in both allocation domains"); + } +} +struct MapReadContext { + LegacyTrace* trace; + Value* target; + Value* copy; + Context* replacement; + unsigned mode; + unsigned reads = 0; + int registration = -1; + Pair* pair = nullptr; + const String* key = nullptr; + unsigned alias = 0; +}; +static void UpdateInheritedMap(void* data) { + auto& context = *static_cast(data); + context.registration = _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(context.target, RecoverLegacy, context.replacement); + if (context.pair) { + if (context.alias == 1) _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(context.pair, context.key, context.target); + else _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(context.pair, context.key, context.target); + } else _ZN3sce4Json5ValueC1ERKS1_(context.copy, context.target); +} +static int APS5_VABI ReadInheritedMap(char& character, void* data) { + auto& context = *static_cast(data); + if (!context.reads) { + if (context.mode) { + if (context.mode == 2) { context.trace->onNotify = UpdateInheritedMap; context.trace->noticeContext = &context; } + context.trace->budget = 0; context.trace->once = true; + Require(!_ZN3sce4Json6MallocEm(8),"Inherited map testing must reject the actual guest allocation"); + context.trace->budget = -1; context.trace->once = false; + } + if (context.mode != 2) UpdateInheritedMap(&context); + } + if (context.reads >= 4) return static_cast(0x80848101); + character = "true"[context.reads++]; return 0; +} +static void CheckInheritedMap(LegacyTrace& trace, Context& first, Context& second) { + const auto guestBaseline = trace.guestGenerations, heapBaseline = trace.heapGenerations; + for (unsigned shape = 0; shape < 3; ++shape) for (bool existing : {false, true}) for (unsigned mode = 0; mode < 3; ++mode) for (unsigned alias = 0; alias < 3; ++alias) { + Value target{}, scalarCopy{}, outer{}; Pair pair; std::memset(&pair, 0xa5, sizeof(pair)); + Value& copy = alias ? pair.value : scalarCopy; + String key{}; if (alias) _ZN3sce4Json6StringC1EPKc(&key, "key"); + ParseLegacy(target, shape == 0 ? "9" : shape == 1 ? "[{\"s\":\"text\"}]" : "{\"a\":[\"text\"]}"); + if (existing) Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&target, RecoverLegacy, &first) == 0, "Prepare an existing legacy mismatch record before inherited failure"); + std::vector targetAllocations; + for (const auto& [pointer, generation] : trace.guestGenerations) { + if (guestBaseline.contains(pointer)) continue; + const auto* bytes = reinterpret_cast(pointer); + targetAllocations.push_back({pointer, generation, {bytes, bytes + trace.guest.at(pointer)}}); + } + const Value targetValue = target; + _ZN3sce4Json5ValueC1Ev(&outer); + MapReadContext context{&trace, &target, ©, &second, mode}; + if (alias) { context.pair = &pair; context.key = &key; context.alias = alias; } + const auto requests = trace.requests.size(); const auto notices = trace.notifications; + const int status = _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&outer, ReadInheritedMap, &context); + const auto error = static_cast(0x80848102); + Require(status == (mode ? error : 0) && context.registration == (mode == 1 ? error : 0) && context.reads == 4 && outer.type == 1 && (outer.payload & 0xff) == 1 && outer.auxiliary, + "Legacy map updates must observe failure only after notifier return without clearing the outer parser error or interrupting its admitted literal"); + auto expected = mode ? std::vector{8} : std::vector{}; + if (alias) { expected.push_back(24); expected.push_back(4); } + if (shape) { + const auto payload = shape == 1 ? std::vector{8, 24, 48, 48, 48, 8, 24, 104, 104, 104, 2, 24, 2, 8, 24, 5} + : std::vector{8, 24, 104, 104, 104, 2, 24, 2, 8, 24, 48, 48, 48, 8, 24, 5}; + for (auto bytes : payload) expected.push_back(bytes); + } + expected.push_back(24); expected.push_back(32); + if (alias) Require(pair.reserved == UINT64_C(0xa5a5a5a5a5a5a5a5) && pair.key.text != key.text && std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair.key), "key") == 0, + "Inherited failure must preserve independent Pair key ownership, complete key contents and inactive padding"); + Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected && trace.notifications == notices + unsigned(mode != 0) && !trace.onNotify, + "Inherited map registration must preserve actual failure, root-admission and notifier ordering"); + Context* targetContext = mode == 1 ? existing ? &first : nullptr : &second; + Context* copyContext = mode == 1 ? nullptr : &second; + CheckMap(trace, {{&target, targetContext}, {©, copyContext}}); + Require(!copy.parent && !copy.auxiliary && copy.type == (shape ? shape == 1 ? 6 : 7 : 3) && (shape || copy.payload == 9) && std::memcmp(&target, &targetValue, sizeof(target)) == 0, + "Inherited callback-map failure must preserve copied payload type and source ownership metadata"); + const auto verifyShape = [&] { + std::string actual; std::vector values; + CollectionShape(copy, nullptr, actual, values); + const char* expectedShape = shape == 0 ? "3:0=9;" : shape == 1 ? "6:0a1[7:0o1{k=s/q=s/5:0s=text;};];" : "7:0o1{k=a/q=a/6:0a1[5:0s=text;];};"; + Require(actual == expectedShape, "Inherited copies must preserve complete nested payloads, independent internal/public keys and unbound descendant parents"); + }; + verifyShape(); + const auto verifyRecovery = [&](const Value& value, Context* expectedContext) { + const auto firstCalls = first.calls, secondCalls = second.calls; + const auto* result = _ZNK3sce4Json5Value10getBooleanEv(&value); + if (expectedContext) Require(result == reinterpret_cast(&expectedContext->replacement.payload) && expectedContext->source == &value && !expectedContext->parent && expectedContext->calls == (expectedContext == &first ? firstCalls : secondCalls) + 1, + "Admitted legacy records must retain their exact callback context, receiver and borrowed result"); + else Require(!*result && first.calls == firstCalls && second.calls == secondCalls, + "An empty map entry retained after inherited failure must use the typed default without callback dispatch"); + }; + verifyRecovery(target, targetContext); verifyRecovery(copy, copyContext); + const auto guest = trace.guestGenerations, heap = trace.heapGenerations; + const auto beforeRetry = trace.requests.size(); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&target, RecoverLegacy, &second) == 0 && _ZN3sce4Json5ValueaSERKS1_(©, &target) == ©, + "Independent registration and copy retry must succeed after the outer failure scope completes"); + Require((shape ? trace.guestGenerations.size() == guest.size() && trace.requests.size() > beforeRetry : trace.guestGenerations == guest && trace.requests.size() == beforeRetry) && trace.heapGenerations == heap, + "Retry must reuse legacy callback records and retain balanced payload ownership across collection replacement"); + verifyShape(); + CheckMap(trace, {{&target, &second}, {©, &second}}); + verifyRecovery(target, &second); verifyRecovery(copy, &second); + for (const auto& snapshot : targetAllocations) Require(trace.guestGenerations.at(snapshot.pointer) == snapshot.generation && std::memcmp(snapshot.pointer, snapshot.bytes.data(), snapshot.bytes.size()) == 0, + "Inherited failure, registration retry and copying must preserve the original source auxiliary and fallback generations"); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&outer, "false", 5) == 0 && outer.type == 1 && !(outer.payload & 0xff), + "The next independent parse must not inherit an already completed callback-map failure"); + if (alias) { + if (alias == 1) _ZN3sce4Json6Object4PairD1Ev(&pair); + else _ZN3sce4Json6Object4PairD2Ev(&pair); + _ZN3sce4Json6StringD1Ev(&key); + } else _ZN3sce4Json5ValueD1Ev(©); + _ZN3sce4Json5ValueD1Ev(&target); _ZN3sce4Json5ValueD1Ev(&outer); CheckMap(trace, {}); + Require(trace.guestGenerations == guestBaseline && trace.heapGenerations == heapBaseline, + "Inherited map updates and retries must reclaim every temporary generation in both allocator domains"); + } +} +static int CheckHeapExceptions(unsigned budget) { + Require(budget <= 2, "Heap exceptions must cover sentinel, entry and complete admission"); + LegacyTrace trace; HeapTrace = &trace; LegacyAllocator allocator; + Parameter parameter{&allocator, &trace, 16384}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&owner, ¶meter) == 0, "Initialize legacy heap exception resources"); + Context context; _ZN3sce4Json5ValueC1Eb(&context.replacement, true); + Value source{}; ParseLegacy(source); + const Value sourceValue = source; + const auto baseline = trace.guestGenerations; + std::vector snapshots; + for (const auto& [pointer, generation] : baseline) { + const auto* bytes = reinterpret_cast(pointer); + snapshots.push_back({pointer, generation, {bytes, bytes + trace.guest.at(pointer)}}); + } + trace.heapBudget = static_cast(budget); + const auto requests = trace.requests.size(); + bool caught = false; + try { Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &context) == 0, "Complete registration must succeed"); } + catch (const std::bad_alloc&) { caught = true; } + Require(caught == (budget < 2) && owner && trace.heap.size() == budget && trace.guest.size() == baseline.size() + 1 && + trace.requests.size() == requests + 1 && trace.requests.back() == 24 && !trace.notifications, + "Libc exceptions must propagate without guest OOM notifications or reclaiming the admitted map header"); + void* header = nullptr; + for (const auto& [pointer, generation] : trace.guestGenerations) if (!baseline.contains(pointer)) { Require(!header, "Admission creates only one guest map header"); header = pointer; } + Require(header && trace.guest.at(header) == 24, "The unpublished header must remain owned in its guest allocator domain"); + const auto headerGeneration = trace.guestGenerations.at(header); + if (!budget) { + std::array expected; expected.fill(0xa5); std::memset(expected.data() + 8, 0, 8); + Require(std::memcmp(header, expected.data(), expected.size()) == 0 && trace.heap.empty(), "Sentinel failure must leave only the count initialized in the unpublished header"); + } else CheckMap(trace, budget == 1 ? std::map{} : std::map{{&source, &context}}); + trace.heapBudget = -1; + const auto retryRequests = trace.requests.size(); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &context) == 0 && + trace.requests.size() == retryRequests + unsigned(!budget) && trace.heap.size() == 2 && trace.guestGenerations.at(header) == headerGeneration, + "Retry must replace only an unpublished map and reuse an already published sentinel"); + CheckMap(trace, {{&source, &context}}); + const auto calls = context.calls; + Require(_ZNK3sce4Json5Value10getBooleanEv(&source) == reinterpret_cast(&context.replacement.payload) && context.calls == calls + 1 && context.source == &source && !context.parent, + "Retried callback metadata must dispatch the original context and borrowed result"); + Require(std::memcmp(&source, &sourceValue, sizeof(source)) == 0, "Heap failure and retry must preserve source metadata ownership"); + for (const auto& snapshot : snapshots) Require(trace.guestGenerations.at(snapshot.pointer) == snapshot.generation && + std::memcmp(snapshot.pointer, snapshot.bytes.data(), snapshot.bytes.size()) == 0, "Heap failure must preserve preexisting allocation bytes and generations"); + _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ValueD1Ev(&context.replacement); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.heap.empty() && trace.heapGenerations.empty() && trace.guest.size() == unsigned(!budget), + "Termination must reclaim published map storage while preserving the orphaned unpublished header"); + if (!budget) { + Require(trace.guestGenerations.at(header) == headerGeneration, "Library cleanup must not falsely claim orphan reclamation"); + FreeLegacy(nullptr, header, &trace); + } + Require(trace.guest.empty() && trace.guestGenerations.empty(), "The fixture separately reclaims any orphaned allocation"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&owner, ¶meter) == 0 && + _ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.guest.empty() && trace.heap.empty(), "Heap exception cleanup must permit a fresh module generation"); + HeapTrace = nullptr; + return 0; +} +static int CheckCopyHeapExceptions(unsigned shape, unsigned alias) { + Require(shape < 4 && alias < 3, "Heap-copy schedules cover scalar, string, nested collections and constructor aliases"); + LegacyTrace trace; HeapTrace = &trace; LegacyAllocator allocator; Parameter parameter{&allocator, &trace, 16384}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&owner, ¶meter) == 0, "Initialize copy exception resources"); + Context context; _ZN3sce4Json5ValueC1Eb(&context.replacement, true); + Value source{}; ParseLegacy(source, shape == 0 ? "9" : shape == 1 ? "\"text\"" : shape == 2 ? "[{\"s\":\"text\"}]" : "{\"a\":[\"text\"]}"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &context) == 0, "Prepare source callback ownership"); + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "key"); + Pair pair; std::memset(&pair, 0xa5, sizeof(pair)); Value scalar{}; Value& copy = alias ? pair.value : scalar; + const Value sourceValue = source; + const auto baseline = trace.guestGenerations, heapBaseline = trace.heapGenerations; + std::vector snapshots; + for (const auto& [pointer, generation] : baseline) { + if (trace.guest.at(pointer) == 24 && trace.heap.contains(*reinterpret_cast(pointer))) continue; + const auto* bytes = reinterpret_cast(pointer); + snapshots.push_back({pointer, generation, {bytes, bytes + trace.guest.at(pointer)}}); + } + const auto requests = trace.requests.size(); trace.heapBudget = 0; + bool caught = false; + try { + if (!alias) _ZN3sce4Json5ValueC1ERKS1_(©, &source); + else if (alias == 1) _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); + else _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); + } catch (const std::bad_alloc&) { caught = true; } + std::vector expected; + if (alias) expected = {24, 4}; + const auto payload = shape == 0 ? std::vector{} : shape == 1 ? std::vector{8, 24, 5} + : shape == 2 ? std::vector{8, 24, 48, 48, 48, 8, 24, 104, 104, 104, 2, 24, 2, 8, 24, 5} + : std::vector{8, 24, 104, 104, 104, 2, 24, 2, 8, 24, 48, 48, 48, 8, 24, 5}; + expected.insert(expected.end(), payload.begin(), payload.end()); + const unsigned retained = shape == 0 ? 0 : shape == 1 ? 3 : 14; + Require(caught && owner && !trace.notifications && trace.heapGenerations == heapBaseline && trace.guest.size() == baseline.size() + retained + unsigned(alias != 0) * 2 && + std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected, + "Map-entry exceptions must preserve completed copy allocations without constructor rollback or guest OOM notices"); + CheckMap(trace, {{&source, &context}}); + const auto verifyPayload = [&] { + std::string actual; std::vector values; CollectionShape(copy, nullptr, actual, values); + const char* expectedShape = shape == 0 ? "3:0=9;" : shape == 1 ? "5:0s=text;" : shape == 2 ? "6:0a1[7:0o1{k=s/q=s/5:0s=text;};];" : "7:0o1{k=a/q=a/6:0a1[5:0s=text;];};"; + Require(actual == expectedShape && !copy.auxiliary, "Heap exceptions must preserve complete payloads and independent callback metadata"); + if (alias) Require(pair.key.text != key.text && std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair.key), "key") == 0 && pair.reserved == UINT64_C(0xa5a5a5a5a5a5a5a5), + "Failed Pair constructors must retain their independent key and inactive padding"); + }; + verifyPayload(); + const auto calls = context.calls; + Require(!*_ZNK3sce4Json5Value10getBooleanEv(©) && context.calls == calls, "An unpublished copy callback must use the typed default"); + Require(_ZNK3sce4Json5Value10getBooleanEv(&source) == reinterpret_cast(&context.replacement.payload) && context.calls == calls + 1 && context.source == &source, + "Copy failure must preserve the source callback and borrowed result"); + trace.heapBudget = -1; + Require(_ZN3sce4Json5ValueaSERKS1_(©, &source) == ©, "Assignment retry must publish the copied callback"); + CheckMap(trace, {{&source, &context}, {©, &context}}); verifyPayload(); + Require(_ZNK3sce4Json5Value10getBooleanEv(©) == reinterpret_cast(&context.replacement.payload) && context.source == © && !context.parent, + "Retried copies must recover through their own callback identity"); + Require(std::memcmp(&source, &sourceValue, sizeof(source)) == 0, "Copy failure and retry must preserve source Value bytes"); + for (const auto& [pointer, generation] : baseline) Require(trace.guestGenerations.at(pointer) == generation, "Retry must preserve every preexisting allocation generation including shared map storage"); + for (const auto& snapshot : snapshots) Require(trace.guestGenerations.at(snapshot.pointer) == snapshot.generation && std::memcmp(snapshot.pointer, snapshot.bytes.data(), snapshot.bytes.size()) == 0, + "Copy failure must preserve all preexisting allocation bytes and generations"); + if (!alias) _ZN3sce4Json5ValueD1Ev(©); + else if (alias == 1) _ZN3sce4Json6Object4PairD1Ev(&pair); + else _ZN3sce4Json6Object4PairD2Ev(&pair); + _ZN3sce4Json6StringD1Ev(&key); _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ValueD1Ev(&context.replacement); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.guest.empty() && trace.heap.empty() && trace.guestGenerations.empty() && trace.heapGenerations.empty(), + "Failed construction, retry and explicit destruction must reclaim both allocator domains"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&owner, ¶meter) == 0 && _ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && trace.guest.empty() && trace.heap.empty(), + "Copy exceptions must permit a fresh module generation"); + HeapTrace = nullptr; + return 0; +} + +static int CheckArrayHeapExceptions(unsigned budget, unsigned alias, bool wrapped = false) { + Require(budget <= 2 && alias < 2, "Array-copy schedules cover both child entries and constructor aliases"); + LegacyTrace trace; HeapTrace = &trace; LegacyAllocator allocator; Parameter parameter{&allocator, &trace, 16384}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&owner, ¶meter) == 0, "Initialize array-copy exception resources"); + Context context; _ZN3sce4Json5ValueC1Eb(&context.replacement, true); Value source{}; ParseLegacy(source); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &context) == 0, "Prepare copied child callbacks"); + Array array{}; _ZN3sce4Json5ArrayC1Ev(&array); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &source); _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &source); + auto* sourceHeader = reinterpret_cast(array.items); + std::map entries{{&source, &context}}; + for (auto* node = sourceHeader->head->next; node != sourceHeader->head; node = node->next) entries.emplace(&node->value, &context); + CheckMap(trace, entries); + const auto baseline = trace.guestGenerations, heapBaseline = trace.heapGenerations; + const Value sourceValue = source; + std::vector snapshots; + for (const auto& [pointer, generation] : baseline) { + if (trace.guest.at(pointer) == 24 && trace.heap.contains(*reinterpret_cast(pointer))) continue; + const auto* bytes = reinterpret_cast(pointer); + snapshots.push_back({pointer, generation, {bytes, bytes + trace.guest.at(pointer)}}); + } + Array directCopy; std::memset(&directCopy, 0xa5, sizeof(directCopy)); Value wrappedCopy; std::memset(&wrappedCopy, 0xa5, sizeof(wrappedCopy)); + const auto requests = trace.requests.size(); trace.heapBudget = static_cast(budget); + bool caught = false; + try { + if (wrapped) { + if (alias) _ZN3sce4Json5ValueC2ERKNS0_5ArrayE(&wrappedCopy, &array); + else _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&wrappedCopy, &array); + } else if (alias) _ZN3sce4Json5ArrayC2ERKS1_(&directCopy, &array); + else _ZN3sce4Json5ArrayC1ERKS1_(&directCopy, &array); + } catch (const std::bad_alloc&) { caught = true; } + if (wrapped) Require(wrappedCopy.type == 6 && !wrappedCopy.parent && !wrappedCopy.auxiliary && trace.guest.contains(reinterpret_cast(wrappedCopy.payload)), + "Value wrappers must publish their independent Array handle and type before callback entry failure"); + Array& copy = wrapped ? *reinterpret_cast(wrappedCopy.payload) : directCopy; + std::vector expected{24, 48, 48, 48}; if (wrapped) expected.insert(expected.begin(), 8); if (budget) expected.push_back(48); + Require(caught == (budget < 2) && trace.guest.contains(copy.items) && trace.guest.size() == baseline.size() + (budget == 1 ? 5u : 4u) + unsigned(wrapped) && + trace.heap.size() == heapBaseline.size() + budget && !trace.notifications && std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected, + "Interrupted Array construction must publish empty destination storage and retain the temporary list without rollback"); + auto* copyHeader = reinterpret_cast(copy.items); + Require(copyHeader->count == (caught ? 0u : 2u) && copyHeader->head && (caught ? copyHeader->head->next == copyHeader->head && copyHeader->head->previous == copyHeader->head : true), + "Only completed list copying may replace the initial empty destination"); + std::map orphans; + if (caught) for (const auto& [pointer, generation] : trace.guestGenerations) { + if (!baseline.contains(pointer) && pointer != copy.items && pointer != copyHeader->head && (!wrapped || pointer != reinterpret_cast(wrappedCopy.payload))) { + Require(trace.guest.at(pointer) == 48, "Only temporary list links become orphaned"); orphans.emplace(pointer, generation); + } + } + Require(orphans.size() == (caught ? budget + 2 : 0), "Failed copies retain the temporary sentinel and admitted child nodes"); + for (const auto& [pointer, generation] : trace.heapGenerations) if (!heapBaseline.contains(pointer)) { + const auto* node = reinterpret_cast(pointer); + Require(!node->nil && node->callback == RecoverLegacy && node->context == &context && node->key->type == 3 && node->key->payload == 9, + "Completed child callback records must remain independently published even in the orphaned temporary list"); + entries.emplace(node->key, &context); + } + CheckMap(trace, entries); trace.heapBudget = -1; + Require(_ZN3sce4Json5ArrayaSERKS1_(©, &array) == © && _ZNK3sce4Json5Array4sizeEv(©) == 2, "Assignment retry must replace the retained empty destination"); + for (const auto& [pointer, generation] : orphans) Require(trace.guestGenerations.at(pointer) == generation, "Retry must not claim orphaned temporary-list ownership"); + for (const auto& snapshot : snapshots) Require(trace.guestGenerations.at(snapshot.pointer) == snapshot.generation && std::memcmp(snapshot.pointer, snapshot.bytes.data(), snapshot.bytes.size()) == 0, + "Interrupted copy and retry must preserve source storage bytes and generations"); + Require(std::memcmp(&source, &sourceValue, sizeof(source)) == 0, "Original callback source metadata must remain unchanged"); + const auto* child = _ZNK3sce4Json5Array5frontEv(©); + Require(_ZNK3sce4Json5Value10getBooleanEv(child) == reinterpret_cast(&context.replacement.payload) && context.source == child && !context.parent, + "Retried children must dispatch through their own callback records"); + if (wrapped) _ZN3sce4Json5ValueD1Ev(&wrappedCopy); + else _ZN3sce4Json5ArrayD1Ev(©); + _ZN3sce4Json5ArrayD1Ev(&array); _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ValueD1Ev(&context.replacement); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.heap.empty() && trace.heapGenerations.empty() && trace.guestGenerations == orphans, + "Module teardown reclaims callback records but cannot reclaim orphaned caller list nodes"); + for (const auto& [pointer, generation] : orphans) FreeLegacy(nullptr, pointer, &trace); + Require(trace.guest.empty() && _ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&owner, ¶meter) == 0 && + _ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && trace.guest.empty() && trace.heap.empty(), "Separate fixture reclamation must leave fresh module initialization usable"); + HeapTrace = nullptr; + return 0; +} + +static int CheckObjectHeapExceptions(unsigned budget, unsigned alias, bool wrapped) { + Require(budget <= 2 && alias < 2, "Object-copy schedules cover both child entries and constructor aliases"); + LegacyTrace trace; HeapTrace = &trace; LegacyAllocator allocator; Parameter parameter{&allocator, &trace, 16384}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&owner, ¶meter) == 0, "Initialize tree-copy exception resources"); + Context context; _ZN3sce4Json5ValueC1Eb(&context.replacement, true); Value source{}, root{}; + ParseLegacy(source); ParseLegacy(root, "{\"a\":9,\"b\":9}"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &context) == 0, "Prepare source callback records"); + auto* object = reinterpret_cast(root.payload); + auto* sourceHeader = reinterpret_cast(object->items); + std::string ignored; std::vector values; CollectionTreeShape(sourceHeader->head->parent, &root, ignored, values); + std::map entries{{&source, &context}}; + for (const auto* value : values) { + _ZN3sce4Json5ValueaSERKS1_(const_cast(value), &source); entries.emplace(value, &context); + } + CheckMap(trace, entries); + const auto baseline = trace.guestGenerations, heapBaseline = trace.heapGenerations; + const Value sourceValue = source, rootValue = root; + std::vector snapshots; + for (const auto& [pointer, generation] : baseline) { + if (trace.guest.at(pointer) == 24 && trace.heap.contains(*reinterpret_cast(pointer))) continue; + const auto* bytes = reinterpret_cast(pointer); + snapshots.push_back({pointer, generation, {bytes, bytes + trace.guest.at(pointer)}}); + } + Object direct; std::memset(&direct, 0xa5, sizeof(direct)); Value value; std::memset(&value, 0xa5, sizeof(value)); + const auto requests = trace.requests.size(); trace.heapBudget = static_cast(budget); bool caught = false; + try { + if (wrapped) { + if (alias) _ZN3sce4Json5ValueC2ERKNS0_6ObjectE(&value, object); + else _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&value, object); + } else if (alias) _ZN3sce4Json6ObjectC2ERKS1_(&direct, object); + else _ZN3sce4Json6ObjectC1ERKS1_(&direct, object); + } catch (const std::bad_alloc&) { caught = true; } + if (wrapped) Require(value.type == 7 && !value.parent && !value.auxiliary && trace.guest.contains(reinterpret_cast(value.payload)), "Value constructors must retain the published Object type and independent handle"); + Object& copy = wrapped ? *reinterpret_cast(value.payload) : direct; + std::vector expected{24, 104, 104, 104, 2, 24, 2}; + if (budget) expected.insert(expected.end(), {104, 2, 24, 2}); + if (wrapped) expected.insert(expected.begin(), 8); + Require(caught == (budget < 2) && trace.guest.contains(copy.items) && trace.guest.size() == baseline.size() + (budget == 0 ? 7u : budget == 1 ? 11u : 10u) + unsigned(wrapped) && + trace.heap.size() == heapBaseline.size() + budget && !trace.notifications && std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected, + "Interrupted tree construction must retain the published empty destination and unfinished replacement tree without rollback"); + auto* header = reinterpret_cast(copy.items); + Require(header->count == (caught ? 0u : 2u) && header->head && (!caught || header->head->parent == header->head), "Only complete tree copying may publish the replacement root"); + std::map orphans; + if (caught) for (const auto& [pointer, generation] : trace.guestGenerations) { + if (!baseline.contains(pointer) && pointer != copy.items && pointer != header->head && (!wrapped || pointer != reinterpret_cast(value.payload))) orphans.emplace(pointer, generation); + } + Require(orphans.size() == (caught ? budget == 0 ? 5u : 9u : 0u), "Unfinished trees retain their sentinel, nodes and independently allocated internal/public keys"); + for (const auto& [pointer, generation] : trace.heapGenerations) if (!heapBaseline.contains(pointer)) { + const auto* node = reinterpret_cast(pointer); + Require(!node->nil && node->callback == RecoverLegacy && node->context == &context && node->key->type == 3 && node->key->payload == 9, + "Completed replacement-tree child records remain published after the next entry allocation fails"); + entries.emplace(node->key, &context); + } + CheckMap(trace, entries); trace.heapBudget = -1; + Require(_ZN3sce4Json6ObjectaSERKS1_(©, object) == ©, "Independent assignment retry must publish a complete tree"); + Value view{nullptr, nullptr, reinterpret_cast(©), 0, TypeObject}; std::string actual; std::vector copiedValues; + CollectionShape(view, nullptr, actual, copiedValues); + Require(actual == "7:0o2{k=a/q=a/3:0=9;k=b/q=b/3:0=9;};", "Retried trees must preserve both keys, scalar contents and parent links"); + const auto* child = &reinterpret_cast(copy.items)->head->left->pair.value; + Require(_ZNK3sce4Json5Value10getBooleanEv(child) == reinterpret_cast(&context.replacement.payload) && context.source == child && !context.parent, + "Retried tree entries must dispatch through their own callback records"); + for (const auto& [pointer, generation] : orphans) Require(trace.guestGenerations.at(pointer) == generation, "Retry must not reclaim the orphaned replacement tree"); + for (const auto& snapshot : snapshots) Require(trace.guestGenerations.at(snapshot.pointer) == snapshot.generation && std::memcmp(snapshot.pointer, snapshot.bytes.data(), snapshot.bytes.size()) == 0, + "Failed tree copies and retry must preserve every original allocation and source byte"); + Require(std::memcmp(&source, &sourceValue, sizeof(source)) == 0 && std::memcmp(&root, &rootValue, sizeof(root)) == 0, "Both original Values must retain their metadata ownership"); + if (wrapped) _ZN3sce4Json5ValueD1Ev(&value); else _ZN3sce4Json6ObjectD1Ev(©); + _ZN3sce4Json5ValueD1Ev(&root); _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ValueD1Ev(&context.replacement); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.heap.empty() && trace.heapGenerations.empty() && trace.guestGenerations == orphans, + "Module teardown reclaims all callback records while leaving the orphaned caller tree intact"); + for (const auto& [pointer, generation] : orphans) FreeLegacy(nullptr, pointer, &trace); + Require(trace.guest.empty() && _ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&owner, ¶meter) == 0 && _ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && trace.guest.empty() && trace.heap.empty(), + "Separate orphan reclamation must preserve fresh module initialization and cleanup"); + HeapTrace = nullptr; + return 0; +} + +int main(int argc, char** argv) { + if (argc == 5 && std::strcmp(argv[1], "object-heap") == 0) return CheckObjectHeapExceptions(static_cast(std::strtoul(argv[2], nullptr, 10)), static_cast(std::strtoul(argv[3], nullptr, 10)), std::strcmp(argv[4], "wrapped") == 0); + if (argc == 4 && std::strcmp(argv[1], "value-array-heap") == 0) return CheckArrayHeapExceptions(static_cast(std::strtoul(argv[2], nullptr, 10)), static_cast(std::strtoul(argv[3], nullptr, 10)), true); + if (argc == 4 && std::strcmp(argv[1], "array-heap") == 0) return CheckArrayHeapExceptions(static_cast(std::strtoul(argv[2], nullptr, 10)), static_cast(std::strtoul(argv[3], nullptr, 10))); + if (argc == 3) return CheckCopyHeapExceptions(static_cast(std::strtoul(argv[1], nullptr, 10)), static_cast(std::strtoul(argv[2], nullptr, 10))); + if (argc == 2) return CheckHeapExceptions(static_cast(std::strtoul(argv[1], nullptr, 10))); + LegacyTrace trace; HeapTrace = &trace; LegacyAllocator allocator; + Parameter parameter{&allocator, &trace, 16384}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&initializer, ¶meter) == 0 && trace.guest.size() == 11, "Initialize legacy allocation and shared mutex resources"); + const auto baseline = trace.guest.size(); + Context first, second; _ZN3sce4Json5ValueC1Eb(&first.replacement, true); _ZN3sce4Json5ValueC1Eb(&second.replacement, false); + Value source{}; _ZN3sce4Json5ValueC1El(&source, 9); + Require(static_cast(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &first)) == 0x80848104 && trace.guest.size() == baseline && trace.heap.empty(), "Legacy mismatch registration must reject a root without auxiliary metadata"); + _ZN3sce4Json5ValueD1Ev(&source); ParseLegacy(source); + const auto before = trace.guest.size(); trace.budget = 0; + Require(static_cast(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &first)) == 0x80848102 && trace.notifications == 1 && trace.guest.size() == before && trace.heap.empty(), "Failed map admission must retain the root and permit retry"); + trace.budget = -1; + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &first) == 0, "Register legacy mismatch after failure"); + CheckMap(trace, {{&source, &first}}); + Require(*_ZNK3sce4Json5Value10getBooleanEv(&source) && first.source == &source && !first.parent, "Legacy getters must dispatch the registered borrowed replacement"); + const auto guest = trace.guest.size(); const auto heap = trace.heap.size(); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &second) == 0 && !_ZNK3sce4Json5ValuecvbEv(&source) && second.calls == 1 && trace.guest.size() == guest && trace.heap.size() == heap, "Legacy callback replacement must reuse the map entry and govern boolean conversion"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, nullptr, nullptr) == 0 && !*_ZNK3sce4Json5Value10getBooleanEv(&source), "A null legacy mismatch callback must disable dispatch without removing the entry"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&source, RecoverLegacy, &first) == 0, "Restore legacy callback"); + Value copy{}; _ZN3sce4Json5ValueC1ERKS1_(©, &source); + Require(!copy.auxiliary && *_ZNK3sce4Json5Value10getBooleanEv(©) && first.source == ©, "Legacy value copies must propagate map callbacks independently of auxiliary ownership"); + Value assigned{}; ParseLegacy(assigned); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&assigned, RecoverLegacy, &second) == 0, "Prepare legacy assignment callback"); + auto* auxiliary = assigned.auxiliary; + _ZN3sce4Json5ValueaSERKS1_(&assigned, &source); + Require(assigned.auxiliary == auxiliary && *_ZNK3sce4Json5Value10getBooleanEv(&assigned) && first.source == &assigned, "Legacy assignment must copy the source map record while retaining destination metadata"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&assigned, RecoverLegacy, &second) == 0, "Prepare distinct swap callbacks"); + _ZN3sce4Json5Value4swapERS1_(&source, &assigned); + Require(!*_ZNK3sce4Json5Value10getBooleanEv(&source) && *_ZNK3sce4Json5Value10getBooleanEv(&assigned) && assigned.auxiliary == auxiliary, "Legacy swap must exchange callback records and retain destination auxiliary ownership"); + CheckMap(trace, {{&source, &second}, {©, &first}, {&assigned, &first}}); + _ZN3sce4Json5Value4swapERS1_(&source, &assigned); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&assigned, RecoverLegacy, &first) == 0, "Restore assignment callback after swap"); + Array array{}; _ZN3sce4Json5ArrayC1Ev(&array); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &source); + const auto* element = _ZNK3sce4Json5Array5frontEv(&array); + Require(*_ZNK3sce4Json5Value10getBooleanEv(element) && first.source == element && !first.parent, "Legacy array append must copy the handler map entry without binding a modern parent"); + CheckMap(trace, {{&source, &first}, {©, &first}, {&assigned, &first}, {element, &first}}); + trace.budget = 0; + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &source); + trace.budget = -1; + Require(_ZNK3sce4Json5Array4sizeEv(&array) == 0, "A failed legacy append must remove the existing tail and its handler record"); + CheckMap(trace, {{&source, &first}, {©, &first}, {&assigned, &first}}); + _ZN3sce4Json5ArrayD1Ev(&array); + Value parsed{}; ParseLegacy(parsed, "[9]"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&parsed, RecoverLegacy, &first) == 0, "Register legacy parsed-container recovery"); + const auto* child = _ZNK3sce4Json5Array5frontEv(reinterpret_cast(parsed.payload)); + Require(*_ZNK3sce4Json5Value10getBooleanEv(child) && first.source == child && first.parent == &parsed, "Legacy descendant mismatch recovery must use the root map and immediate parent"); + _ZN3sce4Json5ValueD1Ev(&parsed); + Value null{}; ParseLegacy(null, "null"); NullContext nullContext{&source, &first}; + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&null, RecoverLegacyNull, &nullContext) == 0 && *_ZNK3sce4Json5Value10getBooleanEv(&null), "Legacy local null recovery must preserve its pre-guard dispatch"); + _ZN3sce4Json5ValueD1Ev(&null); _ZN3sce4Json5ValueD1Ev(&assigned); _ZN3sce4Json5ValueD1Ev(©); _ZN3sce4Json5ValueD1Ev(&source); + CheckMap(trace, {}); + CheckPairCopies(trace, first); + CheckCollectionCopies(trace, first); + CheckInheritedMap(trace, first, second); + std::array roots{}; std::map entries; + for (unsigned index = 0; index < roots.size(); ++index) { + auto& root = roots[index * 13 % roots.size()]; ParseLegacy(root); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, RecoverLegacy, &first) == 0, "Register legacy tree entry"); + entries.emplace(&root, &first); CheckMap(trace, entries); + } + for (unsigned index = 0; index < roots.size(); ++index) { + auto& root = roots[index * 7 % roots.size()]; + _ZN3sce4Json5ValueD1Ev(&root); entries.erase(&root); CheckMap(trace, entries); + } + _ZN3sce4Json5ValueD1Ev(&second.replacement); _ZN3sce4Json5ValueD1Ev(&first.replacement); + Require(trace.guest.size() == baseline + 1 && trace.heap.size() == 1, "Empty legacy map storage must remain owned until termination"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.guest.empty() && trace.heap.empty() && trace.guestGenerations.empty() && trace.heapGenerations.empty(), "Termination must release both allocator domains and all legacy map resources"); + HeapTrace = nullptr; + return 0; +} diff --git a/core/libs/tests/GuestJsonLegacySelf.cpp b/core/libs/tests/GuestJsonLegacySelf.cpp new file mode 100644 index 0000000000..102758b43d --- /dev/null +++ b/core/libs/tests/GuestJsonLegacySelf.cpp @@ -0,0 +1,217 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; int mode; unsigned reserved[3]; }; +using NullHandler = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + static const std::uint32_t words[]{0, 0, 0, 0, 0x05000000}; + return words; +} +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct LegacySelfTrace { + std::map live; + std::map generations; + std::vector requests; + std::vector releases; + unsigned nextGeneration = 0; + unsigned notices = 0; + unsigned nullFrees = 0; + int budget = -1; + bool once = false; + std::vector releaseIds; +}; +static void* APS5_VABI AllocateSelf(void*, std::size_t size, void* data) { + auto& trace = *static_cast(data); trace.requests.push_back(size); + if (!trace.budget) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(size); + Require(memory && trace.live.emplace(memory, size).second && trace.generations.emplace(memory, ++trace.nextGeneration).second, "Self-setting allocations require distinct live generations"); + std::memset(memory, 0xa5, size); return memory; +} +static void APS5_VABI ReleaseSelf(void*, void* memory, void* data) { + auto& trace = *static_cast(data); + if (!memory) { ++trace.nullFrees; return; } + trace.releaseIds.push_back(trace.generations.at(memory)); + Require(trace.live.erase(memory) == 1 && trace.generations.erase(memory) == 1, "Legacy self-setting must release each owned allocation exactly once"); + trace.releases.push_back(memory); std::free(memory); +} +static void APS5_VABI NotifySelf(void*, int error, std::size_t size, void* data) { + auto& trace = *static_cast(data); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == size, "Legacy self-setting failure must identify the rejected sentinel"); + ++trace.notices; +} +static void* const SelfTable[]{nullptr, nullptr, reinterpret_cast(AllocateSelf), reinterpret_cast(ReleaseSelf), reinterpret_cast(NotifySelf)}; +struct LegacySelfAllocator { void* const* table = SelfTable; }; +struct SelfListLink { SelfListLink* previous; SelfListLink* next; Value value; }; +struct SelfTreeLink { + SelfTreeLink* parent; SelfTreeLink* left; SelfTreeLink* right; + const char* key; unsigned length; unsigned capacity; std::uint64_t reserved; + void* publicKey; std::uint64_t padding; Value value; unsigned char color; unsigned char nil; unsigned char tail[6]; +}; +static_assert(sizeof(SelfListLink) == 48 && sizeof(SelfTreeLink) == 104); +static std::string SelfItemState(const Value& value, const Value* owner) { + return std::to_string(value.type) + (value.parent == owner ? "p" : value.parent ? "x" : "n") + std::to_string(value.payload); +} +static void SelfTreeState(const SelfTreeLink* node, const Value* owner, std::string& output, unsigned depth = 0) { + Require(depth < 8, "Replacement tree traversal must remain bounded"); + if (node->nil) return; + SelfTreeState(node->left, owner, output, depth + 1); + output += std::string(node->key) + "/" + (node->publicKey ? *reinterpret_cast(node->publicKey) : "?") + "=" + SelfItemState(node->value, owner) + ";"; + SelfTreeState(node->right, owner, output, depth + 1); +} +static std::string SelfState(const Value& value, bool object) { + auto* words = *reinterpret_cast(value.payload); + std::string output = std::to_string(words[1]) + "["; + if (!words[0]) return output + "missing]"; + if (object) { + auto* head = reinterpret_cast(words[0]); SelfTreeState(head->parent, &value, output); + } else { + auto* head = reinterpret_cast(words[0]); unsigned count = 0; + for (auto* node = head->next; node != head; node = node->next) { + Require(++count < 8, "Replacement list traversal must remain bounded"); output += SelfItemState(node->value, &value) + ";"; + } + } + return output + "]"; +} +template +static std::uint64_t SelfOrder(const std::vector& values, std::size_t begin) { + std::uint64_t hash = UINT64_C(14695981039346656037); + for (auto i = begin; i < values.size(); ++i) { hash ^= values[i]; hash *= UINT64_C(1099511628211); } + return hash; +} +struct NonemptySelfCase { + bool object; bool once; unsigned budget; + std::size_t requests; std::uint64_t requestOrder; unsigned notices; std::size_t releases; std::uint64_t releaseOrder; const char* state; + std::size_t retryRequests; std::uint64_t retryRequestOrder; std::size_t retryReleases; std::uint64_t retryReleaseOrder; const char* retryState; +}; +static const NonemptySelfCase NonemptySelfCases[]{ + {0, 0, 0, 1, UINT64_C(12638135523509116079), 1, 3, UINT64_C(6554792551179442466), "0[missing]", 1, UINT64_C(12638135523509116079), 0, UINT64_C(14695981039346656037), "0[]"}, + {0, 0, 1, 2, UINT64_C(575361273772712493), 1, 3, UINT64_C(6554792551179442466), "0[]", 1, UINT64_C(12638135523509116079), 1, UINT64_C(12638167409346334198), "0[]"}, + {0, 0, 2, 3, UINT64_C(5656224367409952583), 1, 3, UINT64_C(6554792551179442466), "1[3n11;]", 2, UINT64_C(575361273772712493), 2, UINT64_C(613651766217591568), "1[3n11;]"}, + {0, 0, 3, 3, UINT64_C(5656224367409952583), 0, 3, UINT64_C(6554792551179442466), "2[3n11;3n22;]", 3, UINT64_C(5656224367409952583), 3, UINT64_C(12213400556975252055), "2[3n11;3n22;]"}, + {0, 1, 0, 1, UINT64_C(12638135523509116079), 1, 3, UINT64_C(6554792551179442466), "0[missing]", 1, UINT64_C(12638135523509116079), 0, UINT64_C(14695981039346656037), "0[]"}, + {0, 1, 1, 2, UINT64_C(575361273772712493), 1, 3, UINT64_C(6554792551179442466), "0[]", 1, UINT64_C(12638135523509116079), 1, UINT64_C(12638167409346334198), "0[]"}, + {0, 1, 2, 3, UINT64_C(5656224367409952583), 1, 3, UINT64_C(6554792551179442466), "1[3n11;]", 2, UINT64_C(575361273772712493), 2, UINT64_C(613651766217591568), "1[3n11;]"}, + {0, 1, 3, 3, UINT64_C(5656224367409952583), 0, 3, UINT64_C(6554792551179442466), "2[3n11;3n22;]", 3, UINT64_C(5656224367409952583), 3, UINT64_C(12213400556975252055), "2[3n11;3n22;]"}, + {1, 0, 0, 1, UINT64_C(12638197096160295895), 1, 9, UINT64_C(7899594896565220821), "0[missing]", 1, UINT64_C(12638197096160295895), 0, UINT64_C(14695981039346656037), "0[]"}, + {1, 0, 1, 2, UINT64_C(628920684195530125), 1, 9, UINT64_C(7899594896565220821), "0[]", 1, UINT64_C(12638197096160295895), 1, UINT64_C(12638126727416090391), "0[]"}, + {1, 0, 2, 5, UINT64_C(17406812458522649141), 3, 9, UINT64_C(7899594896565220821), "2[/?=3n11;]", 2, UINT64_C(628920684195530125), 2, UINT64_C(566743301632497700), "2[/?=3n11;]"}, + {1, 0, 3, 5, UINT64_C(17406812458522649141), 2, 9, UINT64_C(7899594896565220821), "2[a/?=3n11;]", 3, UINT64_C(3708358159552328189), 3, UINT64_C(1887292020941694844), "2[a/?=3n11;]"}, + {1, 0, 4, 6, UINT64_C(10307126579844094685), 2, 9, UINT64_C(7899594896565220821), "2[a/=3n11;]", 4, UINT64_C(8967656197913659423), 4, UINT64_C(12385604588108498361), "2[a/=3n11;]"}, + {1, 0, 5, 6, UINT64_C(10307126579844094685), 1, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;]", 5, UINT64_C(17406713502476110151), 5, UINT64_C(16291384495448556647), "2[a/a=3n11;]"}, + {1, 0, 6, 8, UINT64_C(8732740249579296847), 2, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;/?=3n22;]", 6, UINT64_C(10307126579844094685), 6, UINT64_C(6796161341782386540), "2[a/a=3n11;/?=3n22;]"}, + {1, 0, 7, 8, UINT64_C(8732740249579296847), 1, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;b/?=3n22;]", 7, UINT64_C(12001570155012663533), 7, UINT64_C(16058915569980617336), "2[a/a=3n11;b/?=3n22;]"}, + {1, 0, 8, 9, UINT64_C(15869276825689737943), 1, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;b/=3n22;]", 8, UINT64_C(8732740249579296847), 8, UINT64_C(13124253386643929941), "2[a/a=3n11;b/=3n22;]"}, + {1, 0, 9, 9, UINT64_C(15869276825689737943), 0, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;b/b=3n22;]", 9, UINT64_C(15869276825689737943), 9, UINT64_C(5256107193438867751), "2[a/a=3n11;b/b=3n22;]"}, + {1, 1, 0, 1, UINT64_C(12638197096160295895), 1, 9, UINT64_C(7899594896565220821), "0[missing]", 1, UINT64_C(12638197096160295895), 0, UINT64_C(14695981039346656037), "0[]"}, + {1, 1, 1, 2, UINT64_C(628920684195530125), 1, 9, UINT64_C(7899594896565220821), "0[]", 1, UINT64_C(12638197096160295895), 1, UINT64_C(12638126727416090391), "0[]"}, + {1, 1, 2, 9, UINT64_C(15869276825689737943), 1, 9, UINT64_C(7899594896565220821), "2[/a=3n11;b/b=3n22;]", 8, UINT64_C(16025805325695402919), 8, UINT64_C(13124253386643929941), "2[/a=3n11;b/b=3n22;]"}, + {1, 1, 3, 8, UINT64_C(10981429471021977615), 1, 9, UINT64_C(7899594896565220821), "2[a/?=3n11;b/b=3n22;]", 7, UINT64_C(12136851201209200173), 7, UINT64_C(16058915569980617336), "2[a/?=3n11;b/b=3n22;]"}, + {1, 1, 4, 9, UINT64_C(15869276825689737943), 1, 9, UINT64_C(7899594896565220821), "2[a/=3n11;b/b=3n22;]", 8, UINT64_C(10981429471021977615), 8, UINT64_C(13124253386643929941), "2[a/=3n11;b/b=3n22;]"}, + {1, 1, 5, 6, UINT64_C(10307126579844094685), 1, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;]", 5, UINT64_C(17406713502476110151), 5, UINT64_C(16291384495448556647), "2[a/a=3n11;]"}, + {1, 1, 6, 9, UINT64_C(15869276825689737943), 1, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;/b=3n22;]", 8, UINT64_C(8707858301437801767), 8, UINT64_C(13124253386643929941), "2[a/a=3n11;/b=3n22;]"}, + {1, 1, 7, 8, UINT64_C(8732740249579296847), 1, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;b/?=3n22;]", 7, UINT64_C(12001570155012663533), 7, UINT64_C(16058915569980617336), "2[a/a=3n11;b/?=3n22;]"}, + {1, 1, 8, 9, UINT64_C(15869276825689737943), 1, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;b/=3n22;]", 8, UINT64_C(8732740249579296847), 8, UINT64_C(13124253386643929941), "2[a/a=3n11;b/=3n22;]"}, + {1, 1, 9, 9, UINT64_C(15869276825689737943), 0, 9, UINT64_C(7899594896565220821), "2[a/a=3n11;b/b=3n22;]", 9, UINT64_C(15869276825689737943), 9, UINT64_C(5256107193438867751), "2[a/a=3n11;b/b=3n22;]"}, +}; + +static void CheckNonemptySelfUse() { + for (const auto& expected : NonemptySelfCases) for (bool setter : {false, true}) { + LegacySelfTrace trace; LegacySelfAllocator allocator; Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == 0, "Initialize nonempty legacy self-use"); + const auto module = trace.generations; + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + const char* text = expected.object ? "{\"a\":11,\"b\":22}" : "[11,22]"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, text, std::strlen(text)) == 0 && + _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&value, nullptr, &trace) == 0, "Prepare nonempty collection and callback context"); + const Value original = value; void* header = *reinterpret_cast(value.payload); + auto protectedGenerations = module; + for (void* pointer : {reinterpret_cast(value.payload), static_cast(value.auxiliary), reinterpret_cast(value.auxiliary)[2]}) protectedGenerations.emplace(pointer, trace.generations.at(pointer)); + std::map> snapshots; + for (const auto& [pointer, generation] : protectedGenerations) { + const auto* bytes = reinterpret_cast(pointer); snapshots.emplace(pointer, std::vector{bytes, bytes + trace.live.at(pointer)}); + } + const auto requests = trace.requests.size(); const auto releases = trace.releaseIds.size(); const auto notices = trace.notices; const auto nullFrees = trace.nullFrees; + trace.budget = static_cast(expected.budget); trace.once = expected.once; + if (setter) _ZN3sce4Json5Value3setERKS1_(&value, &value); + else Require(_ZN3sce4Json5ValueaSERKS1_(&value, &value) == &value, "Nonempty legacy assignment must retain its receiver identity"); + trace.budget = -1; trace.once = false; + Require(trace.requests.size() == requests + expected.requests && SelfOrder(trace.requests, requests) == expected.requestOrder && trace.notices == notices + expected.notices && + trace.releaseIds.size() == releases + expected.releases && SelfOrder(trace.releaseIds, releases) == expected.releaseOrder && trace.nullFrees == nullFrees && SelfState(value, expected.object) == expected.state, + "Legacy self-use must preserve exact admission/release ordering, partial contents, key failures, parent links and retained counts"); + const auto retryRequests = trace.requests.size(); const auto retryReleases = trace.releaseIds.size(); const auto retryNotices = trace.notices; + if (setter) _ZN3sce4Json5Value3setERKS1_(&value, &value); + else Require(_ZN3sce4Json5ValueaSERKS1_(&value, &value) == &value, "Partial self-use retry must retain its receiver identity"); + Require(trace.requests.size() == retryRequests + expected.retryRequests && SelfOrder(trace.requests, retryRequests) == expected.retryRequestOrder && trace.notices == retryNotices && + trace.releaseIds.size() == retryReleases + expected.retryReleases && SelfOrder(trace.releaseIds, retryReleases) == expected.retryReleaseOrder && trace.nullFrees == nullFrees && SelfState(value, expected.object) == expected.retryState, + "Independent retry must clone the actual partial collection without inventing lost entries or repairing observed count/key differences"); + Require(std::memcmp(&value, &original, sizeof(value)) == 0 && *reinterpret_cast(value.payload) == header, "Both self-use attempts must retain Value, header and callback ownership"); + for (const auto& [pointer, generation] : protectedGenerations) Require(trace.generations.at(pointer) == generation && std::memcmp(pointer, snapshots.at(pointer).data(), snapshots.at(pointer).size()) == 0, + "Self-use failure and retry must preserve every module, handle, callback and fallback generation and byte"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.live.empty() && trace.generations.empty(), "All partial nonempty collections and module allocations must be reclaimed"); + } +} + +int main() { + CheckNonemptySelfUse(); + for (bool object : {false, true}) for (bool setter : {false, true}) for (unsigned budget = 0; budget <= 2; ++budget) { + LegacySelfTrace trace; LegacySelfAllocator allocator; Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == 0 && trace.live.size() == 11, "Initialize the eleven-resource legacy module"); + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); + Require(_ZNK3sce4Json5Value10getBooleanEv(&value) == &_ZN3sce4Json5Value10s_nullboolE, "Legacy Boolean default identity"); + Require(_ZNK3sce4Json5Value10getIntegerEv(&value) == &_ZN3sce4Json5Value13s_nullintegerE, "Legacy integer default identity"); + Require(_ZNK3sce4Json5Value11getUIntegerEv(&value) == &_ZN3sce4Json5Value14s_nulluintegerE, "Legacy unsigned default identity"); + Require(_ZNK3sce4Json5Value7getRealEv(&value) == &_ZN3sce4Json5Value10s_nullrealE, "Legacy real default identity"); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, object ? "{}" : "[]", 2) == 0 && + _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&value, nullptr, &trace) == 0, "Prepare legacy collection ownership and auxiliary callback context"); + const Value before = value; const auto generations = trace.generations; + void* header = *reinterpret_cast(value.payload); void* sentinel = *reinterpret_cast(header); + Require(trace.live.at(header) == 24 && trace.live.at(sentinel) == (object ? 104u : 48u), "Legacy collections must retain their 24-byte header and actual sentinel"); + std::map> snapshots; + for (const auto& [pointer, size] : trace.live) { + if (pointer == header || pointer == sentinel) continue; + const auto* bytes = reinterpret_cast(pointer); snapshots.emplace(pointer, std::vector{bytes, bytes + size}); + } + const auto requests = trace.requests.size(); const auto releases = trace.releases.size(); const auto notices = trace.notices; const auto nullFrees = trace.nullFrees; + trace.budget = static_cast(budget); + if (setter) _ZN3sce4Json5Value3setERKS1_(&value, &value); + else Require(_ZN3sce4Json5ValueaSERKS1_(&value, &value) == &value, "Legacy self-assignment must return its receiver"); + trace.budget = -1; + auto* words = reinterpret_cast(header); + Require(std::memcmp(&value, &before, sizeof(value)) == 0 && *reinterpret_cast(value.payload) == header && words[1] == 0 && + trace.requests.size() == requests + 1 && trace.requests.back() == (object ? 104u : 48u) && trace.notices == notices + unsigned(!budget) && + trace.releases.size() == releases + 1 && trace.releases.back() == sentinel && trace.nullFrees == nullFrees, + "Legacy self-use must keep its header and metadata while releasing the original sentinel even when replacement admission fails"); + Require((words[0] != 0) == bool(budget), "Failed legacy replacement must retain a missing sentinel rather than restore the old one"); + if (budget) Require(reinterpret_cast(words[0]) != sentinel && trace.live.at(reinterpret_cast(words[0])) == (object ? 104u : 48u), "Successful legacy replacement must publish a fresh sentinel"); + for (const auto& [pointer, generation] : generations) if (pointer != sentinel) Require(trace.generations.at(pointer) == generation, "Self-use must preserve all non-sentinel allocation generations"); + for (const auto& [pointer, bytes] : snapshots) Require(std::memcmp(pointer, bytes.data(), bytes.size()) == 0, "Self-use must preserve callback, fallback, handle and module bytes"); + const auto retryRequests = trace.requests.size(); const auto retryReleases = trace.releases.size(); const auto retryNullFrees = trace.nullFrees; const auto retryNotices = trace.notices; + const auto previousSentinel = words[0]; + if (setter) _ZN3sce4Json5Value3setERKS1_(&value, &value); + else Require(_ZN3sce4Json5ValueaSERKS1_(&value, &value) == &value, "Legacy self-use retry must return its original receiver"); + Require(words[0] && words[0] != previousSentinel && words[1] == 0 && std::memcmp(&value, &before, sizeof(value)) == 0 && + trace.requests.size() == retryRequests + 1 && trace.requests.back() == (object ? 104u : 48u) && trace.releases.size() == retryReleases + unsigned(previousSentinel != 0) && + trace.nullFrees == retryNullFrees && trace.notices == retryNotices, + "Same-Value retry must repair a missing sentinel without fabricating a null free or replacing its header and metadata"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.live.empty() && trace.generations.empty(), "Partial legacy collections and module resources must remain destructible"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == 0 && _ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && trace.live.empty(), "A completed legacy self-use case must permit a fresh module generation"); + } + return 0; +} diff --git a/core/libs/tests/GuestJsonLegacySerialization.cpp b/core/libs/tests/GuestJsonLegacySerialization.cpp new file mode 100644 index 0000000000..e48e1f7da8 --- /dev/null +++ b/core/libs/tests/GuestJsonLegacySerialization.cpp @@ -0,0 +1,154 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using LegacyNullCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ArrayTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::set headers; +}; +static void* APS5_VABI AllocateArray(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI FreeArray(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + if (trace.headers.erase(memory)) { + const auto* words = reinterpret_cast(memory); + Require(words[0] == 0xffffffff && words[1] == UINT64_MAX, "Array teardown must expose the native header poison before allocator release"); + } + Require(trace.live.erase(memory) == 1, "Array must release through its owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyArray(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Array notification must identify the failed allocation"); ++trace.notifications; +} +static void* const ArrayVtable[]{nullptr, nullptr, reinterpret_cast(AllocateArray), reinterpret_cast(FreeArray), reinterpret_cast(NotifyArray)}; +struct ArrayAllocator { void* const* vtable = ArrayVtable; }; +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + alignas(8) static const std::uint32_t parameters[]{0, 0, 0, 0, 0x05000000, 0}; + return parameters; +} +struct LegacySerializationTrace { std::vector chunks; }; +static int APS5_VABI ObserveLegacy(String& output, void* context) { + static_cast(context)->chunks.emplace_back(_ZNK3sce4Json6String5c_strEv(&output)); + return 23; +} +static const Value& APS5_VABI LegacyNull(ValueType, const Value*, void* context) { + Require(false, "Legacy serialization must clear the root null-access callback before later recovery"); + return *static_cast(context); +} +int main() { + ArrayTrace trace; ArrayAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(&initializer, ¶meter) == 0, "Initialize legacy serialization contracts"); + const auto baseline = trace.live.size(); + struct Case { double value; const char* text; }; + const Case cases[]{ + {0.0, "0"}, {-0.0, "0"}, {1.0, "1"}, {1.25, "1.250000"}, + {0.0000001, "0"}, {-0.0000001, "-0"}, {-0.0000005, "-0"}, + {1.0000005, "1"}, {1.0000015, "1.000001"}, {1234567.875, "1234567.875000"}, + {1e8, "100000000"}, {1e10, "2147483647"}, {1e20, "2147483647"}, {-1e20, "-2147483647"}, + {std::numeric_limits::infinity(), "2147483647"}, {-std::numeric_limits::infinity(), "-2147483647"}, + {std::numeric_limits::quiet_NaN(), "9223372036854775808"}, + {std::bit_cast(UINT64_C(0xfff8000000000001)), "9223372036854775808"}, + }; + for (const auto& test : cases) for (int mode : {0, 1, 2, 3}) { + Value value{}; _ZN3sce4Json5ValueC1Ed(&value, test.value); + String output{}; _ZN3sce4Json6StringC1EPKc(&output, "p"); + const auto requests = trace.requests.size(); + LegacySerializationTrace callback; + int status = 0; + if (mode == 0) status = _ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output); + else if (mode == 1) status = _ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(&value, ObserveLegacy, &callback); + else if (mode == 2) status = _ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_(&value, &output, ObserveLegacy, &callback); + else _ZNK3sce4Json5Value8toStringERNS0_6StringE(&value, &output); + Require(value.type == 4 && value.payload == std::bit_cast(test.value), "Formatting must preserve the original double payload bits, including signed zero and NaN payloads"); + const std::string expected = std::string("p") + test.text; + Require(status == (mode == 1 || mode == 2 ? 23 : 0), "Legacy serialization must preserve callback return values"); + Require(std::string(_ZNK3sce4Json6String5c_strEv(&output)) == (mode == 1 ? "p" : expected), "Legacy real formatting must preserve float32 conversion, truncation, sign and range behavior"); + if (mode == 1 || mode == 2) Require(callback.chunks == std::vector{mode == 1 ? std::string(test.text) : expected}, "Legacy callback serialization must emit the same formatted scalar through its selected output mode"); + std::vector allocations; + const auto length = std::strlen(test.text); + if (mode != 3) { + allocations = {24, 24, 32, (length + 1) | 16}; + Require(!value.parent && value.auxiliary && trace.live.at(value.auxiliary) == 24, "Legacy serialization must admit the root's allocator-backed auxiliary record"); + auto* fallback = reinterpret_cast(value.auxiliary)[2]; + Require(fallback && trace.live.at(fallback) == 32 && reinterpret_cast(fallback)->parent == &value, "The legacy root fallback must be allocator-backed and bound to its owning root"); + } else Require(!value.auxiliary, "Direct toString formatting must not perform serialization's metadata admission"); + if (mode != 1) allocations.push_back((length + 2) | 16); + Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == allocations, "Legacy formatting and metadata admission must preserve the observed allocation order and request sizes"); + _ZN3sce4Json6StringD1Ev(&output); _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Legacy scalar serialization must release all buffers and root metadata on destruction"); + } + for (bool once : {false, true}) for (int budget = 0; budget <= 5; ++budget) { + Value value{}; _ZN3sce4Json5ValueC1Ed(&value, 1.25); + String output{}; _ZN3sce4Json6StringC1EPKc(&output, "p"); + const auto requests = trace.requests.size(); const auto notices = trace.notifications; + trace.budget = budget; trace.once = once; + const int status = _ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output); + trace.budget = -1; trace.once = false; + const bool complete = budget >= 5 || (once && (budget == 1 || budget == 2)); + Require(status == 0 && std::string(_ZNK3sce4Json6String5c_strEv(&output)) == (complete ? "p1.250000" : "p"), "Legacy serialization must retain its observed partial output rather than inventing an out-of-memory return"); + const bool hasAuxiliary = budget >= 2 || (once && budget == 0); + Require((value.auxiliary != nullptr) == hasAuxiliary, "Rejected metadata admission must preserve the native partial auxiliary ownership"); + if (value.auxiliary) Require((reinterpret_cast(value.auxiliary)[2] != nullptr) == (budget >= 3 || (once && budget == 0)), "Fallback rejection must retain the admitted auxiliary record without fabricating a fallback"); + std::vector expected{24, 24}; + if (hasAuxiliary) expected.push_back(32); + if (budget != 0) expected.push_back(25); + if (budget >= 4 || (once && (budget == 1 || budget == 2))) expected.push_back(26); + const unsigned failed = once ? budget < 5 ? 1 : 0 : budget < 3 ? 2 : budget < 5 ? 1 : 0; + Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected && trace.notifications == notices + failed, "Legacy metadata and output failures must preserve exact allocator request order and notification counts"); + _ZN3sce4Json6StringD1Ev(&output); _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Every partial legacy serialization state must remain destructible without leaked ownership"); + } + Value registered{}; _ZN3sce4Json5ValueC1El(®istered, 7); + String registeredOutput{}; _ZN3sce4Json6StringC1EPKc(®isteredOutput, ""); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(®istered, ®isteredOutput) == 0, "Admit metadata before registering a legacy root callback"); + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(®istered, LegacyNull, ®istered) == 0, "Register an existing legacy root null callback"); + void* registeredAuxiliary = registered.auxiliary; + void* registeredFallback = reinterpret_cast(registeredAuxiliary)[2]; + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(®istered, ®isteredOutput) == 0 && registered.auxiliary == registeredAuxiliary && reinterpret_cast(registeredAuxiliary)[2] == registeredFallback && !reinterpret_cast(registeredAuxiliary)[0] && !reinterpret_cast(registeredAuxiliary)[1], "Legacy serialization must reset existing root callback fields while preserving auxiliary and fallback identity"); + _ZN3sce4Json6StringD1Ev(®isteredOutput); _ZN3sce4Json5ValueD1Ev(®istered); + Require(trace.live.size() == baseline, "Reset legacy root handlers must retain normal cleanup ownership"); + Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + const char json[] = "[1,2]"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, json, sizeof(json) - 1) == 0, "Parse legacy collection metadata input"); + auto* child = const_cast(_ZNK3sce4Json5ValueixEm(&root, 0)); + String first{}; _ZN3sce4Json6StringC1EPKc(&first, ""); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(child, &first) == 0 && !child->parent && child->auxiliary, "Serializing a legacy child as a root must detach its parent and admit root metadata"); + void* childAuxiliary = child->auxiliary; + void* childFallback = reinterpret_cast(childAuxiliary)[2]; + String whole{}; _ZN3sce4Json6StringC1EPKc(&whole, ""); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&root, &whole) == 0 && std::strcmp(_ZNK3sce4Json6String5c_strEv(&whole), "[1,2]") == 0, "Legacy collection serialization must preserve its actual contents"); + Require(child->parent == &root && !child->auxiliary && !trace.live.count(childAuxiliary) && !trace.live.count(childFallback), "Serializing the parent must rebind the child and release its former root auxiliary record and fallback"); + _ZN3sce4Json6StringD1Ev(&first); _ZN3sce4Json6StringD1Ev(&whole); _ZN3sce4Json5ValueD1Ev(&root); + Require(trace.live.size() == baseline && _ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Legacy metadata transitions must leave no outstanding ownership"); + return 0; +} diff --git a/core/libs/tests/GuestJsonNullRecovery.cpp b/core/libs/tests/GuestJsonNullRecovery.cpp new file mode 100644 index 0000000000..3ae6759e48 --- /dev/null +++ b/core/libs/tests/GuestJsonNullRecovery.cpp @@ -0,0 +1,114 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using Handler = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +using MismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct Context { int calls = 0; int requested = -1; const Value* parent = nullptr; const Value* replacement; const Value* reenter = nullptr; }; +static const Value* mismatchSource = nullptr; +static const Value& APS5_VABI Mismatch(ValueType requested, const Value* self, const Value* parent, void* context) { + mismatchSource = self; + auto& trace = *static_cast(context); + ++trace.calls; + trace.requested = requested; + trace.parent = parent; + if (trace.reenter) + Require(*_ZNK3sce4Json5Value10getIntegerEv(trace.reenter) == 0, "Reentrant mismatch recovery must return its typed default"); + return *trace.replacement; +} +static const Value& APS5_VABI Recover(ValueType requested, const Value* parent, void* context) { + auto& trace = *static_cast(context); + ++trace.calls; + trace.requested = requested; + trace.parent = parent; + if (trace.reenter) + Require(*_ZNK3sce4Json5Value10getIntegerEv(trace.reenter) == 0, "Reentrant null access must return its typed default"); + return *trace.replacement; +} +int main() { + std::uint8_t initializer = 0, inactive = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Value replacement{}, localReplacement{}; + _ZN3sce4Json5ValueC1El(&replacement, 99); + _ZN3sce4Json5ValueC1El(&localReplacement, 77); + Context global{0, -1, nullptr, &replacement, nullptr}; + Context local{0, -1, nullptr, &localReplacement, nullptr}; + Require(static_cast(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(&initializer, Recover, &global)) == 0x80848110, + "Global callback registration must require an active initializer"); + JsonTest::Allocator allocator; + Parameter parameter{&allocator, nullptr, 0, 2, {}}; + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize null recovery"); + Require(static_cast(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(&inactive, Recover, &global)) == 0x80848110, + "An inactive object cannot register a module-global callback"); + Require(static_cast(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(&initializer, nullptr, nullptr)) == 0x80848120, + "Global registration must reject a null callback"); + Require(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(&initializer, Recover, &global) == 0, "Register global null recovery"); + Require(static_cast(_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_(&initializer, Recover, &local)) == 0x80848112, + "Duplicate global registration must preserve its original callback and context"); + Value root{}; + _ZN3sce4Json5ValueC1Ev(&root); + const char text[] = "{\"n\":null,\"s\":\"text\"}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, text, sizeof(text) - 1) == 0, "Parse null recovery tree"); + const auto* null = _ZNK3sce4Json5ValueixEPKc(&root, "n"); + const auto* string = _ZNK3sce4Json5ValueixEPKc(&root, "s"); + const auto* missing = _ZNK3sce4Json5ValueixEPKc(&root, "missing"); + Require(missing->type == 0 && global.calls == 0, "Missing lookup must not invoke a typed null-access callback"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(null) == 99 && global.calls == 1 && global.requested == 2 && global.parent == &root, + "A typed null getter must call global recovery with its immediate parent"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(string) == 0 && global.calls == 1, + "A non-null type mismatch must not use null-access recovery"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(missing) == 99 && global.calls == 2 && global.parent == &root, + "The parser-owned fallback must retain its root parent"); + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&root, Recover, &local) == 0, + "The value callback setter must take this, callback and context"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(null) == 77 && local.calls == 1 && local.parent == &root && global.calls == 2, + "Root-local null recovery must take precedence over global recovery"); + local.reenter = null; + Require(!*_ZNK3sce4Json5Value10getBooleanEv(null) && local.calls == 2 && local.requested == 1, + "A mismatched callback result and reentrant getter must not recursively dispatch"); + local.reenter = nullptr; + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&root, nullptr, nullptr) == 0, + "A value-local callback may be unregistered"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(null) == 99 && global.calls == 3 && local.calls == 2, + "Unregistration must restore global recovery"); + Require(static_cast(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(const_cast(null), Recover, &local)) == 0x80848104, + "Owned children must reject local null callback registration"); + Context mismatch{0, -1, nullptr, &localReplacement, string}; + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&root, Mismatch, &mismatch) == 0, + "Register ancestor mismatch recovery"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(string) == 77 && mismatch.calls == 1 && mismatchSource == string && + mismatch.parent == &root && mismatch.requested == 2, + "Mismatch recovery must pass the requested type, source and immediate parent"); + Require(!*_ZNK3sce4Json5Value10getBooleanEv(string) && mismatch.calls == 2 && mismatch.requested == 1, + "A wrong-type recovery result must return the requested default without recursively dispatching"); + Require(!_ZNK3sce4Json5ValuecvbEv(string) && mismatch.calls == 3, + "Boolean conversion must consult mismatch recovery before ordinary truth conversion"); + Require(!_ZNK3sce4Json5ValuecvbEv(&root) && mismatch.calls == 4 && mismatchSource == &root && !mismatch.parent, + "A root boolean conversion must pass a null parent to mismatch recovery"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(null) == 77 && global.calls == 3 && mismatch.calls == 5, + "Root mismatch recovery must take precedence over module-global null recovery"); + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&root, Recover, &local) == 0, + "Restore root-local null recovery"); + Require(*_ZNK3sce4Json5Value10getIntegerEv(null) == 77 && local.calls == 3 && mismatch.calls == 5, + "Root-local null recovery must take precedence over mismatch recovery"); + _ZN3sce4Json5ValueD1Ev(&root); + _ZN3sce4Json5ValueD1Ev(&replacement); + _ZN3sce4Json5ValueD1Ev(&localReplacement); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate null recovery"); + Value after{}; + _ZN3sce4Json5ValueC1Ev(&after); + Require(*_ZNK3sce4Json5Value10getIntegerEv(&after) == 0 && global.calls == 3, "Termination must clear global recovery"); + _ZN3sce4Json5ValueD1Ev(&after); + return 0; +} diff --git a/core/libs/tests/GuestJsonObjectAllocator.cpp b/core/libs/tests/GuestJsonObjectAllocator.cpp new file mode 100644 index 0000000000..507a8ebe4a --- /dev/null +++ b/core/libs/tests/GuestJsonObjectAllocator.cpp @@ -0,0 +1,1127 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct StoredText { const char* characters; std::uint32_t length; std::uint32_t capacity; std::uint64_t reserved; }; +struct TreeNode { TreeNode* parent; TreeNode* left; TreeNode* right; StoredText key; Pair pair; std::uint8_t color; std::uint8_t nil; std::uint8_t reserved[6]; }; +struct TreeHeader { TreeNode* head; std::size_t count; std::uint64_t reserved; Value* owner; }; +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static_assert(sizeof(TreeNode) == 104 && offsetof(TreeNode, pair) == 48 && offsetof(TreeNode, color) == 96); +using ReadCallback = int (APS5_VABI*)(char&, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ObjectAllocationTrace { + std::map live; + std::vector requests; + std::set headers; + int budget = -1; + bool failOnce = false; + unsigned notifications = 0; +}; +static void* APS5_VABI AllocateObject(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.failOnce) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI FreeObject(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + if (trace.headers.erase(memory)) { + const auto* words = reinterpret_cast(memory); + Require(words[0] == 0xffffffff && words[1] == UINT64_MAX, "Object teardown must poison the header before freeing it"); + } + Require(trace.live.erase(memory) == 1, "Object resources must return through their owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyObject(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Object notification must identify the failed allocation"); ++trace.notifications; +} +static void* const ObjectVtable[]{nullptr, nullptr, reinterpret_cast(AllocateObject), reinterpret_cast(FreeObject), reinterpret_cast(NotifyObject)}; +struct ObjectAllocator { void* const* vtable = ObjectVtable; }; +static bool Owned(const ObjectAllocationTrace& trace, const void* memory, std::size_t bytes) { + const auto found = trace.live.find(const_cast(memory)); return found != trace.live.end() && found->second == bytes; +} +static Value* Entry(Object& object, const char* key) { + String text{}; _ZN3sce4Json6StringC1EPKc(&text, key); + auto* result = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &text); + _ZN3sce4Json6StringD1Ev(&text); return result; +} +static void CheckTree(const Object& object, ObjectAllocationTrace& trace, const std::map& entries) { + const auto* header = reinterpret_cast(object.items); + Require(Owned(trace, header, 32) && Owned(trace, header->head, 104) && !header->owner && header->count == entries.size(), "Object header and sentinel must be actual allocator-owned resources"); + trace.headers.insert(object.items); + auto* head = header->head; + Require(head->nil == 1 && head->color == 0, "Sentinel flags must mark a black nil node"); + std::vector nodes; + std::function visit = [&](TreeNode* node, TreeNode* parent) -> unsigned { + if (node == head) return 1; + Require(Owned(trace, node, 104) && !node->nil && node->color <= 1 && node->parent == parent, "Every tree node must retain its native allocation and parent link"); + Require(node->color == 0 || (node->left->color == 0 && node->right->color == 0), "Red nodes must have black children"); + const auto left = visit(node->left, node); nodes.push_back(node); const auto right = visit(node->right, node); + Require(left == right, "Every path must retain the same black height"); + Require(Owned(trace, node->pair.key.text, 24) && !node->pair.value.parent && !node->pair.value.auxiliary, "Node pairs must expose native string and value ownership"); + const auto found = entries.find(node->key.characters); + Require(found != entries.end() && found->second == &node->pair.value && std::strcmp(node->key.characters, _ZNK3sce4Json6String5c_strEv(&node->pair.key)) == 0, "Rotations must preserve entry identity and both key representations"); + return left + (node->color == 0); + }; + Require(head->parent->color == 0, "Tree root must remain black"); visit(head->parent, head); + Require(nodes.size() == entries.size() && (nodes.empty() ? head->left == head && head->right == head : head->left == nodes.front() && head->right == nodes.back()), "Sentinel endpoints must match the ordered entries"); + auto expected = entries.begin(); + for (const auto* node : nodes) { Require(expected->first == node->key.characters, "Tree iteration must preserve key order"); ++expected; } +} +static std::vector Keys(const Object& object) { + ObjectIterator current{}, end{}; _ZNK3sce4Json6Object5beginEv(¤t, &object); _ZNK3sce4Json6Object3endEv(&end, &object); + std::vector keys; + while (*reinterpret_cast(current.state) != *reinterpret_cast(end.state)) { + keys.emplace_back(_ZNK3sce4Json6String5c_strEv(&_ZNK3sce4Json6Object8iteratordeEv(¤t)->key)); + _ZN3sce4Json6Object8iteratorppEv(¤t); + } + _ZN3sce4Json6Object8iteratorD1Ev(¤t); _ZN3sce4Json6Object8iteratorD1Ev(&end); return keys; +} +static void CheckSerialized(Value& value, const char* expected) { + String output{}; _ZN3sce4Json6StringC1Ev(&output); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0 && std::strcmp(_ZNK3sce4Json6String5c_strEv(&output), expected) == 0, + "Object keys and string values must retain their distinct binary serialization behavior"); + _ZN3sce4Json6StringD1Ev(&output); +} +static void CheckBinaryKeys() { + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "a"); + const char suffix[]{0, 'b'}; _ZN3sce4Json6String6appendEPKcm(&key, suffix, sizeof(suffix)); + Value value{}; _ZN3sce4Json5ValueC1ERKNS0_6StringE(&value, &key); + CheckSerialized(value, R"("a\u0000b")"); _ZN3sce4Json5ValueD1Ev(&value); + Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &key); + _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&value, &object); CheckSerialized(value, R"({"a":null})"); + _ZN3sce4Json5ValueD1Ev(&value); _ZN3sce4Json6ObjectD1Ev(&object); _ZN3sce4Json6StringD1Ev(&key); + _ZN3sce4Json5ValueC1Ev(&value); + const char input[] = R"({"a":1,"a\u0000suffix":2})"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, input, sizeof(input) - 1) == 0 && _ZNK3sce4Json5Value5countEv(&value) == 2, + "The inspected parser drops the decoded zero byte without treating the two keys as duplicates"); + CheckSerialized(value, R"({"a":1,"asuffix":2})"); _ZN3sce4Json5ValueD1Ev(&value); + _ZN3sce4Json5ValueC1Ev(&value); + const char string[] = R"("a\u0000b")"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, string, sizeof(string) - 1) == 0, "Parse a zero-byte escape in a string value"); + CheckSerialized(value, R"("ab")"); _ZN3sce4Json5ValueD1Ev(&value); +} +static void CheckFailures(ObjectAllocationTrace& trace, const Value* moduleFallback) { + const auto baseline = trace.live.size(); + for (bool once : {false, true}) for (int budget = 0; budget <= 8; ++budget) { + Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); Entry(object, "a"); Entry(object, "c"); + trace.headers.insert(object.items); + String text{}; _ZN3sce4Json6StringC1EPKc(&text, "b"); + trace.budget = budget; trace.failOnce = once; + const auto notifications = trace.notifications; + auto* result = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &text); + trace.budget = -1; + const bool inserted = once ? budget != 4 : budget >= 5; + const auto expected = inserted ? std::size_t{3} : std::size_t{2}; + Require(_ZNK3sce4Json6Object4sizeEv(&object) == expected, "Failed object insertion must retain the observed partial entry count"); + const bool fallback = budget < 7; + Require((result == moduleFallback) == fallback, "Insertion must preserve its operation-level versus late-assignment failure result"); + std::vector expectedKeys{"a", "c"}; + if (inserted) { + if (once && (budget == 0 || budget == 2)) expectedKeys = {"", "a", "c"}; + else expectedKeys = {"a", budget == 8 ? "b" : "", "c"}; + } + Require(Keys(object) == expectedKeys, "Partial allocation failures must preserve the observed public keys and ordering"); + static constexpr unsigned persistentNotifications[]{3, 3, 3, 2, 1, 2, 1, 1, 0}; + Require(trace.notifications - notifications == (once ? static_cast(budget < 8) : persistentNotifications[budget]), "Object failures must notify for each real failed request"); + _ZN3sce4Json6StringD1Ev(&text); _ZN3sce4Json6ObjectD1Ev(&object); + Require(trace.live.size() == baseline, "All partial insertion states must be destructible without leaks"); + } + trace.failOnce = false; +} +static std::string AssignmentContents(const Object& object) { + std::string result; + auto* head = reinterpret_cast(object.items)->head; + auto* node = head->left; + while (node != head) { + result += node->pair.key.text ? reinterpret_cast(node->pair.key.text)->characters : "~"; + result += ":" + std::to_string(node->pair.value.type) + ":" + std::to_string(node->pair.value.payload) + ";"; + if (!node->right->nil) { node = node->right; while (!node->left->nil) node = node->left; } + else { auto* parent = node->parent; while (!parent->nil && node == parent->right) { node = parent; parent = parent->parent; } node = parent; } + } + return result; +} +static void CheckInsertions(ObjectAllocationTrace& trace) { + struct Case { bool duplicate; bool once; int budget; std::size_t count; unsigned notifications; bool state; const char* result; const char* contents; }; + const Case cases[]{ + {0, 0, -1, 3, 0, 1, "b", "a:2:11;b:2:22;c:2:33;"}, + {0, 0, 0, 2, 4, 0, "NONE", "a:2:11;c:2:33;"}, + {0, 0, 1, 2, 3, 1, "END", "a:2:11;c:2:33;"}, + {0, 0, 2, 2, 3, 1, "END", "a:2:11;c:2:33;"}, + {0, 0, 3, 2, 4, 1, "END", "a:2:11;c:2:33;"}, + {0, 0, 4, 2, 3, 1, "END", "a:2:11;c:2:33;"}, + {0, 0, 5, 2, 2, 1, "END", "a:2:11;c:2:33;"}, + {0, 0, 6, 2, 2, 1, "END", "a:2:11;c:2:33;"}, + {0, 0, 7, 2, 1, 1, "END", "a:2:11;c:2:33;"}, + {0, 0, 8, 3, 2, 1, "NULL", "a:2:11;~:0:11936128518282651045;c:2:33;"}, + {0, 0, 9, 3, 1, 1, "NULL", "a:2:11;~:0:11936128518282651045;c:2:33;"}, + {0, 0, 10, 3, 1, 1, "", "a:2:11;:0:11936128518282651045;c:2:33;"}, + {0, 0, 11, 3, 0, 1, "b", "a:2:11;b:2:22;c:2:33;"}, + {0, 0, 12, 3, 0, 1, "b", "a:2:11;b:2:22;c:2:33;"}, + {0, 1, -1, 3, 0, 1, "b", "a:2:11;b:2:22;c:2:33;"}, + {0, 1, 0, 3, 1, 0, "NONE", "a:2:11;b:2:22;c:2:33;"}, + {0, 1, 1, 3, 1, 1, "b", "b:0:11936128518282651045;a:2:11;c:2:33;"}, + {0, 1, 2, 3, 1, 1, "NULL", "a:2:11;~:0:11936128518282651045;c:2:33;"}, + {0, 1, 3, 3, 1, 1, "", "a:2:11;:0:11936128518282651045;c:2:33;"}, + {0, 1, 4, 3, 1, 1, "b", "b:0:11936128518282651045;a:2:11;c:2:33;"}, + {0, 1, 5, 3, 1, 1, "NULL", "a:2:11;~:0:11936128518282651045;c:2:33;"}, + {0, 1, 6, 3, 1, 1, "", "a:2:11;:0:11936128518282651045;c:2:33;"}, + {0, 1, 7, 2, 1, 1, "END", "a:2:11;c:2:33;"}, + {0, 1, 8, 3, 1, 1, "b", "a:2:11;b:0:11936128518282651045;c:2:33;"}, + {0, 1, 9, 3, 1, 1, "NULL", "a:2:11;~:0:11936128518282651045;c:2:33;"}, + {0, 1, 10, 3, 1, 1, "", "a:2:11;:0:11936128518282651045;c:2:33;"}, + {0, 1, 11, 3, 0, 1, "b", "a:2:11;b:2:22;c:2:33;"}, + {0, 1, 12, 3, 0, 1, "b", "a:2:11;b:2:22;c:2:33;"}, + {1, 0, -1, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 0, 0, 2, 4, 0, "NONE", "a:2:11;c:2:33;"}, + {1, 0, 1, 2, 3, 1, "END", "a:2:11;c:2:33;"}, + {1, 0, 2, 2, 3, 1, "END", "a:2:11;c:2:33;"}, + {1, 0, 3, 2, 4, 1, "END", "a:2:11;c:2:33;"}, + {1, 0, 4, 2, 3, 1, "END", "a:2:11;c:2:33;"}, + {1, 0, 5, 2, 1, 1, "a", "a:2:11;c:2:33;"}, + {1, 0, 6, 2, 1, 1, "a", "a:2:11;c:2:33;"}, + {1, 0, 7, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 0, 8, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 0, 9, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 0, 10, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 0, 11, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 0, 12, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, -1, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 0, 2, 1, 0, "NONE", "a:2:11;c:2:33;"}, + {1, 1, 1, 3, 1, 1, "a", "a:0:11936128518282651045;a:2:11;c:2:33;"}, + {1, 1, 2, 2, 1, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 3, 2, 1, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 4, 3, 1, 1, "a", "a:0:11936128518282651045;a:2:11;c:2:33;"}, + {1, 1, 5, 2, 1, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 6, 2, 1, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 7, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 8, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 9, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 10, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 11, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + {1, 1, 12, 2, 0, 1, "a", "a:2:11;c:2:33;"}, + }; + const auto baseline = trace.live.size(); + for (const auto& test : cases) { + Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + auto* first = Entry(object, "a"); _ZN3sce4Json5Value3setEl(first, 11); + _ZN3sce4Json5Value3setEl(Entry(object, "c"), 33); + Pair pair{}; _ZN3sce4Json6StringC1EPKc(&pair.key, test.duplicate ? "a" : "b"); + _ZN3sce4Json5ValueC1Ev(&pair.value); _ZN3sce4Json5Value3setEl(&pair.value, 22); + const auto notifications = trace.notifications; + trace.budget = test.budget; trace.failOnce = test.once; + ObjectIterator result{}; + Require(_ZN3sce4Json6Object6insertERKNS1_4PairE(&result, &object, &pair) == &result, "Insertion must return its result iterator"); + trace.budget = -1; trace.failOnce = false; + Require((result.state != nullptr) == test.state, "Insertion must preserve independent iterator allocation failure"); + auto* node = result.state ? *reinterpret_cast(result.state) : nullptr; + const char* key = !node ? "NONE" : node->nil ? "END" : node->pair.key.text ? reinterpret_cast(node->pair.key.text)->characters : "NULL"; + Require(std::strcmp(key, test.result) == 0, "Insertion must return the reference entry or end node on failure"); + Require(_ZNK3sce4Json6Object4sizeEv(&object) == test.count && AssignmentContents(object) == test.contents, "Insertion must preserve partial keys, inactive payloads and duplicate values"); + Require(trace.notifications - notifications == test.notifications, "Insertion must notify every failed owned allocation"); + Require(first->type == 2 && first->payload == 11 && pair.value.type == 2 && pair.value.payload == 22, "Insertion must preserve existing entries and its input value"); + _ZN3sce4Json6Object8iteratorD1Ev(&result); _ZN3sce4Json6StringD1Ev(&pair.key); _ZN3sce4Json5ValueD1Ev(&pair.value); _ZN3sce4Json6ObjectD1Ev(&object); + Require(trace.live.size() == baseline, "Insertion failure must release temporary allocations and iterator state"); + } +} +static void CheckIteratorFailures(ObjectAllocationTrace& trace) { + const auto baseline = trace.live.size(); + Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + Entry(object, "a"); + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "a"); + ObjectIterator source{}; _ZNK3sce4Json6Object5beginEv(&source, &object); + const auto retained = trace.live.size(); + for (unsigned operation = 0; operation < 5; ++operation) { + const auto requests = trace.requests.size(); + const auto notifications = trace.notifications; + ObjectIterator failed{}; trace.budget = 0; + switch (operation) { + case 0: _ZN3sce4Json6Object8iteratorC1Ev(&failed); break; + case 1: _ZN3sce4Json6Object8iteratorC1ERKS2_(&failed, &source); break; + case 2: _ZNK3sce4Json6Object5beginEv(&failed, &object); break; + case 3: _ZNK3sce4Json6Object3endEv(&failed, &object); break; + case 4: _ZNK3sce4Json6Object4findERKNS0_6StringE(&failed, &object, &key); break; + } + trace.budget = -1; + Require(!failed.state && trace.live.size() == retained, "Failed iterator allocation must retain no state or borrowed resources"); + Require(trace.requests.size() == requests + 1 && trace.requests.back() == 8 && trace.notifications == notifications + 1, "ObjectIterator failure must report its actual eight-byte allocation"); + _ZN3sce4Json6Object8iteratorD1Ev(&failed); + } + ObjectIterator absent{}, copy{}, target{}, end{}; + const auto requests = trace.requests.size(); + trace.budget = 0; + _ZN3sce4Json6Object8iteratorC1ERKS2_(©, &absent); + Require(_ZN3sce4Json6Object8iteratoraSERKS2_(&absent, &source) == &absent && !absent.state && !copy.state && trace.requests.size() == requests, "Copying absent state and assigning into absent state must not allocate"); + trace.budget = -1; + _ZN3sce4Json6Object8iteratorC1ERKS2_(&target, &source); + _ZNK3sce4Json6Object3endEv(&end, &object); + auto* storage = target.state; + trace.budget = 0; + _ZN3sce4Json6Object8iteratoraSERKS2_(&target, &end); + Require(target.state == storage && *reinterpret_cast(target.state) == reinterpret_cast(object.items)->head, "Assignment from end must preserve owned iterator state and copy the sentinel"); + _ZN3sce4Json6Object8iteratoraSERKS2_(&target, &absent); + Require(!target.state && trace.live.size() == retained + 1, "Assignment from absent state must release the destination iterator state"); + trace.budget = -1; + _ZN3sce4Json6Object8iteratorD1Ev(&end); _ZN3sce4Json6Object8iteratorD1Ev(&target); + Object missing{}; + _ZNK3sce4Json6Object5beginEv(&target, &missing); + Require(Owned(trace, target.state, 8) && !*reinterpret_cast(target.state), "A missing object must produce an allocated iterator with no position"); + _ZN3sce4Json6Object8iteratorD1Ev(&target); + _ZN3sce4Json6Object8iteratorD1Ev(&source); _ZN3sce4Json6StringD1Ev(&key); _ZN3sce4Json6ObjectD1Ev(&object); + Require(trace.live.size() == baseline, "ObjectIterator failure and assignment paths must release all owned state"); +} +static void CheckNestedAssignment(ObjectAllocationTrace& trace) { + const auto baseline = trace.live.size(); + Value source{}, destination{}; + _ZN3sce4Json5ValueC1Ev(&source); _ZN3sce4Json5ValueC1Ev(&destination); + const char input[] = "{\"nested\":{\"x\":1},\"array\":[2],\"text\":\"abc\",\"bool\":true,\"real\":1.25,\"nil\":null}"; + const char previous[] = "{\"old\":0}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&source, input, sizeof(input) - 1) == 0, "Parse nested assignment source"); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&destination, previous, sizeof(previous) - 1) == 0, "Parse nested assignment destination"); + auto* target = reinterpret_cast(destination.payload); + _ZN3sce4Json6ObjectaSERKS1_(target, reinterpret_cast(source.payload)); + String expected{}, actual{}; + _ZN3sce4Json6StringC1Ev(&expected); _ZN3sce4Json6StringC1Ev(&actual); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&source, &expected) == 0 && _ZN3sce4Json5Value9serializeERNS0_6StringE(&destination, &actual) == 0, "Serialize nested assignment results"); + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&expected), _ZNK3sce4Json6String5c_strEv(&actual)) == 0, "Assignment must preserve nested collections, strings and scalar types"); + auto* header = reinterpret_cast(target->items); + Require(header->owner == &destination, "Assigned root object must retain its owner"); + ObjectIterator iterator{}; _ZNK3sce4Json6Object5beginEv(&iterator, target); + for (std::size_t index = 0; index < header->count; ++index) { + const auto* pair = _ZNK3sce4Json6Object8iteratordeEv(&iterator); + Require(pair->value.parent == &destination, "Assigned entries must bind to the destination value"); + if (pair->value.type == 6 || pair->value.type == 7) { + auto* nestedStorage = *reinterpret_cast(pair->value.payload); + Value* owner = nullptr; std::memcpy(&owner, reinterpret_cast(nestedStorage) + 24, sizeof(owner)); + Require(owner == &pair->value, "Nested collection ownership must bind to the assigned entry"); + } + _ZN3sce4Json6Object8iteratorppEv(&iterator); + } + _ZN3sce4Json6Object8iteratorD1Ev(&iterator); + _ZN3sce4Json6StringD1Ev(&actual); _ZN3sce4Json6StringD1Ev(&expected); + _ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source); + Require(trace.live.size() == baseline, "Nested assignment must release all resources"); +} +static std::string NestedObjectFailureState(const Value& value, const Value* parent = nullptr) { + std::string state = std::to_string(value.type) + (value.parent == parent ? "p" : value.parent ? "x" : "-"); + if (value.type == 0) return state; + if (value.type < 5) return state + ":" + std::to_string(value.payload); + if (!value.payload) return state + "!"; + const auto* header = *reinterpret_cast(value.payload); + if (!header) return state + "?"; + if (value.type == 5) { + const auto* text = reinterpret_cast(header); + return state + ":" + std::string(text->characters, text->length); + } + const auto* words = reinterpret_cast(header); + state += words[3] == reinterpret_cast(&value) ? "o" : "!"; + state += std::to_string(words[1]) + "["; + if (!words[0]) return state + "?]"; + if (value.type == 6) { + const auto* head = reinterpret_cast(words[0]); + const auto* node = reinterpret_cast(head[1]); + unsigned steps = 0; + while (node != head) { + Require(++steps < 32, "Nested test array links must reach their sentinel"); + state += NestedObjectFailureState(*reinterpret_cast(node + 2), &value) + ";"; + node = reinterpret_cast(node[1]); + } + } else { + Require(value.type == 7, "Nested test collection must be an array or object"); + const auto* head = reinterpret_cast(words[0]); + unsigned steps = 0; + std::function visit = [&](const TreeNode* node) { + if (node == head) return; + Require(node && ++steps < 32, "Nested test tree links must reach their sentinel"); + visit(node->left); + state += node->pair.key.text ? _ZNK3sce4Json6String5c_strEv(&node->pair.key) : "~"; + state += "=" + NestedObjectFailureState(node->pair.value, &value) + ";"; + visit(node->right); + }; + visit(head->parent); + } + return state + "]"; +} +struct NestedReader { + unsigned operation; + Value* source; + Value* destination; + Pair* pair; + ObjectIterator* iterator; + std::size_t offset = 0; + unsigned calls = 0; +}; +static int APS5_VABI ReadWithCollectionOperation(char& character, void* context) { + auto& reader = *static_cast(context); + if (reader.offset == 2) { + ++reader.calls; + if (reader.operation == 0) _ZN3sce4Json5ArrayaSERKS1_(reinterpret_cast(reader.destination->payload), reinterpret_cast(reader.source->payload)); + else if (reader.operation == 1) _ZN3sce4Json6ObjectaSERKS1_(reinterpret_cast(reader.destination->payload), reinterpret_cast(reader.source->payload)); + else _ZN3sce4Json6Object6insertERKNS1_4PairE(reader.iterator, reinterpret_cast(reader.destination->payload), reader.pair); + } + const char text[] = "\"abc\""; + if (reader.offset == sizeof(text) - 1) return static_cast(0x80848101u); + character = text[reader.offset++]; + return 0; +} +static void CheckInheritedCollectionErrors(ObjectAllocationTrace& trace) { + const char* const failedStates[]{"6po2[3-:0;3-:0;]", "7po2[a=3-:0;c=3-:0;]", "7po2[a=0-;z=3p:99;]"}; + const char* const healthyStates[]{"6po2[3p:10;3p:20;]", "7po2[a=3p:11;c=3p:33;]", "7po2[a=2p:11;z=3p:99;]"}; + const auto baseline = trace.live.size(); + for (unsigned operation = 0; operation < 3; ++operation) for (bool fail : {false, true}) { + Value source{}, destination{}, parsed{}; + _ZN3sce4Json5ValueC1Ev(&source); _ZN3sce4Json5ValueC1Ev(&destination); _ZN3sce4Json5ValueC1Ev(&parsed); + const auto* input = operation == 0 ? "[10,20]" : "{\"a\":11,\"c\":33}"; + const auto* previous = operation == 0 ? "[99]" : "{\"z\":99}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&source, input, std::strlen(input)) == 0 && _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&destination, previous, std::strlen(previous)) == 0, "Prepare nested operation source and destination"); + const auto sourceState = NestedObjectFailureState(source); + Pair pair{}; _ZN3sce4Json6StringC1EPKc(&pair.key, "a"); _ZN3sce4Json5Value3setEl(&pair.value, 11); + ObjectIterator iterator{}; + NestedReader reader{operation, &source, &destination, &pair, &iterator}; + const auto notifications = trace.notifications; + trace.budget = fail ? 4 : -1; trace.failOnce = fail; + const auto status = static_cast(_ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&parsed, ReadWithCollectionOperation, &reader)); + trace.budget = -1; trace.failOnce = false; + const auto actual = NestedObjectFailureState(destination); + const auto* expected = fail ? failedStates[operation] : healthyStates[operation]; + if (actual != expected) std::fprintf(stderr, "Nested operation=%u fail=%d expected=%s actual=%s\n", operation, fail, expected, actual.c_str()); + Require(reader.calls == 1 && actual == expected, "Nested collection operations must retain inherited errors and preserve healthy behavior"); + Require(status == (fail ? 0x80848102u : 0u) && trace.notifications - notifications == (fail ? 1u : 0u), "Nested operation completion must retain the parser failure until outer completion"); + Require(NestedObjectFailureState(source) == sourceState, "Nested failure propagation must preserve its source"); + Require(parsed.type == 5 && std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(parsed.payload)), fail ? "bc" : "abc") == 0, "Nested calls must preserve the parser character-retry result"); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&parsed, "true", 4) == 0, "Outer completion must clear inherited errors before the next independent parse"); + _ZN3sce4Json6Object8iteratorD1Ev(&iterator); + _ZN3sce4Json6StringD1Ev(&pair.key); _ZN3sce4Json5ValueD1Ev(&pair.value); + _ZN3sce4Json5ValueD1Ev(&parsed); _ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source); + Require(trace.live.size() == baseline, "Nested operation completion must reclaim parser, collection and iterator resources"); + } +} +static void CheckParserObjectFailures(ObjectAllocationTrace& trace) { + static const char* const states[]{ + "0p", + "7po0[]", + "7po1[~=0-;]", + "7po1[=0p;]", + "7po1[a=0p;]", + "7po1[a=5p:;]", + "7po1[a=5p:abc;]", + "7po2[a=5p:abc;~=0-;]", + "7po2[a=5p:abc;=0p;]", + "7po2[a=5p:abc;b=0p;]", + "7po2[a=5p:abc;b=6po0[];]", + "7po2[a=5p:abc;b=6po1[3p:0;];]", + "7po2[a=5p:abc;b=6po1[3p:2;];]", + "7po1[=0-;]", + "7po1[a=5p:bc;]", + "7po2[=0-;a=5p:abc;]", + "7po2[a=5p:abc;=0-;]", + }; + struct Case { bool once; int budget; unsigned notifications; std::size_t requests; unsigned state; }; + const Case cases[]{ + {0, 0, 2, 2, 0}, + {0, 1, 2, 3, 0}, + {0, 2, 2, 4, 0}, + {0, 3, 2, 5, 1}, + {0, 4, 2, 6, 1}, + {0, 5, 1, 6, 1}, + {0, 6, 3, 9, 1}, + {0, 7, 3, 10, 1}, + {0, 8, 3, 11, 1}, + {0, 9, 3, 12, 1}, + {0, 10, 2, 12, 1}, + {0, 11, 1, 12, 1}, + {0, 12, 2, 14, 2}, + {0, 13, 1, 14, 2}, + {0, 14, 1, 15, 3}, + {0, 15, 1, 16, 4}, + {0, 16, 1, 17, 4}, + {0, 17, 3, 20, 5}, + {0, 18, 3, 21, 6}, + {0, 19, 3, 22, 6}, + {0, 20, 3, 23, 6}, + {0, 21, 3, 24, 6}, + {0, 22, 2, 24, 6}, + {0, 23, 1, 24, 6}, + {0, 24, 2, 26, 7}, + {0, 25, 1, 26, 7}, + {0, 26, 1, 27, 8}, + {0, 27, 1, 28, 9}, + {0, 28, 1, 29, 9}, + {0, 30, 1, 31, 10}, + {0, 31, 1, 32, 11}, + {0, 32, 1, 33, 11}, + {0, 33, 0, 33, 12}, + {1, 0, 1, 3, 0}, + {1, 1, 1, 4, 0}, + {1, 2, 1, 5, 0}, + {1, 3, 1, 5, 1}, + {1, 4, 1, 6, 1}, + {1, 5, 1, 6, 1}, + {1, 6, 1, 11, 13}, + {1, 7, 1, 12, 13}, + {1, 8, 1, 12, 2}, + {1, 9, 1, 13, 13}, + {1, 10, 1, 13, 2}, + {1, 11, 1, 12, 1}, + {1, 12, 1, 14, 13}, + {1, 13, 1, 14, 2}, + {1, 14, 1, 15, 3}, + {1, 15, 1, 16, 4}, + {1, 16, 1, 17, 4}, + {1, 17, 1, 19, 14}, + {1, 18, 1, 23, 15}, + {1, 19, 1, 24, 15}, + {1, 20, 1, 24, 7}, + {1, 21, 1, 25, 15}, + {1, 22, 1, 25, 7}, + {1, 23, 1, 24, 6}, + {1, 24, 1, 26, 16}, + {1, 25, 1, 26, 7}, + {1, 26, 1, 27, 8}, + {1, 27, 1, 28, 9}, + {1, 28, 1, 29, 9}, + {1, 30, 1, 31, 10}, + {1, 31, 1, 32, 11}, + {1, 32, 1, 33, 11}, + {1, 33, 0, 33, 12}, + }; + const auto baseline = trace.live.size(); + for (const auto& test : cases) { + Value value{}; _ZN3sce4Json5ValueC1Ev(&value); _ZN3sce4Json5Value3setEl(&value, 77); + const auto notifications = trace.notifications; + const auto requests = trace.requests.size(); + const char input[] = "{\"a\":\"abc\",\"b\":[2]}"; + trace.budget = test.budget; trace.failOnce = test.once; + const auto status = static_cast(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, input, sizeof(input) - 1)); + trace.budget = -1; trace.failOnce = false; + const auto actual = NestedObjectFailureState(value); + const bool matches = status == (test.notifications ? 0x80848102u : 0u) && actual == states[test.state] && trace.notifications - notifications == test.notifications && trace.requests.size() - requests == test.requests; + if (!matches) std::fprintf(stderr, "Object parse once=%d budget=%d expected=%s actual=%s status=%x requests=%zu notifications=%u\n", test.once, test.budget, states[test.state], actual.c_str(), status, trace.requests.size() - requests, trace.notifications - notifications); + Require(matches, "Object parser failures must retain reference partial members, ownership and allocation accounting"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Object parser failures must reclaim every partial key, member and metadata allocation"); + } +} +static void CheckNestedFailureCases(ObjectAllocationTrace& trace) { + static const char* const states[]{ + "7po1[z=3p:99;]", + "7po0[]", + "7po1[~=0-;]", + "7po1[=0-;]", + "7po1[a=0-;]", + "7po1[a=5p:;]", + "7po1[a=5p:abc;]", + "7po2[a=5p:abc;~=0-;]", + "7po2[a=5p:abc;=0-;]", + "7po2[a=5p:abc;b=0-;]", + "7po2[a=5p:abc;b=7-o0[];]", + "7po2[a=5p:abc;b=7po0[];]", + "7po2[a=5p:abc;b=7po1[~=0-;];]", + "7po2[a=5p:abc;b=7po1[=0-;];]", + "7po2[a=5p:abc;b=7po1[x=0-;];]", + "7po2[a=5p:abc;b=7po1[x=5p:;];]", + "7po2[a=5p:abc;b=7po1[x=5p:def;];]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];~=0-;]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];=0-;]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];c=0-;]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];c=6-o0[?];]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];c=6-o0[];]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];c=6po0[];]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[~=5p:abc;b=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[=5p:abc;b=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[a=5-:;b=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[~=5-:;b=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[=5-:;b=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[a=0-;b=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po2[b=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[a=5p:;b=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[a=5p:abc;~=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[a=5p:abc;=7po1[x=5p:def;];c=6po1[3p:42;];]", + "7po3[b=7-o0[];a=5p:abc;c=6po1[3p:42;];]", + "7po3[a=5p:abc;~=7-o0[];c=6po1[3p:42;];]", + "7po3[a=5p:abc;=7-o0[];c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=0-;c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7-o0[];c=6po1[3p:42;];]", + "7po2[a=5p:abc;c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po0[];c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po1[~=5p:def;];c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po1[=5p:def;];c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po1[x=5-:;];c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po1[~=5-:;];c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po1[=5-:;];c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po1[x=0-;];c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po1[x=5p:;];c=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];~=6po1[3p:42;];]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];=6po1[3p:42;];]", + "7po3[c=6-o0[];a=5p:abc;b=7po1[x=5p:def;];]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];~=6-o0[];]", + "7po3[a=5p:abc;b=7po1[x=5p:def;];=6-o0[];]", + "7p?", + "7p!0[?]", + "7p!0[]", + "7p!3[~=0-;]", + "7p!3[=0-;]", + "7p!3[b=0-;]", + "7p!3[b=7-o0[];]", + "7p!3[b=7-o1[~=0-;];]", + "7p!3[b=7-o1[=0-;];]", + "7p!3[b=7-o1[x=0-;];]", + "7p!3[b=7-o1[x=5p:;];]", + "7p!3[b=7-o1[x=5p:def;];]", + "7p!3[~=0-;b=7-o1[x=5p:def;];]", + "7p!3[=0-;b=7-o1[x=5p:def;];]", + "7p!3[a=0-;b=7-o1[x=5p:def;];]", + "7p!3[a=5-:;b=7-o1[x=5p:def;];]", + "7p!3[a=5-:abc;b=7-o1[x=5p:def;];]", + "7p!3[a=5-:abc;b=7-o1[x=5p:def;];~=0-;]", + "7p!3[a=5-:abc;b=7-o1[x=5p:def;];=0-;]", + "7p!3[a=5-:abc;b=7-o1[x=5p:def;];c=0-;]", + "7p!3[a=5-:abc;b=7-o1[x=5p:def;];c=6-o0[?];]", + "7p!3[a=5-:abc;b=7-o1[x=5p:def;];c=6-o0[];]", + "7p!3[a=5-:abc;b=7-o1[x=5p:def;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;~=7-o1[x=5p:def;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;=7-o1[x=5p:def;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=0-;c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o0[];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o1[~=5p:def;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o1[=5p:def;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o1[x=5-:;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o1[~=5-:;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o1[=5-:;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o1[x=0-;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o1[x=5p:;];c=6-o1[3p:42;];]", + "7p!3[b=7-o1[x=5p:def;];c=6-o1[3p:42;];]", + "7p!3[~=5-:abc;b=7-o1[x=5p:def;];c=6-o1[3p:42;];]", + "7p!3[=5-:abc;b=7-o1[x=5p:def;];c=6-o1[3p:42;];]", + "7p!3[a=0-;b=7-o1[x=5p:def;];c=6-o1[3p:42;];]", + "7p!3[a=5-:;b=7-o1[x=5p:def;];c=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o1[x=5p:def;];~=6-o1[3p:42;];]", + "7p!3[a=5-:abc;b=7-o1[x=5p:def;];=6-o1[3p:42;];]", + }; + struct Case { bool constructor; bool once; int budget; unsigned notifications; std::size_t requests; unsigned state; }; + const Case cases[]{ + {0, 0, 0, 1, 1, 0}, + {0, 0, 1, 1, 2, 0}, + {0, 0, 2, 12, 14, 1}, + {0, 0, 3, 12, 15, 1}, + {0, 0, 4, 12, 16, 1}, + {0, 0, 5, 13, 18, 1}, + {0, 0, 6, 12, 18, 1}, + {0, 0, 7, 13, 20, 1}, + {0, 0, 8, 12, 20, 1}, + {0, 0, 9, 12, 21, 1}, + {0, 0, 10, 12, 22, 1}, + {0, 0, 11, 12, 23, 1}, + {0, 0, 12, 11, 23, 1}, + {0, 0, 13, 11, 24, 1}, + {0, 0, 14, 10, 24, 1}, + {0, 0, 15, 10, 25, 1}, + {0, 0, 16, 9, 25, 1}, + {0, 0, 17, 9, 26, 2}, + {0, 0, 18, 10, 28, 2}, + {0, 0, 19, 10, 29, 3}, + {0, 0, 20, 9, 29, 4}, + {0, 0, 21, 9, 30, 4}, + {0, 0, 22, 9, 31, 5}, + {0, 0, 23, 8, 31, 6}, + {0, 0, 24, 8, 32, 6}, + {0, 0, 25, 8, 33, 6}, + {0, 0, 26, 8, 34, 6}, + {0, 0, 27, 9, 36, 6}, + {0, 0, 28, 8, 36, 6}, + {0, 0, 29, 9, 38, 6}, + {0, 0, 30, 8, 38, 6}, + {0, 0, 31, 8, 39, 6}, + {0, 0, 32, 8, 40, 6}, + {0, 0, 33, 9, 42, 6}, + {0, 0, 34, 9, 43, 6}, + {0, 0, 35, 8, 43, 6}, + {0, 0, 36, 8, 44, 6}, + {0, 0, 37, 7, 44, 6}, + {0, 0, 38, 7, 45, 6}, + {0, 0, 39, 6, 45, 6}, + {0, 0, 40, 6, 46, 6}, + {0, 0, 41, 6, 47, 6}, + {0, 0, 42, 6, 48, 6}, + {0, 0, 43, 6, 49, 6}, + {0, 0, 44, 5, 49, 6}, + {0, 0, 45, 7, 52, 7}, + {0, 0, 46, 6, 52, 7}, + {0, 0, 47, 6, 53, 8}, + {0, 0, 48, 5, 53, 9}, + {0, 0, 49, 5, 54, 9}, + {0, 0, 50, 5, 55, 9}, + {0, 0, 51, 5, 56, 10}, + {0, 0, 52, 5, 57, 10}, + {0, 0, 53, 5, 58, 11}, + {0, 0, 54, 5, 59, 11}, + {0, 0, 55, 8, 63, 11}, + {0, 0, 56, 8, 64, 11}, + {0, 0, 57, 8, 65, 11}, + {0, 0, 58, 9, 67, 11}, + {0, 0, 59, 8, 67, 11}, + {0, 0, 60, 9, 69, 11}, + {0, 0, 61, 8, 69, 11}, + {0, 0, 62, 8, 70, 11}, + {0, 0, 63, 8, 71, 11}, + {0, 0, 64, 8, 72, 11}, + {0, 0, 65, 7, 72, 11}, + {0, 0, 66, 7, 73, 11}, + {0, 0, 67, 6, 73, 11}, + {0, 0, 68, 6, 74, 11}, + {0, 0, 69, 5, 74, 11}, + {0, 0, 70, 7, 77, 12}, + {0, 0, 71, 6, 77, 12}, + {0, 0, 72, 6, 78, 13}, + {0, 0, 73, 5, 78, 14}, + {0, 0, 74, 5, 79, 14}, + {0, 0, 75, 5, 80, 15}, + {0, 0, 76, 4, 80, 16}, + {0, 0, 77, 4, 81, 16}, + {0, 0, 78, 5, 83, 16}, + {0, 0, 79, 4, 83, 16}, + {0, 0, 80, 5, 85, 16}, + {0, 0, 81, 4, 85, 16}, + {0, 0, 82, 5, 87, 16}, + {0, 0, 83, 4, 87, 16}, + {0, 0, 84, 4, 88, 16}, + {0, 0, 85, 6, 91, 16}, + {0, 0, 86, 5, 91, 16}, + {0, 0, 87, 5, 92, 16}, + {0, 0, 88, 4, 92, 16}, + {0, 0, 89, 4, 93, 16}, + {0, 0, 90, 3, 93, 16}, + {0, 0, 91, 3, 94, 16}, + {0, 0, 92, 2, 94, 16}, + {0, 0, 93, 2, 95, 16}, + {0, 0, 94, 3, 97, 16}, + {0, 0, 95, 2, 97, 16}, + {0, 0, 96, 2, 98, 16}, + {0, 0, 97, 1, 98, 16}, + {0, 0, 98, 3, 101, 17}, + {0, 0, 99, 2, 101, 17}, + {0, 0, 100, 2, 102, 18}, + {0, 0, 101, 1, 102, 19}, + {0, 0, 102, 1, 103, 19}, + {0, 0, 103, 2, 105, 20}, + {0, 0, 104, 1, 105, 21}, + {0, 0, 105, 1, 106, 21}, + {0, 0, 106, 1, 107, 22}, + {0, 0, 107, 1, 108, 22}, + {0, 0, 108, 1, 109, 22}, + {0, 0, 109, 0, 109, 23}, + {0, 1, 0, 1, 1, 0}, + {0, 1, 1, 1, 2, 0}, + {0, 1, 2, 1, 104, 23}, + {0, 1, 3, 1, 105, 23}, + {0, 1, 4, 1, 102, 24}, + {0, 1, 5, 1, 106, 25}, + {0, 1, 6, 1, 104, 24}, + {0, 1, 7, 1, 107, 25}, + {0, 1, 8, 1, 106, 23}, + {0, 1, 9, 1, 107, 23}, + {0, 1, 10, 1, 106, 26}, + {0, 1, 11, 1, 107, 26}, + {0, 1, 12, 1, 105, 27}, + {0, 1, 13, 1, 107, 28}, + {0, 1, 14, 1, 105, 29}, + {0, 1, 15, 1, 106, 29}, + {0, 1, 16, 1, 103, 30}, + {0, 1, 17, 1, 108, 26}, + {0, 1, 18, 1, 107, 27}, + {0, 1, 19, 1, 108, 28}, + {0, 1, 20, 1, 107, 29}, + {0, 1, 21, 1, 108, 29}, + {0, 1, 22, 1, 109, 31}, + {0, 1, 23, 1, 95, 23}, + {0, 1, 24, 1, 96, 23}, + {0, 1, 25, 1, 97, 23}, + {0, 1, 26, 1, 102, 32}, + {0, 1, 27, 1, 106, 33}, + {0, 1, 28, 1, 104, 32}, + {0, 1, 29, 1, 107, 33}, + {0, 1, 30, 1, 98, 23}, + {0, 1, 31, 1, 99, 23}, + {0, 1, 32, 1, 100, 23}, + {0, 1, 33, 1, 108, 23}, + {0, 1, 34, 1, 109, 23}, + {0, 1, 35, 1, 84, 34}, + {0, 1, 36, 1, 85, 34}, + {0, 1, 37, 1, 83, 35}, + {0, 1, 38, 1, 85, 36}, + {0, 1, 39, 1, 77, 37}, + {0, 1, 40, 1, 78, 37}, + {0, 1, 41, 1, 79, 37}, + {0, 1, 42, 1, 85, 38}, + {0, 1, 43, 1, 86, 38}, + {0, 1, 44, 1, 78, 39}, + {0, 1, 45, 1, 86, 38}, + {0, 1, 46, 1, 85, 35}, + {0, 1, 47, 1, 86, 36}, + {0, 1, 48, 1, 82, 37}, + {0, 1, 49, 1, 83, 37}, + {0, 1, 50, 1, 84, 37}, + {0, 1, 51, 1, 85, 38}, + {0, 1, 52, 1, 86, 38}, + {0, 1, 53, 1, 87, 40}, + {0, 1, 54, 1, 88, 40}, + {0, 1, 55, 1, 104, 23}, + {0, 1, 56, 1, 105, 23}, + {0, 1, 57, 1, 102, 41}, + {0, 1, 58, 1, 106, 42}, + {0, 1, 59, 1, 104, 41}, + {0, 1, 60, 1, 107, 42}, + {0, 1, 61, 1, 106, 23}, + {0, 1, 62, 1, 107, 23}, + {0, 1, 63, 1, 106, 43}, + {0, 1, 64, 1, 107, 43}, + {0, 1, 65, 1, 105, 44}, + {0, 1, 66, 1, 107, 45}, + {0, 1, 67, 1, 105, 46}, + {0, 1, 68, 1, 106, 46}, + {0, 1, 69, 1, 103, 40}, + {0, 1, 70, 1, 108, 43}, + {0, 1, 71, 1, 107, 44}, + {0, 1, 72, 1, 108, 45}, + {0, 1, 73, 1, 107, 46}, + {0, 1, 74, 1, 108, 46}, + {0, 1, 75, 1, 109, 47}, + {0, 1, 76, 1, 95, 23}, + {0, 1, 77, 1, 96, 23}, + {0, 1, 78, 1, 109, 23}, + {0, 1, 79, 1, 102, 48}, + {0, 1, 80, 1, 106, 49}, + {0, 1, 81, 1, 104, 48}, + {0, 1, 82, 1, 107, 49}, + {0, 1, 83, 1, 98, 23}, + {0, 1, 84, 1, 99, 23}, + {0, 1, 85, 1, 109, 23}, + {0, 1, 86, 1, 108, 23}, + {0, 1, 87, 1, 109, 23}, + {0, 1, 88, 1, 104, 50}, + {0, 1, 89, 1, 105, 50}, + {0, 1, 90, 1, 103, 51}, + {0, 1, 91, 1, 105, 52}, + {0, 1, 92, 1, 97, 19}, + {0, 1, 93, 1, 98, 19}, + {0, 1, 94, 1, 106, 21}, + {0, 1, 95, 1, 105, 21}, + {0, 1, 96, 1, 106, 21}, + {0, 1, 97, 1, 98, 16}, + {0, 1, 98, 1, 106, 21}, + {0, 1, 99, 1, 105, 51}, + {0, 1, 100, 1, 106, 52}, + {0, 1, 101, 1, 102, 19}, + {0, 1, 102, 1, 103, 19}, + {0, 1, 103, 1, 106, 21}, + {0, 1, 104, 1, 105, 21}, + {0, 1, 105, 1, 106, 21}, + {0, 1, 106, 1, 107, 22}, + {0, 1, 107, 1, 108, 22}, + {0, 1, 108, 1, 109, 22}, + {0, 1, 109, 0, 109, 23}, + {1, 0, 0, 1, 1, 53}, + {1, 0, 1, 2, 3, 54}, + {1, 0, 2, 1, 3, 54}, + {1, 0, 3, 1, 4, 55}, + {1, 0, 4, 5, 9, 56}, + {1, 0, 5, 4, 9, 56}, + {1, 0, 6, 4, 10, 57}, + {1, 0, 7, 3, 10, 58}, + {1, 0, 8, 3, 11, 58}, + {1, 0, 9, 3, 12, 58}, + {1, 0, 10, 3, 13, 59}, + {1, 0, 11, 3, 14, 59}, + {1, 0, 12, 6, 18, 59}, + {1, 0, 13, 6, 19, 59}, + {1, 0, 14, 6, 20, 59}, + {1, 0, 15, 7, 22, 59}, + {1, 0, 16, 6, 22, 59}, + {1, 0, 17, 7, 24, 59}, + {1, 0, 18, 6, 24, 59}, + {1, 0, 19, 6, 25, 59}, + {1, 0, 20, 6, 26, 59}, + {1, 0, 21, 6, 27, 59}, + {1, 0, 22, 5, 27, 59}, + {1, 0, 23, 5, 28, 59}, + {1, 0, 24, 4, 28, 59}, + {1, 0, 25, 4, 29, 59}, + {1, 0, 26, 3, 29, 59}, + {1, 0, 27, 5, 32, 60}, + {1, 0, 28, 4, 32, 60}, + {1, 0, 29, 4, 33, 61}, + {1, 0, 30, 3, 33, 62}, + {1, 0, 31, 3, 34, 62}, + {1, 0, 32, 3, 35, 63}, + {1, 0, 33, 2, 35, 64}, + {1, 0, 34, 4, 38, 65}, + {1, 0, 35, 3, 38, 65}, + {1, 0, 36, 3, 39, 66}, + {1, 0, 37, 2, 39, 67}, + {1, 0, 38, 2, 40, 67}, + {1, 0, 39, 2, 41, 68}, + {1, 0, 40, 1, 41, 69}, + {1, 0, 41, 3, 44, 70}, + {1, 0, 42, 2, 44, 70}, + {1, 0, 43, 2, 45, 71}, + {1, 0, 44, 1, 45, 72}, + {1, 0, 45, 1, 46, 72}, + {1, 0, 46, 2, 48, 73}, + {1, 0, 47, 1, 48, 74}, + {1, 0, 48, 1, 49, 74}, + {1, 0, 49, 1, 50, 74}, + {1, 0, 50, 0, 50, 75}, + {1, 1, 0, 1, 1, 53}, + {1, 1, 1, 1, 50, 75}, + {1, 1, 2, 1, 3, 54}, + {1, 1, 3, 1, 4, 55}, + {1, 1, 4, 1, 50, 75}, + {1, 1, 5, 1, 49, 76}, + {1, 1, 6, 1, 50, 77}, + {1, 1, 7, 1, 25, 78}, + {1, 1, 8, 1, 26, 78}, + {1, 1, 9, 1, 27, 78}, + {1, 1, 10, 1, 28, 79}, + {1, 1, 11, 1, 29, 79}, + {1, 1, 12, 1, 45, 75}, + {1, 1, 13, 1, 46, 75}, + {1, 1, 14, 1, 43, 80}, + {1, 1, 15, 1, 47, 81}, + {1, 1, 16, 1, 45, 80}, + {1, 1, 17, 1, 48, 81}, + {1, 1, 18, 1, 47, 75}, + {1, 1, 19, 1, 48, 75}, + {1, 1, 20, 1, 47, 82}, + {1, 1, 21, 1, 48, 82}, + {1, 1, 22, 1, 46, 83}, + {1, 1, 23, 1, 48, 84}, + {1, 1, 24, 1, 46, 85}, + {1, 1, 25, 1, 47, 85}, + {1, 1, 26, 1, 44, 79}, + {1, 1, 27, 1, 49, 82}, + {1, 1, 28, 1, 48, 83}, + {1, 1, 29, 1, 49, 84}, + {1, 1, 30, 1, 48, 85}, + {1, 1, 31, 1, 49, 85}, + {1, 1, 32, 1, 50, 86}, + {1, 1, 33, 1, 44, 87}, + {1, 1, 34, 1, 50, 75}, + {1, 1, 35, 1, 49, 88}, + {1, 1, 36, 1, 50, 89}, + {1, 1, 37, 1, 48, 90}, + {1, 1, 38, 1, 49, 90}, + {1, 1, 39, 1, 50, 91}, + {1, 1, 40, 1, 41, 69}, + {1, 1, 41, 1, 50, 75}, + {1, 1, 42, 1, 49, 92}, + {1, 1, 43, 1, 50, 93}, + {1, 1, 44, 1, 45, 72}, + {1, 1, 45, 1, 46, 72}, + {1, 1, 46, 1, 50, 75}, + {1, 1, 47, 1, 48, 74}, + {1, 1, 48, 1, 49, 74}, + {1, 1, 49, 1, 50, 74}, + {1, 1, 50, 0, 50, 75}, + }; + const auto baseline = trace.live.size(); + for (const auto& test : cases) { + Value source{}, destination{}; + _ZN3sce4Json5ValueC1Ev(&source); _ZN3sce4Json5ValueC1Ev(&destination); + const char input[] = "{\"a\":\"abc\",\"b\":{\"x\":\"def\"},\"c\":[42]}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&source, input, sizeof(input) - 1) == 0, "Prepare nested failure source"); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&destination, "{\"z\":99}", 8) == 0, "Prepare nested failure destination"); + const auto sourceState = NestedObjectFailureState(source); + Object copy{}; + auto* sourceObject = reinterpret_cast(source.payload); + const auto notifications = trace.notifications; + const auto requests = trace.requests.size(); + trace.budget = test.budget; trace.failOnce = test.once; + if (test.constructor) _ZN3sce4Json6ObjectC1ERKS1_(©, sourceObject); + else _ZN3sce4Json6ObjectaSERKS1_(reinterpret_cast(destination.payload), sourceObject); + trace.budget = -1; trace.failOnce = false; + std::string actual; + if (test.constructor) { + Value proxy{}; proxy.type = static_cast(7); proxy.payload = reinterpret_cast(©); + actual = NestedObjectFailureState(proxy); + } else actual = NestedObjectFailureState(destination); + const bool matches = actual == states[test.state] && trace.notifications - notifications == test.notifications && trace.requests.size() - requests == test.requests; + if (!matches) std::fprintf(stderr, "Nested object constructor=%d once=%d budget=%d expected=%s actual=%s requests=%zu notifications=%u\n", test.constructor, test.once, test.budget, states[test.state], actual.c_str(), trace.requests.size() - requests, trace.notifications - notifications); + Require(matches, "Nested object failure must retain the reference state, ownership and allocation accounting"); + Require(NestedObjectFailureState(source) == sourceState, "Nested allocation failures must preserve source contents and ownership"); + if (test.constructor) _ZN3sce4Json6ObjectD1Ev(©); + _ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source); + Require(trace.live.size() == baseline, "Nested object failure must reclaim every admitted resource"); + } +} +static void CheckAssignments(ObjectAllocationTrace& trace) { + struct Case { bool once; int budget; std::size_t count; unsigned notifications; const char* contents; }; + const Case cases[]{ + {0, -1, 2, 0, "a:2:11;c:2:33;"}, + {0, 0, 1, 1, "z:2:99;"}, + {0, 1, 1, 1, "z:2:99;"}, + {0, 2, 0, 6, ""}, + {0, 3, 0, 7, ""}, + {0, 4, 0, 6, ""}, + {0, 5, 0, 7, ""}, + {0, 6, 0, 6, ""}, + {0, 7, 0, 6, ""}, + {0, 8, 0, 5, ""}, + {0, 9, 0, 5, ""}, + {0, 10, 0, 4, ""}, + {0, 11, 1, 4, "~:2:0;"}, + {0, 12, 1, 4, "~:2:0;"}, + {0, 13, 1, 4, ":2:0;"}, + {0, 14, 1, 3, "a:2:11;"}, + {0, 15, 1, 4, "a:2:11;"}, + {0, 16, 1, 3, "a:2:11;"}, + {0, 17, 1, 4, "a:2:11;"}, + {0, 18, 1, 3, "a:2:11;"}, + {0, 19, 1, 3, "a:2:11;"}, + {0, 20, 1, 2, "a:2:11;"}, + {0, 21, 1, 2, "a:2:11;"}, + {0, 22, 1, 1, "a:2:11;"}, + {0, 23, 2, 2, "a:2:11;~:2:0;"}, + {0, 24, 2, 1, "a:2:11;~:2:0;"}, + {0, 25, 2, 1, "a:2:11;:2:0;"}, + {0, 26, 2, 0, "a:2:11;c:2:33;"}, + {1, -1, 2, 0, "a:2:11;c:2:33;"}, + {1, 0, 1, 1, "z:2:99;"}, + {1, 1, 1, 1, "z:2:99;"}, + {1, 2, 2, 1, "~:2:11;c:2:33;"}, + {1, 3, 2, 1, ":2:11;c:2:33;"}, + {1, 4, 2, 1, "~:2:11;c:2:33;"}, + {1, 5, 2, 1, ":2:11;c:2:33;"}, + {1, 6, 2, 1, "a:2:0;c:2:33;"}, + {1, 7, 2, 1, "a:2:0;c:2:33;"}, + {1, 8, 2, 1, "~:2:0;c:2:33;"}, + {1, 9, 2, 1, ":2:0;c:2:33;"}, + {1, 10, 1, 1, "c:2:33;"}, + {1, 11, 2, 1, "a:2:0;c:2:33;"}, + {1, 12, 2, 1, "~:2:0;c:2:33;"}, + {1, 13, 2, 1, ":2:0;c:2:33;"}, + {1, 14, 2, 1, "a:2:11;~:2:33;"}, + {1, 15, 2, 1, "a:2:11;:2:33;"}, + {1, 16, 2, 1, "a:2:11;~:2:33;"}, + {1, 17, 2, 1, "a:2:11;:2:33;"}, + {1, 18, 2, 1, "c:2:0;a:2:11;"}, + {1, 19, 2, 1, "c:2:0;a:2:11;"}, + {1, 20, 2, 1, "a:2:11;~:2:0;"}, + {1, 21, 2, 1, "a:2:11;:2:0;"}, + {1, 22, 1, 1, "a:2:11;"}, + {1, 23, 2, 1, "a:2:11;c:2:0;"}, + {1, 24, 2, 1, "a:2:11;~:2:0;"}, + {1, 25, 2, 1, "a:2:11;:2:0;"}, + {1, 26, 2, 0, "a:2:11;c:2:33;"}, + }; + const auto baseline = trace.live.size(); + for (const auto& test : cases) { + Object source{}, destination{}; + _ZN3sce4Json6ObjectC1Ev(&source); _ZN3sce4Json6ObjectC1Ev(&destination); + _ZN3sce4Json5Value3setEl(Entry(source, "a"), 11); + _ZN3sce4Json5Value3setEl(Entry(source, "c"), 33); + _ZN3sce4Json5Value3setEl(Entry(destination, "z"), 99); + Value owner{}; + reinterpret_cast(destination.items)->owner = &owner; + auto* previous = destination.items; + trace.headers.insert(previous); + const auto notifications = trace.notifications; + trace.budget = test.budget; trace.failOnce = test.once; + Require(_ZN3sce4Json6ObjectaSERKS1_(&destination, &source) == &destination, "Object assignment must return its destination"); + trace.budget = -1; trace.failOnce = false; + Require((destination.items == previous) == (test.budget == 0 || test.budget == 1), "Only initial replacement allocation failures preserve the old object header"); + Require(reinterpret_cast(destination.items)->owner == &owner, "Replacement trees must retain their destination owner"); + Require(_ZNK3sce4Json6Object4sizeEv(&destination) == test.count && AssignmentContents(destination) == test.contents, "Assignment failure must retain the reference partial entries and values"); + Require(trace.notifications - notifications == test.notifications, "Assignment must report each failed owned allocation"); + Require(AssignmentContents(source) == "a:2:11;c:2:33;", "Assignment must preserve the source"); + trace.headers.insert(destination.items); + _ZN3sce4Json6ObjectD1Ev(&destination); _ZN3sce4Json6ObjectD1Ev(&source); + Require(trace.live.size() == baseline, "Assignment failure must reclaim temporary and replaced resources"); + } + Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + _ZN3sce4Json5Value3setEl(Entry(object, "a"), 11); + auto* previous = object.items; + trace.headers.insert(previous); + _ZN3sce4Json6ObjectaSERKS1_(&object, &object); + Require(object.items != previous && AssignmentContents(object) == "a:2:11;", "Modern self-assignment must rebuild while preserving values"); + trace.headers.insert(object.items); + _ZN3sce4Json6ObjectD1Ev(&object); + Require(trace.live.size() == baseline, "Self-assignment must release its previous tree"); +} +int main() { + ObjectAllocationTrace trace; ObjectAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize object allocation contract"); + const auto baseline = trace.live.size(); + for (int budget : {0, 1}) { + Object partial{}; trace.budget = budget; _ZN3sce4Json6ObjectC1Ev(&partial); trace.budget = -1; + Require(!partial.items && _ZNK3sce4Json6Object4sizeEv(&partial) == 0 && trace.live.size() == baseline, "Failed object construction must reclaim partial header ownership"); + _ZN3sce4Json6ObjectD1Ev(&partial); + } + Object object{}; _ZN3sce4Json6ObjectC1Ev(&object); + std::map entries; + CheckTree(object, trace, entries); + for (unsigned index = 0; index < 32; ++index) { + char key[3]; std::snprintf(key, sizeof(key), "%02u", index * 13 % 32); + auto* value = Entry(object, key); _ZN3sce4Json5Value3setEl(value, index); + entries.emplace(key, value); CheckTree(object, trace, entries); + } + ObjectIterator current{}, copy{}; _ZNK3sce4Json6Object5beginEv(¤t, &object); _ZN3sce4Json6Object8iteratorC1ERKS2_(©, ¤t); + Require(Owned(trace, current.state, 8) && Owned(trace, copy.state, 8) && current.state != copy.state && *reinterpret_cast(current.state) == *reinterpret_cast(copy.state), "Object iterator copies must own one independent node pointer"); + _ZN3sce4Json6Object8iteratorD1Ev(©); _ZN3sce4Json6Object8iteratorD1Ev(¤t); + const auto beforeCopies = trace.live.size(); + for (int budget = 0; budget <= 10; ++budget) { + Object duplicate{}; trace.budget = budget; _ZN3sce4Json6ObjectC1ERKS1_(&duplicate, &object); trace.budget = -1; + Require(_ZNK3sce4Json6Object4sizeEv(&duplicate) == (budget >= 4 ? 32 : 0), "Partial tree copies retain the source count once the root allocation succeeds"); + if (duplicate.items) trace.headers.insert(duplicate.items); + _ZN3sce4Json6ObjectD1Ev(&duplicate); Require(trace.live.size() == beforeCopies, "Partial tree copies must release their actual nodes"); + } + for (unsigned index = 0; index < 32; ++index) { + char key[3]; std::snprintf(key, sizeof(key), "%02u", index * 7 % 32); + String text{}; _ZN3sce4Json6StringC1EPKc(&text, key); + const auto requests = trace.requests.size(); + const auto notifications = trace.notifications; + trace.budget = 0; + _ZN3sce4Json6Object5eraseERKNS0_6StringE(&object, &text); + _ZN3sce4Json6Object5eraseERKNS0_6StringE(&object, &text); + trace.budget = -1; + Require(trace.requests.size() == requests && trace.notifications == notifications, "Existing and missing key erasure must work without allocating or reporting allocation failure"); + _ZN3sce4Json6StringD1Ev(&text); + entries.erase(key); CheckTree(object, trace, entries); + } + _ZN3sce4Json6Object5clearEv(&object); _ZN3sce4Json6ObjectD1Ev(&object); + Require(trace.live.size() == baseline, "Tree operations must release all owned resources"); + Value null{}; _ZN3sce4Json5ValueC1Ev(&null); + const auto* moduleFallback = _ZNK3sce4Json5ValueixEm(&null, 0); + _ZN3sce4Json5ValueD1Ev(&null); + CheckFailures(trace, moduleFallback); + CheckIteratorFailures(trace); + CheckInsertions(trace); + CheckAssignments(trace); + CheckNestedAssignment(trace); + CheckNestedFailureCases(trace); + CheckParserObjectFailures(trace); + CheckInheritedCollectionErrors(trace); + CheckBinaryKeys(); + Require(trace.live.size() == baseline, "Binary-key operations must release their storage"); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Terminate object allocation contract"); + return 0; +} diff --git a/core/libs/tests/GuestJsonPairAllocator.cpp b/core/libs/tests/GuestJsonPairAllocator.cpp new file mode 100644 index 0000000000..786a6120e4 --- /dev/null +++ b/core/libs/tests/GuestJsonPairAllocator.cpp @@ -0,0 +1,575 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using NullHandler = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +using MismatchHandler = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +using FloatHandler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +using ElementHandler = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct PairTrace { + std::map live; + std::size_t generation = 0; + std::vector requests; + std::vector releases; + int budget = -1; + bool once = false; + unsigned notices = 0; +}; +static void* APS5_VABI AllocatePair(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); + Require(trace.live.emplace(memory, ++trace.generation).second, "Pair storage must have an independently owned allocation generation"); + return memory; +} +static void APS5_VABI FreePair(void*, void* memory, void* context) { + auto& trace = *static_cast(context); + trace.releases.push_back(memory ? trace.live.at(memory) : 0); + if (!memory) return; + Require(trace.live.erase(memory) == 1, "Pair teardown must release each allocation through its owner exactly once"); + std::free(memory); +} +static void APS5_VABI NotifyPair(void*, int error, std::size_t size, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == size, "Pair notification must identify the failed allocation request"); + ++trace.notices; +} +static void* const PairVtable[]{nullptr, nullptr, reinterpret_cast(AllocatePair), reinterpret_cast(FreePair), reinterpret_cast(NotifyPair)}; +struct PairAllocator { void* const* vtable = PairVtable; }; +static std::string Serialize(Value& value) { + String output{}; _ZN3sce4Json6StringC1EPKc(&output, ""); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0, "Constructed pair values must remain serializable"); + std::string text = _ZNK3sce4Json6String5c_strEv(&output); + _ZN3sce4Json6StringD1Ev(&output); + return text; +} +static void CheckMetadata(const Pair& pair, std::uint64_t pattern) { + Require(pair.reserved == pattern, "Pair construction must preserve the eight inactive bytes between key and value"); + Require(!pair.value.parent && !pair.value.auxiliary && pair.value.reserved == static_cast(pattern), "Pair value construction must initialize ownership without overwriting inactive reserved bytes"); +} +struct InspectArrayLink { InspectArrayLink* previous; InspectArrayLink* next; Value value; }; +struct InspectArray { InspectArrayLink* head; std::size_t count; std::uint64_t reserved; Value* owner; }; +struct InspectObjectLink { InspectObjectLink* parent; InspectObjectLink* left; InspectObjectLink* right; unsigned char key[24]; Pair pair; std::uint8_t color; std::uint8_t nil; unsigned char reserved[6]; }; +struct InspectObject { InspectObjectLink* head; std::size_t count; std::uint64_t reserved; Value* owner; }; +static_assert(sizeof(InspectArrayLink) == 48 && offsetof(InspectArrayLink, value) == 16); +static_assert(sizeof(InspectObjectLink) == 104 && offsetof(InspectObjectLink, nil) == 97 && offsetof(InspectObjectLink, pair) == 48); +static void Shape(Value& value, const Value* parent, std::string& output); +static int Identity(const Value* actual, const Value* expected) { return actual ? (actual == expected ? 1 : 2) : 0; } +static void TreeShape(InspectObjectLink* node, const Value* parent, std::string& output) { + if (node->nil) return; + TreeShape(node->left, parent, output); + Shape(node->pair.value, parent, output); + TreeShape(node->right, parent, output); +} +static void Shape(Value& value, const Value* parent, std::string& output) { + output += std::to_string(value.type) + ":" + std::to_string(Identity(value.parent, parent)); + if (value.type == 6) { + auto* header = *reinterpret_cast(value.payload); + if (!header) { output += "x"; return; } + output += "a" + std::to_string(header->count) + ":" + std::to_string(Identity(header->owner, &value)) + "["; + if (!header->head) { output += "missing]"; return; } + for (auto* node = header->head->next; node != header->head; node = node->next) Shape(node->value, &value, output); + output += "]"; + } + if (value.type == 7) { + auto* header = *reinterpret_cast(value.payload); + if (!header) { output += "x"; return; } + output += "o" + std::to_string(header->count) + ":" + std::to_string(Identity(header->owner, &value)) + "{"; + if (!header->head) { output += "missing}"; return; } + TreeShape(header->head->parent, &value, output); + output += "}"; + } + output += ";"; +} +static std::uint64_t Fingerprint(const std::vector& values, std::size_t first, std::size_t end, std::size_t generation = 0) { + std::uint64_t hash = UINT64_C(14695981039346656037); + for (auto i = first; i < end; ++i) { + hash ^= values[i] ? values[i] - generation : 0; + hash *= UINT64_C(1099511628211); + } + return hash; +} +struct Expected { + unsigned source; + bool once; + int budget; + int type; + const char* key; + const char* text; + const char* shape; + unsigned notifications; + std::size_t requests; + std::uint64_t requestOrder; + std::size_t constructionReleases; + std::uint64_t constructionOrder; + std::size_t destructionReleases; + std::uint64_t destructionOrder; +}; +static const Expected Cases[]{ + {0, 0, 0, 0, "", "null", "0:0;", 1, 1, UINT64_C(0xaf63d54c8601e0a7), 0, UINT64_C(0xcbf29ce484222325), 0, UINT64_C(0xcbf29ce484222325)}, + {0, 0, 1, 0, "", "null", "0:0;", 0, 1, UINT64_C(0xaf63d54c8601e0a7), 0, UINT64_C(0xcbf29ce484222325), 1, UINT64_C(0xaf63bc4c8601b62c)}, + {0, 1, 0, 0, "", "null", "0:0;", 1, 1, UINT64_C(0xaf63d54c8601e0a7), 0, UINT64_C(0xcbf29ce484222325), 0, UINT64_C(0xcbf29ce484222325)}, + {0, 1, 1, 0, "", "null", "0:0;", 0, 1, UINT64_C(0xaf63d54c8601e0a7), 0, UINT64_C(0xcbf29ce484222325), 1, UINT64_C(0xaf63bc4c8601b62c)}, + {1, 0, 0, 0, "", "null", "0:0;", 1, 1, UINT64_C(0xaf63d54c8601e0a7), 0, UINT64_C(0xcbf29ce484222325), 0, UINT64_C(0xcbf29ce484222325)}, + {1, 0, 1, 0, "", "null", "0:0;", 1, 2, UINT64_C(0x08841407b530b4f9), 0, UINT64_C(0xcbf29ce484222325), 1, UINT64_C(0xaf63bc4c8601b62c)}, + {1, 0, 2, 0, "key", "null", "0:0;", 0, 2, UINT64_C(0x08841407b530b4f9), 0, UINT64_C(0xcbf29ce484222325), 2, UINT64_C(0x08395307b4f1348c)}, + {1, 1, 0, 0, "", "null", "0:0;", 1, 1, UINT64_C(0xaf63d54c8601e0a7), 0, UINT64_C(0xcbf29ce484222325), 0, UINT64_C(0xcbf29ce484222325)}, + {1, 1, 1, 0, "", "null", "0:0;", 1, 2, UINT64_C(0x08841407b530b4f9), 0, UINT64_C(0xcbf29ce484222325), 1, UINT64_C(0xaf63bc4c8601b62c)}, + {1, 1, 2, 0, "key", "null", "0:0;", 0, 2, UINT64_C(0x08841407b530b4f9), 0, UINT64_C(0xcbf29ce484222325), 2, UINT64_C(0x08395307b4f1348c)}, + {2, 0, 0, 0, "", "null", "0:0;", 2, 2, UINT64_C(0x08842007b530c95d), 0, UINT64_C(0xcbf29ce484222325), 0, UINT64_C(0xcbf29ce484222325)}, + {2, 0, 1, 0, "", "null", "0:0;", 2, 3, UINT64_C(0xa922fa18e1c37583), 0, UINT64_C(0xcbf29ce484222325), 1, UINT64_C(0xaf63bc4c8601b62c)}, + {2, 0, 2, 0, "key", "null", "0:0;", 1, 3, UINT64_C(0xa922fa18e1c37583), 0, UINT64_C(0xcbf29ce484222325), 2, UINT64_C(0x08395307b4f1348c)}, + {2, 0, 3, 0, "key", "null", "0:0;", 1, 4, UINT64_C(0x29e493479f20d661), 1, UINT64_C(0xaf63be4c8601b992), 2, UINT64_C(0x08395307b4f1348c)}, + {2, 0, 4, 5, "key", "\"\"", "5:0;", 1, 5, UINT64_C(0x503caab364cc52b8), 0, UINT64_C(0xcbf29ce484222325), 4, UINT64_C(0xb345225e3644edb5)}, + {2, 0, 5, 5, "key", "\"recovery\"", "5:0;", 0, 5, UINT64_C(0x503caab364cc52b8), 0, UINT64_C(0xcbf29ce484222325), 5, UINT64_C(0x3f3fcc261a338844)}, + {2, 1, 0, 5, "", "\"recovery\"", "5:0;", 1, 4, UINT64_C(0x919fd647d9d25bc2), 0, UINT64_C(0xcbf29ce484222325), 3, UINT64_C(0xe1efc21870f169c3)}, + {2, 1, 1, 5, "", "\"recovery\"", "5:0;", 1, 5, UINT64_C(0x503caab364cc52b8), 0, UINT64_C(0xcbf29ce484222325), 4, UINT64_C(0xb345225e3644edb5)}, + {2, 1, 2, 0, "key", "null", "0:0;", 1, 3, UINT64_C(0xa922fa18e1c37583), 0, UINT64_C(0xcbf29ce484222325), 2, UINT64_C(0x08395307b4f1348c)}, + {2, 1, 3, 0, "key", "null", "0:0;", 1, 4, UINT64_C(0x29e493479f20d661), 1, UINT64_C(0xaf63be4c8601b992), 2, UINT64_C(0x08395307b4f1348c)}, + {2, 1, 4, 5, "key", "\"\"", "5:0;", 1, 5, UINT64_C(0x503caab364cc52b8), 0, UINT64_C(0xcbf29ce484222325), 4, UINT64_C(0xb345225e3644edb5)}, + {2, 1, 5, 5, "key", "\"recovery\"", "5:0;", 0, 5, UINT64_C(0x503caab364cc52b8), 0, UINT64_C(0xcbf29ce484222325), 5, UINT64_C(0x3f3fcc261a338844)}, + {3, 0, 0, 0, "", "null", "0:0;", 2, 2, UINT64_C(0x08842007b530c95d), 0, UINT64_C(0xcbf29ce484222325), 0, UINT64_C(0xcbf29ce484222325)}, + {3, 0, 1, 0, "", "null", "0:0;", 2, 3, UINT64_C(0xa922fa18e1c37583), 0, UINT64_C(0xcbf29ce484222325), 1, UINT64_C(0xaf63bc4c8601b62c)}, + {3, 0, 2, 0, "key", "null", "0:0;", 1, 3, UINT64_C(0xa922fa18e1c37583), 0, UINT64_C(0xcbf29ce484222325), 2, UINT64_C(0x08395307b4f1348c)}, + {3, 0, 3, 0, "key", "null", "0:0;", 1, 4, UINT64_C(0x29e49b479f20e3f9), 1, UINT64_C(0xaf63be4c8601b992), 2, UINT64_C(0x08395307b4f1348c)}, + {3, 0, 4, 0, "key", "null", "0:0;", 1, 5, UINT64_C(0x50576bb364e2af63), 2, UINT64_C(0x0824ed07b4dfde30), 2, UINT64_C(0x08395307b4f1348c)}, + {3, 0, 5, 7, "key", "{}", "7:0o0:1{};", 1, 6, UINT64_C(0x673b44d46d2fced9), 1, UINT64_C(0xaf63bd4c8601b7df), 5, UINT64_C(0x3f3fcc261a338844)}, + {3, 0, 6, 7, "key", "{}", "7:0o0:1{};", 1, 7, UINT64_C(0x9984a5f5883c36c3), 1, UINT64_C(0xaf63bb4c8601b479), 5, UINT64_C(0x3f3fcc261a338844)}, + {3, 0, 7, 7, "key", "{}", "7:0o0:1{};", 12, 19, UINT64_C(0x57b5704e320be159), 2, UINT64_C(0x08218e07b4dd06ec), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 8, 7, "key", "{}", "7:0o0:1{};", 12, 20, UINT64_C(0xf367fb55d54c9203), 3, UINT64_C(0x1e87e8189340ae76), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 9, 7, "key", "{}", "7:0o0:1{};", 13, 22, UINT64_C(0xf5aab40101cc37d3), 4, UINT64_C(0xf241d4ba207d8e95), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 10, 7, "key", "{}", "7:0o0:1{};", 12, 22, UINT64_C(0xf5aab40101cc37d3), 5, UINT64_C(0x7e6b1d36db19d80d), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 11, 7, "key", "{}", "7:0o0:1{};", 13, 24, UINT64_C(0xfe76783938049f49), 6, UINT64_C(0x74e8056682af8fd0), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 12, 7, "key", "{}", "7:0o0:1{};", 12, 24, UINT64_C(0xfe76783938049f49), 7, UINT64_C(0x7693bb7c7546b046), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 13, 7, "key", "{}", "7:0o0:1{};", 13, 26, UINT64_C(0xde319778ed12258b), 8, UINT64_C(0x4468dd3409107207), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 14, 7, "key", "{}", "7:0o0:1{};", 12, 26, UINT64_C(0xde319778ed12258b), 9, UINT64_C(0xad55aca26461f6ad), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 15, 7, "key", "{}", "7:0o0:1{};", 12, 27, UINT64_C(0xc7ec986f14c73639), 10, UINT64_C(0x4235c494c06acdf8), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 16, 7, "key", "{}", "7:0o0:1{};", 14, 30, UINT64_C(0x8a81bd5371368fab), 12, UINT64_C(0x57e8bc377377a1a8), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 17, 7, "key", "{}", "7:0o0:1{};", 13, 30, UINT64_C(0x8a81bd5371368fab), 13, UINT64_C(0x1268d2826954245d), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 18, 7, "key", "{}", "7:0o0:1{};", 13, 31, UINT64_C(0x96301b105a1ce429), 13, UINT64_C(0xf35671decbb938c3), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 19, 7, "key", "{}", "7:0o0:1{};", 12, 31, UINT64_C(0x96301b105a1ce429), 14, UINT64_C(0x0cab5608e565adf6), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 20, 7, "key", "{}", "7:0o0:1{};", 12, 32, UINT64_C(0x3bc91ee93ed610f9), 15, UINT64_C(0x794fa5db173e30f0), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 21, 7, "key", "{}", "7:0o0:1{};", 11, 32, UINT64_C(0x3bc91ee93ed610f9), 16, UINT64_C(0x41fe94923964f0cb), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 22, 7, "key", "{}", "7:0o0:1{};", 11, 33, UINT64_C(0x8b101e49bc1d3b51), 17, UINT64_C(0xbbeef34e524f8b27), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 23, 7, "key", "{}", "7:0o0:1{};", 10, 33, UINT64_C(0x8b101e49bc1d3b51), 18, UINT64_C(0x655747e04dd5b78a), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 24, 7, "key", "{}", "7:0o0:1{};", 10, 34, UINT64_C(0x4670bb37ce1cd743), 19, UINT64_C(0x575dab813e787668), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 25, 7, "key", "{}", "7:0o0:1{};", 11, 36, UINT64_C(0xbdb571159b86105b), 21, UINT64_C(0xad7895c02a7368e9), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 26, 7, "key", "{}", "7:0o0:1{};", 10, 36, UINT64_C(0xbdb571159b86105b), 22, UINT64_C(0xca7465d28bfd5461), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 27, 7, "key", "{}", "7:0o0:1{};", 10, 37, UINT64_C(0x44d2b5c68fb25c01), 22, UINT64_C(0xad57f867f29b0856), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 28, 7, "key", "{}", "7:0o0:1{};", 9, 37, UINT64_C(0x44d2b5c68fb25c01), 23, UINT64_C(0xa4f6f9a06ded132c), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {3, 0, 29, 7, "key", "{\"\":null}", "7:0o1:1{0:0;};", 9, 38, UINT64_C(0x98134cae02268541), 23, UINT64_C(0xa4f6f9a06ded132c), 6, UINT64_C(0x5883fada9831d117)}, + {3, 0, 30, 7, "key", "{\"\":null}", "7:0o1:1{0:0;};", 10, 40, UINT64_C(0xef7eec8809648b01), 23, UINT64_C(0xa4f6f9a06ded132c), 7, UINT64_C(0x6c521e5e6410ac8b)}, + {3, 0, 31, 7, "key", "{\"\":null}", "7:0o1:1{0:0;};", 10, 41, UINT64_C(0xe9e59080116a59f1), 23, UINT64_C(0xa4f6f9a06ded132c), 8, UINT64_C(0x86a3a0747ab65d86)}, + {3, 0, 32, 7, "key", "{\"a\":null}", "7:0o1:1{0:0;};", 9, 41, UINT64_C(0xe9e59080116a59f1), 23, UINT64_C(0xa4f6f9a06ded132c), 9, UINT64_C(0xacf3512bbda582e0)}, + {3, 0, 33, 7, "key", "{\"a\":null}", "7:0o1:1{0:0;};", 9, 42, UINT64_C(0xeb3e6815095e9583), 24, UINT64_C(0x7e7974b5e6a575d5), 9, UINT64_C(0xacf3512bbda582e0)}, + {3, 0, 34, 7, "key", "{\"a\":[]}", "7:0o1:1{6:0a0:1[missing]};", 10, 44, UINT64_C(0xfbc65d9e64e2dd2b), 24, UINT64_C(0x0d47adecfbfc430c), 11, UINT64_C(0xf238d2bde87a012f)}, + {3, 0, 35, 7, "key", "{\"a\":[]}", "7:0o1:1{6:0a0:1[];};", 9, 44, UINT64_C(0xfbc65d9e64e2dd2b), 24, UINT64_C(0x0d47adecfbfc430c), 12, UINT64_C(0xaf25e2b8d279d422)}, + {3, 0, 36, 7, "key", "{\"a\":[]}", "7:0o1:1{6:0a0:1[];};", 9, 45, UINT64_C(0x62622be92d64ec21), 24, UINT64_C(0x88b696849119ed00), 12, UINT64_C(0xaf25e2b8d279d422)}, + {3, 0, 37, 7, "key", "{\"a\":[]}", "7:0o1:1{6:1a0:1[];};", 9, 46, UINT64_C(0xd5087cc731249313), 26, UINT64_C(0x945498db768529bf), 12, UINT64_C(0x1402fa0bcc4f6376)}, + {3, 0, 38, 7, "key", "{\"a\":[]}", "7:0o1:1{6:1a0:1[];};", 9, 47, UINT64_C(0xdb649185d9c21c79), 26, UINT64_C(0xf414310c6d6a89e5), 12, UINT64_C(0x1402fa0bcc4f6376)}, + {3, 0, 39, 7, "key", "{\"a\":[]}", "7:0o1:1{6:1a0:1[];};", 9, 48, UINT64_C(0x82d36a62e335fbeb), 27, UINT64_C(0x02643bbfdf0d87ea), 12, UINT64_C(0x11ebb0aab788d892)}, + {3, 0, 40, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 8, 48, UINT64_C(0x82d36a62e335fbeb), 27, UINT64_C(0x02643bbfdf0d87ea), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 41, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 8, 49, UINT64_C(0xb283cbd1b5b5f209), 28, UINT64_C(0xa0a087072c09fde7), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 42, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 8, 50, UINT64_C(0x8a9162aba4d6acf3), 29, UINT64_C(0x9c20542712c0a0ef), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 43, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 9, 52, UINT64_C(0x50f37a8bb3884709), 30, UINT64_C(0xcfe6f26979584abc), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 44, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 8, 52, UINT64_C(0x50f37a8bb3884709), 31, UINT64_C(0xf18fa8ba86856bca), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 45, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 9, 54, UINT64_C(0x62ceafb6eec98ffb), 32, UINT64_C(0xa11b393a4792a7cd), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 46, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 8, 54, UINT64_C(0x62ceafb6eec98ffb), 33, UINT64_C(0x9fda7bb9fe326757), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 47, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 8, 55, UINT64_C(0x6b6a32eab66db2f9), 34, UINT64_C(0x7cc8e9986e8bf23c), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 48, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 8, 56, UINT64_C(0xaecb93612cbf336b), 35, UINT64_C(0x684ccd1b16fbce2e), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 49, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 8, 57, UINT64_C(0x4858d152b25ec04b), 36, UINT64_C(0xf3d7ebb38a437d51), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 50, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 7, 57, UINT64_C(0x4858d152b25ec04b), 37, UINT64_C(0x3c9b54e75e043a07), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 51, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 7, 58, UINT64_C(0xfa7380bc30541f43), 38, UINT64_C(0x557d299a96c25a7a), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 52, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 6, 58, UINT64_C(0xfa7380bc30541f43), 39, UINT64_C(0x11fc770274e9a8ea), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 53, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 6, 59, UINT64_C(0x72407261f2c46ee9), 40, UINT64_C(0x8be5dea685297ad5), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 54, 7, "key", "{\"a\":[true]}", "7:0o1:1{6:1a1:1[1:1;];};", 5, 59, UINT64_C(0x72407261f2c46ee9), 41, UINT64_C(0xc570cb729193a42f), 13, UINT64_C(0xe364508a410c0844)}, + {3, 0, 55, 7, "key", "{\"a\":[true],\"\":null}", "7:0o2:1{6:1a1:1[1:1;];0:0;};", 7, 62, UINT64_C(0xdd828768b0673cb1), 41, UINT64_C(0xc570cb729193a42f), 14, UINT64_C(0xd51537a7c0e9393d)}, + {3, 0, 56, 7, "key", "{\"a\":[true],\"\":null}", "7:0o2:1{6:1a1:1[1:1;];0:0;};", 6, 62, UINT64_C(0xdd828768b0673cb1), 41, UINT64_C(0xc570cb729193a42f), 15, UINT64_C(0x42cc3f104f6e4243)}, + {3, 0, 57, 7, "key", "{\"a\":[true],\"\":null}", "7:0o2:1{6:1a1:1[1:1;];0:0;};", 6, 63, UINT64_C(0x6d02e9bcd600b499), 41, UINT64_C(0xc570cb729193a42f), 16, UINT64_C(0x2a0391b8de74bd18)}, + {3, 0, 58, 7, "key", "{\"a\":[true],\"k\":null}", "7:0o2:1{6:1a1:1[1:1;];0:0;};", 5, 63, UINT64_C(0x6d02e9bcd600b499), 41, UINT64_C(0xc570cb729193a42f), 17, UINT64_C(0xb4aa802afc47ece0)}, + {3, 0, 59, 7, "key", "{\"a\":[true],\"k\":null}", "7:0o2:1{6:1a1:1[1:1;];0:0;};", 5, 64, UINT64_C(0x5f482a0d0dc8019b), 42, UINT64_C(0x65054fd4e246057e), 17, UINT64_C(0xb4aa802afc47ece0)}, + {3, 0, 60, 7, "key", "{\"a\":[true],\"k\":\"\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 5, 65, UINT64_C(0x4b4d917e57f86132), 41, UINT64_C(0xc570cb729193a42f), 19, UINT64_C(0x62909e1cec7ae1d3)}, + {3, 0, 61, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 65, UINT64_C(0x4b4d917e57f86132), 41, UINT64_C(0xc570cb729193a42f), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 62, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 66, UINT64_C(0x972352fcc3940a32), 42, UINT64_C(0x28714bacd96afc31), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 63, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 67, UINT64_C(0x9ac6fafb0fd4419a), 43, UINT64_C(0x642ec0bd91056886), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 64, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 68, UINT64_C(0x06fc689c01f51722), 44, UINT64_C(0xa43d450f6a509362), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 65, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 5, 70, UINT64_C(0x0bea874fe70c8b8c), 45, UINT64_C(0x1fceee1263cab1b3), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 66, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 70, UINT64_C(0x0bea874fe70c8b8c), 46, UINT64_C(0xc764841a6cf73959), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 67, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 5, 72, UINT64_C(0x5cf6bc91b66abd26), 47, UINT64_C(0xca2d698260e6ceec), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 68, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 72, UINT64_C(0x5cf6bc91b66abd26), 48, UINT64_C(0x176b5b2a4ffd504a), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 69, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 73, UINT64_C(0x78e6de7db4f012a6), 49, UINT64_C(0xfce7936c92f12f1b), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 70, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 74, UINT64_C(0x783a50ac559170ce), 50, UINT64_C(0x91a5fbcf95a76d5d), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 71, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 5, 76, UINT64_C(0x5a15fa6e26c98802), 52, UINT64_C(0x23a18c828327ce44), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 72, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 5, 77, UINT64_C(0xa82a00a0586d91aa), 52, UINT64_C(0xcae276259cf4c86c), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 73, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 77, UINT64_C(0xa82a00a0586d91aa), 53, UINT64_C(0xeaa902eb2ee886e5), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 74, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 4, 78, UINT64_C(0x626f4aa7f91ef32c), 54, UINT64_C(0x486d2771cfef87e7), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 75, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 3, 78, UINT64_C(0x626f4aa7f91ef32c), 55, UINT64_C(0xb7e4a214cb3bfcc8), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 76, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 3, 79, UINT64_C(0xb91abd6c8103cf8a), 56, UINT64_C(0x7862cb1cd7eccd70), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 77, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 2, 79, UINT64_C(0xb91abd6c8103cf8a), 57, UINT64_C(0x77989b1f55d4e559), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 78, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 2, 80, UINT64_C(0x8b4ae15f38aa07ee), 58, UINT64_C(0x7d721ed202be022f), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 79, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 2, 81, UINT64_C(0x5a447acd48eac8b2), 59, UINT64_C(0x4bb22e9ffd64c5f0), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 80, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 2, 82, UINT64_C(0x4e1a64d2e7c37f86), 61, UINT64_C(0x80ae7b36aecc0210), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 81, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 2, 83, UINT64_C(0x7a59405fd132616a), 61, UINT64_C(0x7f28d8f6eb248efd), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 82, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 1, 83, UINT64_C(0x7a59405fd132616a), 62, UINT64_C(0x048d19db3da2b56b), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 0, 83, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 3, 86, UINT64_C(0x9f5b0f8c3ddf0238), 62, UINT64_C(0x048d19db3da2b56b), 21, UINT64_C(0x690a605d38c59743)}, + {3, 0, 84, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 2, 86, UINT64_C(0x9f5b0f8c3ddf0238), 62, UINT64_C(0x048d19db3da2b56b), 22, UINT64_C(0x6a3f1a2a443276c5)}, + {3, 0, 85, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 2, 87, UINT64_C(0xa6a9474d21df7fca), 62, UINT64_C(0x048d19db3da2b56b), 23, UINT64_C(0x3f954ced238070f8)}, + {3, 0, 86, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 1, 87, UINT64_C(0xa6a9474d21df7fca), 62, UINT64_C(0x048d19db3da2b56b), 24, UINT64_C(0x601715e2f78af79c)}, + {3, 0, 87, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 1, 88, UINT64_C(0x112412108ec65a9e), 63, UINT64_C(0x333ba418287f24ee), 24, UINT64_C(0x601715e2f78af79c)}, + {3, 0, 88, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 1, 89, UINT64_C(0xe6a5a8229b0c9002), 64, UINT64_C(0x72c6e24c51035e34), 24, UINT64_C(0x601715e2f78af79c)}, + {3, 0, 89, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 90, UINT64_C(0xf80cd4cd7658e9c6), 63, UINT64_C(0xe3718003aacfdf89), 27, UINT64_C(0x23c931a0f86ee070)}, + {3, 0, 90, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 91, UINT64_C(0xd6b74720191512aa), 63, UINT64_C(0x859ce136ed5a19e3), 27, UINT64_C(0x23c931a0f86ee070)}, + {3, 0, 91, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 1, 92, UINT64_C(0xee80658a9ece807e), 65, UINT64_C(0x6974aedd6bbd4bfe), 27, UINT64_C(0xb55d4a66e9e7e0ec)}, + {3, 0, 92, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 1, 93, UINT64_C(0x12aca08bd8e3a562), 65, UINT64_C(0x7cb2eddc0608127a), 27, UINT64_C(0xb55d4a66e9e7e0ec)}, + {3, 0, 93, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 3, 96, UINT64_C(0x3c333c9bf74f1224), 66, UINT64_C(0x154d3274d8faa6c5), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 0, 94, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 4, 98, UINT64_C(0xb940cbe38e8e601e), 67, UINT64_C(0xae9fccd09bb2a1a9), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 0, 95, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 3, 98, UINT64_C(0xb940cbe38e8e601e), 68, UINT64_C(0x61f4292581fe1020), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 0, 96, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 4, 100, UINT64_C(0xfcbcf9f4cc7993e0), 69, UINT64_C(0xe79fa9bc314eb694), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 0, 97, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 3, 100, UINT64_C(0xfcbcf9f4cc7993e0), 70, UINT64_C(0x588604dbc9a84b89), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 0, 98, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 3, 101, UINT64_C(0x30f9e0f7075fc2b6), 71, UINT64_C(0x920d6877a5ad485b), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 0, 99, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 2, 101, UINT64_C(0x30f9e0f7075fc2b6), 72, UINT64_C(0xf5adbbea7893fef0), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 0, 100, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 2, 102, UINT64_C(0x9729f5c186b3bae0), 73, UINT64_C(0x29f0f6fd5ed356c0), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 0, 101, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 1, 102, UINT64_C(0x9729f5c186b3bae0), 74, UINT64_C(0xa9a8d50e804943ed), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 0, 102, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 2, 104, UINT64_C(0x234227d767437cfa), 74, UINT64_C(0xa9a8d50e804943ed), 28, UINT64_C(0x5765ff51e1564dec)}, + {3, 0, 103, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 1, 104, UINT64_C(0x234227d767437cfa), 74, UINT64_C(0xa9a8d50e804943ed), 29, UINT64_C(0x4d11975435094ccb)}, + {3, 0, 104, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 74, UINT64_C(0xa9a8d50e804943ed), 30, UINT64_C(0xb50667d7d86f8d99)}, + {3, 0, 105, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 0, 105, UINT64_C(0x2ce6ab0477ad5968), 74, UINT64_C(0xa9a8d50e804943ed), 31, UINT64_C(0x98a23224cfb0ffe0)}, + {3, 1, 0, 7, "", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 104, UINT64_C(0xa89112e8bdfbac92), 74, UINT64_C(0x6ea88119e6b74a5d), 29, UINT64_C(0x15bba93be47766f3)}, + {3, 1, 1, 7, "", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 74, UINT64_C(0x3a169d78c5b81187), 30, UINT64_C(0xcddf81110b348447)}, + {3, 1, 2, 0, "key", "null", "0:0;", 1, 3, UINT64_C(0xa922fa18e1c37583), 0, UINT64_C(0xcbf29ce484222325), 2, UINT64_C(0x08395307b4f1348c)}, + {3, 1, 3, 0, "key", "null", "0:0;", 1, 4, UINT64_C(0x29e49b479f20e3f9), 1, UINT64_C(0xaf63be4c8601b992), 2, UINT64_C(0x08395307b4f1348c)}, + {3, 1, 4, 0, "key", "null", "0:0;", 1, 5, UINT64_C(0x50576bb364e2af63), 2, UINT64_C(0x0824ed07b4dfde30), 2, UINT64_C(0x08395307b4f1348c)}, + {3, 1, 5, 7, "key", "{}", "7:0o0:1{};", 1, 6, UINT64_C(0x673b44d46d2fced9), 1, UINT64_C(0xaf63bd4c8601b7df), 5, UINT64_C(0x3f3fcc261a338844)}, + {3, 1, 6, 7, "key", "{}", "7:0o0:1{};", 1, 7, UINT64_C(0x9984a5f5883c36c3), 1, UINT64_C(0xaf63bb4c8601b479), 5, UINT64_C(0x3f3fcc261a338844)}, + {3, 1, 7, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 91, UINT64_C(0x60517b283d4e7f28), 59, UINT64_C(0x70305a3ea5d29b0b), 31, UINT64_C(0x1987bbdf4f551fe8)}, + {3, 1, 8, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 92, UINT64_C(0x2cdb62dd8baca92e), 60, UINT64_C(0x19657326d928cdd8), 31, UINT64_C(0x2c299448de62a072)}, + {3, 1, 9, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 73, UINT64_C(0x1fc7c6ebd1b98ec6), 31, UINT64_C(0xe32cffe4946de372)}, + {3, 1, 10, 7, "key", "{\"\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 98, UINT64_C(0x27dfd47a22924cde), 68, UINT64_C(0xad21aae5eb3e9d5c), 29, UINT64_C(0xe76f73f6e413690f)}, + {3, 1, 11, 7, "key", "{\"\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x54436514d18ad928), 71, UINT64_C(0xfeaee9cf685870dc), 30, UINT64_C(0xfad1497156057d51)}, + {3, 1, 12, 7, "key", "{\"\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 100, UINT64_C(0x0ec693ac9e264c48), 70, UINT64_C(0x46f3d395149d83b7), 29, UINT64_C(0x9292c45666d5f087)}, + {3, 1, 13, 7, "key", "{\"\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 103, UINT64_C(0x998db6ea3f64f750), 72, UINT64_C(0x6a8506724de1c15f), 30, UINT64_C(0x51fafc33547ec99a)}, + {3, 1, 14, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 94, UINT64_C(0xdd430a82fe7fe43e), 62, UINT64_C(0x6c1ea5a1d32dce85), 31, UINT64_C(0xaa062e38a46dc362)}, + {3, 1, 15, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 95, UINT64_C(0x020439d52e8973d0), 63, UINT64_C(0x6916ebb1bcc7d741), 31, UINT64_C(0xe99cd0eb7f04b0c0)}, + {3, 1, 16, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 73, UINT64_C(0x010e7d553fd5eda0), 31, UINT64_C(0xe32cffe4946de372)}, + {3, 1, 17, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 104, UINT64_C(0x7d3ce9fb1cdc6896), 73, UINT64_C(0x1798f8572a342022), 31, UINT64_C(0xe70b60a34f05f80c)}, + {3, 1, 18, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 73, UINT64_C(0x8df3952bc81fb19a), 31, UINT64_C(0xe32cffe4946de372)}, + {3, 1, 19, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 100, UINT64_C(0x2dfc24178ad786ae), 70, UINT64_C(0xab432aea1b343eb1), 29, UINT64_C(0x3bd25fc07522b6c7)}, + {3, 1, 20, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 101, UINT64_C(0xb1577d1dfe0dc2be), 71, UINT64_C(0xdd3a2a6eb7fab273), 29, UINT64_C(0xa4dacec9da60a18d)}, + {3, 1, 21, 7, "key", "{\"\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 99, UINT64_C(0x4f117cd47c5b07a8), 70, UINT64_C(0x68097b7d7ba01328), 28, UINT64_C(0x9d231f94a3d8505f)}, + {3, 1, 22, 7, "key", "{\"\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 101, UINT64_C(0xaf7fa9cbc785a706), 71, UINT64_C(0xdd3a2a6eb7fab273), 29, UINT64_C(0xa4dacec9da60a18d)}, + {3, 1, 23, 7, "key", "{\"a\":null,\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{0:0;5:1;7:1o1:1{3:1;};};", 1, 93, UINT64_C(0xfe8c535b61975fc0), 65, UINT64_C(0x52dcce1da8492cfc), 27, UINT64_C(0x6b2d1037cf396c3a)}, + {3, 1, 24, 7, "key", "{\"a\":null,\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{0:0;5:1;7:1o1:1{3:1;};};", 1, 94, UINT64_C(0x49656a6b416c8aae), 66, UINT64_C(0xb6023d8fb31fa833), 27, UINT64_C(0x351d6894476b65d4)}, + {3, 1, 25, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x5b8d98fa549824d6), 71, UINT64_C(0x3236647f1b32c90a), 30, UINT64_C(0x4c922b10d249c073)}, + {3, 1, 26, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 101, UINT64_C(0xfd645a891ab048d8), 71, UINT64_C(0x441133cd64aeff34), 30, UINT64_C(0x4bdf8fc323423212)}, + {3, 1, 27, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x5b8d98fa549824d6), 71, UINT64_C(0x6997d907cb99bf48), 30, UINT64_C(0x4c922b10d249c073)}, + {3, 1, 28, 7, "key", "{\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o2:1{5:1;7:1o1:1{3:1;};};", 1, 94, UINT64_C(0xc58789bf9039d336), 70, UINT64_C(0xa480724f4c9ef05f), 23, UINT64_C(0xaefbe7fa88ebe124)}, + {3, 1, 29, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x5b8d98fa549824d6), 72, UINT64_C(0xe344b830f3a11d8a), 29, UINT64_C(0xab5acbf8e6ca0bdf)}, + {3, 1, 30, 7, "key", "{\"\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 101, UINT64_C(0xaf7fa9cbc785a706), 72, UINT64_C(0x902c140387af47d3), 28, UINT64_C(0x66b09ba8d6eda091)}, + {3, 1, 31, 7, "key", "{\"\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x5b8d98fa549824d6), 72, UINT64_C(0xe344b830f3a11d8a), 29, UINT64_C(0xab5acbf8e6ca0bdf)}, + {3, 1, 32, 7, "key", "{\"a\":null,\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{0:0;5:1;7:1o1:1{3:1;};};", 1, 98, UINT64_C(0x63e791635b56fa46), 70, UINT64_C(0x8a8854f75fca9d93), 27, UINT64_C(0x2dfa80357ea0bdb4)}, + {3, 1, 33, 7, "key", "{\"a\":null,\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{0:0;5:1;7:1o1:1{3:1;};};", 1, 99, UINT64_C(0x4a646907627eac30), 71, UINT64_C(0x18b59e7d5b2fe673), 27, UINT64_C(0x2c0f7f6633f03dbe)}, + {3, 1, 34, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x5b8d98fa549824d6), 71, UINT64_C(0x8291d9ce0eae8d1d), 30, UINT64_C(0xeda7450c41cedbee)}, + {3, 1, 35, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 101, UINT64_C(0xfd5972cc2d7ea8a8), 71, UINT64_C(0x931494827e967b6a), 30, UINT64_C(0x52b5b7d7887eb988)}, + {3, 1, 36, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:0a0:1[];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x5b8d98fa549824d6), 71, UINT64_C(0xa4496da3e2ecc4d3), 30, UINT64_C(0x5c17b3d1c0a1b56e)}, + {3, 1, 37, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a0:1[];5:1;7:1o1:1{3:1;};};", 1, 103, UINT64_C(0x7bf409aa512991e0), 73, UINT64_C(0x1091b5eee814c0e9), 30, UINT64_C(0x21d27a28fc8ef91c)}, + {3, 1, 38, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a0:1[];5:1;7:1o1:1{3:1;};};", 1, 104, UINT64_C(0xe05d289941f12cfe), 73, UINT64_C(0xf799f9b52163552a), 30, UINT64_C(0xe93f3e2fb84426fe)}, + {3, 1, 39, 7, "key", "{\"a\":[],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a0:1[];5:1;7:1o1:1{3:1;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 74, UINT64_C(0xf82ec970d3cfc2e0), 30, UINT64_C(0x11bb3330ad0999c0)}, + {3, 1, 40, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 100, UINT64_C(0x835a6ff205dd6146), 68, UINT64_C(0x3b1c5be6adc3631c), 31, UINT64_C(0x9c1079f29a41bd24)}, + {3, 1, 41, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 101, UINT64_C(0x8d63785d9cbce268), 69, UINT64_C(0xa8cdc57a4d527c5c), 31, UINT64_C(0x520bc292ee5b0f92)}, + {3, 1, 42, 7, "key", "{\"a\":[true],\"\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 98, UINT64_C(0x520ba6b37d1f237e), 68, UINT64_C(0x2d7baac348149c62), 29, UINT64_C(0x8098f4d3abda36e7)}, + {3, 1, 43, 7, "key", "{\"a\":[true],\"\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0xec8ebd121f055228), 71, UINT64_C(0x2b515550cedda7c8), 30, UINT64_C(0x86928d977d93f2bb)}, + {3, 1, 44, 7, "key", "{\"a\":[true],\"\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 100, UINT64_C(0x66382e2716717628), 70, UINT64_C(0x0bdc83cc3c8b5cef), 29, UINT64_C(0x4ecf792831a2b853)}, + {3, 1, 45, 7, "key", "{\"a\":[true],\"\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 103, UINT64_C(0xe8acd57ecd9d0bd8), 72, UINT64_C(0xc0509a5f17c137f9), 30, UINT64_C(0xd78e7d4db72bdd00)}, + {3, 1, 46, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x2a58dcccc2fcfd9e), 70, UINT64_C(0x8d2f336da17fdc39), 31, UINT64_C(0x413ebc4e90581e70)}, + {3, 1, 47, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 103, UINT64_C(0x03a8c5ca4270b260), 71, UINT64_C(0xa46bf71b697ba90f), 31, UINT64_C(0x117b52679e50447a)}, + {3, 1, 48, 7, "key", "{\"k\":\"\",\"a\":[true],\"o\":{\"q\":1}}", "7:0o3:1{5:0;6:1a1:1[1:1;];7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x684c99dda1421b91), 72, UINT64_C(0xe793b57af9534fae), 29, UINT64_C(0x67733b1248cb0261)}, + {3, 1, 49, 7, "key", "{\"k\":\"\",\"a\":[true],\"o\":{\"q\":1}}", "7:0o3:1{5:0;6:1a1:1[1:1;];7:1o1:1{3:1;};};", 1, 103, UINT64_C(0xc103fd077d12a831), 73, UINT64_C(0x2fb728bc07b7ae3c), 29, UINT64_C(0xe2affcd4663837ef)}, + {3, 1, 50, 7, "key", "{\"a\":[true],\"\":\"\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:0;7:1o1:1{3:1;};};", 1, 101, UINT64_C(0xf9b8778ce5892e03), 72, UINT64_C(0xa6bcc4a926c9b17f), 28, UINT64_C(0xfe73d9bce6994e15)}, + {3, 1, 51, 7, "key", "{\"a\":[true],\"\":\"\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:0;7:1o1:1{3:1;};};", 1, 103, UINT64_C(0xba3b00c1c283e5e9), 73, UINT64_C(0x2fb728bc07b7ae3c), 29, UINT64_C(0xd1fe98172432957d)}, + {3, 1, 52, 7, "key", "{\"a\":[true],\"k\":null,\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];0:0;7:1o1:1{3:1;};};", 1, 101, UINT64_C(0xa5355b8a98f6cb8b), 72, UINT64_C(0xa6bcc4a926c9b17f), 28, UINT64_C(0xfe73d9bce6994e15)}, + {3, 1, 53, 7, "key", "{\"a\":[true],\"k\":null,\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];0:0;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0xe52e3935b858e4b9), 73, UINT64_C(0x48dbc55354a5d5f7), 28, UINT64_C(0xffb0dab42fe56888)}, + {3, 1, 54, 7, "key", "{\"a\":[true],\"o\":{\"q\":1}}", "7:0o2:1{6:1a1:1[1:1;];7:1o1:1{3:1;};};", 1, 99, UINT64_C(0x9796ad14651215a3), 74, UINT64_C(0x95d5583f8bb4071a), 24, UINT64_C(0xd729a5f6b715fe63)}, + {3, 1, 55, 7, "key", "{\"a\":[true],\"k\":\"\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:0;7:1o1:1{3:1;};};", 1, 104, UINT64_C(0x555db4043c851841), 74, UINT64_C(0x54851835c64759d3), 29, UINT64_C(0xe734f45587bfefcb)}, + {3, 1, 56, 7, "key", "{\"a\":[true],\"\":\"\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:0;7:1o1:1{3:1;};};", 1, 103, UINT64_C(0xba3b00c1c283e5e9), 74, UINT64_C(0xb84e1822ab185956), 28, UINT64_C(0xb37adbd00a14818f)}, + {3, 1, 57, 7, "key", "{\"a\":[true],\"\":\"\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:0;7:1o1:1{3:1;};};", 1, 104, UINT64_C(0x555db4043c851841), 74, UINT64_C(0x54851835c64759d3), 29, UINT64_C(0xe734f45587bfefcb)}, + {3, 1, 58, 7, "key", "{\"a\":[true],\"k\":null,\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];0:0;7:1o1:1{3:1;};};", 1, 103, UINT64_C(0x70181eb04816bf33), 74, UINT64_C(0xb84e1822ab185956), 28, UINT64_C(0xb37adbd00a14818f)}, + {3, 1, 59, 7, "key", "{\"a\":[true],\"k\":null,\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];0:0;7:1o1:1{3:1;};};", 1, 104, UINT64_C(0x555db4043c851841), 75, UINT64_C(0x93238bb2f717715c), 28, UINT64_C(0x6b0001fee18a4342)}, + {3, 1, 60, 7, "key", "{\"a\":[true],\"k\":\"\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 74, UINT64_C(0x152a73fddccffd18), 30, UINT64_C(0x2919611d528894b6)}, + {3, 1, 61, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 91, UINT64_C(0xc4be6f24764b02dc), 59, UINT64_C(0x70b92cf6f5e020be), 31, UINT64_C(0xb62ceac18acff7bb)}, + {3, 1, 62, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 92, UINT64_C(0x61f826a2990473dc), 60, UINT64_C(0xf32be6d03cbedc94), 31, UINT64_C(0xde4498e1728d6c04)}, + {3, 1, 63, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 93, UINT64_C(0x969ed331086ff354), 61, UINT64_C(0xd551a80e1affde03), 31, UINT64_C(0xaf4ee52420b1c109)}, + {3, 1, 64, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 98, UINT64_C(0x79853297e30e1d4c), 68, UINT64_C(0xb65d101d0531c0a2), 29, UINT64_C(0x46c716fcc1328a3d)}, + {3, 1, 65, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x12ef72ce739f4d0e), 71, UINT64_C(0xb4e6bb7515f3cf72), 30, UINT64_C(0x99227e2156785871)}, + {3, 1, 66, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 100, UINT64_C(0x6f83d649fe8c8c72), 70, UINT64_C(0x44eac57908774015), 29, UINT64_C(0x5fcef1cfacefbfb7)}, + {3, 1, 67, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 103, UINT64_C(0x2804f6f1e71ce3f8), 72, UINT64_C(0xb02c45a9887be1a2), 30, UINT64_C(0xf770764b4ef7ecf9)}, + {3, 1, 68, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 94, UINT64_C(0x7c26769eabcb532c), 62, UINT64_C(0x73f92b18a24d6d95), 31, UINT64_C(0x824ac31fee78d5ba)}, + {3, 1, 69, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 95, UINT64_C(0xafbd5cdea9c2bcac), 63, UINT64_C(0xb0539f5485162014), 31, UINT64_C(0xdbe2448368048507)}, + {3, 1, 70, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 96, UINT64_C(0xb33822e607e26de4), 64, UINT64_C(0x33f9c22d193bedf6), 31, UINT64_C(0x51382e7a7c032910)}, + {3, 1, 71, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 104, UINT64_C(0xc06e6a557e87d880), 73, UINT64_C(0x7a0b8e235a97a118), 31, UINT64_C(0xd26bf9954e0a7e12)}, + {3, 1, 72, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 73, UINT64_C(0x61bb4f31921812e7), 31, UINT64_C(0x2e08c6c7d56c3047)}, + {3, 1, 73, 7, "key", "{\"o\":{},\"a\":[true],\"k\":\"recovery\"}", "7:0o3:1{7:0o0:1{};6:1a1:1[1:1;];5:1;};", 1, 89, UINT64_C(0x109ea5036371589a), 62, UINT64_C(0x340e380554d0657b), 26, UINT64_C(0xd9113597a2d80f61)}, + {3, 1, 74, 7, "key", "{\"o\":{},\"a\":[true],\"k\":\"recovery\"}", "7:0o3:1{7:0o0:1{};6:1a1:1[1:1;];5:1;};", 1, 90, UINT64_C(0x9244c3a01dcace7c), 63, UINT64_C(0xd24b3c75b921bd74), 26, UINT64_C(0x1c37ca450bfc8a3d)}, + {3, 1, 75, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 88, UINT64_C(0x9f631208c5a8d666), 62, UINT64_C(0xe93b1055211f4f47), 25, UINT64_C(0xd4a2aa2952606b15)}, + {3, 1, 76, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 90, UINT64_C(0xd3b8a67d8b3576f8), 63, UINT64_C(0xd24b3c75b921bd74), 26, UINT64_C(0xf4e67f641f73ecad)}, + {3, 1, 77, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 1, 82, UINT64_C(0xe0e7732a58e74766), 57, UINT64_C(0x77989b1f55d4e559), 24, UINT64_C(0x607bb464128767f0)}, + {3, 1, 78, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 1, 83, UINT64_C(0x7d5bc46063a8c1e2), 58, UINT64_C(0x7d721ed202be022f), 24, UINT64_C(0x6c0528f9d1ca95c4)}, + {3, 1, 79, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 1, 84, UINT64_C(0x0c778e767eab97ae), 59, UINT64_C(0x4bb22e9ffd64c5f0), 24, UINT64_C(0xedf2404c46b20bf4)}, + {3, 1, 80, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 90, UINT64_C(0x304cacc2dca590f6), 63, UINT64_C(0x29777f1b2af8f733), 27, UINT64_C(0x756f2f41adc7104a)}, + {3, 1, 81, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 91, UINT64_C(0xd6b74720191512aa), 63, UINT64_C(0xb522ebbedf5074e6), 27, UINT64_C(0x122bfe467a81181b)}, + {3, 1, 82, 7, "key", "{\"a\":[true],\"k\":\"recovery\"}", "7:0o2:1{6:1a1:1[1:1;];5:1;};", 1, 83, UINT64_C(0x7a59405fd132616a), 62, UINT64_C(0x048d19db3da2b56b), 20, UINT64_C(0x1a0725f3cd62f080)}, + {3, 1, 83, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 91, UINT64_C(0xd6b74720191512aa), 64, UINT64_C(0x72c6e24c51035e34), 26, UINT64_C(0x4fe2311fc08c62d1)}, + {3, 1, 84, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 90, UINT64_C(0xd3b8a67d8b3576f8), 64, UINT64_C(0x0c961ea2c6d9bdd0), 25, UINT64_C(0xb192d70ba7d71d53)}, + {3, 1, 85, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 91, UINT64_C(0xd6b74720191512aa), 64, UINT64_C(0x72c6e24c51035e34), 26, UINT64_C(0x4fe2311fc08c62d1)}, + {3, 1, 86, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 1, 87, UINT64_C(0xa6a9474d21df7fca), 62, UINT64_C(0x048d19db3da2b56b), 24, UINT64_C(0x601715e2f78af79c)}, + {3, 1, 87, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 1, 88, UINT64_C(0x112412108ec65a9e), 63, UINT64_C(0x333ba418287f24ee), 24, UINT64_C(0x601715e2f78af79c)}, + {3, 1, 88, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":null}", "7:0o3:1{6:1a1:1[1:1;];5:1;0:0;};", 1, 89, UINT64_C(0xe6a5a8229b0c9002), 64, UINT64_C(0x72c6e24c51035e34), 24, UINT64_C(0x601715e2f78af79c)}, + {3, 1, 89, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 90, UINT64_C(0xf80cd4cd7658e9c6), 63, UINT64_C(0xe3718003aacfdf89), 27, UINT64_C(0x23c931a0f86ee070)}, + {3, 1, 90, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:0o0:1{};};", 1, 91, UINT64_C(0xd6b74720191512aa), 63, UINT64_C(0x859ce136ed5a19e3), 27, UINT64_C(0x23c931a0f86ee070)}, + {3, 1, 91, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 1, 92, UINT64_C(0xee80658a9ece807e), 65, UINT64_C(0x6974aedd6bbd4bfe), 27, UINT64_C(0xb55d4a66e9e7e0ec)}, + {3, 1, 92, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 1, 93, UINT64_C(0x12aca08bd8e3a562), 65, UINT64_C(0x7cb2eddc0608127a), 27, UINT64_C(0xb55d4a66e9e7e0ec)}, + {3, 1, 93, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 98, UINT64_C(0xfc0a73e323c44ba4), 68, UINT64_C(0x61f4292581fe1020), 29, UINT64_C(0x32b15a146cdd8b6f)}, + {3, 1, 94, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 102, UINT64_C(0x2c63d677b55e3dfa), 71, UINT64_C(0xd9342f88c76e636d), 30, UINT64_C(0xf08facd70b71fb88)}, + {3, 1, 95, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 100, UINT64_C(0xfdcd03f4cd60d80e), 70, UINT64_C(0x588604dbc9a84b89), 29, UINT64_C(0x176bd9a267ef7e3b)}, + {3, 1, 96, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 1, 103, UINT64_C(0x9c1dc1d99d63a20c), 72, UINT64_C(0xf5adbbea7893fef0), 30, UINT64_C(0x584f1dfc2923487b)}, + {3, 1, 97, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 1, 103, UINT64_C(0xc99430c7bfe41ee4), 72, UINT64_C(0xf5adbbea7893fef0), 30, UINT64_C(0x584f1dfc2923487b)}, + {3, 1, 98, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 1, 104, UINT64_C(0x238cd5d76782dd1e), 73, UINT64_C(0x29f0f6fd5ed356c0), 30, UINT64_C(0x404b79f3610714de)}, + {3, 1, 99, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 1, 102, UINT64_C(0x985bf7c187b7d7dc), 72, UINT64_C(0xf5adbbea7893fef0), 29, UINT64_C(0x79af5c5dc615f4f7)}, + {3, 1, 100, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 1, 104, UINT64_C(0x234227d767437cfa), 73, UINT64_C(0x29f0f6fd5ed356c0), 30, UINT64_C(0x404b79f3610714de)}, + {3, 1, 101, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o0:1{};};", 1, 102, UINT64_C(0x9729f5c186b3bae0), 74, UINT64_C(0xa9a8d50e804943ed), 27, UINT64_C(0x998885b959d4fc68)}, + {3, 1, 102, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 74, UINT64_C(0xa9a8d50e804943ed), 30, UINT64_C(0xb50667d7d86f8d99)}, + {3, 1, 103, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 1, 104, UINT64_C(0x234227d767437cfa), 74, UINT64_C(0xa9a8d50e804943ed), 29, UINT64_C(0x4d11975435094ccb)}, + {3, 1, 104, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"\":0}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:0;};};", 1, 105, UINT64_C(0x2ce6ab0477ad5968), 74, UINT64_C(0xa9a8d50e804943ed), 30, UINT64_C(0xb50667d7d86f8d99)}, + {3, 1, 105, 7, "key", "{\"a\":[true],\"k\":\"recovery\",\"o\":{\"q\":1}}", "7:0o3:1{6:1a1:1[1:1;];5:1;7:1o1:1{3:1;};};", 0, 105, UINT64_C(0x2ce6ab0477ad5968), 74, UINT64_C(0xa9a8d50e804943ed), 31, UINT64_C(0x98a23224cfb0ffe0)}, + {4, 0, 0, 0, "", "null", "0:0;", 2, 2, UINT64_C(0x08842007b530c95d), 0, UINT64_C(0xcbf29ce484222325), 0, UINT64_C(0xcbf29ce484222325)}, + {4, 0, 1, 0, "", "null", "0:0;", 2, 3, UINT64_C(0xa922fa18e1c37583), 0, UINT64_C(0xcbf29ce484222325), 1, UINT64_C(0xaf63bc4c8601b62c)}, + {4, 0, 2, 0, "key", "null", "0:0;", 1, 3, UINT64_C(0xa922fa18e1c37583), 0, UINT64_C(0xcbf29ce484222325), 2, UINT64_C(0x08395307b4f1348c)}, + {4, 0, 3, 0, "key", "null", "0:0;", 1, 4, UINT64_C(0x29e49b479f20e3f9), 1, UINT64_C(0xaf63be4c8601b992), 2, UINT64_C(0x08395307b4f1348c)}, + {4, 0, 4, 6, "key", "[]", "6:0a0:1[missing]", 2, 6, UINT64_C(0x67f9d4d46dd1ec91), 1, UINT64_C(0xaf63bd4c8601b7df), 4, UINT64_C(0xb345225e3644edb5)}, + {4, 0, 5, 6, "key", "[]", "6:0a0:1[];", 1, 6, UINT64_C(0x67f9d4d46dd1ec91), 1, UINT64_C(0xaf63bd4c8601b7df), 5, UINT64_C(0x3f3fcc261a338844)}, + {4, 0, 6, 6, "key", "[]", "6:0a0:1[];", 1, 7, UINT64_C(0x7f7145f69bb51593), 1, UINT64_C(0xaf63bb4c8601b479), 5, UINT64_C(0x3f3fcc261a338844)}, + {4, 0, 7, 6, "key", "[]", "6:0a0:1[];", 4, 11, UINT64_C(0x0ef52d9e6a1d5a93), 2, UINT64_C(0x08218e07b4dd06ec), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {4, 0, 8, 6, "key", "[]", "6:0a0:1[];", 4, 12, UINT64_C(0xa2e7cea7d9800249), 3, UINT64_C(0x1e87e8189340ae76), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {4, 0, 9, 6, "key", "[]", "6:0a0:1[];", 4, 13, UINT64_C(0x1fe4f8370a5a89cb), 4, UINT64_C(0xf241d4ba207d8e95), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {4, 0, 10, 6, "key", "[]", "6:0a0:1[];", 3, 13, UINT64_C(0x1fe4f8370a5a89cb), 5, UINT64_C(0x7e6b1d36db19d80d), 5, UINT64_C(0x18d2da27b6b8ff60)}, + {4, 0, 11, 6, "key", "[null]", "6:0a1:1[0:0;];", 3, 14, UINT64_C(0x8d23b086984bf3e9), 5, UINT64_C(0x7e6b1d36db19d80d), 6, UINT64_C(0xe5658011eb170905)}, + {4, 0, 12, 6, "key", "[null]", "6:0a1:1[0:0;];", 3, 15, UINT64_C(0x4ef6f5b452adc8bb), 6, UINT64_C(0xe60984573090fde3), 6, UINT64_C(0xe5658011eb170905)}, + {4, 0, 13, 6, "key", "[[]]", "6:0a1:1[6:0a0:1[missing]];", 4, 17, UINT64_C(0x9b59153f217aca13), 6, UINT64_C(0xfe81bb0663d6faef), 8, UINT64_C(0xd594f473ed6d03b0)}, + {4, 0, 14, 6, "key", "[[]]", "6:0a1:1[6:0a0:1[];];", 3, 17, UINT64_C(0x9b59153f217aca13), 6, UINT64_C(0xfe81bb0663d6faef), 9, UINT64_C(0x0f060cdee7c6d79c)}, + {4, 0, 15, 6, "key", "[[]]", "6:0a1:1[6:0a0:1[];];", 3, 18, UINT64_C(0xa2f3e5456d9fe0d9), 6, UINT64_C(0x02c88c156775a22c), 9, UINT64_C(0x0f060cdee7c6d79c)}, + {4, 0, 16, 6, "key", "[[]]", "6:0a1:1[6:1a0:1[];];", 3, 19, UINT64_C(0xdf5265fa0b5e084b), 8, UINT64_C(0x7640b8a62d1fce5a), 9, UINT64_C(0x3a62d1866b18afc8)}, + {4, 0, 17, 6, "key", "[[]]", "6:0a1:1[6:1a0:1[];];", 3, 20, UINT64_C(0x927dfae12a29b7e1), 8, UINT64_C(0x1700d06a36def24f), 9, UINT64_C(0x3a62d1866b18afc8)}, + {4, 0, 18, 6, "key", "[[]]", "6:0a1:1[6:1a0:1[];];", 4, 22, UINT64_C(0xe866f0c62e93f421), 9, UINT64_C(0xf24b4415cd4b36d9), 9, UINT64_C(0x08d2d2ce6ebd7ac4)}, + {4, 0, 19, 6, "key", "[[]]", "6:0a1:1[6:1a0:1[];];", 4, 23, UINT64_C(0xaa96fa88c4c89fbb), 10, UINT64_C(0x8d143888e63f9b66), 9, UINT64_C(0x08d2d2ce6ebd7ac4)}, + {4, 0, 20, 6, "key", "[[]]", "6:0a1:1[6:1a0:1[];];", 3, 23, UINT64_C(0xaa96fa88c4c89fbb), 11, UINT64_C(0xaa5c60bc7d9fe37a), 9, UINT64_C(0x08d2d2ce6ebd7ac4)}, + {4, 0, 21, 6, "key", "[[null]]", "6:0a1:1[6:1a1:1[0:0;];];", 3, 24, UINT64_C(0xa746b16660fe9f39), 11, UINT64_C(0xaa5c60bc7d9fe37a), 10, UINT64_C(0x340b2d803261506f)}, + {4, 0, 22, 6, "key", "[[null]]", "6:0a1:1[6:1a1:1[0:0;];];", 3, 25, UINT64_C(0xc63af9f71f110db3), 12, UINT64_C(0xac32a94474c4bc64), 10, UINT64_C(0x340b2d803261506f)}, + {4, 0, 23, 6, "key", "[[\"\"]]", "6:0a1:1[6:1a1:1[5:1;];];", 3, 26, UINT64_C(0xdf2316e9c5861876), 11, UINT64_C(0xaa5c60bc7d9fe37a), 12, UINT64_C(0xc1928666ae5d4a6a)}, + {4, 0, 24, 6, "key", "[[\"recovery\"]]", "6:0a1:1[6:1a1:1[5:1;];];", 2, 26, UINT64_C(0xdf2316e9c5861876), 11, UINT64_C(0xaa5c60bc7d9fe37a), 13, UINT64_C(0xc70e457618281d24)}, + {4, 0, 25, 6, "key", "[[\"recovery\"]]", "6:0a1:1[6:1a1:1[5:1;];];", 2, 27, UINT64_C(0xae82353aa2adaf82), 12, UINT64_C(0x283aa048ed3b7e47), 13, UINT64_C(0xc70e457618281d24)}, + {4, 0, 26, 6, "key", "[[\"recovery\"]]", "6:0a1:1[6:1a1:1[5:1;];];", 2, 28, UINT64_C(0x33fb34a26c513b2e), 13, UINT64_C(0xc3114ee2a85daebf), 13, UINT64_C(0xc70e457618281d24)}, + {4, 0, 27, 6, "key", "[[\"recovery\"]]", "6:0a1:1[6:1a1:1[5:1;];];", 1, 28, UINT64_C(0x33fb34a26c513b2e), 14, UINT64_C(0x0e819e1d9dff465c), 13, UINT64_C(0xc70e457618281d24)}, + {4, 0, 28, 6, "key", "[[\"recovery\"],null]", "6:0a2:1[6:1a1:1[5:1;];0:0;];", 1, 29, UINT64_C(0xa51595fe0e078192), 14, UINT64_C(0x0e819e1d9dff465c), 14, UINT64_C(0xf7ddd57b3ac52386)}, + {4, 0, 29, 6, "key", "[[\"recovery\"],null]", "6:0a2:1[6:1a1:1[5:1;];0:0;];", 1, 30, UINT64_C(0x8b2f90b1d6c16176), 15, UINT64_C(0x27a4fc9d713e1dcf), 14, UINT64_C(0xf7ddd57b3ac52386)}, + {4, 0, 30, 6, "key", "[[\"recovery\"],null]", "6:0a2:1[6:1a1:1[5:1;];0:0;];", 1, 31, UINT64_C(0x4333fc2fea9805fa), 16, UINT64_C(0xb34108ccff78652b), 14, UINT64_C(0xf7ddd57b3ac52386)}, + {4, 0, 31, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:0o0:1{};];", 1, 32, UINT64_C(0xc95b5f6ba051f16e), 15, UINT64_C(0x60ba726eb8e9eafe), 17, UINT64_C(0x1fbdcc46db94b748)}, + {4, 0, 32, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:0o0:1{};];", 1, 33, UINT64_C(0x783429e16b3c8d32), 15, UINT64_C(0xcdedec10fab123de), 17, UINT64_C(0x1fbdcc46db94b748)}, + {4, 0, 33, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 1, 34, UINT64_C(0x7d303c0937e3b596), 17, UINT64_C(0x0e22e134963af1ff), 17, UINT64_C(0xc47810ca7ea08204)}, + {4, 0, 34, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 1, 35, UINT64_C(0x9cac01a9f7ee3e9a), 17, UINT64_C(0x9d9d28d543e28d8d), 17, UINT64_C(0xc47810ca7ea08204)}, + {4, 0, 35, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 3, 38, UINT64_C(0x449d737b6d7045cc), 18, UINT64_C(0x634cab1e73430dc8), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 0, 36, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 4, 40, UINT64_C(0xd8f447b1c5fb9216), 19, UINT64_C(0xe73f82bc8b648d68), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 0, 37, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 3, 40, UINT64_C(0xd8f447b1c5fb9216), 20, UINT64_C(0xa643db5d08c58deb), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 0, 38, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 4, 42, UINT64_C(0x502e87ffe0865028), 21, UINT64_C(0xf96ee5bab261b4ef), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 0, 39, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 3, 42, UINT64_C(0x502e87ffe0865028), 22, UINT64_C(0x3d36dc7057ad8e3c), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 0, 40, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 3, 43, UINT64_C(0x916885ca66ca7fee), 23, UINT64_C(0x8993257e27b863ee), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 0, 41, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 2, 43, UINT64_C(0x916885ca66ca7fee), 24, UINT64_C(0xb355554d556a2233), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 0, 42, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 2, 44, UINT64_C(0xde8cf0eca99ecd68), 25, UINT64_C(0xc52002f46dcf2203), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 0, 43, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 1, 44, UINT64_C(0xde8cf0eca99ecd68), 26, UINT64_C(0x3628a04973bf9fa0), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 0, 44, 6, "key", "[[\"recovery\"],{\"\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 2, 46, UINT64_C(0x2e23e78c9567bb32), 26, UINT64_C(0x3628a04973bf9fa0), 18, UINT64_C(0xed49e4670d08ef12)}, + {4, 0, 45, 6, "key", "[[\"recovery\"],{\"\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 1, 46, UINT64_C(0x2e23e78c9567bb32), 26, UINT64_C(0x3628a04973bf9fa0), 19, UINT64_C(0xc217b532e08d04bf)}, + {4, 0, 46, 6, "key", "[[\"recovery\"],{\"\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 1, 47, UINT64_C(0xcebda3e1df431290), 26, UINT64_C(0x3628a04973bf9fa0), 20, UINT64_C(0xdda94b1d3104c283)}, + {4, 0, 47, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 0, 47, UINT64_C(0xcebda3e1df431290), 26, UINT64_C(0x3628a04973bf9fa0), 21, UINT64_C(0xf0d02532e8d6f88c)}, + {4, 1, 0, 6, "", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 46, UINT64_C(0x92dd27940b88cdee), 26, UINT64_C(0x709eb1bd3f80136c), 19, UINT64_C(0xcc473bf941fe55b3)}, + {4, 1, 1, 6, "", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 47, UINT64_C(0xcebda3e1df431290), 26, UINT64_C(0xdc138678b8467c04), 20, UINT64_C(0x39553f39ddffd6fd)}, + {4, 1, 2, 0, "key", "null", "0:0;", 1, 3, UINT64_C(0xa922fa18e1c37583), 0, UINT64_C(0xcbf29ce484222325), 2, UINT64_C(0x08395307b4f1348c)}, + {4, 1, 3, 0, "key", "null", "0:0;", 1, 4, UINT64_C(0x29e49b479f20e3f9), 1, UINT64_C(0xaf63be4c8601b992), 2, UINT64_C(0x08395307b4f1348c)}, + {4, 1, 4, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 47, UINT64_C(0xcebda3e1df431290), 25, UINT64_C(0x61ac871f77e3e9ef), 21, UINT64_C(0x745e4c543ea53008)}, + {4, 1, 5, 6, "key", "[]", "6:0a0:1[];", 1, 6, UINT64_C(0x67f9d4d46dd1ec91), 1, UINT64_C(0xaf63bd4c8601b7df), 5, UINT64_C(0x3f3fcc261a338844)}, + {4, 1, 6, 6, "key", "[]", "6:0a0:1[];", 1, 7, UINT64_C(0x7f7145f69bb51593), 1, UINT64_C(0xaf63bb4c8601b479), 5, UINT64_C(0x3f3fcc261a338844)}, + {4, 1, 7, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 43, UINT64_C(0x62d08462f86429b0), 21, UINT64_C(0xbc4aea29c30224e9), 21, UINT64_C(0x2195d3c69dbd95a4)}, + {4, 1, 8, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 44, UINT64_C(0x7887af0e0f014fba), 22, UINT64_C(0x0e0620c2831b2fa8), 21, UINT64_C(0xd4097f69bb24ded4)}, + {4, 1, 9, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 47, UINT64_C(0xcebda3e1df431290), 25, UINT64_C(0xe9abf74807bfdead), 21, UINT64_C(0xc78a1d641ccded9c)}, + {4, 1, 10, 6, "key", "[{\"k\":1}]", "6:0a1:1[7:1o1:1{3:1;};];", 1, 34, UINT64_C(0xaca4dcad768758b3), 20, UINT64_C(0x2b1254f33a770e0f), 13, UINT64_C(0x45a62b3550081448)}, + {4, 1, 11, 6, "key", "[null,{\"k\":1}]", "6:0a2:1[0:0;7:1o1:1{3:1;};];", 1, 35, UINT64_C(0x10f4d932a9856119), 20, UINT64_C(0xf63d69101e63889e), 14, UINT64_C(0x8afec3ac50f976fd)}, + {4, 1, 12, 6, "key", "[null,{\"k\":1}]", "6:0a2:1[0:0;7:1o1:1{3:1;};];", 1, 36, UINT64_C(0x463f5c38b808c643), 21, UINT64_C(0xb1e86ad690faa15b), 14, UINT64_C(0x4363778309ad8d0d)}, + {4, 1, 13, 6, "key", "[[],{\"k\":1}]", "6:0a2:1[6:0a0:1[];7:1o1:1{3:1;};];", 1, 39, UINT64_C(0xa97deac3ff265a29), 21, UINT64_C(0x1282acbd55a15693), 17, UINT64_C(0xe511760c18ca0850)}, + {4, 1, 14, 6, "key", "[[],{\"k\":1}]", "6:0a2:1[6:0a0:1[];7:1o1:1{3:1;};];", 1, 38, UINT64_C(0xd16edb648a6ce1ab), 21, UINT64_C(0xd366c3eec13151bd), 17, UINT64_C(0xa892f032cc56afb4)}, + {4, 1, 15, 6, "key", "[[],{\"k\":1}]", "6:0a2:1[6:0a0:1[];7:1o1:1{3:1;};];", 1, 39, UINT64_C(0xa97deac3ff265a29), 21, UINT64_C(0x430b89b128d98bc9), 17, UINT64_C(0x0ffc238e12d8a034)}, + {4, 1, 16, 6, "key", "[[],{\"k\":1}]", "6:0a2:1[6:1a0:1[];7:1o1:1{3:1;};];", 1, 40, UINT64_C(0x064800083d112433), 23, UINT64_C(0x7e3dde20e2c4af28), 17, UINT64_C(0xa9c98a3ab9b643c0)}, + {4, 1, 17, 6, "key", "[[],{\"k\":1}]", "6:0a2:1[6:1a0:1[];7:1o1:1{3:1;};];", 1, 41, UINT64_C(0x960821877b217001), 23, UINT64_C(0xc7fcea40891a9322), 17, UINT64_C(0xae16f39d760e7cc8)}, + {4, 1, 18, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 46, UINT64_C(0x4adbffdc69541eea), 24, UINT64_C(0x76857573ce5936df), 21, UINT64_C(0xae785b5857c280a0)}, + {4, 1, 19, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 47, UINT64_C(0xcebda3e1df431290), 25, UINT64_C(0x4b847b2b3084d143), 21, UINT64_C(0xd558d4feac336e68)}, + {4, 1, 20, 6, "key", "[[],{\"k\":1}]", "6:0a2:1[6:1a0:1[];7:1o1:1{3:1;};];", 1, 44, UINT64_C(0x4e84d7d79cb12b43), 26, UINT64_C(0x2827871680da954c), 17, UINT64_C(0x282bda8ef28461dc)}, + {4, 1, 21, 6, "key", "[[null],{\"k\":1}]", "6:0a2:1[6:1a1:1[0:0;];7:1o1:1{3:1;};];", 1, 45, UINT64_C(0x50892a76fb509509), 26, UINT64_C(0x2fe37edc7395b8a5), 18, UINT64_C(0x9b143889fe3f908f)}, + {4, 1, 22, 6, "key", "[[null],{\"k\":1}]", "6:0a2:1[6:1a1:1[0:0;];7:1o1:1{3:1;};];", 1, 46, UINT64_C(0x3e337cb4623144cb), 27, UINT64_C(0xf0778577f32c1518), 18, UINT64_C(0x76545b682a66d80f)}, + {4, 1, 23, 6, "key", "[[\"\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 47, UINT64_C(0xcebda3e1df431290), 26, UINT64_C(0xba5d9fa327aa54c7), 20, UINT64_C(0x152a5133a69aacd2)}, + {4, 1, 24, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 43, UINT64_C(0x545e9aeceaf07070), 21, UINT64_C(0xb358f17a3ddbd0f3), 21, UINT64_C(0x7f7246030eab6d5c)}, + {4, 1, 25, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 44, UINT64_C(0x5de102f5bf79df84), 22, UINT64_C(0x6d16a137cb9e81de), 21, UINT64_C(0xc94584b061292514)}, + {4, 1, 26, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 45, UINT64_C(0xfdf3d577ce49b8f8), 23, UINT64_C(0x7116aa28dc5729ce), 21, UINT64_C(0x7fb420974764fe44)}, + {4, 1, 27, 6, "key", "[[\"recovery\"]]", "6:0a1:1[6:1a1:1[5:1;];];", 1, 28, UINT64_C(0x33fb34a26c513b2e), 14, UINT64_C(0x0e819e1d9dff465c), 13, UINT64_C(0xc70e457618281d24)}, + {4, 1, 28, 6, "key", "[[\"recovery\"],null]", "6:0a2:1[6:1a1:1[5:1;];0:0;];", 1, 29, UINT64_C(0xa51595fe0e078192), 14, UINT64_C(0x0e819e1d9dff465c), 14, UINT64_C(0xf7ddd57b3ac52386)}, + {4, 1, 29, 6, "key", "[[\"recovery\"],null]", "6:0a2:1[6:1a1:1[5:1;];0:0;];", 1, 30, UINT64_C(0x8b2f90b1d6c16176), 15, UINT64_C(0x27a4fc9d713e1dcf), 14, UINT64_C(0xf7ddd57b3ac52386)}, + {4, 1, 30, 6, "key", "[[\"recovery\"],null]", "6:0a2:1[6:1a1:1[5:1;];0:0;];", 1, 31, UINT64_C(0x4333fc2fea9805fa), 16, UINT64_C(0xb34108ccff78652b), 14, UINT64_C(0xf7ddd57b3ac52386)}, + {4, 1, 31, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:0o0:1{};];", 1, 32, UINT64_C(0xc95b5f6ba051f16e), 15, UINT64_C(0x60ba726eb8e9eafe), 17, UINT64_C(0x1fbdcc46db94b748)}, + {4, 1, 32, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:0o0:1{};];", 1, 33, UINT64_C(0x783429e16b3c8d32), 15, UINT64_C(0xcdedec10fab123de), 17, UINT64_C(0x1fbdcc46db94b748)}, + {4, 1, 33, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 1, 34, UINT64_C(0x7d303c0937e3b596), 17, UINT64_C(0x0e22e134963af1ff), 17, UINT64_C(0xc47810ca7ea08204)}, + {4, 1, 34, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 1, 35, UINT64_C(0x9cac01a9f7ee3e9a), 17, UINT64_C(0x9d9d28d543e28d8d), 17, UINT64_C(0xc47810ca7ea08204)}, + {4, 1, 35, 6, "key", "[[\"recovery\"],{\"\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 40, UINT64_C(0xa3938fb1a75a386c), 20, UINT64_C(0xa643db5d08c58deb), 19, UINT64_C(0xfa57cf84a8b53e9b)}, + {4, 1, 36, 6, "key", "[[\"recovery\"],{\"\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 44, UINT64_C(0x9e7a111eaeebebf2), 23, UINT64_C(0xb411294cf9d6b5fc), 20, UINT64_C(0x76cdc90ed9ee8f5a)}, + {4, 1, 37, 6, "key", "[[\"recovery\"],{\"\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 42, UINT64_C(0x4eb201ffdf431a66), 22, UINT64_C(0x3d36dc7057ad8e3c), 19, UINT64_C(0xb4b45f3eec4e7aa7)}, + {4, 1, 38, 6, "key", "[[\"recovery\"],{\"\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 1, 45, UINT64_C(0x32b0d42505b4e994), 24, UINT64_C(0xb355554d556a2233), 20, UINT64_C(0xe8452972970cdff5)}, + {4, 1, 39, 6, "key", "[[\"recovery\"],{\"k\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 1, 45, UINT64_C(0x939601fb7cc7322c), 24, UINT64_C(0xb355554d556a2233), 20, UINT64_C(0xe8452972970cdff5)}, + {4, 1, 40, 6, "key", "[[\"recovery\"],{\"k\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 1, 46, UINT64_C(0x2e38658c95793a56), 25, UINT64_C(0xc52002f46dcf2203), 20, UINT64_C(0x69a2acc0a7d1c9c0)}, + {4, 1, 41, 6, "key", "[[\"recovery\"],{\"\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 1, 44, UINT64_C(0xdf1b42ecaa175e04), 24, UINT64_C(0xb355554d556a2233), 19, UINT64_C(0x957fcb3b059cab5b)}, + {4, 1, 42, 6, "key", "[[\"recovery\"],{\"\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 1, 46, UINT64_C(0x2e23e78c9567bb32), 25, UINT64_C(0xc52002f46dcf2203), 20, UINT64_C(0x69a2acc0a7d1c9c0)}, + {4, 1, 43, 6, "key", "[[\"recovery\"],{}]", "6:0a2:1[6:1a1:1[5:1;];7:1o0:1{};];", 1, 44, UINT64_C(0xde8cf0eca99ecd68), 26, UINT64_C(0x3628a04973bf9fa0), 17, UINT64_C(0x6bb402bf755397c8)}, + {4, 1, 44, 6, "key", "[[\"recovery\"],{\"k\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 1, 47, UINT64_C(0xcebda3e1df431290), 26, UINT64_C(0x3628a04973bf9fa0), 20, UINT64_C(0xdda94b1d3104c283)}, + {4, 1, 45, 6, "key", "[[\"recovery\"],{\"\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 1, 46, UINT64_C(0x2e23e78c9567bb32), 26, UINT64_C(0x3628a04973bf9fa0), 19, UINT64_C(0xc217b532e08d04bf)}, + {4, 1, 46, 6, "key", "[[\"recovery\"],{\"\":0}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:0;};];", 1, 47, UINT64_C(0xcebda3e1df431290), 26, UINT64_C(0x3628a04973bf9fa0), 20, UINT64_C(0xdda94b1d3104c283)}, + {4, 1, 47, 6, "key", "[[\"recovery\"],{\"k\":1}]", "6:0a2:1[6:1a1:1[5:1;];7:1o1:1{3:1;};];", 0, 47, UINT64_C(0xcebda3e1df431290), 26, UINT64_C(0x3628a04973bf9fa0), 21, UINT64_C(0xf0d02532e8d6f88c)}, +}; +struct PairCallbackContext { + Value replacement{}; + const Value* receiver = nullptr; + unsigned nulls = 0, mismatches = 0, floats = 0, elements = 0; +}; +static const Value& APS5_VABI NullCopy(ValueType type, const Value* parent, void* context) { + Require(type == 2 && !parent, "Copied null recovery must preserve requested type and parent ABI"); + auto& state = *static_cast(context); ++state.nulls; + return state.replacement; +} +static const Value& APS5_VABI MismatchCopy(ValueType type, const Value* self, const Value* parent, void* context) { + auto& state = *static_cast(context); + Require(type == 2 && self == state.receiver && !parent, "Copied mismatch recovery must preserve receiver, type and parent ABI"); + ++state.mismatches; + return state.replacement; +} +static Value* APS5_VABI FloatCopy(Value* result, std::int32_t function, double number, const Value* parent, void* context, bool* error) { + Require(function == 1 && number == std::numeric_limits::infinity() && !parent && !error, "Copied float recovery must preserve its assignment ABI"); + auto& state = *static_cast(context); ++state.floats; + _ZN3sce4Json5ValueC1El(result, static_cast(state.replacement.payload)); + return result; +} +static void APS5_VABI ElementCopy(int error, const String* key, const std::size_t* index, const Value* parent, const Value* self, void* context) { + auto& state = *static_cast(context); + Require(static_cast(error) == 0x80848115 && key && !index && !parent && self == state.receiver && std::strcmp(_ZNK3sce4Json6String5c_strEv(key), "missing") == 0, "Copied element recovery must retain its exact key, receiver and parent ABI"); + ++state.elements; +} +static void Register(Value& value, PairCallbackContext& context) { + context.receiver = &value; + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&value, NullCopy, &context) == 0, "Register source null recovery"); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&value, MismatchCopy, &context) == 0, "Register source mismatch recovery"); + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, FloatCopy, &context) == 0, "Register source float recovery"); + Require(_ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&value, ElementCopy, &context) == 0, "Register source element recovery"); +} +struct MetadataSnapshot { + std::array bytes; + std::array allocations; + std::array generations; +}; +static MetadataSnapshot Capture(const Value& value, const PairTrace& trace) { + MetadataSnapshot snapshot{}; + const auto* words = reinterpret_cast(value.auxiliary); + snapshot.allocations = {value.auxiliary, words[2], words[3], words[4], words[5]}; + std::memcpy(snapshot.bytes.data(), value.auxiliary, snapshot.bytes.size()); + for (unsigned i = 0; i < snapshot.allocations.size(); ++i) snapshot.generations[i] = trace.live.at(snapshot.allocations[i]); + return snapshot; +} +static void Verify(const Value& value, const MetadataSnapshot& snapshot, const PairTrace& trace) { + Require(value.auxiliary == snapshot.allocations[0] && std::memcmp(value.auxiliary, snapshot.bytes.data(), snapshot.bytes.size()) == 0, "Copying must preserve complete source/destination auxiliary bytes and handler/context identities"); + for (unsigned i = 0; i < snapshot.allocations.size(); ++i) Require(trace.live.at(snapshot.allocations[i]) == snapshot.generations[i], "Copying must preserve auxiliary, fallback and all three callback-record allocation generations"); + Require(reinterpret_cast(snapshot.allocations[1])->parent == &value, "Copied callback metadata must retain its original fallback owner"); +} +int main() { + PairTrace trace; PairAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize pair allocation contracts"); + const char* documents[]{"", "null", "\"recovery\"", "{\"k\":\"recovery\",\"o\":{\"q\":1},\"a\":[true]}", "[[\"recovery\"],{\"k\":1}]"}; + const auto moduleBaseline = trace.live; + for (const auto& expected : Cases) for (bool base : {false, true}) for (bool callbackSource : {false, true}) { + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "key"); + Value source{}; _ZN3sce4Json5ValueC1Ev(&source); + const char* document = documents[expected.source]; + if (expected.source) Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&source, document, std::strlen(document)) == 0, "Construct independent pair source"); + PairCallbackContext context; + _ZN3sce4Json5ValueC1El(&context.replacement, 77); + MetadataSnapshot sourceMetadata{}; + if (callbackSource) { Register(source, context); sourceMetadata = Capture(source, trace); } + const auto sourceText = Serialize(source); + const auto baseline = trace.live; + Pair pair; std::memset(&pair, 0xa5, sizeof(pair)); + const auto first = trace.requests.size(), freeFirst = trace.releases.size(), generation = trace.generation; + const auto notifications = trace.notices; + trace.budget = expected.budget; trace.once = expected.once; + if (!expected.source) { + if (base) _ZN3sce4Json6Object4PairC2Ev(&pair); + else _ZN3sce4Json6Object4PairC1Ev(&pair); + } else { + if (base) _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); + else _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(&pair, &key, &source); + } + const auto end = trace.requests.size(), freeEnd = trace.releases.size(); + const auto errors = trace.notices - notifications; + trace.budget = -1; trace.once = false; + CheckMetadata(pair, UINT64_C(0xa5a5a5a5a5a5a5a5)); + std::string shape; Shape(pair.value, nullptr, shape); + const auto text = Serialize(pair.value); + Require(pair.value.type == expected.type && shape == expected.shape && text == expected.text && + std::strcmp(_ZNK3sce4Json6String5c_strEv(&pair.key), expected.key) == 0, + "Partial pair construction must preserve reference contents, collection counts, child parents and storage owners"); + Require(end - first == expected.requests && Fingerprint(trace.requests, first, end) == expected.requestOrder && errors == expected.notifications, + "Pair construction must preserve exact allocation request order and notifications at every failure point"); + Require(freeEnd - freeFirst == expected.constructionReleases && Fingerprint(trace.releases, freeFirst, freeEnd, generation) == expected.constructionOrder, + "Failed pair copies must preserve exact temporary release identities and observable null frees"); + const auto teardown = trace.releases.size(); + if (base) _ZN3sce4Json6Object4PairD2Ev(&pair); + else _ZN3sce4Json6Object4PairD1Ev(&pair); + Require(trace.releases.size() - teardown == expected.destructionReleases && Fingerprint(trace.releases, teardown, trace.releases.size(), generation) == expected.destructionOrder, + "Pair destruction must release the value before its key in the reference allocation-identity order"); + Require(trace.live == baseline && Serialize(source) == sourceText, "Partial pair cleanup must preserve every source allocation generation and value"); + if (callbackSource) { + Verify(source, sourceMetadata, trace); + const auto* borrowed = _ZNK3sce4Json5Value10getIntegerEv(&source); + Require(borrowed == reinterpret_cast(&context.replacement.payload) && *borrowed == 77, + "Source null/mismatch callbacks must retain their borrowed result identity after failed copies are destroyed"); + const bool nullSource = source.type == 0, objectSource = source.type == 7; + if (objectSource) _ZNK3sce4Json5ValueixEPKc(&source, "missing"); + _ZN3sce4Json5Value3setEd(&source, std::numeric_limits::infinity()); + Require(source.type == 2 && source.payload == 77 && context.nulls == unsigned(nullSource) && context.mismatches == unsigned(!nullSource) && context.elements == unsigned(objectSource) && context.floats == 1, + "Every partial-copy failure must leave the source's four independent handlers usable and correctly bound"); + Verify(source, sourceMetadata, trace); + } + _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json5ValueD1Ev(&context.replacement); _ZN3sce4Json6StringD1Ev(&key); + Require(trace.live == moduleBaseline, "Pair failure schedules must leave only the original module resources"); + } + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Release pair allocation resources"); + return 0; +} diff --git a/core/libs/tests/GuestJsonPairLayout.cpp b/core/libs/tests/GuestJsonPairLayout.cpp new file mode 100644 index 0000000000..e7149d4d66 --- /dev/null +++ b/core/libs/tests/GuestJsonPairLayout.cpp @@ -0,0 +1,82 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct PairTrace { std::map live; std::size_t generation = 0; }; +static void* APS5_VABI AllocatePair(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); + Require(trace.live.emplace(memory, ++trace.generation).second, "Pair storage must have an independently owned allocation generation"); + return memory; +} +static void APS5_VABI FreePair(void*, void* memory, void* context) { + if (!memory) return; + Require(static_cast(context)->live.erase(memory) == 1, "Pair teardown must release each allocation through its owner exactly once"); + std::free(memory); +} +static void APS5_VABI NotifyPair(void*, int, std::size_t, void*) { Require(false, "Healthy pair construction must not fail allocation"); } +static void* const PairVtable[]{nullptr, nullptr, reinterpret_cast(AllocatePair), reinterpret_cast(FreePair), reinterpret_cast(NotifyPair)}; +struct PairAllocator { void* const* vtable = PairVtable; }; +static std::string Serialize(Value& value) { + String output{}; _ZN3sce4Json6StringC1EPKc(&output, ""); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0, "Constructed pair values must remain serializable"); + std::string text = _ZNK3sce4Json6String5c_strEv(&output); + _ZN3sce4Json6StringD1Ev(&output); + return text; +} +static void CheckMetadata(const Pair& pair, std::uint64_t pattern) { + Require(pair.reserved == pattern, "Pair construction must preserve the eight inactive bytes between key and value"); + Require(!pair.value.parent && !pair.value.auxiliary && pair.value.reserved == static_cast(pattern), "Pair value construction must initialize ownership without overwriting inactive reserved bytes"); +} +int main() { + PairTrace trace; PairAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize pair layout contracts"); + const auto baseline = trace.live; + for (unsigned char poison : {0x5a, 0xa5}) for (bool base : {false, true}) { + const auto pattern = UINT64_C(0x0101010101010101) * poison; + Pair empty; std::memset(&empty, poison, sizeof(empty)); + if (base) _ZN3sce4Json6Object4PairC2Ev(&empty); + else _ZN3sce4Json6Object4PairC1Ev(&empty); + CheckMetadata(empty, pattern); + Require(empty.key.text && !_ZNK3sce4Json6String5c_strEv(&empty.key)[0] && empty.value.type == 0 && empty.value.payload == pattern, "Default pairs must construct an empty key and null value while preserving inactive payload bytes"); + if (base) _ZN3sce4Json6Object4PairD2Ev(&empty); + else _ZN3sce4Json6Object4PairD1Ev(&empty); + Require(trace.live == baseline, "Default pair destruction must preserve module allocation identities"); + for (const char* document : {"null", "false", "true", "42", "-7", "1.25", "\"x\"", "[1]", "{\"a\":1}"}) { + String key{}; _ZN3sce4Json6StringC1EPKc(&key, "key"); + Value source{}; _ZN3sce4Json5ValueC1Ev(&source); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&source, document, std::strlen(document)) == 0, "Construct the independently owned source value"); + Pair copy; std::memset(©, poison, sizeof(copy)); + if (base) _ZN3sce4Json6Object4PairC2ERKNS0_6StringERKNS0_5ValueE(©, &key, &source); + else _ZN3sce4Json6Object4PairC1ERKNS0_6StringERKNS0_5ValueE(©, &key, &source); + CheckMetadata(copy, pattern); + Require(copy.key.text != key.text && std::strcmp(_ZNK3sce4Json6String5c_strEv(©.key), "key") == 0 && copy.value.type == source.type, "Pair construction must independently own its key and copy the exact source type"); + if (source.type == 0) Require(copy.value.payload == pattern, "Null pair values must preserve inactive payload bytes"); + else if (source.type == 1) Require(copy.value.payload == ((pattern & ~UINT64_C(0xff)) | (source.payload & 0xff)), "Boolean pair values must write only their active payload byte"); + else if (source.type <= 4) Require(copy.value.payload == source.payload, "Numeric pair values must retain exact source payload bits"); + else Require(copy.value.payload != source.payload, "Complex pair values must own independent outer handles"); + Require(Serialize(copy.value) == document && Serialize(source) == document, "Pair copies must preserve source and destination contents independently"); + if (base) _ZN3sce4Json6Object4PairD2Ev(©); + else _ZN3sce4Json6Object4PairD1Ev(©); + _ZN3sce4Json5ValueD1Ev(&source); _ZN3sce4Json6StringD1Ev(&key); + Require(trace.live == baseline, "Pair copies must release their complete independent ownership"); + } + } + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Release pair layout resources"); + return 0; +} diff --git a/core/libs/tests/GuestJsonParserMetadata.cpp b/core/libs/tests/GuestJsonParserMetadata.cpp new file mode 100644 index 0000000000..5e8c11edf1 --- /dev/null +++ b/core/libs/tests/GuestJsonParserMetadata.cpp @@ -0,0 +1,143 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using LookupCallback = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); +using NullCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +using MismatchCallback = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +using FloatCallback = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ArrayTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::set headers; + std::vector frees; + std::map ids; + std::size_t nextId = 0; +}; +static void* APS5_VABI AllocateArray(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); trace.ids[memory] = ++trace.nextId; return memory; +} +static void APS5_VABI FreeArray(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + Require(trace.ids.count(memory) == 1, "Freed metadata must identify a currently live allocation generation"); + trace.frees.push_back(trace.ids.at(memory)); trace.ids.erase(memory); + if (trace.headers.erase(memory)) { + const auto* words = reinterpret_cast(memory); + Require(words[0] == 0xffffffff && words[1] == UINT64_MAX, "Array teardown must expose the native header poison before allocator release"); + } + Require(trace.live.erase(memory) == 1, "Array must release through its owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyArray(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Array notification must identify the failed allocation"); ++trace.notifications; +} +static void* const ArrayVtable[]{nullptr, nullptr, reinterpret_cast(AllocateArray), reinterpret_cast(FreeArray), reinterpret_cast(NotifyArray)}; +struct ArrayAllocator { void* const* vtable = ArrayVtable; }; +static const Value& APS5_VABI RejectNull(ValueType, const Value*, void* context) { + Require(false, "Parser metadata admission must not invoke null recovery"); + return *static_cast(context); +} +static const Value& APS5_VABI RejectMismatch(ValueType, const Value* value, const Value*, void*) { + Require(false, "Parser metadata admission must not invoke mismatch recovery"); + return *value; +} +static Value* APS5_VABI RejectFloat(Value* result, std::int32_t, double, const Value*, void*, bool*) { + Require(false, "Finite parser metadata admission must not invoke special-float recovery"); + return result; +} +static void APS5_VABI RejectElement(int, const String*, const std::size_t*, const Value*, const Value*, void*) { + Require(false, "Parser metadata admission must not invoke element recovery"); +} +static Value* MakeRoot(Value& root) { + _ZN3sce4Json5ValueC1Ev(&root); + constexpr char json[] = "{\"child\":7}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, json, sizeof(json) - 1) == 0, "Parse an owned child for metadata admission"); + auto* child = const_cast(_ZNK3sce4Json5ValueixEPKc(&root, "child")); + Require(child && child->parent == &root && !child->auxiliary && child->type == 3 && child->payload == 7, "Initial child ownership must match the containing object"); + return child; +} +int main() { + ArrayTrace trace; ArrayAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize parser metadata transitions"); + const auto baseline = trace.live.size(); + for (bool once : {false, true}) for (int budget : {0, 1, 2}) { + Value root{}; auto* child = MakeRoot(root); + const Value before = *child; + const auto beforeInvalid = trace.requests.size(); + Require(static_cast(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(child, "tru", 3)) == 0x80848101 && std::memcmp(child, &before, sizeof(before)) == 0 && trace.requests.size() == beforeInvalid, "An incomplete literal must preserve the existing child and metadata without admission"); + const auto requests = trace.requests.size(); const auto notices = trace.notifications; + trace.budget = budget; trace.once = once; + const int status = _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(child, "true", 4); + trace.budget = -1; trace.once = false; + Require(status == (budget < 2 ? static_cast(0x80848102) : 0) && !child->parent && child->type == 1 && (child->payload & 255) == 1, "A recognized public parse must detach the child and retain its admitted scalar even when root metadata allocation fails"); + Require((child->auxiliary != nullptr) == (budget != 0), "Auxiliary allocation failure must leave the detached child without invented metadata"); + if (child->auxiliary) Require((reinterpret_cast(child->auxiliary)[2] != nullptr) == (budget == 2), "Fallback failure must retain its admitted auxiliary record without synthesizing a fallback"); + const auto expected = budget == 0 ? std::vector{72} : std::vector{72, 32}; + Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected && trace.notifications == notices + (budget < 2 ? 1 : 0), "Child-to-root admission must preserve exact allocation order and notification counts"); + void* auxiliary = child->auxiliary; + const auto retryRequests = trace.requests.size(); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(child, "false", 5) == 0 && !child->parent && child->type == 1 && (child->payload & 255) == 0, "Retry must parse normally after independent admission failure"); + Require(trace.requests.size() == retryRequests + (budget == 0 ? 2 : 0) && (budget == 0 || static_cast(child->auxiliary) == auxiliary), "Retry must reuse admitted metadata and must not recreate a missing fallback"); + _ZN3sce4Json5ValueD1Ev(&root); + Require(trace.live.size() == baseline, "The containing object's destruction must release detached child metadata through its original allocator"); + } + struct Recognized { const char* text; std::int32_t type; }; + for (const auto& input : {Recognized{"null", 0}, Recognized{"true", 1}, Recognized{"false", 1}, Recognized{"123", 3}, Recognized{"3.5", 4}, Recognized{"\"x\"", 5}, Recognized{"[1]", 6}, Recognized{"{\"x\":1}", 7}}) { + Value root{}; auto* child = MakeRoot(root); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(child, input.text, std::strlen(input.text)) == 0 && child->type == input.type && !child->parent && child->auxiliary && reinterpret_cast(reinterpret_cast(child->auxiliary)[2])->parent == child, "Every recognized scalar or collection parsed directly into a child must admit it as a root"); + if (input.type == 6) Require(_ZNK3sce4Json5Array5frontEv(reinterpret_cast(child->payload))->parent == child, "A detached parsed array must bind its newly parsed elements to its actual value"); + if (input.type == 7) Require(_ZNK3sce4Json5ValueixEPKc(child, "x")->parent == child, "A detached parsed object must bind its newly parsed entries to its actual value"); + _ZN3sce4Json5ValueD1Ev(&root); + Require(trace.live.size() == baseline, "Every recognized child-to-root type transition must retain complete cleanup ownership"); + } + Value root{}; auto* child = MakeRoot(root); + void* rootAuxiliary = root.auxiliary; + void* rootFallback = reinterpret_cast(rootAuxiliary)[2]; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(child, "true", 4) == 0, "Admit a child as an independent root before registering handlers"); + Require(_ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(child, RejectNull, child) == 0 && + _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(child, RejectMismatch, child) == 0 && + _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(child, RejectFloat, child) == 0 && + _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(child, RejectElement, child) == 0, "Register all four callback forms on the detached root"); + auto** auxiliary = reinterpret_cast(child->auxiliary); + const std::vector owned{auxiliary[3], auxiliary[4], auxiliary[5], auxiliary[2], child->auxiliary}; + for (void* allocation : owned) Require(allocation && trace.live.count(allocation), "All callback records, fallback and auxiliary storage must belong to the supplied allocator"); + const auto beforeRootParse = trace.requests.size(); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(child, "false", 5) == 0 && static_cast(child->auxiliary) == auxiliary && !child->parent && trace.requests.size() == beforeRootParse, "Modern root reparsing must reuse its existing auxiliary record without allocating"); + Require(auxiliary[0] == reinterpret_cast(&RejectNull) && auxiliary[1] == child && auxiliary[2] == owned[3] && auxiliary[3] == owned[0] && auxiliary[4] == owned[1] && auxiliary[5] == owned[2], "Modern root admission must preserve every existing callback, context, handler record and fallback identity"); + std::vector ownedIds; + for (void* allocation : owned) ownedIds.push_back(trace.ids.at(allocation)); + const auto frees = trace.frees.size(); + constexpr char replacement[] = "{\"child\":false}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, replacement, sizeof(replacement) - 1) == 0, "Reparse the containing object using its existing entry"); + Require(child == _ZNK3sce4Json5ValueixEPKc(&root, "child") && child->parent == &root && !child->auxiliary && child->type == 1 && (child->payload & 255) == 0, "Nested admission must preserve node identity, rebind its parent and remove former root metadata"); + std::vector released; + for (auto i = frees; i < trace.frees.size(); ++i) for (auto id : ownedIds) if (id == trace.frees[i]) released.push_back(id); + Require(released == ownedIds && root.auxiliary == rootAuxiliary && reinterpret_cast(rootAuxiliary)[2] == rootFallback, "Nested admission must release every handler record, fallback and auxiliary in native order while preserving root metadata"); + _ZN3sce4Json5ValueD1Ev(&root); + Require(trace.live.size() == baseline && _ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty() && trace.ids.empty(), "Parser metadata transitions must leave no outstanding ownership"); + return 0; +} diff --git a/core/libs/tests/GuestJsonParserState.cpp b/core/libs/tests/GuestJsonParserState.cpp new file mode 100644 index 0000000000..3cac19f7f0 --- /dev/null +++ b/core/libs/tests/GuestJsonParserState.cpp @@ -0,0 +1,321 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; }; +static const char* CleanupDocument = nullptr; +static int CleanupBudget = -1; +static bool CleanupCallback = false; +static void Require(bool valid, const char* message) { + if (!valid) { + if (CleanupDocument) std::fprintf(stderr, "Parser document=%s callback=%d budget=%d\n", CleanupDocument, CleanupCallback, CleanupBudget); + std::fprintf(stderr, "%s\n", message); std::abort(); + } +} +struct ParserTrace : std::map { + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::size_t requests = 0; +}; +static void* APS5_VABI AllocateTracked(void*, std::size_t size, void* context) { + auto& trace = *static_cast(context); + ++trace.requests; + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(size); + Require(memory != nullptr, "Tracking allocator requires host memory"); + static_cast(context)->emplace(memory, size); + return memory; +} +static void APS5_VABI FreeTracked(void*, void* memory, void* context) { + if (!memory) return; + Require(static_cast(context)->erase(memory) == 1, "Parser allocations must be freed exactly once by their owning allocator"); + std::free(memory); +} +static void APS5_VABI NotifyTracked(void*, int error, std::size_t, void* context) { + Require(static_cast(error) == 0x80848102, "Parser allocation failures must report the Json2 out-of-memory error"); + ++static_cast(context)->notifications; +} +static int Parse(Value& value, const char* text) { + return _ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&value, text, std::strlen(text)); +} +static int APS5_VABI ReadNext(char& character, void* context) { + auto& cursor = *static_cast(context); + if (!*cursor) return -1; + character = *cursor++; + return 0; +} +static void CheckArrayEntryFailures(ParserTrace& live, std::size_t baseline) { + for (bool callback : {false, true}) for (bool valid : {false, true}) { + bool completed = false; + unsigned failures = 0; + for (int budget = 0; budget < 32 && !completed; ++budget) { + Value value{}; _ZN3sce4Json5ValueC1El(&value, 77); + Require(Parse(value, "[]") == 0, "Prepare an owned empty array before element admission"); + const char* document = valid ? "[1]" : "[1,"; + const char* cursor = document; + const auto notifications = live.notifications; + live.budget = budget; + const int status = callback ? _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&value, ReadNext, &cursor) : Parse(value, document); + live.budget = -1; + const bool failed = live.notifications != notifications; + const int expected = failed ? static_cast(0x80848102) : valid ? 0 : callback ? -1 : static_cast(0x80848101); + Require(status == expected, "Array element allocation and reader failures must preserve their public status"); + if (!failed) { + completed = true; + Require(value.type == 6 && _ZNK3sce4Json5Value5countEv(&value) == (valid ? 1 : 2), "Complete and malformed array parsing must retain the admitted entry count"); + const auto* first = _ZNK3sce4Json5Array5frontEv(reinterpret_cast(value.payload)); + Require(first->type == 3 && first->payload == 1 && first->parent == &value, "The parsed element must remain owned by its destination array"); + } else { + ++failures; + Require(Parse(value, "false") == 0 && Parse(value, "[]") == 0 && Parse(value, "[1]") == 0 && _ZNK3sce4Json5Value5countEv(&value) == 1, + "A failed array parse must permit cleanup and successful reuse of the same value"); + } + _ZN3sce4Json5ValueD1Ev(&value); + Require(live.size() == baseline, "Element admission and callback parsing must leave no unreachable guest allocations after cleanup"); + } + Require(completed && failures != 0, "Every reader and syntax case must exercise guest allocation failure and complete admission"); + } +} +static void CheckOwnedTree(const Value& value, const Value* parent = nullptr, bool partial = false) { + Require(value.parent == parent || (partial && value.type == TypeNull && !value.parent && !value.auxiliary), + "Admitted payloads must retain their owner; interrupted null placeholders may remain unbound"); + if (value.type == TypeArray) { + const auto count = _ZNK3sce4Json5Value5countEv(&value); + for (std::size_t index = 0; index < count; ++index) { + const auto* child = _ZNK3sce4Json5ValueixEm(&value, index); + Require(child != nullptr, "Admitted array entries must remain reachable"); + CheckOwnedTree(*child, &value, partial); + } + } else if (value.type == TypeObject) { + const auto* object = _ZNK3sce4Json5Value9getObjectEv(&value); + ObjectIterator position{}, end{}; + _ZNK3sce4Json6Object5beginEv(&position, object); + _ZNK3sce4Json6Object3endEv(&end, object); + std::size_t count = 0; + for (; _ZNK3sce4Json6Object8iteratorneERKS2_(&position, &end); _ZN3sce4Json6Object8iteratorppEv(&position)) { + CheckOwnedTree(_ZNK3sce4Json6Object8iteratordeEv(&position)->value, &value, partial); + ++count; + } + _ZN3sce4Json6Object8iteratorD1Ev(&position); + _ZN3sce4Json6Object8iteratorD1Ev(&end); + Require(count == _ZNK3sce4Json5Value5countEv(&value), "Every admitted object entry must remain reachable"); + } +} +static void CheckParserCleanup(ParserTrace& live, std::size_t baseline) { + struct Case { const char* document; bool valid; const char* retained; }; + const Case cases[]{ + {"[1,{\"a\":[true,null]}", false, "[1,{\"a\":[true,null]}]"}, + {"[]", true, "[]"}, {"{}", true, "{}"}, {"[1]", true, "[1]"}, + {"{\"a\":1}", true, "{\"a\":1}"}, + {"[1,{\"long\":\"a sufficiently long string to require its own allocation\"},[false,null]]", true, + "[1,{\"long\":\"a sufficiently long string to require its own allocation\"},[false,null]]"}, + {"{\"long\":\"a sufficiently long string to require its own allocation\",\"nested\":[{\"leaf\":\"another sufficiently long string\"},[true,null,17]]}", true, + "{\"long\":\"a sufficiently long string to require its own allocation\",\"nested\":[{\"leaf\":\"another sufficiently long string\"},[true,null,17]]}"}, + {"{\"a sufficiently long key to require its own allocation\":{\"child\":[1,2]}}", true, + "{\"a sufficiently long key to require its own allocation\":{\"child\":[1,2]}}"}, + {"{\"a\":[{\"b\":\"a sufficiently long string allocation\"},[true,null,17]]", false, + "{\"a\":[{\"b\":\"a sufficiently long string allocation\"},[true,null,17]]}"}, + {"{\"a\":[1]}x", true, "{\"a\":[1]}"}, + {"[{\"a\":[1]}", false, "[{\"a\":[1]}]"}, + }; + unsigned failures = 0, refusals = 0; + for (bool callback : {false, true}) for (const auto& test : cases) { + bool completed = false; + for (int budget = 0; budget < 128 && !completed; ++budget) { + CleanupDocument = test.document; CleanupBudget = budget; CleanupCallback = callback; + Value value{}; _ZN3sce4Json5ValueC1EPKc(&value, "previous"); + const auto notifications = live.notifications; + const char* cursor = test.document; + live.budget = budget; + int status = 0; + bool refused = false; + try { + status = callback ? _ZN3sce4Json6Parser5parseERNS0_5ValueEPFiRcPvES5_(&value, ReadNext, &cursor) : Parse(value, test.document); + } catch (const std::runtime_error& error) { + if (std::strcmp(error.what(), "value not implemented") != 0 || live.notifications == notifications) + std::fprintf(stderr, "Parser document=%s callback=%d budget=%d refusal=%s\n", test.document, callback, budget, error.what()); + Require(std::strcmp(error.what(), "value not implemented") == 0 && live.notifications != notifications, + "Only the documented allocation-failed array-sentinel path may refuse parsing"); + refused = true; + ++refusals; + } + live.budget = -1; + const bool failed = live.notifications != notifications; + if (!refused) { + const int expected = failed ? static_cast(0x80848102) : test.valid ? 0 : callback ? -1 : static_cast(0x80848101); + Require(status == expected, "Guest allocation and syntax failures must retain their public status"); + CheckOwnedTree(value, nullptr, failed); + } + if (!failed) { + completed = true; + String output{}; _ZN3sce4Json6StringC1Ev(&output); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0 && + std::strcmp(_ZNK3sce4Json6String5c_strEv(&output), test.retained) == 0, + "Complete and malformed documents must retain the expected tree"); + _ZN3sce4Json6StringD1Ev(&output); + } else { + ++failures; + Require(Parse(value, "false") == 0 && Parse(value, "[1]") == 0, + "Interrupted parsing must permit cleanup and reuse of the same destination"); + CheckOwnedTree(value, nullptr, failed); + } + _ZN3sce4Json5ValueD1Ev(&value); + Require(live.size() == baseline, "Parser cleanup must leave no unreachable guest allocations"); + } + Require(completed, "Every document and reader must reach its allocation-complete result"); + } + CleanupDocument = nullptr; + Require(failures != 0, "Parser cleanup must exercise guest allocation failures"); + std::printf("Parser cleanup: %u guest allocation failures, %u documented sentinel refusals, no leaks\n", failures, refusals); +} +int main() { + JsonTest::Allocator allocator; + void* const vtable[]{nullptr, nullptr, reinterpret_cast(AllocateTracked), reinterpret_cast(FreeTracked), reinterpret_cast(NotifyTracked)}; + allocator.vtable = vtable; + ParserTrace live; + Parameter parameter{&allocator, &live, 512}; + std::uint64_t initializer[2]{}; + _ZN3sce4Json11InitializerC1Ev(initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE(initializer, ¶meter) == 0, "Initialize parser state tests"); + const auto baseline = live.size(); + CheckParserCleanup(live, baseline); + CheckArrayEntryFailures(live, baseline); + struct FailureCase { bool once; int budget; int type; unsigned notifications; std::size_t requests; const char* text; }; + const FailureCase failures[]{ + {false, 0, 0, 2, 2, ""}, {false, 1, 0, 2, 3, ""}, + {false, 2, 5, 1, 3, ""}, {false, 3, 5, 1, 4, ""}, + {false, 4, 5, 9, 13, ""}, {false, 5, 5, 0, 5, "abcdefghi"}, + {true, 0, 0, 1, 3, ""}, {true, 1, 0, 1, 4, ""}, + {true, 2, 5, 1, 3, ""}, {true, 3, 5, 1, 4, ""}, + {true, 4, 5, 1, 6, "bcdefghi"}, {true, 5, 5, 0, 5, "abcdefghi"}, + }; + for (const auto& failure : failures) { + Value partial{}; _ZN3sce4Json5ValueC1El(&partial, 77); + const auto notifications = live.notifications; + const auto requests = live.requests; + live.budget = failure.budget; live.once = failure.once; + const auto status = static_cast(Parse(partial, "\"abcdefghi\"")); + live.budget = -1; live.once = false; + Require(status == (failure.notifications ? 0x80848102u : 0u) && partial.type == failure.type, "String parser allocation failures must retain the reference type and status"); + Require(live.notifications - notifications == failure.notifications && live.requests - requests == failure.requests, "String parser failures must follow storage-before-metadata admission and character retries"); + if (partial.type == 5) Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(partial.payload)), failure.text) == 0, "Failed string parsing must retain the admitted character suffix"); + _ZN3sce4Json5ValueD1Ev(&partial); + Require(live.size() == baseline, "String parser failure must reclaim partial storage and metadata"); + } + struct ArrayFailure { bool once; int budget; int type; std::size_t count; std::uint64_t number; int lastType; const char* text; unsigned notifications; std::size_t requests; }; + const ArrayFailure arrayFailures[]{ + {0, 0, 0, 0, 0, 0, "", 2, 2}, + {0, 1, 0, 0, 0, 0, "", 2, 3}, + {0, 3, 6, 0, 0, 0, "", 2, 5}, + {0, 4, 6, 0, 0, 0, "", 2, 6}, + {0, 5, 6, 0, 0, 0, "", 1, 6}, + {0, 6, 6, 0, 0, 0, "", 2, 8}, + {0, 7, 6, 0, 0, 0, "", 2, 9}, + {0, 8, 6, 0, 0, 0, "", 1, 9}, + {0, 9, 6, 2, 1, 0, "", 1, 10}, + {0, 10, 6, 2, 1, 0, "", 1, 11}, + {0, 11, 6, 2, 1, 5, "", 3, 14}, + {0, 12, 6, 2, 1, 5, "abc", 0, 12}, + {1, 0, 0, 0, 0, 0, "", 1, 3}, + {1, 1, 0, 0, 0, 0, "", 1, 4}, + {1, 3, 6, 0, 0, 0, "", 1, 5}, + {1, 4, 6, 0, 0, 0, "", 1, 6}, + {1, 5, 6, 0, 0, 0, "", 1, 6}, + {1, 6, 6, 1, 0, 0, "", 1, 8}, + {1, 7, 6, 1, 0, 0, "", 1, 9}, + {1, 8, 6, 0, 0, 0, "", 1, 9}, + {1, 9, 6, 2, 1, 0, "", 1, 10}, + {1, 10, 6, 2, 1, 0, "", 1, 11}, + {1, 11, 6, 2, 1, 5, "bc", 1, 13}, + {1, 12, 6, 2, 1, 5, "abc", 0, 12}, + }; + for (const auto& failure : arrayFailures) { + Value partial{}; _ZN3sce4Json5ValueC1El(&partial, 77); + const auto notifications = live.notifications; + const auto requests = live.requests; + live.budget = failure.budget; live.once = failure.once; + const auto status = static_cast(Parse(partial, "[1,\"abc\"]")); + live.budget = -1; live.once = false; + Require(status == (failure.notifications ? 0x80848102u : 0u) && partial.type == failure.type, "Array parser allocation failures must retain the reference status and type"); + Require(live.notifications - notifications == failure.notifications && live.requests - requests == failure.requests, "Array parser failures must account for numeric-token storage and inherited append errors"); + if (partial.type == 6) { + Require(_ZNK3sce4Json5Value5countEv(&partial) == failure.count, "Array parser failures must retain the reference partial count"); + if (failure.count) { + const auto* array = reinterpret_cast(partial.payload); + const auto* first = _ZNK3sce4Json5Array5frontEv(array); + const auto* last = _ZNK3sce4Json5Array4backEv(array); + Require(first->type == 3 && first->payload == failure.number && first->parent == &partial && last->parent == &partial, "Array parser failures must retain numeric values and admitted parent links"); + if (failure.count == 2) { + Require(last->type == failure.lastType, "Array parser failures must preserve the final admitted value type"); + if (last->type == 5) Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(last->payload)), failure.text) == 0, "Array parser failures must retain the admitted string suffix"); + } + } + } + _ZN3sce4Json5ValueD1Ev(&partial); + Require(live.size() == baseline, "Array parser failure must reclaim all admitted resources"); + } + for (const char* text : {"", "?", "tru"}) { + Value value{}; _ZN3sce4Json5ValueC1El(&value, 77); + Require(static_cast(Parse(value, text)) == 0x80848101 && value.type == 2 && value.payload == 77 && !value.auxiliary, "Unrecognized or incomplete scalar input must preserve the previous payload"); + _ZN3sce4Json5ValueD1Ev(&value); + } + for (const char* text : {"[1,", "[1,?]"}) { + Value value{}; _ZN3sce4Json5ValueC1El(&value, 77); + Require(static_cast(Parse(value, text)) == 0x80848101 && value.type == 6 && _ZNK3sce4Json5Value5countEv(&value) == 2, "Failed array parsing must retain admitted entries instead of restoring the destination"); + auto* array = reinterpret_cast(value.payload); + const auto* first = _ZNK3sce4Json5Array5frontEv(array); + const auto* last = _ZNK3sce4Json5Array4backEv(array); + Require(first->type == 3 && first->payload == 1 && first->parent == &value && last->type == 0, "Partial array entries must retain their payload and destination parent"); + Require(Parse(value, "[]") == 0 && _ZNK3sce4Json5Value5countEv(&value) == 2, "Parsing an empty array into an existing array must retain its entries"); + _ZN3sce4Json5ValueD1Ev(&value); + } + for (const char* text : {"{\"a\":1,", "{\"a\":?}"}) { + Value value{}; _ZN3sce4Json5ValueC1El(&value, 77); + Require(static_cast(Parse(value, text)) == 0x80848101 && value.type == 7 && _ZNK3sce4Json5Value5countEv(&value) == 1, "Failed object parsing must retain the admitted member"); + Require(Parse(value, "{}") == 0 && _ZNK3sce4Json5Value5countEv(&value) == 1, "Parsing an empty object into an existing object must retain its members"); + _ZN3sce4Json5ValueD1Ev(&value); + } + Value missingMember{}; _ZN3sce4Json5ValueC1El(&missingMember, 77); + Require(static_cast(Parse(missingMember, "{\"a\":}")) == 0x80848101 && + _ZNK3sce4Json5Value7getTypeEv(&missingMember) == 7, + "A missing member value must report parse failure while retaining the object type"); + Require(Parse(missingMember, "{}") == 0 && _ZNK3sce4Json5Value7getTypeEv(&missingMember) == 7, + "The retained object must remain usable after a missing-value parse error"); + _ZN3sce4Json5ValueD1Ev(&missingMember); + Require(live.size() == baseline, "The missing-value error and retry must reclaim admitted storage"); + Value nested{}; _ZN3sce4Json5ValueC1El(&nested, 77); + Require(static_cast(Parse(nested, "[[1,")) == 0x80848101 && nested.type == 6, "Nested failures must retain the outer collection"); + const auto* inner = _ZNK3sce4Json5Array5frontEv(reinterpret_cast(nested.payload)); + Require(inner->type == 6 && inner->parent == &nested && _ZNK3sce4Json5Value5countEv(inner) == 2, "Nested partial collections must retain their immediate owner"); + const auto* leaf = _ZNK3sce4Json5Array5frontEv(reinterpret_cast(inner->payload)); + Require(leaf->parent == inner && leaf->payload == 1, "Nested leaf parents must point into the retained destination tree"); + Require(Parse(nested, "42") == 0 && nested.type == 3 && nested.payload == 42, "Numeric parsing must replace an existing collection"); + _ZN3sce4Json5ValueD1Ev(&nested); + Value object{}; _ZN3sce4Json5ValueC1El(&object, 77); + Require(Parse(object, "{\"a\":9}") == 0 && static_cast(Parse(object, "{\"a\":?}")) == 0x80848101, "Prepare duplicate member failure"); + const auto* retained = _ZNK3sce4Json5ValueixEPKc(&object, "a"); + Require(retained->type == 3 && retained->payload == 9 && retained->parent == &object, "An unrecognized duplicate member value must preserve the prior member"); + _ZN3sce4Json5ValueD1Ev(&object); + Value value{}; _ZN3sce4Json5ValueC1El(&value, 77); + Require(static_cast(Parse(value, "\"unterminated")) == 0x80848101 && value.type == 5, "Unterminated strings must change the destination type"); + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(value.payload)), "unterminated") == 0, "Unterminated strings must retain their decoded prefix"); + Require(Parse(value, "\"!\"") == 0 && std::strcmp(_ZNK3sce4Json6String5c_strEv(reinterpret_cast(value.payload)), "unterminated!") == 0, "Parsing another string must append to existing string storage"); + Require(Parse(value, "true") == 0 && Parse(value, "false") == 0 && value.type == 1 && !(value.payload & 255), "Parsing false must replace an existing true boolean"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(live.size() == baseline, "Partial parsing and numeric replacement must reclaim all value allocations"); + Require(_ZN3sce4Json11Initializer9terminateEv(initializer) == 0, "Terminate parser state tests"); + Require(live.empty(), "Termination must reclaim the remaining parser context resources"); + return 0; +} diff --git a/core/libs/tests/GuestJsonRecoveryAllocator.cpp b/core/libs/tests/GuestJsonRecoveryAllocator.cpp new file mode 100644 index 0000000000..86a5df44ee --- /dev/null +++ b/core/libs/tests/GuestJsonRecoveryAllocator.cpp @@ -0,0 +1,966 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static_assert(sizeof(Value) == 32 && sizeof(Parameter) == 40); +using FloatCallback = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct RecoveryAllocationTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::map ids; + std::size_t nextId = 0; +}; +static void* APS5_VABI AllocateRecovery(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); trace.ids[memory] = ++trace.nextId; return memory; +} +static void APS5_VABI FreeRecovery(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + Require(trace.ids.count(memory) == 1, "Freed recovery storage must identify a currently live allocation generation"); + trace.ids.erase(memory); + Require(trace.live.erase(memory) == 1, "Recovery storage must release through its owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyRecovery(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Recovery notification must identify the failed allocation"); ++trace.notifications; +} +static void* const RecoveryVtable[]{nullptr, nullptr, reinterpret_cast(AllocateRecovery), reinterpret_cast(FreeRecovery), reinterpret_cast(NotifyRecovery)}; +struct RecoveryAllocator { void* const* vtable = RecoveryVtable; }; +struct RecoveryContext { const Value* replacement; bool object; unsigned calls = 0; int function = -1; bool errorPointer = false; }; +static Value* APS5_VABI RecoverAllocated(Value* result, std::int32_t function, double number, const Value* parent, void* context, bool* error) { + auto& trace = *static_cast(context); + ++trace.calls; trace.function = function; trace.errorPointer = error != nullptr; + Require(number == std::numeric_limits::infinity() && !parent, "Recovery must receive the original non-finite value and immediate parent"); + if (trace.object) _ZN3sce4Json5ValueC1ERKS1_(result, trace.replacement); + else _ZN3sce4Json5ValueC1EPKc(result, "recovery"); + if (error) *error = false; + return result; +} +static std::string Contents(Value& value) { + if (value.type == 0) return "null"; + if (value.type == 4) { Require(value.payload == std::bit_cast(std::numeric_limits::infinity()), "Serialization recovery must preserve the source real payload bits"); return "inf"; } + if (value.type == 5) return _ZNK3sce4Json6String5c_strEv(reinterpret_cast(value.payload)); + String output{}; _ZN3sce4Json6StringC1EPKc(&output, ""); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output) == 0, "Partial callback-return objects must remain serializable"); + std::string result = _ZNK3sce4Json6String5c_strEv(&output); + _ZN3sce4Json6StringD1Ev(&output); + return result; +} +struct Expected { + std::uint8_t object; bool format; bool once; int budget; int type; + const char* contents; const char* output; std::size_t requests; unsigned notifications; std::uint64_t order; int lookupType; +}; +static const Expected Cases[]{ + {0, 0, 0, 0, 0, "null", "p", 1, 1, UINT64_C(0xaf63d54c8601e0a7), -2}, + {0, 0, 0, 1, 0, "null", "p", 3, 2, UINT64_C(0xa9485c18e1e33846), -2}, + {0, 0, 0, 2, 0, "null", "p", 3, 1, UINT64_C(0xa9485c18e1e33846), -2}, + {0, 0, 0, 3, 0, "null", "p", 4, 1, UINT64_C(0x892cdc47d518c7ba), -2}, + {0, 0, 0, 4, 0, "null", "p", 6, 2, UINT64_C(0xaebceda7a971a713), -2}, + {0, 0, 0, 5, 0, "null", "p", 6, 1, UINT64_C(0xaebceda7a971a713), -2}, + {0, 0, 0, 6, 0, "null", "p", 7, 1, UINT64_C(0x5caedee4ec1ed7b1), -2}, + {0, 0, 0, 7, 0, "null", "p", 9, 2, UINT64_C(0x768bd046d9a8a6e0), -2}, + {0, 0, 0, 8, 0, "null", "p", 9, 1, UINT64_C(0x768bd046d9a8a6e0), -2}, + {0, 0, 0, 9, 0, "null", "p", 10, 1, UINT64_C(0x1839e063d993b768), -2}, + {0, 0, 0, 10, 5, "", "p", 11, 1, UINT64_C(0xbe0faaaab60099d3), -2}, + {0, 0, 0, 11, 5, "recovery", "p", 11, 0, UINT64_C(0xbe0faaaab60099d3), -2}, + {0, 0, 1, 0, 0, "null", "p", 1, 1, UINT64_C(0xaf63d54c8601e0a7), -2}, + {0, 0, 1, 1, 5, "", "p", 8, 1, UINT64_C(0xfc01dbf4e232edfa), -2}, + {0, 0, 1, 2, 0, "null", "p", 3, 1, UINT64_C(0xa9485c18e1e33846), -2}, + {0, 0, 1, 3, 0, "null", "p", 4, 1, UINT64_C(0x892cdc47d518c7ba), -2}, + {0, 0, 1, 4, 5, "", "p", 9, 1, UINT64_C(0x768f2646d9ab6ed9), -2}, + {0, 0, 1, 5, 0, "null", "p", 6, 1, UINT64_C(0xaebceda7a971a713), -2}, + {0, 0, 1, 6, 0, "null", "p", 7, 1, UINT64_C(0x5caedee4ec1ed7b1), -2}, + {0, 0, 1, 7, 5, "", "p", 10, 1, UINT64_C(0x1839e063d993b768), -2}, + {0, 0, 1, 8, 0, "null", "p", 9, 1, UINT64_C(0x768bd046d9a8a6e0), -2}, + {0, 0, 1, 9, 0, "null", "p", 10, 1, UINT64_C(0x1839e063d993b768), -2}, + {0, 0, 1, 10, 5, "", "p", 11, 1, UINT64_C(0xbe0faaaab60099d3), -2}, + {0, 0, 1, 11, 5, "recovery", "p", 11, 0, UINT64_C(0xbe0faaaab60099d3), -2}, + {0, 1, 0, 0, 4, "inf", "p", 2, 2, UINT64_C(0x08843007b530e48d), -2}, + {0, 1, 0, 1, 4, "inf", "p", 3, 2, UINT64_C(0xa9823518e2146e48), -2}, + {0, 1, 0, 2, 4, "inf", "p", 5, 3, UINT64_C(0xc691469ccd6898fb), -2}, + {0, 1, 0, 3, 4, "inf", "p", 5, 2, UINT64_C(0xc691469ccd6898fb), -2}, + {0, 1, 0, 4, 4, "inf", "p", 6, 2, UINT64_C(0xd162e67108ad626b), -2}, + {0, 1, 0, 5, 4, "inf", "p", 8, 3, UINT64_C(0xac5d752721a88d90), -2}, + {0, 1, 0, 6, 4, "inf", "p", 8, 2, UINT64_C(0xac5d752721a88d90), -2}, + {0, 1, 0, 7, 4, "inf", "p", 9, 2, UINT64_C(0x8b87da7e318e2776), -2}, + {0, 1, 0, 8, 4, "inf", "p", 11, 3, UINT64_C(0x7c2105433836840e), -2}, + {0, 1, 0, 9, 4, "inf", "p", 19, 10, UINT64_C(0xaef2a03652afb7be), -2}, + {0, 1, 0, 10, 4, "inf", "p", 11, 1, UINT64_C(0x7c20ff43383679dc), -2}, + {0, 1, 0, 11, 4, "inf", "p\"recovery\"", 11, 0, UINT64_C(0x7c20ff43383679dc), -2}, + {0, 1, 1, 0, 4, "inf", "p", 9, 1, UINT64_C(0x8b87d67e318e20aa), -2}, + {0, 1, 1, 1, 4, "inf", "pnull", 4, 1, UINT64_C(0x1cae974828b789ba), -2}, + {0, 1, 1, 2, 4, "inf", "p\"\"", 9, 1, UINT64_C(0x60e103758cc7062a), -2}, + {0, 1, 1, 3, 4, "inf", "pnull", 6, 1, UINT64_C(0xd173e97108bbdab7), -2}, + {0, 1, 1, 4, 4, "inf", "pnull", 7, 1, UINT64_C(0x78700f11be9e5a67), -2}, + {0, 1, 1, 5, 4, "inf", "p\"\"", 10, 1, UINT64_C(0xa609f86e3495cb61), -2}, + {0, 1, 1, 6, 4, "inf", "pnull", 9, 1, UINT64_C(0x8b5b977e31687ab2), -2}, + {0, 1, 1, 7, 4, "inf", "pnull", 10, 1, UINT64_C(0xa5ffa46e348ce820), -2}, + {0, 1, 1, 8, 4, "inf", "p\"\"", 11, 1, UINT64_C(0x7c21074338368774), -2}, + {0, 1, 1, 9, 4, "inf", "precovery\"", 12, 1, UINT64_C(0x22a0063884a26faf), -2}, + {0, 1, 1, 10, 4, "inf", "p", 11, 1, UINT64_C(0x7c20ff43383679dc), -2}, + {0, 1, 1, 11, 4, "inf", "p\"recovery\"", 11, 0, UINT64_C(0x7c20ff43383679dc), -2}, + {1, 0, 0, 0, 0, "null", "p", 1, 1, UINT64_C(0xaf63c54c8601c577), -2}, + {1, 0, 0, 1, 0, "null", "p", 2, 1, UINT64_C(0x084d9807b50252d5), -2}, + {1, 0, 0, 2, 0, "null", "p", 3, 1, UINT64_C(0x1e2c121892f29727), -2}, + {1, 0, 0, 3, 0, "null", "p", 5, 2, UINT64_C(0x7a8da821d2d372b7), -2}, + {1, 0, 0, 4, 0, "null", "p", 6, 2, UINT64_C(0x10e130793c36cb2d), -2}, + {1, 0, 0, 5, 0, "null", "p", 10, 5, UINT64_C(0xb187c4272f9942bf), -2}, + {1, 0, 0, 6, 0, "null", "p", 11, 5, UINT64_C(0x10b56be74c59bf9d), -2}, + {1, 0, 0, 7, 0, "null", "p", 12, 5, UINT64_C(0xe7ef7936b9e1d087), -2}, + {1, 0, 0, 8, 0, "null", "p", 14, 6, UINT64_C(0x2f22e7a49d59a9e5), -2}, + {1, 0, 0, 9, 0, "null", "p", 14, 5, UINT64_C(0x2f22e7a49d59a9e5), -2}, + {1, 0, 0, 10, 0, "null", "p", 16, 6, UINT64_C(0x3eca414f9a84948f), -2}, + {1, 0, 0, 11, 0, "null", "p", 16, 5, UINT64_C(0x3eca414f9a84948f), -2}, + {1, 0, 0, 12, 0, "null", "p", 17, 5, UINT64_C(0xe4f17e1059ea318d), -2}, + {1, 0, 0, 13, 0, "null", "p", 18, 5, UINT64_C(0xb242dac6f307d43f), -2}, + {1, 0, 0, 14, 0, "null", "p", 19, 5, UINT64_C(0x3c673d38f9599c87), -2}, + {1, 0, 0, 15, 0, "null", "p", 19, 4, UINT64_C(0x3c673d38f9599c87), -2}, + {1, 0, 0, 16, 0, "null", "p", 20, 4, UINT64_C(0x28b4cf2275458c17), -2}, + {1, 0, 0, 17, 0, "null", "p", 20, 3, UINT64_C(0x28b4cf2275458c17), -2}, + {1, 0, 0, 18, 0, "null", "p", 21, 3, UINT64_C(0xbd17e68bbdc2b29d), -2}, + {1, 0, 0, 19, 0, "null", "p", 21, 2, UINT64_C(0xbd17e68bbdc2b29d), -2}, + {1, 0, 0, 20, 0, "null", "p", 24, 4, UINT64_C(0x8aa851c4ba2dfe0d), -2}, + {1, 0, 0, 21, 0, "null", "p", 24, 3, UINT64_C(0x8aa851c4ba2dfe0d), -2}, + {1, 0, 0, 22, 0, "null", "p", 25, 3, UINT64_C(0x960824483eb6c5ad), -2}, + {1, 0, 0, 23, 0, "null", "p", 25, 2, UINT64_C(0x960824483eb6c5ad), -2}, + {1, 0, 0, 24, 0, "null", "p", 26, 2, UINT64_C(0xa664ebc29063a4cf), -2}, + {1, 0, 0, 25, 0, "null", "p", 27, 2, UINT64_C(0x211dfc9b594e1afc), -2}, + {1, 0, 0, 26, 0, "null", "p", 27, 1, UINT64_C(0x211dfc9b594e1afc), -2}, + {1, 0, 0, 27, 0, "null", "p", 28, 1, UINT64_C(0x940f17f8bfb7a3d4), -2}, + {1, 0, 0, 28, 0, "null", "p", 29, 1, UINT64_C(0x4d4977adc50b3874), -2}, + {1, 0, 0, 29, 0, "null", "p", 31, 2, UINT64_C(0x96ac3aa3f26673dc), -2}, + {1, 0, 0, 30, 0, "null", "p", 32, 2, UINT64_C(0x6d883894e473365c), -2}, + {1, 0, 0, 31, 0, "null", "p", 36, 5, UINT64_C(0xa689d44f1e8c390a), -2}, + {1, 0, 0, 32, 0, "null", "p", 37, 5, UINT64_C(0xb52aeaa3bc7e29da), -2}, + {1, 0, 0, 33, 0, "null", "p", 38, 5, UINT64_C(0x2bdfebe44514ddfa), -2}, + {1, 0, 0, 34, 0, "null", "p", 40, 6, UINT64_C(0x52ab3c4c65391168), -2}, + {1, 0, 0, 35, 0, "null", "p", 40, 5, UINT64_C(0x52ab3c4c65391168), -2}, + {1, 0, 0, 36, 0, "null", "p", 42, 6, UINT64_C(0xd4a882bc4fea0176), -2}, + {1, 0, 0, 37, 0, "null", "p", 42, 5, UINT64_C(0xd4a882bc4fea0176), -2}, + {1, 0, 0, 38, 0, "null", "p", 43, 5, UINT64_C(0xa7aebec77c7e3806), -2}, + {1, 0, 0, 39, 0, "null", "p", 44, 5, UINT64_C(0x2c2b5af6b31d5a8a), -2}, + {1, 0, 0, 40, 0, "null", "p", 45, 5, UINT64_C(0xe2e498da0c1ac778), -2}, + {1, 0, 0, 41, 0, "null", "p", 45, 4, UINT64_C(0xe2e498da0c1ac778), -2}, + {1, 0, 0, 42, 0, "null", "p", 46, 4, UINT64_C(0x3097224f03ed303a), -2}, + {1, 0, 0, 43, 0, "null", "p", 46, 3, UINT64_C(0x3097224f03ed303a), -2}, + {1, 0, 0, 44, 0, "null", "p", 47, 3, UINT64_C(0xddcbae42c02cbfc6), -2}, + {1, 0, 0, 45, 0, "null", "p", 47, 2, UINT64_C(0xddcbae42c02cbfc6), -2}, + {1, 0, 0, 46, 0, "null", "p", 50, 4, UINT64_C(0xc12186d4776e2450), -2}, + {1, 0, 0, 47, 0, "null", "p", 50, 3, UINT64_C(0xc12186d4776e2450), -2}, + {1, 0, 0, 48, 0, "null", "p", 51, 3, UINT64_C(0x6659e106d2e5e0e6), -2}, + {1, 0, 0, 49, 0, "null", "p", 51, 2, UINT64_C(0x6659e106d2e5e0e6), -2}, + {1, 0, 0, 50, 0, "null", "p", 52, 2, UINT64_C(0xd0d0ac985ccb66fa), -2}, + {1, 0, 0, 51, 0, "null", "p", 53, 2, UINT64_C(0x9dff9ee5ada2d05f), -2}, + {1, 0, 0, 52, 0, "null", "p", 53, 1, UINT64_C(0x9dff9ee5ada2d05f), -2}, + {1, 0, 0, 53, 0, "null", "p", 54, 1, UINT64_C(0x1c2b7f460ba847cd), -2}, + {1, 0, 0, 54, 0, "null", "p", 55, 1, UINT64_C(0x8630e905cef1bd5f), -2}, + {1, 0, 0, 55, 7, "{}", "p", 56, 1, UINT64_C(0xf6d973dea4c4fecd), -1}, + {1, 0, 0, 56, 7, "{}", "p", 57, 1, UINT64_C(0x387e8851fabcb25f), -1}, + {1, 0, 0, 57, 7, "{}", "p", 61, 4, UINT64_C(0x3956b24396957ae5), -1}, + {1, 0, 0, 58, 7, "{}", "p", 62, 4, UINT64_C(0xd13b7fd8c3c27f9f), -1}, + {1, 0, 0, 59, 7, "{}", "p", 63, 4, UINT64_C(0xf2c99a54716bbccd), -1}, + {1, 0, 0, 60, 7, "{}", "p", 65, 5, UINT64_C(0xf2ece4ea49ff28d7), -1}, + {1, 0, 0, 61, 7, "{}", "p", 65, 4, UINT64_C(0xf2ece4ea49ff28d7), -1}, + {1, 0, 0, 62, 7, "{}", "p", 67, 5, UINT64_C(0xa80b0851a72bbbf5), -1}, + {1, 0, 0, 63, 7, "{}", "p", 67, 4, UINT64_C(0xa80b0851a72bbbf5), -1}, + {1, 0, 0, 64, 7, "{}", "p", 68, 4, UINT64_C(0x355849f180ded08f), -1}, + {1, 0, 0, 65, 7, "{}", "p", 69, 4, UINT64_C(0x84428c5dfaf81765), -1}, + {1, 0, 0, 66, 7, "{}", "p", 70, 4, UINT64_C(0x012028e3c8954fdd), -1}, + {1, 0, 0, 67, 7, "{}", "p", 70, 3, UINT64_C(0x012028e3c8954fdd), -1}, + {1, 0, 0, 68, 7, "{}", "p", 71, 3, UINT64_C(0x4b691e0db8a2f8dd), -1}, + {1, 0, 0, 69, 7, "{}", "p", 71, 2, UINT64_C(0x4b691e0db8a2f8dd), -1}, + {1, 0, 0, 70, 7, "{}", "p", 72, 2, UINT64_C(0xc739a650bd76c67f), -1}, + {1, 0, 0, 71, 7, "{}", "p", 72, 1, UINT64_C(0xc739a650bd76c67f), -1}, + {1, 0, 0, 72, 7, "{\"\":null}", "p", 75, 3, UINT64_C(0x9e8b9c317656d9bf), -1}, + {1, 0, 0, 73, 7, "{\"\":null}", "p", 75, 2, UINT64_C(0x9e8b9c317656d9bf), 0}, + {1, 0, 0, 74, 7, "{\"\":null}", "p", 76, 2, UINT64_C(0xbe361b0c15b0d18f), 0}, + {1, 0, 0, 75, 7, "{\"k\":null}", "p", 76, 1, UINT64_C(0xbe361b0c15b0d18f), 0}, + {1, 0, 0, 76, 7, "{\"k\":null}", "p", 77, 1, UINT64_C(0xe6c18c88db742395), 0}, + {1, 0, 0, 77, 7, "{\"k\":\"\"}", "p", 78, 1, UINT64_C(0x8f05688ce6588214), 5}, + {1, 0, 0, 78, 7, "{\"k\":\"recovery\"}", "p", 78, 0, UINT64_C(0x8f05688ce6588214), 5}, + {1, 0, 1, 0, 0, "null", "p", 1, 1, UINT64_C(0xaf63c54c8601c577), -2}, + {1, 0, 1, 1, 0, "null", "p", 2, 1, UINT64_C(0x084d9807b50252d5), -2}, + {1, 0, 1, 2, 0, "null", "p", 3, 1, UINT64_C(0x1e2c121892f29727), -2}, + {1, 0, 1, 3, 7, "{}", "p", 14, 1, UINT64_C(0xd76d5b07238706ad), -1}, + {1, 0, 1, 4, 7, "{}", "p", 15, 1, UINT64_C(0xccfbd0932ed1c61f), -1}, + {1, 0, 1, 5, 7, "{\"k\":\"recovery\"}", "p", 73, 1, UINT64_C(0x15fb0ebd0a33b7ca), 5}, + {1, 0, 1, 6, 7, "{\"k\":\"recovery\"}", "p", 74, 1, UINT64_C(0x68d020c916f67c54), 5}, + {1, 0, 1, 7, 7, "{\"\":\"recovery\"}", "p", 63, 1, UINT64_C(0x2d23dde27a017aa6), 5}, + {1, 0, 1, 8, 7, "{\"\":\"recovery\"}", "p", 67, 1, UINT64_C(0xe8726979e3018f08), 5}, + {1, 0, 1, 9, 7, "{\"\":\"recovery\"}", "p", 65, 1, UINT64_C(0xc4d03eb0173d3988), 5}, + {1, 0, 1, 10, 7, "{\"\":\"recovery\"}", "p", 68, 1, UINT64_C(0x59a5ec0540fd0250), 5}, + {1, 0, 1, 11, 7, "{\"k\":\"recovery\"}", "p", 75, 1, UINT64_C(0xc4135e36c1583f72), 5}, + {1, 0, 1, 12, 7, "{\"k\":\"recovery\"}", "p", 76, 1, UINT64_C(0x8ede4064f5a34ddc), 5}, + {1, 0, 1, 13, 7, "{\"k\":\"\"}", "p", 67, 1, UINT64_C(0x8c6b9268a2c5cfcd), -1}, + {1, 0, 1, 14, 7, "{\"k\":\"\"}", "p", 68, 1, UINT64_C(0x9f6328349f5efe35), -1}, + {1, 0, 1, 15, 7, "{\"\":\"\"}", "p", 64, 1, UINT64_C(0xddef821334a86667), 5}, + {1, 0, 1, 16, 7, "{\"\":\"\"}", "p", 66, 1, UINT64_C(0x9eaa88f7ed6fe65d), 5}, + {1, 0, 1, 17, 7, "{\"k\":null}", "p", 56, 1, UINT64_C(0x8250699960f4e85f), 0}, + {1, 0, 1, 18, 7, "{\"k\":null}", "p", 57, 1, UINT64_C(0x9b6c3d4dacd7a045), 0}, + {1, 0, 1, 19, 7, "{}", "p", 30, 1, UINT64_C(0x81db53d0e75644ef), -1}, + {1, 0, 1, 20, 7, "{\"k\":\"\"}", "p", 69, 1, UINT64_C(0xd73452dd7647589d), -1}, + {1, 0, 1, 21, 7, "{\"\":\"\"}", "p", 66, 1, UINT64_C(0x9eaa88f7ed6fe65d), 5}, + {1, 0, 1, 22, 7, "{\"\":\"\"}", "p", 67, 1, UINT64_C(0x96214c6d9d4d7ffd), 5}, + {1, 0, 1, 23, 7, "{\"k\":null}", "p", 58, 1, UINT64_C(0x5334f2ccc09e7717), 0}, + {1, 0, 1, 24, 7, "{\"k\":null}", "p", 59, 1, UINT64_C(0x82d1353a8c895ebd), 0}, + {1, 0, 1, 25, 7, "{\"k\":\"\"}", "p", 76, 1, UINT64_C(0x1be6a6f37e090450), 5}, + {1, 0, 1, 26, 0, "null", "p", 27, 1, UINT64_C(0x211dfc9b594e1afc), -2}, + {1, 0, 1, 27, 0, "null", "p", 28, 1, UINT64_C(0x940f17f8bfb7a3d4), -2}, + {1, 0, 1, 28, 0, "null", "p", 29, 1, UINT64_C(0x4d4977adc50b3874), -2}, + {1, 0, 1, 29, 7, "{}", "p", 35, 1, UINT64_C(0x961e76ecf8fd871c), -1}, + {1, 0, 1, 30, 7, "{}", "p", 36, 1, UINT64_C(0xef7cb822b58db19c), -1}, + {1, 0, 1, 31, 7, "{\"k\":\"recovery\"}", "p", 73, 1, UINT64_C(0xdbd99fe5ed4883e4), 5}, + {1, 0, 1, 32, 7, "{\"k\":\"recovery\"}", "p", 74, 1, UINT64_C(0x6a386f444a5e4254), 5}, + {1, 0, 1, 33, 7, "{\"\":\"recovery\"}", "p", 67, 1, UINT64_C(0x7e21bf21deb3808c), 5}, + {1, 0, 1, 34, 7, "{\"\":\"recovery\"}", "p", 71, 1, UINT64_C(0x982621b7c5f73376), 5}, + {1, 0, 1, 35, 7, "{\"\":\"recovery\"}", "p", 69, 1, UINT64_C(0x77d0ac84582d5f7a), 5}, + {1, 0, 1, 36, 7, "{\"\":\"recovery\"}", "p", 72, 1, UINT64_C(0xee3d5bf84f6d2a40), 5}, + {1, 0, 1, 37, 7, "{\"k\":\"recovery\"}", "p", 75, 1, UINT64_C(0xc5d9c4afc4709dd4), 5}, + {1, 0, 1, 38, 7, "{\"k\":\"recovery\"}", "p", 76, 1, UINT64_C(0x8cfe23e5d4680904), 5}, + {1, 0, 1, 39, 7, "{\"k\":\"\"}", "p", 71, 1, UINT64_C(0xe7ff3f815d66ff2e), -1}, + {1, 0, 1, 40, 7, "{\"k\":\"\"}", "p", 72, 1, UINT64_C(0x3296cb16b10379e8), -1}, + {1, 0, 1, 41, 7, "{\"\":\"\"}", "p", 69, 1, UINT64_C(0xeb080365bc3f30bc), 5}, + {1, 0, 1, 42, 7, "{\"\":\"\"}", "p", 71, 1, UINT64_C(0xdf67a4e26b3d2a1e), 5}, + {1, 0, 1, 43, 7, "{\"k\":null}", "p", 65, 1, UINT64_C(0xa0086042559c8e1c), 0}, + {1, 0, 1, 44, 7, "{\"k\":null}", "p", 66, 1, UINT64_C(0x51cdd5df396e0ba0), 0}, + {1, 0, 1, 45, 7, "{}", "p", 51, 1, UINT64_C(0x383161da3f804726), -1}, + {1, 0, 1, 46, 7, "{\"k\":\"\"}", "p", 73, 1, UINT64_C(0x7eb80b99470a2eba), -1}, + {1, 0, 1, 47, 7, "{\"\":\"\"}", "p", 71, 1, UINT64_C(0xdf67a4e26b3d2a1e), 5}, + {1, 0, 1, 48, 7, "{\"\":\"\"}", "p", 72, 1, UINT64_C(0x8f4766e11afb39d4), 5}, + {1, 0, 1, 49, 7, "{\"k\":null}", "p", 67, 1, UINT64_C(0x279a838f9bdfaefc), 0}, + {1, 0, 1, 50, 7, "{\"k\":null}", "p", 68, 1, UINT64_C(0x5a342f85a466b390), 0}, + {1, 0, 1, 51, 7, "{\"k\":\"\"}", "p", 77, 1, UINT64_C(0xe6c18c88db742395), 5}, + {1, 0, 1, 52, 0, "null", "p", 53, 1, UINT64_C(0x9dff9ee5ada2d05f), -2}, + {1, 0, 1, 53, 0, "null", "p", 54, 1, UINT64_C(0x1c2b7f460ba847cd), -2}, + {1, 0, 1, 54, 0, "null", "p", 55, 1, UINT64_C(0x8630e905cef1bd5f), -2}, + {1, 0, 1, 55, 7, "{}", "p", 56, 1, UINT64_C(0xf6d973dea4c4fecd), -1}, + {1, 0, 1, 56, 7, "{}", "p", 57, 1, UINT64_C(0x387e8851fabcb25f), -1}, + {1, 0, 1, 57, 7, "{\"k\":\"recovery\"}", "p", 73, 1, UINT64_C(0x95a65de0d1459ffa), 5}, + {1, 0, 1, 58, 7, "{\"k\":\"recovery\"}", "p", 74, 1, UINT64_C(0xa620195a9833c2b4), 5}, + {1, 0, 1, 59, 7, "{\"\":\"recovery\"}", "p", 71, 1, UINT64_C(0x454d188779b08d02), 5}, + {1, 0, 1, 60, 7, "{\"\":\"recovery\"}", "p", 75, 1, UINT64_C(0x22580a8396a3a0ac), 5}, + {1, 0, 1, 61, 7, "{\"\":\"recovery\"}", "p", 73, 1, UINT64_C(0x3acc0530cfa4dcec), 5}, + {1, 0, 1, 62, 7, "{\"\":\"recovery\"}", "p", 76, 1, UINT64_C(0xb9a8b3368d755ba4), 5}, + {1, 0, 1, 63, 7, "{\"k\":\"recovery\"}", "p", 75, 1, UINT64_C(0x91af3c44d0365042), 5}, + {1, 0, 1, 64, 7, "{\"k\":\"recovery\"}", "p", 76, 1, UINT64_C(0x41b64226cd9d271c), 5}, + {1, 0, 1, 65, 7, "{\"k\":\"\"}", "p", 75, 1, UINT64_C(0xbbddd48e5d45fa45), -1}, + {1, 0, 1, 66, 7, "{\"k\":\"\"}", "p", 76, 1, UINT64_C(0x402f245441340d5d), -1}, + {1, 0, 1, 67, 7, "{\"\":\"\"}", "p", 74, 1, UINT64_C(0x66aaf18c794fd2ff), 5}, + {1, 0, 1, 68, 7, "{\"\":\"\"}", "p", 76, 1, UINT64_C(0xbe140f0c1593d6c5), 5}, + {1, 0, 1, 69, 7, "{\"k\":null}", "p", 74, 1, UINT64_C(0x91322a4e151994d7), 0}, + {1, 0, 1, 70, 7, "{\"k\":null}", "p", 75, 1, UINT64_C(0x9e8ba6317656eabd), 0}, + {1, 0, 1, 71, 7, "{}", "p", 72, 1, UINT64_C(0xc739a650bd76c67f), -1}, + {1, 0, 1, 72, 7, "{\"k\":\"\"}", "p", 77, 1, UINT64_C(0xe6c18c88db742395), -1}, + {1, 0, 1, 73, 7, "{\"\":\"\"}", "p", 76, 1, UINT64_C(0xbe140f0c1593d6c5), 5}, + {1, 0, 1, 74, 7, "{\"\":\"\"}", "p", 77, 1, UINT64_C(0xe6c18c88db742395), 5}, + {1, 0, 1, 75, 7, "{\"k\":null}", "p", 76, 1, UINT64_C(0xbe361b0c15b0d18f), 0}, + {1, 0, 1, 76, 7, "{\"k\":null}", "p", 77, 1, UINT64_C(0xe6c18c88db742395), 0}, + {1, 0, 1, 77, 7, "{\"k\":\"\"}", "p", 78, 1, UINT64_C(0x8f05688ce6588214), 5}, + {1, 0, 1, 78, 7, "{\"k\":\"recovery\"}", "p", 78, 0, UINT64_C(0x8f05688ce6588214), 5}, + {1, 1, 0, 0, 4, "inf", "p", 2, 2, UINT64_C(0x08842007b530c95d), -2}, + {1, 1, 0, 1, 4, "inf", "p", 3, 2, UINT64_C(0xa94bb518e1e60558), -2}, + {1, 1, 0, 2, 4, "inf", "p", 4, 2, UINT64_C(0x925e3a47da742eb6), -2}, + {1, 1, 0, 3, 4, "inf", "p", 5, 2, UINT64_C(0x28fb3718324d62b8), -2}, + {1, 1, 0, 4, 4, "inf", "p", 7, 3, UINT64_C(0x5e96ce153c7cfbb8), -2}, + {1, 1, 0, 5, 4, "inf", "p", 8, 3, UINT64_C(0x76f63f179f84d58e), -2}, + {1, 1, 0, 6, 4, "inf", "p", 12, 6, UINT64_C(0xa616d1c6ca4b37b8), -2}, + {1, 1, 0, 7, 4, "inf", "p", 13, 6, UINT64_C(0x7b61fcc43cb7c366), -2}, + {1, 1, 0, 8, 4, "inf", "p", 14, 6, UINT64_C(0x55e9654cf5f40670), -2}, + {1, 1, 0, 9, 4, "inf", "p", 16, 7, UINT64_C(0x03166de64beed216), -2}, + {1, 1, 0, 10, 4, "inf", "p", 16, 6, UINT64_C(0x03166de64beed216), -2}, + {1, 1, 0, 11, 4, "inf", "p", 18, 7, UINT64_C(0x61960677c5c6ef08), -2}, + {1, 1, 0, 12, 4, "inf", "p", 18, 6, UINT64_C(0x61960677c5c6ef08), -2}, + {1, 1, 0, 13, 4, "inf", "p", 19, 6, UINT64_C(0x7fb54dffd01f18d6), -2}, + {1, 1, 0, 14, 4, "inf", "p", 20, 6, UINT64_C(0x3351fbcf95f4f238), -2}, + {1, 1, 0, 15, 4, "inf", "p", 21, 6, UINT64_C(0x1d49b0fdcdfd5028), -2}, + {1, 1, 0, 16, 4, "inf", "p", 21, 5, UINT64_C(0x1d49b0fdcdfd5028), -2}, + {1, 1, 0, 17, 4, "inf", "p", 22, 5, UINT64_C(0x95f8c66497bb53a0), -2}, + {1, 1, 0, 18, 4, "inf", "p", 22, 4, UINT64_C(0x95f8c66497bb53a0), -2}, + {1, 1, 0, 19, 4, "inf", "p", 23, 4, UINT64_C(0xd152956a474667e6), -2}, + {1, 1, 0, 20, 4, "inf", "p", 23, 3, UINT64_C(0xd152956a474667e6), -2}, + {1, 1, 0, 21, 4, "inf", "p", 26, 5, UINT64_C(0xf155ca552e66308e), -2}, + {1, 1, 0, 22, 4, "inf", "p", 26, 4, UINT64_C(0xf155ca552e66308e), -2}, + {1, 1, 0, 23, 4, "inf", "p", 27, 4, UINT64_C(0x3b227d6a79828cd6), -2}, + {1, 1, 0, 24, 4, "inf", "p", 27, 3, UINT64_C(0x3b227d6a79828cd6), -2}, + {1, 1, 0, 25, 4, "inf", "p", 28, 3, UINT64_C(0x73861aec2a4cce28), -2}, + {1, 1, 0, 26, 4, "inf", "p", 29, 3, UINT64_C(0x90c3c74bdb6352f9), -2}, + {1, 1, 0, 27, 4, "inf", "p", 29, 2, UINT64_C(0x90c3c74bdb6352f9), -2}, + {1, 1, 0, 28, 4, "inf", "p", 30, 2, UINT64_C(0x5fd285e5c9a097b5), -2}, + {1, 1, 0, 29, 4, "inf", "p", 31, 2, UINT64_C(0x71deb9759aa6efd1), -2}, + {1, 1, 0, 30, 4, "inf", "p", 35, 5, UINT64_C(0xcc66351878f7eaae), -2}, + {1, 1, 0, 31, 4, "inf", "p", 36, 5, UINT64_C(0x938d793a6f66fdfa), -2}, + {1, 1, 0, 32, 4, "inf", "p", 40, 8, UINT64_C(0x9ac679090babd038), -2}, + {1, 1, 0, 33, 4, "inf", "p", 41, 8, UINT64_C(0xb441ea5b2ee4ef6c), -2}, + {1, 1, 0, 34, 4, "inf", "p", 42, 8, UINT64_C(0xe25b516d74e1ce28), -2}, + {1, 1, 0, 35, 4, "inf", "p", 44, 9, UINT64_C(0x2adccef255d56a56), -2}, + {1, 1, 0, 36, 4, "inf", "p", 44, 8, UINT64_C(0x2adccef255d56a56), -2}, + {1, 1, 0, 37, 4, "inf", "p", 46, 9, UINT64_C(0x661c8ee64bff6094), -2}, + {1, 1, 0, 38, 4, "inf", "p", 46, 8, UINT64_C(0x661c8ee64bff6094), -2}, + {1, 1, 0, 39, 4, "inf", "p", 47, 8, UINT64_C(0xb77b424f5a5c3448), -2}, + {1, 1, 0, 40, 4, "inf", "p", 48, 8, UINT64_C(0xfb8a4df5be8fafe8), -2}, + {1, 1, 0, 41, 4, "inf", "p", 49, 8, UINT64_C(0xb303414a8c5a6cba), -2}, + {1, 1, 0, 42, 4, "inf", "p", 49, 7, UINT64_C(0xb303414a8c5a6cba), -2}, + {1, 1, 0, 43, 4, "inf", "p", 50, 7, UINT64_C(0x0c8984c4638db2a8), -2}, + {1, 1, 0, 44, 4, "inf", "p", 50, 6, UINT64_C(0x0c8984c4638db2a8), -2}, + {1, 1, 0, 45, 4, "inf", "p", 51, 6, UINT64_C(0xc499abf4c8d2d6f8), -2}, + {1, 1, 0, 46, 4, "inf", "p", 51, 5, UINT64_C(0xc499abf4c8d2d6f8), -2}, + {1, 1, 0, 47, 4, "inf", "p", 54, 7, UINT64_C(0xee5659e8ca9e200e), -2}, + {1, 1, 0, 48, 4, "inf", "p", 54, 6, UINT64_C(0xee5659e8ca9e200e), -2}, + {1, 1, 0, 49, 4, "inf", "p", 55, 6, UINT64_C(0x313624099079c388), -2}, + {1, 1, 0, 50, 4, "inf", "p", 55, 5, UINT64_C(0x313624099079c388), -2}, + {1, 1, 0, 51, 4, "inf", "p", 56, 5, UINT64_C(0x4eb737700cb0e2b8), -2}, + {1, 1, 0, 52, 4, "inf", "p", 57, 5, UINT64_C(0xf578edebf5d33e57), -2}, + {1, 1, 0, 53, 4, "inf", "p", 57, 4, UINT64_C(0xf578edebf5d33e57), -2}, + {1, 1, 0, 54, 4, "inf", "p", 58, 4, UINT64_C(0x106c88119e6508ba), -2}, + {1, 1, 0, 55, 4, "inf", "p{", 58, 3, UINT64_C(0x106c88119e6508ba), -2}, + {1, 1, 0, 56, 4, "inf", "p{", 58, 2, UINT64_C(0x106c88119e6508ba), -2}, + {1, 1, 0, 57, 4, "inf", "p{", 74, 17, UINT64_C(0xb580ca8d7702ee9e), -2}, + {1, 1, 0, 58, 4, "inf", "p{", 74, 16, UINT64_C(0xb580ca8d7702ee9e), -2}, + {1, 1, 0, 59, 4, "inf", "p{\"k\":\"recovery\"", 61, 2, UINT64_C(0x303529626abee9ac), -2}, + {1, 1, 0, 60, 4, "inf", "p{\"k\":\"recovery\"", 61, 1, UINT64_C(0x303529626abee9ac), -2}, + {1, 1, 0, 61, 4, "inf", "p{\"k\":\"recovery\"}", 61, 0, UINT64_C(0x303529626abee9ac), -2}, + {1, 1, 1, 0, 4, "inf", "p", 57, 1, UINT64_C(0x357a103f541adf8f), -2}, + {1, 1, 1, 1, 4, "inf", "pnull", 4, 1, UINT64_C(0x91aa0747d9db038a), -2}, + {1, 1, 1, 2, 4, "inf", "pnull", 5, 1, UINT64_C(0x2a4ba818336b39e0), -2}, + {1, 1, 1, 3, 4, "inf", "pnull", 6, 1, UINT64_C(0xf0414c1d797eadaa), -2}, + {1, 1, 1, 4, 4, "inf", "p{}", 15, 1, UINT64_C(0xb8a11f4b85cfdf68), -2}, + {1, 1, 1, 5, 4, "inf", "p{}", 16, 1, UINT64_C(0x515bb1e0a9377c8a), -2}, + {1, 1, 1, 6, 4, "inf", "p{\"k\":\"recovery\"}", 56, 1, UINT64_C(0x315f2eac37d67b5a), -2}, + {1, 1, 1, 7, 4, "inf", "p{\"k\":\"recovery\"}", 57, 1, UINT64_C(0xcbfa0862d979100c), -2}, + {1, 1, 1, 8, 4, "inf", "p{\"\":\"recovery\"}", 50, 1, UINT64_C(0xd905de7dd0c987e6), -2}, + {1, 1, 1, 9, 4, "inf", "p{\"\":\"recovery\"}", 54, 1, UINT64_C(0x88b0fc6aef4df998), -2}, + {1, 1, 1, 10, 4, "inf", "p{\"\":\"recovery\"}", 52, 1, UINT64_C(0x0ed068e5aec81d18), -2}, + {1, 1, 1, 11, 4, "inf", "p{\"\":\"recovery\"}", 55, 1, UINT64_C(0x9ae649625b45ca98), -2}, + {1, 1, 1, 12, 4, "inf", "p{\"k\":\"recovery\"}", 58, 1, UINT64_C(0x0625a9a88129bed2), -2}, + {1, 1, 1, 13, 4, "inf", "p{\"k\":\"recovery\"}", 59, 1, UINT64_C(0x303de3c047695c84), -2}, + {1, 1, 1, 14, 4, "inf", "p{\"k\":\"\"}", 54, 1, UINT64_C(0xb8015f1c36338c04), -2}, + {1, 1, 1, 15, 4, "inf", "p{\"k\":\"\"}", 55, 1, UINT64_C(0xdb7517e493e6a9b4), -2}, + {1, 1, 1, 16, 4, "inf", "p{\"\":\"\"}", 52, 1, UINT64_C(0x7da62e92d17ad572), -2}, + {1, 1, 1, 17, 4, "inf", "p{\"\":\"\"}", 54, 1, UINT64_C(0x8d200b5a3b7e1874), -2}, + {1, 1, 1, 18, 4, "inf", "p{\"k\":null}", 48, 1, UINT64_C(0x573ea7945c700032), -2}, + {1, 1, 1, 19, 4, "inf", "p{\"k\":null}", 49, 1, UINT64_C(0x27b1db6071fdac8c), -2}, + {1, 1, 1, 20, 4, "inf", "p{}", 31, 1, UINT64_C(0xce687ad15e20ed42), -2}, + {1, 1, 1, 21, 4, "inf", "p{\"k\":\"\"}", 56, 1, UINT64_C(0x3dd0ee7536859154), -2}, + {1, 1, 1, 22, 4, "inf", "p{\"\":\"\"}", 54, 1, UINT64_C(0x8d200b5a3b7e1874), -2}, + {1, 1, 1, 23, 4, "inf", "p{\"\":\"\"}", 55, 1, UINT64_C(0x39985a010413873c), -2}, + {1, 1, 1, 24, 4, "inf", "p{\"k\":null}", 50, 1, UINT64_C(0xe2169cee4d02037a), -2}, + {1, 1, 1, 25, 4, "inf", "p{\"k\":null}", 51, 1, UINT64_C(0x6668922033679bd4), -2}, + {1, 1, 1, 26, 4, "inf", "p{\"k\":\"\"}", 60, 1, UINT64_C(0x7a7b3b316939da83), -2}, + {1, 1, 1, 27, 4, "inf", "pnull", 30, 1, UINT64_C(0x5ffe94e5c9c5ec1d), -2}, + {1, 1, 1, 28, 4, "inf", "pnull", 31, 1, UINT64_C(0x735128759be1a9f9), -2}, + {1, 1, 1, 29, 4, "inf", "pnull", 32, 1, UINT64_C(0x2464e9d5c9a96f25), -2}, + {1, 1, 1, 30, 4, "inf", "p{}", 36, 1, UINT64_C(0x2959406bddec2341), -2}, + {1, 1, 1, 31, 4, "inf", "p{}", 37, 1, UINT64_C(0xff7ab32c63614925), -2}, + {1, 1, 1, 32, 4, "inf", "p{\"k\":\"recovery\"}", 56, 1, UINT64_C(0x1d97c31770f583e8), -2}, + {1, 1, 1, 33, 4, "inf", "p{\"k\":\"recovery\"}", 57, 1, UINT64_C(0xf80d82cd3044afcc), -2}, + {1, 1, 1, 34, 4, "inf", "p{\"\":\"recovery\"}", 54, 1, UINT64_C(0xda80aa339eb5c7f8), -2}, + {1, 1, 1, 35, 4, "inf", "p{\"\":\"recovery\"}", 58, 1, UINT64_C(0x625b8d667a7a4bda), -2}, + {1, 1, 1, 36, 4, "inf", "p{\"\":\"recovery\"}", 56, 1, UINT64_C(0xd1b802c73f7ec5a6), -2}, + {1, 1, 1, 37, 4, "inf", "p{\"\":\"recovery\"}", 59, 1, UINT64_C(0x4d6683f6c036dbd8), -2}, + {1, 1, 1, 38, 4, "inf", "p{\"k\":\"recovery\"}", 58, 1, UINT64_C(0xb64549e8f90110c8), -2}, + {1, 1, 1, 39, 4, "inf", "p{\"k\":\"recovery\"}", 59, 1, UINT64_C(0x5e11fc32d5a26abc), -2}, + {1, 1, 1, 40, 4, "inf", "p{\"k\":\"\"}", 58, 1, UINT64_C(0x9c11d723b8ba768f), -2}, + {1, 1, 1, 41, 4, "inf", "p{\"k\":\"\"}", 59, 1, UINT64_C(0xa9a483305377d8b9), -2}, + {1, 1, 1, 42, 4, "inf", "p{\"\":\"\"}", 57, 1, UINT64_C(0x58e686a85e22d0bf), -2}, + {1, 1, 1, 43, 4, "inf", "p{\"\":\"\"}", 59, 1, UINT64_C(0xd8a3bfa99189d665), -2}, + {1, 1, 1, 44, 4, "inf", "p{\"k\":null}", 57, 1, UINT64_C(0xb7cd827aae5b9de3), -2}, + {1, 1, 1, 45, 4, "inf", "p{\"k\":null}", 58, 1, UINT64_C(0xc901aa4562715073), -2}, + {1, 1, 1, 46, 4, "inf", "p{}", 52, 1, UINT64_C(0xb9b076a5e7fcb303), -2}, + {1, 1, 1, 47, 4, "inf", "p{\"k\":\"\"}", 60, 1, UINT64_C(0x7a7b3b316939da83), -2}, + {1, 1, 1, 48, 4, "inf", "p{\"\":\"\"}", 59, 1, UINT64_C(0xd8a3bfa99189d665), -2}, + {1, 1, 1, 49, 4, "inf", "p{\"\":\"\"}", 60, 1, UINT64_C(0x7a7b3b316939da83), -2}, + {1, 1, 1, 50, 4, "inf", "p{\"k\":null}", 59, 1, UINT64_C(0x8242d1842bb69e53), -2}, + {1, 1, 1, 51, 4, "inf", "p{\"k\":null}", 60, 1, UINT64_C(0x7a7b3b316939da83), -2}, + {1, 1, 1, 52, 4, "inf", "p{\"k\":\"\"}", 61, 1, UINT64_C(0x303529626abee9ac), -2}, + {1, 1, 1, 53, 4, "inf", "p\"k\":\"recovery\"}", 62, 1, UINT64_C(0x621da11fedc3cd77), -2}, + {1, 1, 1, 54, 4, "inf", "p\"k\":\"recovery\"}", 63, 1, UINT64_C(0x54123ca5b4a95d58), -2}, + {1, 1, 1, 55, 4, "inf", "p{}", 58, 1, UINT64_C(0x106c88119e6508ba), -2}, + {1, 1, 1, 56, 4, "inf", "p{}", 58, 1, UINT64_C(0x106c88119e6508ba), -2}, + {1, 1, 1, 57, 4, "inf", "p{\"k\":\"recovery\"}", 61, 1, UINT64_C(0x303529626abee9ac), -2}, + {1, 1, 1, 58, 4, "inf", "p{k\":\"recovery\"}", 62, 1, UINT64_C(0x52c21e3b3173e466), -2}, + {1, 1, 1, 59, 4, "inf", "p{\"k\":\"recovery\"}", 61, 1, UINT64_C(0x303529626abee9ac), -2}, + {1, 1, 1, 60, 4, "inf", "p{\"k\":\"recovery\"", 61, 1, UINT64_C(0x303529626abee9ac), -2}, + {1, 1, 1, 61, 4, "inf", "p{\"k\":\"recovery\"}", 61, 0, UINT64_C(0x303529626abee9ac), -2}, + {2, 0, 0, 0, 0, "null", "p", 1, 1, UINT64_C(0xaf63c54c8601c577), -2}, + {2, 0, 0, 1, 0, "null", "p", 2, 1, UINT64_C(0x084d9807b50252d5), -2}, + {2, 0, 0, 2, 0, "null", "p", 5, 3, UINT64_C(0xd5e2502297cbec7f), -2}, + {2, 0, 0, 3, 0, "null", "p", 5, 2, UINT64_C(0xd5e2502297cbec7f), -2}, + {2, 0, 0, 4, 0, "null", "p", 6, 2, UINT64_C(0x3c3901c7f0249a5d), -2}, + {2, 0, 0, 5, 0, "null", "p", 10, 5, UINT64_C(0x62f0e976a47f1e5d), -2}, + {2, 0, 0, 6, 0, "null", "p", 11, 5, UINT64_C(0x0ff5e378802151e7), -2}, + {2, 0, 0, 7, 0, "null", "p", 12, 5, UINT64_C(0x5b010d0357a36655), -2}, + {2, 0, 0, 8, 0, "null", "p", 12, 4, UINT64_C(0x5b010d0357a36655), -2}, + {2, 0, 0, 9, 0, "null", "p", 13, 4, UINT64_C(0x01a1f1adc408aba7), -2}, + {2, 0, 0, 10, 0, "null", "p", 14, 4, UINT64_C(0xe8f4f20b1cc04b85), -2}, + {2, 0, 0, 11, 0, "null", "p", 16, 5, UINT64_C(0x3713a0a2974a381d), -2}, + {2, 0, 0, 12, 0, "null", "p", 16, 4, UINT64_C(0x3713a0a2974a381d), -2}, + {2, 0, 0, 13, 0, "null", "p", 17, 4, UINT64_C(0x8682ef73727aab77), -2}, + {2, 0, 0, 14, 0, "null", "p", 18, 4, UINT64_C(0x65ba56ded1a9f0b5), -2}, + {2, 0, 0, 15, 0, "null", "p", 19, 4, UINT64_C(0xe1a0f051903ffaff), -2}, + {2, 0, 0, 16, 0, "null", "p", 21, 5, UINT64_C(0x26dd6278cb42aa8f), -2}, + {2, 0, 0, 17, 0, "null", "p", 22, 5, UINT64_C(0xabd24466a76de815), -2}, + {2, 0, 0, 18, 0, "null", "p", 22, 4, UINT64_C(0xabd24466a76de815), -2}, + {2, 0, 0, 19, 0, "null", "p", 23, 4, UINT64_C(0x1fa4cf6e592324e7), -2}, + {2, 0, 0, 20, 0, "null", "p", 24, 4, UINT64_C(0x8db9defb4ed6e27d), -2}, + {2, 0, 0, 21, 0, "null", "p", 25, 4, UINT64_C(0x4e4b45fec78b6fce), -2}, + {2, 0, 0, 22, 0, "null", "p", 25, 3, UINT64_C(0x4e4b45fec78b6fce), -2}, + {2, 0, 0, 23, 0, "null", "p", 26, 3, UINT64_C(0x1f9de5ed6029a77a), -2}, + {2, 0, 0, 24, 0, "null", "p", 27, 3, UINT64_C(0xc749bc5b78e58b26), -2}, + {2, 0, 0, 25, 0, "null", "p", 27, 2, UINT64_C(0xc749bc5b78e58b26), -2}, + {2, 0, 0, 26, 0, "null", "p", 28, 2, UINT64_C(0x87d63d6e6e0b7f2a), -2}, + {2, 0, 0, 27, 0, "null", "p", 29, 2, UINT64_C(0xdc01a4a4fd15a38e), -2}, + {2, 0, 0, 28, 0, "null", "p", 30, 2, UINT64_C(0xecdb0a5a0c2141d2), -2}, + {2, 0, 0, 29, 0, "null", "p", 31, 2, UINT64_C(0x9922d1029c3d7e66), -2}, + {2, 0, 0, 30, 0, "null", "p", 32, 2, UINT64_C(0x74edd56f7d93044a), -2}, + {2, 0, 0, 31, 0, "null", "p", 33, 2, UINT64_C(0x43778e726115a32e), -2}, + {2, 0, 0, 32, 0, "null", "p", 34, 2, UINT64_C(0xbb10725af8d968f2), -2}, + {2, 0, 0, 33, 0, "null", "p", 37, 4, UINT64_C(0x1bc79002c244ddb0), -2}, + {2, 0, 0, 34, 0, "null", "p", 39, 5, UINT64_C(0xe68750e3d28b26fe), -2}, + {2, 0, 0, 35, 0, "null", "p", 39, 4, UINT64_C(0xe68750e3d28b26fe), -2}, + {2, 0, 0, 36, 0, "null", "p", 41, 5, UINT64_C(0xa6cd9e55cb763a84), -2}, + {2, 0, 0, 37, 0, "null", "p", 41, 4, UINT64_C(0xa6cd9e55cb763a84), -2}, + {2, 0, 0, 38, 0, "null", "p", 42, 4, UINT64_C(0xeeec001e3cf3acae), -2}, + {2, 0, 0, 39, 0, "null", "p", 42, 3, UINT64_C(0xeeec001e3cf3acae), -2}, + {2, 0, 0, 40, 0, "null", "p", 43, 3, UINT64_C(0x3845eb624cdaabc4), -2}, + {2, 0, 0, 41, 0, "null", "p", 43, 2, UINT64_C(0x3845eb624cdaabc4), -2}, + {2, 0, 0, 42, 0, "null", "p", 45, 3, UINT64_C(0xc95774996f552f82), -2}, + {2, 0, 0, 43, 0, "null", "p", 45, 2, UINT64_C(0xc95774996f552f82), -2}, + {2, 0, 0, 44, 0, "null", "p", 46, 2, UINT64_C(0x74df0eb82dd11a74), -2}, + {2, 0, 0, 45, 0, "null", "p", 46, 1, UINT64_C(0x74df0eb82dd11a74), -2}, + {2, 0, 0, 46, 0, "null", "p", 47, 1, UINT64_C(0x682056f5da4fbcbc), -2}, + {2, 0, 0, 47, 0, "null", "p", 50, 3, UINT64_C(0x1ab9bdb9424d66cc), -2}, + {2, 0, 0, 48, 0, "null", "p", 50, 2, UINT64_C(0x1ab9bdb9424d66cc), -2}, + {2, 0, 0, 49, 0, "null", "p", 51, 2, UINT64_C(0xb7c285cbaa276b34), -2}, + {2, 0, 0, 50, 0, "null", "p", 55, 5, UINT64_C(0x9424298cac093d34), -2}, + {2, 0, 0, 51, 0, "null", "p", 56, 5, UINT64_C(0x347fcf61ec77d4fc), -2}, + {2, 0, 0, 52, 0, "null", "p", 57, 5, UINT64_C(0xc8c0ee9ba81adea4), -2}, + {2, 0, 0, 53, 0, "null", "p", 57, 4, UINT64_C(0xc8c0ee9ba81adea4), -2}, + {2, 0, 0, 54, 0, "null", "p", 58, 4, UINT64_C(0x6a12467e2f46cd44), -2}, + {2, 0, 0, 55, 0, "null", "p", 59, 4, UINT64_C(0x675a7db8dfd2380c), -2}, + {2, 0, 0, 56, 0, "null", "p", 61, 5, UINT64_C(0xf358f2078632f01c), -2}, + {2, 0, 0, 57, 0, "null", "p", 61, 4, UINT64_C(0xf358f2078632f01c), -2}, + {2, 0, 0, 58, 0, "null", "p", 62, 4, UINT64_C(0xf7023016a7bffac4), -2}, + {2, 0, 0, 59, 0, "null", "p", 63, 4, UINT64_C(0x5c31ebcd95ba888c), -2}, + {2, 0, 0, 60, 0, "null", "p", 64, 4, UINT64_C(0xbfc0288355164f74), -2}, + {2, 0, 0, 61, 0, "null", "p", 66, 5, UINT64_C(0x3c1aaba80763f04c), -2}, + {2, 0, 0, 62, 0, "null", "p", 67, 5, UINT64_C(0x70ea0a0f3208cabc), -2}, + {2, 0, 0, 63, 0, "null", "p", 67, 4, UINT64_C(0x70ea0a0f3208cabc), -2}, + {2, 0, 0, 64, 0, "null", "p", 68, 4, UINT64_C(0x8be363d140998bdc), -2}, + {2, 0, 0, 65, 0, "null", "p", 69, 4, UINT64_C(0x1cce49169e498e2c), -2}, + {2, 0, 0, 66, 0, "null", "p", 70, 4, UINT64_C(0x963b4977257eeb2d), -2}, + {2, 0, 0, 67, 0, "null", "p", 70, 3, UINT64_C(0x963b4977257eeb2d), -2}, + {2, 0, 0, 68, 0, "null", "p", 71, 3, UINT64_C(0x50c8aa75059d1427), -2}, + {2, 0, 0, 69, 0, "null", "p", 72, 3, UINT64_C(0x727e6bd728c0c50d), -2}, + {2, 0, 0, 70, 0, "null", "p", 72, 2, UINT64_C(0x727e6bd728c0c50d), -2}, + {2, 0, 0, 71, 0, "null", "p", 73, 2, UINT64_C(0x4d96439a3f8ec77f), -2}, + {2, 0, 0, 72, 0, "null", "p", 74, 2, UINT64_C(0x65a4561a001088cd), -2}, + {2, 0, 0, 73, 0, "null", "p", 75, 2, UINT64_C(0xc580d32e1b03375f), -2}, + {2, 0, 0, 74, 0, "null", "p", 76, 2, UINT64_C(0x9cccae57e631d82d), -2}, + {2, 0, 0, 75, 0, "null", "p", 77, 2, UINT64_C(0xa2eaa45c27c7763f), -2}, + {2, 0, 0, 76, 0, "null", "p", 78, 2, UINT64_C(0x9cb37f979861790d), -2}, + {2, 0, 0, 77, 0, "null", "p", 79, 2, UINT64_C(0xa6e79397edfd089f), -2}, + {2, 0, 0, 78, 0, "null", "p", 82, 4, UINT64_C(0xf9119f97a351f67f), -2}, + {2, 0, 0, 79, 0, "null", "p", 84, 5, UINT64_C(0xa6ad7c44581adb95), -2}, + {2, 0, 0, 80, 0, "null", "p", 84, 4, UINT64_C(0xa6ad7c44581adb95), -2}, + {2, 0, 0, 81, 0, "null", "p", 86, 5, UINT64_C(0x7e1c2c7a609d2a27), -2}, + {2, 0, 0, 82, 0, "null", "p", 86, 4, UINT64_C(0x7e1c2c7a609d2a27), -2}, + {2, 0, 0, 83, 0, "null", "p", 87, 4, UINT64_C(0x700c2bc962c0dd37), -2}, + {2, 0, 0, 84, 0, "null", "p", 87, 3, UINT64_C(0x700c2bc962c0dd37), -2}, + {2, 0, 0, 85, 0, "null", "p", 88, 3, UINT64_C(0x997f5834c283b41f), -2}, + {2, 0, 0, 86, 0, "null", "p", 88, 2, UINT64_C(0x997f5834c283b41f), -2}, + {2, 0, 0, 87, 0, "null", "p", 90, 3, UINT64_C(0x1d73fdf588d7ef35), -2}, + {2, 0, 0, 88, 0, "null", "p", 90, 2, UINT64_C(0x1d73fdf588d7ef35), -2}, + {2, 0, 0, 89, 0, "null", "p", 91, 2, UINT64_C(0xe41c293786fcd79d), -2}, + {2, 0, 0, 90, 0, "null", "p", 91, 1, UINT64_C(0xe41c293786fcd79d), -2}, + {2, 0, 0, 91, 0, "null", "p", 92, 1, UINT64_C(0x98b1c65a5fa29627), -2}, + {2, 0, 0, 92, 6, "[]", "p", 94, 2, UINT64_C(0x2e21f08ba84ea50f), -2}, + {2, 0, 0, 93, 6, "[]", "p", 94, 1, UINT64_C(0x2e21f08ba84ea50f), -2}, + {2, 0, 0, 94, 6, "[]", "p", 95, 1, UINT64_C(0xb250fc4efda2ca0d), -2}, + {2, 0, 0, 95, 6, "[]", "p", 99, 4, UINT64_C(0x4f89b9b9cb02910d), -2}, + {2, 0, 0, 96, 6, "[]", "p", 100, 4, UINT64_C(0xe36e6c3931047cd7), -2}, + {2, 0, 0, 97, 6, "[]", "p", 101, 4, UINT64_C(0xd458342f0f8181d5), -2}, + {2, 0, 0, 98, 6, "[]", "p", 101, 3, UINT64_C(0xd458342f0f8181d5), -2}, + {2, 0, 0, 99, 6, "[null]", "p", 102, 3, UINT64_C(0x53b420f7595503f7), -2}, + {2, 0, 0, 100, 6, "[null]", "p", 103, 3, UINT64_C(0x3581634c071e8725), -2}, + {2, 0, 0, 101, 6, "[[]]", "p", 105, 4, UINT64_C(0x5da1acf27d62e94d), -2}, + {2, 0, 0, 102, 6, "[[]]", "p", 105, 3, UINT64_C(0x5da1acf27d62e94d), -2}, + {2, 0, 0, 103, 6, "[[]]", "p", 106, 3, UINT64_C(0x219ef50a4a5f0987), -2}, + {2, 0, 0, 104, 6, "[[]]", "p", 107, 3, UINT64_C(0x5058ef7cd5824115), -2}, + {2, 0, 0, 105, 6, "[[]]", "p", 108, 3, UINT64_C(0x24681e1db9aba8ff), -2}, + {2, 0, 0, 106, 6, "[[]]", "p", 110, 4, UINT64_C(0xc3d3dbbd453ec2ff), -2}, + {2, 0, 0, 107, 6, "[[]]", "p", 111, 4, UINT64_C(0xa52f2ae95e6c62a5), -2}, + {2, 0, 0, 108, 6, "[[]]", "p", 111, 3, UINT64_C(0xa52f2ae95e6c62a5), -2}, + {2, 0, 0, 109, 6, "[[null]]", "p", 112, 3, UINT64_C(0x1c470f8b72cda0e7), -2}, + {2, 0, 0, 110, 6, "[[null]]", "p", 113, 3, UINT64_C(0x4f0a40f3c446b1ad), -2}, + {2, 0, 0, 111, 6, "[[\"\"]]", "p", 114, 3, UINT64_C(0x8c8fd0367f4f1ec4), -2}, + {2, 0, 0, 112, 6, "[[\"recovery\"]]", "p", 114, 2, UINT64_C(0x8c8fd0367f4f1ec4), -2}, + {2, 0, 0, 113, 6, "[[\"recovery\"]]", "p", 115, 2, UINT64_C(0x27b3c09a539f280c), -2}, + {2, 0, 0, 114, 6, "[[\"recovery\"]]", "p", 116, 2, UINT64_C(0x1510723c16fda92c), -2}, + {2, 0, 0, 115, 6, "[[\"recovery\"]]", "p", 116, 1, UINT64_C(0x1510723c16fda92c), -2}, + {2, 0, 0, 116, 6, "[[\"recovery\"],null]", "p", 117, 1, UINT64_C(0xc89b401b1106682c), -2}, + {2, 0, 0, 117, 6, "[[\"recovery\"],null]", "p", 118, 1, UINT64_C(0xe635f9fdede2cc64), -2}, + {2, 0, 0, 118, 6, "[[\"recovery\"],null]", "p", 119, 1, UINT64_C(0x1083d67b3860b864), -2}, + {2, 0, 0, 119, 6, "[[\"recovery\"],{}]", "p", 120, 1, UINT64_C(0x70bdb760cc591b8c), -2}, + {2, 0, 0, 120, 6, "[[\"recovery\"],{}]", "p", 121, 1, UINT64_C(0xeb7a7d7b3b6a646c), -2}, + {2, 0, 0, 121, 6, "[[\"recovery\"],{}]", "p", 122, 1, UINT64_C(0x8b878465f5c86d24), -2}, + {2, 0, 0, 122, 6, "[[\"recovery\"],{}]", "p", 123, 1, UINT64_C(0xdfb34540a391b824), -2}, + {2, 0, 0, 123, 6, "[[\"recovery\"],{}]", "p", 126, 3, UINT64_C(0x4e7f4bd6054c0f1e), -2}, + {2, 0, 0, 124, 6, "[[\"recovery\"],{}]", "p", 128, 4, UINT64_C(0xd0b0968fe04c8144), -2}, + {2, 0, 0, 125, 6, "[[\"recovery\"],{}]", "p", 128, 3, UINT64_C(0xd0b0968fe04c8144), -2}, + {2, 0, 0, 126, 6, "[[\"recovery\"],{}]", "p", 130, 4, UINT64_C(0x0f522f8844e15982), -2}, + {2, 0, 0, 127, 6, "[[\"recovery\"],{}]", "p", 130, 3, UINT64_C(0x0f522f8844e15982), -2}, + {2, 0, 0, 128, 6, "[[\"recovery\"],{}]", "p", 131, 3, UINT64_C(0x1cb2c08d27459c90), -2}, + {2, 0, 0, 129, 6, "[[\"recovery\"],{}]", "p", 131, 2, UINT64_C(0x1cb2c08d27459c90), -2}, + {2, 0, 0, 130, 6, "[[\"recovery\"],{}]", "p", 132, 2, UINT64_C(0x09e813d9bbc19cb2), -2}, + {2, 0, 0, 131, 6, "[[\"recovery\"],{}]", "p", 132, 1, UINT64_C(0x09e813d9bbc19cb2), -2}, + {2, 0, 0, 132, 6, "[[\"recovery\"],{\"\":0}]", "p", 134, 2, UINT64_C(0x81f6cedef9521a98), -2}, + {2, 0, 0, 133, 6, "[[\"recovery\"],{\"\":0}]", "p", 134, 1, UINT64_C(0x81f6cedef9521a98), -2}, + {2, 0, 0, 134, 6, "[[\"recovery\"],{\"\":0}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 0, 135, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 0, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 0, 0, "null", "p", 1, 1, UINT64_C(0xaf63c54c8601c577), -2}, + {2, 0, 1, 1, 0, "null", "p", 2, 1, UINT64_C(0x084d9807b50252d5), -2}, + {2, 0, 1, 2, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 3, 6, "[]", "p", 14, 1, UINT64_C(0xe61a5f2be2380035), -2}, + {2, 0, 1, 4, 6, "[]", "p", 15, 1, UINT64_C(0x3444f23f5be2600f), -2}, + {2, 0, 1, 5, 6, "[[\"recovery\"],{\"k\":1}]", "p", 131, 1, UINT64_C(0x7707bc21130f5bae), -2}, + {2, 0, 1, 6, 6, "[[\"recovery\"],{\"k\":1}]", "p", 132, 1, UINT64_C(0x2488217525922a20), -2}, + {2, 0, 1, 7, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 8, 6, "[{\"k\":1}]", "p", 88, 1, UINT64_C(0x962fddd74d36c07f), -2}, + {2, 0, 1, 9, 6, "[null,{\"k\":1}]", "p", 91, 1, UINT64_C(0x8c9cf7e8fd16f66d), -2}, + {2, 0, 1, 10, 6, "[null,{\"k\":1}]", "p", 92, 1, UINT64_C(0xb9bb8920405251af), -2}, + {2, 0, 1, 11, 6, "[[],{\"k\":1}]", "p", 115, 1, UINT64_C(0xe6edb009277e435d), -2}, + {2, 0, 1, 12, 6, "[[],{\"k\":1}]", "p", 114, 1, UINT64_C(0xe0f78966f158d3d7), -2}, + {2, 0, 1, 13, 6, "[[],{\"k\":1}]", "p", 115, 1, UINT64_C(0xe6edb009277e435d), -2}, + {2, 0, 1, 14, 6, "[[],{\"k\":1}]", "p", 116, 1, UINT64_C(0x0e4bd4713cfa929f), -2}, + {2, 0, 1, 15, 6, "[[],{\"k\":1}]", "p", 117, 1, UINT64_C(0x7499037f3f29dab5), -2}, + {2, 0, 1, 16, 6, "[[\"recovery\"],{\"k\":1}]", "p", 134, 1, UINT64_C(0x047f47a3f3e985c0), -2}, + {2, 0, 1, 17, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 18, 6, "[[],{\"k\":1}]", "p", 120, 1, UINT64_C(0xd36e3a8f4ece6abf), -2}, + {2, 0, 1, 19, 6, "[[null],{\"k\":1}]", "p", 123, 1, UINT64_C(0x1162f4e2b7c0964d), -2}, + {2, 0, 1, 20, 6, "[[null],{\"k\":1}]", "p", 124, 1, UINT64_C(0x8200a888509879f7), -2}, + {2, 0, 1, 21, 6, "[[\"\"],{\"k\":1}]", "p", 133, 1, UINT64_C(0xab82a2db1293d4a4), -2}, + {2, 0, 1, 22, 6, "[[\"recovery\"],{\"k\":1}]", "p", 131, 1, UINT64_C(0x29cf3c457348784e), -2}, + {2, 0, 1, 23, 6, "[[\"recovery\"],{\"k\":1}]", "p", 132, 1, UINT64_C(0x9879fdbf86ab412a), -2}, + {2, 0, 1, 24, 6, "[[\"recovery\"],{\"k\":1}]", "p", 133, 1, UINT64_C(0x811dd14cbc76d616), -2}, + {2, 0, 1, 25, 6, "[[\"recovery\"]]", "p", 70, 1, UINT64_C(0xee001ae21f987432), -2}, + {2, 0, 1, 26, 6, "[[\"recovery\"],null]", "p", 73, 1, UINT64_C(0x2a367fa959c067e0), -2}, + {2, 0, 1, 27, 6, "[[\"recovery\"],null]", "p", 74, 1, UINT64_C(0xc6b149f98ccf3efc), -2}, + {2, 0, 1, 28, 6, "[[\"recovery\"],null]", "p", 75, 1, UINT64_C(0x318f98678c3190e8), -2}, + {2, 0, 1, 29, 6, "[[\"recovery\"],{}]", "p", 96, 1, UINT64_C(0x5a0e500c3d11d1cc), -2}, + {2, 0, 1, 30, 6, "[[\"recovery\"],{}]", "p", 97, 1, UINT64_C(0xdfd95d45102e4158), -2}, + {2, 0, 1, 31, 6, "[[\"recovery\"],{}]", "p", 98, 1, UINT64_C(0x1e923d9e3135ee54), -2}, + {2, 0, 1, 32, 6, "[[\"recovery\"],{}]", "p", 99, 1, UINT64_C(0x25637f963a6e0c80), -2}, + {2, 0, 1, 33, 6, "[[\"recovery\"],{\"\":1}]", "p", 120, 1, UINT64_C(0xaf2bbc597666e8b6), -2}, + {2, 0, 1, 34, 6, "[[\"recovery\"],{\"\":1}]", "p", 124, 1, UINT64_C(0x286966ae72068854), -2}, + {2, 0, 1, 35, 6, "[[\"recovery\"],{\"\":1}]", "p", 122, 1, UINT64_C(0x70bdfbd9cc778848), -2}, + {2, 0, 1, 36, 6, "[[\"recovery\"],{\"\":1}]", "p", 125, 1, UINT64_C(0x7f9ec3b496f20e76), -2}, + {2, 0, 1, 37, 6, "[[\"recovery\"],{\"k\":0}]", "p", 127, 1, UINT64_C(0xe435cc31d865c04c), -2}, + {2, 0, 1, 38, 6, "[[\"recovery\"],{\"k\":0}]", "p", 128, 1, UINT64_C(0x0bc93ca23c973146), -2}, + {2, 0, 1, 39, 6, "[[\"recovery\"],{\"\":0}]", "p", 124, 1, UINT64_C(0x5b0438a962c028ee), -2}, + {2, 0, 1, 40, 6, "[[\"recovery\"],{\"\":0}]", "p", 126, 1, UINT64_C(0x749e33fe09846f14), -2}, + {2, 0, 1, 41, 6, "[[\"recovery\"],{}]", "p", 108, 1, UINT64_C(0x1d1930c0b39ff2d6), -2}, + {2, 0, 1, 42, 6, "[[\"recovery\"],{\"k\":0}]", "p", 129, 1, UINT64_C(0x39b811df8065a440), -2}, + {2, 0, 1, 43, 6, "[[\"recovery\"],{\"\":0}]", "p", 126, 1, UINT64_C(0x749e33fe09846f14), -2}, + {2, 0, 1, 44, 6, "[[\"recovery\"],{\"\":0}]", "p", 127, 1, UINT64_C(0xa9ae8c0106305f02), -2}, + {2, 0, 1, 45, 0, "null", "p", 46, 1, UINT64_C(0x74df0eb82dd11a74), -2}, + {2, 0, 1, 46, 0, "null", "p", 47, 1, UINT64_C(0x682056f5da4fbcbc), -2}, + {2, 0, 1, 47, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 48, 6, "[]", "p", 54, 1, UINT64_C(0xd866a499e8246f4c), -2}, + {2, 0, 1, 49, 6, "[]", "p", 55, 1, UINT64_C(0x3843fa114a3e82b4), -2}, + {2, 0, 1, 50, 6, "[[\"recovery\"],{\"k\":1}]", "p", 131, 1, UINT64_C(0xf8f5f9bbf251a3ae), -2}, + {2, 0, 1, 51, 6, "[[\"recovery\"],{\"k\":1}]", "p", 132, 1, UINT64_C(0xfbbd747ae65c5fd6), -2}, + {2, 0, 1, 52, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 53, 6, "[{\"k\":1}]", "p", 105, 1, UINT64_C(0xdf45e48aebd07bac), -2}, + {2, 0, 1, 54, 6, "[null,{\"k\":1}]", "p", 107, 1, UINT64_C(0xf5386814407a5c18), -2}, + {2, 0, 1, 55, 6, "[null,{\"k\":1}]", "p", 108, 1, UINT64_C(0x75ecae360ec86ae0), -2}, + {2, 0, 1, 56, 6, "[[],{\"k\":1}]", "p", 121, 1, UINT64_C(0x9ecbb9e5b73e2b08), -2}, + {2, 0, 1, 57, 6, "[[],{\"k\":1}]", "p", 120, 1, UINT64_C(0x6352de65e7a42820), -2}, + {2, 0, 1, 58, 6, "[[],{\"k\":1}]", "p", 121, 1, UINT64_C(0x9ecbb9e5b73e2b08), -2}, + {2, 0, 1, 59, 6, "[[],{\"k\":1}]", "p", 122, 1, UINT64_C(0xf997bcd7489c6570), -2}, + {2, 0, 1, 60, 6, "[[],{\"k\":1}]", "p", 123, 1, UINT64_C(0xa5cb6694f3def5d8), -2}, + {2, 0, 1, 61, 6, "[[\"recovery\"],{\"k\":1}]", "p", 134, 1, UINT64_C(0x1cff01046f9d81fe), -2}, + {2, 0, 1, 62, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 63, 6, "[[],{\"k\":1}]", "p", 126, 1, UINT64_C(0x6e46235ae155ad00), -2}, + {2, 0, 1, 64, 6, "[[null],{\"k\":1}]", "p", 128, 1, UINT64_C(0x4d9e4c0942fb1314), -2}, + {2, 0, 1, 65, 6, "[[null],{\"k\":1}]", "p", 129, 1, UINT64_C(0x7db796f6516c6044), -2}, + {2, 0, 1, 66, 6, "[[\"\"],{\"k\":1}]", "p", 134, 1, UINT64_C(0x44b6e6e072fd4815), -2}, + {2, 0, 1, 67, 6, "[[\"recovery\"],{\"k\":1}]", "p", 131, 1, UINT64_C(0x5bba7eb92882c42e), -2}, + {2, 0, 1, 68, 6, "[[\"recovery\"],{\"k\":1}]", "p", 132, 1, UINT64_C(0xb32dfae5f269e0bc), -2}, + {2, 0, 1, 69, 6, "[[\"recovery\"],{\"k\":1}]", "p", 133, 1, UINT64_C(0x288b68f5f56c0c8e), -2}, + {2, 0, 1, 70, 6, "[[\"recovery\"]]", "p", 93, 1, UINT64_C(0x2c089b9238e3038c), -2}, + {2, 0, 1, 71, 6, "[[\"recovery\"],null]", "p", 95, 1, UINT64_C(0x9c59076cabc41196), -2}, + {2, 0, 1, 72, 6, "[[\"recovery\"],null]", "p", 96, 1, UINT64_C(0x945067c4b0b7de34), -2}, + {2, 0, 1, 73, 6, "[[\"recovery\"],null]", "p", 97, 1, UINT64_C(0xf3cc42e2cf1b7eb6), -2}, + {2, 0, 1, 74, 6, "[[\"recovery\"],{}]", "p", 108, 1, UINT64_C(0x75541d23077a1f54), -2}, + {2, 0, 1, 75, 6, "[[\"recovery\"],{}]", "p", 109, 1, UINT64_C(0x1597eae911857f46), -2}, + {2, 0, 1, 76, 6, "[[\"recovery\"],{}]", "p", 110, 1, UINT64_C(0xc524da67a13a4e74), -2}, + {2, 0, 1, 77, 6, "[[\"recovery\"],{}]", "p", 111, 1, UINT64_C(0x6981ea88761ff5e6), -2}, + {2, 0, 1, 78, 6, "[[\"recovery\"],{\"\":1}]", "p", 124, 1, UINT64_C(0xc3d0929287f61588), -2}, + {2, 0, 1, 79, 6, "[[\"recovery\"],{\"\":1}]", "p", 128, 1, UINT64_C(0x8669172f2a49755a), -2}, + {2, 0, 1, 80, 6, "[[\"recovery\"],{\"\":1}]", "p", 126, 1, UINT64_C(0x860088c0a1b6b29a), -2}, + {2, 0, 1, 81, 6, "[[\"recovery\"],{\"\":1}]", "p", 129, 1, UINT64_C(0x4a2488df4a66032a), -2}, + {2, 0, 1, 82, 6, "[[\"recovery\"],{\"k\":0}]", "p", 130, 1, UINT64_C(0xaa382e1de6f59b70), -2}, + {2, 0, 1, 83, 6, "[[\"recovery\"],{\"k\":0}]", "p", 131, 1, UINT64_C(0x423f69eae99f2980), -2}, + {2, 0, 1, 84, 6, "[[\"recovery\"],{\"\":0}]", "p", 128, 1, UINT64_C(0xb99b0f902499c508), -2}, + {2, 0, 1, 85, 6, "[[\"recovery\"],{\"\":0}]", "p", 130, 1, UINT64_C(0xebd8dfdbead7bca2), -2}, + {2, 0, 1, 86, 6, "[[\"recovery\"],{}]", "p", 120, 1, UINT64_C(0xce91b9a105dc6166), -2}, + {2, 0, 1, 87, 6, "[[\"recovery\"],{\"k\":0}]", "p", 132, 1, UINT64_C(0x404963a2c4a10eb0), -2}, + {2, 0, 1, 88, 6, "[[\"recovery\"],{\"\":0}]", "p", 130, 1, UINT64_C(0xebd8dfdbead7bca2), -2}, + {2, 0, 1, 89, 6, "[[\"recovery\"],{\"\":0}]", "p", 131, 1, UINT64_C(0x3e79f273ddcb978a), -2}, + {2, 0, 1, 90, 0, "null", "p", 91, 1, UINT64_C(0xe41c293786fcd79d), -2}, + {2, 0, 1, 91, 0, "null", "p", 92, 1, UINT64_C(0x98b1c65a5fa29627), -2}, + {2, 0, 1, 92, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 93, 6, "[]", "p", 94, 1, UINT64_C(0x2e21f08ba84ea50f), -2}, + {2, 0, 1, 94, 6, "[]", "p", 95, 1, UINT64_C(0xb250fc4efda2ca0d), -2}, + {2, 0, 1, 95, 6, "[[\"recovery\"],{\"k\":1}]", "p", 131, 1, UINT64_C(0x6f9d196c9bd4b80e), -2}, + {2, 0, 1, 96, 6, "[[\"recovery\"],{\"k\":1}]", "p", 132, 1, UINT64_C(0xee016a80c22b8f04), -2}, + {2, 0, 1, 97, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 98, 6, "[{\"k\":1}]", "p", 122, 1, UINT64_C(0x7892afa12ce1ab6d), -2}, + {2, 0, 1, 99, 6, "[null,{\"k\":1}]", "p", 123, 1, UINT64_C(0x3767948effb80947), -2}, + {2, 0, 1, 100, 6, "[null,{\"k\":1}]", "p", 124, 1, UINT64_C(0x506c0994a5d90c1d), -2}, + {2, 0, 1, 101, 6, "[[],{\"k\":1}]", "p", 127, 1, UINT64_C(0x9176b3ac4cb728b7), -2}, + {2, 0, 1, 102, 6, "[[],{\"k\":1}]", "p", 126, 1, UINT64_C(0xd58a76f1a9068035), -2}, + {2, 0, 1, 103, 6, "[[],{\"k\":1}]", "p", 127, 1, UINT64_C(0x9176b3ac4cb728b7), -2}, + {2, 0, 1, 104, 6, "[[],{\"k\":1}]", "p", 128, 1, UINT64_C(0xffbfa858653271ad), -2}, + {2, 0, 1, 105, 6, "[[],{\"k\":1}]", "p", 129, 1, UINT64_C(0xf4cd6d09dfbfe95f), -2}, + {2, 0, 1, 106, 6, "[[\"recovery\"],{\"k\":1}]", "p", 134, 1, UINT64_C(0x258db63c2da5cad4), -2}, + {2, 0, 1, 107, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 108, 6, "[[],{\"k\":1}]", "p", 132, 1, UINT64_C(0x587e897afbcc8e9d), -2}, + {2, 0, 1, 109, 6, "[[null],{\"k\":1}]", "p", 133, 1, UINT64_C(0x574165db06887297), -2}, + {2, 0, 1, 110, 6, "[[null],{\"k\":1}]", "p", 134, 1, UINT64_C(0x44b6e6e072fd4815), -2}, + {2, 0, 1, 111, 6, "[[\"\"],{\"k\":1}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 112, 6, "[[\"recovery\"],{\"k\":1}]", "p", 131, 1, UINT64_C(0xbeffc9b3b016a96e), -2}, + {2, 0, 1, 113, 6, "[[\"recovery\"],{\"k\":1}]", "p", 132, 1, UINT64_C(0xcc99fca554c5d906), -2}, + {2, 0, 1, 114, 6, "[[\"recovery\"],{\"k\":1}]", "p", 133, 1, UINT64_C(0xc35b653033194ba6), -2}, + {2, 0, 1, 115, 6, "[[\"recovery\"]]", "p", 116, 1, UINT64_C(0x1510723c16fda92c), -2}, + {2, 0, 1, 116, 6, "[[\"recovery\"],null]", "p", 117, 1, UINT64_C(0xc89b401b1106682c), -2}, + {2, 0, 1, 117, 6, "[[\"recovery\"],null]", "p", 118, 1, UINT64_C(0xe635f9fdede2cc64), -2}, + {2, 0, 1, 118, 6, "[[\"recovery\"],null]", "p", 119, 1, UINT64_C(0x1083d67b3860b864), -2}, + {2, 0, 1, 119, 6, "[[\"recovery\"],{}]", "p", 120, 1, UINT64_C(0x70bdb760cc591b8c), -2}, + {2, 0, 1, 120, 6, "[[\"recovery\"],{}]", "p", 121, 1, UINT64_C(0xeb7a7d7b3b6a646c), -2}, + {2, 0, 1, 121, 6, "[[\"recovery\"],{}]", "p", 122, 1, UINT64_C(0x8b878465f5c86d24), -2}, + {2, 0, 1, 122, 6, "[[\"recovery\"],{}]", "p", 123, 1, UINT64_C(0xdfb34540a391b824), -2}, + {2, 0, 1, 123, 6, "[[\"recovery\"],{\"\":1}]", "p", 128, 1, UINT64_C(0xf0e26e8f61eedf0e), -2}, + {2, 0, 1, 124, 6, "[[\"recovery\"],{\"\":1}]", "p", 132, 1, UINT64_C(0x898913a98d73fe48), -2}, + {2, 0, 1, 125, 6, "[[\"recovery\"],{\"\":1}]", "p", 130, 1, UINT64_C(0x0e41f58843f9c3c4), -2}, + {2, 0, 1, 126, 6, "[[\"recovery\"],{\"\":1}]", "p", 133, 1, UINT64_C(0x2c9072f93d1f7bca), -2}, + {2, 0, 1, 127, 6, "[[\"recovery\"],{\"k\":0}]", "p", 133, 1, UINT64_C(0x7f8a662f14e7da72), -2}, + {2, 0, 1, 128, 6, "[[\"recovery\"],{\"k\":0}]", "p", 134, 1, UINT64_C(0x81e270def940d1d4), -2}, + {2, 0, 1, 129, 6, "[[\"recovery\"],{\"\":0}]", "p", 132, 1, UINT64_C(0x0959c1d9bb490c16), -2}, + {2, 0, 1, 130, 6, "[[\"recovery\"],{\"\":0}]", "p", 134, 1, UINT64_C(0x81f6cedef9521a98), -2}, + {2, 0, 1, 131, 6, "[[\"recovery\"],{}]", "p", 132, 1, UINT64_C(0x09e813d9bbc19cb2), -2}, + {2, 0, 1, 132, 6, "[[\"recovery\"],{\"k\":0}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 133, 6, "[[\"recovery\"],{\"\":0}]", "p", 134, 1, UINT64_C(0x81f6cedef9521a98), -2}, + {2, 0, 1, 134, 6, "[[\"recovery\"],{\"\":0}]", "p", 135, 1, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 0, 1, 135, 6, "[[\"recovery\"],{\"k\":1}]", "p", 135, 0, UINT64_C(0x287c1ee1a68333ae), -2}, + {2, 1, 0, 0, 4, "inf", "p", 2, 2, UINT64_C(0x08842007b530c95d), -2}, + {2, 1, 0, 1, 4, "inf", "p", 3, 2, UINT64_C(0xa94bb518e1e60558), -2}, + {2, 1, 0, 2, 4, "inf", "p", 4, 2, UINT64_C(0x925e3a47da742eb6), -2}, + {2, 1, 0, 3, 4, "inf", "p", 7, 4, UINT64_C(0x873fb06eb56bb310), -2}, + {2, 1, 0, 4, 4, "inf", "p", 7, 3, UINT64_C(0x873fb06eb56bb310), -2}, + {2, 1, 0, 5, 4, "inf", "p", 8, 3, UINT64_C(0x21be1d1f5889f9de), -2}, + {2, 1, 0, 6, 4, "inf", "p", 12, 6, UINT64_C(0xefae9e7acaf5f4de), -2}, + {2, 1, 0, 7, 4, "inf", "p", 13, 6, UINT64_C(0x4657d233983eb2a8), -2}, + {2, 1, 0, 8, 4, "inf", "p", 14, 6, UINT64_C(0xf0284721aceafbd6), -2}, + {2, 1, 0, 9, 4, "inf", "p", 14, 5, UINT64_C(0xf0284721aceafbd6), -2}, + {2, 1, 0, 10, 4, "inf", "p", 15, 5, UINT64_C(0x442e86010f8af8c8), -2}, + {2, 1, 0, 11, 4, "inf", "p", 16, 5, UINT64_C(0x85c06de61045b546), -2}, + {2, 1, 0, 12, 4, "inf", "p", 18, 6, UINT64_C(0x41d98e2f4ad3c91e), -2}, + {2, 1, 0, 13, 4, "inf", "p", 18, 5, UINT64_C(0x41d98e2f4ad3c91e), -2}, + {2, 1, 0, 14, 4, "inf", "p", 19, 5, UINT64_C(0x790df96af2244a78), -2}, + {2, 1, 0, 15, 4, "inf", "p", 20, 5, UINT64_C(0x376dc67cd2ba1f16), -2}, + {2, 1, 0, 16, 4, "inf", "p", 21, 5, UINT64_C(0x3de77c10479abc20), -2}, + {2, 1, 0, 17, 4, "inf", "p", 23, 6, UINT64_C(0x168f5f4ea0496820), -2}, + {2, 1, 0, 18, 4, "inf", "p", 24, 6, UINT64_C(0xbff01ebe0def54c6), -2}, + {2, 1, 0, 19, 4, "inf", "p", 24, 5, UINT64_C(0xbff01ebe0def54c6), -2}, + {2, 1, 0, 20, 4, "inf", "p", 25, 5, UINT64_C(0x6e0c55c54e01aed8), -2}, + {2, 1, 0, 21, 4, "inf", "p", 26, 5, UINT64_C(0x672952c86689b87e), -2}, + {2, 1, 0, 22, 4, "inf", "p", 27, 5, UINT64_C(0x985cc89ee676aa03), -2}, + {2, 1, 0, 23, 4, "inf", "p", 27, 4, UINT64_C(0x985cc89ee676aa03), -2}, + {2, 1, 0, 24, 4, "inf", "p", 28, 4, UINT64_C(0x76a37085ec026a7b), -2}, + {2, 1, 0, 25, 4, "inf", "p", 29, 4, UINT64_C(0x67ea086e2017149b), -2}, + {2, 1, 0, 26, 4, "inf", "p", 29, 3, UINT64_C(0x67ea086e2017149b), -2}, + {2, 1, 0, 27, 4, "inf", "p", 30, 3, UINT64_C(0xa9799d2086fb771b), -2}, + {2, 1, 0, 28, 4, "inf", "p", 31, 3, UINT64_C(0x2528a044e77d2923), -2}, + {2, 1, 0, 29, 4, "inf", "p", 32, 3, UINT64_C(0xb64fe715f69e6223), -2}, + {2, 1, 0, 30, 4, "inf", "p", 33, 3, UINT64_C(0x0d6544514aa46a7b), -2}, + {2, 1, 0, 31, 4, "inf", "p", 34, 3, UINT64_C(0x5206e02138248d5b), -2}, + {2, 1, 0, 32, 4, "inf", "p", 35, 3, UINT64_C(0xb647ba71f04df7e3), -2}, + {2, 1, 0, 33, 4, "inf", "p", 36, 3, UINT64_C(0xca3aa1997d6e5ce3), -2}, + {2, 1, 0, 34, 4, "inf", "p", 39, 5, UINT64_C(0x049c604f5086fbf5), -2}, + {2, 1, 0, 35, 4, "inf", "p", 41, 6, UINT64_C(0xd30bbe54aa67b803), -2}, + {2, 1, 0, 36, 4, "inf", "p", 41, 5, UINT64_C(0xd30bbe54aa67b803), -2}, + {2, 1, 0, 37, 4, "inf", "p", 43, 6, UINT64_C(0x565578045ddc3491), -2}, + {2, 1, 0, 38, 4, "inf", "p", 43, 5, UINT64_C(0x565578045ddc3491), -2}, + {2, 1, 0, 39, 4, "inf", "p", 44, 5, UINT64_C(0x90309b76bc8f1147), -2}, + {2, 1, 0, 40, 4, "inf", "p", 44, 4, UINT64_C(0x90309b76bc8f1147), -2}, + {2, 1, 0, 41, 4, "inf", "p", 45, 4, UINT64_C(0x5ddf35f5bf677aa1), -2}, + {2, 1, 0, 42, 4, "inf", "p", 45, 3, UINT64_C(0x5ddf35f5bf677aa1), -2}, + {2, 1, 0, 43, 4, "inf", "p", 47, 4, UINT64_C(0x1676278f9958f91f), -2}, + {2, 1, 0, 44, 4, "inf", "p", 47, 3, UINT64_C(0x1676278f9958f91f), -2}, + {2, 1, 0, 45, 4, "inf", "p", 48, 3, UINT64_C(0xda674d01c347d925), -2}, + {2, 1, 0, 46, 4, "inf", "p", 48, 2, UINT64_C(0xda674d01c347d925), -2}, + {2, 1, 0, 47, 4, "inf", "p", 49, 2, UINT64_C(0x65f30dfed3920179), -2}, + {2, 1, 0, 48, 4, "inf", "p", 54, 6, UINT64_C(0x914f3d1bf9fab8ba), -2}, + {2, 1, 0, 49, 4, "inf", "p", 54, 5, UINT64_C(0x914f3d1bf9fab8ba), -2}, + {2, 1, 0, 50, 4, "inf", "p", 55, 5, UINT64_C(0xde0c49db59af6846), -2}, + {2, 1, 0, 51, 4, "inf", "p", 59, 8, UINT64_C(0xb7b6ab5b9ab29b66), -2}, + {2, 1, 0, 52, 4, "inf", "p", 60, 8, UINT64_C(0xd3548f8598abb40a), -2}, + {2, 1, 0, 53, 4, "inf", "p", 61, 8, UINT64_C(0xca2dac3b6af6a2c6), -2}, + {2, 1, 0, 54, 4, "inf", "p", 61, 7, UINT64_C(0xca2dac3b6af6a2c6), -2}, + {2, 1, 0, 55, 4, "inf", "p", 62, 7, UINT64_C(0x6c79bb8c2f845522), -2}, + {2, 1, 0, 56, 4, "inf", "p", 63, 7, UINT64_C(0x507800877df2db7e), -2}, + {2, 1, 0, 57, 4, "inf", "p", 65, 8, UINT64_C(0xc7d73b525cb35cde), -2}, + {2, 1, 0, 58, 4, "inf", "p", 65, 7, UINT64_C(0xc7d73b525cb35cde), -2}, + {2, 1, 0, 59, 4, "inf", "p", 66, 7, UINT64_C(0x8fcd0dc6b2475962), -2}, + {2, 1, 0, 60, 4, "inf", "p", 67, 7, UINT64_C(0x1a10f9f3af53143e), -2}, + {2, 1, 0, 61, 4, "inf", "p", 68, 7, UINT64_C(0x8d9413d55ceccf42), -2}, + {2, 1, 0, 62, 4, "inf", "p", 70, 8, UINT64_C(0x3705ed93c71c7dba), -2}, + {2, 1, 0, 63, 4, "inf", "p", 71, 8, UINT64_C(0xb689c5816d1f6a9e), -2}, + {2, 1, 0, 64, 4, "inf", "p", 71, 7, UINT64_C(0xb689c5816d1f6a9e), -2}, + {2, 1, 0, 65, 4, "inf", "p", 72, 7, UINT64_C(0x569f42d7c1e146ba), -2}, + {2, 1, 0, 66, 4, "inf", "p", 73, 7, UINT64_C(0x7000e960937be48e), -2}, + {2, 1, 0, 67, 4, "inf", "p", 74, 7, UINT64_C(0x1516607c80e41fc9), -2}, + {2, 1, 0, 68, 4, "inf", "p", 74, 6, UINT64_C(0x1516607c80e41fc9), -2}, + {2, 1, 0, 69, 4, "inf", "p", 75, 6, UINT64_C(0x96750cb7cbb0a4bb), -2}, + {2, 1, 0, 70, 4, "inf", "p", 76, 6, UINT64_C(0x470cf046dcbd8819), -2}, + {2, 1, 0, 71, 4, "inf", "p", 76, 5, UINT64_C(0x470cf046dcbd8819), -2}, + {2, 1, 0, 72, 4, "inf", "p", 77, 5, UINT64_C(0xc53df0bc2e598963), -2}, + {2, 1, 0, 73, 4, "inf", "p", 78, 5, UINT64_C(0x0f9a2d48a9309429), -2}, + {2, 1, 0, 74, 4, "inf", "p", 79, 5, UINT64_C(0xa7bfb696b47ccdb3), -2}, + {2, 1, 0, 75, 4, "inf", "p", 80, 5, UINT64_C(0xac5533df7aaf9119), -2}, + {2, 1, 0, 76, 4, "inf", "p", 81, 5, UINT64_C(0x3b3567c0e6b18803), -2}, + {2, 1, 0, 77, 4, "inf", "p", 82, 5, UINT64_C(0x06d0d4ed3a90d6c9), -2}, + {2, 1, 0, 78, 4, "inf", "p", 83, 5, UINT64_C(0x9d0b9317b69dcdd3), -2}, + {2, 1, 0, 79, 4, "inf", "p", 86, 7, UINT64_C(0xb3b840baf7320603), -2}, + {2, 1, 0, 80, 4, "inf", "p", 88, 8, UINT64_C(0x4089d5d92e008641), -2}, + {2, 1, 0, 81, 4, "inf", "p", 88, 7, UINT64_C(0x4089d5d92e008641), -2}, + {2, 1, 0, 82, 4, "inf", "p", 90, 8, UINT64_C(0x4ab828316be566fb), -2}, + {2, 1, 0, 83, 4, "inf", "p", 90, 7, UINT64_C(0x4ab828316be566fb), -2}, + {2, 1, 0, 84, 4, "inf", "p", 91, 7, UINT64_C(0x842d7ddee33895bb), -2}, + {2, 1, 0, 85, 4, "inf", "p", 91, 6, UINT64_C(0x842d7ddee33895bb), -2}, + {2, 1, 0, 86, 4, "inf", "p", 92, 6, UINT64_C(0xcca48c677db770e3), -2}, + {2, 1, 0, 87, 4, "inf", "p", 92, 5, UINT64_C(0xcca48c677db770e3), -2}, + {2, 1, 0, 88, 4, "inf", "p", 94, 6, UINT64_C(0x3bd0b6d0949cbca1), -2}, + {2, 1, 0, 89, 4, "inf", "p", 94, 5, UINT64_C(0x3bd0b6d0949cbca1), -2}, + {2, 1, 0, 90, 4, "inf", "p", 95, 5, UINT64_C(0x4afa4667b947c869), -2}, + {2, 1, 0, 91, 4, "inf", "p", 95, 4, UINT64_C(0x4afa4667b947c869), -2}, + {2, 1, 0, 92, 4, "inf", "p", 96, 4, UINT64_C(0xc3cf3a0ff19c4214), -2}, + {2, 1, 0, 93, 4, "inf", "p[", 96, 3, UINT64_C(0xc3cf3a0ff19c4214), -2}, + {2, 1, 0, 94, 4, "inf", "p[", 96, 2, UINT64_C(0xc3cf3a0ff19c4214), -2}, + {2, 1, 0, 95, 4, "inf", "p[", 102, 7, UINT64_C(0xe297ab3390bad1e8), -2}, + {2, 1, 0, 96, 4, "inf", "p[", 102, 6, UINT64_C(0xe297ab3390bad1e8), -2}, + {2, 1, 0, 97, 4, "inf", "p[[", 102, 5, UINT64_C(0xe297ab3390bad1e8), -2}, + {2, 1, 0, 98, 4, "inf", "p[[", 102, 4, UINT64_C(0xe297ab3390bad1e8), -2}, + {2, 1, 0, 99, 4, "inf", "p[[", 114, 15, UINT64_C(0x374af64aaff30d64), -2}, + {2, 1, 0, 100, 4, "inf", "p[[", 114, 14, UINT64_C(0x374af64aaff30d64), -2}, + {2, 1, 0, 101, 4, "inf", "p[[\"recovery\"", 105, 4, UINT64_C(0xce60b037a8707bbe), -2}, + {2, 1, 0, 102, 4, "inf", "p[[\"recovery\"", 105, 3, UINT64_C(0xce60b037a8707bbe), -2}, + {2, 1, 0, 103, 4, "inf", "p[[\"recovery\"]", 105, 2, UINT64_C(0xce60b037a8707bbe), -2}, + {2, 1, 0, 104, 4, "inf", "p[[\"recovery\"]", 111, 7, UINT64_C(0x354affc149c2c62a), -2}, + {2, 1, 0, 105, 4, "inf", "p[[\"recovery\"]", 112, 7, UINT64_C(0x894ddfea63a49cfc), -2}, + {2, 1, 0, 106, 4, "inf", "p[[\"recovery\"],{", 111, 5, UINT64_C(0x354affc149c2c62a), -2}, + {2, 1, 0, 107, 4, "inf", "p[[\"recovery\"],{", 111, 4, UINT64_C(0x354affc149c2c62a), -2}, + {2, 1, 0, 108, 4, "inf", "p[[\"recovery\"],{", 118, 10, UINT64_C(0x885508e0a1d3c190), -2}, + {2, 1, 0, 109, 4, "inf", "p[[\"recovery\"],{", 118, 9, UINT64_C(0x885508e0a1d3c190), -2}, + {2, 1, 0, 110, 4, "inf", "p[[\"recovery\"],{", 115, 5, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 0, 111, 4, "inf", "p[[\"recovery\"],{\"k\":1", 115, 4, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 0, 112, 4, "inf", "p[[\"recovery\"],{\"k\":1", 115, 3, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 0, 113, 4, "inf", "p[[\"recovery\"],{\"k\":1}", 115, 2, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 0, 114, 4, "inf", "p[[\"recovery\"],{\"k\":1}", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 0, 115, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 0, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 0, 4, "inf", "p", 105, 1, UINT64_C(0xfac7f1cbf21951a9), -2}, + {2, 1, 1, 1, 4, "inf", "pnull", 4, 1, UINT64_C(0x91aa0747d9db038a), -2}, + {2, 1, 1, 2, 4, "inf", "pnull", 5, 1, UINT64_C(0x2a4ba818336b39e0), -2}, + {2, 1, 1, 3, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 4, 4, "inf", "p[]", 15, 1, UINT64_C(0x0c8974e4b6ecf110), -2}, + {2, 1, 1, 5, 4, "inf", "p[]", 16, 1, UINT64_C(0x69af6a031832f12a), -2}, + {2, 1, 1, 6, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 111, 1, UINT64_C(0x3f9605949f0b61dc), -2}, + {2, 1, 1, 7, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 112, 1, UINT64_C(0x55e1e9f354626c96), -2}, + {2, 1, 1, 8, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 9, 4, "inf", "p[{\"k\":1}]", 75, 1, UINT64_C(0x591aac29dc23081c), -2}, + {2, 1, 1, 10, 4, "inf", "p[null,{\"k\":1}]", 80, 1, UINT64_C(0xaba4dcbfd93fbe89), -2}, + {2, 1, 1, 11, 4, "inf", "p[null,{\"k\":1}]", 81, 1, UINT64_C(0x732879a78cb66343), -2}, + {2, 1, 1, 12, 4, "inf", "p[[],{\"k\":1}]", 97, 1, UINT64_C(0x73efebbb25af1480), -2}, + {2, 1, 1, 13, 4, "inf", "p[[],{\"k\":1}]", 96, 1, UINT64_C(0xf8c138a5ea57881e), -2}, + {2, 1, 1, 14, 4, "inf", "p[[],{\"k\":1}]", 97, 1, UINT64_C(0x73efebbb25af1480), -2}, + {2, 1, 1, 15, 4, "inf", "p[[],{\"k\":1}]", 98, 1, UINT64_C(0xcddc111172cf8e06), -2}, + {2, 1, 1, 16, 4, "inf", "p[[],{\"k\":1}]", 99, 1, UINT64_C(0x6f4ae13a6baf83f8), -2}, + {2, 1, 1, 17, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 114, 1, UINT64_C(0x3a7766a8697c2a66), -2}, + {2, 1, 1, 18, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 19, 4, "inf", "p[[],{\"k\":1}]", 102, 1, UINT64_C(0x11e37283dc8dd7d6), -2}, + {2, 1, 1, 20, 4, "inf", "p[[null],{\"k\":1}]", 107, 1, UINT64_C(0x848dda53d220f8fb), -2}, + {2, 1, 1, 21, 4, "inf", "p[[null],{\"k\":1}]", 108, 1, UINT64_C(0xa9473dc35a917371), -2}, + {2, 1, 1, 22, 4, "inf", "p[[\"\"],{\"k\":1}]", 113, 1, UINT64_C(0xc5fb8ea3c2b0b1c3), -2}, + {2, 1, 1, 23, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 111, 1, UINT64_C(0xf51092ee3a9ad1fc), -2}, + {2, 1, 1, 24, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 112, 1, UINT64_C(0xedfb1fb02fae5c84), -2}, + {2, 1, 1, 25, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 113, 1, UINT64_C(0x36f8caa611be6164), -2}, + {2, 1, 1, 26, 4, "inf", "p[[\"recovery\"]]", 63, 1, UINT64_C(0xc8600ba3b9a0000c), -2}, + {2, 1, 1, 27, 4, "inf", "p[[\"recovery\"],null]", 69, 1, UINT64_C(0x7846bb94c3fa45a4), -2}, + {2, 1, 1, 28, 4, "inf", "p[[\"recovery\"],null]", 70, 1, UINT64_C(0x34916d381ad6105c), -2}, + {2, 1, 1, 29, 4, "inf", "p[[\"recovery\"],null]", 71, 1, UINT64_C(0xb57428a5b1a5535c), -2}, + {2, 1, 1, 30, 4, "inf", "p[[\"recovery\"],{}]", 84, 1, UINT64_C(0x305be21460b5f84e), -2}, + {2, 1, 1, 31, 4, "inf", "p[[\"recovery\"],{}]", 85, 1, UINT64_C(0x13c0590bc3f0feae), -2}, + {2, 1, 1, 32, 4, "inf", "p[[\"recovery\"],{}]", 86, 1, UINT64_C(0x0cfdde51f4c27156), -2}, + {2, 1, 1, 33, 4, "inf", "p[[\"recovery\"],{}]", 87, 1, UINT64_C(0x8f32c5b6747d2856), -2}, + {2, 1, 1, 34, 4, "inf", "p[[\"recovery\"],{\"\":1}]", 104, 1, UINT64_C(0x488f2fce5d41cff4), -2}, + {2, 1, 1, 35, 4, "inf", "p[[\"recovery\"],{\"\":1}]", 108, 1, UINT64_C(0xd595ff26e9136a52), -2}, + {2, 1, 1, 36, 4, "inf", "p[[\"recovery\"],{\"\":1}]", 106, 1, UINT64_C(0xd2bb220845d5b4be), -2}, + {2, 1, 1, 37, 4, "inf", "p[[\"recovery\"],{\"\":1}]", 109, 1, UINT64_C(0x6ca21d89f8353260), -2}, + {2, 1, 1, 38, 4, "inf", "p[[\"recovery\"],{\"k\":0}]", 110, 1, UINT64_C(0x449e6807405be748), -2}, + {2, 1, 1, 39, 4, "inf", "p[[\"recovery\"],{\"k\":0}]", 111, 1, UINT64_C(0xa260c5b4fb642196), -2}, + {2, 1, 1, 40, 4, "inf", "p[[\"recovery\"],{\"\":0}]", 108, 1, UINT64_C(0xfc1524aac194c92c), -2}, + {2, 1, 1, 41, 4, "inf", "p[[\"recovery\"],{\"\":0}]", 110, 1, UINT64_C(0xf81d5cfa5e6b6022), -2}, + {2, 1, 1, 42, 4, "inf", "p[[\"recovery\"],{}]", 96, 1, UINT64_C(0xa69ee9fea9eb4938), -2}, + {2, 1, 1, 43, 4, "inf", "p[[\"recovery\"],{\"k\":0}]", 112, 1, UINT64_C(0x132c5d9212db309c), -2}, + {2, 1, 1, 44, 4, "inf", "p[[\"recovery\"],{\"\":0}]", 110, 1, UINT64_C(0xf81d5cfa5e6b6022), -2}, + {2, 1, 1, 45, 4, "inf", "p[[\"recovery\"],{\"\":0}]", 111, 1, UINT64_C(0x043e77994de74fd4), -2}, + {2, 1, 1, 46, 4, "inf", "pnull", 49, 1, UINT64_C(0x65610cfed31611a9), -2}, + {2, 1, 1, 47, 4, "inf", "pnull", 50, 1, UINT64_C(0xce02370181186f9d), -2}, + {2, 1, 1, 48, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 49, 4, "inf", "p[]", 55, 1, UINT64_C(0xc3754b6adc679d65), -2}, + {2, 1, 1, 50, 4, "inf", "p[]", 56, 1, UINT64_C(0xde5a0015199ced49), -2}, + {2, 1, 1, 51, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 111, 1, UINT64_C(0x74016e17e4a9193c), -2}, + {2, 1, 1, 52, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 112, 1, UINT64_C(0xe38a0224c4ca8bf8), -2}, + {2, 1, 1, 53, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 54, 4, "inf", "p[{\"k\":1}]", 92, 1, UINT64_C(0x0143704d13918f93), -2}, + {2, 1, 1, 55, 4, "inf", "p[null,{\"k\":1}]", 96, 1, UINT64_C(0x67f7c86fc86745e8), -2}, + {2, 1, 1, 56, 4, "inf", "p[null,{\"k\":1}]", 97, 1, UINT64_C(0xd472dda62fc8b51c), -2}, + {2, 1, 1, 57, 4, "inf", "p[[],{\"k\":1}]", 103, 1, UINT64_C(0x4422ace3e1108167), -2}, + {2, 1, 1, 58, 4, "inf", "p[[],{\"k\":1}]", 102, 1, UINT64_C(0x208e048921aeb27b), -2}, + {2, 1, 1, 59, 4, "inf", "p[[],{\"k\":1}]", 103, 1, UINT64_C(0x4422ace3e1108167), -2}, + {2, 1, 1, 60, 4, "inf", "p[[],{\"k\":1}]", 104, 1, UINT64_C(0xc940fad8d887805b), -2}, + {2, 1, 1, 61, 4, "inf", "p[[],{\"k\":1}]", 105, 1, UINT64_C(0xdd2801bf456fb6c7), -2}, + {2, 1, 1, 62, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 114, 1, UINT64_C(0x38b6d551ca08b970), -2}, + {2, 1, 1, 63, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 64, 4, "inf", "p[[],{\"k\":1}]", 108, 1, UINT64_C(0x88b0a306097cbe3b), -2}, + {2, 1, 1, 65, 4, "inf", "p[[null],{\"k\":1}]", 112, 1, UINT64_C(0x92fbfabfe2bd4d90), -2}, + {2, 1, 1, 66, 4, "inf", "p[[null],{\"k\":1}]", 113, 1, UINT64_C(0x8d853497518ee95c), -2}, + {2, 1, 1, 67, 4, "inf", "p[[\"\"],{\"k\":1}]", 114, 1, UINT64_C(0xcdc25dcc8c6fd758), -2}, + {2, 1, 1, 68, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 111, 1, UINT64_C(0x905ea4dbf4f5ae3c), -2}, + {2, 1, 1, 69, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 112, 1, UINT64_C(0xf52f68a5508e71e2), -2}, + {2, 1, 1, 70, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 113, 1, UINT64_C(0xef2a9233221136ec), -2}, + {2, 1, 1, 71, 4, "inf", "p[[\"recovery\"]]", 86, 1, UINT64_C(0x1948be9f7de8afae), -2}, + {2, 1, 1, 72, 4, "inf", "p[[\"recovery\"],null]", 91, 1, UINT64_C(0xf7140d424fe8b6ee), -2}, + {2, 1, 1, 73, 4, "inf", "p[[\"recovery\"],null]", 92, 1, UINT64_C(0x8315c9b651e19040), -2}, + {2, 1, 1, 74, 4, "inf", "p[[\"recovery\"],null]", 93, 1, UINT64_C(0xe0c1f4211c5b60be), -2}, + {2, 1, 1, 75, 4, "inf", "p[[\"recovery\"],{}]", 96, 1, UINT64_C(0xc0ede8404dad0c5a), -2}, + {2, 1, 1, 76, 4, "inf", "p[[\"recovery\"],{}]", 97, 1, UINT64_C(0x9da215612280ff2c), -2}, + {2, 1, 1, 77, 4, "inf", "p[[\"recovery\"],{}]", 98, 1, UINT64_C(0x202ed5d544886c8a), -2}, + {2, 1, 1, 78, 4, "inf", "p[[\"recovery\"],{}]", 99, 1, UINT64_C(0xb8acaca1beac85bc), -2}, + {2, 1, 1, 79, 4, "inf", "p[[\"recovery\"],{\"\":1}]", 108, 1, UINT64_C(0x52121bbdbfb0762a), -2}, + {2, 1, 1, 80, 4, "inf", "p[[\"recovery\"],{\"\":1}]", 112, 1, UINT64_C(0x48642ece843ee99c), -2}, + {2, 1, 1, 81, 4, "inf", "p[[\"recovery\"],{\"\":1}]", 110, 1, UINT64_C(0x397fa09381d5f75c), -2}, + {2, 1, 1, 82, 4, "inf", "p[[\"recovery\"],{\"\":1}]", 113, 1, UINT64_C(0x10cf6f3e6fb9025c), -2}, + {2, 1, 1, 83, 4, "inf", "p[[\"recovery\"],{\"k\":0}]", 113, 1, UINT64_C(0x71198b642b887e5c), -2}, + {2, 1, 1, 84, 4, "inf", "p[[\"recovery\"],{\"k\":0}]", 114, 1, UINT64_C(0xce2d81c99c68799c), -2}, + {2, 1, 1, 85, 4, "inf", "p[[\"recovery\"],{\"\":0}]", 112, 1, UINT64_C(0x79982ee8a36ed02a), -2}, + {2, 1, 1, 86, 4, "inf", "p[[\"recovery\"],{\"\":0}]", 114, 1, UINT64_C(0x476bb0dcbf90c4a4), -2}, + {2, 1, 1, 87, 4, "inf", "p[[\"recovery\"],{}]", 108, 1, UINT64_C(0x1c22249ac336200c), -2}, + {2, 1, 1, 88, 4, "inf", "p[[\"recovery\"],{\"k\":0}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 89, 4, "inf", "p[[\"recovery\"],{\"\":0}]", 114, 1, UINT64_C(0x476bb0dcbf90c4a4), -2}, + {2, 1, 1, 90, 4, "inf", "p[[\"recovery\"],{\"\":0}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 91, 4, "inf", "p[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x5b13a2832940b6f4), -2}, + {2, 1, 1, 92, 4, "inf", "p[\"recovery\"],{\"k\":1}]", 116, 1, UINT64_C(0xbb5fcbcb673868bd), -2}, + {2, 1, 1, 93, 4, "inf", "p[]", 96, 1, UINT64_C(0xc3cf3a0ff19c4214), -2}, + {2, 1, 1, 94, 4, "inf", "p[]", 96, 1, UINT64_C(0xc3cf3a0ff19c4214), -2}, + {2, 1, 1, 95, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 96, 4, "inf", "p[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 97, 4, "inf", "p[[],{\"k\":1}]", 111, 1, UINT64_C(0xcc5ceaf4e3a4f3aa), -2}, + {2, 1, 1, 98, 4, "inf", "p[[],{\"k\":1}]", 111, 1, UINT64_C(0xcc5ceaf4e3a4f3aa), -2}, + {2, 1, 1, 99, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 100, 4, "inf", "p[[recovery\"],{\"k\":1}]", 116, 1, UINT64_C(0x23a78c04006531a6), -2}, + {2, 1, 1, 101, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 102, 4, "inf", "p[[\"recovery\",{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 103, 4, "inf", "p[[\"recovery\"]]", 105, 1, UINT64_C(0xce60b037a8707bbe), -2}, + {2, 1, 1, 104, 4, "inf", "p[[\"recovery\"]{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 105, 4, "inf", "p[[\"recovery\"]{\"k\":1}]", 116, 1, UINT64_C(0x5652dc9a31d4a622), -2}, + {2, 1, 1, 106, 4, "inf", "p[[\"recovery\"],{}]", 111, 1, UINT64_C(0x354affc149c2c62a), -2}, + {2, 1, 1, 107, 4, "inf", "p[[\"recovery\"],{}]", 111, 1, UINT64_C(0x354affc149c2c62a), -2}, + {2, 1, 1, 108, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 109, 4, "inf", "p[[\"recovery\"],{k\":1}]", 116, 1, UINT64_C(0xd4561202a8aba466), -2}, + {2, 1, 1, 110, 4, "inf", "p[[\"recovery\"],{}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 111, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 112, 4, "inf", "p[[\"recovery\"],{\"k\":1]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 113, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 114, 4, "inf", "p[[\"recovery\"],{\"k\":1}", 115, 1, UINT64_C(0x10e2790b3ef7283c), -2}, + {2, 1, 1, 115, 4, "inf", "p[[\"recovery\"],{\"k\":1}]", 115, 0, UINT64_C(0x10e2790b3ef7283c), -2}, +}; +int main() { + RecoveryAllocationTrace trace; RecoveryAllocator allocator; Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize callback-return allocation contracts"); + Value replacement{}; _ZN3sce4Json5ValueC1Ev(&replacement); + const char document[] = "{\"k\":\"recovery\"}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&replacement, document, sizeof(document) - 1) == 0 && Contents(replacement) == document, "Construct the independently owned callback-return template"); + Value nestedReplacement{}; _ZN3sce4Json5ValueC1Ev(&nestedReplacement); + const char nestedDocument[] = "[[\"recovery\"],{\"k\":1}]"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&nestedReplacement, nestedDocument, sizeof(nestedDocument) - 1) == 0 && Contents(nestedReplacement) == nestedDocument, "Construct the independently owned nested recovery template"); + String lookupKey{}; _ZN3sce4Json6StringC1EPKc(&lookupKey, "k"); + const auto baseline = trace.live; + const auto baselineIds = trace.ids; + for (const auto& expected : Cases) { + Value value{}; _ZN3sce4Json5ValueC1Ed(&value, expected.format ? std::numeric_limits::infinity() : 0.125); + RecoveryContext callback{expected.object == 2 ? &nestedReplacement : &replacement, expected.object != 0}; + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, RecoverAllocated, &callback) == 0, "Register allocation-backed recovery"); + void* auxiliary = value.auxiliary; + void* fallback = reinterpret_cast(auxiliary)[2]; + void* record = reinterpret_cast(auxiliary)[4]; + const auto auxiliaryId = trace.ids.at(auxiliary), fallbackId = trace.ids.at(fallback), recordId = trace.ids.at(record); + String output{}; _ZN3sce4Json6StringC1EPKc(&output, "p"); + const auto requests = trace.requests.size(); const auto notifications = trace.notifications; + trace.budget = expected.budget; trace.once = expected.once; + int status = 0; + if (expected.format) status = _ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output); + else _ZN3sce4Json5Value3setEd(&value, std::numeric_limits::infinity()); + trace.budget = -1; trace.once = false; + const auto end = trace.requests.size(); + const auto errors = trace.notifications - notifications; + Require(value.type == expected.type && Contents(value) == expected.contents && std::strcmp(_ZNK3sce4Json6String5c_strEv(&output), expected.output) == 0, "Callback-return allocation failure must preserve the reference's exact partial value and output"); + if (expected.object) { + const auto* found = value.type == 7 ? _ZN3sce4Json5Value10referValueERKNS0_6StringE(&value, &lookupKey) : nullptr; + const int lookupType = value.type == 7 ? (found ? found->type : -1) : -2; + Require(lookupType == expected.lookupType, "Callback-return cloning must preserve internal tree-key lookup independently of the public key text"); + } + std::uint64_t order = UINT64_C(14695981039346656037); + for (auto i = requests; i < end; ++i) { order ^= trace.requests[i]; order *= UINT64_C(1099511628211); } + if (end - requests != expected.requests || order != expected.order || errors != expected.notifications) std::fprintf(stderr, "Recovery trace differs: object=%d format=%d once=%d budget=%d requests=%zu/%zu notices=%u/%u\n", expected.object, expected.format, expected.once, expected.budget, end - requests, expected.requests, errors, expected.notifications); + Require(end - requests == expected.requests && order == expected.order && errors == expected.notifications, "Callback construction, cloning and final assignment must preserve exact request order and failure notifications"); + if (expected.format) Require(status == 0, "Serialization must preserve the reference callback-recovery status"); + Require(callback.calls == 1 && callback.function == (expected.format ? 0 : 1) && callback.errorPointer == expected.format, "Recovery dispatch must retain callback function, invocation count, error-pointer shape and observed serialization status"); + Require(!value.parent && value.auxiliary == auxiliary && reinterpret_cast(auxiliary)[2] == fallback && reinterpret_cast(auxiliary)[4] == record, "Partial callback cloning must preserve destination callback metadata and fallback identity"); + Require(trace.ids.count(auxiliary) && trace.ids.count(fallback) && trace.ids.count(record), "Destination recovery metadata must retain live allocator ownership"); + Require(trace.ids.at(auxiliary) == auxiliaryId && trace.ids.at(fallback) == fallbackId && trace.ids.at(record) == recordId && reinterpret_cast(record)[0] == reinterpret_cast(&RecoverAllocated) && reinterpret_cast(record)[1] == &callback && reinterpret_cast(fallback)->parent == &value, "Recovery metadata must retain its allocation generation, handler, context and fallback owner"); + Require(Contents(replacement) == document && Contents(nestedReplacement) == nestedDocument, "Failure recovery must preserve the independently owned source template"); + _ZN3sce4Json6StringD1Ev(&output); _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live == baseline && trace.ids == baselineIds, "Every callback-return failure state must reclaim temporary ownership and remain destructible"); + } + _ZN3sce4Json6StringD1Ev(&lookupKey); + _ZN3sce4Json5ValueD1Ev(&nestedReplacement); + _ZN3sce4Json5ValueD1Ev(&replacement); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty() && trace.ids.empty(), "Callback-return allocation checks must release all allocator-owned resources"); + return 0; +} diff --git a/core/libs/tests/GuestJsonReferences.cpp b/core/libs/tests/GuestJsonReferences.cpp new file mode 100644 index 0000000000..538d0bcfd2 --- /dev/null +++ b/core/libs/tests/GuestJsonReferences.cpp @@ -0,0 +1,120 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using Mismatch = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +static_assert(sizeof(Value) == 32 && sizeof(Parameter) == 40 && offsetof(Parameter, mode) == 24); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +static void CheckParameters() { + int first = 1, second = 2; + const Parameter parameters[]{ + {nullptr, nullptr, 0, 0, {1, 2, 3}}, + {&first, &second, 8192, 2, {4, 5, 6}}, + {&second, &first, std::numeric_limits::max(), -17, {7, 8, 9}}, + }; + for (const auto& parameter : parameters) { + const auto before = parameter; + Require(_ZNK3sce4Json14InitParameter212getAllocatorEv(reinterpret_cast(¶meter)) == parameter.allocator && _ZNK3sce4Json14InitParameter211getUserDataEv(reinterpret_cast(¶meter)) == parameter.context, "Parameter getters must return exact pointer fields"); + Require(_ZNK3sce4Json14InitParameter217getFileBufferSizeEv(reinterpret_cast(¶meter)) == parameter.bufferSize && _ZNK3sce4Json14InitParameter225getSpecialFloatFormatTypeEv(reinterpret_cast(¶meter)) == parameter.mode, "Parameter getters must return field bits without normalizing or validating settings"); + Require(std::memcmp(&before, ¶meter, sizeof(parameter)) == 0, "Parameter getters must not mutate the parameter block"); + } +} +static void CheckReferences() { + int storage = 1, replacement = 2; + String string{reinterpret_cast(&storage)}; + for (std::int32_t type : {0, 1, 2, 3, 4, 5, 6, 7, 8, -1, INT32_MIN, INT32_MAX}) { + Value value{}; value.type = static_cast(type); + value.payload = type == 5 ? reinterpret_cast(&string) : UINT64_C(0x1122334455667788); + const auto before = value; + auto* boolean = _ZN3sce4Json5Value12referBooleanEv(&value); + auto* integer = _ZN3sce4Json5Value12referIntegerEv(&value); + auto* uinteger = _ZN3sce4Json5Value13referUIntegerEv(&value); + auto* real = _ZN3sce4Json5Value9referRealEv(&value); + auto* text = _ZN3sce4Json5Value11referStringEv(&value); + Require(reinterpret_cast(boolean) == (type == 1 ? &value.payload : nullptr), "Boolean references must identify this value's payload or return null"); + Require(reinterpret_cast(integer) == ((type == 2 || type == 3) ? &value.payload : nullptr) && reinterpret_cast(uinteger) == reinterpret_cast(integer), "Both integer reference functions must accept signed and unsigned values and share their actual storage"); + Require(reinterpret_cast(real) == (type == 4 ? &value.payload : nullptr) && text == (type == 5 ? &string : nullptr), "Real and string references must return compatible owned storage without fallback"); + Require(std::memcmp(&before, &value, sizeof(value)) == 0, "Obtaining references must preserve the value and its metadata"); + if (boolean) { + *boolean = true; + Require(value.payload == ((before.payload & ~UINT64_C(255)) | 1), "Boolean references must permit in-place writes without changing inactive payload bytes"); + } + if (integer) { + *integer = -123; + Require(value.payload == std::bit_cast(std::int64_t{-123}), "Signed reference writes must preserve their exact payload bits"); + *uinteger = UINT64_MAX; + Require(value.payload == UINT64_MAX, "Unsigned reference writes must update the same integer payload"); + } + if (real) { + *real = -1.25; + Require(value.payload == std::bit_cast(-1.25), "Real references must permit writes to the actual payload"); + } + if (text) { + text->text = reinterpret_cast(&replacement); + Require(string.text == reinterpret_cast(&replacement), "String references must expose the actual mutable string handle"); + } + Require(value.type == before.type && value.parent == before.parent && value.auxiliary == before.auxiliary, "Reference writes must preserve type and ownership metadata"); + } + Value missing{}; missing.type = static_cast(5); + Require(_ZN3sce4Json5Value11referStringEv(&missing) == nullptr, "A string value with missing storage must return null without allocating a replacement"); +} +struct ReferenceTrace { + std::map live; + bool rejectAllocations = false; +}; +static void* APS5_VABI AllocateReference(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(!trace.rejectAllocations, "Reference and parameter access must not allocate"); + void* memory = std::malloc(bytes); Require(memory != nullptr, "Reference allocator requires host memory"); + trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI FreeReference(void*, void* memory, void* context) { + if (!memory) return; + Require(static_cast(context)->live.erase(memory) == 1, "Reference metadata must return through the owning allocator exactly once"); + std::free(memory); +} +static void APS5_VABI NotifyReference(void*, int, std::size_t, void*) { Require(false, "Reference tests must not encounter allocation failure"); } +static void* const ReferenceVtable[]{nullptr, nullptr, reinterpret_cast(AllocateReference), reinterpret_cast(FreeReference), reinterpret_cast(NotifyReference)}; +struct ReferenceAllocator { void* const* vtable = ReferenceVtable; }; +struct Recovery { Value replacement{}; unsigned calls = 0; }; +static const Value& APS5_VABI RecoverReference(ValueType type, const Value*, const Value*, void* context) { + auto& recovery = *static_cast(context); ++recovery.calls; + Require(type == 1, "The preservation control must request a boolean replacement"); + return recovery.replacement; +} +int main() { + CheckParameters(); CheckReferences(); + ReferenceTrace trace; ReferenceAllocator allocator; + Parameter parameter{&allocator, &trace, 8192, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize reference callback tests"); + Value value{}; Recovery recovery; + _ZN3sce4Json5ValueC1Ev(&value); _ZN3sce4Json5ValueC1Eb(&recovery.replacement, true); + Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&value, RecoverReference, &recovery) == 0, "Register a mismatch handler for the callback-bypass test"); + trace.rejectAllocations = true; + CheckParameters(); CheckReferences(); + for (unsigned attempt = 0; attempt < 2; ++attempt) { + Require(!_ZN3sce4Json5Value12referBooleanEv(&value) && !_ZN3sce4Json5Value12referIntegerEv(&value) && !_ZN3sce4Json5Value13referUIntegerEv(&value) && !_ZN3sce4Json5Value9referRealEv(&value) && !_ZN3sce4Json5Value11referStringEv(&value), "Reference accessors must return null for mismatched values even when recovery is registered"); + Require(recovery.calls == attempt, "Reference accessors must never invoke recovery callbacks"); + Require(*_ZNK3sce4Json5Value10getBooleanEv(&value) && recovery.calls == attempt + 1, "Ordinary getters must preserve their working callback recovery behavior"); + } + trace.rejectAllocations = false; + _ZN3sce4Json5ValueD1Ev(&recovery.replacement); _ZN3sce4Json5ValueD1Ev(&value); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Reference teardown must reclaim all initialized resources and callback metadata"); + CheckParameters(); CheckReferences(); + return 0; +} diff --git a/core/libs/tests/GuestJsonSerializationCallback.cpp b/core/libs/tests/GuestJsonSerializationCallback.cpp new file mode 100644 index 0000000000..6d5b227eed --- /dev/null +++ b/core/libs/tests/GuestJsonSerializationCallback.cpp @@ -0,0 +1,170 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ArrayTrace { + std::map live; + std::vector requests; + int budget = -1; + bool once = false; + unsigned notifications = 0; + std::set headers; +}; +static void* APS5_VABI AllocateArray(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI FreeArray(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + if (trace.headers.erase(memory)) { + const auto* words = reinterpret_cast(memory); + Require(words[0] == 0xffffffff && words[1] == UINT64_MAX, "Array teardown must expose the native header poison before allocator release"); + } + Require(trace.live.erase(memory) == 1, "Array must release through its owning allocator"); std::free(memory); +} +static void APS5_VABI NotifyArray(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Array notification must identify the failed allocation"); ++trace.notifications; +} +static void* const ArrayVtable[]{nullptr, nullptr, reinterpret_cast(AllocateArray), reinterpret_cast(FreeArray), reinterpret_cast(NotifyArray)}; +struct ArrayAllocator { void* const* vtable = ArrayVtable; }; +struct SerializationTrace { + std::vector chunks; + String* identity = nullptr; + int stopAt = -1; + int result = 23; + bool mutate = false; +}; +static int APS5_VABI Observe(String& chunk, void* context) { + auto& trace = *static_cast(context); + if (!trace.identity) trace.identity = &chunk; + Require(trace.identity == &chunk, "Serialization must reuse the same chunk handle or supplied output object throughout the call"); + trace.chunks.emplace_back(_ZNK3sce4Json6String5c_strEv(&chunk)); + if (trace.mutate) _ZN3sce4Json6String6appendEPKc(&chunk, "!"); + return static_cast(trace.chunks.size()) == trace.stopAt ? -77 : trace.result; +} +struct CollectionChange { + const Array* array; + int mode; + std::vector chunks; +}; +static int APS5_VABI PrependAtOpening(String& chunk, void* context) { + auto& change = *static_cast(context); + change.chunks.emplace_back(_ZNK3sce4Json6String5c_strEv(&chunk)); + if (change.chunks.size() == 1) { + Value value{}; _ZN3sce4Json5ValueC1El(&value, 0); + if (change.mode == 0) _ZN3sce4Json5Array10push_frontERKNS0_5ValueE(const_cast(change.array), &value); + else if (change.mode == 1) _ZN3sce4Json5Array5clearEv(const_cast(change.array)); + else _ZN3sce4Json5Array9push_backERKNS0_5ValueE(const_cast(change.array), &value); + _ZN3sce4Json5ValueD1Ev(&value); + } + return 0; +} +int main() { + ArrayTrace allocation; ArrayAllocator allocator; + Parameter parameter{&allocator, &allocation, 8192, 2, {}}; + Value root{}; _ZN3sce4Json5ValueC1Ev(&root); + SerializationTrace before; + Require(static_cast(_ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(&root, Observe, &before)) == 0x80848110 && before.chunks.empty(), "Callback serialization before initialization must refuse without invoking the callback"); + String beforeOutput{}; + Require(static_cast(_ZN3sce4Json5Value9serializeERNS0_6StringE(&root, &beforeOutput)) == 0x80848110 && !beforeOutput.text, "Ordinary serialization before initialization must refuse without constructing output storage"); + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize serialization callbacks"); + const char json[] = R"({"a":[1,true,"x\n/\u007f"],"b":{}})"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&root, json, sizeof(json) - 1) == 0, "Parse nested callback serialization input"); + const std::vector chunks{"{", "\"a\":[", "1", ",true", ",\"x\\n\\/\\u007f\"", "]", ",\"b\":{", "}", "}"}; + const auto baseline = allocation.live.size(); + String ordinary{}; + _ZN3sce4Json6StringC1EPKc(&ordinary, "prefix"); + Require(_ZN3sce4Json5Value9serializeERNS0_6StringE(&root, &ordinary) == 0, "Ordinary serialization must use the collecting protocol with a successful internal sink"); + std::string expectedOrdinary = "prefix"; + for (const auto& chunk : chunks) expectedOrdinary += chunk; + Require(std::string(_ZNK3sce4Json6String5c_strEv(&ordinary)) == expectedOrdinary, "Ordinary serialization must preserve the same slash and DEL escaping as callback serialization"); + _ZN3sce4Json6StringD1Ev(&ordinary); + Require(allocation.live.size() == baseline, "Ordinary serialization must release its temporary buffer and collection iterators"); + + for (bool collect : {false, true}) for (bool mutate : {false, true}) for (int stop : {-1, 1, 3, 9}) { + String output{}; _ZN3sce4Json6StringC1EPKc(&output, "prefix"); + SerializationTrace trace; trace.stopAt = stop; trace.mutate = mutate; + if (collect) trace.identity = &output; + const int status = collect ? _ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_(&root, &output, Observe, &trace) : _ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(&root, Observe, &trace); + Require(status == (stop < 0 ? trace.result : -77), "Serialization must propagate the last callback result and stop on negative results"); + const auto count = stop < 0 ? chunks.size() : static_cast(stop); + Require(trace.chunks.size() == count, "Callback errors must stop traversal without emitting later or closing chunks"); + std::string cumulative = "prefix"; + for (std::size_t i = 0; i < count; ++i) { + cumulative += chunks[i]; + Require(trace.chunks[i] == (collect ? cumulative : chunks[i]), "Callback chunks must preserve structural boundaries, slash/DEL escaping and cumulative output semantics"); + if (collect && mutate) cumulative += '!'; + } + Require(std::string(_ZNK3sce4Json6String5c_strEv(&output)) == (collect ? cumulative : "prefix"), "Callback mutations must persist only in the supplied cumulative output, while streaming chunks are cleared after delivery"); + _ZN3sce4Json6StringD1Ev(&output); + Require(allocation.live.size() == baseline, "Successful and interrupted traversals must release all temporary buffers and iterator states"); + } + for (int mode : {0, 1, 2}) { + Value changing{}; _ZN3sce4Json5ValueC1Ev(&changing); + const char arrayJson[] = "[1,2]"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&changing, arrayJson, sizeof(arrayJson) - 1) == 0, "Parse collection mutation input"); + CollectionChange change{_ZNK3sce4Json5Value8getArrayEv(&changing), mode, {}}; + const auto expected = mode == 0 ? std::vector({"[", "0", ",1", ",2", "]"}) : mode == 1 ? std::vector({"[", "]"}) : std::vector({"[", "1", ",2", ",0", "]"}); + Require(_ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(&changing, PrependAtOpening, &change) == 0 && change.chunks == expected, "Serialization must obtain the live collection after the opening callback prepends, clears or appends entries"); + _ZN3sce4Json5ValueD1Ev(&changing); + Require(allocation.live.size() == baseline, "Collection mutation during serialization must leave no temporary or transferred allocations"); + } + Value scalar{}; _ZN3sce4Json5ValueC1El(&scalar, 1234); + for (bool collect : {false, true}) for (bool once : {false, true}) for (int budget : {0, 1, 2, 3}) { + String output{}; _ZN3sce4Json6StringC1EPKc(&output, "p"); + const auto requests = allocation.requests.size(); const auto notices = allocation.notifications; + allocation.budget = budget; allocation.once = once; + SerializationTrace trace; + const int status = collect ? _ZN3sce4Json5Value9serializeERNS0_6StringEPFiS3_PvES4_(&scalar, &output, Observe, &trace) : _ZN3sce4Json5Value9serializeEPFiRNS0_6StringEPvES4_(&scalar, Observe, &trace); + allocation.budget = -1; allocation.once = false; + Require(status == 23 && trace.chunks.size() == 1, "Allocation rejection must retain the reference callback-driven return contract"); + const bool complete = budget >= (collect ? 3 : 2); + Require(trace.chunks[0] == (collect ? complete ? "p1234" : "p" : complete ? "1234" : ""), "Allocation failures must expose the native admitted partial chunk or output"); + std::vector expected{24}; + if (budget >= 1) expected.push_back(21); + if (collect && budget >= 2) expected.push_back(22); + Require(std::vector(allocation.requests.begin() + requests, allocation.requests.end()) == expected, "Scalar callback serialization must allocate its header, chunk and optional cumulative-output growth in order"); + Require(allocation.notifications == notices + (complete ? 0 : 1), "Each scalar serialization rejection must notify exactly once"); + _ZN3sce4Json6StringD1Ev(&output); + Require(allocation.live.size() == baseline, "Rejected serialization allocations must not leak admitted storage"); + } + for (bool once : {false, true}) for (int budget : {0, 1, 2, 3}) { + String output{}; _ZN3sce4Json6StringC1EPKc(&output, "p"); + const auto requests = allocation.requests.size(); const auto notices = allocation.notifications; + allocation.budget = budget; allocation.once = once; + const int status = _ZN3sce4Json5Value9serializeERNS0_6StringE(&scalar, &output); + allocation.budget = -1; allocation.once = false; + Require(status == 0 && std::string(_ZNK3sce4Json6String5c_strEv(&output)) == (budget >= 3 ? "p1234" : "p"), "Ordinary serialization must preserve the native partial-output and return contracts under allocation failure"); + std::vector expected{24}; + if (budget >= 1) expected.push_back(21); + if (budget >= 2) expected.push_back(22); + Require(std::vector(allocation.requests.begin() + requests, allocation.requests.end()) == expected && allocation.notifications == notices + (budget < 3 ? 1 : 0), "Ordinary serialization must use the same allocator requests and failure notifications as the collecting callback path"); + _ZN3sce4Json6StringD1Ev(&output); + Require(allocation.live.size() == baseline, "Ordinary serialization failures must not leak storage"); + } + _ZN3sce4Json5ValueD1Ev(&scalar); _ZN3sce4Json5ValueD1Ev(&root); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && allocation.live.empty(), "Terminate callback serialization allocation context"); + return 0; +} diff --git a/core/libs/tests/GuestJsonSpecialFloat.cpp b/core/libs/tests/GuestJsonSpecialFloat.cpp new file mode 100644 index 0000000000..0a749141ea --- /dev/null +++ b/core/libs/tests/GuestJsonSpecialFloat.cpp @@ -0,0 +1,142 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +using Handler = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct Context { + int calls = 0; + int function = -1; + const Value* parent = nullptr; + bool sawErrorPointer = false; + bool reject = false; + bool returnReal = false; + Value* reenter = nullptr; +}; +static std::string Serialize(Value& value, int expectedError = 0, const char* prefix = "") { + String output{}; + _ZN3sce4Json6StringC1EPKc(&output, prefix); + const int result = _ZN3sce4Json5Value9serializeERNS0_6StringE(&value, &output); + Require(result == expectedError, "Serialization must propagate the callback's error flag"); + std::string text = _ZNK3sce4Json6String5c_strEv(&output); + _ZN3sce4Json6StringD1Ev(&output); + return text; +} +static Value* APS5_VABI Recover(Value* result, std::int32_t function, double number, + const Value* parent, void* context, bool* error) { + auto& trace = *static_cast(context); + ++trace.calls; + trace.function = function; + trace.parent = parent; + trace.sawErrorPointer = error != nullptr; + Require(number == std::numeric_limits::infinity(), "The handler must receive the original non-finite value"); + if (trace.reenter) { + Require(Serialize(*trace.reenter) == "inf", "Reentrant serialization must use the configured fallback"); + _ZN3sce4Json5Value3setEd(trace.reenter, number); + } + if (trace.returnReal) _ZN3sce4Json5ValueC1Ed(result, number); + else _ZN3sce4Json5ValueC1EPKc(result, function == 0 ? "format" : "assign"); + if (error) *error = trace.reject; + return result; +} +static void CheckMode(int mode) { + Value value{}; + _ZN3sce4Json5ValueC1Ed(&value, std::numeric_limits::infinity()); + JsonTest::Allocator allocator; + Parameter parameter{&allocator, nullptr, 0, mode, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize special-float recovery"); + Context trace; + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, Recover, &trace) == 0, + "Register a special-float handler"); + Require(Serialize(value, 0, "prefix:") == "prefix:\"format\"" && value.type == 4, + "Serialization must append a separately owned replacement without mutating the original real"); + Require(trace.calls == 1 && trace.function == 0 && !trace.parent && trace.sawErrorPointer, + "Serialization must supply function 0, the parent and an error pointer"); + String direct{}; + _ZN3sce4Json6StringC1EPKc(&direct, "prefix:"); + _ZNK3sce4Json5Value8toStringERNS0_6StringE(&value, &direct); + const char* directExpected = mode == 0 ? "prefix:\"inf\"" : mode == 1 ? "prefix:null" : "prefix:inf"; + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(&direct), directExpected) == 0 && trace.calls == 1, + "toString must append the configured scalar format without dispatching recovery"); + _ZN3sce4Json6StringD1Ev(&direct); + trace.reject = true; + Require(Serialize(value, static_cast(0x80848114), "keep") == "keep" && value.type == 4, + "A rejected replacement must not overwrite existing output or the source"); + trace.reject = false; + _ZN3sce4Json5Value3setEd(&value, 0.125); + Require(trace.calls == 2 && value.type == 4, "Finite assignment must bypass recovery"); + _ZN3sce4Json5Value3setEd(&value, std::numeric_limits::infinity()); + Require(trace.calls == 3 && trace.function == 1 && !trace.parent && !trace.sawErrorPointer, + "Assignment must supply function 1 and no error pointer for the void setter"); + Require(value.type == 5 && std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&value)), "assign") == 0, + "Assignment must retain the replacement after the callback temporary is destroyed"); + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, nullptr, nullptr) == 0, + "Unregister recovery"); + _ZN3sce4Json5Value3setEd(&value, std::numeric_limits::infinity()); + Require(value.type == (mode == 0 ? 5 : mode == 1 ? 0 : 4) && trace.calls == 3, + "Unregistering must restore the configured fallback conversion"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate recovery tests"); +} +static void CheckParentsAndReentry() { + JsonTest::Allocator allocator; + Parameter parameter{&allocator, nullptr, 0, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize parent recovery"); + Array array{}; + _ZN3sce4Json5ArrayC1Ev(&array); + Value leaf{}, root{}, other{}; + _ZN3sce4Json5ValueC1Ed(&leaf, std::numeric_limits::infinity()); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&array, &leaf); + _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&root, &array); + _ZN3sce4Json5ValueC1Ed(&other, std::numeric_limits::infinity()); + Context trace; + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&root, Recover, &trace) == 0, "Register ancestor recovery"); + auto* child = const_cast(_ZNK3sce4Json5Array5frontEv(_ZNK3sce4Json5Value8getArrayEv(&root))); + Require(static_cast(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(child, Recover, &trace)) == 0x80848104, + "An owned child must reject local handler registration"); + Require(Serialize(root) == "[\"format\"]" && trace.calls == 1 && trace.parent == &root, + "Nested serialization must use the root handler and pass the immediate parent"); + trace.reject = true; + Require(Serialize(root, static_cast(0x80848114), "keep") == "keep[" && trace.calls == 2, + "A nested rejection must retain output already emitted before the refused element"); + trace.reject = false; + _ZN3sce4Json5Value3setEd(child, std::numeric_limits::infinity()); + Require(trace.calls == 3 && trace.function == 1 && trace.parent == &root && child->type == 5, + "Nested assignment must use ancestor recovery"); + trace.reenter = &other; + trace.returnReal = true; + Require(_ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&other, Recover, &trace) == 0, "Register reentry target"); + const int before = trace.calls; + Require(Serialize(other) == "inf" && trace.calls == before + 1, + "A returned non-finite real and reentrant operations must not recursively dispatch recovery"); + trace.reenter = nullptr; + trace.returnReal = false; + Require(Serialize(other) == "\"format\"" && trace.calls == before + 2, + "The callback guard must be released after recovery"); + _ZN3sce4Json5ValueD1Ev(&root); + _ZN3sce4Json5ValueD1Ev(&leaf); + _ZN3sce4Json5ValueD1Ev(&other); + _ZN3sce4Json5ArrayD1Ev(&array); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate parent recovery"); +} +int main() { + for (int mode = 0; mode < 3; ++mode) CheckMode(mode); + CheckParentsAndReentry(); + return 0; +} diff --git a/core/libs/tests/GuestJsonStringAllocator.cpp b/core/libs/tests/GuestJsonStringAllocator.cpp new file mode 100644 index 0000000000..8317264bd8 --- /dev/null +++ b/core/libs/tests/GuestJsonStringAllocator.cpp @@ -0,0 +1,104 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct StringTrace { + std::map live; + std::vector requests; + int budget = -1; + int notifications = 0; +}; +static void* APS5_VABI AllocateString(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) return nullptr; + if (trace.budget > 0) --trace.budget; + void* memory = std::malloc(bytes); + Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI FreeString(void*, void* memory, void* context) { + if (!memory) return; + auto& trace = *static_cast(context); + Require(trace.live.erase(memory) == 1, "String must free through its owning allocation context"); std::free(memory); +} +static void APS5_VABI NotifyString(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "String failure notification must identify the failed request"); ++trace.notifications; +} +static void* const StringVtable[]{nullptr, nullptr, reinterpret_cast(AllocateString), reinterpret_cast(FreeString), reinterpret_cast(NotifyString)}; +struct StringAllocator { void* const* vtable = StringVtable; }; +static void CheckText(const String& text, const char* bytes, std::size_t length, std::size_t capacity) { + Require(_ZNK3sce4Json6String4sizeEv(&text) == length && _ZNK3sce4Json6String8capacityEv(&text) == capacity && + std::memcmp(_ZNK3sce4Json6String5c_strEv(&text), bytes, length + 1) == 0, "String bytes, length and capacity must match the reference"); +} +int main() { + Value beforeInitialization{}; + _ZN3sce4Json5ValueC1Ev(&beforeInitialization); + _ZNK3sce4Json5Value9getStringEv(&beforeInitialization); + _ZN3sce4Json5ValueD1Ev(&beforeInitialization); + StringTrace trace; + StringAllocator allocator; + Parameter parameter{&allocator, &trace, 16384, 2, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize string allocation contract"); + const auto baseline = trace.live.size(); + for (int budget : {0, 1}) { + trace.budget = budget; + String failed{}; _ZN3sce4Json6StringC1EPKc(&failed, "abc"); + Require(budget ? trace.live.count(failed.text) == 1 : failed.text == nullptr, "A header failure and a character failure retain distinct partial ownership"); + CheckText(failed, "", 0, 0); + trace.budget = -1; + _ZN3sce4Json6StringD1Ev(&failed); + Require(!failed.text && trace.live.size() == baseline, "Failed string construction must be destructible without leaking"); + } + String text{}; _ZN3sce4Json6StringC1EPKc(&text, "abc"); + Require(trace.live.at(text.text) == 24 && trace.live.at(*reinterpret_cast(text.text)) == 4, "Actual string storage must use native allocator-backed header and characters"); + for (unsigned i = 16; i < 24; ++i) Require(reinterpret_cast(text.text)[i] == 0xa5, "String construction must preserve reserved header bytes"); + CheckText(text, "abc", 3, 4); + trace.budget = 0; + _ZN3sce4Json6String7reserveEm(&text, 17); CheckText(text, "abc", 3, 4); + _ZN3sce4Json6String6resizeEm(&text, 18); CheckText(text, "abc", 3, 4); + trace.budget = -1; + _ZN3sce4Json6String7reserveEm(&text, 17); CheckText(text, "abc", 3, 17); + _ZN3sce4Json6String6resizeEm(&text, 18); + char grown[19]{}; std::memcpy(grown, "abc", 3); CheckText(text, grown, 18, 25); + _ZN3sce4Json6String6resizeEm(&text, 2); CheckText(text, "ab", 2, 25); + _ZN3sce4Json6String5clearEv(&text); CheckText(text, "", 0, 0); + const char binary[]{'a', 0, 'b', 0}; + _ZN3sce4Json6String6appendEPKcm(&text, binary, 3); CheckText(text, binary, 3, 20); + String copy{}; _ZN3sce4Json6StringC1ERKS1_(©, &text); CheckText(copy, binary, 3, 4); + String appended{}; _ZN3sce4Json6StringC1EPKc(&appended, nullptr); + _ZN3sce4Json6String6appendERKS1_(&appended, &text); CheckText(appended, "a", 1, 18); + _ZN3sce4Json6StringpLEh(&appended, 0); CheckText(appended, "a", 1, 18); + _ZN3sce4Json6StringaSERKS1_(&appended, &text); CheckText(appended, binary, 3, 4); + trace.budget = 0; + _ZN3sce4Json6StringaSERKS1_(&appended, ©); CheckText(appended, "", 0, 0); + trace.budget = -1; + Require(trace.notifications == 5, "Each header, character, growth and assignment failure must notify once"); + _ZN3sce4Json6StringD1Ev(&appended); _ZN3sce4Json6StringD1Ev(©); _ZN3sce4Json6StringD1Ev(&text); + Require(trace.live.size() == baseline, "String operations must release all owned resources"); + Value null{}; _ZN3sce4Json5ValueC1Ev(&null); + const auto* defaultString = _ZNK3sce4Json5Value9getStringEv(&null); + Require(defaultString && defaultString->text && trace.live.at(defaultString->text) == 24, "The default string header must belong to the active allocator"); + _ZN3sce4Json5ValueD1Ev(&null); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Terminate string allocation contract"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Reinitialize default string ownership"); + _ZN3sce4Json5ValueC1Ev(&null); + defaultString = _ZNK3sce4Json5Value9getStringEv(&null); + Require(defaultString && defaultString->text && trace.live.at(defaultString->text) == 24, "A new initialization must own its own default string header"); + _ZN3sce4Json5ValueD1Ev(&null); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Release defaults from repeated initialization"); + return 0; +} diff --git a/core/libs/tests/GuestJsonStringSearch.cpp b/core/libs/tests/GuestJsonStringSearch.cpp new file mode 100644 index 0000000000..fb3192967c --- /dev/null +++ b/core/libs/tests/GuestJsonStringSearch.cpp @@ -0,0 +1,51 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + + +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +int main() { + constexpr auto none = std::numeric_limits::max(); + char bytes[]{'a', 'b', 'X', '\0', 'X', static_cast(0xff), '\0'}; + Text header{bytes, 6, 7, UINT64_C(0x123456789abcdef0)}; + String text{&header}, missing{}; + char needleBytes[]{'X', '\0', 'q', '\0'}; + Text needleHeader{needleBytes, 3, 4, 0}; + String needle{&needleHeader}; + const auto before = header; + char snapshot[sizeof(bytes)]; std::memcpy(snapshot, bytes, sizeof(bytes)); + for (std::size_t i = 0; i < sizeof(bytes); ++i) { + Require(_ZNK3sce4Json6String2atEm(&text, i) == bytes[i], "String at must return the indexed byte or zero at the boundary"); + } + Require(_ZNK3sce4Json6String2atEm(&text, none) == 0 && _ZNK3sce4Json6String2atEm(&missing, 0) == 0, "Out-of-range and missing-storage access must return zero"); + Require(_ZNK3sce4Json6String4findEPKcm(&text, "X", 0) == 2 && _ZNK3sce4Json6String4findEPKcm(&text, "X", 3) == none && _ZNK3sce4Json6String4findEPKcm(&text, "X", 4) == 4, "Forward searches must stop at an embedded zero and honor the starting position"); + Require(_ZNK3sce4Json6String5rfindEPKcm(&text, "X", none) == 4 && _ZNK3sce4Json6String5rfindEPKcm(&text, "X", 3) == 2, "Reverse searches must scan bytes across embedded zeros"); + Require(_ZNK3sce4Json6String4findERKS1_m(&text, &needle, 0) == 2 && _ZNK3sce4Json6String5rfindERKS1_m(&text, &needle, none) == 4, "String needles must use their C-string prefix rather than their stored length"); + Require(_ZNK3sce4Json6String4findEPKcmm(&text, needleBytes, 0, 3) == 2 && _ZNK3sce4Json6String5rfindEPKcmm(&text, needleBytes, none, 3) == 2, "Counted matching must stop comparing at a shared zero"); + for (std::size_t position : {std::size_t{0}, std::size_t{6}, std::size_t{7}, none}) { + const auto forward = position <= 6 ? position : none; + const auto reverse = position < 6 ? position : 6; + Require(_ZNK3sce4Json6String4findEPKcm(&text, nullptr, position) == forward && _ZNK3sce4Json6String4findEPKcmm(&text, nullptr, position, 0) == forward && _ZNK3sce4Json6String4findERKS1_m(&text, &missing, position) == forward, "Empty forward needles must validate the starting position"); + Require(_ZNK3sce4Json6String5rfindEPKcm(&text, nullptr, position) == reverse && _ZNK3sce4Json6String5rfindEPKcmm(&text, nullptr, position, 0) == reverse && _ZNK3sce4Json6String5rfindERKS1_m(&text, &missing, position) == reverse, "Empty reverse needles must clamp the starting position"); + } + Require(_ZNK3sce4Json6String4findEPKcm(&missing, "", 0) == none && _ZNK3sce4Json6String5rfindEPKcm(&missing, "", 0) == none, "Missing haystack storage must reject even empty needles"); + Require(_ZNK3sce4Json6String4findEPKcmm(&text, nullptr, 0, none) == none && _ZNK3sce4Json6String5rfindEPKcmm(&text, nullptr, none, none) == none, "Oversized counts must be rejected before touching the needle"); + Require(_ZNK3sce4Json6String4findEcm(&text, '\0', 0) == 3 && _ZNK3sce4Json6String4findEcm(&text, 'X', 3) == none && _ZNK3sce4Json6String5rfindEcm(&text, '\0', none) == 3, "Character search must preserve forward C-string and reverse byte semantics"); + Require(_ZNK3sce4Json6String4findEcm(&text, static_cast(0xff), 4) == 5 && _ZNK3sce4Json6String5rfindEcm(&text, static_cast(0xff), none) == 5, "Character search must preserve high-bit byte values"); + char tailBytes[]{'a', 'b', 'X', '\0'}; + Text tailHeader{tailBytes, 3, 4, 0}; String tail{&tailHeader}; + Require(_ZNK3sce4Json6String4findEPKcmm(&tail, "X", 0, 2) == 2, "Forward candidates must retain native zero-terminated comparison beyond the initial length check"); + Require(_ZNK3sce4Json6String4findEcm(&tail, '\0', 0) == 3 && _ZNK3sce4Json6String4findEcm(&tail, '\0', 3) == none && _ZNK3sce4Json6String5rfindEcm(&tail, '\0', none) == none, "Only forward character search may include the trailing terminator after an admitted start"); + Require(std::memcmp(&header, &before, sizeof(header)) == 0 && std::memcmp(bytes, snapshot, sizeof(bytes)) == 0 && text.text == &header, "Read-only string operations must preserve all storage and metadata"); + return 0; +} diff --git a/core/libs/tests/GuestJsonTerminationExceptions.cpp b/core/libs/tests/GuestJsonTerminationExceptions.cpp new file mode 100644 index 0000000000..febac8488b --- /dev/null +++ b/core/libs/tests/GuestJsonTerminationExceptions.cpp @@ -0,0 +1,122 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +#if defined(JSON2_TERMINATION_LEGACY_SDK) +static constexpr bool Legacy = true; +extern "C" const void* ApplicationProcessParameters_nid_no_patch() { + static const std::uint32_t words[]{0, 0, 0, 0, 0x05000000}; + return words; +} +#else +static constexpr bool Legacy = false; +#endif +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct TerminationMarker { const void* identity; }; +struct TerminationTrace { + void* allocator = nullptr; + unsigned target = 0; + bool after = false; + std::vector allocations; + std::map live; + std::vector releases; +}; +static void* APS5_VABI AllocateTerminationTracked(void* self, std::size_t size, void* data) { + auto& trace = *static_cast(data); + Require(self == trace.allocator, "Initialization must preserve the allocator receiver and context"); + void* memory = std::malloc(size); + Require(memory && trace.live.emplace(memory, trace.allocations.size() + 1).second, "Each initialization resource must have an independent live identity"); + std::memset(memory, 0xa5, size); + trace.allocations.push_back(memory); + return memory; +} +static void APS5_VABI ReleaseTerminationTracked(void* self, void* memory, void* data) { + auto& trace = *static_cast(data); + Require(self == trace.allocator && (!memory || trace.live.contains(memory)), "Teardown must preserve allocator identity and release only live resources"); + trace.releases.push_back(memory ? trace.live.at(memory) : 0); + if (trace.releases.size() == trace.target && !trace.after) throw TerminationMarker{&trace}; + if (memory) { trace.live.erase(memory); std::free(memory); } + if (trace.releases.size() == trace.target && trace.after) throw TerminationMarker{&trace}; +} +static void APS5_VABI NotifyTerminationTracked(void*, int, std::size_t, void*) { Require(false, "Release exceptions must not fabricate allocation errors"); } +static void* const Table[]{nullptr, nullptr, reinterpret_cast(AllocateTerminationTracked), reinterpret_cast(ReleaseTerminationTracked), reinterpret_cast(NotifyTerminationTracked)}; +struct TerminationAllocator { void* const* table = Table; }; +int main(int argc, char** argv) { + Require(argc == 3 || argc == 4, "Every interrupted teardown requires an independent module process"); + TerminationAllocator allocator; TerminationTrace trace; trace.allocator = &allocator; + trace.target = static_cast(std::strtoul(argv[1], nullptr, 10)); trace.after = std::strcmp(argv[2], "after") == 0; + Require(trace.target <= 12 && (trace.after || std::strcmp(argv[2], "before") == 0), "The schedule must identify a release boundary"); + Parameter parameter{&allocator, &trace, 16384, 0, {}}; + std::uint8_t owner = 0; _ZN3sce4Json11InitializerC1Ev(&owner); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == 0 && owner && trace.live.size() == (Legacy ? 11u : 12u), "Initialize complete module ownership before release failure"); + const auto baseline = trace.live; + bool caught = false; + try { Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0, "Uninterrupted teardown must complete"); } + catch (const TerminationMarker& marker) { Require(marker.identity == &trace, "Free callback exceptions must preserve their identity"); caught = true; } + const std::array order = Legacy ? std::array{0, 10, 11, 3, 2, 6, 5, 4, 9, 8, 7, 1} + : std::array{10, 12, 11, 3, 2, 6, 5, 4, 9, 8, 7, 1}; + const unsigned calls = trace.target ? trace.target : 12; + Require(caught == bool(trace.target) && owner == unsigned(caught) && trace.releases == std::vector(order.begin(), order.begin() + calls), + "Interrupted teardown must retain the active owner and exact release-generation prefix"); + auto expected = baseline; + const unsigned completed = caught ? calls - unsigned(!trace.after) : calls; + for (unsigned i = 0; i < completed; ++i) if (order[i]) expected.erase(trace.allocations[order[i] - 1]); + Require(trace.live == expected, "Only frees completed before the exception may change resource ownership"); + const auto releases = trace.releases; + std::uint8_t other = 0; _ZN3sce4Json11InitializerC1Ev(&other); + if (caught) { + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&owner, reinterpret_cast(¶meter)) == static_cast(0x80848111) && + _ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&other, reinterpret_cast(¶meter)) == static_cast(0x80848111) && + !other && trace.live == expected && trace.releases == releases, + "Both owners must reject initialization after incomplete teardown without further allocator effects"); + if (argc == 4) { + Require(std::strcmp(argv[3], "retry") == 0, "The additional schedule must request a returning reference retry"); + std::vector tail; + switch (trace.target) { + case 1: Require(!trace.after || Legacy, "Only the legacy null-free after-throw retry returns"); tail.assign(order.begin(), order.end()); break; + case 2: Require(Legacy && !trace.after, "Only the legacy fallback before-throw retry returns"); tail.assign(order.begin(), order.end()); break; + case 5: tail = {0, 0, 0, 2, 6, 5, 4, 9, 8, 7, 1}; break; + case 6: tail = {0, 0, 0, 0, 6, 5, 4, 9, 8, 7, 1}; break; + case 8: tail = {0, 0, 0, 0, 4, 9, 8, 7, 1}; break; + case 9: tail = {0, 0, 0, 0, 0, 9, 8, 7, 1}; break; + case 11: tail = {0, 0, 0, 0, 0, 7, 1}; break; + case 12: tail = {0, 0, 0, 0, 0, 0, 1}; break; + default: Require(false, "This retry has no returning reference contract"); + } + Require(!trace.after || (Legacy && trace.target == 1), "Returning resource retries must precede the rejected release"); + auto expectedRetry = releases; expectedRetry.insert(expectedRetry.end(), tail.begin(), tail.end()); + trace.target = 0; + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == 0 && !owner && trace.live.empty() && trace.releases == expectedRetry, + "Returning teardown retry must reclaim the retained resources in exact reference order including null frees"); + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == static_cast(0x80848110) && trace.releases == expectedRetry, + "Completed retry must not repeat any release"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&other, reinterpret_cast(¶meter)) == 0 && other, + "Completed retry must permit a fresh owner generation"); + Require(_ZN3sce4Json11Initializer9terminateEv(&other) == 0 && !other && trace.live.empty(), "Fresh ownership must retain complete cleanup after interrupted retry"); + return 0; + } + if (!Legacy && trace.target < 2) Require(scePthreadMutexDestroy(reinterpret_cast(trace.allocations[11])) == 0, "The fixture must separately reclaim the untouched recovery mutex"); + if (trace.target < 3) Require(scePthreadMutexDestroy(reinterpret_cast(trace.allocations[10])) == 0, "The fixture must separately reclaim the untouched internal mutex"); + for (const auto& [pointer, generation] : trace.live) std::free(pointer); + trace.live.clear(); + } else { + Require(_ZN3sce4Json11Initializer9terminateEv(&owner) == static_cast(0x80848110) && trace.releases == releases, "Completed teardown must remain inactive without repeated frees"); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&other, reinterpret_cast(¶meter)) == 0 && other, "Completed teardown must permit a new module generation"); + Require(_ZN3sce4Json11Initializer9terminateEv(&other) == 0 && !other && trace.live.empty(), "A fresh generation must retain complete cleanup"); + } + return 0; +} diff --git a/core/libs/tests/GuestJsonValueAllocator.cpp b/core/libs/tests/GuestJsonValueAllocator.cpp new file mode 100644 index 0000000000..d895ab9b43 --- /dev/null +++ b/core/libs/tests/GuestJsonValueAllocator.cpp @@ -0,0 +1,609 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; + +using NullCallback = const Value& (APS5_VABI*)(ValueType, const Value*, void*); +using MismatchCallback = const Value& (APS5_VABI*)(ValueType, const Value*, const Value*, void*); +using FloatCallback = Value* (APS5_VABI*)(Value*, std::int32_t, double, const Value*, void*, bool*); +using ElementCallback = void (APS5_VABI*)(int, const String*, const std::size_t*, const Value*, const Value*, void*); +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +struct ValueAllocationTrace { + std::map live; + std::vector requests; + std::vector frees; + int budget = -1; + bool once = false; + unsigned notifications = 0; + unsigned nullFrees = 0; + const Value* registering = nullptr; +}; +static void* APS5_VABI ValueAllocationAllocate(void*, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); trace.requests.push_back(bytes); + if (trace.budget == 0) { if (trace.once) trace.budget = -1; return nullptr; } + if (trace.budget > 0) --trace.budget; + auto* memory = std::malloc(bytes); Require(memory != nullptr, "Tracking allocator requires host memory"); + std::memset(memory, 0xa5, bytes); trace.live.emplace(memory, bytes); return memory; +} +static void APS5_VABI ValueAllocationFree(void*, void* memory, void* context) { + if (!memory) { ++static_cast(context)->nullFrees; return; } + Require(static_cast(context)->live.erase(memory) == 1, "Value resources must return through their owning allocator"); + static_cast(context)->frees.push_back(memory); + std::free(memory); +} +static void APS5_VABI ValueAllocationNotify(void*, int error, std::size_t bytes, void* context) { + auto& trace = *static_cast(context); + Require(static_cast(error) == 0x80848102 && trace.requests.back() == bytes, "Value failure notification must identify its allocation"); + if (trace.registering && bytes != 16) Require(!trace.registering->auxiliary, "Auxiliary admission must remain unpublished during allocation failure notification"); + ++trace.notifications; +} +static void* const ValueAllocationVtable[]{nullptr, nullptr, reinterpret_cast(ValueAllocationAllocate), reinterpret_cast(ValueAllocationFree), reinterpret_cast(ValueAllocationNotify)}; +struct ValueAllocationAllocator { void* const* vtable = ValueAllocationVtable; }; +static bool Owned(const ValueAllocationTrace& trace, const void* pointer, std::size_t bytes) { + const auto found = trace.live.find(const_cast(pointer)); return found != trace.live.end() && found->second == bytes; +} +static void Check(const Value& value, int expected, const ValueAllocationTrace& trace) { + Require(value.type == expected && value.reserved == 0xa5a5a5a5, "Value type admission must preserve inactive metadata"); + if (expected < 5) return; + const auto* handle = reinterpret_cast(value.payload); + Require(Owned(trace, handle, 8) && Owned(trace, *handle, expected == 5 ? 24 : 32), "Complex values must own an actual allocator-backed handle and header"); + if (expected != 5) { + Value* owner = nullptr; std::memcpy(&owner, reinterpret_cast(*handle) + 24, sizeof(owner)); + Require(owner == &value, "Collection headers must bind to their enclosing value"); + } +} +static const Value& APS5_VABI NullRecovery(ValueType, const Value* value, void*) { return *value; } +static const Value& APS5_VABI MismatchRecovery(ValueType, const Value* value, const Value*, void*) { return *value; } +static Value* APS5_VABI FloatRecovery(Value* value, std::int32_t, double, const Value*, void*, bool*) { return value; } +static void APS5_VABI ElementRecovery(int, const String*, const std::size_t*, const Value*, const Value*, void*) {} +static int Register(Value& value, unsigned kind, void* context, bool empty = false) { + switch (kind) { + case 0: return _ZN3sce4Json5Value21setNullAccessCallBackEPFRKS1_NS0_9ValueTypeEPS2_PvES6_(&value, empty ? nullptr : NullRecovery, context); + case 1: return _ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(&value, empty ? nullptr : MismatchRecovery, context); + case 2: return _ZN3sce4Json5Value22setSpecialFloatHandlerEPFKS1_NS0_12FunctionTypeEdPS2_PvPbES5_(&value, empty ? nullptr : FloatRecovery, context); + default: return _ZN3sce4Json5Value30setElementAccessFailureHandlerEPFviPKNS0_6StringEPKmPKS1_S8_PvES9_(&value, empty ? nullptr : ElementRecovery, context); + } +} +static void CheckMetadata(ValueAllocationTrace& trace) { + const auto baseline = trace.live.size(); + for (unsigned kind = 0; kind < 4; ++kind) for (int budget = 0; budget <= 3; ++budget) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast(0)); + const auto payload = value.payload; + const auto notifications = trace.notifications; + trace.budget = budget; + trace.registering = &value; + const auto status = Register(value, kind, &trace); + trace.registering = nullptr; + trace.budget = -1; + const bool admitted = budget >= (kind == 0 ? 2 : 3); + Require(static_cast(status) == (admitted ? 0u : 0x80848102u) && trace.notifications - notifications == (admitted ? 0u : 1u), "Callback metadata admission must return and notify its actual allocation failure"); + Require(value.payload == payload && value.reserved == 0xa5a5a5a5 && value.type == 0, "Callback registration must preserve the value payload"); + Require((value.auxiliary != nullptr) == (budget >= 1), "Failed fallback allocation must retain its auxiliary record"); + if (value.auxiliary) { + Require(Owned(trace, value.auxiliary, 72), "Modern auxiliary records must use the supplied allocator"); + auto* slots = reinterpret_cast(value.auxiliary); + Require((slots[2] != nullptr) == (budget >= 2), "Fallback allocation failure must preserve a null fallback pointer"); + if (slots[2]) { + auto* fallback = reinterpret_cast(slots[2]); + Require(Owned(trace, fallback, 32) && fallback->parent == &value && !fallback->auxiliary && fallback->type == 0 && fallback->payload == UINT64_C(0xa5a5a5a5a5a5a5a5), "Fallback values must retain native ownership and inactive payload bytes"); + } + for (unsigned index = 6; index < 9; ++index) Require(!slots[index], "Auxiliary reserved fields must initialize to zero"); + if (kind && admitted) Require(Owned(trace, slots[kind + 2], 16), "Callback records must own their actual sixteen-byte allocation"); + } + auto* auxiliary = value.auxiliary; + auto* fallback = auxiliary ? reinterpret_cast(auxiliary)[2] : nullptr; + const auto requests = trace.requests.size(); + Require(Register(value, kind, &value) == 0, "Callback registration must remain usable after allocation failure"); + Require(!auxiliary || (static_cast(value.auxiliary) == auxiliary && reinterpret_cast(auxiliary)[2] == fallback), "Registration retry must reuse partial auxiliary state without recreating a missing fallback"); + if (admitted) Require(trace.requests.size() == requests, "Replacing an existing callback must not allocate"); + auto* slots = reinterpret_cast(value.auxiliary); + auto* record = kind ? reinterpret_cast(slots[kind + 2]) : slots; + Require(record[0] && record[1] == &value, "Registration must retain its callback and replacement context"); + const auto retained = trace.requests.size(); + Require(Register(value, kind, &trace, true) == 0 && !record[0] && record[1] == &trace && trace.requests.size() == retained, "A null callback must replace its record without deleting or reallocating metadata"); + _ZN3sce4Json5Value3setENS0_9ValueTypeE(&value, static_cast(2)); + Require(static_cast(value.auxiliary) == slots, "Payload type changes must preserve root callback metadata"); + Value parent{}; value.parent = &parent; + Require(static_cast(Register(value, kind, &trace)) == 0x80848104 && trace.requests.size() == retained, "Owned children must reject callback registration without allocating"); + value.parent = nullptr; + std::vector expectedFrees; + if (kind) expectedFrees.push_back(record); + if (slots[2]) expectedFrees.push_back(slots[2]); + expectedFrees.push_back(slots); + const auto freed = trace.frees.size(); + const auto nullFrees = trace.nullFrees; + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.nullFrees == nullFrees, "Metadata destruction must not free absent callback records"); + Require(trace.frees.size() == freed + expectedFrees.size() && std::equal(expectedFrees.begin(), expectedFrees.end(), trace.frees.begin() + freed), "Metadata teardown must release callback records, fallback and auxiliary in ownership order"); + Require(trace.live.size() == baseline, "Callback metadata destruction must release all admitted and partial resources"); + } +} +static void CheckScalarCopies(ValueAllocationTrace& trace) { + const auto baseline = trace.live.size(); + constexpr const char* text = "a string requiring independently allocated storage"; + for (int previous = -1; previous <= 7; ++previous) for (int type = 0; type <= 5; ++type) for (int budget = 0; budget <= 3; ++budget) { + Value source; std::memset(&source, 0xa5, sizeof(source)); + if (type == 5) _ZN3sce4Json5ValueC1EPKc(&source, text); + else { + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&source, static_cast(type)); + if (type == 1) source.payload = UINT64_C(0xa5a5a5a5a5a5a501); + if (type == 2) source.payload = 42; + if (type == 3) source.payload = UINT64_C(0xfedcba9876543210); + if (type == 4) source.payload = UINT64_C(0x3fe8000000000000); + } + Value destination; std::memset(&destination, 0xa5, sizeof(destination)); + void* auxiliary = nullptr; + std::uint64_t payload = destination.payload; + void* header = nullptr; + if (previous >= 0) { + if (previous == 5) _ZN3sce4Json5ValueC1EPKc(&destination, "previous"); + else _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&destination, static_cast(previous)); + Require(Register(destination, 0, &trace, true) == 0, "Prepare assignment metadata"); + auxiliary = destination.auxiliary; + payload = destination.payload; + if (previous == 5) header = *reinterpret_cast(payload); + } + const auto requests = trace.requests.size(); + trace.budget = budget; + if (previous < 0) _ZN3sce4Json5ValueC1ERKS1_(&destination, &source); + else Require(_ZN3sce4Json5ValueaSERKS1_(&destination, &source) == &destination, "Value assignment must return its destination"); + trace.budget = -1; + const auto expected = type == 5 && previous != 5 && budget < 2 ? 0 : type; + Check(destination, expected, trace); + Require(!destination.parent && destination.auxiliary == auxiliary, "Copy and assignment must preserve their metadata contract"); + if (type == 0) Require(destination.payload == (previous >= 5 ? 0 : payload), "Null copies must preserve inactive scalar payload bytes"); + else if (type == 1) Require(destination.payload == (((previous >= 5 ? 0 : payload) & ~UINT64_C(255)) | 1), "Boolean copies must preserve inactive destination bytes"); + else if (type < 5) Require(destination.payload == source.payload, "Numeric copies must retain the exact active payload"); + else if (!expected) Require(destination.payload == 0, "Failed string admission must leave a null payload pointer"); + else { + const auto* stored = *reinterpret_cast(destination.payload); + const auto* characters = *reinterpret_cast(stored); + const bool complete = budget >= (previous == 5 ? 1 : 3); + Require(std::strcmp(characters, complete ? text : "") == 0, "String copy failure must retain its admitted empty header"); + if (previous == 5) Require(stored == header, "String assignment must reuse the existing header"); + } + if (type < 5) Require(trace.requests.size() == requests, "Scalar copies must not allocate"); + if (type == 5) { + const auto* stored = *reinterpret_cast(source.payload); + Require(std::strcmp(*reinterpret_cast(stored), text) == 0, "String copy failures must preserve the source"); + } + _ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source); + Require(trace.live.size() == baseline, "Copy failures must reclaim destination and source resources"); + } +} +static void CheckCollectionCopies(ValueAllocationTrace& trace) { + const auto baseline = trace.live.size(); + for (bool specialized : {false, true}) for (int previous = -1; previous <= 7; ++previous) for (int type : {6, 7}) for (int budget = 0; budget <= 5; ++budget) { + Value source; std::memset(&source, 0xa5, sizeof(source)); + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&source, static_cast(type)); + const auto sourcePayload = source.payload; + Value destination; std::memset(&destination, 0xa5, sizeof(destination)); + void* auxiliary = nullptr; + void* originalHeader = nullptr; + if (previous >= 0) { + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&destination, static_cast(previous)); + Require(Register(destination, 0, &trace, true) == 0, "Prepare collection assignment metadata"); + auxiliary = destination.auxiliary; + if (previous == type) originalHeader = *reinterpret_cast(destination.payload); + } + const auto notifications = trace.notifications; + trace.budget = budget; + if (specialized && type == 6) { + const auto* array = reinterpret_cast(source.payload); + if (previous < 0) _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&destination, array); + else _ZN3sce4Json5Value3setERKNS0_5ArrayE(&destination, array); + } else if (specialized) { + const auto* object = reinterpret_cast(source.payload); + if (previous < 0) _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&destination, object); + else _ZN3sce4Json5Value3setERKNS0_6ObjectE(&destination, object); + } else if (previous < 0) _ZN3sce4Json5ValueC1ERKS1_(&destination, &source); + else _ZN3sce4Json5ValueaSERKS1_(&destination, &source); + trace.budget = -1; + const auto threshold = type == 6 ? 2 : 3; + const auto expected = previous == type || budget >= threshold ? type : 0; + Check(destination, expected, trace); + Require(!destination.parent && destination.auxiliary == auxiliary, "Collection copies must preserve destination metadata"); + if (!expected) Require(destination.payload == 0, "Failed collection admission must leave a null handle"); + else { + auto* header = *reinterpret_cast(destination.payload); + const auto* words = reinterpret_cast(header); + Require(words[1] == 0 && (words[0] != 0) == !(type == 6 && previous != type && budget == 2), "Collection assignment must preserve the reference partial-header state"); + if (previous == type) Require((header == originalHeader) == (budget < 2), "Same-type assignment must preserve the header only when replacement admission fails"); + } + const auto expectedNotifications = budget == 5 || (previous == type && budget >= 2) ? 0u : type == 6 && previous != type && budget == 2 ? 2u : 1u; + Require(trace.notifications - notifications == expectedNotifications, "Collection copies must notify both type-admission and replacement allocation failures"); + Require(source.payload == sourcePayload && source.type == type, "Collection assignment must preserve source ownership"); + _ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source); + Require(trace.live.size() == baseline, "Collection copies must reclaim partial handles and replaced storage"); + } +} +static void CheckMissingCollections(ValueAllocationTrace& trace) { + const auto baseline = trace.live.size(); + const Array array{}; const Object object{}; + for (int previous = -1; previous <= 7; ++previous) for (bool useArray : {false, true}) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + if (previous >= 0) { + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast(previous)); + Require(Register(value, 0, &trace, true) == 0, "Prepare missing-source collection metadata"); + } + const auto original = value; + const auto requests = trace.requests.size(); + const auto notifications = trace.notifications; + trace.budget = 0; + if (previous < 0) { + if (useArray) _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&value, &array); + else _ZN3sce4Json5ValueC1ERKNS0_6ObjectE(&value, &object); + } else { + if (useArray) _ZN3sce4Json5Value3setERKNS0_5ArrayE(&value, &array); + else _ZN3sce4Json5Value3setERKNS0_6ObjectE(&value, &object); + } + trace.budget = -1; + Require(trace.requests.size() == requests && trace.notifications == notifications, "A missing input header must not allocate or notify"); + if (previous >= 0) Require(std::memcmp(&value, &original, sizeof(value)) == 0, "A missing input header must preserve the entire destination"); + else Require(value.type == 0 && !value.parent && !value.auxiliary && value.payload == original.payload && value.reserved == original.reserved, "Construction from a missing header must initialize null metadata and preserve inactive bytes"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Missing collection inputs must not leak ownership"); + } + for (int type : {6, 7}) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast(type)); + Require(Register(value, 0, &trace, true) == 0, "Prepare collection self-setting metadata"); + const auto handle = value.payload; + const auto* header = *reinterpret_cast(handle); + auto* auxiliary = value.auxiliary; + if (type == 6) _ZN3sce4Json5Value3setERKNS0_5ArrayE(&value, reinterpret_cast(handle)); + else _ZN3sce4Json5Value3setERKNS0_6ObjectE(&value, reinterpret_cast(handle)); + Check(value, type, trace); + Require(value.payload == handle && *reinterpret_cast(handle) != header && static_cast(value.auxiliary) == auxiliary, "Collection self-setting must replace storage while retaining the handle and callback metadata"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Collection self-setting must release replaced storage"); + } +} +static void PrepareNestedSource(Value& value) { + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast(6)); + auto* array = reinterpret_cast(value.payload); + Value item{}; + _ZN3sce4Json5ValueC1EPKc(&item, "abc"); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(array, &item); + _ZN3sce4Json5ValueD1Ev(&item); + _ZN3sce4Json5ValueC1El(&item, 42); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(array, &item); + _ZN3sce4Json5ValueD1Ev(&item); + Value nested{}; + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&nested, static_cast(6)); + _ZN3sce4Json5ValueC1EPKc(&item, "def"); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(reinterpret_cast(nested.payload), &item); + _ZN3sce4Json5ValueD1Ev(&item); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(array, &nested); + _ZN3sce4Json5ValueD1Ev(&nested); +} +static std::string NestedState(const Value& value, const Value* parent = nullptr) { + std::string state = std::to_string(value.type) + (value.parent == parent ? "p" : value.parent ? "x" : "-"); + if (value.type == 0) return state; + if (value.type == 2) return state + std::to_string(value.payload); + if (value.type != 5 && value.type != 6) { Require(false, "Unexpected nested test value type"); return {}; } + if (!value.payload) return state + "!"; + const auto* header = *reinterpret_cast(value.payload); + if (!header) return state + "?"; + if (value.type == 5) return state + ":" + *reinterpret_cast(header); + const auto* words = reinterpret_cast(header); + state += words[3] == reinterpret_cast(&value) ? "o" : "!"; + state += std::to_string(words[1]) + "["; + if (!words[0]) return state + "?]"; + const auto* head = reinterpret_cast(words[0]); + auto* node = reinterpret_cast(head[1]); + std::size_t count = 0; + const auto* previous = head; + while (node != head) { + Require(node[0] == reinterpret_cast(previous), "Nested array predecessor links must preserve ownership order"); + Require(++count <= words[1], "Nested array nodes must close at their sentinel"); + state += NestedState(*reinterpret_cast(node + 2), &value) + ";"; + previous = node; + node = reinterpret_cast(node[1]); + } + Require(head[0] == reinterpret_cast(previous), "Nested array sentinel must retain its last live entry"); + Require(count == words[1], "Nested array count must match its live links"); + return state + "]"; +} +static void CheckNestedCopies(ValueAllocationTrace& trace) { + struct Case { bool once; int budget; unsigned notifications; std::size_t requests; const char* state; }; + const Case cases[]{ + {0, 0, 1, 1, "0p"}, + {0, 1, 1, 2, "0p"}, + {0, 2, 2, 4, "6po0[?]"}, + {0, 3, 1, 4, "6po0[]"}, + {0, 4, 1, 5, "6po0[]"}, + {0, 5, 5, 10, "6po0[]"}, + {0, 6, 5, 11, "6po0[]"}, + {0, 7, 4, 11, "6po0[]"}, + {0, 8, 4, 12, "6po1[0-;]"}, + {0, 9, 4, 13, "6po1[0-;]"}, + {0, 10, 4, 14, "6po1[5p:;]"}, + {0, 11, 3, 14, "6po1[5p:abc;]"}, + {0, 12, 2, 14, "6po2[5p:abc;2p42;]"}, + {0, 13, 2, 15, "6po2[5p:abc;2p42;]"}, + {0, 14, 2, 16, "6po2[5p:abc;2p42;]"}, + {0, 15, 1, 16, "6po2[5p:abc;2p42;]"}, + {0, 16, 1, 17, "6po3[5p:abc;2p42;0-;]"}, + {0, 17, 1, 18, "6po3[5p:abc;2p42;0-;]"}, + {0, 18, 2, 20, "6po3[5p:abc;2p42;6-o0[?];]"}, + {0, 19, 1, 20, "6po3[5p:abc;2p42;6-o0[];]"}, + {0, 20, 1, 21, "6po3[5p:abc;2p42;6-o0[];]"}, + {0, 21, 1, 22, "6po3[5p:abc;2p42;6po0[];]"}, + {0, 22, 1, 23, "6po3[5p:abc;2p42;6po0[];]"}, + {0, 23, 2, 25, "6po3[5p:abc;2p42;6po0[];]"}, + {0, 24, 2, 26, "6po3[5p:abc;2p42;6po0[];]"}, + {0, 25, 1, 26, "6po3[5p:abc;2p42;6po0[];]"}, + {0, 26, 1, 27, "6po3[5p:abc;2p42;6po1[0-;];]"}, + {0, 27, 1, 28, "6po3[5p:abc;2p42;6po1[0-;];]"}, + {0, 28, 1, 29, "6po3[5p:abc;2p42;6po1[5p:;];]"}, + {0, 29, 0, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + {1, 0, 1, 1, "0p"}, + {1, 1, 1, 2, "0p"}, + {1, 2, 1, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + {1, 3, 1, 4, "6po0[]"}, + {1, 4, 1, 5, "6po0[]"}, + {1, 5, 1, 28, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + {1, 6, 1, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + {1, 7, 1, 26, "6po2[2p42;6po1[5p:def;];]"}, + {1, 8, 1, 27, "6po3[0-;2p42;6po1[5p:def;];]"}, + {1, 9, 1, 28, "6po3[0-;2p42;6po1[5p:def;];]"}, + {1, 10, 1, 29, "6po3[5p:;2p42;6po1[5p:def;];]"}, + {1, 11, 1, 29, "6po2[5p:abc;6po1[5p:def;];]"}, + {1, 12, 1, 25, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + {1, 13, 1, 26, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + {1, 14, 1, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + {1, 15, 1, 16, "6po2[5p:abc;2p42;]"}, + {1, 16, 1, 17, "6po3[5p:abc;2p42;0-;]"}, + {1, 17, 1, 18, "6po3[5p:abc;2p42;0-;]"}, + {1, 18, 1, 21, "6po3[5p:abc;2p42;6-o0[];]"}, + {1, 19, 1, 20, "6po3[5p:abc;2p42;6-o0[];]"}, + {1, 20, 1, 21, "6po3[5p:abc;2p42;6-o0[];]"}, + {1, 21, 1, 22, "6po3[5p:abc;2p42;6po0[];]"}, + {1, 22, 1, 23, "6po3[5p:abc;2p42;6po0[];]"}, + {1, 23, 1, 28, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + {1, 24, 1, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + {1, 25, 1, 26, "6po3[5p:abc;2p42;6po0[];]"}, + {1, 26, 1, 27, "6po3[5p:abc;2p42;6po1[0-;];]"}, + {1, 27, 1, 28, "6po3[5p:abc;2p42;6po1[0-;];]"}, + {1, 28, 1, 29, "6po3[5p:abc;2p42;6po1[5p:;];]"}, + {1, 29, 0, 29, "6po3[5p:abc;2p42;6po1[5p:def;];]"}, + }; + const auto baseline = trace.live.size(); + for (bool constructor : {false, true}) for (const auto& test : cases) { + Value source{}; PrepareNestedSource(source); + const auto sourceState = NestedState(source); + Value destination{}; + if (!constructor) _ZN3sce4Json5ValueC1EPKc(&destination, "previous"); + const auto requests = trace.requests.size(); + const auto notifications = trace.notifications; + trace.budget = test.budget; trace.once = test.once; + if (constructor) _ZN3sce4Json5ValueC1ERKS1_(&destination, &source); + else _ZN3sce4Json5ValueaSERKS1_(&destination, &source); + trace.budget = -1; trace.once = false; + const auto state = NestedState(destination); + const bool matches = state == test.state && trace.requests.size() - requests == test.requests && trace.notifications - notifications == test.notifications; + if (!matches) std::fprintf(stderr, "Nested copy constructor=%d once=%d budget=%d expected=%s actual=%s requests=%zu notifications=%u\n", constructor, test.once, test.budget, test.state, state.c_str(), trace.requests.size() - requests, trace.notifications - notifications); + Require(matches, "Nested copy must retain the reference partial state and allocation failure accounting"); + Require(NestedState(source) == sourceState, "Nested allocation failure must preserve its source"); + _ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&source); + Require(trace.live.size() == baseline, "Nested partial-state teardown must release all admitted ownership"); + } +} +static void CheckStringSetterAliases(ValueAllocationTrace& trace) { + const auto baseline = trace.live; + for (unsigned kind = 0; kind < 4; ++kind) for (bool registered : {false, true}) for (bool fail : {false, true}) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + if (!kind) _ZN3sce4Json5ValueC1El(&value, 9); + else _ZN3sce4Json5ValueC1EPKc(&value, "original"); + if (registered) Require(Register(value, 0, &trace) == 0, "Prepare callback metadata before String setter alias testing"); + String missing{}, shared{}; + if (kind >= 2) shared.text = *reinterpret_cast(value.payload); + const String* source = kind < 2 ? &missing : kind == 2 ? reinterpret_cast(value.payload) : &shared; + const Value before = value; const auto live = trace.live; + std::map> snapshots; + for (const auto& [pointer, size] : live) { + const auto* bytes = reinterpret_cast(pointer); snapshots.emplace(pointer, std::vector{bytes, bytes + size}); + } + const auto requests = trace.requests.size(); const auto frees = trace.frees.size(); const auto notices = trace.notifications; const auto nullFrees = trace.nullFrees; + trace.budget = fail ? 0 : -1; + _ZN3sce4Json5Value3setERKNS0_6StringE(&value, source); + trace.budget = -1; + Require(std::memcmp(&value, &before, sizeof(value)) == 0 && trace.live == live && trace.requests.size() == requests && trace.frees.size() == frees && trace.notifications == notices && trace.nullFrees == nullFrees, + "Missing String headers and shared-header self-setting must preserve metadata, payload and allocation effects"); + for (const auto& [pointer, bytes] : snapshots) Require(std::memcmp(pointer, bytes.data(), bytes.size()) == 0, "Ignored or aliased String setting must preserve all original allocation bytes"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live == baseline, "String setter alias cases must reclaim their actual payload and callback resources"); + } + for (unsigned budget = 0; budget <= 1; ++budget) { + Value sourceValue, destination; std::memset(&sourceValue, 0xa5, sizeof(sourceValue)); std::memset(&destination, 0xa5, sizeof(destination)); + _ZN3sce4Json5ValueC1EPKc(&sourceValue, "replacement"); _ZN3sce4Json5ValueC1EPKc(&destination, "old"); + const Value originalSource = sourceValue; + const auto* source = reinterpret_cast(sourceValue.payload); + const auto* sourceHeader = source->text; const auto* sourceCharacters = *reinterpret_cast(sourceHeader); + const auto handle = destination.payload; const auto* header = *reinterpret_cast(handle); void* oldCharacters = *reinterpret_cast(header); + const auto requests = trace.requests.size(); const auto frees = trace.frees.size(); const auto notices = trace.notifications; + trace.budget = static_cast(budget); + _ZN3sce4Json5Value3setERKNS0_6StringE(&destination, source); + trace.budget = -1; + Require(destination.type == 5 && destination.payload == handle && *reinterpret_cast(handle) == header && + std::strcmp(*reinterpret_cast(header), budget ? "replacement" : "") == 0 && + trace.requests.size() == requests + 1 && trace.requests.back() == 12 && trace.notifications == notices + unsigned(!budget) && + trace.frees.size() == frees + 1 && trace.frees.back() == oldCharacters, + "Independent String sources must copy through the existing handle/header and retain empty text after character allocation failure"); + Require(std::memcmp(&sourceValue, &originalSource, sizeof(sourceValue)) == 0 && source->text == sourceHeader && *reinterpret_cast(sourceHeader) == sourceCharacters && + std::strcmp(sourceCharacters, "replacement") == 0, "Distinct-source assignment must preserve all source ownership and text bytes"); + const auto retryRequests = trace.requests.size(); + _ZN3sce4Json5Value3setERKNS0_6StringE(&destination, source); + Require(trace.requests.size() == retryRequests + 1 && trace.requests.back() == 12 && destination.payload == handle && + std::strcmp(*reinterpret_cast(header), "replacement") == 0, "Independent retry must complete the copy using the retained destination header"); + _ZN3sce4Json5ValueD1Ev(&destination); _ZN3sce4Json5ValueD1Ev(&sourceValue); + Require(trace.live == baseline, "Distinct String source cases must reclaim both real payloads"); + } + +} + +static void CheckValueSelfSetting(ValueAllocationTrace& trace) { + const auto baseline = trace.live; + for (int type = 0; type <= 7; ++type) for (bool setter : {false, true}) for (unsigned budget = 0; budget <= 2; ++budget) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + if (type == 5) _ZN3sce4Json5ValueC1EPKc(&value, "self"); + else _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast(type)); + Require(Register(value, 0, &trace, true) == 0, "Prepare callback ownership for Value self-setting"); + const Value before = value; const auto live = trace.live; + std::map> snapshots; + for (const auto& [pointer, size] : live) { + const auto* bytes = reinterpret_cast(pointer); snapshots.emplace(pointer, std::vector{bytes, bytes + size}); + } + void* header = type >= 6 ? *reinterpret_cast(value.payload) : nullptr; + void* sentinel = header ? *reinterpret_cast(header) : nullptr; + const auto requests = trace.requests.size(); const auto frees = trace.frees.size(); const auto notices = trace.notifications; const auto nullFrees = trace.nullFrees; + trace.budget = static_cast(budget); + if (setter) _ZN3sce4Json5Value3setERKS1_(&value, &value); + else Require(_ZN3sce4Json5ValueaSERKS1_(&value, &value) == &value, "Self-assignment must return its original receiver"); + trace.budget = -1; + Require(std::memcmp(&value, &before, sizeof(value)) == 0, "Every self-operation must preserve the inline Value, handle, callbacks and inactive bytes"); + const bool rebuild = type >= 6; + if (!rebuild) { + Require(trace.live == live && trace.requests.size() == requests && trace.frees.size() == frees && trace.notifications == notices && trace.nullFrees == nullFrees, + "Scalar and String self-operations must preserve allocation ownership without effects"); + } else { + std::vector expected{32}; if (budget) expected.push_back(type == 6 ? 48 : 104); + Require(std::vector(trace.requests.begin() + requests, trace.requests.end()) == expected && trace.notifications == notices + unsigned(budget < 2) && + trace.nullFrees == nullFrees + unsigned(!budget) && trace.frees.size() == frees + budget && trace.live.size() == live.size(), + "Collection self-setting must attempt replacement storage with exact requests, null frees and rollback effects"); + auto* replacement = *reinterpret_cast(value.payload); + Require((replacement == header) == (budget < 2) && reinterpret_cast(replacement)[1] == 0 && + reinterpret_cast(replacement)[3] == reinterpret_cast(&value), + "Self-setting must preserve failed replacement storage or publish a fresh empty collection bound to its original owner"); + if (budget == 2) Require(trace.frees[frees] == sentinel && trace.frees[frees + 1] == header, "Completed self-setting must reclaim the original sentinel before its header"); + } + for (const auto& [pointer, bytes] : snapshots) { + if (rebuild && budget == 2 && (pointer == header || pointer == sentinel || pointer == reinterpret_cast(value.payload))) continue; + Require(trace.live.contains(pointer) && std::memcmp(pointer, bytes.data(), bytes.size()) == 0, + "Self-setting must preserve every unaffected allocation, callback record and fallback byte"); + } + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live == baseline, "Self-operations must preserve complete destruction of original or replacement resources"); + } + for (bool array : {false, true}) for (bool setter : {false, true}) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + if (array) PrepareNestedSource(value); + else { + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast(7)); + Value key{}, scalar{}; _ZN3sce4Json5ValueC1EPKc(&key, "key"); _ZN3sce4Json5ValueC1El(&scalar, 9); + auto* member = _ZN3sce4Json6ObjectixERKNS0_6StringE(reinterpret_cast(value.payload), reinterpret_cast(key.payload)); + _ZN3sce4Json5ValueaSERKS1_(member, &scalar); + _ZN3sce4Json5ValueD1Ev(&scalar); _ZN3sce4Json5ValueD1Ev(&key); + } + Require(Register(value, 0, &trace, true) == 0, "Prepare metadata for nonempty collection self-use"); + const Value before = value; const auto* header = *reinterpret_cast(value.payload); + const auto state = array ? NestedState(value) : std::string{}; + const auto requests = trace.requests.size(); + if (setter) _ZN3sce4Json5Value3setERKS1_(&value, &value); + else Require(_ZN3sce4Json5ValueaSERKS1_(&value, &value) == &value, "Nonempty assignment must return its original receiver"); + Require(std::memcmp(&value, &before, sizeof(value)) == 0 && *reinterpret_cast(value.payload) != header && trace.requests.size() > requests, + "Nonempty collection self-use must rebuild storage while retaining the Value handle and callback metadata"); + if (array) Require(state == "6po3[5p:abc;2p42;6p!1[5p:def;];]" && NestedState(value) == "6po3[5p:abc;2p42;6po1[5p:def;];]", "Nested self-use must preserve contents and parent links while rebinding the copied nested header owner"); + else { + auto* replacement = *reinterpret_cast(value.payload); + auto* head = *reinterpret_cast(replacement); + auto* node = *reinterpret_cast(head); + const auto* bytes = reinterpret_cast(node); + const auto* child = reinterpret_cast(bytes + 64); + const auto* keyHeader = *reinterpret_cast(bytes + 48); + Require(reinterpret_cast(replacement)[1] == 1 && child->type == 2 && child->payload == 9 && child->parent == &value && + std::strcmp(*reinterpret_cast(bytes + 24), "key") == 0 && std::strcmp(*reinterpret_cast(keyHeader), "key") == 0, + "Object self-use must preserve ordered internal/public keys, child contents and owner binding"); + } + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live == baseline, "Nonempty self-use must reclaim both replaced and final collection storage"); + } + +} + +int main() { + ValueAllocationTrace trace; ValueAllocationAllocator allocator; Parameter parameter{&allocator, &trace, 16384, 0, {}}; + std::uint8_t initializer = 0; _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize value allocation contract"); + const auto baseline = trace.live.size(); + CheckMetadata(trace); + CheckScalarCopies(trace); + CheckCollectionCopies(trace); + CheckMissingCollections(trace); + CheckNestedCopies(trace); + for (int target = 0; target <= 7; ++target) for (int budget = 0; budget <= 4; ++budget) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + trace.budget = budget; + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast(target)); + trace.budget = -1; + const auto expected = target < 5 ? target : budget < (target == 7 ? 3 : 2) ? 0 : target; + Check(value, expected, trace); + Require(!value.parent && !value.auxiliary, "Typed construction must clear ownership metadata"); + if (target >= 5 && expected == 0) Require(value.payload == 0, "Failed complex construction must clear the payload pointer"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Typed construction must release partial owned resources"); + } + for (int previous = 0; previous <= 7; ++previous) for (int target = 0; target <= 7; ++target) for (int budget = 0; budget <= 4; ++budget) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + _ZN3sce4Json5ValueC1ENS0_9ValueTypeE(&value, static_cast(previous)); + Value owner{}; value.parent = &owner; + const auto payload = value.payload; + const auto requests = trace.requests.size(); + const auto notifications = trace.notifications; + trace.budget = budget; + _ZN3sce4Json5Value3setENS0_9ValueTypeE(&value, static_cast(target)); + trace.budget = -1; + const auto expected = previous == target || target < 5 ? target : budget < (target == 7 ? 3 : 2) ? 0 : target; + Check(value, expected, trace); + Require(value.parent == &owner && !value.auxiliary, "Type changes must preserve parent and auxiliary ownership"); + if (previous == target) Require(value.payload == payload && trace.requests.size() == requests && trace.notifications == notifications, "Retaining the same type must preserve storage without allocating"); + else if (target >= 5 && expected == 0) Require(value.payload == 0 && trace.notifications > notifications, "Failed type changes must clear the previous payload and notify failure"); + else if (target == 0) Require(value.payload == (previous >= 5 ? 0 : payload), "Clearing a scalar must preserve its inactive payload while complex values clear their handle"); + else if (target == 1) Require(value.payload == ((previous >= 5 ? 0 : payload) & ~UINT64_C(255)), "Boolean type changes must clear only their active byte"); + else if (target < 5) Require(value.payload == 0, "Numeric type changes must initialize their active payload"); + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Type changes must reclaim the old payload even when replacement fails"); + } + for (bool constructor : {false, true}) for (int budget = 0; budget <= 5; ++budget) { + Value value; std::memset(&value, 0xa5, sizeof(value)); + if (!constructor) _ZN3sce4Json5ValueC1Ed(&value, 0.125); + const auto payload = value.payload; + const auto notifications = trace.notifications; + trace.budget = budget; + if (constructor) _ZN3sce4Json5ValueC1Ed(&value, std::numeric_limits::infinity()); + else _ZN3sce4Json5Value3setEd(&value, std::numeric_limits::infinity()); + trace.budget = -1; + const auto expected = budget == 0 ? (constructor ? 0 : 4) : budget < 4 ? 0 : 5; + Check(value, expected, trace); + Require(trace.notifications - notifications == (budget == 5 ? 0u : budget == 1 ? 2u : 1u), "Float conversion must report temporary and destination allocation failures"); + if (budget == 0) Require(value.payload == payload, "Failure to allocate the conversion temporary must preserve the prior payload"); + else if (expected == 0) Require(value.payload == 0, "Failed destination conversion must leave a null handle"); + else { + const auto* header = *reinterpret_cast(value.payload); + const auto* characters = *reinterpret_cast(header); + Require(std::strcmp(characters, budget == 4 ? "" : "inf") == 0, "Float conversion must retain the admitted string and character failure state"); + } + _ZN3sce4Json5ValueD1Ev(&value); + Require(trace.live.size() == baseline, "Float conversion must release temporary and destination allocations"); + } + CheckStringSetterAliases(trace); + CheckValueSelfSetting(trace); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0 && trace.live.empty(), "Terminate value allocation contract"); + return 0; +} diff --git a/core/libs/tests/GuestJsonValueLayout.cpp b/core/libs/tests/GuestJsonValueLayout.cpp new file mode 100644 index 0000000000..81e0ada39d --- /dev/null +++ b/core/libs/tests/GuestJsonValueLayout.cpp @@ -0,0 +1,75 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +static_assert(sizeof(Value) == 32 && offsetof(Value, payload) == 16 && offsetof(Value, type) == 28); +static void Require(bool condition, const char* message) { + if (!condition) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +static void Prefix(const Value& value, int type) { + Require(!value.parent && !value.auxiliary && value.type == type, "Value must expose its native parent, auxiliary and inline type fields"); + Require(value.reserved == 0xa5a5a5a5U, "Value construction must preserve the reserved word"); +} +int main() { + alignas(8) std::array storage; + std::memset(storage.data(), 0xa5, sizeof(storage)); + auto& value = storage[1]; + _ZN3sce4Json5ValueC1Ev(&value); + Prefix(value, 0); + Require(value.payload == 0xa5a5a5a5a5a5a5a5ULL, "Null construction must preserve inactive payload bytes"); + _ZN3sce4Json5ValueD1Ev(&value); + _ZN3sce4Json5ValueC1Eb(&value, true); + Prefix(value, 1); + Require(value.payload == 0xa5a5a5a5a5a5a501ULL, "Boolean construction must write only its one-byte payload"); + _ZN3sce4Json5ValueD1Ev(&value); + _ZN3sce4Json5ValueC1El(&value, -73); + Prefix(value, 2); + Require(std::bit_cast(value.payload) == -73 && + _ZNK3sce4Json5Value10getIntegerEv(&value) == reinterpret_cast(&value.payload), + "Signed integer storage must be inline and returned by reference"); + Require(_ZNK3sce4Json5Value11getUIntegerEv(&value) == &value.payload, + "Both integer getters must refer to the same inline bits"); + Require(*_ZNK3sce4Json5Value7getRealEv(&value) == 0.0 && value.type == 2 && + std::bit_cast(value.payload) == -73, + "An unmatched real getter must leave integer storage unchanged and return the default real"); + Value copy; + std::memset(©, 0xa5, sizeof(copy)); + _ZN3sce4Json5ValueC1ERKS1_(©, &value); + Prefix(copy, 2); + Require(copy.payload == value.payload && _ZNK3sce4Json5Value10getIntegerEv(©) != _ZNK3sce4Json5Value10getIntegerEv(&value), + "A scalar copy must own independent inline storage"); + _ZN3sce4Json5Value3setEl(©, 19); + Require(std::bit_cast(value.payload) == -73 && copy.payload == 19, "Assignment must not alias a scalar source"); + _ZN3sce4Json5Value5clearEv(©); + Require(copy.type == 0 && copy.payload == 19 && copy.reserved == 0xa5a5a5a5U, + "Clear must reset type while preserving inactive scalar bytes"); + _ZN3sce4Json5ValueD1Ev(©); + _ZN3sce4Json5ValueD1Ev(&value); + _ZN3sce4Json5ValueC1Em(&value, 0xfedcba9876543210ULL); + Prefix(value, 3); + Require(value.payload == 0xfedcba9876543210ULL && *_ZNK3sce4Json5Value11getUIntegerEv(&value) == value.payload, + "Unsigned construction must preserve all 64 payload bits"); + _ZN3sce4Json5ValueD1Ev(&value); + for (std::uint64_t bits : {0x3fc0000000000000ULL, 0x8000000000000000ULL, 0x7ff80000cafef00dULL}) { + _ZN3sce4Json5ValueC1Ed(&value, std::bit_cast(bits)); + Prefix(value, 4); + Require(value.payload == bits && _ZNK3sce4Json5Value7getRealEv(&value) == reinterpret_cast(&value.payload), + "Real construction must preserve inline payload bits and reference identity"); + Require(_ZNK3sce4Json5Value7getTypeEv(&value) == value.type, "The getter must read the inline type"); + _ZN3sce4Json5ValueD1Ev(&value); + } + for (unsigned index : {0U, 2U}) { + const auto* bytes = reinterpret_cast(&storage[index]); + for (std::size_t offset = 0; offset < sizeof(Value); ++offset) + Require(bytes[offset] == 0xa5, "Value operations must not overwrite neighboring objects"); + } + return 0; +} diff --git a/core/libs/tests/GuestJsonValueOwnership.cpp b/core/libs/tests/GuestJsonValueOwnership.cpp new file mode 100644 index 0000000000..bf0074c81c --- /dev/null +++ b/core/libs/tests/GuestJsonValueOwnership.cpp @@ -0,0 +1,133 @@ +#include "prx/libSceJson2/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" +#include +#include +#include +#include +#include +#include + +using namespace SceJson2; + +struct Parameter { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; }; +static void Require(bool valid, const char* message) { + if (!valid) { std::fprintf(stderr, "%s\n", message); std::abort(); } +} +static const Value& Front(const Value& value) { + return *_ZNK3sce4Json5Array5frontEv(_ZNK3sce4Json5Value8getArrayEv(&value)); +} +static void Tree(const Value& root) { + Require(root.type == 6 && !root.parent, "A copied root must own an independent root value"); + const auto& child = Front(root); + Require(child.parent == &root && child.type == 6, "An array element must refer to its owning value"); + const auto& leaf = Front(child); + Require(leaf.parent == &child && leaf.type == 2 && leaf.payload == 42, + "Nested children must refer to their immediate owner and preserve inline payloads"); +} +int main() { + JsonTest::Allocator allocator; + Parameter parameter{&allocator, nullptr, 0, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize ownership tests"); + Value text{}; + _ZN3sce4Json5ValueC1EPKc(&text, "keep"); + const auto payload = text.payload; + const auto* string = _ZNK3sce4Json5Value9getStringEv(&text); + Require(reinterpret_cast(string) == payload, "A string payload must hold its String object address"); + _ZN3sce4Json5Value3setENS0_9ValueTypeE(&text, static_cast(5)); + Require(text.payload == payload && std::strcmp(_ZNK3sce4Json6String5c_strEv(string), "keep") == 0, + "Setting an existing type must preserve its contents and identity"); + Value leaf{}; + _ZN3sce4Json5ValueC1El(&leaf, 42); + Array inner{}; + _ZN3sce4Json5ArrayC1Ev(&inner); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&inner, &leaf); + Require(!_ZNK3sce4Json5Array5frontEv(&inner)->parent, "A standalone array must not invent a value owner"); + Value child{}; + _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&child, &inner); + Array outer{}; + _ZN3sce4Json5ArrayC1Ev(&outer); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(&outer, &child); + Value root{}; + _ZN3sce4Json5ValueC1ERKNS0_5ArrayE(&root, &outer); + Tree(root); + Require(reinterpret_cast(_ZNK3sce4Json5Value8getArrayEv(&root)) == root.payload, + "An array payload must hold its Array object address"); + Value copy{}; + _ZN3sce4Json5ValueC1ERKS1_(©, &root); + Tree(copy); + Require(copy.payload != root.payload && Front(copy).payload != Front(root).payload, + "Copies must not share owned containers"); + const auto rootPayload = root.payload; + _ZN3sce4Json5Value3setENS0_9ValueTypeE(&root, static_cast(6)); + Require(root.payload == rootPayload && _ZNK3sce4Json5Array4sizeEv(_ZNK3sce4Json5Value8getArrayEv(&root)) == 1, + "Setting the existing array type must retain elements and storage"); + auto* ownedArray = const_cast(_ZNK3sce4Json5Value8getArrayEv(©)); + _ZN3sce4Json5Array9push_backERKNS0_5ValueE(ownedArray, &child); + const auto* appended = _ZNK3sce4Json5Array4backEv(ownedArray); + Require(appended->parent == © && Front(*appended).parent == appended, + "Appending to an owned array must bind both new and nested children"); + _ZN3sce4Json5Value3setERKS1_(©, &Front(copy)); + Require(!copy.parent && Front(copy).parent == © && Front(copy).payload == 42, + "Assignment from a descendant must finish copying before releasing the old tree"); + Value cross{}; + _ZN3sce4Json5ValueC1Ev(&cross); + const char objectInput[] = "{\"a\":[1,2]}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&cross, objectInput, sizeof(objectInput) - 1) == 0, "Parse cross-type ownership tree"); + const auto* arrayMember = _ZNK3sce4Json5ValueixEPKc(&cross, "a"); + _ZN3sce4Json5ValueaSERKS1_(&cross, arrayMember); + Require(cross.type == 6 && Front(cross).payload == 1 && Front(cross).parent == &cross, + "Object-to-array descendant assignment must preserve the source before clearing its owner"); + _ZN3sce4Json5Value5clearEv(&cross); + const char arrayInput[] = "[\"retained\"]"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&cross, arrayInput, sizeof(arrayInput) - 1) == 0, "Parse string descendant tree"); + _ZN3sce4Json5Value3setERKS1_(&cross, &Front(cross)); + Require(cross.type == 5 && std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&cross)), "retained") == 0, + "Array-to-string descendant assignment must preserve text before clearing its owner"); + _ZN3sce4Json5Value5clearEv(&cross); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&cross, objectInput, sizeof(objectInput) - 1) == 0, "Parse raw Array descendant source"); + _ZN3sce4Json5Value3setERKNS0_5ArrayE(&cross, _ZNK3sce4Json5Value8getArrayEv(_ZNK3sce4Json5ValueixEPKc(&cross, "a"))); + Require(cross.type == TypeArray && Front(cross).payload == 1, "Array overload must retain a descendant before changing the destination type"); + _ZN3sce4Json5Value5clearEv(&cross); + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&cross, arrayInput, sizeof(arrayInput) - 1) == 0, "Parse raw String descendant source"); + _ZN3sce4Json5Value3setERKNS0_6StringE(&cross, _ZNK3sce4Json5Value9getStringEv(&Front(cross))); + Require(cross.type == TypeString && std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&cross)), "retained") == 0, + "String overload must retain descendant text before changing the destination type"); + _ZN3sce4Json5Value5clearEv(&cross); + const char nestedObject[] = "[{\"a\":7}]"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&cross, nestedObject, sizeof(nestedObject) - 1) == 0, "Parse raw Object descendant source"); + _ZN3sce4Json5Value3setERKNS0_6ObjectE(&cross, _ZNK3sce4Json5Value9getObjectEv(&Front(cross))); + const auto* member = _ZNK3sce4Json5ValueixEPKc(&cross, "a"); + Require(cross.type == TypeObject && member->payload == 7 && member->parent == &cross, + "Object overload must retain descendant contents and bind their new owner"); + for (bool nested : {false, true}) { + _ZN3sce4Json5Value5clearEv(&cross); + const char* document = nested ? "[{\"retained-key\":7}]" : "{\"retained-key\":7}"; + Require(_ZN3sce4Json6Parser5parseERNS0_5ValueEPKcm(&cross, document, std::strlen(document)) == 0, "Parse object-key ownership tree"); + const auto* object = _ZNK3sce4Json5Value9getObjectEv(nested ? &Front(cross) : &cross); + ObjectIterator iterator{}; + _ZNK3sce4Json6Object5beginEv(&iterator, object); + const auto* pair = _ZNK3sce4Json6Object8iteratordeEv(&iterator); + _ZN3sce4Json5Value3setERKNS0_6StringE(&cross, &pair->key); + Require(cross.type == TypeString && std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&cross)), "retained-key") == 0, + "String assignment from an owned object key must copy text before releasing its tree"); + _ZN3sce4Json6Object8iteratorD1Ev(&iterator); + } + _ZN3sce4Json5ValueD1Ev(&cross); + _ZN3sce4Json5Value4swapERS1_(&root, &text); + Require(root.type == 5 && text.type == 6, "Swap must exchange payloads"); + Tree(text); + Require(std::strcmp(_ZNK3sce4Json6String5c_strEv(_ZNK3sce4Json5Value9getStringEv(&root)), "keep") == 0, + "Swap must preserve string contents"); + _ZN3sce4Json5ValueD1Ev(&root); + _ZN3sce4Json5ValueD1Ev(©); + _ZN3sce4Json5ValueD1Ev(&child); + _ZN3sce4Json5ValueD1Ev(&leaf); + _ZN3sce4Json5ValueD1Ev(&text); + _ZN3sce4Json5ArrayD1Ev(&inner); + _ZN3sce4Json5ArrayD1Ev(&outer); + Require(_ZN3sce4Json11Initializer9terminateEv(&initializer) == 0, "Terminate ownership tests"); + return 0; +} diff --git a/core/libs/tests/GuestKernelAppArchive.cpp b/core/libs/tests/GuestKernelAppArchive.cpp index d3922949f5..d791546be1 100644 --- a/core/libs/tests/GuestKernelAppArchive.cpp +++ b/core/libs/tests/GuestKernelAppArchive.cpp @@ -317,6 +317,21 @@ void Directories() { Check(!DescriptorIsOpen_nid_no_patch(owned)); } +void DirectoryClosedThroughStream(const std::filesystem::path& root) { + std::ofstream(root / "app0" / "StreamClose.txt", std::ios::binary) << "abc"; + const int directory = sceKernelOpen("/app0/sce_sys/Concurrent", SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); + Check(directory >= 0); + auto* stream = fdopen_nid_postfix(directory, "r"); + Check(stream != nullptr); + Check(fclose_nid_postfix(stream) == 0); + const int file = sceKernelOpen("/app0/StreamClose.txt", SCE_KERNEL_O_RDONLY, 0); + Check(file == directory); + char data[4]{}; + Check(sceKernelRead(file, data, sizeof(data)) == 3); + Check(sceKernelClose(file) == 0); + Check(std::filesystem::remove(root / "app0" / "StreamClose.txt")); +} + void Reads() { const auto expected = AppArchiveFixture::Pattern(BigSize); const int fd = sceKernelOpen("/app0/data/big.BIN", SCE_KERNEL_O_RDONLY, 0); @@ -384,10 +399,12 @@ void Reads() { } void Status() { + auto* const badStatus = reinterpret_cast(static_cast(0x10)); + Check(sceKernelStat("/app0/Data/Small.txt", badStatus) == SCE_KERNEL_ERROR_EFAULT); FileStat status{}; Check(sceKernelStat("/app0/Data/Small.txt", &status) == 0); Check((status.st_mode & TypeMask) == RegularType && (status.st_mode & 0222) == 0 && status.st_size == 3 && status.st_blocks == 1); - Check(sceKernelStat("/app0/data", &status) == 0 && (status.st_mode & TypeMask) == DirectoryType); + Check(sceKernelStat("/app0/data", &status) == 0 && (status.st_mode & TypeMask) == DirectoryType && status.st_blksize == 0x10000); Check(sceKernelStat("/app0/Data/", &status) == 0); Check(sceKernelStat("/app0/Data/Small.txt/", &status) == SCE_KERNEL_ERROR_ENOTDIR); Check(sceKernelStat("/app0/Data/Missing.txt", &status) == SCE_KERNEL_ERROR_ENOENT); @@ -496,6 +513,7 @@ int main(int argc, char** argv) { Archive(root); FileNumbers(); ConcurrentArchiveDirectoryEntries(); + DirectoryClosedThroughStream(root); Reads(); Status(); Refusals(); diff --git a/core/libs/tests/GuestKernelErrors.cpp b/core/libs/tests/GuestKernelErrors.cpp index 598af1167c..5096b9e3e2 100644 --- a/core/libs/tests/GuestKernelErrors.cpp +++ b/core/libs/tests/GuestKernelErrors.cpp @@ -50,7 +50,43 @@ static void RequireTimesOut(TLock lock) { Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); } +struct ReaderHold { + PthreadRwlock* lock; + std::atomic held{false}; + std::atomic release{false}; +}; + +static void* APS5_VABI HoldRead(void* argument) { + auto& hold = *static_cast(argument); + Require(scePthreadRwlockRdlock(hold.lock) == SCE_OK); + hold.held.store(true); + while (!hold.release.load()) std::this_thread::yield(); + Require(scePthreadRwlockUnlock(hold.lock) == SCE_OK); + return nullptr; +} + +static void CheckTimedRwlockWithReader() { + PthreadRwlock rwlock = nullptr; + Require(scePthreadRwlockInit(&rwlock, nullptr, nullptr) == SCE_OK); + ReaderHold hold{&rwlock}; + Pthread reader = nullptr; + Require(scePthreadCreate(&reader, nullptr, HoldRead, &hold, "reader-hold") == SCE_OK); + while (!hold.held.load()) std::this_thread::yield(); + Require(scePthreadRwlockTimedrdlock(&rwlock, 20000) == SCE_OK); + Require(scePthreadRwlockUnlock(&rwlock) == SCE_OK); + RequireTimesOut([&](KernelUseconds usec) { return scePthreadRwlockTimedwrlock(&rwlock, usec); }); + Require(scePthreadRwlockTrywrlock(&rwlock) == SCE_KERNEL_ERROR_EBUSY); + hold.release.store(true); + Require(scePthreadJoin(reader, nullptr) == SCE_OK); + Require(scePthreadRwlockTimedwrlock(&rwlock, 20000) == SCE_OK); + Require(scePthreadRwlockUnlock(&rwlock) == SCE_OK); + Require(scePthreadRwlockTimedrdlock(&rwlock, 20000) == SCE_OK); + Require(scePthreadRwlockUnlock(&rwlock) == SCE_OK); + Require(scePthreadRwlockDestroy(&rwlock) == SCE_OK); +} + int main() { + CheckTimedRwlockWithReader(); constexpr int maximum = std::numeric_limits::max(); KernelSema semaphore = nullptr; Require(sceKernelCreateSema(&semaphore, "overflow", 1, maximum - 1, maximum, nullptr) == SCE_OK); @@ -84,6 +120,7 @@ int main() { Require(sceKernelDeleteEqueue(eq) == SCE_KERNEL_ERROR_EBADF); Require(sceKernelWaitEqueue(eq, &event, 1, &count, &timeout) == SCE_KERNEL_ERROR_EBADF); Require(sceKernelCreateEqueue(nullptr, "errors") == SCE_KERNEL_ERROR_EINVAL); + Require(sceKernelCreateEqueue(&eq, nullptr) == SCE_KERNEL_ERROR_EINVAL); PthreadMutexattr attr = nullptr; Require(scePthreadMutexattrInit(&attr) == SCE_OK); diff --git a/core/libs/tests/GuestKernelFcntl.cpp b/core/libs/tests/GuestKernelFcntl.cpp index dc3737eb5e..54f0a6d4e6 100644 --- a/core/libs/tests/GuestKernelFcntl.cpp +++ b/core/libs/tests/GuestKernelFcntl.cpp @@ -17,6 +17,10 @@ #include #include #endif +#ifdef __linux__ +#include +#include +#endif static void Require(bool value) { if (!value) std::abort(); } static constexpr int DuplicateCommand = 0; @@ -39,6 +43,8 @@ static constexpr int ReadOnly = 0; static constexpr int WriteOnly = 1; static constexpr int ReadWrite = 2; static constexpr int NonBlocking = 4; +static constexpr int CreateFlag = 0x200; +static constexpr int ExclusiveFlag = 0x800; static constexpr int Append = 8; static constexpr int ReadAheadFlag = 0x200; static constexpr int CreateReadWrite = 0x202; @@ -207,6 +213,10 @@ static void SocketsKeepTheirPath() { Require(sceKernelFcntl(socket, DarwinReadAhead, 1) == SceEbadf); int queued = -1; Require(ioctl_nid_postfix(socket, ReadableRequest, &queued) == 0 && queued == 0); + Require(ioctl_nid_postfix(socket, CloseOnExecRequest, nullptr) == 0 && fcntl_nid_postfix(socket, GetDescriptorFlags) == 1); + Require(ioctl_nid_postfix(socket, CloseOnExecRequest, &queued) == 0 && fcntl_nid_postfix(socket, GetDescriptorFlags) == 1); + Require(ioctl_nid_postfix(socket, KeepOnExecRequest, nullptr) == 0 && fcntl_nid_postfix(socket, GetDescriptorFlags) == 0); + Require(ioctl_nid_postfix(socket, KeepOnExecRequest, &queued) == 0 && fcntl_nid_postfix(socket, GetDescriptorFlags) == 0); RequireMalformedRequestsFail(socket); RequireFailure(ioctl_nid_postfix(socket, IntegerArgumentRequest, nullptr), GuestEfault); RequireFailure(ioctl_nid_postfix(socket, EmptyInputRequest, nullptr), GuestEfault); @@ -538,6 +548,31 @@ static void HostSocketFlags() { } #endif +#ifdef __linux__ +static void HostOnlyDescriptorsAreNotOpenAndSharedMemoryIs() { + const int descriptors[] = {::epoll_create1(EPOLL_CLOEXEC), ::eventfd(0, EFD_CLOEXEC)}; + for (const int descriptor : descriptors) { + Require(descriptor >= 0); + RequireFailure(fcntl_nid_postfix(descriptor, GetDescriptorFlags), GuestEbadf); + RequireFailure(fcntl_nid_postfix(descriptor, GetFlags), GuestEbadf); + RequireFailure(fcntl_nid_postfix(descriptor, SetDescriptorFlags, 0), GuestEbadf); + int available = 0; + RequireFailure(ioctl_nid_postfix(descriptor, ReadableRequest, &available), GuestEbadf); + Require(::fcntl(descriptor, F_GETFD) == FD_CLOEXEC); + Require(::close(descriptor) == 0); + } + const std::string name = "/anyps5-fcntl-shm-" + std::to_string(::getpid()); + static_cast(shm_unlink_nid_postfix(name.c_str())); + const int shared = shm_open_nid_postfix(name.c_str(), ReadWrite | CreateFlag | ExclusiveFlag, 0600); + Require(shared >= 0); + Require(fcntl_nid_postfix(shared, GetFlags) == ReadWrite); + Require(fcntl_nid_postfix(shared, SetDescriptorFlags, 1) == 0); + Require(fcntl_nid_postfix(shared, GetDescriptorFlags) == 1); + Require(close_nid_postfix(shared) == 0); + Require(shm_unlink_nid_postfix(name.c_str()) == 0); +} +#endif + static void StandardInputFlags() { const int flags = fcntl_nid_postfix(0, GetFlags); Require(flags >= 0); @@ -557,6 +592,9 @@ int main() { PipeFlagsRoundTrip(); #ifndef _WIN32 HostSocketFlags(); +#endif +#ifdef __linux__ + HostOnlyDescriptorsAreNotOpenAndSharedMemoryIs(); #endif StandardInputFlags(); return 0; diff --git a/core/libs/tests/GuestKernelIsStack.cpp b/core/libs/tests/GuestKernelIsStack.cpp index e9fdbae01d..3a3f034eb6 100644 --- a/core/libs/tests/GuestKernelIsStack.cpp +++ b/core/libs/tests/GuestKernelIsStack.cpp @@ -50,6 +50,16 @@ int main() { Require(sceKernelIsStack(&global, &start, &end) == SCE_OK); Require(start == nullptr && end == nullptr); + auto* const badPointerStore = reinterpret_cast(0x10); + Require(sceKernelIsStack(&local, badPointerStore, &end) == SCE_KERNEL_ERROR_EFAULT); + start = nullptr; + Require(sceKernelIsStack(&local, &start, badPointerStore) == SCE_KERNEL_ERROR_EFAULT); + Require(start != nullptr); + Require(sceKernelQueryMemoryProtection(&local, badPointerStore, &end, nullptr) == SCE_KERNEL_ERROR_EFAULT); + Require(sceKernelQueryMemoryProtection(&local, &start, badPointerStore, nullptr) == SCE_KERNEL_ERROR_EFAULT); + auto* const badProtectionStore = reinterpret_cast(0x10); + Require(sceKernelQueryMemoryProtection(&local, &start, &end, badProtectionStore) == SCE_KERNEL_ERROR_EFAULT); + WorkerContext context; Pthread thread = nullptr; Require(scePthreadCreate(&thread, nullptr, Worker, &context, nullptr) == SCE_OK); diff --git a/core/libs/tests/GuestKernelStackProtect.cpp b/core/libs/tests/GuestKernelStackProtect.cpp index 2354076a1d..8a432b51b0 100644 --- a/core/libs/tests/GuestKernelStackProtect.cpp +++ b/core/libs/tests/GuestKernelStackProtect.cpp @@ -254,8 +254,15 @@ static void* APS5_VABI Worker(void* arg) { Require(QueryPage(below).raw == belowBefore); static_cast(start)[0] = 3; const auto outside = reinterpret_cast(start) & ~(Page - 1); - if (outside >= Page) Require(mprotect_nid_postfix(reinterpret_cast(outside - Page), 2 * Page, 3) == -1); - if (outside >= 2 * Page) Require(mprotect_nid_postfix(reinterpret_cast(outside - 2 * Page), Page, 3) == -1); + if (outside >= Page) { + const int straddle = mprotect_nid_postfix(reinterpret_cast(outside - Page), 2 * Page, 3); + Require(QueryPage(below).raw == belowBefore); +#ifndef __linux__ + Require(straddle == 0); +#else + (void)straddle; +#endif + } const auto stackBytes = reinterpret_cast(end) - reinterpret_cast(start); Require(mprotect_nid_postfix(start, stackBytes, 0xc2) == 0); Require(QueryPage(low).writable); @@ -266,7 +273,7 @@ static void* APS5_VABI Worker(void* arg) { #endif void* host = MapHostPages(); Require(host != nullptr); - Require(mprotect_nid_postfix(host, HostBytes, 1) == -1); + Require(mprotect_nid_postfix(host, HostBytes, 1) == 0); Require(QueryPage(reinterpret_cast(host)).writable); static_cast(host)[HostBytes - 1] = 5; UnmapHostPages(host); diff --git a/core/libs/tests/GuestKernelSys.cpp b/core/libs/tests/GuestKernelSys.cpp index 5aeefaf21c..337c82205a 100644 --- a/core/libs/tests/GuestKernelSys.cpp +++ b/core/libs/tests/GuestKernelSys.cpp @@ -1,9 +1,11 @@ #include "prx/libkernel_sys/include/Api.hpp" #include "prx/libkernel/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" #include "prx/libkernel/Apr/include/AprCommandBuffer.hpp" #include "prx/libSceAmpr/include/Api.hpp" #include "prx/libkernel/Pthread/include/Cond.hpp" #include "prx/libkernel/Time/include/Time.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" #include "prx/libkernel/File/include/File.hpp" #include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" @@ -56,6 +58,39 @@ int Error() { return *__error_nid_postfix(); } +void ThreadAndSessionTests(int descriptor) { + PthreadMutex mutex = nullptr; + Require(scePthreadMutexIsowned(&mutex) == 0); + Require(scePthreadMutexInit(&mutex, nullptr, "isowned") == 0); + Require(scePthreadMutexIsowned(&mutex) == 0); + Require(scePthreadMutexLock(&mutex) == 0); + Require(scePthreadMutexIsowned(&mutex) == SceKernelError(1)); + int fromOther = -1; + std::thread([&] { fromOther = scePthreadMutexIsowned(&mutex); }).join(); + Require(fromOther == 0); + Require(scePthreadMutexUnlock(&mutex) == 0); + Require(scePthreadMutexIsowned(&mutex) == 0); + Require(scePthreadMutexDestroy(&mutex) == 0); + Require(scePthreadMutexIsowned(&mutex) == 0); + + const char record[] = "trace"; + *__error_nid_postfix() = 0; + Require(utrace_nid_postfix(record, sizeof(record)) == 0 && Error() == 0); + Require(utrace_nid_postfix(nullptr, 0) == 0); + Require(utrace_nid_postfix(nullptr, 4096) == 0 && Error() == 0); + + const int session = getsid_nid_postfix(0); + Require(session > 0); + Require(tcsetsid_nid_postfix(descriptor, session + 1) == -1 && Error() == 22); + Require(tcsetsid_nid_postfix(-1, session + 1) == -1 && Error() == 22); + Require(tcsetsid_nid_postfix(descriptor, session) == -1 && Error() == 25); + Require(tcsetsid_nid_postfix(-1, session) == -1 && Error() == 9); + const int socketDescriptor = socket_nid_postfix(2, 2, 0); + Require(socketDescriptor >= 0); + Require(tcsetsid_nid_postfix(socketDescriptor, session) == -1 && Error() == 25); + Require(close_nid_postfix(socketDescriptor) == 0); +} + bool ChrootThrows(const std::string& path) { try { chroot_nid_postfix(path.c_str()); @@ -333,6 +368,7 @@ void CheckUnreadablePaths(const std::string& renameSource, int directory) { posixFault([&] { return shm_open_nid_postfix(paths.Unreadable(), SCE_KERNEL_O_RDWR, 0600); }); posixFault([&] { return shm_unlink_nid_postfix(paths.Unreadable()); }); posixFault([&] { return kldfind_nid_postfix(paths.Unreadable()); }); + posixFault([&] { return kldload_nid_postfix(paths.Unreadable()); }); posixFault([&] { return kldstat_nid_postfix(1, paths.Unreadable()); }); posixFault([&] { return kldstat_nid_postfix(1, paths.Boundary()); }); const std::int32_t statVersion = 1056; @@ -355,14 +391,14 @@ void CheckUnreadablePaths(const std::string& renameSource, int directory) { 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")}) + for (const auto& target : {std::string("a/../b"), std::string("a/../../x"), std::string("/abs")}) Require(SymlinkTargetThrows(target.c_str(), created)); #ifdef _WIN32 for (const auto& target : {std::string("C:/"), std::string("\\\\server\\share")}) Require(SymlinkTargetThrows(target.c_str(), created)); #endif const auto links = [](const std::string& created, const char* target) { - char buffer[16]; + char buffer[64]; const auto length = std::strlen(target); return readlink_nid_postfix(created.c_str(), buffer, sizeof(buffer)) == static_cast(length) && std::memcmp(buffer, target, length) == 0; @@ -376,12 +412,92 @@ void CheckSymlinkTargetRestrictions(const std::filesystem::path& root) { const auto ellipsis = (root / "accepted-ellipsis").string(); Require(symlink_nid_postfix("...", ellipsis.c_str()) == 0 && links(ellipsis, "...")); Require(RemoveSymlink(nested) && RemoveSymlink(dots) && RemoveSymlink(ellipsis)); + const auto climb = root / "climb"; + Require(std::filesystem::create_directories(climb / "deeper")); + const auto readsFile = [](const std::string& link) { + const int fd = sceKernelOpen(link.c_str(), SCE_KERNEL_O_RDONLY, 0); + char byte = 0; + const bool read = fd >= 0 && sceKernelRead(fd, &byte, 1) == 1 && byte == 'x'; + return fd >= 0 && sceKernelClose(fd) == 0 && read; + }; + const auto up = (climb / "up").string(); + Require(symlink_nid_postfix("../file.txt", up.c_str()) == 0 && links(up, "../file.txt") && readsFile(up)); + const auto inner = (climb / "inner").string(); + Require(symlink_nid_postfix("deeper/../../file.txt", inner.c_str()) == 0 && links(inner, "deeper/../../file.txt") && readsFile(inner)); + const auto parent = (climb / "parent").string(); + Require(symlink_nid_postfix("..", parent.c_str()) == 0 && links(parent, "..") && readsFile((climb / "parent" / "file.txt").string())); + const auto parentDangling = (climb / "dangling").string(); + Require(symlink_nid_postfix("../missing/x", parentDangling.c_str()) == 0 && links(parentDangling, "../missing/x")); + const auto mount = "/anyps5-symlink-climb-mount"; + AddPathAlias_nid_no_patch(mount, std::filesystem::absolute(climb).string().c_str()); + Require(symlink_nid_postfix("deeper/../up", (std::string(mount) + "/kept").c_str()) == 0 && readsFile((climb / "kept").string())); + RemovePathAlias_nid_no_patch(mount); + Require(RemoveSymlink(up) && RemoveSymlink(inner) && RemoveSymlink(parent) && RemoveSymlink(parentDangling)); + Require(RemoveSymlink((climb / "kept").string())); + Require(std::filesystem::remove(climb / "deeper") && std::filesystem::remove(climb)); } else { Require(link == -1 && Error() == 1); std::fprintf(stderr, "symbolic links unavailable: the symlink target success cases not tested\n"); } } +void CheckLinkSymbolicLinkLimit(const std::filesystem::path& root) { + const auto chain = root / "link-chain"; + const auto folder = chain / "folder"; + Require(std::filesystem::create_directories(folder)); + std::ofstream((chain / "file.txt").string()).put('x'); + std::ofstream((folder / "file.txt").string()).put('x'); + const auto at = [&](const std::string& name) { return (chain / name).string(); }; + const auto fileLink = [](int index) { return "file" + std::to_string(index); }; + const auto folderLink = [](int index) { return "folder" + std::to_string(index); }; + const int first = symlink_nid_postfix("file.txt", at(fileLink(1)).c_str()); + if (first != 0) { + Require(first == -1 && Error() == 1); + std::fprintf(stderr, "symbolic links unavailable: the link chain limit not tested\n"); + Require(std::filesystem::remove(folder / "file.txt") && std::filesystem::remove(folder)); + Require(std::filesystem::remove(chain / "file.txt") && std::filesystem::remove(chain)); + return; + } + Require(symlink_nid_postfix("folder", at(folderLink(1)).c_str()) == 0); + for (int index = 2; index <= 33; ++index) { + Require(symlink_nid_postfix(fileLink(index - 1).c_str(), at(fileLink(index)).c_str()) == 0); + Require(symlink_nid_postfix(folderLink(index - 1).c_str(), at(folderLink(index)).c_str()) == 0); + } + const auto copy = at("copy.txt"); + const auto linksTo = [&](const std::string& source, const std::string& created, const std::filesystem::path& file) { + const bool linked = link_nid_postfix(source.c_str(), created.c_str()) == 0 && std::filesystem::hard_link_count(file) == 2; + return linked && std::filesystem::remove(created); + }; + const auto refused = [](int result) { return result == -1 && Error() == 62; }; + Require(linksTo(at(fileLink(32)), copy, chain / "file.txt")); + Require(refused(link_nid_postfix(at(fileLink(33)).c_str(), copy.c_str()))); + Require(refused(linkat_nid_postfix(-100, at(fileLink(33)).c_str(), -100, copy.c_str(), 0x400))); + Require(linkat_nid_postfix(-100, at(fileLink(33)).c_str(), -100, copy.c_str(), 0) == 0 && RemoveSymlink(copy)); + Require(linksTo(at(folderLink(32) + "/file.txt"), copy, folder / "file.txt")); + Require(refused(link_nid_postfix(at(folderLink(33) + "/file.txt").c_str(), copy.c_str()))); + Require(refused(linkat_nid_postfix(-100, at(folderLink(33) + "/file.txt").c_str(), -100, copy.c_str(), 0))); + Require(symlink_nid_postfix("file.txt", (folder / "inner").string().c_str()) == 0); + Require(linksTo(at(folderLink(31) + "/inner"), copy, folder / "file.txt")); + Require(refused(link_nid_postfix(at(folderLink(32) + "/inner").c_str(), copy.c_str()))); + Require(RemoveSymlink((folder / "inner").string())); + Require(linksTo(at("file.txt"), at(folderLink(32) + "/created.txt"), chain / "file.txt")); + Require(refused(link_nid_postfix(at("file.txt").c_str(), at(folderLink(33) + "/created.txt").c_str()))); + Require(refused(linkat_nid_postfix(-100, at("file.txt").c_str(), -100, at(folderLink(33) + "/created.txt").c_str(), 0x400))); +#ifndef _WIN32 + const int chainDirectory = sceKernelOpen(chain.string().c_str(), SCE_KERNEL_O_RDONLY | SCE_KERNEL_O_DIRECTORY, 0); + Require(chainDirectory >= 0); + const auto belowMissing = "missing/../" + fileLink(33); + Require(linkat_nid_postfix(chainDirectory, belowMissing.c_str(), -100, copy.c_str(), 0x400) == -1 && Error() == 2); + const auto belowFile = "file.txt/../" + fileLink(33); + Require(linkat_nid_postfix(chainDirectory, belowFile.c_str(), -100, copy.c_str(), 0x400) == -1 && Error() == 20); + Require(sceKernelClose(chainDirectory) == 0); +#endif + Require(!std::filesystem::exists(copy) && !std::filesystem::exists(folder / "created.txt")); + for (int index = 1; index <= 33; ++index) Require(RemoveSymlink(at(fileLink(index))) && RemoveSymlink(at(folderLink(index)))); + Require(std::filesystem::remove(folder / "file.txt") && std::filesystem::remove(folder)); + Require(std::filesystem::remove(chain / "file.txt") && std::filesystem::remove(chain)); +} + UserUcontext sigreturnContext{}; UserUcontext sigreturnResume{}; volatile int sigreturnPasses = 0; @@ -465,15 +581,19 @@ void CheckSchedSetscheduler() { Require(sched_setscheduler_nid_postfix(0, 1, nullptr) == -1 && Error() == 14); Require(sched_setscheduler_nid_postfix(getpid_nid_postfix() + 1, 1, ¶m) == -1 && Error() == 3); Require(sched_setscheduler_nid_postfix(-1, 1, ¶m) == -1 && Error() == 3); - for (const int policy : {-1, 0, 4, 7}) Require(sched_setscheduler_nid_postfix(0, policy, ¶m) == -1 && Error() == 22); + Require(sched_setscheduler_nid_postfix(0, 1, reinterpret_cast(std::uintptr_t{0x10})) == -1 && Error() == 14); + Require(sched_setscheduler_nid_postfix(getpid_nid_postfix() + 1, 1, reinterpret_cast(std::uintptr_t{0x10})) == -1 && Error() == 14); + for (const int pid : {0, getpid_nid_postfix()}) { + for (const int policy : {-1, 0, 1, 2, 3, 4, 7}) { + Require(sched_setscheduler_nid_postfix(pid, policy, ¶m) == -1 && Error() == 1); + param.sched_priority = 300; + Require(sched_setscheduler_nid_postfix(pid, policy, ¶m) == -1 && Error() == 1); + param.sched_priority = 700; + } + } int policy = 0; KernelSchedParam current{}; - Require(sched_setscheduler_nid_postfix(0, 1, ¶m) == 0); - Require(scePthreadGetschedparam(self, &policy, ¤t) == 0 && policy == 1 && current.sched_priority == 700); - param.sched_priority = 300; - Require(sched_setscheduler_nid_postfix(0, 3, ¶m) == 0); - Require(scePthreadGetschedparam(self, &policy, ¤t) == 0 && policy == 3 && current.sched_priority == 300); - Require(scePthreadSetschedparam(self, savedPolicy, &saved) == 0); + Require(scePthreadGetschedparam(self, &policy, ¤t) == 0 && policy == savedPolicy && current.sched_priority == saved.sched_priority); } void CheckCondattrClock() { @@ -489,7 +609,7 @@ void CheckCondattrClock() { 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; } + try { scePthreadCondattrGetclock(&attr, nullptr); } catch (const std::invalid_argument&) { getclockThrew = true; } Require(getclockThrew); Require(scePthreadCondattrDestroy(&attr) == 0 && attr == nullptr); clock = -1; @@ -1016,19 +1136,36 @@ void CheckDescriptorPathconf(F fn, int descriptor) { Require(fn(descriptor + 1000, 4) == -1 && Error() == 9); } +template +void CheckPipeBufferPathconf(F fn, int descriptor) { + Require(fn(descriptor, 6) == 512); + Require(fn(descriptor, 53) == 200112); + for (const int name : {0, 1, 4, 5, 7, 8, 12, 18, 54, 55, 61, -1}) + Require(fn(descriptor, name) == -1 && Error() == 22); +} + 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); - } + for (const int end : ends) CheckPipeBufferPathconf(fn, end); Require(close_nid_postfix(ends[0]) == 0 && close_nid_postfix(ends[1]) == 0); } +template +void CheckSocketPathconf(F fn) { + for (const int type : {1, 2}) { + const int descriptor = socket_nid_postfix(2, type, 0); + Require(descriptor >= 0); + CheckPipeBufferPathconf(fn, descriptor); + Require(close_nid_postfix(descriptor) == 0); + const int net = sceNetSocket("pathconf", 2, type, 0); + Require(net >= 0); + CheckPipeBufferPathconf(fn, net); + Require(sceNetSocketClose(net) == 0); + } +} + int Kenv(int what, const char* name, const char* value, int len) { return kenv_nid_postfix(what, name, const_cast(value), len); } @@ -1037,89 +1174,34 @@ void CheckKernelEnvironment() { constexpr int get = 0, set = 1, unset = 2, dump = 3; char buffer[300]; std::memset(buffer, 0x5a, sizeof(buffer)); + const std::string longName(129, 'n'); + const std::string longestName(128, 'n'); + const std::string longValue(1000, 'v'); Require(Kenv(dump, nullptr, nullptr, 0) == 0); + Require(Kenv(dump, nullptr, buffer, sizeof(buffer)) == 0 && buffer[0] == 0x5a); + Require(Kenv(dump, nullptr, reinterpret_cast(0x10), sizeof(buffer)) == 0); + Require(Kenv(dump, reinterpret_cast(0x10), buffer, -1) == 0); Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 2); - Require(Kenv(unset, "anyps5.alpha", nullptr, 0) == -1 && Error() == 2); - Require(Kenv(4, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 22); - Require(Kenv(-1, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 22); - Require(Kenv(set, "anyps5.alpha", "one", 0) == -1 && Error() == 22); - Require(Kenv(set, "anyps5.alpha", "one", 3) == -1 && Error() == 63); - Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 2); - Require(Kenv(set, "anyps5.negative", "one", -1) == 0); - Require(Kenv(get, "anyps5.negative", buffer, sizeof(buffer)) == 4 && std::strcmp(buffer, "one") == 0); - Require(Kenv(set, "anyps5.negative", std::string(128, 'v').c_str(), -1) == 0); - Require(Kenv(set, "anyps5.negative", std::string(129, 'v').c_str(), -1) == -1 && Error() == 63); - Require(Kenv(unset, "anyps5.negative", nullptr, 0) == 0); - - Require(Kenv(set, "anyps5.alpha", "one", 4) == 0); - Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == 4 && std::strcmp(buffer, "one") == 0); - Require(Kenv(get, "anyps5", buffer, sizeof(buffer)) == -1 && Error() == 2); - Require(Kenv(get, "anyps5.alph", buffer, sizeof(buffer)) == -1 && Error() == 2); - Require(Kenv(get, "anyps5.alphaa", buffer, sizeof(buffer)) == -1 && Error() == 2); - std::memset(buffer, 0x5a, sizeof(buffer)); - Require(Kenv(get, "anyps5.alpha", buffer, 2) == 2 && buffer[0] == 'o' && buffer[1] == 'n' && buffer[2] == 0x5a); - Require(Kenv(get, "anyps5.alpha", buffer, 0) == 0 && buffer[2] == 0x5a); - Require(Kenv(get, "anyps5.alpha", nullptr, 0) == 0); - Require(Kenv(get, "anyps5.alpha", nullptr, 4) == -1 && Error() == 14); - Require(Kenv(get, "anyps5.alpha", reinterpret_cast(0x10), 4) == -1 && Error() == 14); - Require(Kenv(set, "anyps5.alpha", "", 1) == 0); - Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == 1 && buffer[0] == '\0'); - Require(Kenv(set, "anyps5.alpha", "one", 129) == 0); - Require(Kenv(set, "anyps5.beta", "two", 1000) == 0); - Require(Kenv(set, "anyps5.alpha", "uno", 4) == 0); - Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == 4 && std::strcmp(buffer, "uno") == 0); - - constexpr char expected[] = "anyps5.alpha=uno\0anyps5.beta=two"; - constexpr int needed = sizeof(expected); - Require(Kenv(dump, nullptr, nullptr, 0) == needed); - Require(Kenv(dump, nullptr, buffer, 0) == needed); - Require(Kenv(dump, nullptr, nullptr, sizeof(buffer)) == needed); - std::memset(buffer, 0x5a, sizeof(buffer)); - Require(Kenv(dump, nullptr, buffer, sizeof(buffer)) == 0 && std::memcmp(buffer, expected, needed) == 0 && buffer[needed] == 0x5a); - Require(Kenv(dump, nullptr, buffer, needed) == 0); - std::memset(buffer, 0x5a, sizeof(buffer)); - Require(Kenv(dump, nullptr, buffer, 20) == needed && std::memcmp(buffer, expected, 20) == 0 && buffer[20] == 0x5a); - Require(Kenv(dump, nullptr, reinterpret_cast(0x10), sizeof(buffer)) == -1 && Error() == 14); - Require(Kenv(dump, reinterpret_cast(0x10), buffer, sizeof(buffer)) == 0); - + Require(Kenv(get, longestName.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 2); + Require(Kenv(get, longName.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 63); Require(Kenv(get, nullptr, buffer, sizeof(buffer)) == -1 && Error() == 14); Require(Kenv(get, reinterpret_cast(0x10), buffer, sizeof(buffer)) == -1 && Error() == 14); + Require(Kenv(4, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 22); + Require(Kenv(-1, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 22); Require(Kenv(4, nullptr, buffer, sizeof(buffer)) == -1 && Error() == 14); - Require(Kenv(set, "anyps5.gamma", nullptr, 4) == -1 && Error() == 14); - Require(Kenv(set, "anyps5.gamma", reinterpret_cast(0x10), 4) == -1 && Error() == 14); - Require(Kenv(get, "anyps5.gamma", buffer, sizeof(buffer)) == -1 && Error() == 2); - - const std::string longest(128, 'n'); - const std::string longestValue(128, 'v'); - Require(Kenv(set, (longest + "x").c_str(), "v", 2) == -1 && Error() == 63); - Require(Kenv(get, (longest + "x").c_str(), buffer, sizeof(buffer)) == -1 && Error() == 63); - Require(Kenv(set, longest.c_str(), longestValue.c_str(), 129) == 0); - Require(Kenv(get, longest.c_str(), buffer, sizeof(buffer)) == 129 && std::strcmp(buffer, longestValue.c_str()) == 0); - Require(Kenv(set, "anyps5.gamma", (longestValue + "v").c_str(), 1000) == -1 && Error() == 63); - Require(Kenv(set, "anyps5.gamma", longestValue.c_str(), 128) == -1 && Error() == 63); - Require(Kenv(unset, longest.c_str(), nullptr, 0) == 0); - Require(Kenv(get, longest.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 2); - - Require(Kenv(set, "anyps5.eq=name", "tail", 5) == 0); - Require(Kenv(get, "anyps5.eq", buffer, sizeof(buffer)) == 10 && std::strcmp(buffer, "name=tail") == 0); - Require(Kenv(unset, "anyps5.eq", nullptr, 0) == 0); - Require(Kenv(unset, "anyps5.eq", nullptr, 0) == -1 && Error() == 2); - Require(Kenv(unset, "anyps5.alpha", nullptr, 0) == 0); + for (const int what : {set, unset}) { + Require(Kenv(what, "anyps5.alpha", "one", 4) == -1 && Error() == 1); + Require(Kenv(what, "anyps5.alpha", "one", 0) == -1 && Error() == 1); + Require(Kenv(what, "anyps5.alpha", "one", -1) == -1 && Error() == 1); + Require(Kenv(what, "anyps5.alpha", longValue.c_str(), 1000) == -1 && Error() == 1); + Require(Kenv(what, "anyps5.alpha", nullptr, 4) == -1 && Error() == 1); + Require(Kenv(what, "anyps5.alpha", reinterpret_cast(0x10), 4) == -1 && Error() == 1); + Require(Kenv(what, nullptr, "one", 4) == -1 && Error() == 1); + Require(Kenv(what, reinterpret_cast(0x10), "one", 4) == -1 && Error() == 1); + Require(Kenv(what, longName.c_str(), "one", 4) == -1 && Error() == 1); + Require(Kenv(what, "", "one", 4) == -1 && Error() == 1); + } Require(Kenv(get, "anyps5.alpha", buffer, sizeof(buffer)) == -1 && Error() == 2); - Require(Kenv(dump, nullptr, nullptr, 0) == static_cast(sizeof("anyps5.beta=two"))); - Require(Kenv(unset, "anyps5.beta", nullptr, 0) == 0); - Require(Kenv(dump, nullptr, nullptr, 0) == 0); - - for (int i = 0; i < 512; ++i) Require(Kenv(set, ("anyps5.fill" + std::to_string(i)).c_str(), "x", 2) == 0); - Require(Kenv(set, "anyps5.overflow", "x", 2) == 0); - Require(Kenv(get, "anyps5.overflow", buffer, sizeof(buffer)) == -1 && Error() == 2); - Require(Kenv(set, "anyps5.fill0", "y", 2) == 0); - Require(Kenv(get, "anyps5.fill0", buffer, sizeof(buffer)) == 2 && buffer[0] == 'y'); - Require(Kenv(unset, "anyps5.fill511", nullptr, 0) == 0); - Require(Kenv(set, "anyps5.overflow", "x", 2) == 0); - Require(Kenv(get, "anyps5.overflow", buffer, sizeof(buffer)) == 2); - for (int i = 0; i < 511; ++i) Require(Kenv(unset, ("anyps5.fill" + std::to_string(i)).c_str(), nullptr, 0) == 0); - Require(Kenv(unset, "anyps5.overflow", nullptr, 0) == 0); Require(Kenv(dump, nullptr, nullptr, 0) == 0); } @@ -1142,6 +1224,8 @@ int main() { Require(profil_nid_postfix(nullptr, 0, 0, 0xFFFFFFFFu) == -1 && Error() == 22); Require(profil_nid_postfix(reinterpret_cast(0x10), 4096, 0, 0) == 0); Require(profil_nid_postfix(nullptr, 0, 0, 0) == 0); + Require(is_in_sandbox_nid_postfix() == 1); + Require(sceKernelIsInSandbox() == 1); Require(umask_nid_postfix(0) == 022); Require(umask_nid_postfix(07777) == 0); Require(umask_nid_postfix(022) == 07777); @@ -1160,11 +1244,13 @@ int main() { Require(std::filesystem::create_directory(root)); DirectoryRenameTests(root); OwnershipAndLinkTests(root); + CheckLinkSymbolicLinkLimit(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); + ThreadAndSessionTests(descriptor); KernelStatfs publicStats{}; auto* viaPublic = reinterpret_cast(&publicStats); KernelStatfs syscallStats{}; @@ -1182,6 +1268,9 @@ int main() { CheckDescriptorPathconf(_fpathconf_nid_postfix, descriptor); CheckPipePathconf(fpathconf_nid_postfix); CheckPipePathconf(_fpathconf_nid_postfix); + Require(sceNetInit_nid_postfix() == 0); + CheckSocketPathconf(fpathconf_nid_postfix); + CheckSocketPathconf(_fpathconf_nid_postfix); const int kqueueDescriptor = kqueue_nid_postfix(); Require(kqueueDescriptor >= 0); CheckUnsupportedPathconf(fpathconf_nid_postfix, kqueueDescriptor); @@ -1414,6 +1503,18 @@ int main() { Require(kldfind_nid_postfix("") == -1 && Error() == 2); Require(kldfind_nid_postfix("kernel") == -1 && Error() == 2); Require(kldfind_nid_postfix("missing.ko") == -1 && Error() == 2); + Require(kldload_nid_postfix(nullptr) == -1 && Error() == 14); + Require(kldload_nid_postfix(reinterpret_cast(0x10)) == -1 && Error() == 14); + Require(kldload_nid_postfix(("/" + std::string(1024, 'p')).c_str()) == -1 && Error() == 63); + const auto kldDirectory = "/" + root.filename().generic_string(); + const auto kldHeader = (root / "kld-header.ko").string(); + std::ofstream(kldHeader, std::ios::binary | std::ios::trunc) << "XELF"; + for (const std::string name : {std::string(), std::string("nomodule"), std::string("missing.ko"), kldDirectory + "/missing.ko", + kldDirectory + "/file.txt", kldDirectory, kldDirectory + "/file.txt/child", kldDirectory + "/kld-header.ko"}) { + *__error_nid_postfix() = 0; + Require(kldload_nid_postfix(name.c_str()) == -1 && Error() == 1); + } + Require(std::filesystem::remove(kldHeader)); Require(kldnext_nid_postfix(-1) == -1 && Error() == 2); Require(kldnext_nid_postfix(0) == 0); Require(kldnext_nid_postfix(1) == -1 && Error() == 2); @@ -1510,6 +1611,9 @@ int main() { Require(lchflags_nid_postfix((file + "/child").c_str(), 0) == -1 && Error() == 20); Require(lchflags_nid_postfix(file.c_str(), 0x20) == -1 && Error() == 45); Require(lchflags_nid_postfix(file.c_str(), 0x200000) == -1 && Error() == 1); + Require(lchflags_nid_postfix(file.c_str(), 0x20000) == -1 && Error() == 1); + Require(lchflags_nid_postfix(file.c_str(), 0x2 | 0x40000) == -1 && Error() == 1); + Require(lchflags_nid_postfix(file.c_str(), 0x40000 | 0x20) == -1 && Error() == 45); const auto symbolic = (root / "symbolic").string(); const int created = symlink_nid_postfix("original.bin", symbolic.c_str()); if (created == 0) { @@ -1841,6 +1945,54 @@ int main() { Require(fchmodat_nid_postfix(base, "chmod-link.txt/child", 0600, 0) == -1 && Error() == 20); Require(RemoveSymlink(chmodLink) && RemoveSymlink(chmodDangling) && RemoveSymlink(chmodLoopA) && RemoveSymlink(chmodLoopB)); } + const auto prefixDangling = (root / "prefix-dangling").string(); + const auto prefixLoopA = (root / "prefix-loop-a").string(); + const auto prefixLoopB = (root / "prefix-loop-b").string(); + if (symlink_nid_postfix("prefix-missing", prefixDangling.c_str()) == 0) { + Require(symlink_nid_postfix("prefix-loop-b", prefixLoopA.c_str()) == 0 && symlink_nid_postfix("prefix-loop-a", prefixLoopB.c_str()) == 0); + FileStat prefixStatus{}; + char prefixBuffer[16]{}; + const auto belowDangling = prefixDangling + "/child"; + const auto belowLoop = prefixLoopA + "/child"; + Require(stat_nid_postfix(belowDangling.c_str(), &prefixStatus) == -1 && Error() == 2); + Require(lstat_nid_postfix(belowDangling.c_str(), &prefixStatus) == -1 && Error() == 2); + Require(chmod_nid_postfix(belowDangling.c_str(), 0600) == -1 && Error() == 2); + Require(utimes_nid_postfix(belowDangling.c_str(), nullptr) == -1 && Error() == 2); + Require(unlink_nid_postfix(belowDangling.c_str()) == -1 && Error() == 2); + Require(open_nid_postfix(belowDangling.c_str(), 0, 0) == -1 && Error() == 2); + Require(fchmodat_nid_postfix(base, "prefix-dangling/child", 0600, 0) == -1 && Error() == 2); + Require(readlink_nid_postfix(belowDangling.c_str(), prefixBuffer, sizeof(prefixBuffer)) == -1 && Error() == 2); + Require(linkat_nid_postfix(-100, belowDangling.c_str(), -100, (root / "prefix-linked").string().c_str(), 0x400) == -1 && Error() == 2); + Require(stat_nid_postfix(belowLoop.c_str(), &prefixStatus) == -1 && Error() == 62); + const auto prefixThroughFile = (root / "prefix-through-file").string(); + Require(symlink_nid_postfix("file.txt/x", prefixThroughFile.c_str()) == 0); + const auto belowThroughFile = prefixThroughFile + "/child"; + Require(stat_nid_postfix(belowThroughFile.c_str(), &prefixStatus) == -1 && Error() == 20); + Require(chmod_nid_postfix(belowThroughFile.c_str(), 0600) == -1 && Error() == 20); + Require(fchmodat_nid_postfix(base, "prefix-through-file/child", 0600, 0) == -1 && Error() == 20); +#ifdef _WIN32 + std::filesystem::create_directories(root / "prefix-up" / "deep"); + { std::ofstream file(root / "prefix-up" / "f2"); file << "x"; } + const auto upDirectory = (root / "prefix-up-link").string(); + const auto upFile = (root / "prefix-up" / "deep" / "pa").string(); + const auto createHostLink = [](const std::string& link, const wchar_t* target, DWORD flags) { + return CreateSymbolicLinkW(std::filesystem::path(link).c_str(), target, flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0; + }; + Require(createHostLink(upDirectory, L"prefix-up\\deep", SYMBOLIC_LINK_FLAG_DIRECTORY) && createHostLink(upFile, L"..\\f2\\x", 0)); + const auto belowUp = upDirectory + "/pa/child"; + Require(stat_nid_postfix(belowUp.c_str(), &prefixStatus) == -1 && Error() == 20); + Require(RemoveSymlink(upFile) && RemoveSymlink(upDirectory)); +#endif + const auto prefixChainEnd = (root / "prefix-chain-end").string(); + const auto prefixChainStart = (root / "prefix-chain-start").string(); + Require(symlink_nid_postfix("file.txt/x", prefixChainEnd.c_str()) == 0); + Require(symlink_nid_postfix("prefix-chain-end", prefixChainStart.c_str()) == 0); + const auto belowChain = prefixChainStart + "/child"; + Require(stat_nid_postfix(belowChain.c_str(), &prefixStatus) == -1 && Error() == 20); + Require(RemoveSymlink(prefixChainStart) && RemoveSymlink(prefixChainEnd)); + Require(RemoveSymlink(prefixThroughFile)); + Require(RemoveSymlink(prefixDangling) && RemoveSymlink(prefixLoopA) && RemoveSymlink(prefixLoopB)); + } Require(fchown_nid_postfix(base, 0, 0) == 0); Require(fchown_nid_postfix(base, 0xFFFFFFFFu, 0xFFFFFFFFu) == 0); Require(fchown_nid_postfix(base, 0, 0xFFFFFFFFu) == 0); diff --git a/core/libs/tests/GuestKernelSysAio.cpp b/core/libs/tests/GuestKernelSysAio.cpp index 200d3290b8..94fffb82f7 100644 --- a/core/libs/tests/GuestKernelSysAio.cpp +++ b/core/libs/tests/GuestKernelSysAio.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include #include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include namespace { @@ -183,6 +185,50 @@ void SyncOrder(int file) { Require(aio_waitcomplete_nid_postfix(&done, &badTime) == -1 && Error() == Einval); } +void TimedWaits(int file) { + using Clock = std::chrono::steady_clock; + char buffer[4] = {}; + AioHoldWorkerForTest_nid_no_patch(true); + auto held = Request(file, buffer, sizeof(buffer)); + Require(aio_read_nid_postfix(&held) == 0); + const GuestAiocb* list[] = {&held}; + const KernelTimespec millisecond{0, 1000000}; + auto start = Clock::now(); + for (int round = 0; round < 20; ++round) Require(aio_suspend_nid_postfix(list, 1, &millisecond) == -1 && Error() == Eagain); + auto elapsed = Clock::now() - start; + Require(elapsed >= std::chrono::milliseconds(20) && elapsed < std::chrono::milliseconds(200)); + + GuestAiocb* done = nullptr; + start = Clock::now(); + for (int round = 0; round < 20; ++round) Require(aio_waitcomplete_nid_postfix(&done, &millisecond) == -1 && Error() == Eagain); + elapsed = Clock::now() - start; + Require(elapsed >= std::chrono::milliseconds(20) && elapsed < std::chrono::milliseconds(200)); + + std::atomic stop{false}; + std::thread waker([&] { + char other[4] = {}; + const auto end = Clock::now() + std::chrono::milliseconds(600); + while (!stop.load() && Clock::now() < end) { + auto request = Request(file, other, sizeof(other)); + Require(aio_read_nid_postfix(&request) == 0); + Require(aio_cancel_nid_postfix(file, &request) == AioCanceled); + Require(aio_return_nid_postfix(&request) == -1 && Error() == Ecanceled); + std::this_thread::yield(); + } + }); + const KernelTimespec brief{0, 30000000}; + start = Clock::now(); + Require(aio_suspend_nid_postfix(list, 1, &brief) == -1 && Error() == Eagain); + elapsed = Clock::now() - start; + stop = true; + waker.join(); + Require(elapsed >= std::chrono::milliseconds(30) && elapsed < std::chrono::milliseconds(300)); + + Require(aio_cancel_nid_postfix(file, &held) == AioCanceled); + Require(aio_return_nid_postfix(&held) == -1 && Error() == Ecanceled); + AioHoldWorkerForTest_nid_no_patch(false); +} + } int main() { @@ -195,6 +241,7 @@ int main() { ReadAndWrite(file); Cancel(file); SyncOrder(file); + TimedWaits(file); Require(close_nid_postfix(readOnly) == 0 && close_nid_postfix(file) == 0); Require(std::filesystem::remove(path)); std::puts("guest_kernel_sys_aio passed"); diff --git a/core/libs/tests/GuestKernelSysBarrier.cpp b/core/libs/tests/GuestKernelSysBarrier.cpp index 084228c772..eec8e7bc91 100644 --- a/core/libs/tests/GuestKernelSysBarrier.cpp +++ b/core/libs/tests/GuestKernelSysBarrier.cpp @@ -34,20 +34,41 @@ static void* APS5_VABI Waiter(void* arg) { int main() { PthreadBarrierattrPrivate* attr = nullptr; + Require(scePthreadBarrierattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierattrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL); + Require(pthread_barrierattr_destroy_nid_postfix(nullptr) == 22); + Require(pthread_barrierattr_destroy_nid_postfix(&attr) == 22); 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(pthread_barrierattr_setpshared_nid_postfix(&attr, -1) == 22); + Require(pthread_barrierattr_setpshared_nid_postfix(&attr, 2) == 22); + Require(pthread_barrierattr_setpshared_nid_postfix(&attr, 9999) == 22); + Require(pthread_barrierattr_setpshared_nid_postfix(&attr, PROCESS_SHARED) == 22); + Require(scePthreadBarrierattrSetpshared(&attr, -1) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierattrSetpshared(&attr, 2) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierattrSetpshared(&attr, 9999) == SCE_KERNEL_ERROR_EINVAL); 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, 0, "zero") == SCE_KERNEL_ERROR_EINVAL && context.barrier == nullptr); + Require(scePthreadBarrierInit(&context.barrier, nullptr, 0, "zero") == SCE_KERNEL_ERROR_EINVAL && context.barrier == nullptr); + Require(scePthreadBarrierInit(nullptr, &attr, 0, "null") == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadBarrierInit(nullptr, nullptr, 0, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(pthread_barrier_init_nid_postfix(&context.barrier, &attr, 0) == 22 && context.barrier == nullptr); + Require(pthread_barrier_init_nid_postfix(&context.barrier, nullptr, 0) == 22 && context.barrier == nullptr); + Require(pthread_barrier_init_nid_postfix(nullptr, &attr, 0) == 22); + Require(pthread_barrier_init_nid_postfix(nullptr, nullptr, 0) == 22); 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); + Require(scePthreadBarrierattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(pthread_barrierattr_destroy_nid_postfix(&attr) == 22); + Require(pthread_barrierattr_destroy_nid_postfix(nullptr) == 22); for (int round = 0; round < 2; ++round) { context.serial = 0; @@ -62,6 +83,8 @@ int main() { Require(scePthreadBarrierWait(nullptr) == SCE_KERNEL_ERROR_EINVAL); PthreadBarrierPrivate* single = nullptr; + Require(scePthreadBarrierInit(&single, nullptr, 0, nullptr) == SCE_KERNEL_ERROR_EINVAL && single == nullptr); + Require(pthread_barrier_init_nid_postfix(&single, nullptr, 0) == 22 && single == nullptr); Require(scePthreadBarrierInit(&single, nullptr, 1, nullptr) == 0); Require(scePthreadBarrierWait(&single) == BARRIER_SERIAL_THREAD); Require(scePthreadBarrierDestroy(&single) == 0); diff --git a/core/libs/tests/GuestKernelSysConstantUnknowns.cpp b/core/libs/tests/GuestKernelSysConstantUnknowns.cpp new file mode 100644 index 0000000000..edd2d3811c --- /dev/null +++ b/core/libs/tests/GuestKernelSysConstantUnknowns.cpp @@ -0,0 +1,26 @@ +#include "prx/libkernel_sys/include/AdditionalExports.hpp" + +#include +#include +#include + +static void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +int main() { + Require(libkernel_sysUnknown_pCbBddNl66Y_nid_no_patch() == 0, "pCbBddNl66Y returns 0"); + Require(libkernel_sysUnknown_zAloppPJxy0_nid_no_patch() == 0, "zAloppPJxy0 returns 0"); + Require(libkernel_sysUnknown_vq2_plus_HyP5U5I_nid_no_patch() == 0, "vq2+HyP5U5I returns 0"); + Require(libkernel_sysUnknown_z_plus_e0vV_minus_tnH4_nid_no_patch() == 0, "z+e0vV-tnH4 returns 0"); + Require(libkernel_sysUnknown_gJ5gpW45mU0_nid_no_patch() == 0, "gJ5gpW45mU0 returns 0"); + Require(libkernel_sysUnknown_N94a0T4F4_plus_U_nid_no_patch() == 0x8002004eu, "N94a0T4F4+U returns 0x8002004e zero-extended"); + Require(libkernel_sysUnknown_C49jelxiaVE_nid_no_patch() == 0x20018u, "C49jelxiaVE returns 0x20018"); + Require(libkernel_sysUnknown_FR0odl35yQY_nid_no_patch() == 0, "FR0odl35yQY reads 0 before the flag is set"); + Require(libkernel_sysUnknown_1WXQlxMpnNA_nid_no_patch() == 0, "1WXQlxMpnNA returns 0"); + Require(libkernel_sysUnknown_FR0odl35yQY_nid_no_patch() == 1, "FR0odl35yQY reads 1 after 1WXQlxMpnNA"); + Require(libkernel_sysUnknown_1WXQlxMpnNA_nid_no_patch() == 0, "a second 1WXQlxMpnNA returns 0"); + Require(libkernel_sysUnknown_FR0odl35yQY_nid_no_patch() == 1, "the flag stays set"); +} diff --git a/core/libs/tests/GuestKernelSysCpuset.cpp b/core/libs/tests/GuestKernelSysCpuset.cpp index 93f6e5c4ff..1c95832857 100644 --- a/core/libs/tests/GuestKernelSysCpuset.cpp +++ b/core/libs/tests/GuestKernelSysCpuset.cpp @@ -107,5 +107,30 @@ int main() { Require(cpuset_getid_nid_postfix(LevelCpuset, WhichProcess, -1, &setid) == 0); Require(setid == 0); + Require(cpuset_getid_nid_postfix(LevelRoot, WhichCpuset, -1, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, -1, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelWhich, WhichCpuset, -1, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, 999999, &setid) == -1 && Error() == Esrch); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, 4294967296LL, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, -4294967296LL, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, 4294967296LL + 999999, &setid) == -1 && Error() == Esrch); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, 0, nullptr) == -1 && Error() == Efault); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, -1, nullptr) == -1 && Error() == Efault); + Require(cpuset_getid_nid_postfix(LevelRoot, WhichCpuset, 0, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, 0, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelWhich, WhichCpuset, 0, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelRoot, WhichCpuset, 1, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, 1, &setid) == 0 && setid == 1); + Require(cpuset_getid_nid_postfix(LevelWhich, WhichCpuset, 1, &setid) == 0 && setid == 1); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, set2, &setid) == -1 && Error() == Esrch); + + int set3 = -1; + Require(cpuset_nid_postfix(&set3) == 0 && set3 >= 2); + Require(cpuset_getid_nid_postfix(LevelRoot, WhichCpuset, set3, &setid) == 0 && setid == 0); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, set3, &setid) == 0 && setid == set3); + Require(cpuset_getid_nid_postfix(LevelWhich, WhichCpuset, set3, &setid) == 0 && setid == set3); + Require(cpuset_setid_nid_postfix(WhichProcess, -1, 0) == 0); + Require(cpuset_getid_nid_postfix(LevelCpuset, WhichCpuset, set3, &setid) == -1 && Error() == Esrch); + return 0; } diff --git a/core/libs/tests/GuestKernelSysKtimer.cpp b/core/libs/tests/GuestKernelSysKtimer.cpp index bb189576b4..304bf49fa0 100644 --- a/core/libs/tests/GuestKernelSysKtimer.cpp +++ b/core/libs/tests/GuestKernelSysKtimer.cpp @@ -90,9 +90,13 @@ void InvalidIds() { 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_gettime_nid_postfix(bad, nullptr) == -1 && Error() == Einval); Require(ktimer_getoverrun_nid_postfix(bad) == -1 && Error() == Einval); } Require(ktimer_settime_nid_postfix(id, 0, nullptr, nullptr) == -1 && Error() == Efault); + const int valid = Create(); + Require(ktimer_gettime_nid_postfix(valid, nullptr) == -1 && Error() == Efault); + Require(ktimer_delete_nid_postfix(valid) == 0); } void Values() { @@ -102,6 +106,9 @@ void Values() { 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 valid{{0, 0}, {1, 0}}; + for (const int badFlags : {-1, 2, 4, ~TimerAbstime}) + Require(ktimer_settime_nid_postfix(id, badFlags, &valid, nullptr) == -1 && Error() == Einval); const GuestItimerspec clear{{-1, 0}, {0, 0}}; Require(ktimer_settime_nid_postfix(id, 0, &clear, nullptr) == 0); diff --git a/core/libs/tests/GuestKernelSysThreadDefaults.cpp b/core/libs/tests/GuestKernelSysThreadDefaults.cpp index f38f272d56..72b76c7593 100644 --- a/core/libs/tests/GuestKernelSysThreadDefaults.cpp +++ b/core/libs/tests/GuestKernelSysThreadDefaults.cpp @@ -3,6 +3,7 @@ #include "prx/libkernel/include/Api.hpp" #include "SceTypes.hpp" #include +#include #include #include @@ -67,8 +68,30 @@ static void CheckSetName() { Require(name[0] == '\0'); } +static void* APS5_VABI ReportFpuControl(void* out) { + asm volatile("fnstcw %0" : "=m"(*static_cast(out))); + return nullptr; +} + +static void CheckFpuControl() { + std::uint16_t creator = 0; + asm volatile("fnstcw %0" : "=m"(creator)); + const std::uint16_t doublePrecision = 0x027f; + asm volatile("fldcw %0" : : "m"(doublePrecision)); + std::uint16_t started = 0; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, ReportFpuControl, &started, "fpu") == 0); + Require(scePthreadJoin(thread, nullptr) == 0); + std::uint16_t creatorAfter = 0; + asm volatile("fnstcw %0" : "=m"(creatorAfter)); + asm volatile("fldcw %0" : : "m"(creator)); + Require(started == 0x037f); + Require(creatorAfter == doublePrecision); +} + int main() { CheckDefaultStackSize(); CheckSetName(); + CheckFpuControl(); return 0; } diff --git a/core/libs/tests/GuestKernelSysUuid.cpp b/core/libs/tests/GuestKernelSysUuid.cpp index 8977988dbb..c0e50dbd9a 100644 --- a/core/libs/tests/GuestKernelSysUuid.cpp +++ b/core/libs/tests/GuestKernelSysUuid.cpp @@ -38,6 +38,7 @@ int main() { Require(uuidgen_nid_postfix(first.data(), 0) == -1 && Error() == 22); Require(uuidgen_nid_postfix(first.data(), 2049) == -1 && Error() == 22); Require(uuidgen_nid_postfix(nullptr, 1) == -1 && Error() == 14); + Require(uuidgen_nid_postfix(reinterpret_cast(0x10), 1) == -1 && Error() == 14); Require(uuidgen_nid_postfix(first.data(), static_cast(first.size())) == 0); Require(uuidgen_nid_postfix(second.data(), static_cast(second.size())) == 0); for (const auto& uuid : first) { diff --git a/core/libs/tests/GuestKqueue.cpp b/core/libs/tests/GuestKqueue.cpp index 110c416f5b..ce01f4b2c6 100644 --- a/core/libs/tests/GuestKqueue.cpp +++ b/core/libs/tests/GuestKqueue.cpp @@ -31,6 +31,8 @@ static constexpr int GuestEperm = 1; static constexpr int GuestEnoent = 2; static constexpr int GuestEbadf = 9; static constexpr int GuestEinval = 22; +static constexpr int GuestEspipe = 29; +static constexpr int GuestEopnotsupp = 45; static constexpr int GuestEpipe = 32; static constexpr int GuestEagain = 35; static constexpr int SetStatusFlags = 4; @@ -189,6 +191,30 @@ static void CreateAndClose() { Require(close_nid_postfix(pipe[0]) == 0 && close_nid_postfix(pipe[1]) == 0); } +static void SocketReplacesDescriptor() { + const int kq = kqueue_nid_postfix(); + int marker = 0; + Require(Apply(kq, Change(1, EvfiltTimer, EvAdd | EvOneshot, 0, 1, &marker)) == 0); + int pair[2]; + Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0); + Require(dup2_nid_postfix(pair[0], kq) == kq); + KernelEvent event{}; + RequireFailure(Wait(kq, &event, 1), GuestEbadf); + Require(close_nid_postfix(kq) == 0); + const int watcher = kqueue_nid_postfix(); + int pipe[2]; + Require(pipe_nid_postfix(pipe) == 0); + Require(write_nid_postfix(pipe[1], "x", 1) == 1); + Require(Apply(watcher, Change(static_cast(pipe[0]), EvfiltRead, EvAdd)) == 0); + Require(Poll(watcher, &event, 1) == 1); + Require(dup2_nid_postfix(pair[1], pipe[0]) == pipe[0]); + Require(Poll(watcher, &event, 1) == 0); + RequireFailure(Apply(watcher, Change(static_cast(pipe[0]), EvfiltRead, EvDelete)), GuestEnoent); + 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); + Require(close_nid_postfix(watcher) == 0); +} + static void Timers() { const int kq = kqueue_nid_postfix(); int marker = 0; @@ -1617,6 +1643,21 @@ static void StandardInput() { Require(close_nid_postfix(kq) == 0); } +static void KqueueDescriptorOperations() { + const int kq = kqueue_nid_postfix(); + Require(kq >= 0); + char byte = 0; + RequireFailure(static_cast(read_nid_postfix(kq, &byte, 1)), GuestEopnotsupp); + RequireFailure(static_cast(write_nid_postfix(kq, "x", 1)), GuestEopnotsupp); + Require(read_nid_postfix(kq, &byte, 0) == 0); + RequireFailure(static_cast(write_nid_postfix(kq, "x", 0)), GuestEopnotsupp); + RequireFailure(static_cast(lseek_nid_postfix(kq, 0, 0)), GuestEspipe); + RequireFailure(fsync_nid_postfix(kq), GuestEinval); + RequireFailure(fdatasync_nid_postfix(kq), GuestEinval); + RequireFailure(flock_nid_postfix(kq, 1), GuestEopnotsupp); + Require(close_nid_postfix(kq) == 0); +} + int main(int argc, char** argv) { if (argc == 2 && std::string(argv[1]) == "cleared-race") { ClearedKqueueKeepsConcurrentActivations(); @@ -1625,6 +1666,8 @@ int main(int argc, char** argv) { StandardInputReplacement(); StandardInput(); CreateAndClose(); + SocketReplacesDescriptor(); + KqueueDescriptorOperations(); Timers(); TimerReadd(); UnsupportedFilters(); diff --git a/core/libs/tests/GuestLibcInternalExceptionDestructors.cpp b/core/libs/tests/GuestLibcInternalExceptionDestructors.cpp new file mode 100644 index 0000000000..c8ddb4aadd --- /dev/null +++ b/core/libs/tests/GuestLibcInternalExceptionDestructors.cpp @@ -0,0 +1,94 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +namespace { + +unsigned releases = 0; +void* lastReleased = nullptr; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "libc internal exception destructors: %s\n", message); + std::abort(); +} + +void* APS5_VABI TestAllocate(std::size_t size) { return std::malloc(size); } +void APS5_VABI TestFree(void* pointer) { + ++releases; + lastReleased = pointer; + std::free(pointer); +} +void* APS5_VABI TestCalloc(std::size_t count, std::size_t size) { return std::calloc(count, size); } +void* APS5_VABI TestReallocate(void* pointer, std::size_t size) { return std::realloc(pointer, size); } +void* APS5_VABI TestAlign(std::size_t, std::size_t size) { return std::malloc(size); } +void* APS5_VABI TestRealign(void* pointer, std::size_t size, std::size_t) { return std::realloc(pointer, size); } +int APS5_VABI TestPosixAlign(void** pointer, std::size_t, std::size_t size) { + *pointer = std::malloc(size); + return *pointer ? 0 : 12; +} + +using Destructor = void (APS5_VABI*)(void*); + +void CheckMessageDestructor(Destructor destructor, const char* text) { + LibcException::ExceptionObject object{}; + _ZNSt13runtime_errorC1EPKc_nid_postfix(&object, text); + Require(object.message != nullptr && std::strcmp(object.message, text) == 0, "runtime_error keeps its message"); + destructor(&object); + Require(object.message == nullptr, "the base-object destructor releases the message"); + destructor(&object); + Require(object.message == nullptr, "a second destruction leaves the message released"); +} + +void CheckFutureError() { + LibcException::FutureErrorObject inline_{}; + inline_.message.capacity = 15; + inline_.message.size = 3; + std::memcpy(inline_.message.buffer, "abc", 4); + releases = 0; + sceLibcInternalFutureErrorBaseDestructor_nid_no_patch(&inline_); + Require(inline_.message.reserved == 0 && inline_.message.size == 0 && inline_.message.capacity == 15, "an inline message is reset"); + Require(inline_.message.buffer[0] == 0 && releases == 0, "an inline message is not released"); + + LibcException::FutureErrorObject heap{}; + heap.message.capacity = 24; + heap.message.size = 24; + heap.message.pointer = static_cast(_Znwm_nid_postfix(25)); + std::memcpy(heap.message.pointer, "future already retrieved", 25); + char* storage = heap.message.pointer; + releases = 0; + sceLibcInternalFutureErrorBaseDestructor_nid_no_patch(&heap); + Require(releases == 1 && lastReleased == storage, "a heap message is released"); + Require(heap.message.size == 0 && heap.message.capacity == 15 && heap.message.buffer[0] == 0, "a heap message is reset to the inline buffer"); + + LibcException::FutureErrorObject expected{}; + expected.message.capacity = 15; + LibcException::FutureErrorObject actual = expected; + _ZNSt12future_errorD1Ev_nid_postfix(&expected); + sceLibcInternalFutureErrorBaseDestructor_nid_no_patch(&actual); + Require(std::memcmp(&expected, &actual, sizeof(expected)) == 0, "D2 leaves the object as D1 does"); +} + +} + +int main() { + const std::array api{ + reinterpret_cast(&TestAllocate), reinterpret_cast(&TestFree), + reinterpret_cast(&TestCalloc), reinterpret_cast(&TestReallocate), + reinterpret_cast(&TestAlign), reinterpret_cast(&TestRealign), + reinterpret_cast(&TestPosixAlign), nullptr, nullptr, nullptr}; + ApplicationHeapRegister_nid_no_patch(api.data()); + + CheckMessageDestructor(&sceLibcInternalOverflowErrorBaseDestructor_nid_no_patch, "value too large"); + CheckMessageDestructor(&sceLibcInternalUnderflowErrorBaseDestructor_nid_no_patch, "value too small"); + CheckFutureError(); + + std::puts("libc internal exception base-object destructor checks passed"); + return 0; +} diff --git a/core/libs/tests/GuestLibcInternalPad.cpp b/core/libs/tests/GuestLibcInternalPad.cpp new file mode 100644 index 0000000000..d27aa7b0cc --- /dev/null +++ b/core/libs/tests/GuestLibcInternalPad.cpp @@ -0,0 +1,48 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "libc internal _Pad destructor: %s\n", message); + std::abort(); +} + +template +bool Throws(Function function) { + try { + function(); + } catch (const std::exception&) { + return true; + } + return false; +} + +} + +int main() { + GuestPad pad{}; + _ZNSt4_PadC2Ev_nid_postfix(&pad); + Require(pad.condition != nullptr && pad.mutex != nullptr && !pad.started, "the base-object constructor creates the mutex and condition"); + + sceLibcInternalPadCompleteDestructor_nid_no_patch(&pad); + Require(pad.condition == nullptr && pad.mutex == nullptr, "the complete-object destructor releases the mutex and condition"); + Require(!pad.started && pad.vtable == nullptr, "the destructor leaves the other members alone"); + + GuestPad again{}; + _ZNSt4_PadC2Ev_nid_postfix(&again); + sceLibcInternalPadCompleteDestructor_nid_no_patch(&again); + _ZNSt4_PadC2Ev_nid_postfix(&again); + Require(again.condition != nullptr && again.mutex != nullptr, "a destroyed pad can be constructed again"); + _ZNSt4_PadD2Ev_nid_postfix(&again); + + Require(Throws([&] { sceLibcInternalPadCompleteDestructor_nid_no_patch(&pad); }), "destroying a pad twice is rejected"); + Require(Throws([] { sceLibcInternalPadCompleteDestructor_nid_no_patch(nullptr); }), "a null pad is rejected"); + + std::puts("libc internal _Pad complete-object destructor checks passed"); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslBio.cpp b/core/libs/tests/GuestLibreSslBio.cpp new file mode 100644 index 0000000000..650b44b984 --- /dev/null +++ b/core/libs/tests/GuestLibreSslBio.cpp @@ -0,0 +1,564 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibreSsl/ErrorQueue.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef ANYPS5_BIO_DYNAMIC +#ifdef _WIN32 +#include +#else +#include +#endif +#endif + +using LibreSsl::Bio; +using LibreSsl::BioBuffer; +using LibreSsl::BioMethod; + + +#ifdef ANYPS5_BIO_DYNAMIC +namespace { +decltype(&BIO_s_mem_nid_postfix) bioMemoryMethod; +decltype(&BIO_new_nid_postfix) bioNew; +decltype(&BIO_new_mem_buf_nid_postfix) bioNewBuffer; +decltype(&BIO_free_nid_postfix) bioFree; +decltype(&BIO_ctrl_nid_postfix) bioControl; +decltype(&BIO_write_nid_postfix) bioWrite; +decltype(&BIO_puts_nid_postfix) bioPuts; +decltype(&calloc_nid_postfix) guestCalloc; +decltype(&free_nid_postfix) guestFree; +decltype(&ApplicationHeapRegister_nid_no_patch) registerHeap; +decltype(&ERR_clear_error_nid_postfix) clearError; +decltype(&ERR_peek_error_nid_postfix) peekError; +decltype(&ERR_get_error_nid_postfix) getError; + +void* Load(const std::filesystem::path& path) { +#ifdef _WIN32 + auto* module = LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); +#else + auto* module = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); +#endif + if (!module) throw std::runtime_error("failed to load patched BIO dependency: " + path.string()); + return module; +} + +template +void Resolve(TFunction& function, void* module, const char* name) { +#ifdef _WIN32 + function = reinterpret_cast(GetProcAddress(static_cast(module), name)); +#else + function = reinterpret_cast(dlsym(module, name)); +#endif + if (!function) throw std::runtime_error(std::string("missing patched BIO dependency: ") + name); +} + +void InitializePatched(const std::filesystem::path& directory) { + void* libc = Load(directory / "libc.prx"); + void* bio = Load(directory / "libSceLibreSsl.prx"); + Resolve(bioMemoryMethod, bio, "R86sXhW699g"); + Resolve(bioNew, bio, "ARlY6zhSacE"); + Resolve(bioNewBuffer, bio, "GMzjS9jvyDQ"); + Resolve(bioFree, bio, "lpJ3zhgftCg"); + Resolve(bioControl, bio, "RHpq9hQfTj8"); + Resolve(bioWrite, bio, "nH7XM+HhXWc"); + Resolve(bioPuts, bio, "eVcWmVlimPA"); + Resolve(guestCalloc, libc, "2X5agFjKxMc"); + Resolve(guestFree, libc, "tIhsqj0qsFE"); + Resolve(registerHeap, libc, "ApplicationHeapRegister_nid_no_patch"); + Resolve(clearError, bio, "sieQ--PINWg"); + Resolve(peekError, bio, "Aytjey6FPTU"); + Resolve(getError, bio, "DJg+qWX1rwY"); +} +} // namespace + +#define BIO_s_mem_nid_postfix bioMemoryMethod +#define BIO_new_nid_postfix bioNew +#define BIO_new_mem_buf_nid_postfix bioNewBuffer +#define BIO_free_nid_postfix bioFree +#define BIO_ctrl_nid_postfix bioControl +#define BIO_write_nid_postfix bioWrite +#define BIO_puts_nid_postfix bioPuts +#define calloc_nid_postfix guestCalloc +#define free_nid_postfix guestFree +#define ApplicationHeapRegister_nid_no_patch registerHeap +#define ERR_clear_error_nid_postfix clearError +#define ERR_peek_error_nid_postfix peekError +#define ERR_get_error_nid_postfix getError +#endif + +namespace { + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +int allocationsUntilFailure = -1; +std::atomic allocations{0}; +void* wipePointer = nullptr; +std::size_t wipeSize = 0; + +void* APS5_VABI Allocate(std::size_t) { throw std::logic_error("unexpected nonzeroed BIO allocation"); } +void* APS5_VABI Reallocate(void*, std::size_t) { throw std::logic_error("unexpected BIO reallocation"); } +void* APS5_VABI Align(std::size_t, std::size_t) { throw std::logic_error("unexpected BIO aligned allocation"); } +void* APS5_VABI Realign(void*, std::size_t, std::size_t) { throw std::logic_error("unexpected BIO realignment"); } +int APS5_VABI PosixAlign(void**, std::size_t, std::size_t) { throw std::logic_error("unexpected BIO aligned allocation"); } + +void* APS5_VABI Calloc(std::size_t count, std::size_t size) { + if (allocationsUntilFailure == 0) return nullptr; + if (allocationsUntilFailure > 0) --allocationsUntilFailure; + void* result = std::calloc(count, size); + if (result) ++allocations; + return result; +} + +void APS5_VABI Free(void* pointer) { + if (pointer && pointer == wipePointer) { + for (std::size_t i = 0; i < wipeSize; ++i) Require(static_cast(pointer)[i] == 0, "freed buffer was not wiped"); + wipePointer = nullptr; + } + if (pointer) --allocations; + std::free(pointer); +} + +void InitializeHeap() { + const std::array api{ + reinterpret_cast(Allocate), reinterpret_cast(Free), reinterpret_cast(Calloc), + reinterpret_cast(Reallocate), reinterpret_cast(Align), reinterpret_cast(Realign), + reinterpret_cast(PosixAlign)}; + ApplicationHeapRegister_nid_no_patch(api.data()); +} + +template +void RequireUnsupported(TFunction function) { + bool threw = false; + try { + function(); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "unsupported BIO path did not throw"); +} + +void RequireError(std::uint64_t expected) { + Require(ERR_peek_error_nid_postfix() == expected, "BIO error code or ordering"); + Require(ERR_get_error_nid_postfix() == expected, "consume BIO error"); + Require(ERR_get_error_nid_postfix() == 0, "unexpected extra BIO error"); +} + +BioBuffer* Buffer(Bio* bio) { + BioBuffer* buffer = nullptr; + Require(BIO_ctrl_nid_postfix(bio, 115, 0, &buffer) == 1 && buffer, "get buffer"); + return buffer; +} + +void RequireData(Bio* bio, const std::string& expected) { + char* bytes = nullptr; + Require(BIO_ctrl_nid_postfix(bio, 3, 0, &bytes) == static_cast(expected.size()), "buffer data length"); + Require(expected.empty() || (bytes && std::memcmp(bytes, expected.data(), expected.size()) == 0), "buffer data bytes"); + Require(BIO_ctrl_nid_postfix(bio, 10, 0, nullptr) == static_cast(expected.size()), "pending bytes"); + Require(BIO_ctrl_nid_postfix(bio, 2, 0, nullptr) == expected.empty(), "EOF flag"); +} + +void TestWritable() { + const auto* method = BIO_s_mem_nid_postfix(); + Require(method && method == BIO_s_mem_nid_postfix(), "stable method pointer"); + Require(method->type == 0x401 && std::strcmp(method->name, "memory buffer") == 0, "method identity"); + Require(method->write && method->read && method->puts && method->gets && method->control && method->create && method->destroy, "method callbacks"); + Require(method->callbackControl == nullptr, "no callback control method"); + Bio* bio = BIO_new_nid_postfix(method); + Require(bio && bio->method == method && bio->initialized == 1 && bio->references == 1, "new BIO state"); + Require(!bio->callback && !bio->callbackArgument && !bio->flags && !bio->retryReason && !bio->next && !bio->previous && !bio->extraData, "new BIO zero fields"); + Require(bio->number == -1 && bio->bytesRead == 0 && bio->bytesWritten == 0, "new BIO counters"); + Require(BIO_write_nid_postfix(bio, nullptr, 7) == -1, "null write reaches memory method"); + RequireError(0x20fff073); + Require(BIO_write_nid_postfix(bio, "x", 0) == 0, "zero-length owned write"); + RequireError(0); + RequireData(bio, ""); + auto* buffer = Buffer(bio); + Require(buffer->capacity == 0 && buffer->data == nullptr, "new buffer storage"); + char output[16]{}; + Require(method->read(bio, output, sizeof(output)) == -1 && (bio->flags & 0xf) == 9, "empty writable BIO retry"); + Require(BIO_write_nid_postfix(bio, "abc", 3) == 3 && (bio->flags & 0xf) == 0, "write clears retry flags"); + RequireData(bio, "abc"); + Require(buffer->capacity == 8 && buffer->data[3] == 0, "first buffer expansion"); + std::memset(buffer->data + buffer->length, 0xa5, buffer->capacity - buffer->length); + wipePointer = buffer->data; + wipeSize = buffer->capacity; + Require(BIO_write_nid_postfix(bio, "0123456789", 10) == 10, "second write"); + Require(wipePointer == nullptr, "growth frees the wiped allocation"); + Require(buffer->capacity == 20 && buffer->data[13] == 0, "second buffer expansion"); + RequireData(bio, "abc0123456789"); + Require(method->read(bio, output, 4) == 4 && std::memcmp(output, "abc0", 4) == 0, "partial memory read"); + RequireData(bio, "123456789"); + Require(BIO_puts_nid_postfix(bio, "xy") == 2 && bio->bytesWritten == 15, "puts and byte accounting"); + RequireData(bio, "123456789xy"); + Require(method->read(bio, nullptr, 3) == 3, "null raw read reports available count"); + RequireData(bio, "123456789xy"); + Require(method->read(bio, output, -3) == -3, "negative raw read"); + Require(BIO_write_nid_postfix(bio, nullptr, 5) == -1, "reject null public source"); + RequireError(0x20fff073); + RequireData(bio, "123456789xy"); + Require(BIO_ctrl_nid_postfix(bio, 11, 0, nullptr) == 1 && BIO_ctrl_nid_postfix(bio, 12, 0, nullptr) == 1, "flush and duplicate controls"); + Require(BIO_ctrl_nid_postfix(bio, 13, 0, nullptr) == 0, "no pending output"); + Require(BIO_ctrl_nid_postfix(bio, 6, 0, nullptr) == 0 && BIO_ctrl_nid_postfix(bio, 7, 0, nullptr) == 0, "push and pop controls"); + Require(BIO_ctrl_nid_postfix(bio, 0x7fffffff, 0, nullptr) == 0, "unknown memory control"); + Require(BIO_ctrl_nid_postfix(bio, 1, 0, nullptr) == 1, "reset writable BIO"); + RequireData(bio, ""); + for (std::size_t i = 0; i < buffer->capacity; ++i) Require(buffer->data[i] == 0, "reset wipes full allocation"); + Require(BIO_ctrl_nid_postfix(bio, 130, 7, nullptr) == 1, "set EOF return"); + Require(method->read(bio, output, 1) == 7 && (bio->flags & 0xf) == 9, "configured EOF return and retry"); + Require(BIO_ctrl_nid_postfix(bio, 130, 0, nullptr) == 1, "set terminal EOF"); + Require(method->read(bio, output, 1) == 0 && (bio->flags & 0xf) == 0, "terminal EOF clears retry"); + Require(BIO_puts_nid_postfix(bio, "secret") == 6, "write before destruction"); + wipePointer = buffer->data; + wipeSize = buffer->capacity; + Require(BIO_free_nid_postfix(bio) == 1, "free writable BIO"); + Require(wipePointer == nullptr, "destruction frees wiped storage"); +} + +void TestReadOnlyAndLines() { + const std::array source{'a', '\0', 'b', '\n', 'c', 'd', '\n', 'e'}; + Bio* bio = BIO_new_mem_buf_nid_postfix(source.data(), source.size()); + Require(bio && (bio->flags & 0x200) && bio->number == 0, "read-only BIO state"); + auto* buffer = Buffer(bio); + Require(buffer->data == source.data() && buffer->capacity == source.size(), "read-only buffer is borrowed"); + char output[16]; + std::memset(output, 0x55, sizeof(output)); + Require(bio->method->gets(bio, output, sizeof(output)) == 4 && std::memcmp(output, source.data(), 4) == 0 && output[4] == 0, "line includes embedded NUL and newline"); + Require(buffer->data == source.data() + 4 && buffer->length == 4, "borrowed buffer advances"); + Require(bio->method->gets(bio, output, 3) == 2 && std::strcmp(output, "cd") == 0, "line length bound"); + Require(bio->method->gets(bio, output, 1) == 0 && output[0] == 0 && buffer->length == 2, "one-byte line buffer"); + Require(bio->method->gets(bio, output, sizeof(output)) == 1 && std::strcmp(output, "\n") == 0, "newline on next read"); + Require(bio->method->read(bio, output, sizeof(output)) == 1 && output[0] == 'e', "last read-only byte"); + Require(bio->method->read(bio, output, 1) == 0 && (bio->flags & 0xf) == 0, "read-only EOF"); + Require(BIO_ctrl_nid_postfix(bio, 1, 0, nullptr) == 1, "read-only reset"); + Require(buffer->data == source.data() && buffer->length == source.size(), "read-only reset restores original range"); + Require(BIO_write_nid_postfix(bio, "x", 1) == -1, "reject read-only write"); + RequireError(0x20fff07e); + Require(buffer->data == source.data() && buffer->length == source.size(), "rejected write preserves input"); + Require(BIO_free_nid_postfix(bio) == 1 && source[0] == 'a' && source[7] == 'e', "borrowed bytes survive free"); + for (const int size : {-1, -99}) { + bio = BIO_new_mem_buf_nid_postfix("ab\0cd", size); + RequireData(bio, "ab"); + Require(BIO_free_nid_postfix(bio) == 1, "free string BIO"); + } + bio = BIO_new_mem_buf_nid_postfix("", 0); + RequireData(bio, ""); + output[0] = 'x'; + Require(bio->method->gets(bio, output, 8) == 0 && output[0] == 0, "empty gets"); + Require(BIO_free_nid_postfix(bio) == 1, "free empty BIO"); +} + +void TestBufferOwnership() { + Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + Require(BIO_puts_nid_postfix(bio, "retained") == 8, "write retained buffer"); + auto* retained = Buffer(bio); + Require(BIO_ctrl_nid_postfix(bio, 8, 0, nullptr) == 1, "default close flag"); + Require(BIO_ctrl_nid_postfix(bio, 9, 0, nullptr) == 1 && BIO_ctrl_nid_postfix(bio, 8, 0, nullptr) == 0, "disable close"); + Require(BIO_free_nid_postfix(bio) == 1, "free BIO without its buffer"); + Require(retained->length == 8 && std::memcmp(retained->data, "retained", 8) == 0, "detached storage remains valid"); + free_nid_postfix(retained->data); + free_nid_postfix(retained); + + bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + Require(BIO_puts_nid_postfix(bio, "old") == 3, "write old buffer"); + auto* replacement = static_cast(calloc_nid_postfix(1, sizeof(BioBuffer))); + replacement->data = static_cast(calloc_nid_postfix(8, 1)); + replacement->length = 3; + replacement->capacity = 8; + std::memcpy(replacement->data, "new", 3); + Require(BIO_ctrl_nid_postfix(bio, 114, 0, replacement) == 1 && Buffer(bio) == replacement, "replace buffer"); + RequireData(bio, "new"); + Require(BIO_write_nid_postfix(bio, "!", 1) == 1, "write replacement buffer"); + RequireData(bio, "new!"); + Require(BIO_free_nid_postfix(bio) == 1, "free BIO retaining replacement"); + Require(replacement->length == 4 && std::memcmp(replacement->data, "new!", 4) == 0, "caller owns replacement"); + free_nid_postfix(replacement->data); + free_nid_postfix(replacement); + + bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + Require(BIO_ctrl_nid_postfix(bio, 114, 1, nullptr) == 1, "detach without replacement"); + Require(BIO_ctrl_nid_postfix(bio, 8, 0, nullptr) == 1, "close control without storage"); + Require(BIO_ctrl_nid_postfix(bio, 11, 0, nullptr) == 1, "flush without storage"); + void* missing = bio; + Require(BIO_ctrl_nid_postfix(bio, 115, 0, &missing) == 1 && missing == nullptr, "query detached storage"); + Require(BIO_free_nid_postfix(bio) == 1, "free detached BIO"); +} + +void TestInterleavedTransfers() { + Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + std::string pending; + std::uint32_t sequence = 0x346de821; + std::uint64_t written = 0; + for (int step = 0; step < 2000; ++step) { + sequence = sequence * 1664525 + 1013904223; + const int count = static_cast((sequence >> 16) % 65); + if ((sequence & 3) != 0) { + std::array input{}; + for (int i = 0; i < count; ++i) input[i] = static_cast(sequence + i * 37); + Require(BIO_write_nid_postfix(bio, input.data(), count) == count, "interleaved write count"); + pending.append(input.data(), count); + written += count; + } else { + std::array output; + output.fill(static_cast(0xa5)); + const int expected = static_cast(std::min(count, pending.size())); + const int result = bio->method->read(bio, output.data(), count); + Require(result == (pending.empty() ? -1 : expected), "interleaved read count"); + if (expected != 0) Require(std::memcmp(output.data(), pending.data(), expected) == 0, "interleaved read bytes"); + Require(output[expected] == static_cast(0xa5), "read preserves destination tail"); + pending.erase(0, expected); + } + RequireData(bio, pending); + Require(bio->bytesWritten == written && bio->bytesRead == 0, "interleaved transfer counters"); + if (step % 127 == 0) { + Require(BIO_ctrl_nid_postfix(bio, 1, 0, nullptr) == 1, "interleaved reset"); + pending.clear(); + } + } + Require(BIO_free_nid_postfix(bio) == 1, "free interleaved BIO"); +} + +struct WriteEvent { + int operation; + const char* source; + int size; + std::int64_t result; + int flags; + std::uint64_t bytesWritten; +}; + +struct WriteTrace { + std::vector events; + int veto = 0; + int postAdjustment = 0; +}; + +std::int64_t APS5_VABI TraceWrite(Bio* bio, int operation, const char* source, int size, std::int64_t, std::int64_t result) { + auto& trace = *reinterpret_cast(bio->callbackArgument); + trace.events.push_back({operation, source, size, result, bio->flags, bio->bytesWritten}); + if (operation == 3 && trace.veto) return trace.veto; + return (operation & 0x80) ? result + trace.postAdjustment : result; +} + +void TestWriteBoundaries() { + const char source[] = "abc"; + for (const bool readOnly : {false, true}) { + Bio* bio = readOnly ? BIO_new_mem_buf_nid_postfix(source, 3) : BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + WriteTrace trace; + bio->callback = TraceWrite; + bio->callbackArgument = reinterpret_cast(&trace); + for (const int size : {0, 1}) { + bio->flags |= 0x4009; + const int flags = bio->flags; + trace.events.clear(); + Require(BIO_write_nid_postfix(bio, nullptr, size) == -1, "null source dispatch result"); + RequireError(0x20fff073); + Require(trace.events.size() == 2, "null source has pre/post callbacks"); + Require(trace.events[0].operation == 3 && trace.events[1].operation == 0x83, "null source callback operations"); + Require(trace.events[0].source == nullptr && trace.events[1].source == nullptr && + trace.events[0].size == size && trace.events[1].size == size, "null source callback arguments"); + Require(trace.events[0].result == 1 && trace.events[1].result == -1, "null source callback results"); + Require(trace.events[0].flags == flags && trace.events[1].flags == flags && bio->flags == flags, "null source preserves retry flags"); + Require(trace.events[0].bytesWritten == 0 && trace.events[1].bytesWritten == 0 && bio->bytesWritten == 0, "failed write does not count bytes"); + } + bio->flags |= 0x4009; + const int flags = bio->flags; + trace.events.clear(); + Require(BIO_write_nid_postfix(bio, source, 0) == (readOnly ? -1 : 0), "zero-length method result"); + RequireError(readOnly ? 0x20fff07e : 0); + Require(trace.events.size() == 2 && trace.events[0].operation == 3 && trace.events[1].operation == 0x83, "zero-length write callback operations"); + Require(trace.events[0].source == source && trace.events[1].source == source && + trace.events[0].size == 0 && trace.events[1].size == 0, "zero-length callback arguments"); + Require(trace.events[0].result == 1 && trace.events[1].result == (readOnly ? -1 : 0), "zero-length callback results"); + Require(trace.events[0].flags == flags && trace.events[1].flags == (readOnly ? flags : flags & ~0xf), "retry flags change during the method"); + Require(bio->flags == (readOnly ? flags : flags & ~0xf) && bio->bytesWritten == 0, "zero-length retry flags and byte count"); + RequireData(bio, readOnly ? "abc" : ""); + trace.events.clear(); + trace.veto = -7; + Require(BIO_write_nid_postfix(bio, nullptr, 0) == -7 && trace.events.size() == 1, "null write callback veto prevents method"); + RequireError(0); + trace.veto = 0; + trace.postAdjustment = 5; + trace.events.clear(); + Require(BIO_write_nid_postfix(bio, nullptr, 0) == 4 && bio->bytesWritten == 0, "post callback can override failure without counting bytes"); + RequireError(0x20fff073); + bio->callback = nullptr; + Require(BIO_free_nid_postfix(bio) == 1, "free boundary BIO"); + } + Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + WriteTrace trace; + bio->callback = TraceWrite; + bio->callbackArgument = reinterpret_cast(&trace); + const auto* method = bio->method; + bio->method = nullptr; + Require(BIO_write_nid_postfix(bio, nullptr, 0) == -2 && trace.events.empty(), "method validation precedes callback"); + RequireError(0x20fff079); + bio->method = method; + bio->initialized = 0; + Require(BIO_write_nid_postfix(bio, nullptr, 0) == -2 && trace.events.size() == 1 && trace.events[0].operation == 3, "initialization validation follows pre-callback"); + RequireError(0x20fff078); + bio->initialized = 1; + bio->callback = nullptr; + Require(BIO_free_nid_postfix(bio) == 1, "free dispatch-order BIO"); +} + +struct CallbackState { + std::vector events; + std::int64_t argument = 0; + int veto = 0; +}; + +std::int64_t APS5_VABI Callback(Bio* bio, int operation, const char*, int, std::int64_t argument, std::int64_t result) { + auto& state = *reinterpret_cast(bio->callbackArgument); + state.events.push_back(operation); + state.argument = argument; + if (state.veto && !(operation & 0x80)) return state.veto; + return (operation & 0x80) ? result + 2 : 1; +} + +int APS5_VABI CreateCustom(Bio* bio) { + bio->initialized = 1; + return 1; +} + +int APS5_VABI WriteCustom(Bio*, const char*, int count) { + return count - 1; +} + +std::int64_t APS5_VABI ControlCustom(Bio*, int command, std::int64_t argument, void*) { + return argument + command; +} + +int APS5_VABI RejectCreate(Bio*) { + return 0; +} + +void TestDispatchAndReferences() { + Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + CallbackState state; + bio->callback = Callback; + bio->callbackArgument = reinterpret_cast(&state); + Require(BIO_write_nid_postfix(bio, "abc", 3) == 5 && bio->bytesWritten == 3, "callback changes result after accounting"); + Require(state.events == std::vector{3, 0x83}, "write callback ordering"); + state.events.clear(); + Require(BIO_puts_nid_postfix(bio, "d") == 3 && state.events == std::vector{4, 0x84}, "puts callbacks"); + state.events.clear(); + Require(BIO_ctrl_nid_postfix(bio, 10, 0x1234567887654321LL, nullptr) == 6, "control callback result"); + Require(state.argument == 0x1234567887654321LL && state.events == std::vector{6, 0x86}, "64-bit control argument"); + state.events.clear(); + state.veto = -7; + Require(BIO_write_nid_postfix(bio, "x", 1) == -7 && state.events == std::vector{3}, "write callback veto"); + Require(bio->bytesWritten == 4 && static_cast(bio->data)->length == 4, "veto prevents transfer"); + state.events.clear(); + Require(BIO_free_nid_postfix(bio) == -7 && state.events == std::vector{1} && bio->references == 0, "free callback veto"); + bio->callback = nullptr; + Require(BIO_free_nid_postfix(bio) == 1, "free after veto"); + + BioMethod custom{0x480, "test", WriteCustom, nullptr, nullptr, nullptr, ControlCustom, CreateCustom, nullptr, nullptr}; + bio = BIO_new_nid_postfix(&custom); + Require(bio && bio->method == &custom, "title-defined method"); + Require(BIO_write_nid_postfix(bio, "abc", 3) == 2 && bio->bytesWritten == 2, "title-defined writer"); + Require(BIO_write_nid_postfix(bio, nullptr, 3) == 2 && bio->bytesWritten == 4, "custom method receives null source"); + Require(BIO_write_nid_postfix(bio, "", 0) == -1 && bio->bytesWritten == 4, "custom method receives zero length"); + RequireError(0); + Require(BIO_ctrl_nid_postfix(bio, 17, 0x1234567800000000LL, nullptr) == 0x1234567800000011LL, "64-bit method result"); + constexpr int referenceCount = 8; + std::atomic_ref(bio->references).store(referenceCount + 1); + std::array threads; + for (auto& thread : threads) thread = std::thread([bio] { Require(BIO_free_nid_postfix(bio) == 1, "release shared reference"); }); + for (auto& thread : threads) thread.join(); + Require(bio->references == 1 && BIO_free_nid_postfix(bio) == 1, "last reference release"); + custom.create = RejectCreate; + Require(BIO_new_nid_postfix(&custom) == nullptr, "custom create can reject allocation"); +} + +void TestUnsupported() { + Require(BIO_free_nid_postfix(nullptr) == 0, "free null"); + Require(BIO_ctrl_nid_postfix(nullptr, 1, 0, nullptr) == 0, "control null"); + Require(BIO_write_nid_postfix(nullptr, "a", 1) == 0, "write null BIO"); + Require(BIO_new_mem_buf_nid_postfix(nullptr, 0) == nullptr, "reject null borrowed input"); + RequireError(0x20fff073); + Require(BIO_puts_nid_postfix(nullptr, "a") == -2, "reject puts without a method"); + RequireError(0x20fff079); + Bio* bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + bio->initialized = 0; + Require(BIO_write_nid_postfix(bio, "a", 1) == -2, "reject uninitialized write"); + RequireError(0x20fff078); + bio->initialized = 1; + const BioMethod* method = bio->method; + bio->method = nullptr; + Require(BIO_ctrl_nid_postfix(bio, 1, 0, nullptr) == -2, "reject control without a method"); + RequireError(0x20fff079); + Require(BIO_write_nid_postfix(bio, "a", 1) == -2, "reject write without a method"); + RequireError(0x20fff079); + bio->method = method; + auto* buffer = Buffer(bio); + buffer->length = 0x5ffffffd; + Require(BIO_write_nid_postfix(bio, "a", 1) == -1, "reject oversized growth"); + RequireError(0x07fff041); + buffer->length = 0; + Require(BIO_free_nid_postfix(bio) == 1, "free after unsupported cases"); + for (const int failAfter : {0, 1}) { + allocationsUntilFailure = failAfter; + Require(BIO_new_nid_postfix(BIO_s_mem_nid_postfix()) == nullptr, "failed BIO allocation"); + RequireError(failAfter == 0 ? 0x20fff041 : 0x07fff041); + Require(allocations == 0, "failed creation leaked guest heap storage"); + } + allocationsUntilFailure = -1; + bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + Require(BIO_puts_nid_postfix(bio, "abc") == 3, "write before growth failure"); + allocationsUntilFailure = 0; + Require(BIO_puts_nid_postfix(bio, "0123456789") == -1, "failed buffer expansion"); + RequireError(0x07fff041); + RequireData(bio, "abc"); + allocationsUntilFailure = -1; + Require(BIO_free_nid_postfix(bio) == 1, "free after growth failure"); + + bio = BIO_new_nid_postfix(BIO_s_mem_nid_postfix()); + bio->initialized = 0; + Require(BIO_puts_nid_postfix(bio, "a") == -2, "reject uninitialized puts"); + RequireError(0x20fff078); + bio->initialized = 1; + Require(bio->method->write(bio, nullptr, 1) == -1, "reject null raw write source"); + RequireError(0x20fff073); + Require(BIO_free_nid_postfix(bio) == 1, "free after method errors"); +} + +} // namespace + +int main(int argc, char** argv) { +#ifdef ANYPS5_BIO_DYNAMIC + Require(argc == 2, "patched BIO test needs the libraries directory"); + InitializePatched(std::filesystem::absolute(argv[1])); +#else + static_cast(argc); + static_cast(argv); +#endif + InitializeHeap(); + ERR_clear_error_nid_postfix(); + TestWritable(); + TestReadOnlyAndLines(); + TestBufferOwnership(); + TestInterleavedTransfers(); + TestWriteBoundaries(); + TestDispatchAndReferences(); + TestUnsupported(); + Require(allocations == 0, "BIO tests leaked guest heap storage"); + Require(ERR_get_error_nid_postfix() == 0, "BIO tests left an unexpected error"); +} diff --git a/core/libs/tests/GuestLibreSslBn.cpp b/core/libs/tests/GuestLibreSslBn.cpp new file mode 100644 index 0000000000..09e6c4e641 --- /dev/null +++ b/core/libs/tests/GuestLibreSslBn.cpp @@ -0,0 +1,204 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +using Bn = Bignum; +using BnCtx = BignumContext; + +static_assert(sizeof(Bn) == 24); +static_assert(offsetof(Bn, top) == 8); +static_assert(offsetof(Bn, neg) == 16); + +static void Require(bool value, int line = __builtin_LINE()) { + if (value) return; + std::fprintf(stderr, "check failed at line %d\n", line); + std::abort(); +} + +static Bn* FromHex(const char* text) { + Bn* value = nullptr; + Require(BN_hex2bn_nid_postfix(&value, text) > 0 && value); + return value; +} + +static std::string Hex(const Bn* value) { + unsigned char bytes[64]; + const int length = BN_bn2bin_nid_postfix(value, bytes); + Require(length >= 0 && length <= 64); + static constexpr char digits[] = "0123456789ABCDEF"; + std::string text; + for (int i = 0; i < length; i++) { + text += digits[bytes[i] >> 4]; + text += digits[bytes[i] & 15]; + } + while (text.size() > 1 && text[0] == '0') text.erase(0, 1); + if (text.empty()) text = "0"; + return value->neg ? "-" + text : text; +} + +static void InitializeHeap() { + void* const api[10] {}; + ApplicationHeapRegister_nid_no_patch(api); +} + +int main() { + InitializeHeap(); + Bn* fresh = BN_new_nid_postfix(); + Require(fresh && fresh->top == 0 && fresh->neg == 0 && BN_num_bits_nid_postfix(fresh) == 0); + BN_free_nid_postfix(fresh); + BN_free_nid_postfix(nullptr); + + BnCtx* ctx = BN_CTX_new_nid_postfix(); + Require(ctx); + BN_CTX_start_nid_postfix(ctx); + BN_CTX_end_nid_postfix(ctx); + BN_CTX_free_nid_postfix(ctx); + BN_CTX_free_nid_postfix(nullptr); + ctx = BN_CTX_new_nid_postfix(); + Require(ctx); + + { + Bn* a = FromHex("FFFFFFFFFFFFFFFF"); + Bn* b = FromHex("FFFFFFFFFFFFFFFF"); + Bn* r = BN_new_nid_postfix(); + Require(BN_mul_nid_postfix(r, a, b, ctx) == 1); + Require(Hex(r) == "FFFFFFFFFFFFFFFE0000000000000001"); + Require(r->top == 2 && r->neg == 0); + BN_free_nid_postfix(a); + BN_free_nid_postfix(b); + BN_free_nid_postfix(r); + } + { + Bn* a = FromHex("0123456789ABCDEFFEDCBA98765432100F1E2D3C4B5A6978"); + Bn* b = FromHex("00FF00FF00FF00FF00FF00FF00FF00FF00FF"); + Bn* r = BN_new_nid_postfix(); + Require(BN_mul_nid_postfix(r, a, b, ctx) == 1); + Require(Hex(r) == "12223444566678876665444322210000F0F1CFC09E8F6D55CB34DD56FF79219B43BB52CA61D970E88"); + Require(BN_mul_nid_postfix(a, a, a, ctx) == 1); + Require(Hex(a) == "14B66DC33F6ACDEE9D609EBE253A6424AD884E9A04269A9706601BB096443363E2CE44634913DF9C3B9FAA3D3A840"); + Bn* c = FromHex("-3"); + Bn* d = FromHex("5"); + Require(BN_mul_nid_postfix(d, c, d, ctx) == 1 && Hex(d) == "-F" && d->neg == 1); + Require(BN_mul_nid_postfix(c, c, c, ctx) == 1 && Hex(c) == "9" && c->neg == 0); + Bn* zero = FromHex("0"); + Require(BN_mul_nid_postfix(d, d, zero, ctx) == 1 && d->top == 0 && d->neg == 0); + BN_free_nid_postfix(a); + BN_free_nid_postfix(b); + BN_free_nid_postfix(c); + BN_free_nid_postfix(d); + BN_free_nid_postfix(r); + BN_free_nid_postfix(zero); + } + { + const unsigned char bytes[] = {0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}; + Bn* value = BN_bin2bn_nid_postfix(bytes, sizeof(bytes), nullptr); + Require(value && value->neg == 0 && value->top == 2); + Require(Hex(value) == "10203040506070809"); + unsigned char out[16]; + std::memset(out, 0xEE, sizeof(out)); + Require(BN_bn2bin_nid_postfix(value, out) == 9); + Require(std::memcmp(out, bytes + 2, 9) == 0 && out[9] == 0xEE); + Require(BN_bin2bn_nid_postfix(bytes, 4, value) == value && Hex(value) == "102" && value->top == 1); + const unsigned char zeros[] = {0, 0, 0}; + Require(BN_bin2bn_nid_postfix(zeros, 3, value) == value && value->top == 0); + std::memset(out, 0xEE, sizeof(out)); + Require(BN_bn2bin_nid_postfix(value, out) == 0 && out[0] == 0xEE); + Require(BN_bin2bn_nid_postfix(bytes, 0, value) == value && value->top == 0); + Require(BN_bin2bn_nid_postfix(bytes, -1, value) == nullptr); + BN_free_nid_postfix(value); + } + { + Bn* value = BN_new_nid_postfix(); + Require(BN_num_bits_nid_postfix(value) == 0); + Require(BN_set_word_nid_postfix(value, 1) == 1 && BN_num_bits_nid_postfix(value) == 1); + Require(BN_set_word_nid_postfix(value, 0x100) == 1 && BN_num_bits_nid_postfix(value) == 9); + Bn* big = FromHex("10000000000000000"); + Require(BN_num_bits_nid_postfix(big) == 65 && big->top == 2); + Require(BN_set_word_nid_postfix(big, 0) == 1 && BN_num_bits_nid_postfix(big) == 0 && big->top == 0 && big->neg == 0); + Require(BN_set_word_nid_postfix(value, 0xFFFFFFFFFFFFFFFFull) == 1 && BN_num_bits_nid_postfix(value) == 64); + BN_free_nid_postfix(value); + BN_free_nid_postfix(big); + } + { + Bn* value = nullptr; + Require(BN_hex2bn_nid_postfix(&value, "-1F") == 3 && value); + Require(value->neg == 1 && value->top == 1 && value->d[0] == 0x1F); + Bn* same = value; + Require(BN_hex2bn_nid_postfix(&value, "ABC") == 3 && value == same); + Require(value->neg == 0 && value->d[0] == 0xABC); + Require(BN_hex2bn_nid_postfix(&value, "FFzz") == 2 && value->d[0] == 0xFF); + Require(BN_hex2bn_nid_postfix(&value, "") == 0 && value->d[0] == 0xFF); + Require(BN_hex2bn_nid_postfix(&value, "zz") == 0 && BN_hex2bn_nid_postfix(&value, "-") == 0); + Require(BN_hex2bn_nid_postfix(&value, "-0") == 2 && value->top == 0 && value->neg == 0); + Require(BN_hex2bn_nid_postfix(&value, "deadBEEF0123456789abcdef") == 24); + Require(Hex(value) == "DEADBEEF0123456789ABCDEF" && value->top == 2); + Require(BN_hex2bn_nid_postfix(nullptr, "12G") == 2); + BN_free_nid_postfix(value); + } + { + Bn* value = FromHex("FFFFFFFFFFFFFFFF"); + Require(BN_add_word_nid_postfix(value, 1) == 1); + Require(value->top == 2 && value->d[0] == 0 && value->d[1] == 1 && Hex(value) == "10000000000000000"); + Require(BN_sub_word_nid_postfix(value, 1) == 1); + Require(value->top == 1 && value->d[0] == 0xFFFFFFFFFFFFFFFFull && value->neg == 0); + Require(BN_set_word_nid_postfix(value, 3) == 1); + Require(BN_sub_word_nid_postfix(value, 5) == 1 && value->neg == 1 && value->top == 1 && value->d[0] == 2); + Require(BN_add_word_nid_postfix(value, 2) == 1 && value->top == 0 && value->neg == 0); + Require(BN_sub_word_nid_postfix(value, 7) == 1 && value->neg == 1 && Hex(value) == "-7"); + Require(BN_add_word_nid_postfix(value, 10) == 1 && value->neg == 0 && Hex(value) == "3"); + Require(BN_sub_word_nid_postfix(value, 0) == 1 && Hex(value) == "3"); + Bn* wide = FromHex("-10000000000000000"); + Require(BN_sub_word_nid_postfix(wide, 1) == 1 && wide->neg == 1 && Hex(wide) == "-10000000000000001"); + Require(BN_add_word_nid_postfix(wide, 2) == 1 && wide->neg == 1 && Hex(wide) == "-FFFFFFFFFFFFFFFF" && wide->top == 1); + Bn* chain = FromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + Require(BN_add_word_nid_postfix(chain, 1) == 1 && chain->top == 3 && Hex(chain) == "100000000000000000000000000000000"); + Require(BN_sub_word_nid_postfix(chain, 1) == 1 && chain->top == 2 && Hex(chain) == "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + BN_free_nid_postfix(value); + BN_free_nid_postfix(wide); + BN_free_nid_postfix(chain); + } + { + Bn* r = BN_new_nid_postfix(); + Bn* three = FromHex("3"); + Bn* eleven = FromHex("B"); + Require(BN_mod_inverse_nid_postfix(r, three, eleven, ctx) == r && Hex(r) == "4"); + Bn* seven = FromHex("7"); + Bn* forty = FromHex("28"); + Require(BN_mod_inverse_nid_postfix(r, seven, forty, ctx) == r && Hex(r) == "17"); + Bn* four = FromHex("4"); + Bn* eight = FromHex("8"); + Require(BN_mod_inverse_nid_postfix(r, four, eight, ctx) == nullptr); + Bn* allocated = BN_mod_inverse_nid_postfix(nullptr, three, eleven, ctx); + Require(allocated && Hex(allocated) == "4"); + Bn* minusThree = FromHex("-3"); + Require(BN_mod_inverse_nid_postfix(r, minusThree, eleven, ctx) == r && Hex(r) == "7"); + Bn* fifteen = FromHex("F"); + Require(BN_mod_inverse_nid_postfix(r, fifteen, eleven, ctx) == r && Hex(r) == "3"); + Bn* inPlace = FromHex("3"); + Require(BN_mod_inverse_nid_postfix(inPlace, inPlace, eleven, ctx) == inPlace && Hex(inPlace) == "4"); + + Bn* prime = FromHex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + Bn* a = FromHex("123456789ABCDEF0FEDCBA9876543210"); + Bn* inverse = BN_mod_inverse_nid_postfix(nullptr, a, prime, ctx); + Require(inverse && Hex(inverse) == "62CFEFE6EB41053969967BD711C5169F"); + Bn* product = BN_new_nid_postfix(); + Require(BN_mul_nid_postfix(product, a, inverse, ctx) == 1); + Bn* quotient = FromHex("E0DA4F6BE78BFD74B8C9F95AF178811"); + Bn* check = BN_new_nid_postfix(); + Require(BN_mul_nid_postfix(check, quotient, prime, ctx) == 1); + Require(BN_add_word_nid_postfix(check, 1) == 1); + Require(Hex(check) == Hex(product)); + + Bn* all[] = {r, three, eleven, seven, forty, four, eight, allocated, minusThree, fifteen, inPlace, prime, a, inverse, product, quotient, check}; + for (Bn* value : all) BN_free_nid_postfix(value); + } + BN_CTX_free_nid_postfix(ctx); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslCmac.cpp b/core/libs/tests/GuestLibreSslCmac.cpp new file mode 100644 index 0000000000..aa4890d4bf --- /dev/null +++ b/core/libs/tests/GuestLibreSslCmac.cpp @@ -0,0 +1,444 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libSceLibreSsl/include/Cmac.hpp" +#include "prx/libSceLibreSsl/ErrorQueue.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +#ifdef ANYPS5_CMAC_DYNAMIC +namespace { +decltype(&CMAC_CTX_new_nid_postfix) cmacNew; +decltype(&CMAC_CTX_free_nid_postfix) cmacFree; +decltype(&CMAC_Init_nid_postfix) cmacInit; +decltype(&CMAC_Update_nid_postfix) cmacUpdate; +decltype(&CMAC_Final_nid_postfix) cmacFinal; +decltype(&EVP_aes_128_cbc_nid_postfix) aes128Method; +decltype(&EVP_aes_192_cbc_nid_postfix) aes192Method; +decltype(&EVP_aes_256_cbc_nid_postfix) aes256Method; +decltype(&ERR_clear_error_nid_postfix) clearError; +decltype(&ERR_get_error_nid_postfix) getError; +decltype(&ApplicationHeapRegister_nid_no_patch) registerHeap; + +void* Load(const std::string& path) { +#ifdef _WIN32 + auto* library = LoadLibraryExA(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); +#else + auto* library = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); +#endif + if (library == nullptr) throw std::runtime_error("failed to load patched CMAC dependency: " + path); + return library; +} + +template void Resolve(TFunction& destination, void* library, const char* name) { +#ifdef _WIN32 + destination = reinterpret_cast(GetProcAddress(static_cast(library), name)); +#else + destination = reinterpret_cast(dlsym(library, name)); +#endif + if (destination == nullptr) throw std::runtime_error(std::string("missing patched CMAC dependency: ") + name); +} + +void InitializePatched(const char* directory) { + const auto root = std::string(directory) + "/"; + auto* libc = Load(root + "libc.prx"); + auto* library = Load(root + "libSceLibreSsl.prx"); + Resolve(cmacNew, library, "mwJYST4Bb3U"); + Resolve(cmacFree, library, "cBNGAG1DkXc"); + Resolve(cmacInit, library, "LATrF+dIZYI"); + Resolve(cmacUpdate, library, "XpAJ1Ckg-q4"); + Resolve(cmacFinal, library, "6MM7ng9P+18"); + Resolve(aes128Method, library, "rV07jlO-+6Q"); + Resolve(aes192Method, library, "0X5DnA3olsk"); + Resolve(aes256Method, library, "J44dsZUcK4I"); + Resolve(clearError, library, "sieQ--PINWg"); + Resolve(getError, library, "DJg+qWX1rwY"); + Resolve(registerHeap, libc, "ApplicationHeapRegister_nid_no_patch"); +} +} + +#define CMAC_CTX_new_nid_postfix cmacNew +#define CMAC_CTX_free_nid_postfix cmacFree +#define CMAC_Init_nid_postfix cmacInit +#define CMAC_Update_nid_postfix cmacUpdate +#define CMAC_Final_nid_postfix cmacFinal +#define EVP_aes_128_cbc_nid_postfix aes128Method +#define EVP_aes_192_cbc_nid_postfix aes192Method +#define EVP_aes_256_cbc_nid_postfix aes256Method +#define ERR_clear_error_nid_postfix clearError +#define ERR_get_error_nid_postfix getError +#define ApplicationHeapRegister_nid_no_patch registerHeap +#endif + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "failed: %s\n", message); + std::abort(); +} + +int allocationsUntilFailure = -1; +std::vector> allocations; + +void* APS5_VABI Allocate(std::size_t size) { + if (allocationsUntilFailure == 0) return nullptr; + if (allocationsUntilFailure > 0) --allocationsUntilFailure; + auto* pointer = std::malloc(size); + if (pointer != nullptr) allocations.emplace_back(pointer, size); + return pointer; +} +void* APS5_VABI Reallocate(void*, std::size_t) { throw std::logic_error("unexpected CMAC reallocation"); } +void* APS5_VABI Align(std::size_t, std::size_t) { throw std::logic_error("unexpected CMAC aligned allocation"); } +void* APS5_VABI Realign(void*, std::size_t, std::size_t) { throw std::logic_error("unexpected CMAC realignment"); } +int APS5_VABI PosixAlign(void**, std::size_t, std::size_t) { throw std::logic_error("unexpected CMAC aligned allocation"); } + +void* APS5_VABI Calloc(std::size_t count, std::size_t size) { + if (allocationsUntilFailure == 0) return nullptr; + if (allocationsUntilFailure > 0) --allocationsUntilFailure; + auto* pointer = std::calloc(count, size); + if (pointer != nullptr) allocations.emplace_back(pointer, count * size); + return pointer; +} + +void APS5_VABI Free(void* pointer) { + if (pointer == nullptr) return; + const auto found = std::find_if(allocations.begin(), allocations.end(), [&](const auto& entry) { return entry.first == pointer; }); + Require(found != allocations.end(), "free a guest-owned allocation"); + const auto* bytes = static_cast(pointer); + Require(std::all_of(bytes, bytes + found->second, [](auto value) { return value == 0; }), "wipe contexts and key storage before freeing"); + allocations.erase(found); + std::free(pointer); +} + +void InitializeHeap() { + const std::array api{ + reinterpret_cast(Allocate), reinterpret_cast(Free), reinterpret_cast(Calloc), + reinterpret_cast(Reallocate), reinterpret_cast(Align), reinterpret_cast(Realign), + reinterpret_cast(PosixAlign)}; + ApplicationHeapRegister_nid_no_patch(api.data()); +} + +template void RequireThrows(TAction action) { + try { action(); } catch (const std::exception&) { return; } + Require(false, "unsupported input must throw"); +} + +std::vector Hex(std::string_view text) { + Require(text.size() % 2 == 0, "hex fixture has complete bytes"); + std::vector bytes; + for (std::size_t index = 0; index < text.size(); index += 2) { + const auto nibble = [](char value) { return value <= '9' ? value - '0' : value - 'a' + 10; }; + bytes.push_back(static_cast((nibble(text[index]) << 4) | nibble(text[index + 1]))); + } + return bytes; +} + +using CmacFree = void (APS5_VABI*)(LibreSslCmac*); +using Context = std::unique_ptr; + +Context NewContext() { + Context context(CMAC_CTX_new_nid_postfix(), CMAC_CTX_free_nid_postfix); + Require(context != nullptr, "allocate CMAC context"); + return context; +} + +std::array Final(LibreSslCmac* context) { + std::array output; + output.fill(0xa5); + std::size_t length = 99; + Require(CMAC_Final_nid_postfix(context, output.data() + 1, &length) == 1, "finalize CMAC"); + Require(length == 16 && output.front() == 0xa5 && output.back() == 0xa5, "tag size and output bounds"); + std::array result; + std::copy_n(output.data() + 1, 16, result.data()); + return result; +} + +const LibreSslCipher* Methods[3]; + +void RfcVectors() { + const auto key = Hex("2b7e151628aed2a6abf7158809cf4f3c"); + const auto message = Hex("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710"); + const std::size_t lengths[] = {0, 16, 40, 64}; + const char* tags[] = {"bb1d6929e95937287fa37d129b756746", "070a16b46b4d4144f79bdd9dd04a287c", "dfa66747de9ae63030ca32611497c827", "51f0bebf7e3b9d92fc49741779363cfe"}; + auto context = NewContext(); + for (std::size_t vector = 0; vector < 4; ++vector) { + const auto expected = Hex(tags[vector]); + for (std::size_t split = 0; split <= lengths[vector]; ++split) { + Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), Methods[0], nullptr) == 1, "RFC key initialization"); + Require(CMAC_Update_nid_postfix(context.get(), message.data(), split) == 1, "RFC first update"); + Require(CMAC_Update_nid_postfix(context.get(), nullptr, 0) == 1, "empty update"); + Require(CMAC_Update_nid_postfix(context.get(), message.data() + split, lengths[vector] - split) == 1, "RFC second update"); + std::size_t length = 99; + Require(CMAC_Final_nid_postfix(context.get(), nullptr, &length) == 1 && length == 16, "length-only final"); + Require(CMAC_Final_nid_postfix(context.get(), nullptr, &length) == 1 && length == 16, "repeat length query"); + const auto actual = Final(context.get()); + if (!std::equal(actual.begin(), actual.end(), expected.begin())) { + std::fprintf(stderr, "RFC vector %zu split %zu: ", vector, split); + for (auto byte : actual) std::fprintf(stderr, "%02x", byte); + std::fputc('\n', stderr); + } + Require(std::equal(actual.begin(), actual.end(), expected.begin()), "RFC 4493 tag"); + } + } +} + +void Differential() { + const mbedtls_cipher_type_t types[] = {MBEDTLS_CIPHER_AES_128_ECB, MBEDTLS_CIPHER_AES_192_ECB, MBEDTLS_CIPHER_AES_256_ECB}; + std::array key; + std::array message; + for (std::size_t index = 0; index < key.size(); ++index) key[index] = static_cast(index * 73 + 41); + for (std::size_t index = 0; index < message.size(); ++index) message[index] = static_cast(index * 197 + 17); + auto context = NewContext(); + for (std::size_t method = 0; method < 3; ++method) { + const auto* cipher = Methods[method]; + Require(cipher->nid == 419 + static_cast(method) * 4 && cipher->blockSize == 16 && cipher->keyLength == 16 + static_cast(method) * 8 && cipher->ivLength == 16 && cipher->flags == 0x5002, "AES descriptor metadata"); + const auto* reference = mbedtls_cipher_info_from_type(types[method]); + Require(reference != nullptr, "reference cipher available"); + for (std::size_t length = 0; length <= message.size(); ++length) { + std::array expected; + Require(mbedtls_cipher_cmac(reference, key.data(), cipher->keyLength * 8, message.data(), length, expected.data()) == 0, "reference CMAC"); + for (const auto chunk : {1u, 7u, 15u, 16u, 17u, 64u, 257u}) { + Require(CMAC_Init_nid_postfix(context.get(), key.data(), cipher->keyLength, cipher, nullptr) == 1, "differential initialization"); + for (std::size_t offset = 0; offset < length;) { + const auto take = std::min(static_cast(chunk), length - offset); + Require(CMAC_Update_nid_postfix(context.get(), message.data() + offset, take) == 1, "differential update"); + offset += take; + } + Require(Final(context.get()) == expected, "CMAC agrees with independent backend"); + } + } + } +} + +void Lifecycle() { + auto context = NewContext(); + const std::array key{}; + const std::array message{}; + std::array output; + output.fill(0xa5); + std::size_t length = 99; + Require(CMAC_Update_nid_postfix(context.get(), nullptr, 1) == 0, "uninitialized update"); + Require(CMAC_Final_nid_postfix(context.get(), output.data(), &length) == 0 && length == 99 && output[0] == 0xa5, "uninitialized final preserves outputs"); + Require(CMAC_Final_nid_postfix(context.get(), nullptr, nullptr) == 0, "uninitialized final precedes null output length"); + Require(CMAC_Init_nid_postfix(context.get(), nullptr, 0, nullptr, nullptr) == 0, "uninitialized restart"); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), 16, nullptr, nullptr) == 0, "key without cipher"); + Require(CMAC_Init_nid_postfix(context.get(), nullptr, 1, nullptr, nullptr) == 1, "nonzero key length without a key is not restart"); + Require(CMAC_Init_nid_postfix(context.get(), nullptr, 0, Methods[0], nullptr) == 1, "stage cipher selection"); + Require(CMAC_Update_nid_postfix(context.get(), message.data(), 1) == 0, "cipher-only context has no key"); + ERR_clear_error_nid_postfix(); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), 15, nullptr, nullptr) == 0, "reject invalid key length"); + Require(ERR_get_error_nid_postfix() == 0x06fff082, "invalid key length publishes EVP error"); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), 16, nullptr, nullptr) == 1, "finish staged initialization"); + Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1, "first message"); + const auto expected = Final(context.get()); + const auto repeated = Final(context.get()); + Require(repeated != expected, "final advances the CBC state"); + Require(CMAC_Init_nid_postfix(context.get(), nullptr, 0, nullptr, nullptr) == 1, "restart reuses the key"); + Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1 && Final(context.get()) == expected, "restart resets state"); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), (std::size_t{1} << 32) + 16, nullptr, nullptr) == 1, "upstream key length narrows to int"); + Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1 && Final(context.get()) == expected, "narrowed key length tag"); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), 24, Methods[1], nullptr) == 1, "switch AES key size"); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), 32, Methods[2], nullptr) == 1, "switch AES key size again"); + RequireThrows([&] { CMAC_Init_nid_postfix(nullptr, key.data(), 16, Methods[0], nullptr); }); + RequireThrows([&] { CMAC_Update_nid_postfix(nullptr, nullptr, 0); }); + RequireThrows([&] { CMAC_Final_nid_postfix(nullptr, output.data(), &length); }); + RequireThrows([&] { CMAC_Update_nid_postfix(context.get(), nullptr, 1); }); + RequireThrows([&] { CMAC_Final_nid_postfix(context.get(), output.data(), nullptr); }); + CMAC_CTX_free_nid_postfix(nullptr); +} + +void AllocationFailures() { + allocationsUntilFailure = 0; + Require(CMAC_CTX_new_nid_postfix() == nullptr, "context allocation failure"); + allocationsUntilFailure = 1; + auto context = NewContext(); + const std::array key{}; + ERR_clear_error_nid_postfix(); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), Methods[0], nullptr) == 0, "cipher allocation failure"); + Require(ERR_get_error_nid_postfix() == 0x06fff041, "cipher allocation failure publishes EVP error"); + allocationsUntilFailure = -1; + Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), Methods[0], nullptr) == 1, "recover from failed allocation"); +} + +void CipherCallbacks() { + std::array key{}; + std::array iv; + for (std::size_t index = 0; index < iv.size(); ++index) iv[index] = static_cast(index + 1); + for (const auto* method : Methods) { + for (std::size_t length = 0; length <= 48; ++length) { + const auto padded = (length + 15) / 16 * 16; + std::vector input(padded); + for (std::size_t index = 0; index < length; ++index) input[index] = static_cast(index * 31 + 17); + std::vector expected(padded); + auto referenceIv = iv; + mbedtls_aes_context reference; + mbedtls_aes_init(&reference); + Require(mbedtls_aes_setkey_enc(&reference, key.data(), method->keyLength * 8) == 0, "reference AES key"); + Require(mbedtls_aes_crypt_cbc(&reference, MBEDTLS_AES_ENCRYPT, padded, referenceIv.data(), input.data(), expected.data()) == 0, "reference CBC"); + mbedtls_aes_free(&reference); + std::vector storage(method->contextSize); + LibreSslCipherContext context{}; + context.cipher = method; + context.cipherData = storage.data(); + context.keyLength = method->keyLength; + for (const bool inPlace : {false, true}) { + context.encrypt = 1; + std::copy(iv.begin(), iv.end(), context.iv); + Require(method->init(&context, key.data(), nullptr, 1) == 1, "AES descriptor encryption setup"); + auto output = input; + output.push_back(0xa5); + Require(method->cipher(&context, output.data(), inPlace ? output.data() : input.data(), length) == 1, "AES descriptor partial encryption"); + Require(output.back() == 0xa5 && std::equal(expected.begin(), expected.end(), output.begin()), "CBC partial encryption and bounds"); + Require(std::equal(referenceIv.begin(), referenceIv.end(), context.iv), "CBC encryption IV"); + context.encrypt = 0; + std::copy(iv.begin(), iv.end(), context.iv); + Require(method->init(&context, key.data(), nullptr, 0) == 1, "AES descriptor decryption setup"); + if (!inPlace) output.assign(padded + 1, 0xa5); + const auto before = output; + Require(method->cipher(&context, output.data(), inPlace ? output.data() : expected.data(), length) == 1, "AES descriptor partial decryption"); + Require(std::equal(input.begin(), input.begin() + length, output.begin()), "CBC decrypted prefix"); + Require(std::equal(before.begin() + length, before.end(), output.begin() + length), "partial decryption preserves trailing bytes"); + Require(std::equal(referenceIv.begin(), referenceIv.end(), context.iv), "CBC decryption IV"); + } + } + } +} + +int APS5_VABI DesInit(LibreSslCipherContext* context, const unsigned char* key, const unsigned char*, int) { + auto* des = static_cast(context->cipherData); + mbedtls_des3_init(des); + return mbedtls_des3_set3key_enc(des, key) == 0; +} + +int APS5_VABI DesCipher(LibreSslCipherContext* context, unsigned char* output, const unsigned char* input, std::size_t length) { + return mbedtls_des3_crypt_cbc(static_cast(context->cipherData), MBEDTLS_DES_ENCRYPT, length, context->iv, input, output) == 0; +} + +void GuestDes() { + const LibreSslCipher method{44, 8, 24, 8, 2, DesInit, DesCipher, nullptr, sizeof(mbedtls_des3_context), nullptr, nullptr, nullptr, nullptr}; + std::array key; + std::array message; + for (std::size_t index = 0; index < key.size(); ++index) key[index] = static_cast(index * 31 + 7); + for (std::size_t index = 0; index < message.size(); ++index) message[index] = static_cast(index * 71 + 3); + const auto* reference = mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_DES_EDE3_ECB); + Require(reference != nullptr, "reference 64-bit cipher"); + auto context = NewContext(); + for (std::size_t length = 0; length <= message.size(); ++length) { + std::array expected; + Require(mbedtls_cipher_cmac(reference, key.data(), 192, message.data(), length, expected.data()) == 0, "reference 64-bit CMAC"); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), &method, nullptr) == 1, "guest 64-bit cipher initialization"); + for (std::size_t index = 0; index < length; ++index) Require(CMAC_Update_nid_postfix(context.get(), message.data() + index, 1) == 1, "guest 64-bit cipher update"); + std::array actual; + actual.fill(0xa5); + std::size_t outputLength = 99; + Require(CMAC_Final_nid_postfix(context.get(), actual.data() + 1, &outputLength) == 1, "guest 64-bit cipher final"); + Require(outputLength == 8 && actual.front() == 0xa5 && actual.back() == 0xa5 && std::equal(expected.begin(), expected.end(), actual.begin() + 1), "64-bit CMAC tag and output bounds"); + } +} + +const LibreSslCipher* delegated; +int initCalls; +int cipherCalls; +int cleanupCalls; +int failAt; + +int APS5_VABI GuestInit(LibreSslCipherContext* context, const unsigned char* key, const unsigned char* iv, int encrypt) { + ++initCalls; + Require(context->encrypt == 1 && context->keyLength == 16 && context->cipherData != nullptr, "guest callback context"); + return delegated->init(context, key, iv, encrypt); +} + +int APS5_VABI GuestCipher(LibreSslCipherContext* context, unsigned char* output, const unsigned char* input, std::size_t length) { + ++cipherCalls; + if (failAt == cipherCalls) { + std::memset(output, 0x5a, length); + return 0; + } + return delegated->cipher(context, output, input, length); +} + +int APS5_VABI GuestCleanup(LibreSslCipherContext*) { + ++cleanupCalls; + return 1; +} + +int APS5_VABI GuestControl(LibreSslCipherContext* context, int command, int argument, void*) { + if (command == 0) return 1; + if (command == 1 && argument == 16) { + context->keyLength = argument; + return 1; + } + return -1; +} + +void Callbacks() { + delegated = Methods[0]; + auto method = *delegated; + method.init = GuestInit; + method.cipher = GuestCipher; + method.cleanup = GuestCleanup; + method.control = GuestControl; + method.flags |= 0xc0; + std::array key{}; + std::array message{}; + { + auto context = NewContext(); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), &method, nullptr) == 1 && initCalls == 1, "guest initialization callback"); + Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1 && cipherCalls == 3, "guest cipher callbacks"); + const auto tag = Final(context.get()); + Require(CMAC_Init_nid_postfix(context.get(), nullptr, 0, nullptr, nullptr) == 1, "guest callback restart"); + Require(CMAC_Update_nid_postfix(context.get(), message.data(), message.size()) == 1 && Final(context.get()) == tag, "guest-defined cipher matches builtin"); + failAt = cipherCalls + 1; + std::array output; + output.fill(0xa5); + std::size_t length = 99; + Require(CMAC_Final_nid_postfix(context.get(), output.data(), &length) == 0 && length == 16, "cipher failure propagates"); + Require(std::all_of(output.begin(), output.end(), [](auto value) { return value == 0; }), "failed final clears output"); + failAt = 0; + } + Require(cleanupCalls == 1, "guest cleanup callback"); + auto context = NewContext(); + method.blockSize = 32; + ERR_clear_error_nid_postfix(); + Require(CMAC_Init_nid_postfix(context.get(), key.data(), key.size(), &method, nullptr) == 0, "reject unsupported block size"); + Require(ERR_get_error_nid_postfix() == 0x06fff088, "bad block length publishes EVP error"); +} +} + +int main(int argc, char** argv) { +#ifdef ANYPS5_CMAC_DYNAMIC + Require(argc == 2, "patched test needs the library directory"); + InitializePatched(argv[1]); +#endif + InitializeHeap(); + Methods[0] = EVP_aes_128_cbc_nid_postfix(); + Methods[1] = EVP_aes_192_cbc_nid_postfix(); + Methods[2] = EVP_aes_256_cbc_nid_postfix(); + RfcVectors(); + Differential(); + Lifecycle(); + Callbacks(); + AllocationFailures(); + CipherCallbacks(); + GuestDes(); + Require(allocations.empty(), "all guest allocations released"); + std::puts("CMAC vectors, streaming, lifecycle and callback checks passed"); +} diff --git a/core/libs/tests/GuestLibreSslError.cpp b/core/libs/tests/GuestLibreSslError.cpp index 5c7adcee6a..905139250f 100644 --- a/core/libs/tests/GuestLibreSslError.cpp +++ b/core/libs/tests/GuestLibreSslError.cpp @@ -1,6 +1,7 @@ -#include "prx/libSceLibreSsl/include/Api.hpp" -#include "prx/libSceLibreSsl/include/Error.hpp" +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceLibreSsl/include/Error.hpp" #include #include #include diff --git a/core/libs/tests/GuestLibreSslErrors.cpp b/core/libs/tests/GuestLibreSslErrors.cpp new file mode 100644 index 0000000000..8a5ea99206 --- /dev/null +++ b/core/libs/tests/GuestLibreSslErrors.cpp @@ -0,0 +1,181 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#include "prx/libSceLibreSsl3/libSceLibreSsl3.hpp" +#include "prx/libSceLibreSsl/ErrorQueue.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +struct ErrorString { + std::uint32_t code; + const char* text; +}; + +#include "ErrorStrings.inc" +} + +static void Require(bool value, const char* message) { + if (value) return; + std::fputs(message, stderr); + std::fputc('\n', stderr); + std::abort(); +} + +static void ExpectString(std::uint64_t code, const char* expected) { + std::array buffer{}; + Require(ERR_error_string_nid_postfix(code, buffer.data()) == buffer.data(), "caller buffer returned"); + Require(std::strcmp(buffer.data(), expected) == 0, buffer.data()); +} + +static void ExpectNumeric(std::uint64_t code) { + std::array hexadecimal{}; + std::snprintf(hexadecimal.data(), hexadecimal.size(), "%08llX", static_cast(code)); + const auto expected = "error:" + std::string(hexadecimal.data()) + + ":lib(" + std::to_string((code >> 24) & 255) + + "):func(" + std::to_string((code >> 12) & 4095) + + "):reason(" + std::to_string(code & 4095) + ")"; + ExpectString(code, expected.c_str()); +} + +static void TestNumericCodes() { + for (const auto& entry : CryptoStrings) ExpectNumeric(entry.code); + for (std::uint64_t library = 0; library < 256; ++library) ExpectNumeric(library << 24); + for (std::uint64_t reason = 1; reason <= 127; ++reason) ExpectNumeric(0x02000000 | reason); + for (const auto code : {0x100000000ULL, 0x12345678e1abc789ULL, 0xffffffffffffffffULL, 0x8000000000000000ULL}) ExpectNumeric(code); + std::uint64_t code = 0x51e073ab94d62fc8; + for (int index = 0; index < 2000; ++index) { + code ^= code << 13; + code ^= code >> 7; + code ^= code << 17; + ExpectNumeric(code); + } +} + +static void TestStrings() { + TestNumericCodes(); + ExpectString(0, "error:00000000:lib(0):func(0):reason(0)"); + ExpectString(0x20fff043, "error:20FFF043:lib(32):func(4095):reason(67)"); + ExpectString(0x20fff07e, "error:20FFF07E:lib(32):func(4095):reason(126)"); + ExpectString(0x03fff06c, "error:03FFF06C:lib(3):func(4095):reason(108)"); + ExpectString(0x0dfff0a8, "error:0DFFF0A8:lib(13):func(4095):reason(168)"); + ExpectString(0x08fff065, "error:08FFF065:lib(8):func(4095):reason(101)"); + ExpectString(0xe1abc789, "error:E1ABC789:lib(225):func(2748):reason(1929)"); + ExpectString(0x12345678e1abc789, "error:12345678E1ABC789:lib(225):func(2748):reason(1929)"); + ExpectString(0x02002023, "error:02002023:lib(2):func(2):reason(35)"); + auto* shared = ERR_error_string_nid_postfix(0xe1abc789, nullptr); + Require(shared != nullptr, "static buffer exists"); + Require(ERR_error_string_nid_postfix(0, nullptr) == shared, "static buffer reused"); + Require(std::strcmp(shared, "error:00000000:lib(0):func(0):reason(0)") == 0, "static buffer overwritten"); + constexpr auto expected = "error:E1ABC789:lib(225):func(2748):reason(1929)"; + for (std::size_t size = 0; size <= std::strlen(expected) + 2; ++size) { + std::array buffer; + buffer.fill('#'); + ERR_error_string_n_nid_postfix(0xe1abc789, buffer.data() + 1, size); + Require(buffer.front() == '#' && buffer[size + 1] == '#', "bounded formatting canaries"); + if (size == 0) continue; + const auto count = std::strlen(buffer.data() + 1); + Require(count < size, "bounded formatting terminates"); + if (size > 4 && size <= std::strlen(expected)) { + std::size_t colons = 0; + for (std::size_t index = 1; index <= count; ++index) colons += buffer[index] == ':'; + Require(colons == 4, "truncation keeps four colons"); + } + if (size > std::strlen(expected)) Require(std::strcmp(buffer.data() + 1, expected) == 0, "full bounded string"); + } + char tiny[6]{}; + ERR_error_string_n_nid_postfix(0xe1abc789, tiny, sizeof(tiny)); + Require(std::strcmp(tiny, "e::::") == 0, "historical truncation placement"); + ERR_error_string_n_nid_postfix(0, nullptr, 0); +} + +static void TestQueue() { + ERR_clear_error_nid_postfix(); + Require(ERR_get_error_nid_postfix() == 0 && ERR_peek_error_nid_postfix() == 0 && + ERR_peek_last_error_nid_postfix() == 0, "initially empty"); + auto* guestError = __error_nid_postfix(); + errno = EDOM; + const int savedGuestError = *guestError; + LibreSslPutError_nid_no_patch(32, 0xfff, 67, "producer", 19); + Require(errno == EDOM, "publication preserves host errno"); + Require(*guestError == savedGuestError, "publication preserves guest errno"); + LibreSslPutError_nid_no_patch(3, 0xfff, 108, nullptr, 0); + Require(ERR_peek_error_nid_postfix() == 0x20fff043 && ERR_peek_error_nid_postfix() == 0x20fff043, "peek oldest without removal"); + Require(ERR_peek_last_error_nid_postfix() == 0x03fff06c && ERR_peek_last_error_nid_postfix() == 0x03fff06c, "cross-library peek newest"); + Require(ERR_get_error_nid_postfix() == 0x20fff043 && ERR_get_error_nid_postfix() == 0x03fff06c, "FIFO removal"); + Require(ERR_get_error_nid_postfix() == 0 && ERR_peek_last_error_nid_postfix() == 0, "drained queue"); + LibreSslPutError_nid_no_patch(-1, -1, -1, nullptr, 0); + Require(ERR_get_error_nid_postfix() == 0xffffffff, "pack masks negative fields"); + LibreSslPutError_nid_no_patch(0x132, 0x2345, 0x3456, nullptr, 0); + Require(ERR_get_error_nid_postfix() == 0x32345456, "pack masks oversized fields"); + LibreSslPutError_nid_no_patch(0, 0, 0, nullptr, 0); + LibreSslPutError_nid_no_patch(0, 0, 1, nullptr, 0); + Require(ERR_get_error_nid_postfix() == 0 && ERR_get_error_nid_postfix() == 1, "zero code occupies a slot"); + for (int index = 1; index <= 100; ++index) LibreSslPutError_nid_no_patch(128, index, index, nullptr, index); + for (std::uint64_t index = 86; index <= 100; ++index) { + Require(ERR_get_error_nid_postfix() == (0x80000000 | (index << 12) | index), "retain newest fifteen after overflow"); + } + Require(ERR_get_error_nid_postfix() == 0, "overflow queue drained"); + LibreSslPutError_nid_no_patch(3, 1, 2, nullptr, 0); + ERR_clear_error_nid_postfix(); + Require(ERR_peek_error_nid_postfix() == 0 && ERR_peek_last_error_nid_postfix() == 0, "clear both queue ends"); + LibreSslPutError_nid_no_patch(32, 0xfff, 67, nullptr, 0); + ERR_remove_thread_state_nid_postfix(nullptr); + Require(ERR_get_error_nid_postfix() == 0, "remove current state"); + LibreSslPutError_nid_no_patch(32, 0xfff, 67, nullptr, 0); + Require(ERR_get_error_nid_postfix() == 0x20fff043, "reuse after remove"); +} + +static void TestThreads() { + ERR_clear_error_nid_postfix(); + LibreSslPutError_nid_no_patch(128, 7, 19, nullptr, 0); + std::atomic ready{0}; + std::array threads; + for (std::size_t index = 0; index < threads.size(); ++index) { + threads[index] = std::thread([&, index] { + Require(ERR_get_error_nid_postfix() == 0, "new thread starts empty"); + LibreSslPutError_nid_no_patch(129, 0, static_cast(index + 1), nullptr, 0); + ++ready; + while (ready.load() != static_cast(threads.size())) std::this_thread::yield(); + ERR_free_strings_nid_postfix(); + ERR_load_crypto_strings_nid_postfix(); + ExpectString(0x0dfff0a8, "error:0DFFF0A8:lib(13):func(4095):reason(168)"); + Require(ERR_peek_last_error_nid_postfix() == 0x81000001 + index, "thread-local newest"); + if (index % 2) ERR_clear_error_nid_postfix(); + else ERR_remove_thread_state_nid_postfix(nullptr); + Require(ERR_get_error_nid_postfix() == 0, "thread-local cleanup"); + LibreSslPutError_nid_no_patch(130, 1, 3, nullptr, 0); + }); + } + for (auto& thread : threads) thread.join(); + Require(ERR_get_error_nid_postfix() == 0x80007013, "child queues leave parent unchanged"); + std::thread replacement([] { Require(ERR_get_error_nid_postfix() == 0, "no stale state after thread exit"); }); + replacement.join(); +} + +int main(int argc, char** argv) { + if (argc == 2) { + if (std::strcmp(argv[1], "free") == 0) TestStrings(); + LibreSslPutError_nid_no_patch(32, 0xfff, 67, nullptr, 0); + ERR_free_strings_nid_postfix(); + TestStrings(); + Require(ERR_peek_error_nid_postfix() == 0x20fff043, "free strings preserves queued error"); + ERR_load_crypto_strings_nid_postfix(); + TestStrings(); + ERR_free_strings_nid_postfix(); + ERR_load_crypto_strings_nid_postfix(); + TestStrings(); + Require(ERR_get_error_nid_postfix() == 0x20fff043, "free strings preserves queue"); + return 0; + } + TestStrings(); + ERR_load_crypto_strings_nid_postfix(); + TestStrings(); + TestQueue(); + TestThreads(); +} diff --git a/core/libs/tests/GuestLibreSslEvp.cpp b/core/libs/tests/GuestLibreSslEvp.cpp new file mode 100644 index 0000000000..c96b5e0002 --- /dev/null +++ b/core/libs/tests/GuestLibreSslEvp.cpp @@ -0,0 +1,410 @@ +#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 +#include + +struct Algorithm { + const char* name; + const EvpMd* md; + int type; + int pkeyType; + int size; + int block; + int contextSize; + std::uint64_t flags; + const char* digests[3]; + const char* macs[6]; +}; + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL EVP 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 int allocationsBeforeFailure = -1; +static int liveBlocks = 0; + +static bool FailAllocation() { + if (allocationsBeforeFailure < 0) return false; + return allocationsBeforeFailure-- == 0; +} + +static void* APS5_VABI Allocate(std::size_t bytes) { + if (FailAllocation()) return nullptr; + void* const block = std::malloc(bytes ? bytes : 1); + if (block) ++liveBlocks; + return block; +} + +static void APS5_VABI Release(void* block) { + if (block) --liveBlocks; + std::free(block); +} + +static void* APS5_VABI AllocateZeroed(std::size_t count, std::size_t bytes) { + if (FailAllocation()) return nullptr; + void* const block = std::calloc(count ? count : 1, bytes ? bytes : 1); + if (block) ++liveBlocks; + return block; +} + +static void* APS5_VABI Reallocate(void*, std::size_t) { return nullptr; } +static void* APS5_VABI Align(std::size_t, std::size_t) { return nullptr; } +static void* APS5_VABI Realign(void*, std::size_t, std::size_t) { return nullptr; } +static int APS5_VABI PosixAlign(void**, std::size_t, std::size_t) { return 12; } + +static void InitializeHeap() { + void* const api[10] { + reinterpret_cast(&Allocate), reinterpret_cast(&Release), reinterpret_cast(&AllocateZeroed), + reinterpret_cast(&Reallocate), reinterpret_cast(&Align), reinterpret_cast(&Realign), + reinterpret_cast(&PosixAlign), + }; + ApplicationHeapRegister_nid_no_patch(api); +} + +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::vector Bytes(const char* text) { + return std::vector(text, text + std::strlen(text)); +} + +static bool AllZero(const void* data, std::size_t size) { + const auto* bytes = static_cast(data); + return std::all_of(bytes, bytes + size, [](unsigned char value) { return value == 0; }); +} + +template static T Field(const void* base, std::size_t offset) { + T value; + std::memcpy(&value, static_cast(base) + offset, sizeof(value)); + return value; +} + +static std::string DigestInChunks(EvpMdContext& context, const EvpMd* md, const std::vector& message, std::size_t chunk) { + unsigned char digest[64] {}; + unsigned int size = 0; + Require(EVP_DigestInit_ex_nid_postfix(&context, md, nullptr) == 1); + for (std::size_t offset = 0; offset < message.size(); offset += chunk) + Require(EVP_DigestUpdate_nid_postfix(&context, message.data() + offset, std::min(chunk, message.size() - offset)) == 1); + Require(EVP_DigestFinal_ex_nid_postfix(&context, digest, &size) == 1); + return Hex(digest, size); +} + +static std::string MacInChunks(HmacContext* context, const std::vector& key, const EvpMd* md, const std::vector& message, std::size_t chunk) { + unsigned char mac[64] {}; + unsigned int size = 0; + Require(HMAC_Init_ex_nid_postfix(context, key.data(), static_cast(key.size()), md, nullptr) == 1); + for (std::size_t offset = 0; offset < message.size(); offset += chunk) + Require(HMAC_Update_nid_postfix(context, message.data() + offset, std::min(chunk, message.size() - offset)) == 1); + Require(HMAC_Final_nid_postfix(context, mac, &size) == 1); + return Hex(mac, size); +} + +static int customInits = 0; +static int customCleanups = 0; + +static int APS5_VABI CustomInit(EvpMdContext* context) { + ++customInits; + std::memset(context->mdData, 0, 4); + return 1; +} + +static int APS5_VABI CustomUpdate(EvpMdContext* context, const void* data, std::size_t length) { + auto* sum = static_cast(context->mdData); + for (std::size_t i = 0; i < length; ++i) sum[i % 4] = static_cast(sum[i % 4] + static_cast(data)[i]); + return 1; +} + +static int APS5_VABI CustomFinal(EvpMdContext* context, unsigned char* digest) { + std::memcpy(digest, context->mdData, 4); + return 1; +} + +static int APS5_VABI CustomCleanup(EvpMdContext*) { + ++customCleanups; + return 1; +} + +int main() { + InitializeHeap(); + const std::vector empty; + const auto abc = Bytes("abc"); + std::vector longMessage(1000); + for (std::size_t i = 0; i < longMessage.size(); ++i) longMessage[i] = static_cast(i * 7 + 3); + const std::vector> messages {empty, abc, longMessage}; + + std::vector, std::vector>> macCases; + macCases.emplace_back(std::vector(20, 0x0b), Bytes("Hi There")); + macCases.emplace_back(Bytes("Jefe"), Bytes("what do ya want for nothing?")); + macCases.emplace_back(std::vector(20, 0xaa), std::vector(50, 0xdd)); + std::vector counting(25); + for (std::size_t i = 0; i < counting.size(); ++i) counting[i] = static_cast(i + 1); + macCases.emplace_back(counting, std::vector(50, 0xcd)); + macCases.emplace_back(std::vector(131, 0xaa), Bytes("Test Using Larger Than Block-Size Key - Hash Key First")); + macCases.emplace_back(std::vector(131, 0xaa), Bytes("This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.")); + + const Algorithm algorithms[] { + {"MD5", EVP_md5_nid_postfix(), 4, 8, 16, 64, 100, 0, + {"d41d8cd98f00b204e9800998ecf8427e", "900150983cd24fb0d6963f7d28e17f72", "10046f077f2082ac19676b8079f1cb1a"}, + {"5ccec34ea9656392457fa1ac27f08fbc", "750c783e6ab0b503eaa86e310a5db738", "2ab8b9a9f7d3894d15ad8383b97044b2", + "697eaf0aca3a3aea3a75164746ffaa79", "bfecaf4efff90a3a668f3922fec3762d", "09b8ae7b15adbbb243aca3491b51512b"}}, + {"SHA256", EVP_sha256_nid_postfix(), 672, 668, 32, 64, 120, 0xc, + {"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", + "1e9bc38cbf860b9ec31918b065f9b52476c549a782e0e7990bed8ce3868d2371"}, + {"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7", "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843", + "773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe", "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b", + "60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54", "9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2"}}, + {"SHA384", EVP_sha384_nid_postfix(), 673, 669, 48, 128, 224, 0xc, + {"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7", + "94c38db521ca733b8904c2d14b6e82d33dcfcc26e1318c579dbee1fc2f472019034e792263b9d46e90e700de2f6e7e91"}, + {"afd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6", + "af45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649", + "88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27", + "3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb", + "4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952", + "6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e"}}, + {"SHA512", EVP_sha512_nid_postfix(), 674, 670, 64, 128, 224, 0xc, + {"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", + "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", + "00e36fccf193e59697a92b5ab24666ce6326d7fa16bf10832d0991ddc591112e9dfa6a636950ed9c4d67344a760654c2ff7785e1d60094d651038735b5dccabd"}, + {"87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854", + "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737", + "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb", + "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd", + "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598", + "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"}}, + }; + + Require(EVP_MD_size_nid_postfix(nullptr) == -1); + Require(EVP_md5_nid_postfix() == algorithms[0].md && EVP_sha512_nid_postfix() == algorithms[3].md); + for (std::size_t i = 0; i < 4; ++i) + for (std::size_t j = i + 1; j < 4; ++j) Require(algorithms[i].md != algorithms[j].md); + for (const auto& algorithm : algorithms) { + Require(algorithm.md != nullptr); + Require(EVP_MD_size_nid_postfix(algorithm.md) == algorithm.size); + Require(Field(algorithm.md, 0) == algorithm.type); + Require(Field(algorithm.md, 4) == algorithm.pkeyType); + Require(Field(algorithm.md, 8) == algorithm.size); + Require(Field(algorithm.md, 16) == algorithm.flags); + Require(Field(algorithm.md, 80) == 6 && Field(algorithm.md, 84) == 19); + Require(Field(algorithm.md, 100) == algorithm.block); + Require(Field(algorithm.md, 104) == algorithm.contextSize); + Require(Field(algorithm.md, 24) && Field(algorithm.md, 32) && Field(algorithm.md, 40)); + Require(Field(algorithm.md, 48) == nullptr && Field(algorithm.md, 56) == nullptr); + + auto* created = EVP_MD_CTX_create_nid_postfix(); + Require(created && AllZero(created, sizeof(EvpMdContext))); + for (std::size_t m = 0; m < messages.size(); ++m) { + for (const std::size_t chunk : {std::size_t(1), std::size_t(algorithm.block - 1), std::size_t(algorithm.block), std::size_t(algorithm.block + 1), std::size_t(1000)}) + Require(DigestInChunks(*created, algorithm.md, messages[m], chunk) == algorithm.digests[m]); + unsigned char digest[64] {}; + unsigned int size = 0; + Require(EVP_Digest_nid_postfix(messages[m].data(), messages[m].size(), digest, &size, algorithm.md, nullptr) == 1); + Require(size == static_cast(algorithm.size) && Hex(digest, size) == algorithm.digests[m]); + Require(EVP_Digest_nid_postfix(messages[m].data(), messages[m].size(), digest, nullptr, algorithm.md, nullptr) == 1); + } + Require(created->digest == algorithm.md && Field(created, 40) == Field(algorithm.md, 32)); + Require(created->mdData && AllZero(created->mdData, static_cast(algorithm.contextSize))); + void* const data = created->mdData; + Require(EVP_DigestInit_ex_nid_postfix(created, algorithm.md, nullptr) == 1 && created->mdData == data); + Require(EVP_MD_CTX_cleanup_nid_postfix(created) == 1 && AllZero(created, sizeof(EvpMdContext))); + EVP_MD_CTX_destroy_nid_postfix(created); + + HmacContext hmac; + const auto* hmacBytes = reinterpret_cast(&hmac); + std::memset(&hmac, 0x5a, sizeof(hmac)); + HMAC_CTX_init_nid_postfix(&hmac); + Require(Field(&hmac, 0) == nullptr && AllZero(hmacBytes + 8, 3 * sizeof(EvpMdContext))); + for (std::size_t c = 0; c < macCases.size(); ++c) { + const auto& [key, message] = macCases[c]; + for (const std::size_t chunk : {std::size_t(1), std::size_t(algorithm.block), std::size_t(1000)}) + Require(MacInChunks(&hmac, key, algorithm.md, message, chunk) == algorithm.macs[c]); + Require(Field(&hmac, 0) == algorithm.md); + const bool hashed = key.size() > static_cast(algorithm.block); + const auto keyLength = Field(&hmac, 152); + Require(keyLength == (hashed ? static_cast(algorithm.size) : key.size())); + Require(hashed || std::memcmp(hmacBytes + 156, key.data(), key.size()) == 0); + Require(AllZero(hmacBytes + 156 + keyLength, 128 - keyLength)); + + unsigned char mac[64] {}; + unsigned int size = 0; + Require(HMAC_Init_ex_nid_postfix(&hmac, nullptr, 0, nullptr, nullptr) == 1); + Require(HMAC_Update_nid_postfix(&hmac, message.data(), message.size()) == 1); + Require(HMAC_Final_nid_postfix(&hmac, mac, &size) == 1 && Hex(mac, size) == algorithm.macs[c]); + Require(HMAC_Init_ex_nid_postfix(&hmac, nullptr, 0, algorithm.md, nullptr) == 1); + Require(HMAC_Update_nid_postfix(&hmac, message.data(), message.size()) == 1); + Require(HMAC_Final_nid_postfix(&hmac, mac, nullptr) == 1 && Hex(mac, static_cast(algorithm.size)) == algorithm.macs[c]); + + std::memset(mac, 0, sizeof(mac)); + size = 0; + Require(HMAC_nid_postfix(algorithm.md, key.data(), static_cast(key.size()), message.data(), message.size(), mac, &size) == mac); + Require(size == static_cast(algorithm.size) && Hex(mac, size) == algorithm.macs[c]); + const unsigned char* shared = HMAC_nid_postfix(algorithm.md, key.data(), static_cast(key.size()), message.data(), message.size(), nullptr, nullptr); + Require(shared && Hex(shared, static_cast(algorithm.size)) == algorithm.macs[c]); + } + HMAC_CTX_cleanup_nid_postfix(&hmac); + Require(AllZero(&hmac, sizeof(hmac))); + } + + const auto* sha256 = EVP_sha256_nid_postfix(); + const auto* sha512 = EVP_sha512_nid_postfix(); + const auto* md5 = EVP_md5_nid_postfix(); + + EvpMdContext context; + std::memset(&context, 0x77, sizeof(context)); + EVP_MD_CTX_init_nid_postfix(&context); + Require(AllZero(&context, sizeof(context))); + Require(EVP_DigestInit_ex_nid_postfix(&context, nullptr, nullptr) == 0); + RequireThrows([&] { EVP_DigestUpdate_nid_postfix(&context, abc.data(), abc.size()); }); + RequireThrows([&] { EVP_DigestFinal_ex_nid_postfix(&context, nullptr, nullptr); }); + Require(EVP_DigestInit_ex_nid_postfix(&context, sha256, nullptr) == 1); + Require(EVP_DigestInit_ex_nid_postfix(&context, sha512, nullptr) == 1 && context.digest == sha512); + Require(DigestInChunks(context, sha512, abc, 2) == algorithms[3].digests[1]); + RequireThrows([&] { EVP_DigestInit_ex_nid_postfix(&context, nullptr, nullptr); }); + RequireThrows([&] { EVP_DigestUpdate_nid_postfix(&context, nullptr, 1); }); + RequireThrows([&] { EVP_DigestUpdate_nid_postfix(nullptr, abc.data(), abc.size()); }); + RequireThrows([&] { EVP_MD_CTX_init_nid_postfix(nullptr); }); + RequireThrows([&] { EVP_MD_CTX_cleanup_nid_postfix(nullptr); }); + EVP_MD_CTX_destroy_nid_postfix(nullptr); + Require(EVP_MD_CTX_cleanup_nid_postfix(&context) == 1); + + EVP_MD_CTX_init_nid_postfix(&context); + context.flags = 0x100; + Require(EVP_DigestInit_ex_nid_postfix(&context, sha256, nullptr) == 1); + Require(context.digest == sha256 && context.mdData == nullptr && context.update == nullptr); + Require(EVP_MD_CTX_cleanup_nid_postfix(&context) == 1 && AllZero(&context, sizeof(context))); + + EvpMd custom {}; + custom.type = 12345; + custom.mdSize = 4; + custom.init = CustomInit; + custom.update = CustomUpdate; + custom.finalize = CustomFinal; + custom.cleanup = CustomCleanup; + custom.blockSize = 64; + custom.contextSize = 16; + unsigned char sum[4] {}; + unsigned int sumSize = 0; + const unsigned char bytes[] {1, 2, 3, 4, 5, 6, 7, 8, 9}; + Require(EVP_Digest_nid_postfix(bytes, sizeof(bytes), sum, &sumSize, &custom, nullptr) == 1); + Require(sumSize == 4 && sum[0] == 15 && sum[1] == 8 && sum[2] == 10 && sum[3] == 12); + Require(customInits == 1 && customCleanups == 1); + Require(EVP_MD_size_nid_postfix(&custom) == 4); + custom.mdSize = 65; + Require(EVP_Digest_nid_postfix(bytes, sizeof(bytes), sum, &sumSize, &custom, nullptr) == 0); + + HmacContext hmac {}; + const auto* hmacBytes = reinterpret_cast(&hmac); + HMAC_CTX_init_nid_postfix(&hmac); + unsigned char mac[64] {}; + unsigned int macSize = 0; + Require(HMAC_Update_nid_postfix(&hmac, abc.data(), abc.size()) == 0); + Require(HMAC_Final_nid_postfix(&hmac, mac, &macSize) == 0); + Require(HMAC_Init_ex_nid_postfix(&hmac, nullptr, 0, nullptr, nullptr) == 0); + Require(HMAC_Init_ex_nid_postfix(&hmac, nullptr, 0, sha256, nullptr) == 0); + Require(HMAC_Init_ex_nid_postfix(&hmac, "k", -1, sha256, nullptr) == 0); + Require(HMAC_Init_ex_nid_postfix(&hmac, "Jefe", 4, sha256, nullptr) == 1); + Require(HMAC_Init_ex_nid_postfix(&hmac, nullptr, 0, md5, nullptr) == 0); + Require(HMAC_Init_ex_nid_postfix(&hmac, "Jefe", -1, nullptr, nullptr) == 0); + Require(HMAC_Init_ex_nid_postfix(&hmac, "Jefe", 4, nullptr, nullptr) == 1); + const auto question = Bytes("what do ya want for nothing?"); + Require(HMAC_Update_nid_postfix(&hmac, question.data(), question.size()) == 1); + Require(HMAC_Final_nid_postfix(&hmac, mac, &macSize) == 1 && Hex(mac, macSize) == algorithms[1].macs[1]); + HMAC_CTX_cleanup_nid_postfix(&hmac); + + std::memset(&hmac, 0x33, sizeof(hmac)); + Require(HMAC_Init_nid_postfix(&hmac, "Jefe", 4, md5) == 1); + Require(HMAC_Update_nid_postfix(&hmac, question.data(), question.size()) == 1); + Require(HMAC_Final_nid_postfix(&hmac, mac, &macSize) == 1 && Hex(mac, macSize) == algorithms[0].macs[1]); + Require(HMAC_Init_nid_postfix(&hmac, nullptr, 0, nullptr) == 1); + Require(HMAC_Update_nid_postfix(&hmac, question.data(), question.size()) == 1); + Require(HMAC_Final_nid_postfix(&hmac, mac, &macSize) == 1 && Hex(mac, macSize) == algorithms[0].macs[1]); + HMAC_CTX_cleanup_nid_postfix(&hmac); + + std::vector blockKey(128); + for (std::size_t i = 0; i < blockKey.size(); ++i) blockKey[i] = static_cast(i * 3 + 1); + const struct { const EvpMd* md; std::size_t keySize; const char* mac; } blockKeyCases[] { + {sha256, 64, "631102ef12fb140c75c4a32ec732a95ce65da9a6603779445246ba02f9df465e"}, + {sha512, 128, "4638cabcab823870028a23978c0291c3c51861040154631b99929a35e84aa6f5eebf2d3b0081ae6a45dd8241f0175302562bdf3f83b299af324bb54095207b03"}, + }; + for (const auto& [md, keySize, expected] : blockKeyCases) { + const std::vector key(blockKey.begin(), blockKey.begin() + static_cast(keySize)); + HMAC_CTX_init_nid_postfix(&hmac); + for (const std::size_t chunk : {std::size_t(1), keySize, std::size_t(1000)}) { + Require(MacInChunks(&hmac, key, md, longMessage, chunk) == expected); + Require(Field(&hmac, 152) == keySize && std::memcmp(hmacBytes + 156, key.data(), keySize) == 0); + } + HMAC_CTX_cleanup_nid_postfix(&hmac); + Require(HMAC_nid_postfix(md, key.data(), static_cast(keySize), longMessage.data(), longMessage.size(), mac, &macSize) == mac); + Require(Hex(mac, macSize) == expected); + } + + Require(HMAC_nid_postfix(sha256, nullptr, 0, abc.data(), abc.size(), mac, &macSize) == nullptr); + Require(HMAC_nid_postfix(sha256, "", 0, nullptr, 0, mac, &macSize) == mac && macSize == 32); + Require(Hex(mac, macSize) == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"); + RequireThrows([&] { HMAC_CTX_init_nid_postfix(nullptr); }); + RequireThrows([&] { HMAC_Update_nid_postfix(nullptr, abc.data(), abc.size()); }); + Require(liveBlocks == 0); + + EVP_MD_CTX_init_nid_postfix(&context); + allocationsBeforeFailure = 0; + Require(EVP_DigestInit_ex_nid_postfix(&context, sha256, nullptr) == 0); + Require(AllZero(&context, sizeof(context)) && liveBlocks == 0); + RequireThrows([&] { EVP_DigestUpdate_nid_postfix(&context, abc.data(), abc.size()); }); + RequireThrows([&] { EVP_DigestFinal_ex_nid_postfix(&context, mac, &macSize); }); + Require(DigestInChunks(context, sha256, abc, 1) == algorithms[1].digests[1] && liveBlocks == 1); + allocationsBeforeFailure = 0; + Require(EVP_DigestInit_ex_nid_postfix(&context, sha512, nullptr) == 0); + Require(AllZero(&context, sizeof(context)) && liveBlocks == 0); + Require(DigestInChunks(context, sha512, abc, 1) == algorithms[3].digests[1] && liveBlocks == 1); + Require(EVP_MD_CTX_cleanup_nid_postfix(&context) == 1 && liveBlocks == 0); + + allocationsBeforeFailure = 0; + Require(EVP_MD_CTX_create_nid_postfix() == nullptr && liveBlocks == 0); + allocationsBeforeFailure = 0; + Require(EVP_Digest_nid_postfix(abc.data(), abc.size(), mac, &macSize, sha256, nullptr) == 0 && liveBlocks == 0); + Require(EVP_Digest_nid_postfix(abc.data(), abc.size(), mac, &macSize, sha256, nullptr) == 1 && Hex(mac, macSize) == algorithms[1].digests[1]); + + for (const std::size_t c : {std::size_t(1), std::size_t(4)}) { + const auto& [key, message] = macCases[c]; + for (int failure = 0; failure < 3; ++failure) { + HMAC_CTX_init_nid_postfix(&hmac); + allocationsBeforeFailure = failure; + Require(HMAC_Init_ex_nid_postfix(&hmac, key.data(), static_cast(key.size()), sha256, nullptr) == 0); + Require(allocationsBeforeFailure == -1 && liveBlocks == failure); + for (const std::size_t offset : {std::size_t(8), std::size_t(56), std::size_t(104)}) + Require(Field(&hmac, offset + 24) || (!Field(&hmac, offset) && !Field(&hmac, offset + 40))); + if (key.size() <= 64) RequireThrows([&] { HMAC_Update_nid_postfix(&hmac, message.data(), message.size()); }); + Require(MacInChunks(&hmac, key, sha256, message, 1000) == algorithms[1].macs[c] && liveBlocks == 3); + HMAC_CTX_cleanup_nid_postfix(&hmac); + Require(liveBlocks == 0); + } + allocationsBeforeFailure = 2; + Require(HMAC_nid_postfix(sha256, key.data(), static_cast(key.size()), message.data(), message.size(), mac, &macSize) == nullptr); + Require(allocationsBeforeFailure == -1 && liveBlocks == 0); + } + return 0; +} diff --git a/core/libs/tests/GuestLibreSslEvpCipherCtx.cpp b/core/libs/tests/GuestLibreSslEvpCipherCtx.cpp new file mode 100644 index 0000000000..34f4558bcd --- /dev/null +++ b/core/libs/tests/GuestLibreSslEvpCipherCtx.cpp @@ -0,0 +1,90 @@ +#include "prx/libSceLibreSsl/include/Api.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +constexpr std::size_t ContextBytes = 168; +constexpr std::size_t FlagsOffset = 112; + +static std::uint64_t Flags(const unsigned char* context) { + std::uint64_t flags = 0; + std::memcpy(&flags, context + FlagsOffset, sizeof(flags)); + return flags; +} + +static void SetFlags(unsigned char* context, std::uint64_t flags) { + std::memcpy(context + FlagsOffset, &flags, sizeof(flags)); +} + +static void InitializeHeap() { + void* const api[10] {}; + ApplicationHeapRegister_nid_no_patch(api); +} + +static LibreSslEvpCipherCtx* Context(unsigned char* memory) { + return reinterpret_cast(memory); +} + +static void Require(bool condition, std::source_location location = std::source_location::current()) { + if (condition) return; + std::fprintf(stderr, "LibreSSL EVP_CIPHER_CTX check failed at line %u\n", location.line()); + std::abort(); +} + +template static void RequireThrows(TAction action, std::source_location location = std::source_location::current()) { + try { action(); } catch (const std::exception&) { return; } + Require(false, location); +} + +static void TestInit() { + unsigned char memory[ContextBytes + 16]; + std::memset(memory, 0xAA, sizeof(memory)); + EVP_CIPHER_CTX_init_nid_postfix(Context(memory)); + for (std::size_t index = 0; index < ContextBytes; ++index) Require(memory[index] == 0); + for (std::size_t index = ContextBytes; index < sizeof(memory); ++index) Require(memory[index] == 0xAA); + RequireThrows([] { EVP_CIPHER_CTX_init_nid_postfix(nullptr); }); +} + +static void TestNew() { + for (int round = 0; round < 4; ++round) { + auto* context = reinterpret_cast(EVP_CIPHER_CTX_new_nid_postfix()); + Require(context != nullptr); + for (std::size_t index = 0; index < ContextBytes; ++index) Require(context[index] == 0); + std::memset(context, 0x5A, ContextBytes); + free_nid_postfix(context); + } +} + +static void TestSetPadding() { + unsigned char context[ContextBytes] {}; + Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1); + Require(Flags(context) == 0x100); + Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1); + Require(Flags(context) == 0x100); + Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 1) == 1); + Require(Flags(context) == 0); + SetFlags(context, UINT64_C(0xFFFFFFFF000000FF)); + Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), 0) == 1); + Require(Flags(context) == UINT64_C(0xFFFFFFFF000001FF)); + Require(EVP_CIPHER_CTX_set_padding_nid_postfix(Context(context), -5) == 1); + Require(Flags(context) == UINT64_C(0xFFFFFFFF000000FF)); + for (std::size_t index = 0; index < ContextBytes; ++index) { + if (index < FlagsOffset || index >= FlagsOffset + 8) Require(context[index] == 0); + } + RequireThrows([] { EVP_CIPHER_CTX_set_padding_nid_postfix(nullptr, 0); }); +} + +int main() { + InitializeHeap(); + TestInit(); + TestNew(); + TestSetPadding(); + return 0; +} diff --git a/core/libs/tests/GuestLibreSslX509Extension.cpp b/core/libs/tests/GuestLibreSslX509Extension.cpp new file mode 100644 index 0000000000..f7d0845e64 --- /dev/null +++ b/core/libs/tests/GuestLibreSslX509Extension.cpp @@ -0,0 +1,60 @@ +#include "prx/libSceLibreSsl/libSceLibreSsl.hpp" +#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(); +} + +int main() { + static_assert(sizeof(void*) == 8); + Require(X509_EXTENSION_get_critical_nid_postfix(nullptr) == 0, "null extension is not critical"); + Require(X509_EXTENSION_get_data_nid_postfix(nullptr) == nullptr, "null extension has no data"); + Require(X509_EXTENSION_get_object_nid_postfix(nullptr) == nullptr, "null extension has no object"); + + struct CriticalCase { + std::int32_t stored; + int expected; + }; + constexpr CriticalCase criticalCases[] { + {std::numeric_limits::min(), 0}, {-256, 0}, {-1, 0}, {0, 0}, + {1, 1}, {127, 1}, {128, 1}, {255, 1}, {256, 1}, {65536, 1}, + {std::numeric_limits::max(), 1} + }; + unsigned char object[16] {}; + unsigned char octetString[24] {}; + alignas(8) std::array storage; + void* extension = storage.data() + 8; + void* pointers[] {nullptr, object, octetString, extension, + reinterpret_cast(std::uintptr_t{0x00007abc12345678})}; + for (void* objectPointer : pointers) { + for (void* dataPointer : pointers) { + for (const auto& item : criticalCases) { + for (unsigned char padding : {0x00, 0x7f, 0x80, 0xff}) { + storage.fill(padding); + std::memcpy(storage.data() + 8, &objectPointer, 8); + std::memcpy(storage.data() + 16, &item.stored, 4); + std::memcpy(storage.data() + 24, &dataPointer, 8); + const auto before = storage; + Require(X509_EXTENSION_get_critical_nid_postfix(extension) == item.expected, "signed critical field ignores padding"); + Require(X509_EXTENSION_get_object_nid_postfix(extension) == objectPointer, "borrowed object pointer is preserved"); + Require(X509_EXTENSION_get_data_nid_postfix(extension) == dataPointer, "borrowed ASN1 string pointer is preserved"); + Require(storage == before, "getters preserve the extension and adjacent bytes"); + } + } + } + } + + void* dataPointer = octetString; + std::memcpy(storage.data() + 24, &dataPointer, 8); + auto* borrowed = static_cast(X509_EXTENSION_get_data_nid_postfix(extension)); + borrowed[3] = 0x5a; + Require(octetString[3] == 0x5a, "data is borrowed, not copied or decoded"); + std::puts("libSceLibreSsl X509 extension tests passed"); +} diff --git a/core/libs/tests/GuestLocale.cpp b/core/libs/tests/GuestLocale.cpp index 35c2a6ece2..d334314329 100644 --- a/core/libs/tests/GuestLocale.cpp +++ b/core/libs/tests/GuestLocale.cpp @@ -484,6 +484,25 @@ static void CheckFacetGetcat() { 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); + Require(sizeof(_ZNSt8numpunctIwE2idE_nid_postfix) == 8); + _ZNSt8numpunctIwE2idE_nid_postfix = 11; + Require(_ZNSt8numpunctIwE2idE_nid_postfix == 11); + _ZNSt8numpunctIwE2idE_nid_postfix = 0; + GuestLocale::Facet* widePunct = nullptr; + Require(_ZNSt8numpunctIwE7_GetcatEPPKNSt6locale5facetEPKS1__nid_postfix(&widePunct, &locale) == 4 && widePunct != nullptr); + auto* widePunctVtable = reinterpret_cast(widePunct->vtable); + auto* widePunctFacet = reinterpret_cast(widePunct); + Require(widePunctVtable->decimalPoint(widePunctFacet) == u'.' && widePunctVtable->thousandsSep(widePunctFacet) == u','); + GuestLocale::String wideName{}; + Require(widePunctVtable->grouping(&wideName, widePunctFacet) == &wideName && wideName.size == 0); + Require(wideName.capacity == 15 && wideName.buffer[0] == '\0'); + const auto* wideContents = reinterpret_cast(wideName.size <= 7 ? wideName.buffer : wideName.pointer); + Require(widePunctVtable->falsename(&wideName, widePunctFacet)->size == 5); + wideContents = reinterpret_cast(wideName.size <= 7 ? wideName.buffer : wideName.pointer); + Require(std::memcmp(wideContents, u"false", 10) == 0); + Require(widePunctVtable->truename(&wideName, widePunctFacet)->size == 4); + wideContents = reinterpret_cast(wideName.size <= 7 ? wideName.buffer : wideName.pointer); + Require(std::memcmp(wideContents, u"true", 8) == 0); GuestLocale::Facet* numPut = nullptr; Require(_ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(&numPut, &locale) == 4 && numPut != nullptr); GuestLocale::Facet* timePut = nullptr; @@ -491,7 +510,7 @@ static void CheckFacetGetcat() { _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}) { + for (GuestLocale::Facet* facet : {codecvt, punct, widePunct, numPut, timePut}) { facet->vtable->retain(facet); facet->vtable->retain(facet); Require(facet->references == 2); diff --git a/core/libs/tests/GuestLoginService.cpp b/core/libs/tests/GuestLoginService.cpp index dfee6d6f6c..a2f3fde5ec 100644 --- a/core/libs/tests/GuestLoginService.cpp +++ b/core/libs/tests/GuestLoginService.cpp @@ -1,27 +1,31 @@ #include "prx/libSceLoginService/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" -#include #include -#include -#include -static void Require(bool value) { +namespace { + +void Require(bool value) { if (!value) std::abort(); } -int main() { - Require(sceLoginServiceInitialize() == 0); - Require(sceLoginServiceTerminate() == 0); - unsigned char buffer[0x80]{}; - try { - sceLoginServiceRequestDevices(reinterpret_cast(buffer)); +constexpr int kErrNotInitialized = static_cast(0x813a0001u); +constexpr int kErrInvalidArg = static_cast(0x813a0003u); - } 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()); - return 1; - } - std::fprintf(stderr, "sceLoginServiceRequestDevices returned instead of reporting that it is not implemented\n"); - return 1; +} + +int main() { + unsigned char buffer[0x80]{}; + const auto* request = reinterpret_cast(buffer); + + Require(sceLoginServiceRequestDevices(nullptr) == kErrInvalidArg); + Require(sceLoginServiceRequestDevices(request) == kErrNotInitialized); + + Require(sceLoginServiceInitialize() == 0); + Require(sceLoginServiceRequestDevices(nullptr) == kErrInvalidArg); + + Require(sceLoginServiceTerminate() == 0); + Require(sceLoginServiceRequestDevices(nullptr) == kErrInvalidArg); + Require(sceLoginServiceRequestDevices(request) == kErrNotInitialized); + return 0; } diff --git a/core/libs/tests/GuestMainStack.cpp b/core/libs/tests/GuestMainStack.cpp new file mode 100644 index 0000000000..0e2241682e --- /dev/null +++ b/core/libs/tests/GuestMainStack.cpp @@ -0,0 +1,107 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +#ifdef ANYPS5_ENABLE_ASAN +static constexpr std::uintptr_t GuestArenaOffset = 0x0000100000000000ull; +#else +static constexpr std::uintptr_t GuestArenaOffset = 0; +#endif +static constexpr std::uintptr_t ConsoleMainStackTop = 0x00000007EEFFC000ull + GuestArenaOffset; +static constexpr std::uintptr_t SmallestStack = 0x100000; +static int marker = 0; +static bool expectRefusal = false; + +#define Require(value) do { if (!(value)) { std::fprintf(stderr, "GuestMainStack failed at line %d\n", __LINE__); std::_Exit(1); } } while (false) + +static char Permissions(std::uintptr_t address) { + std::FILE* maps = std::fopen("/proc/self/maps", "r"); + Require(maps != nullptr); + char result = 0; + char line[512]; + while (std::fgets(line, sizeof(line), maps) != nullptr) { + unsigned long long low = 0; + unsigned long long high = 0; + char permissions[5] = {}; + if (std::sscanf(line, "%llx-%llx %4s", &low, &high, permissions) != 3 || address < low || address >= high) continue; + result = permissions[0] == 'r' && permissions[1] == 'w' ? 'w' : permissions[0] == 'r' ? 'r' : '-'; + break; + } + std::fclose(maps); + return result; +} + +struct WorkerContext { + std::atomic queried{false}; + void* local = nullptr; + void* start = nullptr; + void* end = nullptr; + int result = -1; +}; + +static void* APS5_VABI Worker(void* arg) { + auto& context = *static_cast(arg); + int local = 0; + context.local = &local; + context.result = sceKernelIsStack(&local, &context.start, &context.end); + context.queried.store(true); + return nullptr; +} + +static void APS5_VABI Entry(void* arguments) { + Require(!expectRefusal); + Require(arguments == &marker); + int local = 0; + const auto address = reinterpret_cast(&local); + void* start = nullptr; + void* end = nullptr; + Require(sceKernelIsStack(&local, &start, &end) == 0); + const auto low = reinterpret_cast(start); + const auto high = reinterpret_cast(end); + Require(high == ConsoleMainStackTop); + Require(low <= address && address < high); + Require(high - low >= SmallestStack); + Require(Permissions(low) == 'w' && Permissions(high - 1) == 'w'); + Require(Permissions(low - 1) == '-'); + + WorkerContext context; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, Worker, &context, nullptr) == 0); + Require(scePthreadJoin(thread, nullptr) == 0); + Require(context.queried.load() && context.result == 0); + Require(reinterpret_cast(context.end) <= low || reinterpret_cast(context.start) >= high); + std::exit(0); +} + +static void RefusesAnOverlap() { + constexpr std::size_t Bytes = 0x4000; + constexpr std::uint32_t Pattern = 0xa55a1234u; + void* sentinel = reinterpret_cast(ConsoleMainStackTop - 0x10000); + Require(sceKernelMapFlexibleMemory(&sentinel, Bytes, 3, 0x10) == 0); + Require(reinterpret_cast(sentinel) == ConsoleMainStackTop - 0x10000); + auto* words = static_cast(sentinel); + for (std::size_t i = 0; i < Bytes / 4; ++i) words[i] = Pattern; + bool refused = false; + expectRefusal = true; + try { + EnterGuestMainStack_nid_no_patch(&marker, Entry); + } catch (const std::runtime_error& error) { + refused = std::string(error.what()).find("overlaps a guest mapping") != std::string::npos; + } + expectRefusal = false; + Require(refused); + Require(Permissions(reinterpret_cast(sentinel)) == 'w'); + for (std::size_t i = 0; i < Bytes / 4; ++i) Require(words[i] == Pattern); + Require(sceKernelMunmap(reinterpret_cast(sentinel), Bytes) == 0); +} + +int main() { + RefusesAnOverlap(); + EnterGuestMainStack_nid_no_patch(&marker, Entry); +} diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp index f78f8f82bd..e8afd12c3e 100644 --- a/core/libs/tests/GuestMath.cpp +++ b/core/libs/tests/GuestMath.cpp @@ -129,7 +129,7 @@ static void CheckNearbyint() { Require(nearbyint_nid_postfix(0x1p53) == 0x1p53); Require(nearbyint_nid_postfix(INFINITY) == INFINITY && nearbyint_nid_postfix(-INFINITY) == -INFINITY); Require(std::isnan(nearbyint_nid_postfix(NAN))); - Require(std::fetestexcept(FE_INEXACT) == 0); + Require(std::fetestexcept(FE_INEXACT) != 0); std::feclearexcept(FE_ALL_EXCEPT); std::uint16_t savedControl; asm volatile("fnstcw %0" : "=m"(savedControl)); @@ -488,6 +488,13 @@ static void CheckNanConversions() { } static void CheckFloatUnderflowErrno() { + struct WideUnderflow { const char16_t* text; std::uint64_t bits; int error; }; + for (const WideUnderflow& test : {WideUnderflow{u"1e-310", 0x000012688b70e62bull, ERANGE}, WideUnderflow{u"-4.9406564584124654e-324", 0x8000000000000001ull, ERANGE}, + WideUnderflow{u"1e-330", 0, ERANGE}, WideUnderflow{u"2.2250738585072014e-308", 0x0010000000000000ull, 0}, WideUnderflow{u"0x1p-1074", 1, 0}, + WideUnderflow{u"0x1.8p-1075", 1, ERANGE}, WideUnderflow{u"-0x1.000001p-1075", 0x8000000000000001ull, ERANGE}}) { + errno = 0; + Require(std::bit_cast(wcstod_nid_postfix(test.text, nullptr)) == test.bits && errno == test.error); + } char* end = nullptr; errno = 7; Require(strtod_nid_postfix("1e-310", &end) == 1e-310 && errno == ERANGE); @@ -719,6 +726,55 @@ struct RestoreFloatingPointState { ~RestoreFloatingPointState() { Require(std::fesetenv(&environment) == 0); errno = error; } }; +static void CheckNearbyintEnvironment() { + const RestoreFloatingPointState saved; + const struct { + int mode; + double positiveHalf, negativeHalf, positiveTwoHalf, negativeTwoHalf; + } modes[] = { + {FE_TONEAREST, 0.0, -0.0, 2.0, -2.0}, + {FE_UPWARD, 1.0, -0.0, 3.0, -2.0}, + {FE_DOWNWARD, 0.0, -1.0, 2.0, -3.0}, + {FE_TOWARDZERO, 0.0, -0.0, 2.0, -2.0}, + }; + for (const auto& mode : modes) { + Require(std::fesetround(mode.mode) == 0); + const double cases[][2] = { + {0.5, mode.positiveHalf}, {-0.5, mode.negativeHalf}, + {2.5, mode.positiveTwoHalf}, {-2.5, mode.negativeTwoHalf}, + }; + for (const auto& [input, expected] : cases) { + Require(std::feclearexcept(FE_ALL_EXCEPT) == 0); + Require(std::feraiseexcept(FE_DIVBYZERO) == 0); + errno = 1234; + Require(std::bit_cast(nearbyint_nid_postfix(input)) == std::bit_cast(expected)); + Require(std::fetestexcept(FE_DIVBYZERO | FE_INEXACT) == (FE_DIVBYZERO | FE_INEXACT)); + Require(std::fegetround() == mode.mode && errno == 1234); + } + for (const double input : {0.0, -0.0, 2.0, -2.0, 0x1p52, -0x1p52, 0x1p53, -0x1p53, + std::numeric_limits::infinity(), -std::numeric_limits::infinity()}) { + Require(std::feclearexcept(FE_ALL_EXCEPT) == 0); + Require(std::feraiseexcept(FE_DIVBYZERO | FE_INEXACT) == 0); + Require(std::bit_cast(nearbyint_nid_postfix(input)) == std::bit_cast(input)); + Require(std::fetestexcept(FE_DIVBYZERO | FE_INEXACT) == (FE_DIVBYZERO | FE_INEXACT)); + Require(std::fegetround() == mode.mode); + } + const std::uint64_t nanCases[][2] = { + {0x7ff0000000000001ull, 0x7ff8000000000001ull}, + {0xfff4000000000123ull, 0xfffc000000000123ull}, + {0x7ff8000000000456ull, 0x7ff8000000000456ull}, + }; + for (const auto& [input, expected] : nanCases) { + Require(std::feclearexcept(FE_ALL_EXCEPT) == 0); + Require(std::feraiseexcept(FE_DIVBYZERO) == 0); + Require(std::bit_cast(nearbyint_nid_postfix(std::bit_cast(input))) == expected); + Require(std::fetestexcept(FE_DIVBYZERO) != 0); + if ((input & 0x0008000000000000ull) == 0) Require(std::fetestexcept(FE_INVALID) != 0); + Require(std::fegetround() == mode.mode); + } + } +} + static void CheckAcosfErrno() { constexpr std::uint32_t domain[][2] = { {0x40000000u, 0xffc00000u}, {0xc0000000u, 0xffc00000u}, {0x7f800000u, 0xffc00000u}, {0xff800000u, 0xffc00000u}, @@ -865,6 +921,74 @@ static long double MakeExtended(std::uint64_t mantissa, std::uint16_t signExpone return value; } +static void CheckExpConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0x3ff0000000000000ull, 0}, {0x8000000000000000ull, 0x3ff0000000000000ull, 0}, + {0x3e2fffffffffffffull, 0x3ff0000001000000ull, 0}, {0x3e30000000000000ull, 0x3ff0000001000000ull, 0}, + {0x3fd62e42fefa39efull, 0x3ff6a09e667f3bccull, 0}, {0x3fd62e4300000000ull, 0x3ff6a09e66dbc8daull, 0}, + {0x3ff0a2b1ffffffffull, 0x4006a09e0d126a08ull, 0}, {0x3ff0a2b200000000ull, 0x4006a09e0d126a0aull, 0}, + {0x3ff0000000000000ull, 0x4005bf0a8b145769ull, 0}, {0xbff0000000000000ull, 0x3fd78b56362cef38ull, 0}, + {0x4024000000000000ull, 0x40d5829dcf950560ull, 0}, {0xc024000000000000ull, 0x3f07cd79b5647c9aull, 0}, + {0x40862e42fefa39efull, 0x7fefffffffffff2aull, 0}, {0x40862e42fefa39f0ull, 0x7ff0000000000000ull, ERANGE}, + {0xc086232bdd7abcd2ull, 0x001000000000007cull, 0}, {0xc086232bdd7abcd3ull, 0x000ffffffffffe7cull, 0}, + {0xc0874910d52d3051ull, 0x0000000000000001ull, 0}, {0xc0874910d52d3052ull, 0x0000000000000000ull, ERANGE}, + {0x7fefffffffffffffull, 0x7ff0000000000000ull, ERANGE}, {0xffefffffffffffffull, 0x0000000000000000ull, ERANGE}, + {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, {0xfff0000000000000ull, 0x0000000000000000ull, 0}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0x4033ddceb4331e0aull, 0x41b94d703f21e868ull, 0}, + {0x401ce991ff636adcull, 0x4095866363930ec4ull, 0}, {0xc05c538238a89dfaull, 0x35b731b89f8aa7a0ull, 0}, + {0xbfcc83b6d471607dull, 0x3fe99c0d1b777558ull, 0}, {0x3fb6d36f6d11ceb0ull, 0x3ff17dfdd6f97518ull, 0}, + {0x4046b271a77d0918ull, 0x440677f14023ea1cull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(exp_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + +static void CheckLog10EdgeFlags() { + for (const double input : {-1.0, -std::numeric_limits::infinity(), -std::numeric_limits::quiet_NaN()}) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(log10_nid_postfix(input)) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + } + for (const double input : {0.0, -0.0}) { + errno = 1234; + std::feclearexcept(FE_ALL_EXCEPT); + Require(log10_nid_postfix(input) == -std::numeric_limits::infinity() && errno == 1234 && std::fetestexcept(FE_DIVBYZERO) == 0); + } +} +static void CheckLog10ConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0xfff0000000000000ull, 0}, {0x8000000000000000ull, 0xfff0000000000000ull, 0}, + {0x0000000000000001ull, 0xc07434e6420f4374ull, 0}, {0x8000000000000001ull, 0xfff8000000000000ull, EDOM}, + {0x000fffffffffffffull, 0xc0733a7146f72a42ull, 0}, {0x0010000000000000ull, 0xc0733a7146f72a42ull, 0}, + {0x3ff0000000000000ull, 0x0000000000000000ull, 0}, {0x3fefffffffffffffull, 0xbc8bcb7b1526e50full, 0}, + {0x3ff0000000000001ull, 0x3c9bcb7b1526e50dull, 0}, {0x3ff00000fffffffeull, 0x3e9bcb7a36937eb6ull, 0}, + {0x3ff6a09e00000000ull, 0x3fc3441254d07a4eull, 0}, {0x4024000000000000ull, 0x3ff0000000000000ull, 0}, + {0x408f400000000000ull, 0x4008000000000000ull, 0}, {0x3fb999999999999aull, 0xbff0000000000000ull, 0}, + {0x7fefffffffffffffull, 0x40734413509f79ffull, 0}, {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, + {0xbff0000000000000ull, 0xfff8000000000000ull, EDOM}, {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, + {0x4003a67d5567227bull, 0x3fd8fa5a3b281494ull, 0}, {0x4047f462f020acdeull, 0x3ffae2ff9b87b792ull, 0}, + {0x3ff9e926ec917ab6ull, 0x3fcacc4f022be644ull, 0}, {0x40bb970e057a80b2ull, 0x400ecabc9e60637cull, 0}, + {0x4052444de3537dcfull, 0x3ffdd1cefbc9b58cull, 0}, {0x401d69da29dc587bull, 0x3febba41e2e308e6ull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(log10_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + static void CheckExpm1ConsoleBits() { constexpr struct { std::uint64_t input; @@ -1263,6 +1387,48 @@ static void CheckExpm1ScalingRounding() { _mm_setcsr(savedCsr); } + + +static void CheckLog2EdgeFlags() { + for (const double input : {-1.0, -std::numeric_limits::infinity(), -std::numeric_limits::quiet_NaN()}) { + errno = 0; + std::feclearexcept(FE_ALL_EXCEPT); + Require(std::isnan(log2_nid_postfix(input)) && errno == EDOM && std::fetestexcept(FE_INVALID) != 0); + } + for (const double input : {0.0, -0.0}) { + errno = 1234; + std::feclearexcept(FE_ALL_EXCEPT); + Require(log2_nid_postfix(input) == -std::numeric_limits::infinity() && errno == 1234 && std::fetestexcept(FE_DIVBYZERO) == 0); + } +} +static void CheckLog2ConsoleBits() { + constexpr struct { + std::uint64_t input; + std::uint64_t expected; + int error; + } cases[] = { + {0x0000000000000000ull, 0xfff0000000000000ull, 0}, {0x8000000000000000ull, 0xfff0000000000000ull, 0}, + {0x0000000000000001ull, 0xc090c80000000000ull, 0}, {0x8000000000000001ull, 0xfff8000000000000ull, EDOM}, + {0x000fffffffffffffull, 0xc08ff00000000000ull, 0}, {0x0010000000000000ull, 0xc08ff00000000000ull, 0}, + {0x3ff0000000000000ull, 0x0000000000000000ull, 0}, {0x3fefffffffffffffull, 0xbca71547652b82feull, 0}, + {0x3ff0000000000001ull, 0x3cb71547652b82fdull, 0}, {0x3ff00000fffffffeull, 0x3eb71546ac5324fbull, 0}, + {0x3ff6a09e00000000ull, 0x3fdffffe5dc146d0ull, 0}, {0x4024000000000000ull, 0x400a934f0979a371ull, 0}, + {0x4020000000000000ull, 0x4008000000000000ull, 0}, {0x3fd0000000000000ull, 0xc000000000000000ull, 0}, + {0x7fefffffffffffffull, 0x4090000000000000ull, 0}, {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, + {0xbff0000000000000ull, 0xfff8000000000000ull, EDOM}, {0xfff0000000000000ull, 0xfff8000000000000ull, EDOM}, + {0x7ff8000000000001ull, 0x7ff8000000000001ull, 0}, {0xfff8000000000002ull, 0xfff8000000000002ull, EDOM}, + {0x3f3738a98e70dc5cull, 0xc026ecdca3e646fcull, 0}, {0x3ff6e68ed8bdd52full, 0x3fe08dd74b2b1990ull, 0}, + {0x3f2b147b0942849aull, 0xc0287b4fe8effe10ull, 0}, {0x3fe318aee27b543aull, 0xbfe7d5278168f30eull, 0}, + {0x3fbdbeed4cb253bcull, 0xc008d7d45ccba59eull, 0}, {0x3fece143fc9534f6ull, 0xbfc2f1cb5ae7bbf1ull, 0}, + }; + for (const auto& item : cases) { + errno = 1234; + Require(std::bit_cast(log2_nid_postfix(std::bit_cast(item.input))) == item.expected); + Require(errno == (item.error != 0 ? item.error : 1234)); + } +} + + 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}, @@ -2104,6 +2270,68 @@ static void CheckIntegerAbsAndDivision() { Require(large.quot == -0x12345678ll && large.rem == 0x9abll); } +static void CheckFrexpEnvironment() { + constexpr struct { + std::uint64_t input; + std::uint64_t fraction; + int exponent; + } cases[] = { + {0x0000000000000000ull, 0x0000000000000000ull, 0}, + {0x8000000000000000ull, 0x8000000000000000ull, 0}, + {0x0000000000000001ull, 0x3fe0000000000000ull, -1073}, + {0x8000000000000001ull, 0xbfe0000000000000ull, -1073}, + {0x000fffffffffffffull, 0x3feffffffffffffeull, -1022}, + {0x800fffffffffffffull, 0xbfeffffffffffffeull, -1022}, + {0x0010000000000000ull, 0x3fe0000000000000ull, -1021}, + {0x8010000000000000ull, 0xbfe0000000000000ull, -1021}, + {0x3fe0000000000000ull, 0x3fe0000000000000ull, 0}, + {0x3ff0000000000000ull, 0x3fe0000000000000ull, 1}, + {0xbff0000000000000ull, 0xbfe0000000000000ull, 1}, + {0x4028000000000000ull, 0x3fe8000000000000ull, 4}, + {0x7fefffffffffffffull, 0x3fefffffffffffffull, 1024}, + {0xffefffffffffffffull, 0xbfefffffffffffffull, 1024}, + {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0}, + {0xfff0000000000000ull, 0xfff0000000000000ull, 0}, + {0x7ff0000000000001ull, 0x7ff0000000000001ull, 0}, + {0xfff0000000000001ull, 0xfff0000000000001ull, 0}, + {0x7ff8000000001234ull, 0x7ff8000000001234ull, 0}, + {0xfff8000000001234ull, 0xfff8000000001234ull, 0}, + }; + std::fenv_t saved; + Require(std::fegetenv(&saved) == 0); + const unsigned savedCsr = _mm_getcsr(); + constexpr int rounding[] = {FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO}; + const int savedError = *__error_nid_postfix(); + for (unsigned mode = 0; mode != 4; ++mode) { + for (unsigned denorm = 0; denorm != 4; ++denorm) { + const unsigned control = 0x1f80u | (mode << 13) | ((denorm & 1u) ? 0x40u : 0u) | ((denorm & 2u) ? 0x8000u : 0u); + for (const unsigned sticky : {0u, 5u, 32u}) { + for (const auto& item : cases) { + Require(std::fesetenv(FE_DFL_ENV) == 0); + Require(std::fesetround(rounding[mode]) == 0); + *__error_nid_postfix() = 97; + _mm_setcsr(control | sticky); + int exponent = 77; + const auto fraction = std::bit_cast(frexp_nid_postfix(std::bit_cast(item.input), &exponent)); + const unsigned after = _mm_getcsr(); + const bool subnormal = (item.input & 0x7fffffffffffffffull) != 0 && (item.input & 0x7fffffffffffffffull) < 0x0010000000000000ull; + const bool flushed = subnormal && (denorm & 1u) != 0; + const unsigned flags = sticky | (subnormal && !flushed ? 2u : 0u); + const auto expectedFraction = flushed ? (item.input & 0x8000000000000000ull) | 0x3fe0000000000000ull : item.fraction; + const int expectedExponent = flushed ? -1076 : item.exponent; + if (fraction != expectedFraction || exponent != expectedExponent || after != (control | flags) || *__error_nid_postfix() != 97) { + std::fprintf(stderr, "frexp input=%016llx mode=%u denorm=%u sticky=%u fraction=%016llx exponent=%d csr=%x\n", static_cast(item.input), mode, denorm, sticky, static_cast(fraction), exponent, after); + std::abort(); + } + } + } + } + } + Require(std::fesetenv(&saved) == 0); + _mm_setcsr(savedCsr); + *__error_nid_postfix() = savedError; +} + static void CheckScaling() { Require(scalbnl_nid_postfix(3.0L, 4) == 48.0L && scalbnl_nid_postfix(1.0L, -16445) == 0x1p-16445L); Require(scalbnl_nid_postfix(1.0L, 16384) == std::numeric_limits::infinity()); @@ -2466,6 +2694,44 @@ static void CheckExpm1fConsoleBits() { } } +static void CheckAtanConsoleBits() { + constexpr std::uint64_t atanCases[][2] = { + {0x0000000000000000ull, 0x0000000000000000ull}, {0x8000000000000000ull, 0x8000000000000000ull}, + {0x3e3fffffffffffffull, 0x3e3fffffffffffffull}, {0x3fdbffffffffffffull, 0x3fda64eec3cc23fcull}, + {0x3fdc000000000000ull, 0x3fda64eec3cc23fdull}, {0x3fe6000000000000ull, 0x3fe345f01cce37bbull}, + {0xbff0000000000000ull, 0xbfe921fb54442d18ull}, {0x3ff3000000000000ull, 0x3febde70ed439fe7ull}, + {0x4003800000000000ull, 0x3ff2e75728833a54ull}, {0xc010000000000000ull, 0xbff5368c951e9cfdull}, + {0x4410000000000000ull, 0x3ff921fb54442d18ull}, {0x7ff0000000000000ull, 0x3ff921fb54442d18ull}, + {0xfff0000000000000ull, 0xbff921fb54442d18ull}, {0x3fad508ceb7f682cull, 0x3fad485dbf410edfull}, + {0x3fa082f78cc13f61ull, 0x3fa08180a68db531ull}, {0x3faeddbbe09a8750ull, 0x3faed42ea4a74ad3ull}, + {0x3fac3936c3cca451ull, 0x3fac31e8af531a27ull}, + }; + for (const auto& [input, expected] : atanCases) { + errno = 1234; + Require(std::bit_cast(atan_nid_postfix(std::bit_cast(input))) == expected && errno == 1234); + } + constexpr std::uint64_t atan2Cases[][3] = { + {0x0000000000000000ull, 0xbff0000000000000ull, 0x400921fb54442d18ull}, {0x8000000000000000ull, 0xbff0000000000000ull, 0xc00921fb54442d18ull}, + {0x3ff0000000000000ull, 0x0000000000000000ull, 0x3ff921fb54442d18ull}, {0x7ff0000000000000ull, 0x7ff0000000000000ull, 0x3fe921fb54442d18ull}, + {0xfff0000000000000ull, 0xfff0000000000000ull, 0xc002d97c7f3321d2ull}, {0x3ff0000000000000ull, 0xfff0000000000000ull, 0x400921fb54442d18ull}, + {0xbff0000000000000ull, 0x7ff0000000000000ull, 0x8000000000000000ull}, {0x4400000000000000ull, 0x3ff8000000000000ull, 0x3ff921fb54442d18ull}, + {0x3c00000000000000ull, 0xc000000000000000ull, 0x400921fb54442d18ull}, {0x4008000000000000ull, 0x3ff0000000000000ull, 0x3ff3fc176b7a8560ull}, + {0xc000000000000000ull, 0xc008000000000000ull, 0xc0046dc09ec29433ull}, {0x3fe774163fc8a61cull, 0xbfa03b1919b0841eull, 0x3ff9d308efab3d4eull}, + {0x3f89168e4bb7028dull, 0xbf770218cd6ae2acull, 0x40000181c1e22d3cull}, {0x4001cb803efd1d84ull, 0x403006a063c24f8eull, 0x3fc1a744f5606d17ull}, + {0x40482af634e7d295ull, 0xbfc02d27b8b994d8ull, 0x3ff92cb0ec26f870ull}, {0xbf7313a5e3168694ull, 0x406689afeebd4e09ull, 0xbefb15f897fdbebbull}, + {0xbfc6d4284a7ef189ull, 0xbfa287cd540b2831ull, 0xbffc560b5133f96cull}, + {0x7ff0000000000001ull, 0x7ff8000000000456ull, 0x7ff8000000000001ull}, {0x7ff8000000000456ull, 0xfff4000000000123ull, 0x7ff8000000000456ull}, + {0x7ff8000000000123ull, 0xfff8000000000456ull, 0x7ff8000000000123ull}, {0xfff8000000000456ull, 0x7ff8000000000123ull, 0xfff8000000000456ull}, + {0x3ff0000000000000ull, 0x7ff0000000000001ull, 0x7ff8000000000001ull}, {0xfff4000000000123ull, 0x4000000000000000ull, 0xfffc000000000123ull}, + }; + for (const auto& [y, x, expected] : atan2Cases) { + errno = 1234; + Require(std::bit_cast(atan2_nid_postfix(std::bit_cast(y), std::bit_cast(x))) == expected && errno == 1234); + } + Require(std::isnan(atan_nid_postfix(std::numeric_limits::quiet_NaN()))); + Require(std::isnan(atan2_nid_postfix(std::numeric_limits::quiet_NaN(), 1.0)) && std::isnan(atan2_nid_postfix(1.0, std::numeric_limits::quiet_NaN()))); +} + int main() { CheckFloatClassification(); CheckDinkumwareConstants(); @@ -2638,11 +2904,15 @@ int main() { CheckExp2Errno(); CheckExactOperations(); CheckNearbyint(); + CheckNearbyintEnvironment(); CheckFegetenv(); CheckLongDouble(); CheckScalbnErrno(); + CheckAtanConsoleBits(); CheckNearbyintl(); CheckCbrtConsoleBits(); + CheckLog10ConsoleBits(); + CheckLog10EdgeFlags(); CheckLogbConsoleBits(); CheckExpm1ConsoleBits(); CheckLog1pConsoleBits(); @@ -2658,6 +2928,11 @@ int main() { CheckNextafterEnvironment(nextafterf_nid_postfix, 0x80000000u, 0x3f800000u, 0x00800000u, 0x7f7fffffu, 0x7f800000u); CheckNextafterEnvironment(nextafter_nid_postfix, std::uint64_t{0x8000000000000000ull}, std::uint64_t{0x3ff0000000000000ull}, std::uint64_t{0x0010000000000000ull}, std::uint64_t{0x7fefffffffffffffull}, std::uint64_t{0x7ff0000000000000ull}); CheckExpm1ScalingRounding(); + CheckExpConsoleBits(); + CheckLog10ConsoleBits(); + CheckLog10EdgeFlags(); + CheckLog2ConsoleBits(); + CheckLog2EdgeFlags(); CheckAsinl(); CheckTanhfConsoleBits(); CheckExpm1fConsoleBits(); @@ -2802,6 +3077,7 @@ int main() { CheckFmodfErrno(); CheckDoubleClassification(); CheckIntegerAbsAndDivision(); + CheckFrexpEnvironment(); CheckScaling(); CheckLongDoubleFunctions(); CheckLongDoubleHyperbolic(); diff --git a/core/libs/tests/GuestMathExact.cpp b/core/libs/tests/GuestMathExact.cpp index c1075d499e..3144de5fec 100644 --- a/core/libs/tests/GuestMathExact.cpp +++ b/core/libs/tests/GuestMathExact.cpp @@ -9,6 +9,7 @@ #include #include #include +#include static void Require(bool value, std::source_location location = std::source_location::current()) { if (value) return; @@ -153,6 +154,14 @@ static void CheckRoundingModes() { Require(rintf_nid_postfix(-1.5f) == static_cast(test.negative) && rintf_nid_postfix(1.5f) == static_cast(test.positive)); Require(rintl_nid_postfix(-1.5L) == test.negative && rintl_nid_postfix(1.5L) == test.positive); Require(lrint_nid_postfix(-1.5) == static_cast(test.negative) && llrint_nid_postfix(1.5) == static_cast(test.positive)); + for (const double small : {0.0, 0.1, 0.5, 0x1.8p-1030}) { + Require(floor_nid_postfix(small) == 0.0 && !std::signbit(floor_nid_postfix(small))); + Require(floorf_nid_postfix(static_cast(small)) == 0.0f && !std::signbit(floorf_nid_postfix(static_cast(small)))); + } + for (const float small : {std::numeric_limits::denorm_min(), 0x1p-130f, std::numeric_limits::min()}) + Require(floorf_nid_postfix(small) == 0.0f && !std::signbit(floorf_nid_postfix(small))); + Require(floor_nid_postfix(-0.5) == -1.0 && floorf_nid_postfix(-0.5f) == -1.0f); + Require(std::signbit(floor_nid_postfix(-0.0)) && std::signbit(floorf_nid_postfix(-0.0f))); Require(std::fegetround() == test.mode); } Require(std::fesetround(saved) == 0); @@ -190,6 +199,26 @@ static void CheckClassification() { Require(_LDsign_nid_postfix(-2.0L) == -32768 && _LDsign_nid_postfix(2.0L) == 0); } +static void CheckRoundHalfwayAwayFromZero() { + const double doubleCases[][2] = {{0.5, 1.0}, {-0.5, -1.0}, {1.5, 2.0}, {2.5, 3.0}, {-1.5, -2.0}, {-2.5, -3.0}, {0.4, 0.0}, {-0.6, -1.0}, {4503599627370495.5, 4503599627370496.0}, {-4503599627370495.5, -4503599627370496.0}}; + for (const auto& item : doubleCases) Require(round_nid_postfix(item[0]) == item[1]); + Require(round_nid_postfix(0.49999999999999994) == 0.0 && !std::signbit(round_nid_postfix(0.49999999999999994))); + Require(std::signbit(round_nid_postfix(-0.4)) && round_nid_postfix(-0.4) == 0.0); + Require(!std::signbit(round_nid_postfix(0.0)) && std::signbit(round_nid_postfix(-0.0))); + Require(round_nid_postfix(INFINITY) == INFINITY && round_nid_postfix(-INFINITY) == -INFINITY); + Require(std::isnan(round_nid_postfix(NAN))); + Require(round_nid_postfix(4503599627370497.0) == 4503599627370497.0 && round_nid_postfix(1e300) == 1e300); + + const float floatCases[][2] = {{0.5f, 1.0f}, {-0.5f, -1.0f}, {1.5f, 2.0f}, {2.5f, 3.0f}, {-1.5f, -2.0f}, {-2.5f, -3.0f}, {0.4f, 0.0f}, {-0.6f, -1.0f}, {8388607.5f, 8388608.0f}, {-8388607.5f, -8388608.0f}}; + for (const auto& item : floatCases) Require(roundf_nid_postfix(item[0]) == item[1]); + Require(roundf_nid_postfix(0.49999997f) == 0.0f && !std::signbit(roundf_nid_postfix(0.49999997f))); + Require(std::signbit(roundf_nid_postfix(-0.4f)) && roundf_nid_postfix(-0.4f) == 0.0f); + Require(!std::signbit(roundf_nid_postfix(0.0f)) && std::signbit(roundf_nid_postfix(-0.0f))); + Require(roundf_nid_postfix(INFINITY) == INFINITY && roundf_nid_postfix(-INFINITY) == -INFINITY); + Require(std::isnan(roundf_nid_postfix(NAN))); + Require(roundf_nid_postfix(8388609.0f) == 8388609.0f && roundf_nid_postfix(1e38f) == 1e38f); +} + int main() { CheckRoundingAndScaling(); CheckLogblLroundl(); @@ -200,5 +229,6 @@ int main() { CheckRoundingModes(); CheckRintTiesToEven(); CheckClassification(); + CheckRoundHalfwayAwayFromZero(); return 0; } diff --git a/core/libs/tests/GuestMbus.cpp b/core/libs/tests/GuestMbus.cpp index 1324294c49..8288fd5dc9 100644 --- a/core/libs/tests/GuestMbus.cpp +++ b/core/libs/tests/GuestMbus.cpp @@ -20,6 +20,7 @@ void Require(bool condition, const char* message) { } void CheckDeviceIdConversion() { + constexpr auto unconvertibleClass = static_cast(0x809B0008u); std::uint32_t local = 7; std::uint32_t deviceClass = 7; Require(sceMbusConvertToLocalDeviceId(0x0020123456789ABCull, &local, &deviceClass) == 0, "local conversion failed"); @@ -40,7 +41,6 @@ void CheckDeviceIdConversion() { Require(sceMbusConvertToMbusDeviceId(0x123456789ABCull, 0x20, &id) == 0 && id == 0x0020123456789ABCull, "class 0x20 is placed in the top 16 bits"); Require(sceMbusConvertToMbusDeviceId(0xFFFF000000000001ull, 0x10, &id) == 0 && id == 0xFFFF000000000001ull, "the local id is not masked"); Require(sceMbusConvertToMbusDeviceId(1, 0x10, nullptr) == 0, "conversion without an output"); - constexpr auto unconvertibleClass = static_cast(0x809B0008u); for (const std::uint32_t other : {0u, 1u, 0x11u, 0x21u, 0x30u, 0x1000u, 0xFFFFFFFFu}) { id = 7; Require(sceMbusConvertToMbusDeviceId(1, other, &id) == unconvertibleClass && id == 0, "other classes are refused and clear the output"); @@ -52,6 +52,7 @@ void CheckDeviceIdConversion() { int main() { static_assert(sizeof(MbusEventCallbacks) == 16); + sceMbusEventCallbackFuncsInit_(nullptr, sizeof(MbusEventCallbacks)); alignas(MbusEventCallbacks) std::array callbackBytes{}; callbackBytes.fill(0xa5); sceMbusEventCallbackFuncsInit_(reinterpret_cast(callbackBytes.data()), 8); diff --git a/core/libs/tests/GuestMemcmpProtected.cpp b/core/libs/tests/GuestMemcmpProtected.cpp new file mode 100644 index 0000000000..e1046d85d6 --- /dev/null +++ b/core/libs/tests/GuestMemcmpProtected.cpp @@ -0,0 +1,96 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestMemcmpProtected check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + unsigned char* At(std::size_t count) { + Require(count <= page - 32); + return allocation + page * 2 - (std::max)(count, std::size_t{1}); + } +}; + +static int Sign(int value) { return (value > 0) - (value < 0); } + +int main() { + Buffer left, right; + auto* a = left.At(1); + auto* b = right.At(1); + for (unsigned x = 0; x < 256; ++x) { + for (unsigned y = 0; y < 256; ++y) { + *a = static_cast(x); + *b = static_cast(y); + Require(Sign(memcmp_nid_postfix(a, b, 1)) == Sign(static_cast(x) - static_cast(y))); + Require(*a == x && *b == y); + } + } + for (std::size_t length = 0; length <= 128; ++length) { + a = left.At(length); + b = right.At(length); + const auto extent = (std::max)(length, std::size_t{1}); + std::memset(a, 0x80, extent); + std::memset(b, 0x80, extent); + Require(memcmp_nid_postfix(a, b, length) == 0); + for (std::size_t position = 0; position < length; ++position) { + a[position] = 0xff; + b[position] = 0x7f; + if (position + 1 < length) { a[position + 1] = 0; b[position + 1] = 0xff; } + Require(memcmp_nid_postfix(a, b, length) > 0); + Require(memcmp_nid_postfix(b, a, length) < 0); + Require(memcmp_nid_postfix(a, b, position) == 0); + std::memset(a, 0x80, extent); + std::memset(b, 0x80, extent); + } + } +} diff --git a/core/libs/tests/GuestMemcpyProtected.cpp b/core/libs/tests/GuestMemcpyProtected.cpp new file mode 100644 index 0000000000..15789cc3cc --- /dev/null +++ b/core/libs/tests/GuestMemcpyProtected.cpp @@ -0,0 +1,79 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestMemcpyProtected check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + unsigned char* At(std::size_t count) { + Require(count <= page - 32); + return allocation + page * 2 - (std::max)(count, std::size_t{1}); + } +}; + +int main() { + Buffer input, output; + for (std::size_t length = 0; length <= 256; ++length) { + auto* source = input.At(length); + auto* destination = output.At(length); + const auto extent = (std::max)(length, std::size_t{1}); + for (std::size_t i = 0; i < extent; ++i) source[i] = static_cast(i * 131 + length); + std::vector before(source, source + extent); + std::memset(destination - 16, 0xa5, extent + 16); + Require(memcpy_nid_postfix(destination, source, length) == destination); + for (std::size_t i = 0; i < length; ++i) Require(destination[i] == before[i]); + if (length == 0) Require(destination[0] == 0xa5); + Require(std::equal(before.begin(), before.end(), source)); + for (int i = 1; i <= 16; ++i) Require(destination[-i] == 0xa5); + } +} diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 49c01cebc1..c399ffd493 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -38,6 +38,8 @@ #include #include #include +#elif defined(__APPLE__) +#include #endif #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -167,15 +169,6 @@ static void CheckPageTableStats() { } static void CheckInternalMemoryAvailableSize() { - std::size_t available = 1; - bool threw = false; - try { - sceKernelInternalMemoryGetAvailableSize(&available); - - } catch (const std::logic_error&) { - threw = true; - } - Require(threw && available == 1); Require(sceKernelInternalMemoryGetAvailableSize(nullptr) == -1); } @@ -425,6 +418,7 @@ static void CheckMunmapScopedFlexibleRelease() { Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); } +#ifndef __APPLE__ static void CheckGuestModuleUsesFlexibleMemory() { constexpr std::size_t moduleData = 0x10000; const auto path = std::filesystem::relative(GUEST_MEMORY_MODULE).generic_string(); @@ -439,6 +433,7 @@ static void CheckGuestModuleUsesFlexibleMemory() { Require(dlclose_nid_postfix(module) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); } +#endif static void CheckBatchMapStopsAtInvalidEntry() { constexpr std::size_t page = 0x4000; @@ -626,6 +621,41 @@ static void CheckDirectMemoryFollowsPhysicalPages() { Require(sceKernelReleaseDirectMemory(again, page * 2) == 0); } +static void CheckDirectMemoryBecomesExecutable() { + constexpr std::size_t page = 0x4000; + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); + void* mapped = nullptr; + Require(sceKernelMapDirectMemory(&mapped, page, 3, 0, phys, 0) == 0); + constexpr std::array code{0xb8, 0x2a, 0x00, 0x00, 0x00, 0xc3}; + std::memcpy(mapped, code.data(), code.size()); + Require(sceKernelMprotect(mapped, page, 5) == 0); + Require(reinterpret_cast(mapped)() == 42); + Require(sceKernelMprotect(mapped, page, 7) == 0); + static_cast(mapped)[1] = 0x2b; + Require(reinterpret_cast(mapped)() == 43); + Require(sceKernelMunmap(reinterpret_cast(mapped), page) == 0); + Require(sceKernelReleaseDirectMemory(phys, page) == 0); +} + +#ifndef _WIN32 +static void CheckDirectMemoryReleasesHostDescriptors() { + constexpr std::size_t page = 0x4000; + const int before = dup(0); + Require(before >= 0 && close(before) == 0); + for (int round = 0; round < 64; ++round) { + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); + void* mapped = nullptr; + Require(sceKernelMapDirectMemory(&mapped, page, 3, 0, phys, 0) == 0); + Require(sceKernelMunmap(reinterpret_cast(mapped), page) == 0); + Require(sceKernelReleaseDirectMemory(phys, page) == 0); + } + const int after = dup(0); + Require(after == before && close(after) == 0); +} +#endif + static void CheckSparseDirectMemoryBacking() { constexpr std::size_t page = 0x4000; constexpr std::size_t size = 256u * 1024u * 1024u; @@ -2424,6 +2454,41 @@ static void CheckSecondDirectMappingWaitsForLeases() { #endif } +static GuestAllocations::Lease pinHolder; + +static bool PinWaiterTakesANewLease(std::uintptr_t, std::size_t) { + pinHolder = GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); + return true; +} + +static void CheckPinnedRangeIsLeftOutOfNewLeases() { + constexpr std::size_t page = 0x4000; + const auto failed = reinterpret_cast(static_cast(-1)); + void* raw = mmap_nid_postfix(nullptr, page * 2, 3, 0x1002, -1, 0); + Require(raw != failed); + const auto holds = [&] { + return std::count_if(pinHolder.begin(), pinHolder.end(), [&](const auto& range) { return range->address == reinterpret_cast(raw); }) == 1; + }; + pinHolder = GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); + Require(holds()); + GuestAllocations::GuestAllocationsSetPinWaiter_nid_postfix(PinWaiterTakesANewLease); + bool unmapped = false; + try { + unmapped = munmap_nid_postfix(raw, page * 2) == 0; + } catch (const std::exception&) { + } + GuestAllocations::GuestAllocationsSetPinWaiter_nid_postfix(nullptr); + Require(unmapped); + Require(!holds()); + pinHolder.clear(); + void* again = mmap_nid_postfix(nullptr, page, 3, 0x1002, -1, 0); + Require(again != failed); + auto lease = GuestAllocations::GuestAllocationsAcquireAll_nid_postfix(); + Require(std::count_if(lease.begin(), lease.end(), [&](const auto& range) { return range->address == reinterpret_cast(again); }) == 1); + lease.clear(); + Require(munmap_nid_postfix(again, page) == 0); +} + #ifdef _WIN32 static void RequireResidentMapping(const void* address) { MEMORY_BASIC_INFORMATION info{}; @@ -3088,6 +3153,46 @@ static void CheckDirectMemoryBackingNeedsNoFilesystem() { Require(sceKernelReleaseDirectMemory(phys, page) == 0); } +static void CheckDirectMemorySharedBackingRuns() { + constexpr std::size_t page = 0x4000; + std::int64_t physical = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 4, 0, 0, &physical) == 0); + void* reserved = nullptr; + Require(sceKernelReserveVirtualRange(&reserved, page * 4, 0, 0) == 0 && reserved != nullptr); + auto* bytes = static_cast(reserved); + constexpr int fixed = 0x10; + void* piece = bytes; + Require(sceKernelMapDirectMemory(&piece, page * 2, 3, fixed, physical + page * 2, 0) == 0 && piece == bytes); + piece = bytes + page * 2; + Require(sceKernelMapDirectMemory(&piece, page, 3, fixed, physical, 0) == 0 && piece == bytes + page * 2); + piece = bytes + page * 3; + Require(sceKernelMapDirectMemory(&piece, page, 3, fixed, physical + page, 0) == 0 && piece == bytes + page * 3); + bytes[page * 2 + 7] = 0x6b; + const auto address = reinterpret_cast(reserved); + const auto offset = static_cast(physical); + int file = -1; + GuestArena::SharedBackingRun runs[4]{}; + std::size_t count = 0; + Require(GuestArena::GuestArenaSharedBackingRuns_nid_postfix(address, page * 4, &file, runs, 4, &count)); + Require(count == 2 && runs[0].offset == offset + page * 2 && runs[0].bytes == page * 2); + Require(runs[1].offset == offset && runs[1].bytes == page * 2); + unsigned char byte = 0; + Require(pread(file, &byte, 1, static_cast(runs[1].offset + 7)) == 1 && byte == 0x6b); + close(file); + Require(GuestArena::GuestArenaSharedBackingRuns_nid_postfix(address + page, page * 3, &file, runs, 4, &count)); + Require(count == 2 && runs[0].offset == offset + page * 3 && runs[0].bytes == page); + Require(runs[1].offset == offset && runs[1].bytes == page * 2); + close(file); + file = -1; + Require(!GuestArena::GuestArenaSharedBackingRuns_nid_postfix(address, page * 4, &file, runs, 1, &count)); + Require(file == -1 && count == 0); + Require(!GuestArena::GuestArenaSharedBackingRuns_nid_postfix(address, page * 5, &file, runs, 4, &count)); + Require(!GuestArena::GuestArenaSharedBackingRuns_nid_postfix(address, 0, &file, runs, 4, &count)); + Require(sceKernelMunmap(reinterpret_cast(reserved), page * 4) == 0); + Require(sceKernelReleaseDirectMemory(physical, page * 4) == 0); +} + + static void CheckDirectMemorySharedBacking() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; @@ -3302,6 +3407,41 @@ static void CheckMappingCostFollowsTheMappingCount() { } #endif +static void CheckOverlapQueriesSeeRangesStartingBelowTheAddress() { + constexpr std::size_t page = 0x4000; + constexpr std::size_t many = 4096; + constexpr std::uintptr_t first = 0x7f0000000000ull; + for (std::size_t index = 0; index < many; ++index) { + GuestAllocations::Mutation mutation; + mutation.Add(reinterpret_cast(first + page * 2 * index), page, true, true, false); + } + const auto last = first + page * 2 * (many - 1); + const auto inside = reinterpret_cast(last + page / 2); + const auto atBase = reinterpret_cast(last); + const auto before = reinterpret_cast(last - page / 2); + Require(GuestAllocations::GuestAllocationsOverlaps_nid_postfix(nullptr, inside, 1)); + Require(GuestAllocations::GuestAllocationsOverlaps_nid_postfix(nullptr, atBase, 1)); + Require(!GuestAllocations::GuestAllocationsOverlaps_nid_postfix(nullptr, before, 1)); + bool refused = false; + try { + GuestAllocations::GuestAllocationsRequireAvailable_nid_postfix(nullptr, inside, 1); + } catch (const std::exception&) { + refused = true; + } + Require(refused); + refused = false; + try { + GuestAllocations::GuestAllocationsRequireAvailable_nid_postfix(nullptr, before, 1); + } catch (const std::exception&) { + refused = true; + } + Require(!refused); + for (std::size_t index = 0; index < many; ++index) { + GuestAllocations::Mutation mutation; + mutation.Remove(reinterpret_cast(first + page * 2 * index)); + } +} + #if defined(__linux__) || defined(_WIN32) static void CheckRegularFileMapping() { constexpr std::size_t page = 0x4000; @@ -4001,6 +4141,8 @@ static void CheckArenaFirstFitAgainstPageModel() { int main(int argc, char** argv) { #ifdef _WIN32 _putenv_s("APS5_PIN_WAIT_MS", "1000"); +#else + setenv("APS5_PIN_WAIT_MS", "1000", 1); #endif if (argc == 2 && std::strcmp(argv[1], "fixed-anonymous") == 0) { CheckPosixFixedAnonymousMapping(); @@ -4022,6 +4164,7 @@ int main(int argc, char** argv) { CheckArenaFirstFitAfterPartialRelease(); CheckArenaFirstFitAgainstPageModel(); CheckAvailableDirectMemoryIsLargestFreeBlock(); + CheckOverlapQueriesSeeRangesStartingBelowTheAddress(); CheckMappedLeasesPreserveReadableRuns(); CheckVirtualQuerySplitFlexibleRanges(); CheckVirtualQueryForNonReadableGuestRange(); @@ -4040,7 +4183,9 @@ int main(int argc, char** argv) { CheckInternalNamedFlexibleMapping(); CheckFlexibleFixedReplacement(); CheckMunmapScopedFlexibleRelease(); +#ifndef __APPLE__ CheckGuestModuleUsesFlexibleMemory(); +#endif CheckBatchMapStopsAtInvalidEntry(); CheckBatchMapPlacesAtTheGivenStart(); CheckCheckedReleaseDirectMemory(); @@ -4051,6 +4196,10 @@ int main(int argc, char** argv) { CheckDirectMapRefusesExecute(); CheckPosixMprotectIgnoresUndefinedBits(); CheckDirectMemoryFollowsPhysicalPages(); + CheckDirectMemoryBecomesExecutable(); +#ifndef _WIN32 + CheckDirectMemoryReleasesHostDescriptors(); +#endif CheckSparseDirectMemoryBacking(); CheckSavedDirectMemorySurvivesCachePressure(); CheckReusedDirectMemorySections(); @@ -4114,6 +4263,7 @@ int main(int argc, char** argv) { CheckReleaseKeepsPhysicalUntilViewsUnmapped(sceKernelReleaseDirectMemory); CheckReleaseKeepsPhysicalUntilViewsUnmapped(sceKernelCheckedReleaseDirectMemory); CheckConcurrentCheckedReleasesUnmapViewsOnce(); + CheckPinnedRangeIsLeftOutOfNewLeases(); CheckBreakingAResidentRunKeepsConcurrentAccesses(); CheckRemappedDirectMemoryIsChargedOnce(); CheckFixedRemapReprotectsHost(); @@ -4126,6 +4276,7 @@ int main(int argc, char** argv) { CheckDirectMemoryWriteWatchFollowsRemovedMappings(); CheckMappingCostFollowsTheMappingCount(); CheckDirectMemoryBackingNeedsNoFilesystem(); + CheckDirectMemorySharedBackingRuns(); CheckDirectMemorySharedBacking(); CheckVirtualQueryFindsNeighbours(); CheckReleaseFindsEveryDirectMapping(); @@ -4252,8 +4403,7 @@ int main(int argc, char** argv) { Require(madvise_nid_postfix(memory, page, 4) == 0); Require(mincore_nid_postfix(memory, page, mincoreVec) == -1 && *__error_nid_postfix() == 12); *__error_nid_postfix() = 0; - Require(mprotect_nid_postfix(memory, page, 1) == -1 && *__error_nid_postfix() == 22); - *__error_nid_postfix() = 0; + Require(mprotect_nid_postfix(memory, page, 1) == 0 && *__error_nid_postfix() == 0); Require(mprotect_nid_postfix(memory, std::numeric_limits::max(), 1) == -1 && *__error_nid_postfix() == 22); for (int protection : {0, 1, 3, 5}) { diff --git a/core/libs/tests/GuestMemoryModule.cpp b/core/libs/tests/GuestMemoryModule.cpp index ed8642ed65..e54d70c243 100644 --- a/core/libs/tests/GuestMemoryModule.cpp +++ b/core/libs/tests/GuestMemoryModule.cpp @@ -1 +1,3 @@ -extern "C" unsigned char guestMemoryModuleData[0x10000] = {1}; +extern "C" { +unsigned char guestMemoryModuleData[0x10000] = {1}; +} diff --git a/core/libs/tests/GuestMemsetExtent.cpp b/core/libs/tests/GuestMemsetExtent.cpp new file mode 100644 index 0000000000..681d38eced --- /dev/null +++ b/core/libs/tests/GuestMemsetExtent.cpp @@ -0,0 +1,82 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestMemsetExtent check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +int main() { + Buffer output; + for (std::size_t count = 0; count <= 256; ++count) { + auto* destination = output.At(count); + for (int value : {-1025, -256, -1, 0, 1, 127, 128, 255, 256, 0x1234, (-2147483647 - 1), 2147483647}) { + std::memset(destination - 16, 0xa5, (std::max)(count, std::size_t{1}) + 16); + Require(memset_nid_postfix(destination, value, count) == destination); + for (std::size_t index = 0; index < count; ++index) Require(destination[index] == static_cast(value)); + if (count == 0) Require(destination[0] == 0xa5); + Prefix(destination); + } + } +} diff --git a/core/libs/tests/GuestMessageDestructorAbi.cpp b/core/libs/tests/GuestMessageDestructorAbi.cpp index 90d524adf0..4e796cb3b6 100644 --- a/core/libs/tests/GuestMessageDestructorAbi.cpp +++ b/core/libs/tests/GuestMessageDestructorAbi.cpp @@ -152,7 +152,7 @@ static int TestVtable(const char* mode) { if (!deleting && actual->message) FixtureFail(59); } else { const char* result = ProbeWhat(what, actual, unused); - if (!result || std::strcmp(result, plain ? "std::bad_alloc" : "actual virtual message") || + if (!result || std::strcmp(result, plain ? "bad allocation" : "actual virtual message") || objectFrees || messageFrees) FixtureFail(60); } if (other->vtable != originalOther.vtable || other->message != originalOther.message || diff --git a/core/libs/tests/GuestMktime.cpp b/core/libs/tests/GuestMktime.cpp index 8fd2fbeba0..69e1290f5b 100644 --- a/core/libs/tests/GuestMktime.cpp +++ b/core/libs/tests/GuestMktime.cpp @@ -113,7 +113,7 @@ 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 converters{libc_mktime_nid_postfix, mktime_nid_postfix, timelocal_nid_postfix}; constexpr std::array timers{-86401, -2, -1, 0, 1, 59, 60, 86400}; for (auto convert : converters) { for (const auto timer : timers) { @@ -124,6 +124,47 @@ static void CheckZone(const char* zone) { } } +static void CheckTimelocalDst() { + Require(setenv_nid_postfix("TZ", "EST5EDT", 1) == 0, "Could not set EST5EDT zone"); + tzset_nid_postfix(); + + constexpr std::int64_t summerTimer = 995198400; + GuestTm summer{}; + Require(localtime_s_nid_postfix(&summerTimer, &summer) == &summer, "Local conversion failed"); + Require(summer.tm_isdst == 1, "Summer isdst was not 1 under EST5EDT"); + + GuestTm forMktime = summer; + forMktime.tm_isdst = 0; + const std::int64_t mktimeResult = mktime_nid_postfix(&forMktime); + + GuestTm forTimelocal = summer; + forTimelocal.tm_isdst = 0; + const std::int64_t timelocalResult = timelocal_nid_postfix(&forTimelocal); + + Require(mktimeResult != -1 && timelocalResult != -1, "Conversion failed"); + Require(timelocalResult == summerTimer, "timelocal did not determine DST with tm_isdst=-1"); +#ifndef _WIN32 + Require(mktimeResult == summerTimer + 3600, "mktime did not respect forced standard time tm_isdst=0"); +#endif + Require(forTimelocal.tm_isdst == 1, "timelocal normalized tm_isdst was not 1 in summer DST"); + + GuestTm failure{}; + failure.tm_year = std::numeric_limits::max(); + failure.tm_mon = std::numeric_limits::max(); + failure.tm_mday = 1; + failure.tm_wday = 4; + failure.tm_yday = 12; + failure.tm_isdst = 5; + failure.tm_gmtoff = 123; + failure.tm_zone = "unchanged"; + + GuestTm expectedFailure = failure; + expectedFailure.tm_isdst = -1; + + Require(timelocal_nid_postfix(&failure) == -1, "timelocal accepted unrepresentable date"); + Require(Equal(failure, expectedFailure), "timelocal failure modified fields other than tm_isdst"); +} + int main() { CheckZone("UTC0"); CheckZone("UTC-2"); @@ -133,4 +174,5 @@ int main() { CheckEquivalentFutureCalendars("America/New_York"); CheckEquivalentFutureCalendars("Pacific/Kiritimati"); #endif + CheckTimelocalDst(); } diff --git a/core/libs/tests/GuestMlock.cpp b/core/libs/tests/GuestMlock.cpp index 867a8c211d..00918c7e38 100644 --- a/core/libs/tests/GuestMlock.cpp +++ b/core/libs/tests/GuestMlock.cpp @@ -28,17 +28,6 @@ static void RequireLockAllInvalid(int flags) { Require(mlockall_nid_postfix(flags) == -1 && *__error_nid_postfix() == 22); } -static void RequireLockAllUnsupported(int flags) { - bool rejected = false; - try { - mlockall_nid_postfix(flags); - - } catch (const std::runtime_error&) { - rejected = true; - } - Require(rejected); -} - #if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN) && !defined(ANYPS5_ENABLE_TSAN) #define HAS_LOCKED_KILOBYTES static std::size_t LockedKilobytes() { @@ -183,9 +172,6 @@ int main() { RequireLockAllInvalid(4); RequireLockAllInvalid(1 | 4); RequireLockAllInvalid(-1); - RequireLockAllUnsupported(1); - RequireLockAllUnsupported(2); - RequireLockAllUnsupported(1 | 2); *__error_nid_postfix() = untouched; Require(munlockall_nid_postfix() == 0); diff --git a/core/libs/tests/GuestModuleInfo.cpp b/core/libs/tests/GuestModuleInfo.cpp index fd112c8f69..531b9050c8 100644 --- a/core/libs/tests/GuestModuleInfo.cpp +++ b/core/libs/tests/GuestModuleInfo.cpp @@ -12,6 +12,10 @@ #include #include #include +#ifdef __APPLE__ +#include +#include +#endif static void Require(bool value, const std::source_location& location = std::source_location::current()) { if (!value) { @@ -54,10 +58,15 @@ int main(int argc, char** argv) { Require(executable); #if defined(_WIN32) && defined(__SEH__) Require(info.eh_frame_addr == 0 && info.eh_frame_size == 0); +#elif defined(__APPLE__) + unsigned long frameSize = 0; + const auto* frames = getsectiondata(reinterpret_cast(info.segments[0].address), SEG_TEXT, "__eh_frame", &frameSize); + Require(info.eh_frame_addr == reinterpret_cast(frames) && info.eh_frame_size == frameSize); + Require(info.eh_frame_hdr_addr == 0 && info.eh_frame_hdr_size == 0); #else Require(info.eh_frame_addr != 0 && info.eh_frame_size != 0); #endif -#ifndef _WIN32 +#if !defined(_WIN32) && !defined(__APPLE__) Require(info.eh_frame_hdr_addr != 0 && info.eh_frame_hdr_size != 0); #endif Require(info.init_proc_addr == 0 || info.init_proc_addr >= info.segments[0].address); diff --git a/core/libs/tests/GuestMprotectUnmapped.cpp b/core/libs/tests/GuestMprotectUnmapped.cpp index 326051d6ab..d69d9756c3 100644 --- a/core/libs/tests/GuestMprotectUnmapped.cpp +++ b/core/libs/tests/GuestMprotectUnmapped.cpp @@ -7,11 +7,11 @@ #include #include #include +#include namespace { constexpr std::size_t Page = 0x4000; -constexpr int ErrorInvalid = 22; constexpr int ProtectRead = 1; constexpr int ProtectReadWrite = 3; @@ -63,11 +63,14 @@ int main() { Require(Protect(memory + Page - 8, 16, ProtectReadWrite) == 0, "an unaligned range that reaches into the unmapped page protects the mapped page"); Require(Protect(memory + 100, Page - 200, ProtectReadWrite) == 0, "an unaligned range inside the first page must work"); - Require(Protect(memory + Page, Page, ProtectRead) == SCE_KERNEL_ERROR_EINVAL, "a range that is completely unmapped must return EINVAL"); + Require(Protect(memory + Page, Page, ProtectRead) == 0, "a range that is completely unmapped must succeed"); Require(Protect(memory + Page * 2, Page, ProtectReadWrite) == 0, "protecting the last page alone must work"); - Require(Protect(memory + Page, Page * 2, ProtectRead) == SCE_KERNEL_ERROR_EINVAL, "a range whose start is unmapped must return EINVAL even when later pages are mapped"); - Require(ProtectionAt(memory + Page * 2) == ProtectReadWrite, "a refused range must not change the mapped pages behind its start"); - Require(mprotect_nid_postfix(memory + Page, Page, ProtectRead) == -1 && *__error_nid_postfix() == ErrorInvalid, "mprotect over a range that is completely unmapped must fail with EINVAL (22)"); + Require(Protect(memory + Page, Page * 2, ProtectRead) == 0, "a range whose start is unmapped must succeed for the mapped pages behind it"); + Require(ProtectionAt(memory + Page * 2) == ProtectRead, "the mapped pages behind an unmapped start must be protected"); + Require(ProtectionAt(memory) == ProtectReadWrite, "a range that starts in a gap must not change the page before it"); + Require(mprotect_nid_postfix(memory + Page, Page, ProtectRead) == 0, "mprotect over a range that is completely unmapped must succeed"); + Require(mprotect_nid_postfix(memory + Page, Page * 2, ProtectReadWrite) == 0 && ProtectionAt(memory + Page * 2) == ProtectReadWrite, "mprotect over an unmapped start must protect the mapped pages behind it"); + Require(sceKernelMtypeprotect(memory + Page, Page, 0, ProtectRead) == 0, "sceKernelMtypeprotect over a range that is completely unmapped must succeed"); Require(munmap_nid_postfix(memory, Page) == 0 && munmap_nid_postfix(memory + Page * 2, Page) == 0, "munmap of the remaining pages failed"); @@ -76,5 +79,24 @@ int main() { Require(Protect(tail, Page * 2, ProtectRead) == 0, "a range that ends in an unmapped page must succeed for its mapped part"); Require(ProtectionAt(tail) == ProtectRead, "the mapped part before a trailing gap must be protected"); Require(munmap_nid_postfix(tail, Page) == 0, "munmap of the remaining page failed"); + Require(Protect(tail, Page * 2, ProtectRead) == 0, "a range over pages that were all unmapped must succeed"); + + std::int64_t physical = -1; + Require(sceKernelAllocateDirectMemory(0, std::numeric_limits::max(), Page * 3, 0, 0, &physical) == 0, "direct memory allocation failed"); + void* direct = nullptr; + Require(sceKernelMapDirectMemory(&direct, Page * 3, ProtectReadWrite, 0, physical, 0) == 0, "direct memory map failed"); + auto* directPages = static_cast(direct); + Require(munmap_nid_postfix(directPages, Page) == 0, "munmap of the first direct page failed"); + Require(sceKernelMtypeprotect(directPages, Page * 3, 3, ProtectRead) == 0, "sceKernelMtypeprotect over an unmapped start must succeed for the mapped direct pages"); + Require(ProtectionAt(directPages + Page) == ProtectRead && ProtectionAt(directPages + Page * 2) == ProtectRead, "the mapped direct pages behind an unmapped start must be protected"); + for (int page = 0; page < 3; ++page) { + int type = -1; + std::int64_t start = 0; + std::int64_t end = 0; + Require(sceKernelGetDirectMemoryType(physical + page * static_cast(Page), &type, &start, &end) == 0, "direct memory type query failed"); + Require(type == (page == 0 ? 0 : 3), "only the mapped direct pages must take the new memory type"); + } + Require(munmap_nid_postfix(directPages + Page, Page * 2) == 0, "munmap of the direct pages failed"); + Require(sceKernelReleaseDirectMemory(physical, Page * 3) == 0, "direct memory release failed"); return 0; } diff --git a/core/libs/tests/GuestMsgDialogNative.cpp b/core/libs/tests/GuestMsgDialogNative.cpp index 49cf6cbffb..a50ac8ce88 100644 --- a/core/libs/tests/GuestMsgDialogNative.cpp +++ b/core/libs/tests/GuestMsgDialogNative.cpp @@ -76,6 +76,7 @@ void CancelAndFallbacks() { Require(sceMsgDialogOpen(&system) == 0 && sceMsgDialogGetStatus() == kStatusFinished && Session() != ModalDialogState::Showing); result = MsgDialogResult{}; Require(sceMsgDialogGetResult(&result) == 0 && result.button_id == kButtonIdOk); + Require(sceMsgDialogGetResult(reinterpret_cast(0x10)) == kErrArgNull); ModalDialogSetPresenter_nid_postfix(false); UserMessage focusNo{MsgDialog::ButtonTypeYesNoFocusNo, 0, "Delete?"}; @@ -220,6 +221,7 @@ int main() { Require(sceMsgDialogInitialize() == kErrAlreadyInitialized); Require(sceMsgDialogClose() == kErrNotRunning); Require(sceMsgDialogGetResult(&result) == kErrNotFinished); + Require(sceMsgDialogGetResult(reinterpret_cast(0x10)) == kErrNotFinished); Require(sceMsgDialogGetResult(nullptr) == kErrArgNull); Require(sceMsgDialogOpen(nullptr) == kErrArgNull); Require(sceMsgDialogUpdateStatus() == kStatusInitialized); diff --git a/core/libs/tests/GuestMsync.cpp b/core/libs/tests/GuestMsync.cpp index e5dc64d5ce..3022daf468 100644 --- a/core/libs/tests/GuestMsync.cpp +++ b/core/libs/tests/GuestMsync.cpp @@ -54,6 +54,8 @@ 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(Fails(mapped, std::numeric_limits::max() - (Page - 1) + 1, Sync, Invalid)); + Require(Fails(mapped + 1, std::numeric_limits::max() - (Page - 1), Sync, Invalid)); Require(Fails(mapped, Page, Invalidate, Busy)); Require(Fails(mapped, 3 * Page, Sync | Invalidate, Busy)); Require(Fails(mapped + 1, 16, Invalidate, Busy)); diff --git a/core/libs/tests/GuestNetKernelForwards.cpp b/core/libs/tests/GuestNetKernelForwards.cpp new file mode 100644 index 0000000000..abbb70927b --- /dev/null +++ b/core/libs/tests/GuestNetKernelForwards.cpp @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "prx/libSceNet/libSceNet.hpp" +#include "prx/libkernel/libkernel.hpp" +#ifdef _WIN32 +#include +#else +#include +#endif + +#define Require(condition) RequireAt((condition), __LINE__) + +static void RequireAt(bool condition, int line) { + if (condition) return; + std::fprintf(stderr, "failed at line %d, errno %d\n", line, *__error_nid_postfix()); + std::abort(); +} + +static bool Failed(std::int64_t result, int error) { + const bool matched = result == -1 && *__error_nid_postfix() == error; + *__error_nid_postfix() = 0; + return matched; +} + +static int Open(bool net, int type) { + const int protocol = type == 1 ? 6 : 17; + return net ? sceNetSocket("forward", 2, type, protocol) : socket_nid_postfix(2, type, protocol); +} + +static int Close(bool net, int descriptor) { + return net ? sceNetSocketClose(descriptor) : close_nid_postfix(descriptor); +} + +static char* UnreadablePage() { +#ifdef _WIN32 + return static_cast(VirtualAlloc(nullptr, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); +#else + return static_cast(mmap(nullptr, 4096, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); +#endif +} + +static void CheckDatagram(bool net) { + const int receiver = Open(net, 2); + const int sender = Open(net, 2); + Require(receiver >= 0 && sender >= 0); + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, local.data(), local.size()) == 0); + Require(bind_nid_postfix(sender, local.data(), local.size()) == 0); + std::array receiverAddress{}; + std::array senderAddress{}; + std::uint32_t size = receiverAddress.size(); + Require(getsockname_nid_postfix(receiver, receiverAddress.data(), &size) == 0 && size == receiverAddress.size()); + Require((receiverAddress[2] | receiverAddress[3]) != 0); + size = senderAddress.size(); + Require(getsockname_nid_postfix(sender, senderAddress.data(), &size) == 0 && size == senderAddress.size()); + char received[8]{}; + Require(recvfrom_nid_postfix(receiver, received, 0, 0, nullptr, nullptr) == 0); + const char payload[] = "ping"; + for (const std::uint32_t capacity : {16u, 4u, 0u}) { + Require(sendto_nid_postfix(sender, payload, sizeof(payload), 0, receiverAddress.data(), receiverAddress.size()) == sizeof(payload)); + std::array from{}; + size = capacity; + Require(recvfrom_nid_postfix(receiver, received, sizeof(received), 0, from.data(), &size) == sizeof(payload)); + Require(std::strcmp(received, payload) == 0 && size == capacity); + if (capacity == from.size()) Require(from[2] == senderAddress[2] && from[3] == senderAddress[3]); + } + Require(sendto_nid_postfix(sender, payload, sizeof(payload), 0, receiverAddress.data(), receiverAddress.size()) == sizeof(payload)); + size = 123; + Require(recvfrom_nid_postfix(receiver, received, sizeof(received), 0, nullptr, &size) == sizeof(payload)); + Require(size == 123 && std::strcmp(received, payload) == 0); + Require(Close(net, receiver) == 0 && Close(net, sender) == 0); +} + +static void CheckArguments(bool net) { + const int socket = Open(net, 2); + Require(socket >= 0); + char* const unreadable = UnreadablePage(); + Require(unreadable != nullptr); + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + std::array otherFamily{16, 28}; + std::array longAddress{}; + Require(Failed(bind_nid_postfix(socket, nullptr, 16), 14)); + Require(Failed(bind_nid_postfix(socket, local.data(), 1), 22)); + Require(Failed(bind_nid_postfix(socket, local.data(), 17), 22)); + Require(Failed(bind_nid_postfix(socket, longAddress.data(), 256), 63)); + Require(Failed(bind_nid_postfix(socket, otherFamily.data(), 16), 47)); + Require(bind_nid_postfix(socket, local.data(), 16) == 0); + std::uint32_t size = 16; + Require(Failed(getsockname_nid_postfix(socket, nullptr, &size), 14)); + Require(Failed(getsockname_nid_postfix(socket, local.data(), nullptr), 14)); + Require(Failed(sendto_nid_postfix(socket, nullptr, 1, 0, local.data(), 16), 14)); + Require(Failed(sendto_nid_postfix(socket, "x", 1, 0, local.data(), 1), 22)); + Require(Failed(sendto_nid_postfix(socket, "x", 1, 0, local.data(), 17), 22)); + Require(Failed(sendto_nid_postfix(socket, "x", 1, 0, longAddress.data(), 256), 63)); + Require(Failed(sendto_nid_postfix(socket, "x", 1, 0, otherFamily.data(), 16), 47)); + Require(Failed(sendto_nid_postfix(socket, "x", 1, 0, unreadable, 16), 14)); + Require(Failed(sendto_nid_postfix(socket, "x", 1, 0, nullptr, 16), 22)); + Require(Failed(sendto_nid_postfix(socket, "x", 1, 0x2, local.data(), 16), 45)); + Require(Failed(sendto_nid_postfix(socket, "x", std::uint64_t{INT_MAX} + 1, 0, local.data(), 16), 40)); + char received[8]{}; + Require(Failed(recvfrom_nid_postfix(socket, nullptr, 1, 0, nullptr, nullptr), 14)); + Require(Failed(recvfrom_nid_postfix(socket, received, sizeof(received), 0, local.data(), nullptr), 14)); + Require(Failed(recvfrom_nid_postfix(socket, received, sizeof(received), 0, nullptr, reinterpret_cast(unreadable)), 14)); + Require(Failed(recvfrom_nid_postfix(socket, received, sizeof(received), 0, local.data(), reinterpret_cast(unreadable)), 14)); + Require(Failed(recvfrom_nid_postfix(socket, received, sizeof(received), 0x1, nullptr, nullptr), 45)); + Require(Failed(recvfrom_nid_postfix(socket, received, std::uint64_t{INT_MAX} + 1, 0, nullptr, nullptr), 40)); + Require(Failed(recvfrom_nid_postfix(socket, received, sizeof(received), 0x80, nullptr, nullptr), 35)); + Require(Close(net, socket) == 0); +} + +struct Stream { + int listener; + int client; + int accepted; +}; + +static Stream OpenStream(bool net) { + Stream stream{Open(net, 1), Open(net, 1), -1}; + Require(stream.listener >= 0 && stream.client >= 0); + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(stream.listener, local.data(), local.size()) == 0); + std::uint32_t size = local.size(); + Require(getsockname_nid_postfix(stream.listener, local.data(), &size) == 0); + Require((net ? sceNetListen(stream.listener, 1) : listen_nid_postfix(stream.listener, 1)) == 0); + Require((net ? sceNetConnect(stream.client, local.data(), 16) : connect_nid_postfix(stream.client, local.data(), 16)) == 0); + stream.accepted = net ? sceNetAccept(stream.listener, nullptr, nullptr) : accept_nid_postfix(stream.listener, nullptr, nullptr); + Require(stream.accepted >= 0); + return stream; +} + +static void CheckStreamWaitAll(bool net) { + const int unconnected = Open(net, 1); + char unused = 0; + Require(unconnected >= 0 && Failed(recvfrom_nid_postfix(unconnected, &unused, 0, 0, nullptr, nullptr), 57)); + Require(Close(net, unconnected) == 0); + const auto stream = OpenStream(net); + constexpr int WaitAll = 0x40; + std::thread writer([&] { + Require(sendto_nid_postfix(stream.client, "ab", 2, 0, nullptr, 0) == 2); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(sendto_nid_postfix(stream.client, "cd", 2, 0, nullptr, 0) == 2); + }); + char received[4]{}; + std::array from; + from.fill(0xa5); + std::uint32_t size = from.size(); + Require(recvfrom_nid_postfix(stream.accepted, received, sizeof(received), WaitAll, from.data(), &size) == 4); + writer.join(); + Require(std::memcmp(received, "abcd", 4) == 0 && size == 0 && from[0] == 0xa5); + Require(sendto_nid_postfix(stream.client, "ef", 2, 0, nullptr, 0) == 2); + char peeked[2]{}; + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + Require(recvfrom_nid_postfix(stream.accepted, peeked, 0, 0, nullptr, nullptr) == 0); + const int peek = net ? WaitAll | 0x2 : 0x2; + Require(recvfrom_nid_postfix(stream.accepted, peeked, sizeof(peeked), peek, nullptr, nullptr) == 2); + Require(std::memcmp(peeked, "ef", 2) == 0); + Require(sendto_nid_postfix(stream.client, "g", 1, 0, nullptr, 0) == 1); + Require((net ? sceNetShutdown(stream.client, 1) : shutdown_nid_postfix(stream.client, 1)) == 0); + char rest[8]{}; + Require(recvfrom_nid_postfix(stream.accepted, rest, sizeof(rest), WaitAll, nullptr, nullptr) == 3); + Require(std::memcmp(rest, "efg", 3) == 0); + Require(Close(net, stream.accepted) == 0 && Close(net, stream.client) == 0 && Close(net, stream.listener) == 0); +} + +static void CheckUnknownDescriptor() { + constexpr int unknown = 0x7ffffff0; + std::array local{16, 2, 0, 0, 127, 0, 0, 1}; + std::uint32_t size = local.size(); + char byte = 0; + Require(Failed(bind_nid_postfix(unknown, local.data(), local.size()), 9)); + Require(Failed(getsockname_nid_postfix(unknown, local.data(), &size), 9)); + Require(Failed(sendto_nid_postfix(unknown, &byte, 1, 0, local.data(), local.size()), 9)); + Require(Failed(recvfrom_nid_postfix(unknown, &byte, 1, 0, nullptr, nullptr), 9)); +} + +int main() { + for (const bool net : {false, true}) { + CheckDatagram(net); + CheckArguments(net); + CheckStreamWaitAll(net); + } + CheckUnknownDescriptor(); + return 0; +} diff --git a/core/libs/tests/GuestNetSendmsgFaults.cpp b/core/libs/tests/GuestNetSendmsgFaults.cpp new file mode 100644 index 0000000000..0bd3c4c8be --- /dev/null +++ b/core/libs/tests/GuestNetSendmsgFaults.cpp @@ -0,0 +1,132 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libSceNet/include/Net.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::size_t Page = 0x4000; +constexpr std::int64_t Efault = static_cast(static_cast(0x8041010eu)); + +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(); + } +} + +struct Sockets { + int sender = -1; + int receiver = -1; + std::array destination{16, 2, 0, 0, 127, 0, 0, 1}; +}; + +Sockets Open() { + Sockets sockets; + sockets.receiver = sceNetSocket("sendmsg-faults-receiver", 2, 2, 17); + sockets.sender = sceNetSocket("sendmsg-faults-sender", 2, 2, 17); + Require(sockets.receiver >= 0 && sockets.sender >= 0); + Require(sceNetBind_nid_postfix(sockets.receiver, sockets.destination.data(), sockets.destination.size()) == 0); + std::uint32_t size = sockets.destination.size(); + Require(sceNetGetsockname(sockets.receiver, sockets.destination.data(), &size) == 0 && size == sockets.destination.size()); + return sockets; +} + +void RequireFault(std::int64_t result) { + Require(result == Efault); + Require(*sceNetErrnoLoc() == 14); +} + +void RequireNothingSent(Sockets& sockets) { + const char marker[] = "marker"; + Require(sceNetSendto(sockets.sender, marker, sizeof(marker), 0, sockets.destination.data(), sockets.destination.size()) == sizeof(marker)); + char received[64]{}; + Require(sceNetRecv(sockets.receiver, received, sizeof(received), 0) == sizeof(marker)); + Require(std::memcmp(received, marker, sizeof(marker)) == 0); +} + +void RequireSend(Sockets& sockets, const NetMsghdr* message) { + Require(sceNetSendmsg(sockets.sender, message, 0) == 8); + unsigned char received[64]{}; + Require(sceNetRecv(sockets.receiver, received, sizeof(received), 0) == 8); + for (std::size_t i = 0; i < 8; ++i) Require(received[i] == 0x5a); +} + +} + +int main(int argc, char** argv) { + Require(argc == 2); + const std::string_view mode = argv[1]; + Require(mode == "payload" || mode == "iovec" || mode == "header" || mode == "name"); + Require(sceNetInit_nid_postfix() == 0); + auto sockets = Open(); + + std::int64_t physical = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, Page * 3, 0, 0, &physical) == 0); + void* mapping = nullptr; + Require(sceKernelMapDirectMemory(&mapping, Page * 3, 3, 0, physical, 0) == 0); + auto* bytes = static_cast(mapping); + std::memset(bytes, 0x5a, Page * 3); + auto* first = bytes; + auto* second = bytes + Page; + auto* third = bytes + Page * 2; + + auto* message = reinterpret_cast(first + 0x100); + auto* vector = reinterpret_cast(first + 0x200); + *vector = NetIovec{third, 8}; + *message = NetMsghdr{sockets.destination.data(), sockets.destination.size(), vector, 1, nullptr, 0, 0}; + RequireSend(sockets, message); + + *sceNetErrnoLoc() = 0; + if (mode == "payload") { + *vector = NetIovec{second + Page - 8, 16}; + Require(sceKernelMprotect(third, Page, 0) == 0); + RequireFault(sceNetSendmsg(sockets.sender, message, 0)); + *sceNetErrnoLoc() = 0; + RequireFault(sceNetSendto(sockets.sender, second + Page - 8, 16, 0, sockets.destination.data(), sockets.destination.size())); + Require(sceKernelMprotect(third, Page, 3) == 0); + *vector = NetIovec{third, 8}; + } else if (mode == "iovec") { + auto* crossing = reinterpret_cast(second - sizeof(void*)); + const unsigned char* payload = third; + std::memcpy(crossing, &payload, sizeof(payload)); + const std::uint64_t length = 8; + std::memcpy(second, &length, sizeof(length)); + message->iov = crossing; + Require(sceKernelMprotect(second, Page, 0) == 0); + RequireFault(sceNetSendmsg(sockets.sender, message, 0)); + Require(sceKernelMprotect(second, Page, 3) == 0); + message->iov = vector; + } else if (mode == "header") { + auto* crossing = reinterpret_cast(second - sizeof(void*)); + std::memcpy(crossing, message, sizeof(*message)); + Require(sceKernelMprotect(second, Page, 0) == 0); + RequireFault(sceNetSendmsg(sockets.sender, crossing, 0)); + Require(sceKernelMprotect(second, Page, 3) == 0); + } else { + *vector = NetIovec{first + 0x400, 8}; + auto* name = third; + std::memcpy(name, sockets.destination.data(), sockets.destination.size()); + message->name = name; + Require(sceKernelMprotect(third, Page, 0) == 0); + RequireFault(sceNetSendmsg(sockets.sender, message, 0)); + *sceNetErrnoLoc() = 0; + const char data[8]{}; + RequireFault(sceNetSendto(sockets.sender, data, sizeof(data), 0, name, sockets.destination.size())); + Require(sceKernelMprotect(third, Page, 3) == 0); + message->name = sockets.destination.data(); + } + RequireNothingSent(sockets); + RequireSend(sockets, message); + + Require(sceNetSocketClose(sockets.sender) == 0); + Require(sceNetSocketClose(sockets.receiver) == 0); + std::printf("guest net sendmsg %s fault test passed\n", argv[1]); + return 0; +} diff --git a/core/libs/tests/GuestNpCommon.cpp b/core/libs/tests/GuestNpCommon.cpp index 08313861e1..f007128cf5 100644 --- a/core/libs/tests/GuestNpCommon.cpp +++ b/core/libs/tests/GuestNpCommon.cpp @@ -4,7 +4,9 @@ #include "SceTypes.hpp" #include #include +#include #include +#include static constexpr int INVALID_ARGUMENT = static_cast(0x80550003); static constexpr int INVALID_PLATFORM_TYPE = static_cast(0x80550004); @@ -93,6 +95,68 @@ static void* APS5_VABI returnTicket(void* arg) { return reinterpret_cast(reinterpret_cast(arg) + 1); } +struct Wakeup { + PthreadCond cond = nullptr; + PthreadMutex mutex = nullptr; + bool lightweight = false; + int waiting = 0; + int woken = 0; + bool ready = false; +}; + +static void* APS5_VABI waitUntilReady(void* arg) { + auto* wakeup = static_cast(arg); + sceNpMutexLock(&wakeup->mutex); + ++wakeup->waiting; + int result = 0; + while (!wakeup->ready && result == 0) result = wakeup->lightweight ? sceNpLwCondWait(&wakeup->cond, &wakeup->mutex) : sceNpCondWait(&wakeup->cond, &wakeup->mutex); + if (result == 0) ++wakeup->woken; + sceNpMutexUnlock(&wakeup->mutex); + return nullptr; +} + +enum class Wake { One, All, To }; + +static bool wakeWaiters(bool lightweight, int count, Wake wake) { + Wakeup wakeup; + wakeup.lightweight = lightweight; + bool ok = expect(sceNpMutexInit(&wakeup.mutex, "np-wake", 0) == 0, "the wake mutex was not created"); + ok &= expect((lightweight ? sceNpLwCondInit(&wakeup.cond, "np-lw-wake") : sceNpCondInit(&wakeup.cond, "np-wake", 0)) == 0 && wakeup.cond, "the wake condition was not created"); + if (!ok) return false; + Pthread threads[2] = {}; + for (int index = 0; index < count; ++index) + if (!expect(scePthreadCreate(&threads[index], nullptr, waitUntilReady, &wakeup, "np-waiter") == 0, "a waiter was not created")) std::abort(); + for (;;) { + sceNpMutexLock(&wakeup.mutex); + const bool waiting = wakeup.waiting == count; + if (waiting) break; + sceNpMutexUnlock(&wakeup.mutex); + scePthreadYield(); + } + wakeup.ready = true; + int signalled = 0; + if (wake == Wake::All) signalled = lightweight ? sceNpLwCondSignalAll(&wakeup.cond) : sceNpCondSignalAll(&wakeup.cond); + else if (wake == Wake::To) signalled = lightweight ? sceNpLwCondSignalTo(&wakeup.cond, threads[0]) : sceNpCondSignalTo(&wakeup.cond, threads[0]); + else signalled = lightweight ? sceNpLwCondSignal(&wakeup.cond) : sceNpCondSignal(&wakeup.cond); + sceNpMutexUnlock(&wakeup.mutex); + if (!expect(signalled == 0, "signalling the waiters failed")) std::abort(); + for (int index = 0; index < count; ++index) ok &= expect(scePthreadJoin(threads[index], nullptr) == 0, "a waiter was not joined"); + ok &= expect(wakeup.woken == count, "not every waiter returned from its wait"); + ok &= expect((lightweight ? sceNpLwCondDestroy(&wakeup.cond) : sceNpCondDestroy(&wakeup.cond)) == 0, "the wake condition was not destroyed"); + ok &= expect(sceNpMutexDestroy(&wakeup.mutex) == 0, "the wake mutex was not destroyed"); + return ok; +} + +static int testWake() { + bool ok = true; + for (const bool lightweight : {false, true}) { + ok &= expect(wakeWaiters(lightweight, 1, Wake::One), lightweight ? "sceNpLwCondSignal did not wake its waiter" : "sceNpCondSignal did not wake its waiter"); + ok &= expect(wakeWaiters(lightweight, 2, Wake::All), lightweight ? "sceNpLwCondSignalAll did not wake both waiters" : "sceNpCondSignalAll did not wake both waiters"); + ok &= expect(wakeWaiters(lightweight, 1, Wake::To), lightweight ? "sceNpLwCondSignalTo did not wake its thread" : "sceNpCondSignalTo did not wake its thread"); + } + return ok ? 0 : 1; +} + static int testSync() { PthreadMutex plain = nullptr; PthreadMutex recursive = nullptr; @@ -141,7 +205,7 @@ static int testClock() { std::memset(version, 'x', sizeof(version)); sceNpGetSdkVersion(version); - ok &= expect(std::strcmp(version, "1.00") == 0, "the version is not 1.00"); + ok &= expect(std::memcmp(version, "1.00\0\0\0\0", 8) == 0, "the version is not 1.00 padded with zeros"); ok &= expect(version[8] == 'x', "the version was written past 8 bytes"); return ok ? 0 : 1; } @@ -152,6 +216,7 @@ int main(int argc, char** argv) { 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(); + else if (std::strcmp(argv[1], "wake") == 0) status = testWake(); 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 index 5b2ca6ea88..660ce02fd9 100644 --- a/core/libs/tests/GuestNpCommonRuntime.cpp +++ b/core/libs/tests/GuestNpCommonRuntime.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ constexpr int NpStreamBufferTooSmall = static_cast(0x80559E0D); constexpr int NpTitleIdInvalid = static_cast(0x80559E11); constexpr int NpUtilParseOutOfRange = static_cast(0x8055060A); constexpr int NpTitleIdBufferTooSmall = static_cast(0x80559E0D); +constexpr int NpTitleSecretInvalid = static_cast(0x80559E11); constexpr int NpStreamShort = static_cast(0x80559E10); constexpr int KernelNotPermitted = static_cast(0x80020001); constexpr int KernelNoSuchObject = static_cast(0x80020003); @@ -77,6 +79,12 @@ void TestSemaphoresAndFlags() { 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(sceNpSetEventFlag(flag, 6) == 0 && sceNpClearEventFlag(flag, 5) == 0, "bits are set and then masked"); + Require(sceNpPollEventFlag(flag, 2, EventFlagWaitAnd, &result) == NpEventFlagBusy, "a bit outside the clear mask is cleared"); + Require(sceNpPollEventFlag(flag, 5, EventFlagWaitAnd, &result) == 0 && result == 5, "bits inside the clear mask stay set"); + int waiting = -1; + Require(sceNpCancelEventFlag(flag, 0x10, &waiting) == 0 && waiting == 0, "a cancel with no waiter reports none"); + Require(sceNpPollEventFlag(flag, 0x10, EventFlagWaitAnd, &result) == 0 && result == 0x10, "a cancel sets the pattern it is given"); Require(sceNpDeleteEventFlag(flag) == 0, "the event flag is deleted"); } @@ -286,6 +294,41 @@ void TestObjects() { "a failed allocation returns null"); } +void TestAllocatorEx() { + NpMemoryHeap memoryHeap {}; + Require(sceNpMemoryHeapInit(&memoryHeap, 0x1000, "np-test") == 0, "a memory heap is created"); + NpAllocatorEx allocator {}; + sceNpMemoryHeapGetAllocatorEx(&memoryHeap, 0x1234, &allocator); + + Require(_sceNpAllocatorExConvertAllocator(&allocator) == &allocator.allocator, "the extended allocator converts to its allocator"); + auto* block = static_cast(_sceNpAllocatorExMalloc(&allocator, 16)); + Require(block != nullptr, "the extended allocator hands out memory"); + block[15] = 0x5A; + block = static_cast(_sceNpAllocatorExRealloc(&allocator, block, 128)); + Require(block != nullptr && block[15] == 0x5A, "reallocation keeps the contents"); + _sceNpAllocatorExFree(&allocator, block); + char* copy = _sceNpAllocatorExStrdup(&allocator, "np-common"); + + Require(copy != nullptr && std::strcmp(copy, "np-common") == 0, "a string is duplicated"); + _sceNpAllocatorExFree(&allocator, copy); + copy = _sceNpAllocatorExStrndup(&allocator, "np-common", 2); + Require(copy != nullptr && std::strcmp(copy, "np") == 0, "a duplicated prefix is terminated"); + _sceNpAllocatorExFree(&allocator, copy); + copy = _sceNpAllocatorExStrndup(&allocator, "np", 16); + + Require(copy != nullptr && std::strcmp(copy, "np") == 0, "a short string is duplicated whole"); + _sceNpAllocatorExFree(&allocator, copy); + sceNpMemoryHeapDestroy(&memoryHeap); + AllocatorRecord record {}; + record.fail = true; + NpAllocatorEx failing {{RecordAllocate, nullptr, RecordRelease, &record}, 0}; + + Require(_sceNpAllocatorExStrdup(&failing, "abc") == nullptr && record.size == 4, "a failed duplicate returns null after asking for the string and its terminator"); + Require(_sceNpAllocatorExStrndup(&failing, "abcdef", 3) == nullptr && record.size == 4, "a failed prefix duplicate returns null"); + _sceNpAllocatorExFree(&failing, record.block.data()); + Require(record.released == record.block.data(), "freeing goes through the allocator"); +} + void TestObjectsThroughStoredAllocator() { AllocatorRecord record {}; NpAllocator allocator {RecordAllocate, nullptr, RecordRelease, &record}; @@ -318,6 +361,67 @@ void TestObjectsThroughStoredAllocator() { "a failed array allocation returns null"); } +struct ExRecord { + std::array block {}; + int allocations = 0; + int reallocations = 0; + int releases = 0; + void* reallocated = nullptr; + std::size_t size = 0; + void* released = nullptr; + bool fail = false; +}; + +void* APS5_VABI ExAllocate(std::size_t size, void* context) { + auto* record = static_cast(context); + ++record->allocations; + record->size = size; + return record->fail ? nullptr : record->block.data(); +} + +void* APS5_VABI ExReallocate(void* pointer, std::size_t size, void* context) { + auto* record = static_cast(context); + ++record->reallocations; + record->reallocated = pointer; + record->size = size; + return record->fail ? nullptr : record->block.data() + 8; +} + +void APS5_VABI ExRelease(void* pointer, void* context) { + auto* record = static_cast(context); + ++record->releases; + record->released = pointer; +} + +void TestAllocatorExRecorded() { + ExRecord record {}; + NpAllocatorEx allocator {{ExAllocate, ExReallocate, ExRelease, &record}, 0x1234}; + std::uint8_t old = 0; + + Require(_sceNpAllocatorExRealloc(&allocator, &old, 96) == record.block.data() + 8 && record.reallocated == &old && record.size == 96 + && record.reallocations == 1 && record.allocations == 0 && record.releases == 0, + "reallocation is one call to the allocator's reallocate with the pointer and size"); + record.fail = true; + Require(_sceNpAllocatorExRealloc(&allocator, &old, 32) == nullptr && record.reallocations == 2 && record.releases == 0, + "a failed reallocation returns null and keeps the old block"); + Require(_sceNpAllocatorExMalloc(&allocator, 24) == nullptr && record.allocations == 1 && record.size == 24, + "a failed allocation is one call to allocate"); + _sceNpAllocatorExFree(&allocator, nullptr); + Require(record.releases == 1 && record.released == nullptr, "freeing null still calls release"); + + record.fail = false; + record.size = 0; + Require(_sceNpAllocatorExStrndup(&allocator, "abc", 10) != nullptr && record.size == 4, + "a short string asks for its length and terminator, not the limit"); + Require(_sceNpAllocatorExStrndup(&allocator, "abc", SIZE_MAX) != nullptr && record.size == 4 + && std::memcmp(record.block.data(), "abc", 4) == 0, + "an unbounded limit duplicates the whole string"); + const char unterminated[3] = {'x', 'y', 'z'}; + Require(_sceNpAllocatorExStrndup(&allocator, unterminated, sizeof(unterminated)) != nullptr && record.size == 4 + && std::memcmp(record.block.data(), "xyz", 4) == 0, + "the limit bounds the read of an unterminated buffer"); +} + void TestTitleIdObjects() { NpTitleIdObject parsed {}; std::memset(&parsed, 0x77, sizeof(parsed)); @@ -372,6 +476,62 @@ void TestTitleIdObjects() { Require(record.released == record.block.data(), "the deleting destructor frees through the stored allocator"); } +void TestTitleSecretObjects() { + std::uint8_t data[200]; + for (std::size_t i = 0; i < sizeof(data); ++i) data[i] = static_cast(i * 7 + 1); + NpTitleSecretObject secret {}; + _ZN3sce2np13NpTitleSecretC1Ev(&secret); + Require(secret.vtable != nullptr && _ZNK3sce2np13NpTitleSecret7IsEmptyEv(&secret), "a default title secret is empty"); + Require(_ZN3sce2np13NpTitleSecret13SetSecretDataEPvm(&secret, data, 127) == NpTitleSecretInvalid && _ZNK3sce2np13NpTitleSecret7IsEmptyEv(&secret), + "secret data must be 128 bytes and a refused size changes nothing"); + Require(_ZN3sce2np13NpTitleSecret13SetSecretDataEPvm(&secret, data, 128) == 0 && std::memcmp(secret.secret.data, data, 128) == 0, + "128 bytes of secret data are stored"); + Require(_ZNK3sce2np13NpTitleSecret6EqualsEPvm(&secret, data, 128) && !_ZNK3sce2np13NpTitleSecret6EqualsEPvm(&secret, data, 127), + "comparing with raw data needs 128 bytes"); + + NpTitleSecretObject fromData {}; + _ZN3sce2np13NpTitleSecretC1EPKvm(&fromData, data, 128); + bool refused = false; + NpTitleSecretObject wrongSize {}; + try { + _ZN3sce2np13NpTitleSecretC1EPKvm(&wrongSize, data, 129); + } catch (const std::invalid_argument&) { + refused = true; + } + Require(refused && _ZN3sce2npeqERKNS0_13NpTitleSecretES3_(&fromData, &secret), "constructing from data asserts the 128-byte size"); + + NpTitleSecret plain = secret.secret; + plain.data[127] ^= 1; + Require(_ZN3sce2npneERK16SceNpTitleSecretRKNS0_13NpTitleSecretE(&plain, &secret) && !_ZN3sce2npeqERKNS0_13NpTitleSecretERK16SceNpTitleSecret(&secret, &plain) + && !_ZNK3sce2np13NpTitleSecret6EqualsERK16SceNpTitleSecret(&secret, &plain), "all 128 bytes take part in the comparison"); + NpTitleSecretObject copy {}; + _ZN3sce2np13NpTitleSecretC1ERK16SceNpTitleSecret(©, &plain); + Require(_ZN3sce2npeqERK16SceNpTitleSecretRKNS0_13NpTitleSecretE(&plain, ©) && _ZN3sce2npneERKNS0_13NpTitleSecretES3_(©, &secret), + "a secret built from a SceNpTitleSecret compares equal to it"); + _ZN3sce2np13NpTitleSecretaSERKS1_(©, &secret); + Require(_ZNK3sce2np13NpTitleSecret6EqualsERKS1_(©, &secret), "assignment copies the secret"); + + char hex[300]; + std::memset(hex, 'x', sizeof(hex)); + Require(_ZN3sce2np13NpTitleSecret8BuildHexERKS1_Pcmb(&secret, hex, 256, false) == NpBufferTooSmall && hex[0] == 'x', "the hex form needs 257 bytes"); + Require(_ZN3sce2np13NpTitleSecret8BuildHexERKS1_Pcmb(&secret, hex, 257, true) == 0 && std::strlen(hex) == 256 && std::strncmp(hex, "01080F", 6) == 0, + "the secret is written as 256 hex digits"); + NpTitleSecretObject parsed {}; + _ZN3sce2np13NpTitleSecretC1Ev(&parsed); + Require(_ZN3sce2np13NpTitleSecret8ParseHexEPS1_PKcm(&parsed, hex, 256) == 0 && _ZN3sce2npeqERKNS0_13NpTitleSecretES3_(&parsed, &secret), + "the hex form parses back to the same secret"); + Require(_ZN3sce2np13NpTitleSecret8ParseHexEPS1_PKcm(&parsed, hex, 255) == NpInvalidArgument + && _ZN3sce2np13NpTitleSecret8ParseHexEPS1_PKcm(&parsed, hex, 258) == NpBufferTooSmall, "parsing needs an even length of at most 256 digits"); + + AllocatorRecord record {}; + NpAllocator allocator {RecordAllocate, nullptr, RecordRelease, &record}; + auto* owned = static_cast(_ZN3sce2np6ObjectnwEmR14SceNpAllocator(sizeof(NpTitleSecretObject), &allocator)); + _ZN3sce2np13NpTitleSecretC1Ev(owned); + using Destructor = void APS5_VABI (*)(NpTitleSecretObject*); + reinterpret_cast(owned->vtable[1])(owned); + Require(record.released == record.block.data(), "the deleting destructor frees through the stored allocator"); +} + struct CalloutRecord { std::atomic order {0}; std::atomic first {0}; @@ -432,6 +592,40 @@ void TestCallouts() { } +struct SelfTermination { + NpCalloutContext* context {}; + std::atomic finished {0}; + std::string message; +}; + +SelfTermination g_selfTermination; + +void APS5_VABI TerminateOwnContext(void*) { + try { + sceNpCalloutTermCtx(g_selfTermination.context); + } catch (const std::runtime_error& error) { + g_selfTermination.message = error.what(); + } + g_selfTermination.finished.store(1, std::memory_order_release); +} + +void TestCalloutSelfTermination() { + NpCalloutContext context {}; + NpCallout callout {}; + g_selfTermination.context = &context; + Require(sceNpCalloutInitCtx(&context, "np-self-term", 0x4000, 0, 0) == 0, "a callout context starts"); + Require(sceNpCalloutStartOnCtx(&context, &callout, 0, TerminateOwnContext, nullptr) == 0, "the terminating callout is queued"); + for (int i = 0; i < 2000 && g_selfTermination.finished.load(std::memory_order_acquire) == 0; ++i) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(g_selfTermination.finished.load(std::memory_order_acquire) == 1, "the terminating callout returns"); + Require(g_selfTermination.message.find("sceNpCalloutTermCtx") == 0, "terminating a context from its own callout is refused"); + Require(context.initialized == 1, "a refused termination leaves the context running"); + + sceNpCalloutTermCtx(&context); + + Require(context.initialized == 0, "the context terminates from another thread"); +} + bool Equals(const char* actual, const char* expected) { return std::strcmp(actual, expected) == 0; } @@ -529,6 +723,78 @@ void TestJsonText() { Require(sceNpUtilHttpUrlEncode(url, 3, "@", 1, &length) == NpUtilBufferTooSmall, "encoding needs room for the terminator"); } +void TestXmlText() { + char escaped[64] = {}; + std::size_t length = 0; + const char* special = "a\"&'<>b"; + const char* specialEscaped = "a"&'<>b"; + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), special, std::strlen(special), &length) == 0 && Equals(escaped, specialEscaped) + && length == std::strlen(specialEscaped) + 1, + "xml escaping replaces the five special characters and counts the terminator"); + length = 0; + Require(sceNpUtilXmlEscape(nullptr, 0, special, std::strlen(special), &length) == 0 && length == std::strlen(specialEscaped) + 1, + "xml escaping without a buffer only measures"); + const char* text = "\t\n\r \xC3\xA9\xE2\x82\xAC\xF0\x9F\x98\x80"; + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), text, std::strlen(text), &length) == 0 && Equals(escaped, text) && length == std::strlen(text) + 1, + "xml escaping copies whitespace and UTF-8 sequences"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), "a\x01", 2, &length) == NpUtilInvalidCharacter, "xml escaping refuses control characters"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), "\x7F", 1, &length) == NpUtilInvalidCharacter, "xml escaping refuses DEL"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), "\0", 1, &length) == NpUtilInvalidCharacter, "xml escaping refuses a NUL byte"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), "\x80", 1, &length) == NpUtilInvalidCharacter, "xml escaping refuses a lone continuation byte"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), "\xC1\xBF", 2, &length) == NpUtilInvalidCharacter, "xml escaping refuses an overlong lead byte"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), "\xC3(", 2, &length) == NpUtilInvalidCharacter, "xml escaping refuses a broken sequence"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), "\xF8\x80\x80\x80", 4, &length) == NpUtilInvalidCharacter, "xml escaping refuses a five-byte lead"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), "\xC3\xA9", 1, &length) == 0 && Equals(escaped, "\xC3\xA9") && length == 3, + "a sequence that starts inside the source is copied whole"); + Require(sceNpUtilXmlEscape(escaped, 5, "&", 1, &length) == NpUtilBufferTooSmall, "an entity must fit whole"); + Require(sceNpUtilXmlEscape(escaped, 5, "abcde", 5, &length) == NpUtilBufferTooSmall, "the terminator must fit"); + Require(sceNpUtilXmlEscape(escaped, 6, "abcde", 5, &length) == 0 && Equals(escaped, "abcde"), "a buffer of the measured size is enough"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), nullptr, 1, &length) == NpUtilInvalid, "xml escaping needs a source"); + Require(sceNpUtilXmlEscape(escaped, sizeof(escaped), "a", 0, &length) == NpUtilInvalid, "xml escaping needs a source length"); + Require(sceNpUtilXmlEscape(nullptr, 0, "a", 1, nullptr) == NpUtilInvalid, "xml escaping needs a buffer or a length"); + + char unescaped[64] = {}; + const char* entities = "x<>&"'y"; + Require(sceNpUtilXmlUnescape(unescaped, sizeof(unescaped), entities, std::strlen(entities), &length) == 0 && Equals(unescaped, "x<>&\"'y") + && length == 8, + "xml unescaping replaces the five entities and counts the terminator"); + length = 0; + Require(sceNpUtilXmlUnescape(nullptr, 0, entities, std::strlen(entities), &length) == 0 && length == 8, "xml unescaping without a buffer only measures"); + const char* others = " &A&"; + Require(sceNpUtilXmlUnescape(unescaped, sizeof(unescaped), others, std::strlen(others), &length) == 0 && Equals(unescaped, others), + "other ampersands are copied"); + Require(sceNpUtilXmlUnescape(unescaped, sizeof(unescaped), "a\0b", 3, &length) == 0 && unescaped[0] == 'a' && unescaped[1] == '\0' + && unescaped[2] == 'b' && unescaped[3] == '\0' && length == 4, + "xml unescaping copies every other byte"); + Require(sceNpUtilXmlUnescape(unescaped, sizeof(unescaped), "&", 1, &length) == 0 && Equals(unescaped, "&") && length == 2, + "an entity that starts inside the source is read whole"); + Require(sceNpUtilXmlUnescape(unescaped, 3, "<ab", 6, &length) == NpUtilBufferTooSmall, "each character must leave room for the terminator"); + Require(sceNpUtilXmlUnescape(unescaped, 3, "<a", 5, &length) == 0 && Equals(unescaped, " g_jsonEvents; @@ -758,6 +1024,62 @@ void TestTitleIds() { Require(sceNpUtilBuildTitleId(&parts, titleId, sizeof(titleId)) == NpUtilOutOfRange, "the index is at most 99"); } +void TestJid() { + constexpr int NpUtilInvalidJid = static_cast(0x80550604); + NpJid jid{}; + char buffer[64]; + std::size_t length = 0; + Require(sceNpUtilParseJid(&jid, "user@np.playstation.net/res", buffer, sizeof(buffer), &length) == 0 && Equals(jid.user, "user") + && Equals(jid.domain, "np.playstation.net") && Equals(jid.resource, "res") && length == 28 && jid.user == buffer, + "a jid splits into user, domain and resource in the buffer"); + Require(sceNpUtilParseJid(&jid, " a.b_c+d@x-y.z", buffer, sizeof(buffer), &length) == 0 && Equals(jid.user, "a.b_c+d") && Equals(jid.domain, "x-y.z") + && Equals(jid.resource, "") && length == 15, + "leading spaces are skipped and a missing resource is empty"); + Require(sceNpUtilParseJid(&jid, "nodomain", buffer, sizeof(buffer), &length) == 0 && Equals(jid.user, "") && Equals(jid.domain, "nodomain"), + "text without an at sign is a domain"); + Require(sceNpUtilParseJid(&jid, "a@b./res", buffer, sizeof(buffer), &length) == 0 && Equals(jid.domain, "b") && Equals(jid.resource, "/res"), + "trailing dots leave the domain and one character after it is skipped"); + Require(sceNpUtilParseJid(&jid, "a@b c", buffer, sizeof(buffer), &length) == 0 && Equals(jid.domain, "b") && Equals(jid.resource, "c"), + "the character after the domain is skipped whatever it is"); + length = 0; + Require(sceNpUtilParseJid(nullptr, "user@dom/res", nullptr, 0, &length) == 0 && length == 13, "parsing without a buffer only measures"); + Require(sceNpUtilParseJid(&jid, "user@dom/res", buffer, 12, &length) == NpUtilBufferTooSmall && jid.user == nullptr && jid.domain == nullptr, + "a buffer that is too small clears the jid"); + Require(sceNpUtilParseJid(&jid, "user@dom/res", buffer, 13, &length) == 0 && Equals(jid.resource, "res"), "a buffer of the measured size is enough"); + Require(sceNpUtilParseJid(&jid, " ", buffer, sizeof(buffer), &length) == NpUtilInvalidJid, "a blank jid is refused"); + Require(sceNpUtilParseJid(&jid, "a-b@c_d", buffer, sizeof(buffer), &length) == 0 && Equals(jid.user, "a-b") && Equals(jid.domain, "c_d"), + "a user may hold a dash and a domain an underscore"); + Require(sceNpUtilParseJid(&jid, "a@b+c", buffer, sizeof(buffer), &length) == 0 && Equals(jid.domain, "b") && Equals(jid.resource, "c"), + "a plus sign ends the domain"); + Require(sceNpUtilParseJid(&jid, "a@b../r", buffer, sizeof(buffer), &length) == 0 && Equals(jid.domain, "b") && Equals(jid.resource, "./r"), + "every trailing dot leaves the domain but only one is skipped"); + Require(sceNpUtilParseJid(&jid, "\ta@b", buffer, sizeof(buffer), &length) == 0 && Equals(jid.user, "") && Equals(jid.domain, "") + && Equals(jid.resource, "a@b"), + "only spaces are skipped before a jid"); + jid = {buffer, buffer, buffer, 0}; + Require(sceNpUtilParseJid(&jid, " ", buffer, sizeof(buffer), &length) == NpUtilInvalidJid && jid.user == nullptr && jid.resource == nullptr, + "a refused jid is cleared"); + Require(sceNpUtilParseJid(&jid, nullptr, buffer, sizeof(buffer), &length) == NpUtilInvalid, "parsing needs text"); + Require(sceNpUtilParseJid(&jid, "a@b", nullptr, 0, nullptr) == NpUtilInvalid, "parsing needs a buffer or a length"); + + NpIdFull npId{}; + std::memset(&npId, 0xCC, sizeof(npId)); + const NpJid psn{"user", "a1.b2.np.playstation.net", "r", 0}; + Require(sceNpUtilJidToNpId(&npId, &psn) == 0 && Equals(npId.handle.data, "user") && npId.opt[0] == 'a' && npId.opt[1] == '1' && npId.opt[2] == 'b' + && npId.opt[3] == '2' && npId.opt[4] == 'r' && npId.opt[5] == 0 && npId.reserved[0] == 1 && npId.reserved[1] == 0, + "a PSN jid becomes an npid with its domain code and resource"); + const NpJid sixteen{"abcdefghijklmnop", "a1.b2..playstation.net", nullptr, 0}; + Require(sceNpUtilJidToNpId(&npId, &sixteen) == 0 && std::memcmp(npId.handle.data, "abcdefghijklmnop", 16) == 0 && npId.handle.data[16] == 0, + "a 16-character user fills the online id and the label may be empty"); + const NpJid invalid[] = {{"", psn.domain, nullptr, 0}, {"abcdefghijklmnopq", psn.domain, nullptr, 0}, {"u", "a1.b2.np.playstation.ne", nullptr, 0}, + {"u", "a.b2.np.playstation.net", nullptr, 0}, {"u", "a1.b2", nullptr, 0}, {"u", psn.domain, "abcde", 0}, {"u", nullptr, nullptr, 0}, + {nullptr, psn.domain, nullptr, 0}}; + for (const NpJid& bad : invalid) Require(sceNpUtilJidToNpId(&npId, &bad) == NpUtilInvalidJid, "only a PSN jid with a short user and resource converts"); + const NpJid shapes[] = {{"u", "a1.-2.np.playstation.net", nullptr, 0}, {"u", "a1.b2.np.playstation.network", nullptr, 0}}; + for (const NpJid& bad : shapes) Require(sceNpUtilJidToNpId(&npId, &bad) == NpUtilInvalidJid, "the domain code is alphanumeric and the suffix exact"); + Require(sceNpUtilJidToNpId(nullptr, &psn) == NpUtilInvalid &&sceNpUtilJidToNpId(&npId, nullptr) == NpUtilInvalid, "conversion needs both pointers"); +} + void TestLanguagesAndDebugIds() { char code[6] = {}; char tag[36] = {}; @@ -866,6 +1188,9 @@ void TestHexAndCopies() { 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"); + const char unterminated[] = {'A', 'B', 'C', 'D', 'E', 'F', '\0'}; + Require(sceNpStrncpyToBuf(copy, 8, unterminated, 4) == 0 && Equals(copy, "ABCD"), + "a source without a terminator within the limit is cut at the limit"); } } @@ -1135,9 +1460,12 @@ int main() { TestConditionWakeups(); TestHeaps(); TestObjects(); + TestAllocatorEx(); TestObjectsThroughStoredAllocator(); + TestAllocatorExRecorded(); TestMemoryStreams(); TestTitleIdObjects(); + TestTitleSecretObjects(); TestStreamHelpers(); TestRefObjects(); TestCommIdObjects(); @@ -1145,9 +1473,12 @@ int main() { TestNumbers(); TestHexAndCopies(); TestCallouts(); + TestCalloutSelfTermination(); TestIds(); + TestJid(); TestLanguagesAndDebugIds(); TestJsonText(); + TestXmlText(); TestJsonParser(); TestTitleIds(); std::puts("guest np common runtime: PASS"); diff --git a/core/libs/tests/GuestNpCppWebApiActivities.cpp b/core/libs/tests/GuestNpCppWebApiActivities.cpp new file mode 100644 index 0000000000..2e7ae314d5 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiActivities.cpp @@ -0,0 +1,97 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, std::source_location at = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "requirement failed at %s:%u\n", at.file_name(), at.line()); + std::abort(); + } +} + +constexpr std::size_t kParameterSize = 0x10; +constexpr std::size_t kGuardSize = 0x10; +constexpr std::size_t kBaseOffset = 0x00; +constexpr std::size_t kStateOffset = 0x08; +constexpr int kInvalidArgument = static_cast(0x80553503); +constexpr int kAlreadyInitialized = static_cast(0x8055350f); +constexpr int kNotInitialized = static_cast(0x80553510); +constexpr int kNotSignedIn = static_cast(0x80552907); +constexpr unsigned char kFill = 0xA5; + +struct Storage { + alignas(8) unsigned char bytes[kParameterSize + kGuardSize]; +}; + +template +TValue Load(const Storage& storage, std::size_t offset) { + TValue value; + std::memcpy(&value, storage.bytes + offset, sizeof(value)); + return value; +} + +bool GuardIntact(const Storage& storage) { + for (std::size_t index = kParameterSize; index < sizeof(storage.bytes); ++index) { + if (storage.bytes[index] != kFill) return false; + } + return true; +} + +void TestParameterLifecycle() { + std::uint64_t libContext = 1; + Storage storage; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + + Require(_ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesC1Ev(storage.bytes) == 0); + Require(Load(storage, kBaseOffset) == nullptr); + Require(Load(storage, kStateOffset) == nullptr); + Require(GuardIntact(storage)); + + Require(_ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities10initializeEPNS1_6Common10LibContextENS7_12IntrusivePtrINS7_6VectorINS7_6StringEEEEE( + storage.bytes, nullptr, nullptr) == kInvalidArgument); + Require(_ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities10initializeEPNS1_6Common10LibContextENS7_12IntrusivePtrINS7_6VectorINS7_6StringEEEEE( + storage.bytes, &libContext, nullptr) == 0); + Require(Load(storage, kBaseOffset) == &libContext); + Require(Load(storage, kStateOffset) != nullptr); + Require(_ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities10initializeEPNS1_6Common10LibContextENS7_12IntrusivePtrINS7_6VectorINS7_6StringEEEEE( + storage.bytes, &libContext, nullptr) == kAlreadyInitialized); + + _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities8setlimitEi(storage.bytes, 12); + _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities17setnpServiceLabelEi(storage.bytes, 3); + Require(GuardIntact(storage)); + + Require(_ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities9terminateEv(storage.bytes) == 0); + Require(Load(storage, kBaseOffset) == nullptr); + Require(Load(storage, kStateOffset) == nullptr); + Require(_ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivities9terminateEv(storage.bytes) == kNotInitialized); + Require(GuardIntact(storage)); + + _ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesD1Ev(storage.bytes); + Require(GuardIntact(storage)); + + Storage copyStorage; + std::memset(copyStorage.bytes, kFill, sizeof(copyStorage.bytes)); + Require(_ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesC2Ev(copyStorage.bytes) == 0); + Require(_ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi28ParameterToGetUserActivitiesD2Ev(copyStorage.bytes) == 0); + Require(GuardIntact(copyStorage)); +} + +void TestRequestIsSignedOut() { + Require(_ZN3sce2Np9CppWebApi10Activities2V114UserActivities11ActivityApi17getUserActivitiesEiRKNS5_28ParameterToGetUserActivitiesERNS1_6Common11TransactionINS9_12IntrusivePtrINS4_26GetUsersActivitiesResponseEEENSB_INS9_18ResponseHeaderBaseEEEEE( + 0, nullptr, nullptr) == kNotSignedIn); +} + +} + +int main() { + TestParameterLifecycle(); + TestRequestIsSignedOut(); + return 0; +} diff --git a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp index 38dd085db7..e9a056947e 100644 --- a/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp +++ b/core/libs/tests/GuestNpCppWebApiIntrusivePtr.cpp @@ -20,6 +20,8 @@ static void Require(bool value) { if (!value) std::abort(); } static constexpr std::size_t ObjectSize = 0x40; static constexpr std::size_t VectorReferenceCountOffset = 0x30; +static constexpr std::size_t StringVectorReferenceCountOffset = 0x38; +static constexpr std::size_t ScalarVectorReferenceCountOffset = 0x20; struct DestroyCase { Destroy destroy; @@ -116,6 +118,7 @@ static void TestAssign(const AssignCase& testCase) { testCase.assign(&pointer, &pointer); Require(pointer.object == first && pointer.deleter == RecordDelete && pointer.libContext == &context); Require(ReadCount(first, offset) == 1); + Require(deleterCalls == 0); const IntrusivePtr alias{first, nullptr, nullptr}; testCase.assign(&pointer, &alias); @@ -301,11 +304,11 @@ static void TestVectorPushBackReleasesElements() { Require(deletedObject == object); } -static void TestAdoptedVectorBodyDestroyedThroughDeleter() { +static void TestAdoptedStringVectorCountsReferences() { int context = 0; alignas(8) unsigned char body[ObjectSize]; std::memset(body, 0x5a, sizeof(body)); - WriteCount(body, VectorReferenceCountOffset, 1); + WriteCount(body, StringVectorReferenceCountOffset, 0); deleterCalls = 0; deletedObject = nullptr; @@ -315,12 +318,52 @@ static void TestAdoptedVectorBodyDestroyedThroughDeleter() { Require(vector.object == body); Require(vector.deleter == RecordDelete); Require(vector.libContext == &context); + Require(ReadCount(body, StringVectorReferenceCountOffset) == 1); + Require(OnlyCountChanged(body, StringVectorReferenceCountOffset)); + + IntrusivePtr copy; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1ERS7_(©, &vector); + Require(copy.object == body); + Require(ReadCount(body, StringVectorReferenceCountOffset) == 2); + + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(©); + Require(ReadCount(body, StringVectorReferenceCountOffset) == 1); + Require(deleterCalls == 0); _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(&vector); - Require(ReadCount(body, VectorReferenceCountOffset) == 0); + Require(ReadCount(body, StringVectorReferenceCountOffset) == 0); + Require(OnlyCountChanged(body, StringVectorReferenceCountOffset)); Require(deleterCalls == 1); Require(deletedObject == body); Require(vector.object == nullptr); + + IntrusivePtr empty; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEEC1EPS6_PFvS8_EPNS2_10LibContextE(&empty, nullptr, RecordDelete, &context); + Require(empty.object == nullptr); + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev(&empty); + Require(deleterCalls == 1); +} + +static void TestScalarVectorAssignment() { + int context = 0; + alignas(8) unsigned char first[ObjectSize]; + alignas(8) unsigned char second[ObjectSize]; + std::memset(first, 0x5a, sizeof(first)); + std::memset(second, 0x5a, sizeof(second)); + WriteCount(first, ScalarVectorReferenceCountOffset, 2); + WriteCount(second, ScalarVectorReferenceCountOffset, 1); + deleterCalls = 0; + + IntrusivePtr target{first, RecordDelete, &context}; + const IntrusivePtr source{second, RecordDelete, &context}; + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEaSERS6_(&target, &source); + + Require(target.object == second); + Require(ReadCount(first, ScalarVectorReferenceCountOffset) == 1); + Require(ReadCount(second, ScalarVectorReferenceCountOffset) == 2); + Require(OnlyCountChanged(first, ScalarVectorReferenceCountOffset)); + Require(OnlyCountChanged(second, ScalarVectorReferenceCountOffset)); + Require(deleterCalls == 0); } static void TestVectorAliasedGrowth() { @@ -399,6 +442,48 @@ static void TestUserProfileModelAndTransaction() { Require(_ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(transaction) == 0); } +static void TestUserProfilePublicProfileRequest() { + constexpr int NotSignedIn = static_cast(0x80552907); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfileC1Ev() == 0); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfile10initializeEPNS1_6Common10LibContextEPKc() == 0); + + std::uint64_t transaction[0x20]; + std::memset(transaction, 0xa5, sizeof(transaction)); + Require(_ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEEC1Ev(transaction) == 0); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi16getPublicProfileEiRKNS4_27ParameterToGetPublicProfileERNS1_6Common11TransactionINS8_12IntrusivePtrINS3_24GetPublicProfileResponseEEENSA_INS8_18ResponseHeaderBaseEEEEE() == NotSignedIn); + Require(_ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEE5startEPNS2_10LibContextE() == NotSignedIn); + Require(_ZNK3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEE11getResponseERS8_() == NotSignedIn); + Require(_ZN3sce2Np9CppWebApi6Common15TransactionBaseINS2_12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEENS4_INS2_18ResponseHeaderBaseEEEE28setResponseInformationOptionEP37SceNpWebApi2ResponseInformationOption() == 0); + Require(_ZN3sce2Np9CppWebApi6Common11TransactionINS2_12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEENS4_INS2_18ResponseHeaderBaseEEEED1Ev(transaction) == 0); + + Require(_ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfile9terminateEv() == 0); + Require(_ZN3sce2Np9CppWebApi11UserProfile2V115BasicProfileApi27ParameterToGetPublicProfileD1Ev() == 0); +} + +static void TestUserProfileBoolAndSize() { + int object = 0; + const IntrusivePtr empty{nullptr, RecordDelete, nullptr}; + const IntrusivePtr filled{&object, nullptr, nullptr}; + Require(!_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEcvbEv(&empty)); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEcvbEv(&filled)); + Require(!_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEcvbEv(&empty)); + Require(_ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEcvbEv(&filled)); + + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE4sizeEv(&empty) == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE4sizeEv(&empty) == 0); + + // a body created by pushBack is host-side, so size reads it + int context = 0; + IntrusivePtr vector; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEEC1EPNS2_10LibContextE(&vector, &context); + const IntrusivePtr element{nullptr, nullptr, &context}; + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(&vector, &element) == 0); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEE8pushBackERKS8_(&vector, &element) == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEEE4sizeEv(&vector) == 2); + Require(_ZNK3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEEEE4sizeEv(&vector) == 2); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V126RequestPlayerSessionPlayerEEEED1Ev(&vector); +} + int main(int argc, char** argv) { if (argc == 2 && std::strcmp(argv[1], "alias-vector") == 0) { TestVectorAliasedGrowth(); return 0; } Require(sizeof(IntrusivePtr) == 0x18); @@ -418,6 +503,8 @@ int main(int argc, char** argv) { _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEC1Ev, _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEC1Ev, _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEC1Ev, + _ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V114PersonalDetailEEC1Ev, }; for (const auto construct : constructors) TestConstruct(construct); @@ -459,6 +546,10 @@ int main(int argc, char** argv) { _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V112BasicProfileEEptEv, _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V125GetPublicProfilesResponseEEptEv, _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEEdeEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V16AvatarEEptEv, + _ZNK3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V16AvatarEEEEEEdeEv, }; for (const auto access : accessors) TestAccess(access); @@ -478,6 +569,7 @@ int main(int argc, char** argv) { {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_14SessionManager2V113PlayerSessionEEaSERS7_, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_14SessionManager2V113PlayerSessionEEEEEEaSERKSA_, VectorReferenceCountOffset}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_13InGameCatalog2V59ContainerEEEEEEaSERKSA_, VectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEEaSERS6_, ScalarVectorReferenceCountOffset}, }; for (const auto& testCase : assignments) TestAssign(testCase); @@ -493,6 +585,8 @@ int main(int argc, char** argv) { {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_15ProfanityFilter2V219WebApiFilterRequestEED1Ev, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_18ResponseHeaderBaseEED1Ev, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6StringEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev, StringVectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEED1Ev, ScalarVectorReferenceCountOffset}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V113BasicPresenceEEEEEED1Ev, VectorReferenceCountOffset}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V57ProductEED1Ev, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_13InGameCatalog2V59ContainerEED1Ev, 0}, @@ -506,6 +600,10 @@ int main(int argc, char** argv) { {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V113BasicPresenceEED1Ev, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_30CommunicationRestrictionStatus2V338CommunicationRestrictionStatusResponseEED1Ev, 0}, {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V112BasicProfileEEEEEED1Ev, VectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS1_11UserProfile2V124GetPublicProfileResponseEED1Ev, 0}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS3_INS1_11UserProfile2V16AvatarEEEEEED1Ev, VectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorINS2_6StringEEEED1Ev, StringVectorReferenceCountOffset}, + {_ZN3sce2Np9CppWebApi6Common12IntrusivePtrINS2_6VectorImEEED1Ev, ScalarVectorReferenceCountOffset}, }; for (const auto& testCase : destructors) TestDestroy(testCase); @@ -513,8 +611,11 @@ int main(int argc, char** argv) { TestMatchmakingSharedOwnership(); TestMatchTeamVectorSize(); TestVectorPushBackReleasesElements(); - TestAdoptedVectorBodyDestroyedThroughDeleter(); + TestAdoptedStringVectorCountsReferences(); + TestScalarVectorAssignment(); TestUserProfileVectorAssign(); TestUserProfileModelAndTransaction(); + TestUserProfilePublicProfileRequest(); + TestUserProfileBoolAndSize(); return 0; } diff --git a/core/libs/tests/GuestNpCppWebApiMatches.cpp b/core/libs/tests/GuestNpCppWebApiMatches.cpp new file mode 100644 index 0000000000..10f6fe2c62 --- /dev/null +++ b/core/libs/tests/GuestNpCppWebApiMatches.cpp @@ -0,0 +1,251 @@ +#include "prx/libSceNpCppWebApi/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, std::source_location at = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "requirement failed at %s:%u\n", at.file_name(), at.line()); + std::abort(); + } +} + +template +T Read(const std::uint8_t* object, const std::size_t offset) { + T value; + std::memcpy(&value, object + offset, sizeof(value)); + return value; +} + +template +void Write(std::uint8_t* object, const std::size_t offset, const T value) { + std::memcpy(object + offset, &value, sizeof(value)); +} + +} + +int main() { + alignas(8) std::array request{}; + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer12setAccountIdEPKc(request.data(), "value") == 0); + Require(Read(request.data(), 96 + 0x08) != nullptr && std::strcmp(Read(request.data(), 96 + 0x08), "value") == 0); + Require(request[128] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer13setPlayerNameEPKc(request.data(), "value") == 0); + Require(Read(request.data(), 56 + 0x08) != nullptr && std::strcmp(Read(request.data(), 56 + 0x08), "value") == 0); + Require(request[88] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V111AddedPlayer9setTeamIdEPKc(request.data(), "value") == 0); + Require(Read(request.data(), 136 + 0x08) != nullptr && std::strcmp(Read(request.data(), 136 + 0x08), "value") == 0); + Require(request[168] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam11setTeamNameEPKc(request.data(), "value") == 0); + Require(Read(request.data(), 48 + 0x08) != nullptr && std::strcmp(Read(request.data(), 48 + 0x08), "value") == 0); + Require(request[80] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest9setZoneIdEPKc(request.data(), "value") == 0); + Require(Read(request.data(), 56 + 0x08) != nullptr && std::strcmp(Read(request.data(), 56 + 0x08), "value") == 0); + Require(request[88] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V118RequestMatchPlayer12setAccountIdEPKc(request.data(), "value") == 0); + Require(Read(request.data(), 96 + 0x08) != nullptr && std::strcmp(Read(request.data(), 96 + 0x08), "value") == 0); + Require(request[128] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V118RequestMatchPlayer13setPlayerNameEPKc(request.data(), "value") == 0); + Require(Read(request.data(), 56 + 0x08) != nullptr && std::strcmp(Read(request.data(), 56 + 0x08), "value") == 0); + Require(request[88] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest9setZoneIdEPKc(request.data(), "value") == 0); + Require(Read(request.data(), 24 + 0x08) != nullptr && std::strcmp(Read(request.data(), 24 + 0x08), "value") == 0); + Require(request[56] == 1); + std::memset(request.data(), 0, request.size()); + const int intValue = 0x1234; + Require(_ZN3sce2Np9CppWebApi7Matches2V113RemovedPlayer9setReasonERKNS3_11LeaveReasonE(request.data(), &intValue) == 0); + Require(Read(request.data(), 48) == intValue && request[52] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V116JoinMatchRequest17setNpServiceLabelERKi(request.data(), &intValue) == 0); + Require(Read(request.data(), 16) == intValue && request[20] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V117LeaveMatchRequest17setNpServiceLabelERKi(request.data(), &intValue) == 0); + Require(Read(request.data(), 16) == intValue && request[20] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setExpirationTimeERKi(request.data(), &intValue) == 0); + Require(Read(request.data(), 92) == intValue && request[96] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest17setNpServiceLabelERKi(request.data(), &intValue) == 0); + Require(Read(request.data(), 16) == intValue && request[20] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCooperativeResultERKNS3_24RequestCooperativeResultE(request.data(), &intValue) == 0); + Require(Read(request.data(), 20) == intValue && request[24] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest17setNpServiceLabelERKi(request.data(), &intValue) == 0); + Require(Read(request.data(), 16) == intValue && request[20] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setExpirationTimeERKi(request.data(), &intValue) == 0); + Require(Read(request.data(), 60) == intValue && request[64] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest17setNpServiceLabelERKi(request.data(), &intValue) == 0); + Require(Read(request.data(), 16) == intValue && request[20] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchStatusRequest17setNpServiceLabelERKi(request.data(), &intValue) == 0); + Require(Read(request.data(), 16) == intValue && request[20] == 1); + std::memset(request.data(), 0, request.size()); + const double doubleValue = 1.5; + Require(_ZN3sce2Np9CppWebApi7Matches2V118RequestTeamResults8setScoreERKd(request.data(), &doubleValue) == 0); + Require(Read(request.data(), 56) == doubleValue && request[64] == 1); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V120RequestPlayerResults8setScoreERKd(request.data(), &doubleValue) == 0); + Require(Read(request.data(), 56) == doubleValue && request[64] == 1); + alignas(8) std::array objectBody{}; + Write(objectBody.data(), 0x00, 1); + const IntrusivePtr objectValue{objectBody.data(), nullptr, nullptr}; + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V118CreateMatchRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(request.data(), &objectValue) == 0); + Require(Read(request.data(), 152) == objectBody.data() && request[176] == 1); + Require(Read(objectBody.data(), 0x00) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V119RequestMatchResults20setCompetitiveResultERKNS1_6Common12IntrusivePtrINS3_24RequestCompetitiveResultEEE(request.data(), &objectValue) == 0); + Require(Read(request.data(), 32) == objectBody.data() && request[56] == 1); + Require(Read(objectBody.data(), 0x00) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V120ReportResultsRequest18setMatchStatisticsERKNS1_6Common12IntrusivePtrINS3_22RequestMatchStatisticsEEE(request.data(), &objectValue) == 0); + Require(Read(request.data(), 48) == objectBody.data() && request[72] == 1); + Require(Read(objectBody.data(), 0x00) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V124UpdateMatchDetailRequest15setInGameRosterERKNS1_6Common12IntrusivePtrINS3_19RequestInGameRosterEEE(request.data(), &objectValue) == 0); + Require(Read(request.data(), 112) == objectBody.data() && request[136] == 1); + Require(Read(objectBody.data(), 0x00) >= 2); + alignas(8) std::array vectorBody{}; + Write(vectorBody.data(), 0x30, 1); + const IntrusivePtr vectorValue{vectorBody.data(), nullptr, nullptr}; + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam10setMembersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_13RequestMemberEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 88) == vectorBody.data() && request[112] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V118RequestTeamResults20setTeamMemberResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_23RequestTeamMemberResultEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 72) == vectorBody.data() && request[96] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10setPlayersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestMatchPlayerEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 16) == vectorBody.data() && request[40] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster8setTeamsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_16RequestMatchTeamEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 48) == vectorBody.data() && request[72] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic23setTeamMemberStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_26RequestTeamMemberStatisticEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 80) == vectorBody.data() && request[104] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic8setStatsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 48) == vectorBody.data() && request[72] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics17setTeamStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestTeamStatisticEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 48) == vectorBody.data() && request[72] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics19setPlayerStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_22RequestPlayerStatisticEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 16) == vectorBody.data() && request[40] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult14setTeamResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestTeamResultsEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 48) == vectorBody.data() && request[72] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult16setPlayerResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestPlayerResultsEEEEE(request.data(), &vectorValue) == 0); + Require(Read(request.data(), 16) == vectorBody.data() && request[40] == 1); + Require(Read(vectorBody.data(), 0x30) >= 2); + std::uint64_t vectorContext = 9; + alignas(8) IntrusivePtr emptyMembers{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V113RequestMemberEEEEC1EPNS2_10LibContextE(&emptyMembers, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V116RequestMatchTeam10setMembersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_13RequestMemberEEEEE(request.data(), &emptyMembers) == 0); + Require(Read(request.data(), 88) == nullptr && request[112] == 1); + alignas(8) IntrusivePtr emptyTeamMemberResults{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V123RequestTeamMemberResultEEEEC1EPNS2_10LibContextE(&emptyTeamMemberResults, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V118RequestTeamResults20setTeamMemberResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_23RequestTeamMemberResultEEEEE(request.data(), &emptyTeamMemberResults) == 0); + Require(Read(request.data(), 72) == nullptr && request[96] == 1); + alignas(8) IntrusivePtr emptyPlayers{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V118RequestMatchPlayerEEEEC1EPNS2_10LibContextE(&emptyPlayers, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10setPlayersERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestMatchPlayerEEEEE(request.data(), &emptyPlayers) == 0); + Require(Read(request.data(), 16) == nullptr && request[40] == 1); + alignas(8) IntrusivePtr emptyTeams{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V116RequestMatchTeamEEEEC1EPNS2_10LibContextE(&emptyTeams, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V119RequestInGameRoster8setTeamsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_16RequestMatchTeamEEEEE(request.data(), &emptyTeams) == 0); + Require(Read(request.data(), 48) == nullptr && request[72] == 1); + alignas(8) IntrusivePtr emptyTeamMemberStatistics{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V126RequestTeamMemberStatisticEEEEC1EPNS2_10LibContextE(&emptyTeamMemberStatistics, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic23setTeamMemberStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_26RequestTeamMemberStatisticEEEEE(request.data(), &emptyTeamMemberStatistics) == 0); + Require(Read(request.data(), 80) == nullptr && request[104] == 1); + alignas(8) IntrusivePtr emptyStats{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V119AdditionalStatisticEEEEC1EPNS2_10LibContextE(&emptyStats, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V120RequestTeamStatistic8setStatsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_19AdditionalStatisticEEEEE(request.data(), &emptyStats) == 0); + Require(Read(request.data(), 48) == nullptr && request[72] == 1); + alignas(8) IntrusivePtr emptyTeamStatistics{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V120RequestTeamStatisticEEEEC1EPNS2_10LibContextE(&emptyTeamStatistics, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics17setTeamStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestTeamStatisticEEEEE(request.data(), &emptyTeamStatistics) == 0); + Require(Read(request.data(), 48) == nullptr && request[72] == 1); + alignas(8) IntrusivePtr emptyPlayerStatistics{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V122RequestPlayerStatisticEEEEC1EPNS2_10LibContextE(&emptyPlayerStatistics, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V122RequestMatchStatistics19setPlayerStatisticsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_22RequestPlayerStatisticEEEEE(request.data(), &emptyPlayerStatistics) == 0); + Require(Read(request.data(), 16) == nullptr && request[40] == 1); + alignas(8) IntrusivePtr emptyTeamResults{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V118RequestTeamResultsEEEEC1EPNS2_10LibContextE(&emptyTeamResults, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult14setTeamResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_18RequestTeamResultsEEEEE(request.data(), &emptyTeamResults) == 0); + Require(Read(request.data(), 48) == nullptr && request[72] == 1); + alignas(8) IntrusivePtr emptyPlayerResults{}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_7Matches2V120RequestPlayerResultsEEEEC1EPNS2_10LibContextE(&emptyPlayerResults, &vectorContext); + std::memset(request.data(), 0, request.size()); + Require(_ZN3sce2Np9CppWebApi7Matches2V124RequestCompetitiveResult16setPlayerResultsERKNS1_6Common6VectorINS5_12IntrusivePtrINS3_20RequestPlayerResultsEEEEE(request.data(), &emptyPlayerResults) == 0); + Require(Read(request.data(), 16) == nullptr && request[40] == 1); + std::memset(request.data(), 0, request.size()); + Write(request.data(), 0x48, 1); + Require(_ZNK3sce2Np9CppWebApi7Matches2V119RequestInGameRoster10teamsIsSetEv(request.data())); + Write(request.data(), 0x28, 1); + Require(_ZNK3sce2Np9CppWebApi7Matches2V119RequestInGameRoster12playersIsSetEv(request.data())); + Require(_ZNK3sce2Np9CppWebApi7Matches2V119CreateMatchResponse10getMatchIdEv(request.data()) == request.data() + 0x30); + alignas(8) std::array joinParameter{}; + alignas(8) std::array joinBodyObject{}; + Write(joinBodyObject.data(), 0x00, 1); + const IntrusivePtr joinBody{joinBodyObject.data(), nullptr, nullptr}; + std::uint64_t context = 1; + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_16JoinMatchRequestEEE(joinParameter.data(), &context, "room", &joinBody) == 0); + Require(Read(joinParameter.data(), 0x08) != nullptr); + Require(Read(joinBodyObject.data(), 0x00) == 2); + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_16JoinMatchRequestEEE(joinParameter.data(), &context, "room", &joinBody) == static_cast(0x8055350f)); + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch9terminateEv(joinParameter.data()) == 0); + Require(Read(joinBodyObject.data(), 0x00) == 1); + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch9terminateEv(joinParameter.data()) == static_cast(0x80553510)); + + alignas(8) std::array emptyParameter{}; + const IntrusivePtr emptyBody{nullptr, nullptr, nullptr}; + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_16JoinMatchRequestEEE(emptyParameter.data(), &context, "room", &emptyBody) == static_cast(0x80553512)); + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_16JoinMatchRequestEEE(emptyParameter.data(), nullptr, "room", &joinBody) == static_cast(0x80553503)); + std::uint64_t zero = 0; + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_16JoinMatchRequestEEE(emptyParameter.data(), &zero, "room", &joinBody) == static_cast(0x80553502)); + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi20ParameterToJoinMatch10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_16JoinMatchRequestEEE(emptyParameter.data(), &context, nullptr, &joinBody) == static_cast(0x80553503)); + + alignas(8) std::array createParameter{}; + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch10initializeEPNS1_6Common10LibContextENS6_12IntrusivePtrINS3_18CreateMatchRequestEEE(createParameter.data(), &context, &joinBody) == 0); + Require(_ZN3sce2Np9CppWebApi7Matches2V18MatchApi22ParameterToCreateMatch9terminateEv(createParameter.data()) == 0); + + return 0; +} diff --git a/core/libs/tests/GuestNpCppWebApiOffline.cpp b/core/libs/tests/GuestNpCppWebApiOffline.cpp index 40a67516ed..d66fca391b 100644 --- a/core/libs/tests/GuestNpCppWebApiOffline.cpp +++ b/core/libs/tests/GuestNpCppWebApiOffline.cpp @@ -268,6 +268,66 @@ static void TestUserProfileParameterLifecycles() { }); } +template +static int InitializeSessionWithBody(TInitialize initialize, void* parameter, const std::uint64_t* context, bool present) { + if (present) return initialize(parameter, context, "session", &body); + Require(initialize(parameter, context, "session", &missingBody) == ParameterBodyMissing, "initialize accepted a missing request body"); + return initialize(parameter, context, nullptr, &body); +} + +static void TestPlayerSessionParameterLifecycles() { + TestParameterLifecycle( + { + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsC1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessionsD1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions9terminateEv, + nullptr, + }, + [](void* parameter, const std::uint64_t* context, bool present) { + return _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi28ParameterToGetPlayerSessions10initializeEPNS1_6Common10LibContextEPKc(parameter, context, present ? "session" : nullptr); + }); + TestParameterLifecycle( + { + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsC1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitationsD1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations9terminateEv, + nullptr, + }, + [](void* parameter, const std::uint64_t* context, bool present) { + return InitializeSessionWithBody(_ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi39ParameterToSendPlayerSessionInvitations10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_49PostPlayerSessionsSessionIdInvitationsRequestBodyEEE, parameter, context, present); + }); + TestParameterLifecycle( + { + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesC1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionPropertiesD1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties9terminateEv, + nullptr, + }, + [](void* parameter, const std::uint64_t* context, bool present) { + return InitializeSessionWithBody(_ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi37ParameterToSetPlayerSessionProperties10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_39PatchPlayerSessionsSessionIdRequestBodyEEE, parameter, context, present); + }); + TestParameterLifecycle( + { + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderC1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeaderD1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader9terminateEv, + nullptr, + }, + [](void* parameter, const std::uint64_t* context, bool present) { + return InitializeSessionWithBody(_ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi36ParameterToChangePlayerSessionLeader10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_43PutPlayerSessionsSessionIdLeaderRequestBodyEEE, parameter, context, present); + }); + TestParameterLifecycle( + { + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersC1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsersD1Ev, + _ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers9terminateEv, + nullptr, + }, + [](void* parameter, const std::uint64_t* context, bool present) { + return InitializeSessionWithBody(_ZN3sce2Np9CppWebApi14SessionManager2V117PlayerSessionsApi49ParameterToAddPlayerSessionJoinableSpecifiedUsers10initializeEPNS1_6Common10LibContextEPKcNS6_12IntrusivePtrINS3_60PostPlayerSessionsSessionIdJoinableSpecifiedUsersRequestBodyEEE, parameter, context, present); + }); +} + static void TestIteratorDestructors() { unsigned char iterator[0x20]; std::memset(iterator, 0x5a, sizeof(iterator)); @@ -331,6 +391,7 @@ int main() { TestSessionManagerParameterLifecycles(); TestLeaderboardsParameterLifecycles(); TestUserProfileParameterLifecycles(); + TestPlayerSessionParameterLifecycles(); TestIteratorDestructors(); TestVectorSizeAndElements(); std::puts("NpCppWebApi offline tests passed"); diff --git a/core/libs/tests/GuestNpCppWebApiString.cpp b/core/libs/tests/GuestNpCppWebApiString.cpp index c08b3138dc..92b90704c1 100644 --- a/core/libs/tests/GuestNpCppWebApiString.cpp +++ b/core/libs/tests/GuestNpCppWebApiString.cpp @@ -24,9 +24,11 @@ constexpr std::size_t kReferenceCountOffset = 0x00; constexpr std::size_t kBufferOffset = 0x08; constexpr std::size_t kBufferSizeOffset = 0x10; constexpr std::size_t kLibContextOffset = 0x18; +constexpr int kStringInvalidArgument = static_cast(0x80553503); constexpr unsigned char kFill = 0xA5; -constexpr std::size_t kVectorSize = 0x18; +constexpr std::size_t kVectorSize = 0x48; +constexpr std::size_t kVectorReferenceCountOffset = 0x38; constexpr std::size_t kObjectOffset = 0x00; constexpr std::size_t kBodyMagicOffset = 0x00; constexpr std::size_t kBodyReferenceCountOffset = 0x30; @@ -34,6 +36,10 @@ constexpr std::size_t kBodyElementKindOffset = 0x34; constexpr std::size_t kBodyElementsOffset = 0x38; constexpr std::size_t kBodySizeOffset = 0x40; +constexpr std::size_t kIteratorSize = 0x10; +constexpr std::size_t kIteratorReferenceCountOffset = 0x00; +constexpr std::size_t kIteratorElementOffset = 0x08; + struct Storage { alignas(8) unsigned char bytes[kStringSize + kGuardSize]; }; @@ -42,6 +48,10 @@ struct VectorStorage { alignas(8) unsigned char bytes[kVectorSize + kGuardSize]; }; +struct IteratorStorage { + alignas(8) unsigned char bytes[kIteratorSize + kGuardSize]; +}; + template TValue Load(const Storage& storage, std::size_t offset) { TValue value; @@ -75,6 +85,20 @@ bool VectorGuardIntact(const VectorStorage& storage) { return true; } +bool IteratorGuardIntact(const IteratorStorage& storage) { + for (std::size_t i = kIteratorSize; i < sizeof(storage.bytes); i++) { + if (storage.bytes[i] != kFill) return false; + } + return true; +} + +template +TValue LoadIterator(const IteratorStorage& storage, std::size_t offset) { + TValue value; + std::memcpy(&value, storage.bytes + offset, sizeof(value)); + return value; +} + void TestConstructedStringIsEmpty() { std::uint64_t libContext = 1; Storage storage; @@ -86,6 +110,8 @@ void TestConstructedStringIsEmpty() { Require(Load(storage, kBufferSizeOffset) == 0); Require(Load(storage, kLibContextOffset) == &libContext); Require(GuardIntact(storage)); + Require(_ZNK3sce2Np9CppWebApi6Common6String4sizeEv(reinterpret_cast(storage.bytes)) == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6String6lengthEv(reinterpret_cast(storage.bytes)) == 0); const char* text = _ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)); Require(text != nullptr && text[0] == '\0'); @@ -116,8 +142,10 @@ void TestAppendBuildsAndClearsBuffer() { Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(storage.bytes), "rank") == 0); Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)), "rank") == 0); + Require(Load(storage, kBufferSizeOffset) == 5); Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(storage.bytes), "ing") == 0); Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)), "ranking") == 0); + Require(Load(storage, kBufferSizeOffset) == 8); Require(Load(storage, kBufferOffset) != nullptr); Require(GuardIntact(storage)); @@ -133,13 +161,83 @@ void TestAppendNullKeepsEmpty() { std::memset(storage.bytes, kFill, sizeof(storage.bytes)); _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(storage.bytes), &libContext); - Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(storage.bytes), nullptr) == 0); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(storage.bytes), nullptr) == kStringInvalidArgument); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(storage.bytes), "") == 0); Require(Load(storage, kBufferOffset) == nullptr); Require(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes))[0] == '\0'); _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(storage.bytes)); } +void TestStringCopyFromAndObservers() { + std::uint64_t libContext = 1; + std::uint64_t sourceContext = 2; + Storage storage; + Storage source; + std::memset(storage.bytes, kFill, sizeof(storage.bytes)); + std::memset(source.bytes, kFill, sizeof(source.bytes)); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(storage.bytes), &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6String8copyFromEPKc(reinterpret_cast(storage.bytes), "alpha") == 0); + Require(Load(storage, kBufferSizeOffset) == 6); + Require(_ZNK3sce2Np9CppWebApi6Common6String4sizeEv(reinterpret_cast(storage.bytes)) == 5); + Require(_ZNK3sce2Np9CppWebApi6Common6String6lengthEv(reinterpret_cast(storage.bytes)) == 5); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)), "alpha") == 0); + Require(_ZN3sce2Np9CppWebApi6Common6String8copyFromEPKc(reinterpret_cast(storage.bytes), nullptr) == kStringInvalidArgument); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)), "alpha") == 0); + + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(source.bytes), &sourceContext); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(source.bytes), "beta") == 0); + Require(_ZN3sce2Np9CppWebApi6Common6String8copyFromERKS3_(reinterpret_cast(storage.bytes), reinterpret_cast(source.bytes)) == 0); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)), "beta") == 0); + Require(Load(storage, kBufferSizeOffset) == 5); + Require(_ZN3sce2Np9CppWebApi6Common6StringeqERKS3_(reinterpret_cast(storage.bytes), reinterpret_cast(source.bytes)) == 1); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendERKS3_(reinterpret_cast(storage.bytes), reinterpret_cast(source.bytes)) == 0); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(reinterpret_cast(storage.bytes)), "betabeta") == 0); + + Storage emptySource; + std::memset(emptySource.bytes, kFill, sizeof(emptySource.bytes)); + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(emptySource.bytes), &sourceContext); + Require(_ZN3sce2Np9CppWebApi6Common6String8copyFromERKS3_(reinterpret_cast(storage.bytes), reinterpret_cast(emptySource.bytes)) == 0); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + + Storage contextLess; + std::memset(contextLess.bytes, kFill, sizeof(contextLess.bytes)); + _ZN3sce2Np9CppWebApi6Common6StringC1Ev(reinterpret_cast(contextLess.bytes)); + Require(Load(contextLess, kLibContextOffset) == nullptr); + Require(_ZN3sce2Np9CppWebApi6Common6String8copyFromERKS3_(reinterpret_cast(contextLess.bytes), reinterpret_cast(source.bytes)) == 0); + Require(Load(contextLess, kLibContextOffset) == &sourceContext); + + Storage emptyContextLess; + std::memset(emptyContextLess.bytes, kFill, sizeof(emptyContextLess.bytes)); + _ZN3sce2Np9CppWebApi6Common6StringC1Ev(reinterpret_cast(emptyContextLess.bytes)); + Require(_ZN3sce2Np9CppWebApi6Common6String8copyFromERKS3_(reinterpret_cast(emptyContextLess.bytes), reinterpret_cast(emptySource.bytes)) == 0); + Require(Load(emptyContextLess, kLibContextOffset) == &sourceContext); + Require(Load(emptyContextLess, kBufferOffset) == nullptr); + + _ZN3sce2Np9CppWebApi6Common6String10setContextEPNS2_10LibContextE(reinterpret_cast(contextLess.bytes), &libContext); + Require(Load(contextLess, kLibContextOffset) == &libContext); + Require(Load(contextLess, kReferenceCountOffset) == 0); + Require(_ZN3sce2Np9CppWebApi6Common6String21intrusive_ptr_add_refEPS3_(reinterpret_cast(contextLess.bytes)) == 1); + Require(_ZN3sce2Np9CppWebApi6Common6String21intrusive_ptr_add_refEPS3_(reinterpret_cast(contextLess.bytes)) == 2); + Require(_ZN3sce2Np9CppWebApi6Common6String21intrusive_ptr_sub_refEPS3_(reinterpret_cast(contextLess.bytes)) == 2); + Require(Load(contextLess, kReferenceCountOffset) == 1); + + _ZN3sce2Np9CppWebApi6Common6String5clearEv(reinterpret_cast(storage.bytes)); + Require(Load(storage, kBufferOffset) == nullptr); + Require(Load(storage, kBufferSizeOffset) == 0); + Require(_ZNK3sce2Np9CppWebApi6Common6String5emptyEv(reinterpret_cast(storage.bytes)) == 1); + Require(_ZN3sce2Np9CppWebApi6Common6StringeqERKS3_(reinterpret_cast(storage.bytes), reinterpret_cast(emptySource.bytes)) == 1); + + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(storage.bytes)); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(source.bytes)); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(emptySource.bytes)); + _ZN3sce2Np9CppWebApi6Common6StringD2Ev(reinterpret_cast(contextLess.bytes)); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(emptyContextLess.bytes)); + Require(GuardIntact(storage) && GuardIntact(source)); +} + void TestVectorPushBackCopiesStringContent() { std::uint64_t libContext = 1; VectorStorage vector; @@ -149,6 +247,9 @@ void TestVectorPushBackCopiesStringContent() { _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(reinterpret_cast(vector.bytes), &libContext); Require(VectorGuardIntact(vector)); + std::int32_t referenceCount; + std::memcpy(&referenceCount, vector.bytes + kVectorReferenceCountOffset, sizeof(referenceCount)); + Require(referenceCount == 0); _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(element.bytes), &libContext); Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(element.bytes), "privilege") == 0); @@ -178,6 +279,93 @@ void TestVectorPushBackCopiesStringContent() { Require(VectorGuardIntact(vector)); } +void TestVectorIterationVisitsStringsInOrder() { + std::uint64_t libContext = 1; + VectorStorage vector; + IteratorStorage first; + IteratorStorage last; + std::memset(vector.bytes, kFill, sizeof(vector.bytes)); + std::memset(first.bytes, kFill, sizeof(first.bytes)); + std::memset(last.bytes, kFill, sizeof(last.bytes)); + auto* self = reinterpret_cast(vector.bytes); + auto* firstIterator = reinterpret_cast(first.bytes); + auto* lastIterator = reinterpret_cast(last.bytes); + + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEEC1EPNS2_10LibContextE(self, &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE5beginEv(firstIterator, self) == firstIterator); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE3endEv(lastIterator, self) == lastIterator); + Require(!_ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEneERKS5_(firstIterator, lastIterator)); + + const char* const texts[] = {"first", "second", "third"}; + for (const char* text : texts) { + Storage element; + std::memset(element.bytes, kFill, sizeof(element.bytes)); + _ZN3sce2Np9CppWebApi6Common6StringC1EPNS2_10LibContextE(reinterpret_cast(element.bytes), &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6String6appendEPKc(reinterpret_cast(element.bytes), text) == 0); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE8pushBackERKS4_(self, element.bytes) == 0); + _ZN3sce2Np9CppWebApi6Common6StringD1Ev(reinterpret_cast(element.bytes)); + } + + std::memset(first.bytes, kFill, sizeof(first.bytes)); + std::memset(last.bytes, kFill, sizeof(last.bytes)); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE5beginEv(firstIterator, self); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE3endEv(lastIterator, self); + Require(LoadIterator(first, kIteratorReferenceCountOffset) == 0); + Require(LoadIterator(last, kIteratorReferenceCountOffset) == 0); + Require(LoadIterator(last, kIteratorElementOffset) - LoadIterator(first, kIteratorElementOffset) == 3 * kStringSize); + Require(IteratorGuardIntact(first) && IteratorGuardIntact(last)); + + std::size_t visited = 0; + while (_ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEneERKS5_(firstIterator, lastIterator)) { + Require(visited < 3); + const CommonString* element = _ZNK3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEdeEv(firstIterator); + Require(static_cast(element) == LoadIterator(first, kIteratorElementOffset)); + Require(std::strcmp(_ZNK3sce2Np9CppWebApi6Common6String5c_strEv(element), texts[visited]) == 0); + Require(_ZN3sce2Np9CppWebApi6Common8IteratorINS2_6StringEEppEv(firstIterator) == firstIterator); + ++visited; + } + Require(visited == 3); + Require(IteratorGuardIntact(first)); + + _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_6StringEED2Ev(firstIterator); + _ZN3sce2Np9CppWebApi6Common13ConstIteratorINS2_6StringEED2Ev(lastIterator); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEED1Ev(self); + Require(VectorGuardIntact(vector)); +} + +bool BeginThrows(const IntrusivePtr* vector) { + IteratorStorage iterator; + try { + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE5beginEv(reinterpret_cast(iterator.bytes), vector); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +bool EndThrows(const IntrusivePtr* vector) { + IteratorStorage iterator; + try { + _ZN3sce2Np9CppWebApi6Common6VectorINS2_6StringEE3endEv(reinterpret_cast(iterator.bytes), vector); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + +void TestVectorIterationRejectsOtherBodies() { + std::uint64_t libContext = 1; + VectorStorage vector; + std::memset(vector.bytes, kFill, sizeof(vector.bytes)); + auto* self = reinterpret_cast(vector.bytes); + const IntrusivePtr empty{nullptr, nullptr, &libContext}; + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEEC1EPNS2_10LibContextE(self, &libContext); + Require(_ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEE8pushBackERKS8_(self, &empty) == 0); + Require(BeginThrows(self)); + Require(EndThrows(self)); + _ZN3sce2Np9CppWebApi6Common6VectorINS2_12IntrusivePtrINS1_14SessionManager2V112JoinableUserEEEED1Ev(self); +} + void TestStringEmptyAndEquals() { std::uint64_t libContext = 1; Storage storage; @@ -327,7 +515,10 @@ int main(int argc, char** argv) { TestAppendBuildsAndClearsBuffer(); TestAppendNullKeepsEmpty(); TestVectorPushBackCopiesStringContent(); + TestVectorIterationVisitsStringsInOrder(); + TestVectorIterationRejectsOtherBodies(); TestStringEmptyAndEquals(); + TestStringCopyFromAndObservers(); TestIntrusivePtrVectorPushBack(); TestOfflineResponses(); } diff --git a/core/libs/tests/GuestNpFriendListDialog.cpp b/core/libs/tests/GuestNpFriendListDialog.cpp index b71297ccdc..10f738abb4 100644 --- a/core/libs/tests/GuestNpFriendListDialog.cpp +++ b/core/libs/tests/GuestNpFriendListDialog.cpp @@ -4,6 +4,7 @@ #include "prx/libSceIme/include/PsnDialogNotice.hpp" #include #include +#include namespace { @@ -51,7 +52,8 @@ void RequireNotices() { int main() { std::int64_t param = 0; - std::int32_t result[2] = {-1, -1}; + std::int32_t result[2]; + std::memset(result, 0x5a, sizeof(result)); Require(sceNpFriendListDialogTerminate() == kErrNotInitialized); Require(sceNpFriendListDialogOpenA(nullptr) == kErrNotInitialized); @@ -68,7 +70,10 @@ int main() { Require(sceNpFriendListDialogUpdateStatus() == kStatusInitialized); Require(sceNpFriendListDialogOpenA(nullptr) == kErrArgNull); + Require(sceNpFriendListDialogGetResultA(nullptr) == kErrArgNull); Require(sceNpFriendListDialogGetResultA(result) == kErrNotFinished); + Require(result[0] == 0x5a5a5a5a); + Require(result[1] == 0x5a5a5a5a); Require(sceNpFriendListDialogOpenA(¶m) == 0); Require(sceNpFriendListDialogGetStatus() == kStatusFinished); @@ -77,7 +82,7 @@ int main() { Require(sceNpFriendListDialogGetResultA(nullptr) == kErrArgNull); Require(sceNpFriendListDialogGetResultA(result) == 0); Require(result[0] == kResultUserCanceled); - Require(result[1] == -1); + Require(result[1] == 0x5a5a5a5a); Require(sceNpFriendListDialogClose() == 0); Require(sceNpFriendListDialogTerminate() == 0); @@ -94,9 +99,10 @@ int main() { Require(sceNpFriendListDialogUpdateStatus() == kStatusInitialized); Require(sceNpFriendListDialogClose() == 0); Require(sceNpFriendListDialogUpdateStatus() == kStatusFinished); - result[0] = -1; + std::memset(result, 0x5a, sizeof(result)); Require(sceNpFriendListDialogGetResultA(result) == 0); Require(result[0] == kResultUserCanceled); + Require(result[1] == 0x5a5a5a5a); Require(sceNpFriendListDialogTerminate() == 0); Require(sceNpFriendListDialogTerminate() == kErrNotInitialized); Require(sceNpFriendListDialogOpenA(nullptr) == kErrNotInitialized); diff --git a/core/libs/tests/GuestNpIntegerRadices.cpp b/core/libs/tests/GuestNpIntegerRadices.cpp new file mode 100644 index 0000000000..17a5b2061d --- /dev/null +++ b/core/libs/tests/GuestNpIntegerRadices.cpp @@ -0,0 +1,69 @@ +#include "prx/libSceNpCommon/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (!value) { + std::fprintf(stderr, "check failed at line %u\n", location.line()); + std::abort(); + } +} + +template +static void Check(TValue value, std::uint32_t radix, std::uint32_t flags, TFormat format) { + std::array reference{}; + const auto converted = std::to_chars(reference.data(), reference.data() + reference.size(), value, radix); + Require(converted.ec == std::errc{}); + std::string expected(reference.data(), converted.ptr); + std::string sign; + if (expected.front() == '-') { sign = "-"; expected.erase(0, 1); } + else if (value != 0 && (flags & 4)) sign = "+"; + if (flags & 1) { + for (char& c : expected) if (c >= 'a' && c <= 'z') c = static_cast(c - 'a' + 'A'); + } + if ((flags & 2) && (radix == 8 || radix == 16)) expected.insert(0, radix == 8 ? "0" : (flags & 1) ? "0X" : "0x"); + expected.insert(0, sign); + std::size_t length = 999; + Require(format(nullptr, 0, value, radix, flags, &length) == 0 && length == expected.size()); + std::array output{}; + for (const auto capacity : {length, length + 1}) { + output.fill('!'); + std::size_t actual = 777; + const int status = format(output.data() + 1, capacity, value, radix, flags, &actual); + if (capacity == length) { + Require(status == static_cast(0x80558008) && actual == 777); + Require(std::all_of(output.begin(), output.end(), [](char c) { return c == '!'; })); + } else { + Require(status == 0 && actual == expected.size()); + Require(std::string(output.data() + 1) == expected); + Require(output.front() == '!' && output[capacity + 1] == '!'); + } + } +} + +int main() { + for (std::uint32_t radix = 2; radix <= 36; ++radix) { + for (std::uint32_t flags = 0; flags < 8; ++flags) { + for (std::uint64_t value : {std::uint64_t{0}, std::uint64_t{1}, std::uint64_t{35}, std::numeric_limits::max()}) + Check(value, radix, flags, sceNpUInt64ToStr); + for (std::uint32_t value : {std::uint32_t{0}, std::uint32_t{1}, std::numeric_limits::max()}) + Check(value, radix, flags, sceNpUInt32ToStr); + for (std::int32_t value : {0, 1, -1, std::numeric_limits::min(), std::numeric_limits::max()}) + Check(value, radix, flags, sceNpInt32ToStr); + for (std::int64_t value : {std::int64_t{0}, std::int64_t{1}, std::numeric_limits::max()}) + Check(value, radix, flags, sceNpInt64ToStr); + if (flags & 4) { + Check(std::int64_t{-1}, radix, flags, sceNpInt64ToStr); + Check(std::numeric_limits::min(), radix, flags, sceNpInt64ToStr); + } + } + } +} diff --git a/core/libs/tests/GuestNpManager.cpp b/core/libs/tests/GuestNpManager.cpp index b467b90a8b..61724a38b8 100644 --- a/core/libs/tests/GuestNpManager.cpp +++ b/core/libs/tests/GuestNpManager.cpp @@ -1,4 +1,5 @@ #include "prx/libSceNpManager/include/Api.hpp" +#include "prx/libSceNpManager/include/InGameMessageIds.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include @@ -23,6 +24,8 @@ constexpr int IdMapperRefusedArgument = static_cast(0x80553083u); constexpr int IdMapperRefusedRequestId = static_cast(0x80553085u); constexpr int ManagerIntTableFull = static_cast(0x80559D05u); constexpr int ManagerIntNotInTable = static_cast(0x80559D0Au); +constexpr int InGameMessageContextNotFound = static_cast(0x80552B03u); +constexpr int InGameMessageHandleNotFound = static_cast(0x80552B05u); void Require(bool condition, const char* message) { if (!condition) { @@ -501,7 +504,18 @@ void CheckUserIdQueries() { 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(sceNpSetGamePresenceOnlineA(request, userId, 0) == SignedOut, "sceNpSetGamePresenceOnlineA must report NP as signed out"); } + NpCreateAsyncRequestParameter asyncParam{}; + const int asyncRequest = sceNpCreateAsyncRequest(&asyncParam); + Require(asyncRequest > 0, "sceNpCreateAsyncRequest must return a positive request id"); + Require(sceNpSetGamePresenceOnlineA(asyncRequest, InitialUserId, 0) == SignedOut, "sceNpSetGamePresenceOnlineA must report NP as signed out"); + Require(sceNpDeleteRequest(asyncRequest) == 0, "sceNpDeleteRequest failed"); + Require(sceNpSetGamePresenceOnlineA(asyncRequest, InitialUserId, 0) == RequestNotFound, "sceNpSetGamePresenceOnlineA must reject a deleted request"); + Require(sceNpSetGamePresenceOnlineA(request, InvalidUserId, 0) == InvalidArgument, "sceNpSetGamePresenceOnlineA must reject the invalid user id"); + Require(sceNpSetGamePresenceOnlineA(0, InitialUserId, 0) == InvalidArgument, "sceNpSetGamePresenceOnlineA must reject request id 0"); + Require(sceNpSetGamePresenceOnlineA(NeverCreated, InitialUserId, 0) == RequestNotFound, "sceNpSetGamePresenceOnlineA must reject a request that was never created"); + Require(sceNpSetGamePresenceOnlineA(NeverCreated, InvalidUserId, 0) == InvalidArgument, "sceNpSetGamePresenceOnlineA must check the user before the request"); 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"); @@ -523,6 +537,7 @@ void CheckUserIdQueries() { 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(sceNpSetGamePresenceOnlineA(request, InitialUserId, 0) == RequestNotFound, "sceNpSetGamePresenceOnlineA 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; @@ -683,5 +698,34 @@ int main() { std::memset(&untouchedPremiumResult, 0x5a, sizeof(untouchedPremiumResult)); Require(sceNpCheckPremium(1, &premiumParameter, &premiumResult) == SignedOut, "sceNpCheckPremium must report the user as signed out"); Require(std::memcmp(&premiumResult, &untouchedPremiumResult, sizeof(premiumResult)) == 0, "sceNpCheckPremium must leave the result untouched"); + const int messageContext = sceNpInGameMessageInitialize(0x4000, nullptr); + Require(messageContext > 0, "sceNpInGameMessageInitialize must return a context id"); + const int otherMessageContext = sceNpInGameMessageInitialize(0x4000, nullptr); + Require(otherMessageContext > 0 && otherMessageContext != messageContext, "sceNpInGameMessageInitialize must return a new id for each context"); + Require(sceNpInGameMessageCreateHandle(-1) == InGameMessageContextNotFound, "sceNpInGameMessageCreateHandle must reject an unknown context"); + const int messageHandle = sceNpInGameMessageCreateHandle(messageContext); + Require(messageHandle > 0, "sceNpInGameMessageCreateHandle must return a handle id"); + const int secondMessageHandle = sceNpInGameMessageCreateHandle(messageContext); + Require(secondMessageHandle > 0 && secondMessageHandle != messageHandle, "sceNpInGameMessageCreateHandle must return a new id for each handle"); + Require(sceNpInGameMessageDeleteHandle(messageContext, secondMessageHandle) == 0, "sceNpInGameMessageDeleteHandle must delete the second handle"); + Require(sceNpInGameMessageDeleteHandle(otherMessageContext, messageHandle) == InGameMessageHandleNotFound, "a handle must belong to the context that created it"); + Require(sceNpInGameMessageAbortHandle(-1, messageHandle) == InGameMessageContextNotFound, "sceNpInGameMessageAbortHandle must reject an unknown context"); + Require(sceNpInGameMessageAbortHandle(messageContext, -1) == InGameMessageHandleNotFound, "sceNpInGameMessageAbortHandle must reject an unknown handle"); + Require(sceNpInGameMessageAbortHandle(messageContext, messageHandle) == 0, "sceNpInGameMessageAbortHandle must accept a live handle"); + Require(sceNpInGameMessageDeleteHandle(messageContext, messageHandle) == 0, "sceNpInGameMessageDeleteHandle must delete the handle"); + Require(sceNpInGameMessageDeleteHandle(messageContext, messageHandle) == InGameMessageHandleNotFound, "a deleted handle must not be found"); + Require(sceNpInGameMessageTerminate(messageContext) == 0, "sceNpInGameMessageTerminate must end the context"); + Require(sceNpInGameMessageTerminate(messageContext) == InGameMessageContextNotFound, "a terminated context must not be found"); + Require(sceNpInGameMessageCreateHandle(messageContext) == InGameMessageContextNotFound, "a terminated context must not create handles"); + Require(sceNpInGameMessageTerminate(otherMessageContext) == 0, "sceNpInGameMessageTerminate must end the second context"); + std::int32_t nextMessageId = std::numeric_limits::max() - 1; + Require(TakeInGameMessageId(nextMessageId, "ids") == std::numeric_limits::max() - 1, "the last in-game message id below the limit must be handed out"); + try { + TakeInGameMessageId(nextMessageId, "ids"); + Require(false, "the in-game message ids must not pass the 32-bit limit"); + } catch (const std::runtime_error& error) { + Require(std::string(error.what()) == "ids: the in-game message ids are exhausted", "the exhausted in-game message ids threw"); + } + Require(nextMessageId == std::numeric_limits::max(), "an exhausted in-game message id must not wrap"); return 0; } diff --git a/core/libs/tests/GuestNpMatching2Offline.cpp b/core/libs/tests/GuestNpMatching2Offline.cpp index 67687e16d6..79f12f6d62 100644 --- a/core/libs/tests/GuestNpMatching2Offline.cpp +++ b/core/libs/tests/GuestNpMatching2Offline.cpp @@ -1,9 +1,16 @@ #include "prx/libSceNpMatching2/include/Api.hpp" #include "SceTypes.hpp" +#include #include #include +#include #include #include +#ifdef _WIN32 +#include +#else +#include +#endif @@ -18,6 +25,14 @@ constexpr int ContextMax = static_cast(0x80550C04u); constexpr int ContextNotStarted = static_cast(0x80550C08u); constexpr int InvalidArgument = static_cast(0x80550C0Au); constexpr int InvalidContextId = static_cast(0x80550C0Bu); +constexpr int ContextNotFound = static_cast(0x80550C06u); +constexpr int InvalidServerId = static_cast(0x80550C0Cu); +constexpr int InvalidRoomId = static_cast(0x80550C0Fu); +constexpr int ServerNotAvailable = static_cast(0x80550C28u); +constexpr int SessionNotFound = static_cast(0x80550C3Au); +constexpr int SignalingContextNotFound = static_cast(0x80550E05u); +constexpr int SignalingInvalidArgument = static_cast(0x80550E15u); +constexpr int SignalingPeerNotFound = static_cast(0x80550E19u); constexpr std::int32_t ConnStatusInactive = 0; constexpr std::int32_t StatusSentinel = 0x5A5A5A5A; constexpr std::uint32_t PeerAddrSentinel = 0xA5A5A5A5u; @@ -31,6 +46,104 @@ void Require(bool value, const char* name, const char* what) { ++failures; } +using CreateContextAFn = decltype(&sceNpMatching2CreateContextA); +using RegisterLobbyCallbackFn = int (APS5_VABI*)(std::uint16_t, void*, void*); +using RequestFn = int (APS5_VABI*)(std::uint16_t, const void*, const void*, std::uint32_t*); +using GetServerIdFn = int (APS5_VABI*)(std::uint16_t, std::uint16_t*); +using SlotMaskFn = int (APS5_VABI*)(std::uint16_t, std::uint64_t, std::uint64_t*); +using WorldIdsFn = int (APS5_VABI*)(std::uint16_t, void*, std::uint64_t, void*); +using ConnectionInfoFn = int (APS5_VABI*)(std::uint16_t, std::uint64_t, std::uint16_t, std::int32_t, void*); +using GetPortFn = int (APS5_VABI*)(std::uint16_t*); +using SetPortFn = int (APS5_VABI*)(std::uint16_t); + +template +Function Resolve(const char* name) { +#ifdef _WIN32 + void* symbol = reinterpret_cast(GetProcAddress(GetModuleHandleA("libSceNpMatching2.prx"), name)); +#else + void* symbol = dlsym(RTLD_DEFAULT, name); +#endif + if (!symbol) { + std::fprintf(stderr, "%s is not exported by libSceNpMatching2\n", name); + std::abort(); + } + return reinterpret_cast(symbol); +} + +void CheckOfflineRequests(CreateContextAFn createContextA) { + const auto getWorldInfoList = Resolve("sceNpMatching2GetWorldInfoList"); + const auto setUserInfo = Resolve("sceNpMatching2SetUserInfo"); + const auto setRoomDataExternal = Resolve("sceNpMatching2SetRoomDataExternal"); + const auto setRoomDataInternal = Resolve("sceNpMatching2SetRoomDataInternal"); + const auto getRoomDataInternal = Resolve("sceNpMatching2GetRoomDataInternal"); + const auto getPingInfo = Resolve("sceNpMatching2SignalingGetPingInfo"); + const auto leaveRoom = Resolve("sceNpMatching2LeaveRoom"); + const auto getServerId = Resolve("sceNpMatching2GetServerId"); + const auto getSlotMask = Resolve("sceNpMatching2GetRoomJoinedSlotMaskLocal"); + const auto getWorldIds = Resolve("sceNpMatching2GetWorldIdArrayForAllServers"); + const auto getConnectionInfo = Resolve("sceNpMatching2SignalingGetConnectionInfo"); + const auto getConnectionInfoA = Resolve("sceNpMatching2SignalingGetConnectionInfoA"); + const auto getPort = Resolve("sceNpMatching2SignalingGetPort"); + const auto setPort = Resolve("sceNpMatching2SignalingSetPort"); + unsigned char serverRequest[32] = {1}; + unsigned char roomRequest[64] = {5}; + unsigned char zeroed[64] = {}; + std::uint32_t requestId = 77; + + Require(getWorldInfoList(1, serverRequest, nullptr, &requestId) == NotInitialized && requestId == 77, "sceNpMatching2GetWorldInfoList", "a request before initialization did not return NOT_INITIALIZED"); + NpMatching2InitializeParameter init{}; + init.size = 0x30; + NpMatching2CreateContextParameter create{}; + create.size = 0x28; + NpMatching2CreateContextParameterA createA{1, 0, 0x10}; + std::uint16_t context = 0; + std::uint16_t contextA = 0; + Require(sceNpMatching2Initialize(&init) == 0, "sceNpMatching2Initialize", "initializing for the requests failed"); + Require(sceNpMatching2CreateContext(&create, &context) == 0 && context == 1, "sceNpMatching2CreateContext", "the request context is not id 1"); + Require(createContextA(&createA, &contextA) == 0 && contextA == 0x102, "sceNpMatching2CreateContextA", "the request A context is not id 0x102"); + + Require(getWorldInfoList(context, nullptr, nullptr, &requestId) == InvalidArgument, "sceNpMatching2GetWorldInfoList", "a null request was taken"); + Require(getWorldInfoList(context, serverRequest, nullptr, nullptr) == InvalidArgument, "sceNpMatching2GetWorldInfoList", "a null request id was taken"); + for (const RequestFn request : {getWorldInfoList, setUserInfo}) { + Require(request(context, zeroed, nullptr, &requestId) == InvalidServerId, "server request", "server id 0 did not return INVALID_SERVER_ID"); + Require(request(99, zeroed, nullptr, &requestId) == InvalidServerId, "server request", "the server id was not checked before the context"); + Require(request(99, serverRequest, nullptr, &requestId) == ContextNotFound && request(0, serverRequest, nullptr, &requestId) == ContextNotFound, "server request", "an unknown context did not return CONTEXT_NOT_FOUND"); + } + for (const RequestFn request : {setRoomDataExternal, setRoomDataInternal, getRoomDataInternal, getPingInfo, leaveRoom}) { + Require(request(context, zeroed, nullptr, &requestId) == InvalidRoomId, "room request", "room id 0 did not return INVALID_ROOM_ID"); + Require(request(99, zeroed, nullptr, &requestId) == InvalidRoomId, "room request", "the room id was not checked before the context"); + Require(request(99, roomRequest, nullptr, &requestId) == ContextNotFound, "room request", "an unknown context did not return CONTEXT_NOT_FOUND"); + } + Require(requestId == 77, "request", "a refused request wrote a request id"); + Require(leaveRoom(context, roomRequest, nullptr, &requestId) == SessionNotFound && leaveRoom(contextA, roomRequest, nullptr, &requestId) == SessionNotFound && requestId == 77, "sceNpMatching2LeaveRoom", "leaving with no room joined did not return SESSION_NOT_FOUND"); + + std::uint16_t serverId = 0xA5A5; + Require(getServerId(99, nullptr) == InvalidArgument, "sceNpMatching2GetServerId", "a null output was not refused before the context"); + Require(getServerId(99, &serverId) == ContextNotFound, "sceNpMatching2GetServerId", "an unknown context did not return CONTEXT_NOT_FOUND"); + Require(getServerId(context, &serverId) == ContextNotStarted && getServerId(contextA, &serverId) == ContextNotStarted && serverId == 0xA5A5, "sceNpMatching2GetServerId", "a never-started context did not return CONTEXT_NOT_STARTED without writing"); + std::uint64_t mask = 0xA5A5A5A5A5A5A5A5ull; + Require(getSlotMask(99, 5, nullptr) == InvalidArgument, "sceNpMatching2GetRoomJoinedSlotMaskLocal", "a null mask was not refused before the context"); + Require(getSlotMask(context, 0, &mask) == InvalidServerId && getSlotMask(contextA, 0, &mask) == InvalidServerId, "sceNpMatching2GetRoomJoinedSlotMaskLocal", "room 0 did not return 0x80550C0C"); + Require(getSlotMask(99, 0, &mask) == ContextNotFound, "sceNpMatching2GetRoomJoinedSlotMaskLocal", "room 0 was checked before the context"); + Require(getSlotMask(99, 5, &mask) == ContextNotFound, "sceNpMatching2GetRoomJoinedSlotMaskLocal", "an unknown context did not return CONTEXT_NOT_FOUND"); + Require(getSlotMask(context, 5, &mask) == ContextNotStarted && mask == 0xA5A5A5A5A5A5A5A5ull, "sceNpMatching2GetRoomJoinedSlotMaskLocal", "a never-started context did not return CONTEXT_NOT_STARTED without writing"); + std::uint32_t worldIds[4] = {0xA5A5A5A5u, 0xA5A5A5A5u, 0xA5A5A5A5u, 0xA5A5A5A5u}; + std::uint32_t worldIdCount = 0xA5A5A5A5u; + Require(getWorldIds(99, nullptr, 0, nullptr) == ContextNotFound, "sceNpMatching2GetWorldIdArrayForAllServers", "the context was not checked before the pointers"); + Require(getWorldIds(context, nullptr, 0, nullptr) == InvalidArgument, "sceNpMatching2GetWorldIdArrayForAllServers", "null pointers were taken"); + Require(getWorldIds(context, worldIds, 4, &worldIdCount) == ServerNotAvailable && worldIds[0] == 0xA5A5A5A5u && worldIdCount == 0xA5A5A5A5u, "sceNpMatching2GetWorldIdArrayForAllServers", "a never-started context did not return SERVER_NOT_AVAILABLE without writing"); + unsigned char info[64] = {}; + for (const ConnectionInfoFn connectionInfo : {getConnectionInfo, getConnectionInfoA}) { + Require(connectionInfo(99, 5, 1, 1, nullptr) == SignalingInvalidArgument, "connection info", "a null info was not refused with 0x80550E15 before the context"); + Require(connectionInfo(99, 5, 1, 1, info) == SignalingContextNotFound, "connection info", "an unknown context did not return 0x80550E05"); + Require(connectionInfo(context, 5, 1, 1, info) == SignalingPeerNotFound, "connection info", "a never-started context did not return 0x80550E19"); + } + std::uint16_t port = 0xA5A5; + Require(getPort(&port) == 0 && port == 0, "sceNpMatching2SignalingGetPort", "the port did not start at 0"); + Require(setPort(3659) == 0 && getPort(&port) == 0 && port == 3659, "sceNpMatching2SignalingSetPort", "the port that was set is not reported"); + Require(sceNpMatching2Terminate() == 0, "sceNpMatching2Terminate", "terminating after the requests failed"); +} + bool NullStatusThrows(std::uint16_t ctxId) { try { sceNpMatching2SignalingGetConnectionStatus(ctxId, 1, 1, nullptr, nullptr, nullptr); @@ -43,9 +156,13 @@ bool NullStatusThrows(std::uint16_t ctxId) { } int main() { + const auto createContextA = Resolve("sceNpMatching2CreateContextA"); + const auto registerLobbyEvent = Resolve("sceNpMatching2RegisterLobbyEventCallback"); + const auto registerLobbyMessage = Resolve("sceNpMatching2RegisterLobbyMessageCallback"); NpMatching2InitializeParameter init{}; NpMatching2CreateContextParameter create{}; create.size = 0x28; + NpMatching2CreateContextParameterA createA{1, 0, 0x10}; unsigned char requestOpt[24] = {}; std::uint16_t context = 77; @@ -61,6 +178,9 @@ int main() { 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(createContextA(nullptr, nullptr) == NotInitialized, "sceNpMatching2CreateContextA", "a call before initialization did not return NOT_INITIALIZED first"); + Require(registerLobbyEvent(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterLobbyEventCallback", "a call before initialization did not return NOT_INITIALIZED"); + Require(registerLobbyMessage(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterLobbyMessageCallback", "a call before initialization did not return NOT_INITIALIZED"); Require(sceNpMatching2SetDefaultRequestOptParam(1, nullptr) == NotInitialized, "sceNpMatching2SetDefaultRequestOptParam", "a call before initialization did not return NOT_INITIALIZED first"); Require(sceNpMatching2Terminate() == NotInitialized, "sceNpMatching2Terminate", "terminating before initialization did not return NOT_INITIALIZED"); Require(sceNpMatching2Initialize(nullptr) == InvalidArgument, "sceNpMatching2Initialize", "a null parameter did not return INVALID_ARGUMENT"); @@ -120,6 +240,23 @@ int main() { 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(registerLobbyEvent(99, nullptr, nullptr) == InvalidContextId, "sceNpMatching2RegisterLobbyEventCallback", "an unknown context was taken"); + Require(registerLobbyEvent(1, nullptr, nullptr) == 0, "sceNpMatching2RegisterLobbyEventCallback", "registering failed"); + Require(registerLobbyMessage(99, nullptr, nullptr) == InvalidContextId, "sceNpMatching2RegisterLobbyMessageCallback", "an unknown context was taken"); + Require(registerLobbyMessage(1, nullptr, nullptr) == 0, "sceNpMatching2RegisterLobbyMessageCallback", "registering failed"); + created = 77; + Require(createContextA(nullptr, &created) == InvalidArgument, "sceNpMatching2CreateContextA", "a null parameter was taken"); + createA.size = 0x28; + Require(createContextA(&createA, &created) == InvalidArgument, "sceNpMatching2CreateContextA", "parameter size 0x28 was taken"); + createA.size = 0x10; + Require(createContextA(&createA, nullptr) == InvalidArgument, "sceNpMatching2CreateContextA", "a null output was taken"); + Require(created == 77, "sceNpMatching2CreateContextA", "a refused call wrote the context id"); + Require(createContextA(&createA, &created) == 0 && created == 0x102, "sceNpMatching2CreateContextA", "the freed slot 2 was not handed out as A context 0x102"); + created = 77; + Require(createContextA(&createA, &created) == ContextMax && created == 77, "sceNpMatching2CreateContextA", "an eleventh context did not return CONTEXT_MAX"); + Require(sceNpMatching2ContextStop(0x102) == ContextNotStarted, "sceNpMatching2CreateContextA", "the context it created is not a regular context"); + Require(sceNpMatching2DestroyContext(0x102) == 0, "sceNpMatching2CreateContextA", "the A context was not destroyed"); + Require(sceNpMatching2CreateContext(&create, &created) == 0 && created == 2, "sceNpMatching2CreateContextA", "destroying the A context did not free slot 2"); 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"); @@ -139,6 +276,10 @@ int main() { Require(sceNpMatching2ContextStop(1) == NotInitialized, "sceNpMatching2ContextStop", "a call after termination did not return NOT_INITIALIZED"); Require(sceNpMatching2RegisterContextCallback(nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterContextCallback", "a call after termination did not return NOT_INITIALIZED"); Require(sceNpMatching2RegisterSignalingCallback(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterSignalingCallback", "a call after termination did not return NOT_INITIALIZED"); + Require(registerLobbyEvent(1, nullptr, nullptr) == NotInitialized, "sceNpMatching2RegisterLobbyEventCallback", "a call after termination did not return NOT_INITIALIZED"); + Require(createContextA(&createA, &created) == NotInitialized, "sceNpMatching2CreateContextA", "a call after termination did not return NOT_INITIALIZED"); + + CheckOfflineRequests(createContextA); if (failures != 0) return 1; std::puts("NpMatching2 offline tests passed"); diff --git a/core/libs/tests/GuestNpProfileDialog.cpp b/core/libs/tests/GuestNpProfileDialog.cpp index 49ba4eb805..2fb03b3baf 100644 --- a/core/libs/tests/GuestNpProfileDialog.cpp +++ b/core/libs/tests/GuestNpProfileDialog.cpp @@ -176,6 +176,7 @@ int main() { Require(sceNpProfileDialogGetStatus() == kStatusFinished); Require(sceNpProfileDialogUpdateStatus() == kStatusFinished); Require(sceNpProfileDialogGetResult(nullptr) == kErrArgNull); + Require(sceNpProfileDialogGetResult(reinterpret_cast(0x10)) == static_cast(0x80B8000Au)); Require(sceNpProfileDialogGetResult(reinterpret_cast(result)) == 0); RequireCanceled(result, &first); RequireThrows([] { sceNpProfileDialogClose(); }); diff --git a/core/libs/tests/GuestNpScoreOffline.cpp b/core/libs/tests/GuestNpScoreOffline.cpp index 15cc132c95..285a181019 100644 --- a/core/libs/tests/GuestNpScoreOffline.cpp +++ b/core/libs/tests/GuestNpScoreOffline.cpp @@ -2,7 +2,14 @@ #include "SceTypes.hpp" #include #include +#include #include +#include +#ifdef _WIN32 +#include +#else +#include +#endif namespace { @@ -27,6 +34,42 @@ unsigned char infos[2 * 200]; unsigned char board[24]; unsigned char manyNpIds[101 * 36]; unsigned char manyRanks[101 * 0x88]; +unsigned char ranksA[2 * 0x98]; +std::uint64_t accountIds[2] = {1, 2}; +unsigned char npIdPcIds[2 * 44]; +unsigned char accountIdPcIds[2 * 16]; + +using RankingFn = int (APS5_VABI*)(int, std::uint32_t, const void*, std::uint64_t, void*, std::uint64_t, void*, std::uint64_t, void*, std::uint64_t, std::uint64_t, void*, void*, void*); +using AccountRankingFn = int (APS5_VABI*)(int, std::uint32_t, const std::uint64_t*, std::uint64_t, void*, std::uint64_t, void*, std::uint64_t, void*, std::uint64_t, std::uint64_t, void*, void*, void*); +using RangeFn = int (APS5_VABI*)(int, std::uint32_t, std::uint32_t, void*, std::uint64_t, void*, std::uint64_t, void*, std::uint64_t, std::uint64_t, void*, void*, void*); +using FriendsFn = int (APS5_VABI*)(int, std::uint32_t, int, void*, std::uint64_t, void*, std::uint64_t, void*, std::uint64_t, std::uint64_t, void*, void*, const NpScoreFriendRankingOption*); +using GameDataFn = int (APS5_VABI*)(int, std::uint32_t, const void*, std::uint64_t*, std::uint64_t, void*, void*); +using GameDataByAccountIdFn = int (APS5_VABI*)(int, std::uint32_t, std::uint64_t, std::uint64_t*, std::uint64_t, void*, void*); +using BoardInfoFn = int (APS5_VABI*)(int, std::uint32_t, void*, void*); +using CreateTitleCtxAFn = int (APS5_VABI*)(std::uint32_t, int); +using SetPlayerCharacterIdFn = int (APS5_VABI*)(int, int); +using WaitAsyncFn = int (APS5_VABI*)(int, int*); +using SetTimeoutFn = int (APS5_VABI*)(int, int, int, int, int, int); +using SetThreadParamFn = int (APS5_VABI*)(int, std::uint64_t); +using CreateTitleCtxFn = int (APS5_VABI*)(const void*, const void*, const void*); +using CensorFn = int (APS5_VABI*)(int, const char*, void*); +using SanitizeFn = int (APS5_VABI*)(int, const char*, char*, void*); +using RecordGameDataFn = int (APS5_VABI*)(int, std::uint32_t, std::int64_t, std::uint64_t, std::uint64_t, const void*, void*); +using ChangeModeFn = int (APS5_VABI*)(int, std::uint32_t); + +template +Function Resolve(const char* name) { +#ifdef _WIN32 + void* symbol = reinterpret_cast(GetProcAddress(GetModuleHandleA("libSceNpScoreRanking.prx"), name)); +#else + void* symbol = dlsym(RTLD_DEFAULT, name); +#endif + if (!symbol) { + std::fprintf(stderr, "%s is not exported by libSceNpScoreRanking\n", name); + std::abort(); + } + return reinterpret_cast(symbol); +} void Require(bool value, const char* name, const char* what) { if (value) return; @@ -43,6 +86,7 @@ bool Untouched() { 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; + for (const unsigned char value : ranksA) if (value != 0xA5) return false; return true; } @@ -179,6 +223,234 @@ void CheckBoard() { } +void CheckRangeAndFriendsA() { + const auto rangeA = Resolve("sceNpScoreGetRankingByRangeA"); + const auto rangeAAsync = Resolve("sceNpScoreGetRankingByRangeAAsync"); + const auto friendsA = Resolve("sceNpScoreGetFriendsRankingA"); + const auto friendsAAsync = Resolve("sceNpScoreGetFriendsRankingAAsync"); + const int req = NewRequest(); + Require(rangeA(req, 1, 0, ranksA, 0x90, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidArgument, "sceNpScoreGetRankingByRangeA", "rank 0 was taken"); + Require(rangeA(req, 1, 1, ranksA, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByRangeA", "a rank array size other than 0x90 per rank was taken"); + Require(rangeA(req, 1, 1, ranksA, 0x120, comments, 64, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByRangeA", "a comment array size other than 64 per rank was taken"); + CheckOffline("sceNpScoreGetRankingByRangeA", rangeA(req, 1, 1, ranksA, 0x120, comments, 128, infos, 400, 2, nullptr, nullptr, nullptr), req); + Require(rangeAAsync(req, 1, 1, ranksA, 0x90, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByRangeAAsync", "valid arguments did not return SIGNED_OUT"); + Require(friendsA(req, 1, 1, nullptr, 0x90, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidArgument, "sceNpScoreGetFriendsRankingA", "a null rank array was taken"); + Require(friendsA(req, 1, 1, ranksA, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetFriendsRankingA", "a rank array size other than 0x90 per friend was taken"); + Require(friendsA(req, 1, 1, ranksA, 0x90, nullptr, 0, infos, 400, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetFriendsRankingA", "an info array size other than 200 per friend was taken"); + CheckOffline("sceNpScoreGetFriendsRankingA", friendsA(req, 1, 1, ranksA, 0x120, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr), req); + Require(friendsAAsync(req, 1, 0, ranksA, 0x90, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetFriendsRankingAAsync", "valid arguments did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); +} + +void CheckAccountIds() { + const auto byAccountId = Resolve("sceNpScoreGetRankingByAccountId"); + const auto byAccountIdAsync = Resolve("sceNpScoreGetRankingByAccountIdAsync"); + const auto byAccountIdPcId = Resolve("sceNpScoreGetRankingByAccountIdPcId"); + const auto byAccountIdPcIdAsync = Resolve("sceNpScoreGetRankingByAccountIdPcIdAsync"); + int req = NewRequest(); + Require(byAccountId(req, 1, accountIds, 16, ranksA, 0x130, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, board) == InvalidArgument, "sceNpScoreGetRankingByAccountId", "an option was taken"); + Require(byAccountId(req, 1, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0, 102, nullptr, nullptr, nullptr) == TooManyNpId, "sceNpScoreGetRankingByAccountId", "102 account ids were not refused before the null arrays"); + Require(byAccountId(req, 1, accountIds, 0, ranksA, 0, nullptr, 0, nullptr, 0, 0, nullptr, nullptr, nullptr) == InsufficientArgument, "sceNpScoreGetRankingByAccountId", "zero account ids were taken"); + Require(byAccountId(req, 1, nullptr, 16, ranksA, 0x130, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == InsufficientArgument, "sceNpScoreGetRankingByAccountId", "a null account id array was taken"); + Require(byAccountId(req, 1, accountIds, 8, ranksA, 0x130, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByAccountId", "an account id array size other than 8 per id was taken"); + Require(byAccountId(req, 1, accountIds, 16, ranksA, 0x110, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByAccountId", "a rank array size other than 0x98 per id was taken"); + Require(byAccountId(req + 100, 1, accountIds, 16, ranksA, 0x130, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidId, "sceNpScoreGetRankingByAccountId", "an unknown request was taken"); + CheckOffline("sceNpScoreGetRankingByAccountId", byAccountId(req, 1, accountIds, 16, ranksA, 0x130, comments, 128, infos, 400, 2, nullptr, nullptr, nullptr), req); + Require(byAccountIdAsync(req, 1, accountIds, 8, ranksA, 0x98, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByAccountIdAsync", "valid arguments did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); + + req = NewRequest(); + Require(byAccountIdPcId(req, 1, accountIdPcIds, 16, ranksA, 0x130, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByAccountIdPcId", "an id array size other than 16 per id was taken"); + Require(byAccountIdPcId(req, 1, accountIdPcIds, 32, ranksA, 0x120, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByAccountIdPcId", "a rank array size other than 0x98 per id was taken"); + CheckOffline("sceNpScoreGetRankingByAccountIdPcId", byAccountIdPcId(req, 1, accountIdPcIds, 32, ranksA, 0x130, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr), req); + Require(byAccountIdPcIdAsync(req, 1, accountIdPcIds, 16, ranksA, 0x98, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByAccountIdPcIdAsync", "valid arguments did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); +} + +void CheckNpIdPcId() { + const auto byNpIdPcId = Resolve("sceNpScoreGetRankingByNpIdPcId"); + const auto byNpIdPcIdAsync = Resolve("sceNpScoreGetRankingByNpIdPcIdAsync"); + const int req = NewRequest(); + Require(byNpIdPcId(req, 1, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0, 102, nullptr, nullptr, nullptr) == InsufficientArgument, "sceNpScoreGetRankingByNpIdPcId", "null arrays were not refused before 102 ids"); + Require(byNpIdPcId(req, 1, npIdPcIds, 102 * 44, manyRanks, 102 * 0x88, nullptr, 0, nullptr, 0, 102, nullptr, nullptr, nullptr) == TooManyNpId, "sceNpScoreGetRankingByNpIdPcId", "102 ids were taken"); + Require(byNpIdPcId(req, 1, npIdPcIds, 72, ranks, 0x110, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByNpIdPcId", "an id array size other than 44 per id was taken"); + Require(byNpIdPcId(req, 1, npIdPcIds, 88, ranks, 0x100, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByNpIdPcId", "a rank array size other than 0x88 per id was taken"); + CheckOffline("sceNpScoreGetRankingByNpIdPcId", byNpIdPcId(req, 1, npIdPcIds, 88, ranks, 0x110, comments, 128, infos, 400, 2, nullptr, nullptr, nullptr), req); + Require(byNpIdPcIdAsync(req, 1, npIdPcIds, 44, ranks, 0x88, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByNpIdPcIdAsync", "valid arguments did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); +} + +void CheckGameDataAndBoard() { + const auto gameData = Resolve("sceNpScoreGetGameData"); + const auto gameDataAsync = Resolve("sceNpScoreGetGameDataAsync"); + const auto byAccountId = Resolve("sceNpScoreGetGameDataByAccountId"); + const auto byAccountIdAsync = Resolve("sceNpScoreGetGameDataByAccountIdAsync"); + const auto boardInfoAsync = Resolve("sceNpScoreGetBoardInfoAsync"); + unsigned char npId[36] = {'p', 'l', 'a', 'y', 'e', 'r'}; + const unsigned char emptyNpId[36] = {}; + std::uint64_t totalSize = 9; + unsigned char data[16]; + int req = NewRequest(); + Require(gameData(req, 1, nullptr, &totalSize, sizeof(data), data, board) == InsufficientArgument, "sceNpScoreGetGameData", "a null NpId was not refused before the option"); + Require(gameData(req, 1, npId, &totalSize, sizeof(data), data, board) == InvalidArgument, "sceNpScoreGetGameData", "an option was taken"); + Require(gameData(req + 100, 1, npId, &totalSize, sizeof(data), data, nullptr) == InvalidId, "sceNpScoreGetGameData", "an unknown request was taken"); + CheckOffline("sceNpScoreGetGameData", gameData(req, 1, npId, &totalSize, sizeof(data), data, nullptr), req); + Require(gameDataAsync(req, 1, npId, &totalSize, sizeof(data), data, nullptr) == SignedOut, "sceNpScoreGetGameDataAsync", "valid arguments did not return SIGNED_OUT"); + Require(totalSize == 9, "sceNpScoreGetGameData", "the total size was written"); + sceNpScoreDeleteRequest(req); + req = NewRequest(); + int result = 0; + Require(gameData(req, 1, emptyNpId, &totalSize, sizeof(data), data, nullptr) == InvalidArgument, "sceNpScoreGetGameData", "an empty online id was taken"); + Require(sceNpScorePollAsync(req, &result) == PollFinished && result == InvalidArgument, "sceNpScoreGetGameData", "an empty online id did not finish the request with INVALID_ARGUMENT"); + sceNpScoreDeleteRequest(req); + + req = NewRequest(); + Require(byAccountId(req, 1, 5, &totalSize, sizeof(data), data, board) == InvalidArgument, "sceNpScoreGetGameDataByAccountId", "an option was taken"); + Require(byAccountId(req, 1, 0, &totalSize, sizeof(data), data, nullptr) == InvalidArgument, "sceNpScoreGetGameDataByAccountId", "account id 0 was taken"); + Require(sceNpScorePollAsync(req, nullptr) == PollRunning, "sceNpScoreGetGameDataByAccountId", "a refused call finished the request"); + CheckOffline("sceNpScoreGetGameDataByAccountId", byAccountId(req, 1, 5, &totalSize, sizeof(data), data, nullptr), req); + Require(byAccountIdAsync(req, 1, 5, &totalSize, sizeof(data), data, nullptr) == SignedOut, "sceNpScoreGetGameDataByAccountIdAsync", "valid arguments did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); + + req = NewRequest(); + Require(boardInfoAsync(req, 1, board, board) == InvalidArgument, "sceNpScoreGetBoardInfoAsync", "an option was taken"); + CheckOffline("sceNpScoreGetBoardInfoAsync", boardInfoAsync(req, 1, board, nullptr), req); + sceNpScoreDeleteRequest(req); +} + +void CheckContextsAndWaiting() { + const auto createA = Resolve("sceNpScoreCreateNpTitleCtxA"); + const auto setPcId = Resolve("sceNpScoreSetPlayerCharacterId"); + const auto waitAsync = Resolve("sceNpScoreWaitAsync"); + Require(createA(0, 1) == SignedOut, "sceNpScoreCreateNpTitleCtxA", "a context was created for a signed-out user"); + Require(createA(0xFFFFFFFFu, 1) == SignedOut, "sceNpScoreCreateNpTitleCtxA", "the service label was checked before the sign-in"); + const int req = NewRequest(); + Require(setPcId(context, -1) == InvalidArgument, "sceNpScoreSetPlayerCharacterId", "a negative character id was taken"); + Require(setPcId(context, 3) == 0, "sceNpScoreSetPlayerCharacterId", "a title context was not found"); + Require(setPcId(req, 3) == 0, "sceNpScoreSetPlayerCharacterId", "a request was not found"); + Require(setPcId(999, 3) == InvalidId, "sceNpScoreSetPlayerCharacterId", "an unknown id was taken"); + int result = 0; + Require(waitAsync(req + 100, &result) == InvalidId, "sceNpScoreWaitAsync", "an unknown request was taken"); + Require(sceNpScoreRecordScoreAsync(req, 1, 5, nullptr, nullptr, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreRecordScoreAsync", "valid arguments did not return SIGNED_OUT"); + Require(waitAsync(req, &result) == 0 && result == SignedOut, "sceNpScoreWaitAsync", "a finished request did not report SIGNED_OUT"); + Require(waitAsync(req, nullptr) == 0, "sceNpScoreWaitAsync", "a null result was not accepted"); + sceNpScoreDeleteRequest(req); +} + +void CheckCrossSave() { + const auto byRange = Resolve("sceNpScoreGetRankingByRangeForCrossSave"); + const auto byRangeAsync = Resolve("sceNpScoreGetRankingByRangeForCrossSaveAsync"); + const auto friends = Resolve("sceNpScoreGetFriendsRankingForCrossSave"); + const auto friendsAsync = Resolve("sceNpScoreGetFriendsRankingForCrossSaveAsync"); + const auto byAccountId = Resolve("sceNpScoreGetRankingByAccountIdForCrossSave"); + const auto byAccountIdAsync = Resolve("sceNpScoreGetRankingByAccountIdForCrossSaveAsync"); + const auto byAccountIdPcId = Resolve("sceNpScoreGetRankingByAccountIdPcIdForCrossSave"); + const auto byAccountIdPcIdAsync = Resolve("sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync"); + int req = NewRequest(); + Require(sceNpScoreGetRankingByRange(req, 1, 1, ranksA, 2 * 0x90, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByRange", "0x90 bytes per rank were not taken"); + Require(byRange(req, 1, 1, ranksA, 0x88, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByRangeForCrossSave", "0x88 bytes per rank were taken"); + Require(byRange(req, 1, 0, ranksA, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidArgument, "sceNpScoreGetRankingByRangeForCrossSave", "rank 0 was taken"); + CheckOffline("sceNpScoreGetRankingByRangeForCrossSave", byRange(req, 1, 1, ranksA, 0x90, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr), req); + Require(byRangeAsync(req, 1, 1, ranks, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByRangeForCrossSaveAsync", "valid arguments did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); + + req = NewRequest(); + Require(friends(req, 1, 1, ranks, 0x88, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetFriendsRankingForCrossSave", "0x88 bytes per friend were taken"); + CheckOffline("sceNpScoreGetFriendsRankingForCrossSave", friends(req, 1, 1, ranks, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr), req); + Require(friendsAsync(req, 1, 0, ranks, 0x80, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetFriendsRankingForCrossSaveAsync", "valid arguments did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); + + req = NewRequest(); + Require(byAccountId(req, 1, accountIds, 4, ranksA, 0x98, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByAccountIdForCrossSave", "an account id array size other than 8 per id was taken"); + CheckOffline("sceNpScoreGetRankingByAccountIdForCrossSave", byAccountId(req, 1, accountIds, 16, ranksA, 0x130, nullptr, 0, nullptr, 0, 2, nullptr, nullptr, nullptr), req); + Require(byAccountIdAsync(req, 1, accountIds, 8, ranksA, 0x98, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByAccountIdForCrossSaveAsync", "valid arguments did not return SIGNED_OUT"); + Require(byAccountIdPcId(req, 1, accountIdPcIds, 8, ranksA, 0x98, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == InvalidAlignment, "sceNpScoreGetRankingByAccountIdPcIdForCrossSave", "an id array size other than 16 per id was taken"); + Require(byAccountIdPcId(req, 1, accountIdPcIds, 16, ranksA, 0x98, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByAccountIdPcIdForCrossSave", "valid arguments did not return SIGNED_OUT"); + Require(byAccountIdPcIdAsync(req, 1, accountIdPcIds, 16, ranksA, 0x98, nullptr, 0, nullptr, 0, 1, nullptr, nullptr, nullptr) == SignedOut, "sceNpScoreGetRankingByAccountIdPcIdForCrossSaveAsync", "valid arguments did not return SIGNED_OUT"); + sceNpScoreDeleteRequest(req); +} + +void CheckSettings() { + const auto setTimeout = Resolve("sceNpScoreSetTimeout"); + const auto setThreadParam = Resolve("sceNpScoreSetThreadParam"); + const auto createTitleCtx = Resolve("sceNpScoreCreateTitleCtx"); + const int req = NewRequest(); + Require(setTimeout(context, 0, 0, 0, 0, 0) == 0, "sceNpScoreSetTimeout", "a title context was not found"); + Require(setTimeout(req, 1, 1000000, 10000000, 10000000, 10000000) == 0, "sceNpScoreSetTimeout", "the minimum timeouts were refused for a request"); + Require(setTimeout(999, 0, 0, 0, 0, 0) == InvalidId, "sceNpScoreSetTimeout", "an unknown id was taken"); + Require(setTimeout(999, -1, 0, 0, 0, 0) == InvalidArgument, "sceNpScoreSetTimeout", "a negative retry count was not refused before the id"); + Require(setTimeout(req, 0, 999999, 0, 0, 0) == InvalidArgument, "sceNpScoreSetTimeout", "a resolve timeout below 1 s was taken"); + Require(setTimeout(req, 0, -1, 0, 0, 0) == InvalidArgument, "sceNpScoreSetTimeout", "a negative resolve timeout was taken"); + Require(setTimeout(req, 0, 0, 9999999, 0, 0) == InvalidArgument, "sceNpScoreSetTimeout", "a connect timeout below 10 s was taken"); + Require(setTimeout(req, 0, 0, 0, 9999999, 0) == InvalidArgument, "sceNpScoreSetTimeout", "a send timeout below 10 s was taken"); + Require(setTimeout(req, 0, 0, 0, 0, 9999999) == InvalidArgument, "sceNpScoreSetTimeout", "a receive timeout below 10 s was taken"); + Require(setThreadParam(700, 0) == 0, "sceNpScoreSetThreadParam", "setting the worker thread parameters failed"); + unsigned char titleId[12] = {}; + unsigned char titleSecret[128] = {}; + Require(createTitleCtx(nullptr, titleSecret, selfNpId) == InsufficientArgument, "sceNpScoreCreateTitleCtx", "a null title id was taken"); + Require(createTitleCtx(titleId, nullptr, selfNpId) == InsufficientArgument, "sceNpScoreCreateTitleCtx", "a null title secret was taken"); + Require(createTitleCtx(titleId, titleSecret, nullptr) == InsufficientArgument, "sceNpScoreCreateTitleCtx", "a null NpId was taken"); + const int created = createTitleCtx(titleId, titleSecret, selfNpId); + Require(created > 0, "sceNpScoreCreateTitleCtx", "no title context was created"); + Require(sceNpScoreCreateRequest(created) > 0, "sceNpScoreCreateTitleCtx", "the context does not take requests"); + Require(sceNpScoreDeleteNpTitleCtx(created) == 0, "sceNpScoreCreateTitleCtx", "the context could not be deleted"); + sceNpScoreDeleteRequest(req); +} + +} + +void CheckCommentsAndGameData() { + const auto censor = Resolve("sceNpScoreCensorComment"); + const auto censorAsync = Resolve("sceNpScoreCensorCommentAsync"); + const auto sanitize = Resolve("sceNpScoreSanitizeComment"); + const auto sanitizeAsync = Resolve("sceNpScoreSanitizeCommentAsync"); + const auto recordGameData = Resolve("sceNpScoreRecordGameData"); + const auto recordGameDataAsync = Resolve("sceNpScoreRecordGameDataAsync"); + const auto changeMode = Resolve("sceNpScoreChangeModeForOtherSaveDataOwners"); + char longest[256]; + std::memset(longest, 'a', 255); + longest[255] = '\0'; + char tooLong[257]; + std::memset(tooLong, 'a', 256); + tooLong[256] = '\0'; + char sanitized[256]; + std::memset(sanitized, 0xA5, sizeof(sanitized)); + int option = 0; + const unsigned char data[16] = {}; + + for (const CensorFn call : {censor, censorAsync}) { + const int req = NewRequest(); + Require(call(req, longest, &option) == InvalidArgument, "sceNpScoreCensorComment", "an option was taken"); + Require(call(req + 100, nullptr, nullptr) == InsufficientArgument, "sceNpScoreCensorComment", "a null comment was not refused before the request lookup"); + Require(call(req + 100, tooLong, nullptr) == InvalidId, "sceNpScoreCensorComment", "the request was not looked up before the length"); + Require(call(req, tooLong, nullptr) == InvalidArgument, "sceNpScoreCensorComment", "a 256-character comment was taken"); + Require(sceNpScorePollAsync(req, nullptr) == PollRunning, "sceNpScoreCensorComment", "a refused call finished the request"); + CheckOffline("sceNpScoreCensorComment", call(req, longest, nullptr), req); + sceNpScoreDeleteRequest(req); + } + for (const SanitizeFn call : {sanitize, sanitizeAsync}) { + const int req = NewRequest(); + Require(call(req, longest, sanitized, &option) == InvalidArgument, "sceNpScoreSanitizeComment", "an option was taken"); + Require(call(req + 100, nullptr, sanitized, nullptr) == InsufficientArgument, "sceNpScoreSanitizeComment", "a null comment was not refused before the request lookup"); + Require(call(req, tooLong, sanitized, nullptr) == InvalidArgument, "sceNpScoreSanitizeComment", "a 256-character comment was taken"); + CheckOffline("sceNpScoreSanitizeComment", call(req, longest, nullptr, nullptr), req); + sceNpScoreDeleteRequest(req); + } + for (const unsigned char value : sanitized) Require(value == 0xA5, "sceNpScoreSanitizeComment", "the sanitized comment was written"); + for (const RecordGameDataFn call : {recordGameData, recordGameDataAsync}) { + const int req = NewRequest(); + Require(call(req, 1, 5, 16, 16, data, &option) == InvalidArgument, "sceNpScoreRecordGameData", "an option was taken"); + Require(call(req + 100, 1, 5, 16, 16, nullptr, nullptr) == InsufficientArgument, "sceNpScoreRecordGameData", "null data was not refused before the request lookup"); + Require(call(req + 100, 1, 5, 16, 16, data, nullptr) == InvalidId, "sceNpScoreRecordGameData", "an unknown request was taken"); + CheckOffline("sceNpScoreRecordGameData", call(req, 1, 5, 4, 16, data, nullptr), req); + sceNpScoreDeleteRequest(req); + } + const int req = NewRequest(); + Require(changeMode(req + 100, 4) == InvalidArgument && changeMode(req + 100, 0xFFFFFFFFu) == InvalidArgument, "sceNpScoreChangeModeForOtherSaveDataOwners", "a mode past 3 was not refused before the id"); + Require(changeMode(req + 100, 3) == InvalidId, "sceNpScoreChangeModeForOtherSaveDataOwners", "an unknown id was taken"); + Require(changeMode(context, 3) == 0 && changeMode(req, 0) == 0, "sceNpScoreChangeModeForOtherSaveDataOwners", "a title context or request was refused"); + Require(sceNpScorePollAsync(req, nullptr) == PollRunning, "sceNpScoreChangeModeForOtherSaveDataOwners", "changing the mode finished the request"); + sceNpScoreDeleteRequest(req); } int main() { @@ -186,6 +458,7 @@ int main() { std::memset(comments, 0xA5, sizeof(comments)); std::memset(infos, 0xA5, sizeof(infos)); std::memset(board, 0xA5, sizeof(board)); + std::memset(ranksA, 0xA5, sizeof(ranksA)); CheckContexts(); CheckRequests(); CheckRecord(); @@ -193,6 +466,14 @@ int main() { CheckNpId(); CheckFriends(); CheckBoard(); + CheckRangeAndFriendsA(); + CheckAccountIds(); + CheckNpIdPcId(); + CheckGameDataAndBoard(); + CheckContextsAndWaiting(); + CheckCrossSave(); + CheckSettings(); + CheckCommentsAndGameData(); Require(Untouched(), "libSceNpScoreRanking", "an output array was written"); if (failures != 0) return 1; std::puts("NpScoreRanking offline tests passed"); diff --git a/core/libs/tests/GuestNpTrophy.cpp b/core/libs/tests/GuestNpTrophy.cpp index 75c31429a3..c492e40b60 100644 --- a/core/libs/tests/GuestNpTrophy.cpp +++ b/core/libs/tests/GuestNpTrophy.cpp @@ -22,6 +22,7 @@ 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 InvalidTrpFileFormat = static_cast(0x80551616u); constexpr int TitleConfNotInstalled = static_cast(0x80551626u); constexpr int TrophyNotUnlocked = static_cast(0x8055161au); @@ -53,6 +54,62 @@ void WritePackage(const std::filesystem::path& path, const std::vector(bytes.data()), static_cast(bytes.size())); } +void PatchPackage(const std::filesystem::path& path, std::size_t at, std::uint64_t value, int width) { + std::vector bytes(width); + PutBigEndian(bytes, 0, value, width); + std::fstream file(path, std::ios::binary | std::ios::in | std::ios::out); + file.seekp(static_cast(at)); + file.write(reinterpret_cast(bytes.data()), static_cast(bytes.size())); + Require(static_cast(file), "patch the trophy package"); +} + +void TestPackageBounds() { + const std::filesystem::path path("app0/sce_sys/trophy/trophy02.trp"); + const std::vector> files{{"TROPCONF.ESFM", "conf"}, {"ICON0.PNG", "game-icon"}, {"TROP000.PNG", "first"}, {"TROP001.PNG", "second"}}; + WritePackage(path, files); + const auto fileSize = std::filesystem::file_size(path); + int handle = 0; + Require(sceNpTrophyCreateHandle(&handle) == 0, "create a package bounds handle"); + for (const auto& [offset, size] : std::vector>{ + {fileSize + 1, 0}, {fileSize + 1, 1}, {fileSize, 1}, {fileSize - 1, 2}, {UINT64_MAX, 1}, {1, UINT64_MAX}, {fileSize - 1, UINT64_MAX}}) { + for (const std::size_t index : {0u, 1u, 3u}) { + WritePackage(path, files); + PatchPackage(path, 0x60 + index * 0x40 + 32, offset, 8); + PatchPackage(path, 0x60 + index * 0x40 + 40, size, 8); + int context = 0; + Require(sceNpTrophyCreateContext(&context, 1, 2, 0) == 0, "create a package bounds context"); + Require(sceNpTrophyRegisterContext(context, handle, 0) == InvalidTrpFileFormat, "register a package whose entry extends past the file"); + TrophyFlagArray flags{}; + std::uint32_t count = 0; + Require(sceNpTrophyGetTrophyUnlockState(context, handle, &flags, &count) == NotRegistered, "a rejected package registered its context"); + WritePackage(path, files); + Require(sceNpTrophyRegisterContext(context, handle, 0) == 0, "retry registration after repairing the package"); + std::uint64_t iconSize = 0; + Require(sceNpTrophyGetGameIcon(context, handle, nullptr, &iconSize) == 0 && iconSize == 9, "a rejected package left stale icon metadata"); + Require(sceNpTrophyDestroyContext(context) == 0, "destroy a package bounds context"); + } + } + for (const std::uint64_t size : {0u, 4u}) { + WritePackage(path, files); + PatchPackage(path, 0x60 + 0x40 + 32, fileSize - size, 8); + PatchPackage(path, 0x60 + 0x40 + 40, size, 8); + int context = 0; + Require(sceNpTrophyCreateContext(&context, 1, 2, 0) == 0 && sceNpTrophyRegisterContext(context, handle, 0) == 0, "an entry ending exactly at EOF was rejected"); + std::uint64_t iconSize = 0; + Require(sceNpTrophyGetGameIcon(context, handle, nullptr, &iconSize) == 0 && iconSize == size, "the EOF entry has the wrong size"); + if (size != 0) { + char bytes[4]{}; + Require(sceNpTrophyGetGameIcon(context, handle, bytes, &iconSize) == 0 && std::memcmp(bytes, "cond", 4) == 0, "the EOF entry has the wrong bytes"); + } + Require(sceNpTrophyDestroyContext(context) == 0, "destroy the EOF entry context"); + } + WritePackage(path, files); + PatchPackage(path, 0x10, UINT32_MAX, 4); + int context = 0; + Require(sceNpTrophyCreateContext(&context, 1, 2, 0) == 0 && sceNpTrophyRegisterContext(context, handle, 0) == InvalidTrpFileFormat, "a table extending past the file was accepted"); + Require(sceNpTrophyDestroyContext(context) == 0 && sceNpTrophyDestroyHandle(handle) == 0, "destroy the package bounds context and handle"); +} + } int main() { @@ -146,6 +203,8 @@ int main() { Require(sceNpTrophyAbortHandle(handle) == InvalidHandle, "abort a destroyed handle"); Require(sceNpTrophyDestroyContext(context) == InvalidContext, "destroy the context twice"); + TestPackageBounds(); + std::filesystem::current_path(root.parent_path()); std::error_code error; std::filesystem::remove_all(root, error); diff --git a/core/libs/tests/GuestNpTrophy2.cpp b/core/libs/tests/GuestNpTrophy2.cpp index 923c21abd2..50365c3bbc 100644 --- a/core/libs/tests/GuestNpTrophy2.cpp +++ b/core/libs/tests/GuestNpTrophy2.cpp @@ -2,6 +2,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceNpTrophy2/include/NpTrophy2Constants.hpp" #include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp" +#include "tests/ExportLookup.hpp" #include #include #include @@ -10,6 +11,38 @@ static void Require(bool value) { if (!value) std::abort(); } static void APS5_VABI OnUnlock(int, int, void*) {} +static void ResolvePendingExports(const char* path) { + struct Export { + int (APS5_VABI* function)(void); + const char* name; + }; + const Export exports[] = { + {sceNpTrophy2SystemAbortHandle, "sceNpTrophy2SystemAbortHandle"}, + {sceNpTrophy2SystemBuildGroupIconUri, "sceNpTrophy2SystemBuildGroupIconUri"}, + {sceNpTrophy2SystemBuildRewardIconUri, "sceNpTrophy2SystemBuildRewardIconUri"}, + {sceNpTrophy2SystemBuildTrophyIconUri, "sceNpTrophy2SystemBuildTrophyIconUri"}, + {sceNpTrophy2SystemCheckCallback, "sceNpTrophy2SystemCheckCallback"}, + {sceNpTrophy2SystemCheckNetSyncTitles, "sceNpTrophy2SystemCheckNetSyncTitles"}, + {sceNpTrophy2SystemCreateHandle, "sceNpTrophy2SystemCreateHandle"}, + {sceNpTrophy2SystemDebugLockTrophy, "sceNpTrophy2SystemDebugLockTrophy"}, + {sceNpTrophy2SystemDebugUnlockTrophy, "sceNpTrophy2SystemDebugUnlockTrophy"}, + {sceNpTrophy2SystemDestroyContext, "sceNpTrophy2SystemDestroyContext"}, + {sceNpTrophy2SystemGetTrophyDetails, "sceNpTrophy2SystemGetTrophyDetails"}, + {sceNpTrophy2SystemGetTrophyGroupConf, "sceNpTrophy2SystemGetTrophyGroupConf"}, + {sceNpTrophy2SystemGetTrophySetArray, "sceNpTrophy2SystemGetTrophySetArray"}, + {sceNpTrophy2SystemGetTrophyTitleConf, "sceNpTrophy2SystemGetTrophyTitleConf"}, + {sceNpTrophy2SystemGetTrophyTitleDetails, "sceNpTrophy2SystemGetTrophyTitleDetails"}, + {sceNpTrophy2SystemGetTrpIconByUri, "sceNpTrophy2SystemGetTrpIconByUri"}, + {sceNpTrophy2SystemIsServerAvailable, "sceNpTrophy2SystemIsServerAvailable"}, + {sceNpTrophy2SystemRegisterTitleSyncedCallback, "sceNpTrophy2SystemRegisterTitleSyncedCallback"}, + {sceNpTrophy2SystemRegisterTitleUpdateCallback, "sceNpTrophy2SystemRegisterTitleUpdateCallback"}, + {sceNpTrophy2SystemRemoveAll, "sceNpTrophy2SystemRemoveAll"}, + {sceNpTrophy2SystemRemoveTitleData, "sceNpTrophy2SystemRemoveTitleData"}, + {sceNpTrophy2SystemRemoveUserData, "sceNpTrophy2SystemRemoveUserData"}, + }; + Require(ExportsResolveByName(path, exports, "sceNpTrophy2SystemNotExported")); +} + template static bool ThrowsInvalidArgument(Call call) { try { call(); @@ -19,7 +52,7 @@ template static bool ThrowsInvalidArgument(Call call) { return false; } -int main() { +int main(int argc, char** argv) { int userdata = 0; Require(sceNpTrophy2UnregisterUnlockCallback() == 0); Require(sceNpTrophy2RegisterUnlockCallback(reinterpret_cast(&OnUnlock), &userdata) == 0); @@ -31,6 +64,7 @@ int main() { auto* volatile linked = &sceNpTrophy2SystemCreateContext; Require(linked != nullptr); + ResolvePendingExports(argc > 1 ? argv[1] : nullptr); NpTrophy2GameDetails details{}; NpTrophy2GameData data{}; diff --git a/core/libs/tests/GuestNpTrophyData.cpp b/core/libs/tests/GuestNpTrophyData.cpp index 71275707f9..7c7c1f1fcc 100644 --- a/core/libs/tests/GuestNpTrophyData.cpp +++ b/core/libs/tests/GuestNpTrophyData.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp" #include "prx/libSceNpTrophy2/include/TrophyStore.hpp" +#include "tests/ExportLookup.hpp" #include #include @@ -228,7 +229,28 @@ static void ReportExportLinked() { Require(linked != nullptr, "sceNpUniversalDataSystemIntInitialize is not linked"); } -int main() { +static void ResolveInternalExports(const char* path) { + struct Export { + int (APS5_VABI* function)(void); + const char* name; + }; + const Export exports[] = { + {sceNpUniversalDataSystemIntAbortHandle, "sceNpUniversalDataSystemIntAbortHandle"}, + {sceNpUniversalDataSystemIntCheckNetSyncTitles, "sceNpUniversalDataSystemIntCheckNetSyncTitles"}, + {sceNpUniversalDataSystemIntCreateContext, "sceNpUniversalDataSystemIntCreateContext"}, + {sceNpUniversalDataSystemIntCreateHandle, "sceNpUniversalDataSystemIntCreateHandle"}, + {sceNpUniversalDataSystemIntDeleteAllData, "sceNpUniversalDataSystemIntDeleteAllData"}, + {sceNpUniversalDataSystemIntDeleteUserDataWithServerData, "sceNpUniversalDataSystemIntDeleteUserDataWithServerData"}, + {sceNpUniversalDataSystemIntDestroyContext, "sceNpUniversalDataSystemIntDestroyContext"}, + {sceNpUniversalDataSystemIntDestroyHandle, "sceNpUniversalDataSystemIntDestroyHandle"}, + {sceNpUniversalDataSystemIntNetSyncTitles, "sceNpUniversalDataSystemIntNetSyncTitles"}, + {sceNpUniversalDataSystemIntTerminate, "sceNpUniversalDataSystemIntTerminate"}, + }; + Require(ExportsResolveByName(path, exports, "sceNpUniversalDataSystemIntNotExported"), + "the internal exports do not all resolve by name"); +} + +int main(int argc, char** argv) { const auto previous = std::filesystem::current_path(); const auto root = std::filesystem::temp_directory_path() / ("anyps5-trophy-data-" + std::to_string(std::random_device{}())); std::filesystem::create_directories(root); @@ -242,6 +264,7 @@ int main() { PostUnlocks(context); ReportPlaceholders(); ReportExportLinked(); + ResolveInternalExports(argc > 1 ? argv[1] : nullptr); std::filesystem::current_path(previous); std::filesystem::remove_all(root); diff --git a/core/libs/tests/GuestNpTrophyHelpers.cpp b/core/libs/tests/GuestNpTrophyHelpers.cpp new file mode 100644 index 0000000000..7f20a62d3b --- /dev/null +++ b/core/libs/tests/GuestNpTrophyHelpers.cpp @@ -0,0 +1,90 @@ +#include "prx/libSceNpTrophy/include/Api.hpp" + +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace { + +using WordsFn = int (APS5_VABI*)(const std::uint32_t*); +using BytesFn = int (APS5_VABI*)(const std::uint8_t*); +using FlagArrayFn = int (APS5_VABI*)(const std::uint8_t*, std::uint8_t*); + +template +Function Resolve(const char* name) { +#ifdef _WIN32 + void* symbol = reinterpret_cast(GetProcAddress(LoadLibraryA("libSceNpTrophy.prx"), name)); +#else + void* symbol = dlsym(RTLD_DEFAULT, name); +#endif + if (!symbol) { + std::fprintf(stderr, "%s is not exported by libSceNpTrophy\n", name); + std::abort(); + } + return reinterpret_cast(symbol); +} + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "NpTrophy helpers: %s\n", what); + std::abort(); +} + +} + +int main() { + const auto numInfoGetTotal = Resolve("sceNpTrophyNumInfoGetTotal"); + const auto groupArrayGetNum = Resolve("sceNpTrophyGroupArrayGetNum"); + const auto setInfoGetTrophyNum = Resolve("sceNpTrophySetInfoGetTrophyNum"); + const auto setInfoGetTrophyFlagArray = Resolve("sceNpTrophySetInfoGetTrophyFlagArray"); + const auto configHasGroupFeature = Resolve("sceNpTrophyConfigHasGroupFeature"); + + const std::uint32_t counts[4] = {1, 20, 300, 4000}; + Require(numInfoGetTotal(counts) == 4321, "the four counts were not summed"); + const std::uint32_t wrapping[4] = {0xFFFFFFFFu, 2, 0, 0}; + Require(numInfoGetTotal(wrapping) == 1, "the sum did not wrap at 32 bits"); + + const std::uint32_t groups[4] = {0x12345678u, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu}; + Require(groupArrayGetNum(groups) == 13, "the group count read more than the first word"); + const std::uint32_t allGroups[4] = {0xFFFFFFFFu, 0, 0, 0}; + Require(groupArrayGetNum(allGroups) == 32, "32 groups were not counted"); + + std::uint8_t setInfo[128]; + std::memset(setInfo, 0x0F, 64); + std::memset(setInfo + 64, 0xFF, 64); + Require(setInfoGetTrophyNum(setInfo) == 256, "the trophy count read past the four flag arrays"); + + std::memset(setInfo, 0, sizeof(setInfo)); + std::memset(setInfo + 32, 0xFF, 16); + std::uint8_t flags[32]; + std::memset(flags, 0xA5, sizeof(flags)); + Require(setInfoGetTrophyFlagArray(setInfo, flags) == 128, "a full flag array did not return 128 set bits"); + for (std::size_t index = 0; index < 16; ++index) Require(flags[index] == 0xFF, "one full flag array did not fill the output"); + for (std::size_t index = 16; index < 32; ++index) Require(flags[index] == 0xA5, "bytes past the 16-byte output were written"); + std::memset(setInfo, 0, sizeof(setInfo)); + setInfo[0] = 0x01; + setInfo[16] = 0x02; + setInfo[32] = 0x04; + setInfo[48 + 15] = 0x80; + Require(setInfoGetTrophyFlagArray(setInfo, flags) == 4 && flags[0] == 0x07 && flags[15] == 0x80 && flags[1] == 0, "the four flag arrays were not combined with OR, or the set bits were not counted"); + std::memset(setInfo, 0, sizeof(setInfo)); + Require(setInfoGetTrophyFlagArray(setInfo, flags) == 0 && flags[0] == 0, "an empty set did not return 0"); + setInfo[0] = 0x01; + Require(setInfoGetTrophyFlagArray(setInfo, flags) == 1, "a single trophy did not return 1"); + + std::uint8_t config[0x100] = {}; + Require(configHasGroupFeature(nullptr) == 0, "a null config did not answer 0"); + config[0x90] = 0x03; + Require(configHasGroupFeature(config) == 1, "bit 0 of the feature byte was not reported"); + config[0x90] = 0x02; + Require(configHasGroupFeature(config) == 0, "a bit other than bit 0 was reported"); + + std::puts("NpTrophy helper tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestNpWebApi.cpp b/core/libs/tests/GuestNpWebApi.cpp index 1f6122817c..c6221662e3 100644 --- a/core/libs/tests/GuestNpWebApi.cpp +++ b/core/libs/tests/GuestNpWebApi.cpp @@ -27,6 +27,8 @@ 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 HttpInvalidId = static_cast(0x80431100u); +constexpr int HttpInvalidResponse = static_cast(0x80432060u); constexpr int MethodGet = 0; constexpr int MethodPost = 1; @@ -180,6 +182,49 @@ void TestRequests() { Require(sceNpWebApiTerminate(context) == 0, "terminate the request library context"); } +void TestResponses() { + const int context = sceNpWebApiInitialize(1, 0x10000); + const int user = sceNpWebApiCreateContext(context, onlineId); + Require(user > 0, "user context for responses"); + std::int64_t request = 0; + std::int64_t aborted = 0; + Require(sceNpWebApiCreateRequest(user, "group", "/path", MethodGet, nullptr, &request) == 0, "request for responses"); + Require(sceNpWebApiCreateRequest(user, "group", "/path", MethodGet, nullptr, &aborted) == 0, "request to abort"); + Require(sceNpWebApiSendRequest(request, nullptr, 0) == NotSignedIn, "the request is never sent"); + Require(sceNpWebApiAbortRequest(aborted) == 0, "abort the second request"); + + std::array data{}; + data.fill('x'); + Require(sceNpWebApiReadData(request, nullptr, data.size()) == InvalidArgument, "read without a buffer"); + Require(sceNpWebApiReadData(request, data.data(), 0) == InvalidArgument, "read of zero bytes"); + Require(sceNpWebApiReadData(-1, nullptr, 0) == InvalidArgument, "the buffer is checked before the request"); + Require(sceNpWebApiReadData(-1, data.data(), data.size()) == LibContextNotFound, "read request -1"); + Require(sceNpWebApiReadData(static_cast(user + 1) << 32 | 1, data.data(), data.size()) == UserContextNotFound, "read a request of an unknown user context"); + Require(sceNpWebApiReadData(request + 0x10000, data.data(), data.size()) == RequestNotFound, "read an unknown request"); + Require(sceNpWebApiReadData(request, data.data(), data.size()) == HttpInvalidId, "read a request that was never sent"); + Require(sceNpWebApiReadData(aborted, data.data(), data.size()) == Aborted, "read an aborted request"); + Require(data[0] == 'x' && data[7] == 'x', "a failed read leaves the buffer alone"); + + std::array value{}; + value.fill('x'); + std::size_t length = 123; + Require(sceNpWebApiGetHttpResponseHeaderValue(request, nullptr, value.data(), value.size()) == InvalidArgument, "header value without a field name"); + Require(sceNpWebApiGetHttpResponseHeaderValue(request, "Content-Type", nullptr, value.size()) == InvalidArgument, "header value without a buffer"); + Require(sceNpWebApiGetHttpResponseHeaderValue(request, "Content-Type", value.data(), 0) == InvalidArgument, "header value with an empty buffer"); + Require(sceNpWebApiGetHttpResponseHeaderValueLength(request, nullptr, &length) == InvalidArgument, "header length without a field name"); + Require(sceNpWebApiGetHttpResponseHeaderValueLength(request, "Content-Type", nullptr) == InvalidArgument, "header length without an output"); + Require(sceNpWebApiGetHttpResponseHeaderValue(-1, "Content-Type", value.data(), value.size()) == LibContextNotFound, "header value of request -1"); + Require(sceNpWebApiGetHttpResponseHeaderValueLength(request + 0x10000, "Content-Type", &length) == RequestNotFound, "header length of an unknown request"); + Require(sceNpWebApiGetHttpResponseHeaderValue(request, "Content-Type", value.data(), value.size()) == HttpInvalidResponse, "header value of a request that was never sent"); + Require(sceNpWebApiGetHttpResponseHeaderValueLength(request, "Content-Type", &length) == HttpInvalidResponse, "header length of a request that was never sent"); + Require(sceNpWebApiGetHttpResponseHeaderValueLength(aborted, "Content-Type", &length) == HttpInvalidResponse, "header length of an aborted request"); + Require(value[0] == 'x' && value[7] == 'x' && length == 123, "a failed header query leaves the outputs alone"); + + Require(sceNpWebApiDeleteRequest(request) == 0 && sceNpWebApiDeleteRequest(aborted) == 0, "delete the response requests"); + Require(sceNpWebApiReadData(request, data.data(), data.size()) == RequestNotFound, "read a deleted request"); + Require(sceNpWebApiTerminate(context) == 0, "terminate the response library context"); +} + void TestPushEventCallbacks() { const int context = sceNpWebApiInitialize(1, 0x10000); const int other = sceNpWebApiInitialize(1, 0x10000); @@ -389,6 +434,7 @@ int main() { TestLibraryContexts(); TestUserContexts(); TestRequests(); + TestResponses(); TestPushEventCallbacks(); TestOlderSdks(); TestHandleOperations(); diff --git a/core/libs/tests/GuestOfflineImports.cpp b/core/libs/tests/GuestOfflineImports.cpp index 9e441d4b0c..b5977fd120 100644 --- a/core/libs/tests/GuestOfflineImports.cpp +++ b/core/libs/tests/GuestOfflineImports.cpp @@ -82,6 +82,21 @@ int main() { Require(sceNpEntitlementAccessRequestServiceEntitlementInfo(0, 0, nullptr, nullptr) == signedOut); Require(sceNpEntitlementAccessPollServiceEntitlementInfo(0, nullptr, nullptr) == signedOut); + std::array transactionId{}; + std::int64_t requestId = 0x5a5a5a5a5a5a5a5a; + std::int32_t result = 0x5a5a5a5a; + std::array info; + info.fill(0xa5); + const auto originalInfo = info; + Require(sceNpEntitlementAccessRequestConsumeEntitlement(1, 0, &label, transactionId.data(), 1, &requestId) == signedOut); + Require(sceNpEntitlementAccessRequestConsumeEntitlement(1, 0, nullptr, nullptr, 1, nullptr) == signedOut); + Require(sceNpEntitlementAccessRequestConsumableEntitlementInfo(1, 0, &label, &requestId) == signedOut); + Require(sceNpEntitlementAccessRequestConsumableEntitlementInfo(1, 0, nullptr, nullptr) == signedOut); + Require(requestId == 0x5a5a5a5a5a5a5a5a); + Require(sceNpEntitlementAccessPollConsumableEntitlementInfo(1, &result, info.data()) == signedOut); + Require(sceNpEntitlementAccessPollConsumableEntitlementInfo(1, nullptr, nullptr) == signedOut); + Require(result == 0x5a5a5a5a && info == originalInfo); + Require(sceRudpActivate(-1, nullptr, 0) == static_cast(0x80770001)); std::array status; status.fill(0x5a); diff --git a/core/libs/tests/GuestOpusDecCtl.cpp b/core/libs/tests/GuestOpusDecCtl.cpp new file mode 100644 index 0000000000..ee2dad3444 --- /dev/null +++ b/core/libs/tests/GuestOpusDecCtl.cpp @@ -0,0 +1,183 @@ +#include "prx/libSceOpusCeltDec/libSceOpusCeltDec.hpp" +#include "prx/libSceOpusDec/libSceOpusDec.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +using Ctl = int (APS5_VABI*)(void*, int, ...); + +struct Api { + bool celt; + std::uint32_t errorBase; + int (APS5_VABI* initialize)(std::uint32_t*); + int (APS5_VABI* terminate)(const std::uint32_t*); + int (APS5_VABI* createEx)(const std::uint32_t*, void*, int, int); + int (APS5_VABI* decode)(void*, const std::uint8_t*, int, std::int16_t*, int); + Ctl ctl; + int (APS5_VABI* destroy)(void*); + int error(std::uint32_t offset) const { return static_cast(errorBase + offset); } +}; + +std::vector> Packets(int count) { + int error = 0; + std::unique_ptr encoder(opus_encoder_create(48000, 2, OPUS_APPLICATION_RESTRICTED_LOWDELAY, &error), opus_encoder_destroy); + Require(encoder && error == OPUS_OK); + Require(opus_encoder_ctl(encoder.get(), OPUS_SET_BITRATE(24000)) == OPUS_OK); + std::vector> packets; + std::array pcm{}; + for (int frame = 0; frame < count; ++frame) { + for (int i = 0; i < 960; ++i) { + const double t = (frame * 960 + i) / 48000.0; + pcm[i * 2] = static_cast(9000 * std::sin(2 * 3.14159265 * 440 * t)); + pcm[i * 2 + 1] = static_cast(9000 * std::sin(2 * 3.14159265 * 440 * t + 2.5)); + } + std::array packet{}; + const int bytes = opus_encode(encoder.get(), pcm.data(), 960, packet.data(), packet.size()); + Require(bytes > 1 && (packet[0] >> 3) >= 16); + packets.emplace_back(packet.begin(), packet.begin() + bytes); + } + return packets; +} + +struct Reference { + std::unique_ptr decoder{nullptr, opus_decoder_destroy}; + explicit Reference(int rate) { + int error = 0; + decoder.reset(opus_decoder_create(rate, 2, &error)); + Require(decoder && error == OPUS_OK); + } + std::vector Decode(const std::vector& packet, opus_uint32* range) { + std::vector pcm(5760 * 2); + const int samples = opus_decode(decoder.get(), packet.data(), static_cast(packet.size()), pcm.data(), 5760, 0); + Require(samples > 0); + pcm.resize(static_cast(samples) * 2); + Require(opus_decoder_ctl(decoder.get(), OPUS_GET_FINAL_RANGE(range)) == OPUS_OK); + return pcm; + } +}; + +std::vector Decode(const Api& api, void* state, const std::vector& packet, int samples) { + std::vector pcm(static_cast(samples) * 2); + Require(api.decode(state, packet.data(), static_cast(packet.size()), pcm.data(), samples * 4) == samples * 4); + return pcm; +} + +std::int32_t Get(const Api& api, void* state, int request) { + std::int32_t value = 0x5A5A5A5A; + Require(api.ctl(state, request, &value) == 0); + return value; +} + +void RequireRequests(const Api& api) { + std::uint32_t context = 0; + Require(api.initialize(&context) == 0); + std::array state{}, unknown{}; + Require(api.createEx(&context, state.data(), 24000, 2) == 0); + std::int32_t value = 0; + Require(api.ctl(nullptr, OPUS_GET_GAIN_REQUEST, &value) == api.error(0)); + Require(api.ctl(unknown.data(), OPUS_GET_GAIN_REQUEST, &value) == api.error(1)); + for (int request : {-2, 0, 4000, 4011, 4014, 4027, 4030, 4032, 4033, 4035, 4044, 4048, 4049, 4052, 4100, 5000, std::numeric_limits::min()}) + Require(api.ctl(state.data(), request, &value) == api.error(14)); + for (int request : {0, 4100, -2}) Require(api.ctl(state.data(), request) == api.error(14)); + if (api.celt) + for (int request : {OPUS_SET_INBAND_FEC_REQUEST, OPUS_GET_INBAND_FEC_REQUEST, 4050, 4051}) + Require(api.ctl(state.data(), request, &value) == api.error(14)); + for (int request : {OPUS_GET_SAMPLE_RATE_REQUEST, OPUS_GET_FINAL_RANGE_REQUEST, OPUS_GET_GAIN_REQUEST, OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST}) + Require(api.ctl(state.data(), request, static_cast(nullptr)) == api.error(13)); + if (!api.celt) + for (int request : {OPUS_GET_INBAND_FEC_REQUEST, 4051}) + Require(api.ctl(state.data(), request, static_cast(nullptr)) == api.error(13)); + std::int32_t first = 0, second = 0; + Require(api.ctl(state.data(), -1, &first, static_cast(nullptr)) == api.error(13)); + Require(api.ctl(state.data(), -1, static_cast(nullptr), &second) == api.error(13)); + first = second = 0x5A5A5A5A; + Require(api.ctl(state.data(), -1, &first, &second) == 0 && first == 0 && second == 0); + + Require(Get(api, state.data(), OPUS_GET_SAMPLE_RATE_REQUEST) == 24000); + Require(Get(api, state.data(), OPUS_GET_FINAL_RANGE_REQUEST) == 0); + Require(Get(api, state.data(), OPUS_GET_GAIN_REQUEST) == 0); + for (int gain : {32768, -32769, 0x10000, std::numeric_limits::min(), std::numeric_limits::max()}) + Require(api.ctl(state.data(), OPUS_SET_GAIN_REQUEST, gain) == api.error(12)); + for (int gain : {-32768, 32767, -300}) + Require(api.ctl(state.data(), OPUS_SET_GAIN_REQUEST, gain) == 0 && Get(api, state.data(), OPUS_GET_GAIN_REQUEST) == gain); + for (int flag : {2, -1, std::numeric_limits::max()}) + Require(api.ctl(state.data(), OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, flag) == api.error(api.celt ? 17 : 27)); + Require(api.ctl(state.data(), OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, 1) == 0); + Require(Get(api, state.data(), OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST) == 1); + if (!api.celt) { + Require(api.ctl(state.data(), OPUS_SET_INBAND_FEC_REQUEST, 2) == api.error(16)); + Require(api.ctl(state.data(), OPUS_SET_INBAND_FEC_REQUEST, 1) == 0 && Get(api, state.data(), OPUS_GET_INBAND_FEC_REQUEST) == 1); + for (int flag : {2, -1}) + Require(api.ctl(state.data(), 4050, flag) == api.error(17)); + Require(api.ctl(state.data(), 4050, 0) == 0 && Get(api, state.data(), 4051) == 0); + Require(api.ctl(state.data(), 4050, 1) == 0 && Get(api, state.data(), 4051) == 1); + } + Require(api.ctl(state.data(), OPUS_RESET_STATE) == 0); + Require(Get(api, state.data(), OPUS_GET_GAIN_REQUEST) == 0 && Get(api, state.data(), OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST) == 0); + Require(Get(api, state.data(), OPUS_GET_SAMPLE_RATE_REQUEST) == 24000); + if (!api.celt) Require(Get(api, state.data(), OPUS_GET_INBAND_FEC_REQUEST) == 0 && Get(api, state.data(), 4051) == 0); + Require(api.destroy(state.data()) == 0); + Require(api.terminate(&context) == 0); +} + +void RequireDecoding(const Api& api, const std::vector>& packets) { + std::uint32_t context = 0; + Require(api.initialize(&context) == 0); + std::array state{}; + Require(api.createEx(&context, state.data(), 48000, 2) == 0); + Reference reference(48000); + opus_uint32 range = 0; + bool gainChanged = false; + for (std::size_t index = 0; index < 4; ++index) { + const auto expected = reference.Decode(packets[index], &range); + const auto plain = Decode(api, state.data(), packets[index], 960); + Require(plain == expected && static_cast(Get(api, state.data(), OPUS_GET_FINAL_RANGE_REQUEST)) == range); + } + Require(api.ctl(state.data(), OPUS_SET_GAIN_REQUEST, -1536) == 0); + Require(api.ctl(state.data(), OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, 1) == 0); + Require(opus_decoder_ctl(reference.decoder.get(), OPUS_SET_GAIN(-1536)) == OPUS_OK); + Require(opus_decoder_ctl(reference.decoder.get(), OPUS_SET_PHASE_INVERSION_DISABLED(1)) == OPUS_OK); + Reference unscaled(48000); + opus_uint32 unscaledRange = 0; + for (std::size_t index = 0; index < 4; ++index) unscaled.Decode(packets[index], &unscaledRange); + for (std::size_t index = 4; index < 8; ++index) { + const auto expected = reference.Decode(packets[index], &range); + const auto scaled = Decode(api, state.data(), packets[index], 960); + Require(scaled == expected && static_cast(Get(api, state.data(), OPUS_GET_FINAL_RANGE_REQUEST)) == range); + gainChanged = gainChanged || scaled != unscaled.Decode(packets[index], &unscaledRange); + } + Require(gainChanged); + Require(api.ctl(state.data(), OPUS_RESET_STATE) == 0); + Require(static_cast(Get(api, state.data(), OPUS_GET_FINAL_RANGE_REQUEST)) == range); + Reference fresh(48000); + for (std::size_t index = 8; index < packets.size(); ++index) { + const auto expected = fresh.Decode(packets[index], &range); + Require(Decode(api, state.data(), packets[index], 960) == expected); + } + Require(api.destroy(state.data()) == 0); + Require(api.terminate(&context) == 0); +} + +} // namespace + +int main() { + const Api opus{false, 0x81740000u, sceOpusDecInitialize, sceOpusDecTerminate, sceOpusDecCreateEx, sceOpusDecDecode, sceOpusDecCtl, sceOpusDecDestroy}; + const Api celt{true, 0x81400000u, sceOpusCeltDecInitialize, sceOpusCeltDecTerminate, sceOpusCeltDecCreateEx, sceOpusCeltDecDecode, sceOpusCeltDecCtl, sceOpusCeltDecDestroy}; + const auto packets = Packets(12); + for (const auto* api : {&opus, &celt}) { + RequireRequests(*api); + RequireDecoding(*api, packets); + } +} diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp index d894f17ec9..960817c100 100644 --- a/core/libs/tests/GuestPad.cpp +++ b/core/libs/tests/GuestPad.cpp @@ -4,6 +4,9 @@ #include "prx/libScePad/include/InputCommandFile.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include +#include +#include #include #include #include @@ -154,11 +157,23 @@ static void CheckStickDeadZones(int handle) { Require(info.stickInfo.deadZoneLeft == 13 && info.stickInfo.deadZoneRight == 13); } -static void CheckTouchContact() { +static void CheckTouchContact(int handle) { + Require(scePadReadState(handle, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadRead_nid_postfix(handle, nullptr, 1) == PAD_ERROR_INVALID_ARG); + for (const int invalid : {-1, 0, handle + 1, 9999}) { + Require(scePadReadState(invalid, nullptr) == PAD_ERROR_INVALID_HANDLE); + Require(scePadRead_nid_postfix(invalid, nullptr, 1) == PAD_ERROR_INVALID_HANDLE); + } PadInputState touch; touch.buttons = static_cast(Pad::PadButton::TouchPad); touch.touch[0] = {true, 960, 471, 0}; PadPublishInput_nid_postfix(touch); + PadData stateData{}; + Require(scePadReadState(handle, &stateData) == PAD_OK); + Require((stateData.buttons & static_cast(Pad::PadButton::TouchPad)) != 0); + Require(stateData.touch_data_touch_num == 1); + Require(stateData.touch_data_touch0_x == 960); + Require(stateData.touch_data_touch0_y == 471); const auto data = Pad::ReadState(); Require((data.buttons & static_cast(Pad::PadButton::TouchPad)) != 0); Require(data.touch_data_touch_num == 1); @@ -215,6 +230,10 @@ static void CheckHandleQueries(int user, int handle) { PadMotionSensorPosition position{1.0f, 2.0f, 3.0f}; Require(scePadGetMotionSensorPosition(handle, nullptr) == PAD_ERROR_INVALID_ARG); Require(scePadGetMotionSensorPosition(closed, &position) == PAD_ERROR_INVALID_HANDLE); + for (const int invalid : {-1, 0, 9999}) { + Require(scePadGetMotionSensorPosition(invalid, &position) == PAD_ERROR_INVALID_HANDLE); + Require(scePadGetMotionSensorPosition(invalid, nullptr) == PAD_ERROR_INVALID_ARG); + } Require(scePadGetMotionSensorPosition(handle, &position) == notConnected); Require(position.x == 1.0f && position.y == 2.0f && position.z == 3.0f); @@ -249,10 +268,15 @@ static void CheckHandleQueries(int user, int handle) { Require(!scePadIsValidHandle(special, PAD_PORT_TYPE_SPECIAL)); } -static void CheckVirtualTouchContact() { +static void CheckVirtualTouchContact(int handle) { PadInputState left; left.touchLeft = true; PadPublishInput_nid_postfix(left); + PadData leftState{}; + Require(scePadReadState(handle, &leftState) == PAD_OK); + Require(leftState.touch_data_touch_num == 1); + Require(leftState.touch_data_touch0_x == kTouchPadWidth / 4); + Require(leftState.touch_data_touch0_y == kTouchPadHeight / 2); const auto leftData = Pad::ReadState(); Require(leftData.touch_data_touch_num == 1); Require(leftData.touch_data_touch0_x == kTouchPadWidth / 4); @@ -261,6 +285,11 @@ static void CheckVirtualTouchContact() { PadInputState right; right.touchRight = true; PadPublishInput_nid_postfix(right); + PadData rightState{}; + Require(scePadReadState(handle, &rightState) == PAD_OK); + Require(rightState.touch_data_touch_num == 1); + Require(rightState.touch_data_touch0_x == kTouchPadWidth * 3 / 4); + Require(rightState.touch_data_touch0_y == kTouchPadHeight / 2); const auto rightData = Pad::ReadState(); Require(rightData.touch_data_touch_num == 1); Require(rightData.touch_data_touch0_x == kTouchPadWidth * 3 / 4); @@ -489,6 +518,12 @@ static void CheckSpecialPort(int user, int 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(scePadSetVibration(special, &vibration) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetVibration(special, nullptr) == PAD_ERROR_INVALID_HANDLE); + for (const int invalidHandle : {-1, -2, -100}) { + Require(scePadSetVibration(invalidHandle, &vibration) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetVibration(invalidHandle, nullptr) == PAD_ERROR_INVALID_HANDLE); + } Require(scePadGetHandle(user, 0, 0) == handle); Require(scePadReadState(handle, data) == PAD_OK); const int other = user + 1; @@ -644,6 +679,8 @@ static void CheckVibrationForce(int user, int handle) { Require(scePadSetVibrationForce(special, &strong) == PAD_OK && VibrationIs(0, 5, 6)); Require(scePadSetVibrationForce(special, nullptr) == PAD_ERROR_INVALID_ARG); Require(scePadClose_nid_postfix(special) == PAD_OK); + Require(scePadSetVibration(special, &strong) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetVibration(special, nullptr) == PAD_ERROR_INVALID_HANDLE); const PadVibrationParam off{0, 0}; Require(scePadSetVibrationForce(handle, &off) == PAD_OK && VibrationIs(0, 0, 0)); } @@ -663,6 +700,18 @@ static void CheckTriggerEffect(int user, int handle) { const std::array weapon{0x25, 0x24, 0x00, 0x07}; const std::array feedback{0x21, 0x80, 0x03, 0x00, 0x00, 0x40, 0x12}; const std::array off{0x05}; + Require(scePadSetTriggerEffect(handle, nullptr) == PAD_ERROR_INVALID_ARG); + for (const int other : {handle + 1, 0x999, -1}) Require(scePadSetTriggerEffect(other, param.data()) == PAD_ERROR_INVALID_HANDLE); + for (const int other : {handle + 1, 0x999, -1}) Require(scePadSetTriggerEffect(other, nullptr) == PAD_ERROR_INVALID_HANDLE); + param[0] = 0x4; + Require(scePadSetTriggerEffect(handle, param.data()) == PAD_ERROR_INVALID_ARG); + param[0] = 0; + command(0, 7, {0, 0, 0}); + Require(scePadSetTriggerEffect(handle, param.data()) == PAD_ERROR_INVALID_ARG); + command(0, 0, {0, 0, 0}); + command(1, 7, {0, 0, 0}); + Require(scePadSetTriggerEffect(handle, param.data()) == PAD_ERROR_INVALID_ARG); + command(1, 0, {0, 0, 0}); command(0, 2, {2, 5, 8}); command(1, 1, {7, 3, 0}); param[0] = 0x1; @@ -821,6 +870,29 @@ static void CheckInjectedQueueTimes(const std::filesystem::path& path) { PadPublishInput_nid_postfix(PadInputState{}); } +void CheckConcurrentSpecialOpens() { + constexpr int threads = 8; + for (int round = 0; round < 200; ++round) { + std::array handles{}; + std::atomic ready{0}; + std::vector workers; + for (int index = 0; index < threads; ++index) { + workers.emplace_back([&, index] { + ready.fetch_add(1); + while (ready.load() < threads) std::this_thread::yield(); + handles[index] = scePadOpen_nid_postfix(0x20000000 + index, PAD_PORT_TYPE_SPECIAL, 0, nullptr); + }); + } + for (auto& worker : workers) worker.join(); + const std::set distinct(handles.begin(), handles.end()); + Require(distinct.size() == threads); + for (const auto handle : handles) { + Require(handle >= 0); + Require(scePadClose_nid_postfix(handle) == PAD_OK); + } + } +} + static void CheckPortZeroWithdrawn() { constexpr int user = 0x10000000; constexpr int noHandle = static_cast(0x80920008); @@ -866,6 +938,7 @@ static void CheckPortZeroWithdrawn() { } int main() { + CheckConcurrentSpecialOpens(); CheckInputCommands(); std::uint32_t accessibilityState = 7; Require(scePadGetAccessibilityControllerState(PAD_HANDLE, nullptr) == PAD_ERROR_NOT_INITIALIZED); @@ -935,8 +1008,10 @@ int main() { constexpr int user = 0x10000000; Require(scePadGetHandle(user, 0, 0) == noHandle); + for (const int invalidUser : {-1, 0, 0x10000005}) Require(scePadGetHandle(invalidUser, 0, 0) == noHandle); Require(scePadOpen_nid_postfix(user, 1, 0, nullptr) == PAD_ERROR_INVALID_ARG); Require(scePadOpen_nid_postfix(user, 0, 1, nullptr) == static_cast(0x809b0003)); + for (const int type : {3, 4}) Require(scePadOpen_nid_postfix(user, type, 0, nullptr) == PAD_ERROR_INVALID_ARG); 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); @@ -967,12 +1042,13 @@ int main() { Require(handle > 0); Require(scePadUnknown_n3kSX62fgNo() == PAD_OK); Require(scePadGetHandle(user, 0, 0) == handle); + for (const int invalidUser : {-1, 0, 0x10000005}) Require(scePadGetHandle(invalidUser, 0, 0) == noHandle); CheckSpecialPort(user, handle); CheckTiltCorrection(handle); CheckAngularVelocityDeadband(handle); - CheckTouchContact(); + CheckTouchContact(handle); CheckHandleQueries(user, handle); - CheckVirtualTouchContact(); + CheckVirtualTouchContact(handle); CheckTouchPadInformation(handle); CheckStickDeadZones(handle); CheckExtControllerInformation(handle); @@ -1002,7 +1078,10 @@ int main() { Require(scePadResetLightBar(handle + 1) == PAD_ERROR_INVALID_HANDLE); Require(scePadSetMotionSensorState(handle, true) == PAD_OK); Require(scePadSetMotionSensorState(handle, false) == PAD_OK); - Require(scePadSetMotionSensorState(handle + 1, true) == PAD_ERROR_INVALID_HANDLE); + for (const int invalid : {-1, 0, handle + 1, 9999}) { + Require(scePadSetMotionSensorState(invalid, true) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetMotionSensorState(invalid, false) == PAD_ERROR_INVALID_HANDLE); + } Require(scePadSetVibrationMode(handle, 1) == 0); Require(scePadSetVibrationMode(handle, 2) == 0); Require(scePadSetVibrationMode(handle, 3) == 0); @@ -1079,7 +1158,11 @@ int main() { Require(scePadSetVibration(handle2, &vibFull) == PAD_OK); Require(scePadSetVibration(handle2, &vibMixed) == PAD_OK); Require(scePadSetVibration(handle2 + 99, &vibMixed) == PAD_ERROR_INVALID_HANDLE); - Require(scePadSetVibration(-1, &vibMixed) == PAD_ERROR_INVALID_HANDLE); + for (const int invalidHandle : {-1, -2, -100, -9999}) { + Require(scePadSetVibration(invalidHandle, &vibMixed) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetVibration(invalidHandle, nullptr) == PAD_ERROR_INVALID_HANDLE); + } + Require(scePadSetVibration(handle2 + 99, nullptr) == PAD_ERROR_INVALID_HANDLE); Require(scePadSetVibration(handle2, nullptr) == PAD_ERROR_INVALID_ARG); const PadLightBarParam lightOff{0, 0, 0}; @@ -1092,6 +1175,12 @@ int main() { Require(scePadSetLightBar(-1, &lightColor) == PAD_ERROR_INVALID_HANDLE); Require(scePadSetLightBar(handle2, nullptr) == PAD_ERROR_INVALID_ARG); + const std::array triggerZero{}; + Require(scePadSetTriggerEffect(handle2, triggerZero.data()) == PAD_OK); + Require(scePadSetTriggerEffect(handle2 + 99, triggerZero.data()) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetTriggerEffect(-1, triggerZero.data()) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetTriggerEffect(handle2, nullptr) == PAD_ERROR_INVALID_ARG); + PadInputState p0MotionSample{}; p0MotionSample.hasMotion = true; p0MotionSample.accel = {0.0f, 9.80665f, 0.0f}; diff --git a/core/libs/tests/GuestPadAudioRoute.cpp b/core/libs/tests/GuestPadAudioRoute.cpp new file mode 100644 index 0000000000..c1edff0d25 --- /dev/null +++ b/core/libs/tests/GuestPadAudioRoute.cpp @@ -0,0 +1,82 @@ +#include "SceTypes.hpp" +#include "prx/libScePad/include/Pad.hpp" +#include "prx/libScePad/include/PadState.hpp" +#include +#include + +static void RequireAt(bool value, int line) { if (!value) { std::fprintf(stderr, "check failed at line %d\n", line); std::abort(); } } +#define Require(value) RequireAt((value), __LINE__) + +static PadOutputState FetchAt(int port, std::uint32_t& seen, int line) { + PadOutputState out{}; + RequireAt(PadFetchOutputForPort_nid_postfix(port, &seen, &out), line); + return out; +} + +int main() { + const PadVolumeGain gain{40, 50, 0, 60}; + Require(scePadIsSupportedAudioFunction(1) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadSetAudioOutPath(1, 3) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadSetAudioOutPath(1, 9) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadSetVolumeGain(1, &gain) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadSetVolumeGain(1, nullptr) == PAD_ERROR_NOT_INITIALIZED); + + Require(scePadInit_nid_postfix() == PAD_OK); + for (int handle : {-1, 0, 1, 12345}) { + Require(scePadIsSupportedAudioFunction(handle) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetAudioOutPath(handle, 3) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetAudioOutPath(handle, 5) == PAD_ERROR_INVALID_ARG); + Require(scePadSetAudioOutPath(handle, -1) == PAD_ERROR_INVALID_ARG); + Require(scePadSetVolumeGain(handle, &gain) == PAD_ERROR_INVALID_HANDLE); + Require(scePadSetVolumeGain(handle, nullptr) == PAD_ERROR_INVALID_ARG); + } + + const int handle = scePadOpen_nid_postfix(0x10000000, PAD_PORT_TYPE_STANDARD, 0, nullptr); + Require(handle > 0); + std::uint32_t seen = 0xffffffffu; + PadOutputState out = FetchAt(0, seen, __LINE__); + Require(out.audioOutPath == 0 && !out.volumeGainValid); + + Require(scePadIsSupportedAudioFunction(handle) == 1); + for (int path : {0, 1, 2, 3, 4}) { + Require(scePadSetAudioOutPath(handle, path) == PAD_OK); + if (path != 0) { + out = FetchAt(0, seen, __LINE__); + Require(out.audioOutPath == path); + } + } + Require(scePadSetAudioOutPath(handle, 5) == PAD_ERROR_INVALID_ARG); + Require(scePadSetAudioOutPath(handle, 0x7fffffff) == PAD_ERROR_INVALID_ARG); + Require(!PadFetchOutputForPort_nid_postfix(0, &seen, &out)); + Require(scePadSetAudioOutPath(handle, 4) == PAD_OK); + Require(!PadFetchOutputForPort_nid_postfix(0, &seen, &out)); + + Require(scePadSetVolumeGain(handle, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadSetVolumeGain(handle, &gain) == PAD_OK); + out = FetchAt(0, seen, __LINE__); + Require(out.volumeGainValid && out.speakerVolume == 40 && out.headsetVolume == 50 && out.micGain == 60); + Require(scePadSetVolumeGain(handle, &gain) == PAD_OK); + Require(!PadFetchOutputForPort_nid_postfix(0, &seen, &out)); + const PadVolumeGain other{0, 255, 7, 1}; + Require(scePadSetVolumeGain(handle, &other) == PAD_OK); + out = FetchAt(0, seen, __LINE__); + Require(out.speakerVolume == 0 && out.headsetVolume == 255 && out.micGain == 1); + + PadSetPortConnected_nid_postfix(0, false); + Require(scePadIsSupportedAudioFunction(handle) == static_cast(0x80920007)); + Require(scePadSetAudioOutPath(handle, 1) == PAD_OK); + PadSetPortConnected_nid_postfix(0, true); + Require(scePadClose_nid_postfix(handle) == PAD_OK); + Require(scePadSetAudioOutPath(handle, 2) == PAD_OK); + + const int special = scePadOpen_nid_postfix(0x10000000, PAD_PORT_TYPE_SPECIAL, 0, nullptr); + Require(special > 0); + Require(scePadIsSupportedAudioFunction(special) == static_cast(0x80920007)); + Require(scePadSetAudioOutPath(special, 1) == PAD_OK); + Require(scePadSetAudioOutPath(special, 5) == PAD_ERROR_INVALID_ARG); + Require(scePadSetVolumeGain(special, &gain) == PAD_OK); + Require(scePadSetVolumeGain(special, nullptr) == PAD_ERROR_INVALID_ARG); + out = FetchAt(0, seen, __LINE__); + Require(out.audioOutPath == 2); + Require(scePadClose_nid_postfix(special) == PAD_OK); +} diff --git a/core/libs/tests/GuestPadControllerQueries.cpp b/core/libs/tests/GuestPadControllerQueries.cpp new file mode 100644 index 0000000000..e2f48f6480 --- /dev/null +++ b/core/libs/tests/GuestPadControllerQueries.cpp @@ -0,0 +1,69 @@ +#include "SceTypes.hpp" +#include "prx/libScePad/include/Pad.hpp" +#include "prx/libScePad/include/PadState.hpp" +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +int main() { + int busType = -1; + int jack = -1; + PadContainerIdInformation container{}; + Require(scePadGetParticularMode() == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetControllerBusType(1, &busType) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetControllerBusType(1, nullptr) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetJackState(1, &jack) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadIsControllerUpdateRequired(1) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetContainerIdInformation(1, &container) == PAD_ERROR_NOT_INITIALIZED); + Require(busType == -1 && jack == -1); + + Require(scePadInit_nid_postfix() == PAD_OK); + Require(scePadGetParticularMode() == 0); + Require(scePadSetParticularMode(true) == PAD_OK); + Require(scePadGetParticularMode() == 1); + Require(scePadSetParticularMode(false) == PAD_OK); + Require(scePadGetParticularMode() == 0); + + for (int handle : {-1, 0, 1, 12345}) { + Require(scePadGetControllerBusType(handle, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetJackState(handle, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetContainerIdInformation(handle, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetControllerBusType(handle, &busType) == PAD_ERROR_INVALID_HANDLE); + Require(scePadGetJackState(handle, &jack) == PAD_ERROR_INVALID_HANDLE); + Require(scePadIsControllerUpdateRequired(handle) == PAD_ERROR_INVALID_HANDLE); + Require(scePadGetContainerIdInformation(handle, &container) == PAD_ERROR_INVALID_HANDLE); + } + Require(busType == -1 && jack == -1 && container.size == 0); + + const int handle = scePadOpen_nid_postfix(0x10000000, PAD_PORT_TYPE_STANDARD, 0, nullptr); + Require(handle > 0); + Require(scePadGetControllerBusType(handle, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetControllerBusType(handle, &busType) == PAD_OK); + Require(busType == PAD_CONNECT_TYPE_BLUETOOTH); + Require(scePadGetJackState(handle, &jack) == PAD_OK); + Require(jack == 0); + Require(scePadIsControllerUpdateRequired(handle) == 0); + std::memset(&container, 0x5a, sizeof(container)); + Require(scePadGetContainerIdInformation(handle, &container) == PAD_OK); + Require(container.size == 0 && container.id[0] == '\0'); + + PadSetPortConnected_nid_postfix(0, false); + Require(scePadGetControllerBusType(handle, &busType) == static_cast(0x80920007)); + Require(scePadGetJackState(handle, &jack) == static_cast(0x80920007)); + Require(scePadIsControllerUpdateRequired(handle) == static_cast(0x80920007)); + Require(scePadGetContainerIdInformation(handle, &container) == static_cast(0x80920007)); + PadSetPortConnected_nid_postfix(0, true); + Require(scePadGetJackState(handle, &jack) == PAD_OK); + + Require(scePadClose_nid_postfix(handle) == PAD_OK); + Require(scePadGetJackState(handle, &jack) == PAD_OK); + + const int special = scePadOpen_nid_postfix(0x10000000, PAD_PORT_TYPE_SPECIAL, 0, nullptr); + Require(special > 0); + Require(scePadGetControllerBusType(special, &busType) == static_cast(0x80920007)); + Require(scePadGetJackState(special, &jack) == static_cast(0x80920007)); + Require(scePadIsControllerUpdateRequired(special) == static_cast(0x80920007)); + Require(scePadGetContainerIdInformation(special, &container) == static_cast(0x80920007)); + Require(scePadClose_nid_postfix(special) == PAD_OK); +} diff --git a/core/libs/tests/GuestPadFirmwareChecks.cpp b/core/libs/tests/GuestPadFirmwareChecks.cpp new file mode 100644 index 0000000000..3c359ade31 --- /dev/null +++ b/core/libs/tests/GuestPadFirmwareChecks.cpp @@ -0,0 +1,62 @@ +#include "SceTypes.hpp" +#include "prx/libScePad/include/Pad.hpp" +#include "prx/libScePad/include/PadState.hpp" +#include +#include +#include + +using ReadHistoryFn = int (APS5_VABI*)(std::int32_t, PadHistoryData*, std::uint32_t); +using OpenExt2Fn = int (APS5_VABI*)(std::int32_t, std::int32_t, const void*); + +static void Require(bool value) { if (!value) std::abort(); } + +template +static Function As(Export function) { + return reinterpret_cast(function); +} + +int main() { + const auto readHistory = As(&scePadReadHistory); + const auto openExt2 = As(&scePadOpenExt2); + alignas(8) std::array buffer{}; + std::array param{}; + std::uint32_t capability = 7; + std::uint64_t count = 7; + std::uint64_t activeTime = 7; + std::int32_t deviceId = 7; + auto* data = buffer.data(); + + Require(scePadGetDeviceInfo(0, reinterpret_cast(data)) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetCapability(0, &capability) == PAD_ERROR_NOT_INITIALIZED && capability == 7); + Require(scePadGetVersionInfo(1, reinterpret_cast(data)) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetIdleCount(1) == 0); + Require(scePadGetControllerIdleCount(1, &count) == PAD_ERROR_NOT_INITIALIZED && count == 7); + Require(scePadGetActiveTime(1, 0, &activeTime) == PAD_ERROR_NOT_INITIALIZED && activeTime == 7); + Require(scePadSetPS4BcVibrationMode(0) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetInfoByPortType(1, reinterpret_cast(data)) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetInfoByPortType(0, reinterpret_cast(data)) == PAD_ERROR_NOT_INITIALIZED); + Require(scePadGetInfoByPortType(2, nullptr) == PAD_ERROR_INVALID_ARG && scePadGetInfoByPortType(1, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetDeviceId(1, &deviceId) == PAD_ERROR_NOT_INITIALIZED && deviceId == 7); + Require(scePadGetExtensionUnitInfo(1, reinterpret_cast(data)) == PAD_ERROR_NOT_INITIALIZED); + Require(readHistory(1, reinterpret_cast(data), 0x960) == PAD_ERROR_NOT_INITIALIZED); + Require(openExt2(0x10000000, 0x10, param.data()) == PAD_ERROR_NOT_INITIALIZED); + + Require(scePadInit_nid_postfix() == PAD_OK); + Require(scePadGetDeviceInfo(0, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetCapability(0, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetVersionInfo(1, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetControllerIdleCount(0, &count) == PAD_ERROR_INVALID_ARG && scePadGetControllerIdleCount(1, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetActiveTime(1, 0, nullptr) == PAD_ERROR_INVALID_ARG); + for (int type : {1, 3, 4, -1}) Require(scePadGetInfoByPortType(type, reinterpret_cast(data)) == PAD_ERROR_INVALID_ARG); + Require(scePadGetInfoByPortType(1, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetDeviceId(1, nullptr) == PAD_ERROR_INVALID_ARG); + Require(scePadGetDeviceId(12345, &deviceId) == PAD_ERROR_INVALID_HANDLE && deviceId == -1); + Require(scePadGetExtensionUnitInfo(1, nullptr) == PAD_ERROR_INVALID_ARG); + for (std::uint32_t size : {0u, 0x48u, 0x958u, 0x961u}) Require(readHistory(1, reinterpret_cast(data), size) == PAD_ERROR_INVALID_ARG); + Require(openExt2(0x10000000, 0x8, param.data()) == PAD_ERROR_INVALID_ARG && openExt2(0x10000000, 0x10, nullptr) == PAD_ERROR_INVALID_ARG); + + const int handle = scePadOpen_nid_postfix(0x10000000, PAD_PORT_TYPE_STANDARD, 0, nullptr); + Require(handle > 0); + Require(scePadReadStateExt(handle, nullptr) == PAD_ERROR_INVALID_ARG); + return 0; +} diff --git a/core/libs/tests/GuestPathErrors.cpp b/core/libs/tests/GuestPathErrors.cpp index 9eb21a8c7a..ca40f6829d 100644 --- a/core/libs/tests/GuestPathErrors.cpp +++ b/core/libs/tests/GuestPathErrors.cpp @@ -56,6 +56,13 @@ void OpenAndUnlink() { Check(sceKernelOpen(LongName.c_str(), SCE_KERNEL_O_RDONLY, 0) == SceEnametoolong); Check(sceKernelOpen((LongName + "/inside").c_str(), SCE_KERNEL_O_RDONLY, 0) == SceEnametoolong); Check(sceKernelOpen(LongName.c_str(), SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT, 0644) == SceEnametoolong); + Check(sceKernelOpen("missing-create/", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT, 0644) == SCE_KERNEL_ERROR_ENOENT); + Check(PosixFails(open_nid_postfix("missing-create/", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT, 0644), 2)); + Check(sceKernelOpen("missing-create-exclusive/", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL, 0644) == SCE_KERNEL_ERROR_ENOENT); + Check(PosixFails(open_nid_postfix("missing-create-exclusive/", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT | SCE_KERNEL_O_EXCL, 0644), 2)); + Check(!std::filesystem::exists("missing-create") && !std::filesystem::exists("missing-create-exclusive")); + Check(sceKernelOpen("loop/", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT, 0644) == SceEloop); + Check(PosixFails(open_nid_postfix("loop/", SCE_KERNEL_O_WRONLY | SCE_KERNEL_O_CREAT, 0644), GuestEloop)); Check(PosixFails(open_nid_postfix("loop", SCE_KERNEL_O_RDONLY, 0), GuestEloop)); Check(PosixFails(open_nid_postfix(LongName.c_str(), SCE_KERNEL_O_RDONLY, 0), GuestEnametoolong)); Check(sceKernelUnlink(LongName.c_str()) == SceEnametoolong); diff --git a/core/libs/tests/GuestPathNameLength.cpp b/core/libs/tests/GuestPathNameLength.cpp index c6197d2e8f..0276453546 100644 --- a/core/libs/tests/GuestPathNameLength.cpp +++ b/core/libs/tests/GuestPathNameLength.cpp @@ -1,4 +1,6 @@ #include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include @@ -24,6 +26,18 @@ bool TooLong(const std::string& path) { return Fails(stat_nid_postfix(path.c_str(), reinterpret_cast(status)), ErrorNameTooLong); } +void RequireSceTooLong(const std::string& path, const char* what) { + unsigned char status[256]; + const char* name = path.c_str(); + const int results[] = {sceKernelStat(name, reinterpret_cast(status)), sceKernelUnlink(name), sceKernelMkdir(name, 0755), sceKernelRmdir(name), sceKernelChmod_nid_postfix(name, 0644), sceKernelTruncate_nid_postfix(name, 0), sceKernelUtimes_nid_postfix(name, nullptr)}; + const char* calls[] = {"sceKernelStat", "sceKernelUnlink", "sceKernelMkdir", "sceKernelRmdir", "sceKernelChmod", "sceKernelTruncate", "sceKernelUtimes"}; + for (int index = 0; index < 7; ++index) { + if (results[index] == SCE_KERNEL_ERROR_ENAMETOOLONG) continue; + std::fprintf(stderr, "%s: %s returned 0x%X\n", what, calls[index], static_cast(results[index])); + std::abort(); + } +} + } int main() { @@ -49,9 +63,11 @@ int main() { 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)"); + RequireSceTooLong(block + block + block + block, "the sce path calls must fail a path of exactly 1024 bytes with SCE_KERNEL_ERROR_ENAMETOOLONG"); Require(rmdir_nid_postfix("anyps5_name_length") == 0, "rmdir of the test directory failed"); 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)"); + RequireSceTooLong(longPath, "the sce path calls must fail a path of 1100 bytes with SCE_KERNEL_ERROR_ENAMETOOLONG"); 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)"); diff --git a/core/libs/tests/GuestPipeFault.cpp b/core/libs/tests/GuestPipeFault.cpp new file mode 100644 index 0000000000..e36f8f8cc2 --- /dev/null +++ b/core/libs/tests/GuestPipeFault.cpp @@ -0,0 +1,30 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" + +#include +#include +#include +#include + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "pipe fault check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static constexpr int GuestEfault = 14; + +int main() { + int first[2]{-1, -1}; + Require(pipe_nid_postfix(first) == 0); + Require(close_nid_postfix(first[0]) == 0 && close_nid_postfix(first[1]) == 0); + for (const std::uintptr_t address : {std::uintptr_t{1}, ~std::uintptr_t{0xfff}}) { + *__error_nid_postfix() = 0; + Require(pipe_nid_postfix(reinterpret_cast(address)) == -1 && *__error_nid_postfix() == GuestEfault); + } + int second[2]{-1, -1}; + Require(pipe_nid_postfix(second) == 0 && second[0] == first[0] && second[1] == first[1]); + Require(close_nid_postfix(second[0]) == 0 && close_nid_postfix(second[1]) == 0); +} diff --git a/core/libs/tests/GuestPlayGo.cpp b/core/libs/tests/GuestPlayGo.cpp index d0321f63e9..8e62a04c2b 100644 --- a/core/libs/tests/GuestPlayGo.cpp +++ b/core/libs/tests/GuestPlayGo.cpp @@ -291,6 +291,79 @@ static int CheckNoPlayGoFileAnswered(const char* root, const char* answer) { return 0; } +static int CheckNoPlayGoFileExplicit(const char* root, const char* value) { + constexpr int badChunkId = static_cast(0x80B2000C); + SetUnlistedChunks(value); + std::filesystem::current_path(root); + PlayGoInitParams init{}; + int handle = 0; + Require(scePlayGoInitialize(&init) == 0); + Require(scePlayGoOpen(&handle, nullptr) == 0); + const std::uint16_t listed[2] = {0, 1}; + std::int8_t loci[2] = {0, 0}; + Require(scePlayGoGetLocus(handle, listed, 2, loci) == 0 && loci[0] == 3 && loci[1] == 3); + const std::uint16_t unlisted[2] = {2, 999}; + Require(scePlayGoGetLocus(handle, unlisted, 2, loci) == badChunkId); + std::uint32_t chunkCount = 0; + Require(scePlayGoGetChunkId(handle, nullptr, 0, &chunkCount) == 0 && chunkCount == 2); + std::uint16_t ids[2] = {0xFFFF, 0xFFFF}; + Require(scePlayGoGetChunkId(handle, ids, 2, &chunkCount) == 0 && chunkCount == 2 && ids[0] == 0 && ids[1] == 1); + PlayGoProgress progress{}; + Require(scePlayGoGetProgress(handle, listed, 2, &progress) == 0 && progress.progress_size == 1 && progress.total_size == 1); + Require(scePlayGoGetProgress(handle, unlisted, 2, &progress) == badChunkId); + Require(scePlayGoClose(handle) == 0); + return 0; +} + +static int CheckNoPlayGoFileExplicitInvalid(const char* root, const char* value) { + SetUnlistedChunks(value); + std::filesystem::current_path(root); + PlayGoInitParams init{}; + int handle = 0; + Require(scePlayGoInitialize(&init) == 0); + Require(scePlayGoOpen(&handle, nullptr) == 0); + const std::uint16_t chunk = 0; + std::int8_t locus = 0; + bool threw = false; + try { + scePlayGoGetLocus(handle, &chunk, 1, &locus); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()).find("APS5_PLAYGO_UNLISTED_CHUNKS must be installed, absent, or a comma-separated list") != std::string_view::npos; + } + Require(threw); + return 0; +} + +static int CheckPackageChunkTableExplicit(const char* root) { + SetUnlistedChunks("0,1"); + return CheckPackageChunkTable(root); +} + +static int CheckScenarioWithoutChunkTableExplicit(const char* root) { + constexpr int badChunkId = static_cast(0x80B2000C); + SetUnlistedChunks("0,1"); + std::filesystem::current_path(root); + PlayGoInitParams init{}; + int handle = 0; + Require(scePlayGoInitialize(&init) == 0); + Require(scePlayGoOpen(&handle, nullptr) == 0); + const std::uint16_t listed[2] = {0, 999}; + std::int8_t loci[2] = {0, 0}; + 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); + const std::uint16_t unlisted = 1000; + bool threw = false; + try { + scePlayGoGetLocus(handle, &unlisted, 1, loci); + } catch (const std::runtime_error& error) { + threw = std::string_view(error.what()).find("APS5_PLAYGO_UNLISTED_CHUNKS=installed or =absent") != std::string_view::npos; + } + Require(threw); + Require(scePlayGoClose(handle) == 0); + return 0; +} + static int CheckScenarioWithoutChunkTableAnswered(const char* root, const char* answer) { constexpr int badChunkId = static_cast(0x80B2000C); SetUnlistedChunks(answer); @@ -326,7 +399,7 @@ static int CheckUnknownUnlistedChunkAnswer(const char* root) { 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; + threw = std::string_view(error.what()).find("APS5_PLAYGO_UNLISTED_CHUNKS must be installed, absent, or a comma-separated list") != std::string_view::npos; } Require(threw); return 0; @@ -362,7 +435,14 @@ int main(int argc, char** argv) { if (mode == "--no-file-installed") return CheckNoPlayGoFileAnswered(argv[2], "installed"); if (mode == "--no-file-absent") return CheckNoPlayGoFileAnswered(argv[2], "absent"); if (mode == "--no-file-unknown-answer") return CheckUnknownUnlistedChunkAnswer(argv[2]); + if (mode == "--no-file-explicit") return CheckNoPlayGoFileExplicit(argv[2], "0,1"); + if (mode == "--no-file-explicit-one") return CheckNoPlayGoFileExplicit(argv[2], "1"); + if (mode == "--no-file-explicit-empty") return CheckNoPlayGoFileExplicitInvalid(argv[2], "0,"); + if (mode == "--no-file-explicit-range") return CheckNoPlayGoFileExplicitInvalid(argv[2], "65536"); + if (mode == "--no-file-explicit-text") return CheckNoPlayGoFileExplicitInvalid(argv[2], "0,x"); if (mode == "--package-chunk-table" || mode == "--scenario-with-chunk-table") return CheckPackageChunkTable(argv[2]); + if (mode == "--package-chunk-table-explicit") return CheckPackageChunkTableExplicit(argv[2]); + if (mode == "--scenario-only-explicit") return CheckScenarioWithoutChunkTableExplicit(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"); @@ -374,7 +454,14 @@ int main(int argc, char** argv) { RunLayout(argv[0], "--no-file-installed", {}); RunLayout(argv[0], "--no-file-absent", {}); RunLayout(argv[0], "--no-file-unknown-answer", {}); + RunLayout(argv[0], "--no-file-explicit", {}); + RunLayout(argv[0], "--no-file-explicit-one", {}); + RunLayout(argv[0], "--no-file-explicit-empty", {}); + RunLayout(argv[0], "--no-file-explicit-range", {}); + RunLayout(argv[0], "--no-file-explicit-text", {}); RunLayout(argv[0], "--package-chunk-table", {{"playgo-chunk.dat", ChunkTableFile()}}); + RunLayout(argv[0], "--package-chunk-table-explicit", {{"playgo-chunk.dat", ChunkTableFile()}}); + RunLayout(argv[0], "--scenario-only-explicit", {{"playgo-scenario.json", "{}"}}); 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; diff --git a/core/libs/tests/GuestPlayReady.cpp b/core/libs/tests/GuestPlayReady.cpp index 07892d95de..a90c9cb0d4 100644 --- a/core/libs/tests/GuestPlayReady.cpp +++ b/core/libs/tests/GuestPlayReady.cpp @@ -33,6 +33,7 @@ void CheckBufferInit() { } sceBufferInit(nullptr); Require(scePlayReadyBufferInit(nullptr) == InvalidArgument, "scePlayReadyBufferInit rejects a null buffer"); + Require(scePlayReadyBufferInit(reinterpret_cast(0x10)) == InvalidArgument, "scePlayReadyBufferInit rejects an unwritable buffer"); } void CheckInitParams() { @@ -63,6 +64,7 @@ void CheckVersion() { Require(std::memcmp(version, expected[index], sizeof(expected[index])) == 0, "scePlayReadyApiVersion writes the version"); Require(Filled(reinterpret_cast(version), 32, sizeof(version), 0xA5), "scePlayReadyApiVersion writes 32 bytes"); Require(scePlayReadyApiVersion(index, nullptr) == InvalidArgument, "scePlayReadyApiVersion rejects a null output"); + Require(scePlayReadyApiVersion(index, reinterpret_cast(0x10)) == InvalidArgument, "scePlayReadyApiVersion rejects an unwritable output"); } for (uint32_t index : {2u, 3u, 0x80000000u, 0xFFFFFFFFu}) { uint64_t version[4]; diff --git a/core/libs/tests/GuestPlayerReviewDialog.cpp b/core/libs/tests/GuestPlayerReviewDialog.cpp index 1f608a2e58..6d64f2bb15 100644 --- a/core/libs/tests/GuestPlayerReviewDialog.cpp +++ b/core/libs/tests/GuestPlayerReviewDialog.cpp @@ -12,6 +12,7 @@ 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_INVALID_ARGUMENT = static_cast(0x80B8000Au); constexpr int COMMON_DIALOG_ERROR_NOT_FINISHED = static_cast(0x80B80005u); constexpr int COMMON_DIALOG_ERROR_ARG_NULL = static_cast(0x80B8000Du); @@ -76,6 +77,7 @@ int main() { Require(scePlayerReviewDialogGetStatus() == COMMON_DIALOG_STATUS_FINISHED); Require(scePlayerReviewDialogUpdateStatus() == COMMON_DIALOG_STATUS_FINISHED); Require(scePlayerReviewDialogGetResult(nullptr) == COMMON_DIALOG_ERROR_ARG_NULL); + Require(scePlayerReviewDialogGetResult(reinterpret_cast(0x10)) == COMMON_DIALOG_ERROR_INVALID_ARGUMENT); 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); diff --git a/core/libs/tests/GuestPlayerSelectionDialog.cpp b/core/libs/tests/GuestPlayerSelectionDialog.cpp index 80a87cfd4a..56e768ca4a 100644 --- a/core/libs/tests/GuestPlayerSelectionDialog.cpp +++ b/core/libs/tests/GuestPlayerSelectionDialog.cpp @@ -1,6 +1,7 @@ #include "prx/libScePlayerSelectionDialog/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include +#include #include #include #include @@ -11,10 +12,14 @@ 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 kErrInvalidArg = static_cast(0x80B8000A); +constexpr int kErrNotRunning = static_cast(0x80B8000B); constexpr int kErrArgNull = static_cast(0x80B8000D); constexpr int kResultUserCanceled = 1; @@ -35,6 +40,9 @@ int main() { Require(scePlayerSelectionDialogOpen(nullptr) == kErrArgNull); Require(scePlayerSelectionDialogParamInitialize(nullptr) == kErrArgNull); Require(scePlayerSelectionDialogParamInitialize(¶m) == 0); + errno = 71; + Require(scePlayerSelectionDialogParamInitialize(reinterpret_cast(0x10)) == kErrArgNull); + Require(errno == 71); Require(param.base.size == sizeof(param.base)); Require(param.base.magic == static_cast(reinterpret_cast(¶m)) - 1060003575u); Require(param.size == sizeof(param)); @@ -54,59 +62,165 @@ int main() { Require(scePlayerSelectionDialogClose() == kErrNotInitialized); Require(scePlayerSelectionDialogGetResult(nullptr) == kErrArgNull); Require(scePlayerSelectionDialogGetResult(&result) == kErrNotInitialized); - Require(scePlayerSelectionDialogGetStatus() == 0); - Require(scePlayerSelectionDialogUpdateStatus() == 0); + Require(scePlayerSelectionDialogGetStatus() == kStatusNone); + Require(scePlayerSelectionDialogUpdateStatus() == kStatusNone); Require(scePlayerSelectionDialogInitialize() == 0); Require(scePlayerSelectionDialogInitialize() == kErrAlreadyInitialized); - Require(scePlayerSelectionDialogGetStatus() == 1); - Require(scePlayerSelectionDialogUpdateStatus() == 1); + Require(scePlayerSelectionDialogGetStatus() == kStatusInitialized); + Require(scePlayerSelectionDialogUpdateStatus() == kStatusInitialized); Require(scePlayerSelectionDialogOpen(nullptr) == kErrArgNull); Require(scePlayerSelectionDialogGetResult(&result) == kErrNotFinished); - Require(scePlayerSelectionDialogOpen(¶m) == 0); - Require(scePlayerSelectionDialogGetStatus() == kStatusFinished); + + param.base.size = sizeof(param.base) - 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.base.size = sizeof(param.base) + 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.base.size = sizeof(param.base); + + const auto savedMagic = param.base.magic; + param.base.magic = 0; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.base.magic = savedMagic + 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.base.magic = savedMagic; + + param.base.reserved[0] = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.base.reserved[0] = 0; + param.base.reserved[35] = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.base.reserved[35] = 0; param.size = 0; - Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.size = sizeof(param) - 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); param.size = sizeof(param); + + param.userId = -1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.userId = 0; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.userId = 42; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.userId = 999; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.userId = USER_SERVICE_INITIAL_USER_ID; + param.maxPlayers = 257; - Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.maxPlayers = 256; + Require(scePlayerSelectionDialogOpen(¶m) == 0); + Require(scePlayerSelectionDialogGetStatus() == kStatusFinished); + Require(scePlayerSelectionDialogClose() == kErrNotRunning); + param.maxPlayers = 1; param.mode = 4; - Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.mode = 3; + Require(scePlayerSelectionDialogOpen(¶m) == 0); param.mode = 0; + + param.playerCount = 257; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); 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.players = nullptr; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + + PlayerSelectionDialogPlayer singlePlayer{}; + param.players = &singlePlayer; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + singlePlayer.accountId = 1; + singlePlayer.option0 = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); 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; + singlePlayer.option0 = 2; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + singlePlayer.option0 = 1; + singlePlayer.option1 = 2; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + singlePlayer.option1 = 1; + singlePlayer.mode = 3; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + singlePlayer.mode = 2; + singlePlayer.reserved[0] = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + singlePlayer.reserved[0] = 0; + singlePlayer.reserved[63] = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + singlePlayer.reserved[63] = 0; + param.reserved[0] = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + param.reserved[0] = 0; param.reserved[63] = 1; - Require(scePlayerSelectionDialogOpen(¶m) == static_cast(0x80B8000Au)); + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); param.reserved[63] = 0; Require(scePlayerSelectionDialogOpen(¶m) == 0); Require(scePlayerSelectionDialogGetStatus() == kStatusFinished); Require(scePlayerSelectionDialogUpdateStatus() == kStatusFinished); + PlayerSelectionDialogPlayer multiPlayers[2]{}; + multiPlayers[0].accountId = 1; + multiPlayers[0].option0 = 0; + multiPlayers[0].option1 = 0; + multiPlayers[0].mode = 0; + multiPlayers[1].accountId = 2; + multiPlayers[1].option0 = 0; + multiPlayers[1].option1 = 0; + multiPlayers[1].mode = 0; + param.players = multiPlayers; + param.playerCount = 2; + param.maxPlayers = 2; + Require(scePlayerSelectionDialogOpen(¶m) == 0); + + multiPlayers[1].accountId = 0; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + multiPlayers[1].accountId = 2; + + multiPlayers[1].option0 = 2; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + multiPlayers[1].option0 = 0; + + multiPlayers[1].option1 = 2; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + multiPlayers[1].option1 = 0; + + multiPlayers[1].mode = 3; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + multiPlayers[1].mode = 0; + + multiPlayers[1].reserved[0] = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + multiPlayers[1].reserved[0] = 0; + + multiPlayers[1].reserved[63] = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + multiPlayers[1].reserved[63] = 0; + + param.maxPlayers = 1; + multiPlayers[0].option0 = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + multiPlayers[0].option0 = 0; + multiPlayers[1].option0 = 1; + Require(scePlayerSelectionDialogOpen(¶m) == kErrInvalidArg); + multiPlayers[1].option0 = 0; + + param.maxPlayers = 2; + multiPlayers[0].option0 = 1; + multiPlayers[1].option0 = 1; + Require(scePlayerSelectionDialogOpen(¶m) == 0); + Require(scePlayerSelectionDialogGetResult(nullptr) == kErrArgNull); + errno = 71; + Require(scePlayerSelectionDialogGetResult(reinterpret_cast(0x10)) == kErrArgNull); + Require(errno == 71); Require(scePlayerSelectionDialogGetResult(&result) == kResultUserCanceled); Require(result.result == kResultUserCanceled); Require(result.option == 99 && result.accountIds == nullptr && result.count == 77 && result.padding == 55); - Require(scePlayerSelectionDialogClose() == static_cast(0x80B8000Bu)); + Require(scePlayerSelectionDialogClose() == kErrNotRunning); Require(scePlayerSelectionDialogTerminate() == 0); Require(scePlayerSelectionDialogTerminate() == kErrNotInitialized); Require(scePlayerSelectionDialogOpen(nullptr) == kErrArgNull); @@ -114,7 +228,15 @@ int main() { Require(scePlayerSelectionDialogClose() == kErrNotInitialized); Require(scePlayerSelectionDialogGetResult(nullptr) == kErrArgNull); Require(scePlayerSelectionDialogGetResult(&result) == kErrNotInitialized); - Require(scePlayerSelectionDialogGetStatus() == 0); - Require(scePlayerSelectionDialogUpdateStatus() == 0); + Require(scePlayerSelectionDialogGetStatus() == kStatusNone); + Require(scePlayerSelectionDialogUpdateStatus() == kStatusNone); + + Require(scePlayerSelectionDialogInitialize() == 0); + Require(scePlayerSelectionDialogInitialize() == kErrAlreadyInitialized); + Require(scePlayerSelectionDialogGetStatus() == kStatusInitialized); + Require(scePlayerSelectionDialogOpen(¶m) == 0); + Require(scePlayerSelectionDialogGetStatus() == kStatusFinished); + Require(scePlayerSelectionDialogGetResult(&result) == kResultUserCanceled); + Require(scePlayerSelectionDialogTerminate() == 0); Require(sceUserServiceTerminate() == 0); } diff --git a/core/libs/tests/GuestPoll.cpp b/core/libs/tests/GuestPoll.cpp index c00934f83f..be82e99f0a 100644 --- a/core/libs/tests/GuestPoll.cpp +++ b/core/libs/tests/GuestPoll.cpp @@ -327,10 +327,33 @@ static void PollsAHungUpSocketWithoutPollout() { } #endif +static void PollsAPipeClosedDuringTheWait() { + for (int attempt = 0; attempt < 200; ++attempt) { + int pair[2]{-1, -1}; + Require(pipe_nid_postfix(pair) == 0); + int result = 0; + short revents = 0; + std::atomic started{false}; + std::thread waiter([&] { + GuestPollDescriptor entry{pair[0], In, 0}; + started = true; + result = poll_nid_postfix(&entry, 1, 20); + revents = entry.revents; + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::microseconds(1000 + (attempt % 20) * 100)); + Require(close_nid_postfix(pair[0]) == 0); + waiter.join(); + Require(close_nid_postfix(pair[1]) == 0); + Require(result == 0 || (result == 1 && (revents & Invalid) != 0)); + } +} + int main() { for (const int descriptor : {0, 1, 2}) StandardDescriptorFile(descriptor); PollsUrgentData(); PollsAStreamWhosePeerClosed(); + PollsAPipeClosedDuringTheWait(); #ifndef _WIN32 PollsAHungUpSocketWithoutPollout(); #endif diff --git a/core/libs/tests/GuestPosixExit.cpp b/core/libs/tests/GuestPosixExit.cpp new file mode 100644 index 0000000000..53e577a825 --- /dev/null +++ b/core/libs/tests/GuestPosixExit.cpp @@ -0,0 +1,80 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" +#include "SceTypes.hpp" + +#include +#include +#include + +namespace { + +void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +struct Context { + PthreadKey key = 0; + int stored = 0; + int exitValue = 0; + bool exitWithValue = true; + std::atomic afterExit{false}; + std::atomic sequence{0}; + std::atomic cleanupOrder{0}; + std::atomic destructorOrder{0}; + std::atomic cleanupCalls{0}; + std::atomic destructorCalls{0}; + std::atomic destructorValue{nullptr}; +}; + +Context* g_context = nullptr; + +void APS5_VABI Cleanup(void* argument) { + auto& context = *static_cast(argument); + context.cleanupOrder = ++context.sequence; + ++context.cleanupCalls; +} + +void APS5_VABI Destroy(void* value) { + g_context->destructorValue = value; + g_context->destructorOrder = ++g_context->sequence; + ++g_context->destructorCalls; +} + +void* APS5_VABI Worker(void* argument) { + auto& context = *static_cast(argument); + Require(pthread_setspecific_nid_postfix(context.key, &context.stored) == 0, "worker setspecific returns 0"); + pthread_cleanup_push_nid_postfix(Cleanup, &context); + pthread_exit_nid_postfix(context.exitWithValue ? &context.exitValue : nullptr); + context.afterExit = true; + return argument; +} + +void Run(Context& context) { + g_context = &context; + Require(pthread_key_create_nid_postfix(&context.key, reinterpret_cast(Destroy)) == 0, "key create returns 0"); + Pthread thread = nullptr; + Require(pthread_create_nid_postfix(&thread, nullptr, Worker, &context) == 0, "create returns 0"); + void* result = &context; + Require(pthread_join_nid_postfix(thread, &result) == 0, "join returns 0"); + Require(!context.afterExit, "code after pthread_exit never runs"); + Require(context.destructorCalls == 1, "the key destructor runs once on pthread_exit"); + Require(context.cleanupCalls == 1, "the cleanup handler runs once on pthread_exit"); + Require(context.cleanupOrder < context.destructorOrder, "the cleanup handler runs before the key destructor"); + Require(context.destructorValue == &context.stored, "the key destructor receives the stored value"); + Require(result == (context.exitWithValue ? static_cast(&context.exitValue) : nullptr), "join returns the value passed to pthread_exit"); + Require(pthread_key_delete_nid_postfix(context.key) == 0, "key delete returns 0"); +} + +} + +int main() { + Context withValue; + Run(withValue); + Context withNull; + withNull.exitWithValue = false; + Run(withNull); + return 0; +} diff --git a/core/libs/tests/GuestPosixSem.cpp b/core/libs/tests/GuestPosixSem.cpp index aa3aea380f..6c66c1a240 100644 --- a/core/libs/tests/GuestPosixSem.cpp +++ b/core/libs/tests/GuestPosixSem.cpp @@ -115,6 +115,8 @@ int main(int argc, char** argv) { } return 0; } + *__error_nid_postfix() = 0; + Require(sem_post_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == 22, "null semaphore pointer in sem_post must fail with EINVAL"); Require(sem_init_nid_postfix(&sem, 0, maximum) == 0, "maximum count must initialize"); Require(sem_getvalue_nid_postfix(&sem, &value) == 0 && value == static_cast(maximum), "maximum count must be readable"); *__error_nid_postfix() = 0; @@ -130,6 +132,11 @@ int main(int argc, char** argv) { Require(sem_getvalue_nid_postfix(&sem, &value) == 0 && value == static_cast(maximum), "repeated overflow changed count"); Require(sem_destroy_nid_postfix(&sem) == 0, "destroy must succeed"); Require(sem_init_nid_postfix(&sem, 0, 0) == 0, "zero count must initialize"); + *__error_nid_postfix() = 0; + Require(sem_trywait_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == 22, "null semaphore pointer must report EINVAL"); + std::uintptr_t invalidSem = 0; + *__error_nid_postfix() = 0; + Require(sem_trywait_nid_postfix(&invalidSem) == -1 && *__error_nid_postfix() == 22, "uninitialized semaphore descriptor must report EINVAL"); Require(sem_trywait_nid_postfix(&sem) == -1 && *__error_nid_postfix() == 35, "empty semaphore must report EAGAIN"); Require(sem_post_nid_postfix(&sem) == 0, "normal post must succeed"); Require(sem_getvalue_nid_postfix(&sem, &value) == 0 && value == 1, "normal post must increment"); diff --git a/core/libs/tests/GuestPosixSpawnErrors.cpp b/core/libs/tests/GuestPosixSpawnErrors.cpp new file mode 100644 index 0000000000..771e1bd88e --- /dev/null +++ b/core/libs/tests/GuestPosixSpawnErrors.cpp @@ -0,0 +1,67 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "Posix spawn errors check failed at line %d\n", line); + std::exit(EXIT_FAILURE); + } +} +#define Check(value) Require((value), __LINE__) + +int main(int count, char** arguments) { + if (count == 2 && std::strcmp(arguments[1], "spawn-errors-child") == 0) return 43; + std::filesystem::create_directories("app0"); + { + std::ofstream image("app0/eboot.bin"); + image << "image"; + Check(image.good()); + } + std::filesystem::remove("app0/spawn-error-loop"); + std::filesystem::create_symlink("spawn-error-loop", "app0/spawn-error-loop"); + const std::array cases{ + std::pair{std::string("/app0/spawn-error-loop"), 62}, + std::pair{std::string("/app0/") + std::string(256, 'x'), 63}, + std::pair{std::string("/app0/eboot.bin"), 0} + }; + char program[] = "/app0/eboot.bin"; + char role[] = "spawn-errors-child"; + char* childArguments[] = {program, role, nullptr}; + bool passed = true; + for (auto spawn : std::array{&posix_spawn_nid_postfix, &posix_spawnp_nid_postfix}) { + for (const auto& [path, expected] : cases) { + PosixSpawnFileActions* actions = nullptr; + Check(posix_spawn_file_actions_init_nid_postfix(&actions) == 0); + Check(posix_spawn_file_actions_addopen_nid_postfix(&actions, 9, path.c_str(), 0, 0) == 0); + constexpr std::int32_t Sentinel = -12345; + std::int32_t process = Sentinel; + const int result = spawn(&process, program, &actions, nullptr, childArguments, nullptr); + Check(posix_spawn_file_actions_destroy_nid_postfix(&actions) == 0); + if (result == 0) { + Check(process > 0); + int status = -1; + Check(waitpid_nid_postfix(process, &status, 0) == process); + Check((status & 0x7f) == 0 && ((status >> 8) & 0xff) == 43); + } else { + Check(process == Sentinel); + } + if (result != expected) { + std::fprintf(stderr, "Spawn action %s: expected guest error %d, observed %d\n", path.c_str(), expected, result); + passed = false; + } + } + } + Check(std::filesystem::remove("app0/spawn-error-loop")); + Check(std::filesystem::remove("app0/eboot.bin")); + Check(std::filesystem::remove("app0")); + return passed ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/core/libs/tests/GuestPosixSpawnOptionalPid.cpp b/core/libs/tests/GuestPosixSpawnOptionalPid.cpp new file mode 100644 index 0000000000..9d616b3caa --- /dev/null +++ b/core/libs/tests/GuestPosixSpawnOptionalPid.cpp @@ -0,0 +1,47 @@ +#include "prx/libSceLibcInternal/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include +#include +#include +#include +#include +#include + +static void Require(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "Posix spawn optional PID check failed at line %d\n", line); + std::exit(EXIT_FAILURE); + } +} +#define Check(value) Require((value), __LINE__) + +int main(int count, char** arguments) { +#ifdef _WIN32 + constexpr int ChildArgument = 2; +#else + constexpr int ChildArgument = 1; +#endif + if (count == ChildArgument + 1 && std::strcmp(arguments[ChildArgument], "spawn-optional-pid-child") == 0) return 43; + std::filesystem::create_directories("app0"); + { + std::ofstream image("app0/eboot.bin"); + image << "image"; + Check(image.good()); + } + char program[] = "/app0/eboot.bin"; + char role[] = "spawn-optional-pid-child"; + char* childArguments[] = {program, role, nullptr}; + const std::array spawnFunctions{&posix_spawn_nid_postfix, &posix_spawnp_nid_postfix}; + for (std::size_t index = 0; index < spawnFunctions.size(); ++index) { + const auto spawn = spawnFunctions[index]; + std::fprintf(stderr, "Spawn wrapper %zu: testing a null PID output\n", index); + Check(spawn(nullptr, program, nullptr, nullptr, childArguments, nullptr) == 0); + int status = -1; + Check(waitpid_nid_postfix(-1, &status, 0) > 0); + Check((status & 0x7f) == 0 && ((status >> 8) & 0xff) == 43); + Check(spawn(nullptr, "/app0/missing.bin", nullptr, nullptr, childArguments, nullptr) == 2); + Check(spawn(nullptr, program, nullptr, nullptr, nullptr, nullptr) == 14); + } + Check(std::filesystem::remove("app0/eboot.bin")); + Check(std::filesystem::remove("app0")); +} diff --git a/core/libs/tests/GuestPthreadAttr.cpp b/core/libs/tests/GuestPthreadAttr.cpp index b29f772084..770f317e89 100644 --- a/core/libs/tests/GuestPthreadAttr.cpp +++ b/core/libs/tests/GuestPthreadAttr.cpp @@ -2,6 +2,7 @@ #include "prx/libkernel/include/Api.hpp" #include "prx/libc/include/Api.hpp" #include "SceTypes.hpp" +#include #include #include #include @@ -53,15 +54,45 @@ static ReportedAttributes Query(Pthread thread) { static void CheckStackSizeLimit() { PthreadAttr attr = nullptr; + PthreadAttr unset = nullptr; + Require(scePthreadAttrSetstacksize(nullptr, MIN_STACK_SIZE) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetstacksize(&unset, MIN_STACK_SIZE) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrInit(&attr) == SCE_OK); std::size_t stackSize = 0; Require(scePthreadAttrSetstacksize(&attr, MIN_STACK_SIZE) == SCE_OK); Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK && stackSize == MIN_STACK_SIZE); - Require(scePthreadAttrSetstacksize(&attr, MIN_STACK_SIZE - 1) == SCE_KERNEL_ERROR_EINVAL); - Require(scePthreadAttrSetstacksize(&attr, 0) == SCE_KERNEL_ERROR_EINVAL); + for (const std::size_t rejected : std::array{0, 1, 512, 4096, 8192, MIN_STACK_SIZE - 1}) { + Require(scePthreadAttrSetstacksize(&attr, rejected) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK && stackSize == MIN_STACK_SIZE); + } Require(pthread_attr_setstacksize_nid_postfix(&attr, MIN_STACK_SIZE - 1) == GUEST_EINVAL); Require(scePthreadAttrGetstacksize(&attr, &stackSize) == SCE_OK && stackSize == MIN_STACK_SIZE); Require(scePthreadAttrDestroy(&attr) == SCE_OK); + Require(scePthreadAttrSetstacksize(&attr, MIN_STACK_SIZE) == SCE_KERNEL_ERROR_EINVAL); +} + +static void CheckGuardSize() { + PthreadAttr attr = nullptr; + std::size_t guardSize = 0; + PthreadAttr unset = nullptr; + Require(scePthreadAttrGetguardsize(nullptr, &guardSize) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrGetguardsize(&unset, &guardSize) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetguardsize(nullptr, 4096) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetguardsize(&unset, 4096) == SCE_KERNEL_ERROR_EINVAL); + + Require(scePthreadAttrInit(&attr) == SCE_OK); + Require(scePthreadAttrGetguardsize(&attr, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrGetguardsize(&attr, &guardSize) == SCE_OK && guardSize == 4096); + + for (const std::size_t size : std::array{0, 4096, 8192, 16384, 65536}) { + Require(scePthreadAttrSetguardsize(&attr, size) == SCE_OK); + guardSize = 0; + Require(scePthreadAttrGetguardsize(&attr, &guardSize) == SCE_OK && guardSize == size); + } + + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + Require(scePthreadAttrGetguardsize(&attr, &guardSize) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetguardsize(&attr, 4096) == SCE_KERNEL_ERROR_EINVAL); } static void CheckSoloSched() { @@ -126,6 +157,34 @@ static void CheckSchedArguments() { Require(scePthreadAttrDestroy(&attr) == SCE_OK); } +static void CheckSchedPolicyAndStackGetters() { + PthreadAttr unset = nullptr; + PthreadAttr destroyed = nullptr; + PthreadAttr live = nullptr; + Require(scePthreadAttrInit(&destroyed) == SCE_OK); + Require(scePthreadAttrDestroy(&destroyed) == SCE_OK); + Require(scePthreadAttrInit(&live) == SCE_OK); + for (PthreadAttr* invalid : {static_cast(nullptr), &unset, &destroyed}) { + int policy = -1; + KernelSchedParam param{-1}; + void* address = &policy; + std::size_t size = 7; + Require(pthread_attr_getschedpolicy_nid_postfix(invalid, &policy) == GUEST_EINVAL && policy == -1); + Require(pthread_attr_getschedparam_nid_postfix(invalid, ¶m) == GUEST_EINVAL && param.sched_priority == -1); + Require(pthread_attr_getstacksize_nid_postfix(invalid, &size) == GUEST_EINVAL && size == 7); + Require(pthread_attr_getstackaddr_nid_postfix(invalid, &address) == GUEST_EINVAL && address == &policy); + Require(pthread_attr_getstack_nid_postfix(invalid, &address, &size) == GUEST_EINVAL && address == &policy && size == 7); + } + int policy = -1; + void* address = &policy; + std::size_t size = 7; + Require(pthread_attr_getschedpolicy_nid_postfix(&live, nullptr) == GUEST_EINVAL); + Require(pthread_attr_getstackaddr_nid_postfix(&live, nullptr) == GUEST_EINVAL); + Require(pthread_attr_getstack_nid_postfix(&live, nullptr, &size) == GUEST_EINVAL && size == 7); + Require(pthread_attr_getstack_nid_postfix(&live, &address, nullptr) == GUEST_EINVAL && address == &policy); + Require(scePthreadAttrDestroy(&live) == SCE_OK); +} + static void* APS5_VABI Worker(void* arg) { static_cast*>(arg)->get(); return nullptr; @@ -134,18 +193,27 @@ static void* APS5_VABI Worker(void* arg) { static void CheckSchedulingAttributes() { int inherit = -1; int scope = -1; + PthreadAttr unset = nullptr; Require(pthread_attr_getinheritsched_nid_postfix(nullptr, &inherit) == GUEST_EINVAL); Require(pthread_attr_setinheritsched_nid_postfix(nullptr, EXPLICIT_SCHED) == GUEST_EINVAL); Require(pthread_attr_getscope_nid_postfix(nullptr, &scope) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(nullptr, SCOPE_SYSTEM) == GUEST_EINVAL); + Require(scePthreadAttrGetinheritsched(nullptr, &inherit) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrGetinheritsched(&unset, &inherit) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetinheritsched(nullptr, EXPLICIT_SCHED) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetinheritsched(&unset, EXPLICIT_SCHED) == SCE_KERNEL_ERROR_EINVAL); PthreadAttr attr = nullptr; Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == GUEST_EINVAL); Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == GUEST_EINVAL); Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_SYSTEM) == GUEST_EINVAL); + Require(scePthreadAttrGetinheritsched(&attr, &inherit) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetinheritsched(&attr, EXPLICIT_SCHED) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadAttrInit(&attr) == SCE_OK); + Require(scePthreadAttrGetinheritsched(&attr, nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrGetinheritsched(&attr, &inherit) == SCE_OK && inherit == INHERIT_SCHED); Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == 0 && inherit == INHERIT_SCHED); Require(pthread_attr_setinheritsched_nid_postfix(&attr, 1) == GUEST_ENOTSUP); Require(pthread_attr_setinheritsched_nid_postfix(&attr, -1) == GUEST_ENOTSUP); @@ -154,6 +222,19 @@ static void CheckSchedulingAttributes() { Require(pthread_attr_setinheritsched_nid_postfix(&attr, INHERIT_SCHED) == 0); Require(pthread_attr_getinheritsched_nid_postfix(&attr, &inherit) == 0 && inherit == INHERIT_SCHED); + Require(scePthreadAttrSetinheritsched(&attr, EXPLICIT_SCHED) == SCE_OK); + inherit = -1; + Require(scePthreadAttrGetinheritsched(&attr, &inherit) == SCE_OK && inherit == EXPLICIT_SCHED); + Require(scePthreadAttrSetinheritsched(&attr, INHERIT_SCHED) == SCE_OK); + inherit = -1; + Require(scePthreadAttrGetinheritsched(&attr, &inherit) == SCE_OK && inherit == INHERIT_SCHED); + + for (const int rejected : {-1, 1, 2, 3, 5, 8, 16, 100}) { + Require(scePthreadAttrSetinheritsched(&attr, rejected) == SCE_KERNEL_ERROR_EOPNOTSUPP); + inherit = -1; + Require(scePthreadAttrGetinheritsched(&attr, &inherit) == SCE_OK && inherit == INHERIT_SCHED); + } + Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == 0 && scope == SCOPE_SYSTEM); Require(pthread_attr_setscope_nid_postfix(&attr, 1) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(&attr, 3) == GUEST_EINVAL); @@ -172,6 +253,8 @@ static void CheckSchedulingAttributes() { Require(pthread_attr_setinheritsched_nid_postfix(&attr, EXPLICIT_SCHED) == GUEST_EINVAL); Require(pthread_attr_getscope_nid_postfix(&attr, &scope) == GUEST_EINVAL); Require(pthread_attr_setscope_nid_postfix(&attr, SCOPE_SYSTEM) == GUEST_EINVAL); + Require(scePthreadAttrGetinheritsched(&attr, &inherit) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadAttrSetinheritsched(&attr, EXPLICIT_SCHED) == SCE_KERNEL_ERROR_EINVAL); } static void CheckThreadScope() { @@ -334,14 +417,27 @@ static void CheckGetprioOfTheCallingThread() { Require(scePthreadGetprio(self, &priority) == SCE_OK && priority == original); } +static void CheckSetStackSizeOnUnsetAttributes() { + PthreadAttr unset = nullptr; + PthreadAttr destroyed = nullptr; + Require(scePthreadAttrInit(&destroyed) == SCE_OK); + Require(scePthreadAttrDestroy(&destroyed) == SCE_OK); + Require(pthread_attr_setstacksize_nid_postfix(nullptr, STACK_SIZE) == GUEST_EINVAL); + Require(pthread_attr_setstacksize_nid_postfix(&unset, STACK_SIZE) == GUEST_EINVAL); + Require(pthread_attr_setstacksize_nid_postfix(&destroyed, STACK_SIZE) == GUEST_EINVAL); +} + int main() { CheckGetprioOfTheCallingThread(); CheckStackSizeLimit(); + CheckGuardSize(); CheckSoloSched(); CheckSchedArguments(); CheckSchedulingAttributes(); + CheckSchedPolicyAndStackGetters(); CheckThreadScope(); CheckEndedThreads(); + CheckSetStackSizeOnUnsetAttributes(); PthreadAttr attr = nullptr; Require(scePthreadAttrInit(&attr) == SCE_OK); Require(scePthreadAttrSetinheritsched(&attr, EXPLICIT_SCHED) == SCE_OK); diff --git a/core/libs/tests/GuestPthreadBarrier.cpp b/core/libs/tests/GuestPthreadBarrier.cpp index df85bc1cbb..597a37a7bf 100644 --- a/core/libs/tests/GuestPthreadBarrier.cpp +++ b/core/libs/tests/GuestPthreadBarrier.cpp @@ -101,10 +101,16 @@ int main() { Require(rejected); PthreadBarrierPrivate* invalid = nullptr; + Require(pthread_barrier_destroy_nid_postfix(nullptr) == GUEST_EINVAL); + Require(pthread_barrier_destroy_nid_postfix(&invalid) == GUEST_EINVAL); Require(pthread_barrier_init_nid_postfix(nullptr, &attr, 1) == GUEST_EINVAL); + Require(pthread_barrier_init_nid_postfix(nullptr, nullptr, 1) == GUEST_EINVAL); + Require(pthread_barrier_init_nid_postfix(nullptr, nullptr, 0) == GUEST_EINVAL); + Require(pthread_barrier_init_nid_postfix(&invalid, nullptr, 0) == GUEST_EINVAL && invalid == nullptr); 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); + Require(pthread_barrier_destroy_nid_postfix(&invalid) == GUEST_EINVAL); Context withAttr; Require(pthread_barrier_init_nid_postfix(&withAttr.barrier, &attr, WAITERS) == 0 && withAttr.barrier != nullptr); @@ -133,8 +139,10 @@ int main() { } PthreadBarrierPrivate* single = nullptr; + Require(pthread_barrier_destroy_nid_postfix(nullptr) == GUEST_EINVAL); Require(pthread_barrierattr_init_nid_postfix(&attr) == 0); Require(pthread_barrier_init_nid_postfix(&single, &attr, 1) == 0); + Require(pthread_barrier_wait_nid_postfix(nullptr) == GUEST_EINVAL); Require(pthread_barrier_wait_nid_postfix(&single) == BARRIER_SERIAL_THREAD); Require(pthread_barrier_wait_nid_postfix(&single) == BARRIER_SERIAL_THREAD); Require(pthread_barrier_destroy_nid_postfix(&single) == 0); diff --git a/core/libs/tests/GuestPthreadCondattr.cpp b/core/libs/tests/GuestPthreadCondattr.cpp index 0aab5e73df..0ac21c203c 100644 --- a/core/libs/tests/GuestPthreadCondattr.cpp +++ b/core/libs/tests/GuestPthreadCondattr.cpp @@ -26,7 +26,7 @@ static void CheckClockRefused(const PthreadCondattr* attr, KernelClockid* clockI bool threw = false; try { pthread_condattr_getclock_nid_postfix(attr, clockId); - } catch (const std::runtime_error&) { + } catch (const std::invalid_argument&) { threw = true; } Require(threw); @@ -44,6 +44,8 @@ int main() { Require(scePthreadCondattrSetclock(&unset, Monotonic) == SceEinval); Require(scePthreadCondattrDestroy(nullptr) == SceEinval); Require(scePthreadCondattrDestroy(&unset) == SceEinval); + Require(pthread_condattr_destroy_nid_postfix(nullptr) == GuestEinval); + Require(pthread_condattr_destroy_nid_postfix(&unset) == GuestEinval); Require(pthread_condattr_setclock_nid_postfix(&unset, Monotonic) == GuestEinval); Require(pthread_condattr_setpshared_nid_postfix(nullptr, 0) == GuestEinval); Require(pthread_condattr_setpshared_nid_postfix(&unset, 0) == GuestEinval); @@ -70,18 +72,21 @@ int main() { CheckClock(&other, Monotonic); CheckClock(&attr, Monotonic); Require(pthread_condattr_destroy_nid_postfix(&other) == 0 && other == nullptr); + Require(pthread_condattr_destroy_nid_postfix(&other) == GuestEinval); + Require(pthread_condattr_destroy_nid_postfix(nullptr) == GuestEinval); Require(pthread_condattr_getclock_nid_postfix(&other, &clockId) == GuestEinval && clockId == -1); Require(scePthreadCondattrSetpshared(&attr, 1) == SceEinval); Require(scePthreadCondattrSetpshared(&attr, 0) == SCE_OK); Require(scePthreadCondattrGetpshared(&attr, &sharing) == SCE_OK && sharing == 0); Require(pthread_condattr_getpshared_nid_postfix(&attr, &sharing) == 0 && sharing == 0); - for (const KernelClockid clock : {-1, 3, 5, 12, 13, 14, 15}) { + for (const KernelClockid clock : {-1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}) { Require(scePthreadCondattrSetclock(&attr, clock) == SceEinval); CheckClock(&attr, Monotonic); Require(pthread_condattr_setclock_nid_postfix(&attr, clock) == GuestEinval); CheckClock(&attr, Monotonic); } - for (const int invalid : {1, 2, -1}) { + for (const int invalid : {1, 2, -1, 100, static_cast(0x7fffffff)}) { + Require(scePthreadCondattrSetpshared(&attr, invalid) == SceEinval); Require(pthread_condattr_setpshared_nid_postfix(&attr, invalid) == GuestEinval); sharing = -1; Require(pthread_condattr_getpshared_nid_postfix(&attr, &sharing) == 0 && sharing == 0); @@ -100,6 +105,9 @@ int main() { Require(scePthreadCondattrDestroy(&attr) == SCE_OK && attr == nullptr); Require(pthread_condattr_getclock_nid_postfix(&attr, &clockId) == GuestEinval && clockId == -1); Require(scePthreadCondattrDestroy(&attr) == SceEinval); + Require(scePthreadCondattrDestroy(nullptr) == SceEinval); + Require(pthread_condattr_destroy_nid_postfix(&attr) == GuestEinval); + Require(pthread_condattr_destroy_nid_postfix(nullptr) == GuestEinval); sharing = -1; Require(pthread_condattr_getpshared_nid_postfix(&attr, &sharing) == GuestEinval && sharing == -1); Require(pthread_condattr_setpshared_nid_postfix(&attr, 0) == GuestEinval); diff --git a/core/libs/tests/GuestPthreadDetachAttr.cpp b/core/libs/tests/GuestPthreadDetachAttr.cpp new file mode 100644 index 0000000000..b84d74dffd --- /dev/null +++ b/core/libs/tests/GuestPthreadDetachAttr.cpp @@ -0,0 +1,57 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; +static constexpr int ROUNDS = 50; +static constexpr int DETACHED = 1; + +static void Require(bool condition) { if (!condition) std::abort(); } + +struct Worker { + std::atomic release{false}; + std::atomic finished{false}; +}; + +static void* APS5_VABI Wait(void* opaque) { + auto* worker = static_cast(opaque); + while (!worker->release.load()) std::this_thread::yield(); + worker->finished.store(true); + return nullptr; +} + +int main() { + for (int round = 0; round < ROUNDS; ++round) { + Worker worker; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, Wait, &worker, "detach_attr") == SCE_OK); + std::atomic observing{true}; + std::atomic started{false}; + std::thread observer([&] { + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == SCE_OK); + while (observing.load()) { + Require(scePthreadAttrGet(thread, &attr) == SCE_OK); + started.store(true); + } + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + }); + while (!started.load()) std::this_thread::yield(); + Require(scePthreadDetach(thread) == SCE_OK); + PthreadAttr attr = nullptr; + Require(scePthreadAttrInit(&attr) == SCE_OK); + Require(scePthreadAttrGet(thread, &attr) == SCE_OK); + int state = -1; + Require(scePthreadAttrGetdetachstate(&attr, &state) == SCE_OK && state == DETACHED); + Require(scePthreadAttrDestroy(&attr) == SCE_OK); + observing.store(false); + observer.join(); + worker.release.store(true); + while (!worker.finished.load()) std::this_thread::yield(); + } + return 0; +} diff --git a/core/libs/tests/GuestPthreadFinalReleaseRace.cpp b/core/libs/tests/GuestPthreadFinalReleaseRace.cpp new file mode 100644 index 0000000000..76007755e6 --- /dev/null +++ b/core/libs/tests/GuestPthreadFinalReleaseRace.cpp @@ -0,0 +1,104 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include +#include +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; + +extern "C" void APS5_Test_SetFinalThreadReleaseHook(Pthread thread, void (*hook)(Pthread)); + +static void Require(bool condition) { if (!condition) std::abort(); } + +struct Worker { + std::atomic started{false}; + std::atomic release{false}; +}; + +static void* APS5_VABI Wait(void* opaque) { + auto* worker = static_cast(opaque); + worker->started.store(true); + while (!worker->release.load()) std::this_thread::yield(); + return nullptr; +} + +struct FinalReleaseGate { + std::mutex mutex; + std::condition_variable changed; + Pthread thread = nullptr; + bool reached = false; + bool release = false; +}; + +static FinalReleaseGate gate; + +static void HoldFinalRelease(Pthread thread) { + std::unique_lock lock(gate.mutex); + Require(gate.thread == thread); + gate.reached = true; + gate.changed.notify_all(); + gate.changed.wait(lock, [] { return gate.release; }); +} + +static void WaitForFinalRelease() { + std::unique_lock lock(gate.mutex); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + Require(gate.changed.wait_until(lock, deadline, [] { return gate.reached; })); +} + +static void ResumeFinalRelease() { + { + std::lock_guard lock(gate.mutex); + gate.release = true; + } + gate.changed.notify_all(); +} + +static void WaitUntilReleased(Pthread thread) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + int priority = 0; + while (scePthreadGetprio(thread, &priority) != SCE_KERNEL_ERROR_ESRCH) { + Require(std::chrono::steady_clock::now() < deadline); + std::this_thread::yield(); + } +} + +static void RequireAdmissionRejectsFinalRelease(int operation) { + Worker worker; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, Wait, &worker, "release_race") == SCE_OK); + while (!worker.started.load()) std::this_thread::yield(); + Require(scePthreadDetach(thread) == SCE_OK); + + { + std::lock_guard lock(gate.mutex); + gate.thread = thread; + gate.reached = false; + gate.release = false; + } + APS5_Test_SetFinalThreadReleaseHook(thread, HoldFinalRelease); + worker.release.store(true); + WaitForFinalRelease(); + + int result = SCE_OK; + if (operation == 0) result = scePthreadJoin(thread, nullptr); + else if (operation == 1) result = scePthreadDetach(thread); + else result = scePthreadCancel(thread); + + ResumeFinalRelease(); + WaitUntilReleased(thread); + Require(result == SCE_KERNEL_ERROR_ESRCH); +} + +int main() { + for (int round = 0; round < 20; ++round) { + RequireAdmissionRejectsFinalRelease(0); + RequireAdmissionRejectsFinalRelease(1); + RequireAdmissionRejectsFinalRelease(2); + } + return 0; +} diff --git a/core/libs/tests/GuestPthreadHandleAfterExit.cpp b/core/libs/tests/GuestPthreadHandleAfterExit.cpp new file mode 100644 index 0000000000..411680441f --- /dev/null +++ b/core/libs/tests/GuestPthreadHandleAfterExit.cpp @@ -0,0 +1,71 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; + +static void Require(bool condition) { if (!condition) std::abort(); } + +struct Worker { + std::atomic started{false}; + std::atomic release{false}; +}; + +static void* APS5_VABI Wait(void* opaque) { + auto* worker = static_cast(opaque); + worker->started.store(true); + while (!worker->release.load()) std::this_thread::yield(); + return nullptr; +} + +static void RequireAlive(Pthread thread) { + char name[32]; + int priority = 0; + KernelCpumask mask = 0; + Require(scePthreadGetname(thread, name) == SCE_OK); + Require(scePthreadGetprio(thread, &priority) == SCE_OK); + Require(scePthreadGetaffinity(thread, &mask) == SCE_OK && mask != 0); + Require(scePthreadSetaffinity(thread, mask) == SCE_OK); + Require(scePthreadRename(thread, "renamed") == SCE_OK); +} + +static void RequireEnded(Pthread thread) { + char name[32]; + int priority = 0; + KernelCpumask mask = 0; + Require(scePthreadGetname(thread, name) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadGetprio(thread, &priority) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadGetaffinity(thread, &mask) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadSetaffinity(thread, 1) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadRename(thread, "renamed") == SCE_KERNEL_ERROR_ESRCH); +} + +static void WaitUntilReleased(Pthread thread) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + int priority = 0; + while (scePthreadGetprio(thread, &priority) != SCE_KERNEL_ERROR_ESRCH) { + Require(std::chrono::steady_clock::now() < deadline); + std::this_thread::yield(); + } +} + +int main() { + RequireAlive(scePthreadSelf()); + + for (int round = 0; round < 20; ++round) { + Worker detached; + Pthread detachedThread = nullptr; + Require(scePthreadCreate(&detachedThread, nullptr, Wait, &detached, "detached") == SCE_OK); + while (!detached.started.load()) std::this_thread::yield(); + RequireAlive(detachedThread); + Require(scePthreadDetach(detachedThread) == SCE_OK); + detached.release.store(true); + WaitUntilReleased(detachedThread); + RequireEnded(detachedThread); + } + return 0; +} diff --git a/core/libs/tests/GuestPthreadHostFaultMask.cpp b/core/libs/tests/GuestPthreadHostFaultMask.cpp new file mode 100644 index 0000000000..024e59079a --- /dev/null +++ b/core/libs/tests/GuestPthreadHostFaultMask.cpp @@ -0,0 +1,36 @@ +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include + +static constexpr int FaultSignals[] = {SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP}; +static void Require(bool value) { if (!value) std::abort(); } + +static void* APS5_VABI CheckMask(void* arg) { + sigset_t mask; + Require(pthread_sigmask(SIG_SETMASK, nullptr, &mask) == 0); + for (const int signal : FaultSignals) Require(sigismember(&mask, signal) == 0); + *static_cast(arg) = true; + return nullptr; +} + +int main() { + sigset_t oldMask; + sigset_t blocked; + sigemptyset(&blocked); + for (const int signal : FaultSignals) sigaddset(&blocked, signal); + Require(pthread_sigmask(SIG_BLOCK, &blocked, &oldMask) == 0); + + bool ran = false; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, CheckMask, &ran, "host-fault-mask") == 0); + + sigset_t creatorMask; + Require(pthread_sigmask(SIG_SETMASK, nullptr, &creatorMask) == 0); + for (const int signal : FaultSignals) Require(sigismember(&creatorMask, signal) == 1); + Require(pthread_sigmask(SIG_SETMASK, &oldMask, nullptr) == 0); + + Require(scePthreadJoin(thread, nullptr) == 0); + Require(ran); +} diff --git a/core/libs/tests/GuestPthreadMutexErrors.cpp b/core/libs/tests/GuestPthreadMutexErrors.cpp index 6a99ea4c97..587c57a449 100644 --- a/core/libs/tests/GuestPthreadMutexErrors.cpp +++ b/core/libs/tests/GuestPthreadMutexErrors.cpp @@ -120,14 +120,21 @@ int main() { for (int type : {1, 2, 3, 4}) Require(scePthreadMutexattrSettype(&attr, type) == SCE_OK); for (int protocol : {-1, 3}) Require(scePthreadMutexattrSetprotocol(&attr, protocol) == SCE_KERNEL_ERROR_EINVAL); for (int protocol : {0, 1, 2}) Require(scePthreadMutexattrSetprotocol(&attr, protocol) == SCE_OK); + for (int prioceiling : {-10, -1, 0, 255, 768, 1000}) Require(scePthreadMutexattrSetprioceiling(&attr, prioceiling) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetprioceiling(&attr, 300) == SCE_OK); PthreadMutexattr unset = nullptr; Require(scePthreadMutexattrSettype(&unset, 1) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadMutexattrSetprotocol(&unset, 0) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetprioceiling(&unset, 300) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadMutexattrDestroy(&unset) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadMutexattrSettype(nullptr, 1) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrSetprioceiling(nullptr, 300) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadMutexattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(Throws([] { pthread_mutex_destroy_nid_postfix(nullptr); })); + Require(Throws([] { scePthreadMutexDestroy(nullptr); })); + PthreadMutex mutex = nullptr; Require(scePthreadMutexInit(&mutex, &unset, "unset") == SCE_OK); Require(scePthreadMutexLock(&mutex) == SCE_OK); diff --git a/core/libs/tests/GuestPthreadMutexattr.cpp b/core/libs/tests/GuestPthreadMutexattr.cpp index 4568fc1464..675f5b8723 100644 --- a/core/libs/tests/GuestPthreadMutexattr.cpp +++ b/core/libs/tests/GuestPthreadMutexattr.cpp @@ -48,6 +48,8 @@ static void CheckTimedlockInvalidDeadline() { static void CheckType() { PthreadMutexattr attr = nullptr; + Require(pthread_mutexattr_destroy_nid_postfix(nullptr) == GUEST_EINVAL); + Require(pthread_mutexattr_destroy_nid_postfix(&attr) == GUEST_EINVAL); Require(pthread_mutexattr_init_nid_postfix(&attr) == 0); int type = -1; Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == 0 && type == 1); @@ -55,10 +57,13 @@ static void CheckType() { Require(pthread_mutexattr_settype_nid_postfix(&attr, set) == 0); Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == 0 && type == set); } + Require(pthread_mutexattr_settype_nid_postfix(nullptr, 1) == GUEST_EINVAL); Require(pthread_mutexattr_settype_nid_postfix(&attr, 5) == GUEST_EINVAL); Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == 0 && type == 1); Require(Throws([&] { pthread_mutexattr_gettype_nid_postfix(&attr, nullptr); })); Require(pthread_mutexattr_destroy_nid_postfix(&attr) == 0); + Require(pthread_mutexattr_destroy_nid_postfix(nullptr) == GUEST_EINVAL); + Require(pthread_mutexattr_destroy_nid_postfix(&attr) == GUEST_EINVAL); type = -1; Require(pthread_mutexattr_gettype_nid_postfix(&attr, &type) == GUEST_EINVAL && type == -1); Require(pthread_mutexattr_gettype_nid_postfix(nullptr, &type) == GUEST_EINVAL && type == -1); @@ -125,6 +130,8 @@ static void CheckKind() { static void CheckSceAccessors() { PthreadMutexattr attr = nullptr; int value = -1; + Require(scePthreadMutexattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL); 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); @@ -158,12 +165,16 @@ static void CheckSceAccessors() { Require(scePthreadMutexattrSetpshared(&attr, 0) == SCE_OK); Require(scePthreadMutexattrGetpshared(&attr, &value) == SCE_OK && value == 0); Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); + Require(scePthreadMutexattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadMutexattrGettype(&attr, &value) == SCE_KERNEL_ERROR_EINVAL); } static void CheckProcessSharing() { PthreadMutexattr attr = nullptr; int sharing = -1; + Require(pthread_mutexattr_destroy_nid_postfix(nullptr) == GUEST_EINVAL); + Require(pthread_mutexattr_destroy_nid_postfix(&attr) == GUEST_EINVAL); Require(pthread_mutexattr_getpshared_nid_postfix(nullptr, &sharing) == GUEST_EINVAL && sharing == -1); Require(pthread_mutexattr_getpshared_nid_postfix(&attr, &sharing) == GUEST_EINVAL && sharing == -1); Require(pthread_mutexattr_setpshared_nid_postfix(nullptr, 0) == GUEST_EINVAL); @@ -189,6 +200,8 @@ static void CheckProcessSharing() { Require(pthread_mutexattr_setpshared_nid_postfix(&attr, 0) == 0); Require(pthread_mutexattr_getpshared_nid_postfix(&attr, &sharing) == 0 && sharing == 0); Require(pthread_mutexattr_destroy_nid_postfix(&attr) == 0); + Require(pthread_mutexattr_destroy_nid_postfix(nullptr) == GUEST_EINVAL); + Require(pthread_mutexattr_destroy_nid_postfix(&attr) == GUEST_EINVAL); sharing = -1; Require(pthread_mutexattr_getpshared_nid_postfix(&attr, &sharing) == GUEST_EINVAL && sharing == -1); Require(pthread_mutexattr_setpshared_nid_postfix(&attr, 0) == GUEST_EINVAL); @@ -197,6 +210,8 @@ static void CheckProcessSharing() { static void CheckInvalidPriorityAttribute(PthreadMutexattr* attr) { int ceiling = -1; int protocol = -1; + Require(pthread_mutexattr_destroy_nid_postfix(attr) == GUEST_EINVAL); + Require(scePthreadMutexattrDestroy(attr) == SCE_KERNEL_ERROR_EINVAL); Require(pthread_mutexattr_setprotocol_nid_postfix(attr, PRIO_PROTECT) == GUEST_EINVAL); Require(pthread_mutexattr_setprotocol_nid_postfix(attr, -1) == GUEST_EINVAL); Require(pthread_mutexattr_getprotocol_nid_postfix(attr, &protocol) == GUEST_EINVAL && protocol == -1); @@ -206,12 +221,33 @@ static void CheckInvalidPriorityAttribute(PthreadMutexattr* attr) { Require(pthread_mutexattr_getprioceiling_nid_postfix(attr, nullptr) == GUEST_EINVAL); } +static void CheckDestroy() { + PthreadMutexattr attr = nullptr; + Require(pthread_mutexattr_destroy_nid_postfix(nullptr) == GUEST_EINVAL); + Require(pthread_mutexattr_destroy_nid_postfix(&attr) == GUEST_EINVAL); + Require(scePthreadMutexattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL); + Require(pthread_mutexattr_init_nid_postfix(&attr) == 0); + Require(pthread_mutexattr_destroy_nid_postfix(&attr) == 0); + Require(pthread_mutexattr_destroy_nid_postfix(nullptr) == GUEST_EINVAL); + Require(pthread_mutexattr_destroy_nid_postfix(&attr) == GUEST_EINVAL); + Require(scePthreadMutexattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrInit(&attr) == SCE_OK); + Require(scePthreadMutexattrDestroy(&attr) == SCE_OK); + Require(pthread_mutexattr_destroy_nid_postfix(nullptr) == GUEST_EINVAL); + Require(pthread_mutexattr_destroy_nid_postfix(&attr) == GUEST_EINVAL); + Require(scePthreadMutexattrDestroy(nullptr) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadMutexattrDestroy(&attr) == SCE_KERNEL_ERROR_EINVAL); +} + int main() { CheckType(); CheckKind(); CheckSceAccessors(); CheckProcessSharing(); CheckTimedlockInvalidDeadline(); + CheckDestroy(); PthreadMutexattr attr = nullptr; CheckInvalidPriorityAttribute(nullptr); CheckInvalidPriorityAttribute(&attr); diff --git a/core/libs/tests/GuestPthreadPosixCalls.cpp b/core/libs/tests/GuestPthreadPosixCalls.cpp index 91d08bad91..9ea4243501 100644 --- a/core/libs/tests/GuestPthreadPosixCalls.cpp +++ b/core/libs/tests/GuestPthreadPosixCalls.cpp @@ -65,6 +65,15 @@ int main() { Check(pthread_attr_destroy_nid_postfix(&attr) == 0); int afterDestroy = -1; Check(pthread_attr_getdetachstate_nid_postfix(&attr, &afterDestroy) == GuestEinval); + PthreadAttr unset = nullptr; + std::size_t guard = 7; + afterDestroy = -1; + for (PthreadAttr* invalid : {static_cast(nullptr), &unset, &attr}) { + Check(pthread_attr_getdetachstate_nid_postfix(invalid, &afterDestroy) == GuestEinval && afterDestroy == -1); + Check(pthread_attr_setdetachstate_nid_postfix(invalid, 0) == GuestEinval); + Check(pthread_attr_getguardsize_nid_postfix(invalid, &guard) == GuestEinval && guard == 7); + Check(pthread_attr_setguardsize_nid_postfix(invalid, 4096) == GuestEinval); + } Check(pthread_attr_init_nid_postfix(nullptr) == GuestEinval); Check(pthread_attr_destroy_nid_postfix(nullptr) == GuestEinval); diff --git a/core/libs/tests/GuestPthreadReleasedHandle.cpp b/core/libs/tests/GuestPthreadReleasedHandle.cpp new file mode 100644 index 0000000000..84edc39549 --- /dev/null +++ b/core/libs/tests/GuestPthreadReleasedHandle.cpp @@ -0,0 +1,59 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; + +static void Require(bool condition) { if (!condition) std::abort(); } + +struct Worker { + std::atomic started{false}; + std::atomic release{false}; +}; + +static void* APS5_VABI Wait(void* opaque) { + auto* worker = static_cast(opaque); + worker->started.store(true); + while (!worker->release.load()) std::this_thread::yield(); + return nullptr; +} + +static void WaitUntilReleased(Pthread thread) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + int priority = 0; + while (scePthreadGetprio(thread, &priority) != SCE_KERNEL_ERROR_ESRCH) { + Require(std::chrono::steady_clock::now() < deadline); + std::this_thread::yield(); + } +} + +int main() { + for (int round = 0; round < 20; ++round) { + Worker worker; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, Wait, &worker, "released") == SCE_OK); + while (!worker.started.load()) std::this_thread::yield(); + Require(scePthreadDetach(thread) == SCE_OK); + std::atomic finished{false}; + std::thread observer([&] { + while (!finished.load()) { + int priority = 0; + const int result = scePthreadGetprio(thread, &priority); + Require(result == SCE_OK || result == SCE_KERNEL_ERROR_ESRCH); + std::this_thread::yield(); + } + }); + worker.release.store(true); + WaitUntilReleased(thread); + finished.store(true); + observer.join(); + Require(scePthreadJoin(thread, nullptr) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadDetach(thread) == SCE_KERNEL_ERROR_ESRCH); + Require(scePthreadCancel(thread) == SCE_KERNEL_ERROR_ESRCH); + } + return 0; +} diff --git a/core/libs/tests/GuestPthreadRename.cpp b/core/libs/tests/GuestPthreadRename.cpp index ccfa5ced39..f4dbb24202 100644 --- a/core/libs/tests/GuestPthreadRename.cpp +++ b/core/libs/tests/GuestPthreadRename.cpp @@ -7,8 +7,29 @@ #include #include +#ifdef _WIN32 +#include +#endif + static void Require(bool value) { if (!value) std::abort(); } +#ifdef _WIN32 +static void CheckDescription(const std::wstring& expected) { + PWSTR description = nullptr; + Require(SUCCEEDED(GetThreadDescription(GetCurrentThread(), &description)) && description != nullptr); + const std::wstring actual(description); + LocalFree(description); + if (actual != expected) { + std::fprintf(stderr, "thread description units:"); + for (const wchar_t unit : actual) std::fprintf(stderr, " %04x", static_cast(unit)); + std::fprintf(stderr, "; expected:"); + for (const wchar_t unit : expected) std::fprintf(stderr, " %04x", static_cast(unit)); + std::fprintf(stderr, "\n"); + std::abort(); + } +} +#endif + static void CheckStoredName(Pthread thread, const std::string& expected) { std::array buffer; buffer.fill(0xaa); @@ -45,6 +66,11 @@ static void CheckThreadRename(Pthread thread) { static void* APS5_VABI Worker(void*) { const Pthread self = scePthreadSelf(); Require(self != nullptr); +#ifdef _WIN32 + CheckDescription(L"Z\u00e4hler"); + Require(scePthreadRename(self, "Z\xc3\xa4hler-\xe6\x97\xa5") == 0); + CheckDescription(L"Z\u00e4hler-\u65e5"); +#endif CheckThreadRename(self); return nullptr; } @@ -54,6 +80,6 @@ int main() { Require(self != nullptr); CheckThreadRename(self); Pthread thread = nullptr; - Require(scePthreadCreate(&thread, nullptr, Worker, nullptr, "rename-test") == 0); + Require(scePthreadCreate(&thread, nullptr, Worker, nullptr, "Z\xc3\xa4hler") == 0); Require(scePthreadJoin(thread, nullptr) == 0); } diff --git a/core/libs/tests/GuestPthreadRwlock.cpp b/core/libs/tests/GuestPthreadRwlock.cpp index f57954b010..0d95ba0e13 100644 --- a/core/libs/tests/GuestPthreadRwlock.cpp +++ b/core/libs/tests/GuestPthreadRwlock.cpp @@ -157,7 +157,9 @@ static void CheckDestroyLifecycle() { Require(held == DestroyedRwlock); Require(scePthreadRwlockUnlock(&held) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadRwlockRdlock(&held) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadRwlockWrlock(&held) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadRwlockTryrdlock(&held) == SCE_KERNEL_ERROR_EINVAL); + Require(scePthreadRwlockTrywrlock(&held) == SCE_KERNEL_ERROR_EINVAL); Require(scePthreadRwlockDestroy(&held) == SCE_KERNEL_ERROR_EINVAL); Require(pthread_rwlock_rdlock_nid_postfix(&held) == GUEST_EINVAL); Require(held == DestroyedRwlock); diff --git a/core/libs/tests/GuestQsort.cpp b/core/libs/tests/GuestQsort.cpp index bdc17d9494..6de50251d7 100644 --- a/core/libs/tests/GuestQsort.cpp +++ b/core/libs/tests/GuestQsort.cpp @@ -126,6 +126,45 @@ bool ExpectQsortRValidation() { } return threw; } +std::uint32_t comparisonHash = 2166136261u; +std::size_t comparisonCount = 0; + +int APS5_VABI CompareSelfSwap(const void* left, const void* right) { + comparisonHash = (comparisonHash ^ static_cast(static_cast(left)->tag)) * 16777619u; + comparisonHash = (comparisonHash ^ static_cast(static_cast(right)->tag)) * 16777619u; + ++comparisonCount; + return CompareKeys(left, right); +} + +int APS5_VABI CompareSelfSwapS(const void* left, const void* right, void* context) { + return *static_cast(context) * CompareSelfSwap(left, right); +} + +bool ExpectSelfSwap() { + constexpr std::array keys{3, 1, 0, 1, 0, 1, 1, 0, 1, 3, 3, 2, 0, 2, 2, 3, 2, 1, 2, 3, 0, 0, 0, 3, 0, 2, 0, 3, 0, 2, 3, 1, 3}; + constexpr std::array tags{2, 4, 7, 12, 20, 21, 22, 24, 26, 28, 1, 3, 5, 6, 8, 17, 31, 16, 13, 14, 11, 18, 25, 29, 10, 19, 15, 27, 0, 9, 30, 23, 32}; + for (const bool withContext : {false, true}) { + std::array elements{}; + for (std::size_t i = 0; i < keys.size(); ++i) elements[i] = {keys[i], static_cast(i)}; + comparisonHash = 2166136261u; + comparisonCount = 0; + int direction = 1; + if (withContext) { + if (qsort_s_nid_postfix(elements.data(), elements.size(), sizeof(Element), CompareSelfSwapS, &direction) != 0) return false; + } else qsort_nid_postfix(elements.data(), elements.size(), sizeof(Element), CompareSelfSwap); + if (comparisonCount != 181 || comparisonHash != 0x78b091e1u) { + std::fprintf(stderr, "partition self-swap: comparator order differs (%zu calls, hash %08x)\n", comparisonCount, comparisonHash); + return false; + } + for (std::size_t i = 0; i < tags.size(); ++i) { + if (elements[i].tag != tags[i]) { + std::fprintf(stderr, "partition self-swap: element %zu has tag %d, expected %d\n", i, elements[i].tag, tags[i]); + return false; + } + } + } + return true; +} bool ExpectOrderS(const char* name, const std::vector& keys, const std::vector& tags) { std::vector elements; @@ -195,7 +234,7 @@ bool ExpectQsortS() { std::fprintf(stderr, "qsort_s of no elements failed\n"); return false; } - unsigned char bytes[] = {3, 1, 2}; + alignas(Element) unsigned char bytes[sizeof(Element)] = {3, 1, 2}; if (qsort_s_nid_postfix(bytes, 3, 0, CompareKeysWithDirection, &direction) != 0 || bytes[0] != 3 || bytes[1] != 1 || bytes[2] != 2) { std::fprintf(stderr, "qsort_s with zero-size elements changed them\n"); return false; @@ -217,6 +256,51 @@ bool ExpectOrder(const char* name, const std::vector& keys, const std::vect return true; } +bool ExpectOrderHash(const char* name, const std::vector& keys, std::uint32_t expected) { + 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); + std::uint32_t hash = 2166136261u; + for (const auto& element : elements) hash = (hash ^ static_cast(element.tag)) * 16777619u; + if (hash != expected) { + std::fprintf(stderr, "%s: tag order hashes to %08x, expected %08x\n", name, hash, expected); + return false; + } + return true; +} + +int comparisons = 0; + +int APS5_VABI CountFirstByte(const void* left, const void* right) { + ++comparisons; + return CompareFirstByte(left, right); +} + +bool ExpectConsoleEdges() { + unsigned char bytes[] = {3, 1, 2}; + qsort_nid_postfix(nullptr, 5, 1, CountFirstByte); + qsort_nid_postfix(bytes, 3, 1, nullptr); + bool correct = comparisons == 0 && bytes[0] == 3 && bytes[1] == 1 && bytes[2] == 2; + qsort_nid_postfix(bytes, 3, 0, CountFirstByte); + correct &= comparisons == 4 && bytes[0] == 3 && bytes[1] == 1 && bytes[2] == 2; + if (!correct) std::fprintf(stderr, "qsort with a null base or comparator or zero-size elements: %d comparisons, bytes %d %d %d\n", comparisons, bytes[0], bytes[1], bytes[2]); + return correct; +} + +bool ExpectShortArraysUntouched() { + int values[2] = {2, 1}; + qsort_nid_postfix(values, 0, sizeof(int), nullptr); + qsort_nid_postfix(values, 1, sizeof(int), nullptr); + qsort_nid_postfix(values, 0, 0, CompareKeys); + qsort_nid_postfix(values, 1, 0, CompareKeys); + qsort_nid_postfix(nullptr, 0, 0, nullptr); + if (values[0] != 2 || values[1] != 1) { + std::fprintf(stderr, "qsort of zero or one element changed the array\n"); + return false; + } + return true; +} + bool ExpectSorted(std::size_t size, std::size_t count) { std::vector bytes(size * count); unsigned int state = static_cast(size * 31 + count); @@ -269,6 +353,25 @@ bool CheckMergesort() { return correct; } +std::uintptr_t heapsortBase = 0; +std::uintptr_t heapsortLimit = 0; +std::size_t heapsortStride = 0; +bool heapsortArgumentsValid = true; +int heapsortCompareCalls = 0; + +int APS5_VABI RecordingCompareKeys(const void* left, const void* right) { + ++heapsortCompareCalls; + const auto inArray = [](const void* candidate) { + const std::uintptr_t address = reinterpret_cast(candidate); + return address >= heapsortBase && address < heapsortLimit + && (address - heapsortBase) % heapsortStride == 0; + }; + const bool leftInArray = inArray(left); + const bool rightInArray = inArray(right); + if (!leftInArray && !rightInArray) heapsortArgumentsValid = false; + return CompareKeys(left, right); +} + bool CheckHeapsort() { bool correct = true; for (const std::size_t count : {0, 1, 2, 3, 7, 16, 33, 1000}) { @@ -281,6 +384,60 @@ bool CheckHeapsort() { correct &= before.key <= after.key; } } + for (const std::size_t count : {0, 1, 2, 3, 7, 16, 33, 1000}) { + std::vector elements; + for (std::size_t index = 0; index < count; ++index) elements.push_back({static_cast((index * 7919) % 11), static_cast(index)}); + std::vector counts(11); + for (const auto& element : elements) ++counts[element.key]; + correct &= heapsort_nid_postfix(elements.data(), count, sizeof(Element), CompareKeys) == 0; + for (std::size_t index = 1; index < count; ++index) correct &= elements[index - 1].key <= elements[index].key; + std::vector seen(count); + for (const auto& element : elements) { + if (element.key < 0 || element.key >= 11 || element.tag < 0 || static_cast(element.tag) >= count) { + correct = false; + continue; + } + --counts[element.key]; + correct &= !seen[element.tag] && element.key == (element.tag * 7919) % 11; + seen[element.tag] = true; + } + for (const int remaining : counts) correct &= remaining == 0; + } + { + unsigned char payload[8] = {5, 2, 4, 1, 3, 2, 5, 1}; + correct &= heapsort_nid_postfix(payload, 8, 1, CompareFirstByte) == 0; + const unsigned char expected[8] = {1, 1, 2, 2, 3, 4, 5, 5}; + correct &= std::memcmp(payload, expected, sizeof(payload)) == 0; + } + { + struct ThreeByte { unsigned char key; unsigned char tag[2]; }; + ThreeByte records[6] = {{4, {1,17}}, {1, {2,29}}, {4, {3,43}}, {2, {4,61}}, {0, {5,83}}, {3, {6,107}}}; + static_assert(sizeof(ThreeByte) == 3); + const auto original = std::to_array(records); + correct &= heapsort_nid_postfix(records, 6, sizeof(ThreeByte), CompareFirstByte) == 0; + for (std::size_t index = 1; index < 6; ++index) correct &= records[index - 1].key <= records[index].key; + std::array seen{}; + for (const auto& record : records) { + if (record.tag[0] < 1 || record.tag[0] > 6) { + correct = false; + continue; + } + const auto index = record.tag[0] - 1; + correct &= !seen[index] && record.key == original[index].key && record.tag[1] == original[index].tag[1]; + seen[index] = true; + } + } + { + std::vector elements; + for (int index = 0; index < 64; ++index) elements.push_back({(index * 11) % 7, index}); + heapsortBase = reinterpret_cast(elements.data()); + heapsortStride = sizeof(Element); + heapsortLimit = heapsortBase + elements.size() * heapsortStride; + heapsortArgumentsValid = true; + heapsortCompareCalls = 0; + correct &= heapsort_nid_postfix(elements.data(), elements.size(), sizeof(Element), RecordingCompareKeys) == 0 && heapsortArgumentsValid && heapsortCompareCalls > 0; + for (std::size_t index = 1; index < elements.size(); ++index) correct &= elements[index - 1].key <= elements[index].key; + } unsigned char bytes[3] = {3, 1, 2}; *__error_nid_postfix() = 0; correct &= heapsort_nid_postfix(nullptr, 0, 0, nullptr) == 0; @@ -288,6 +445,8 @@ bool CheckHeapsort() { correct &= heapsort_nid_postfix(&single, 1, 0, nullptr) == 0; correct &= heapsort_nid_postfix(bytes, 3, 0, CompareFirstByte) == -1 && *__error_nid_postfix() == 22 && bytes[0] == 3; correct &= heapsort_nid_postfix(nullptr, 0, 4, CompareKeys) == 0; + correct &= heapsort_nid_postfix(nullptr, 1, 4, CompareKeys) == 0; + correct &= heapsort_nid_postfix(nullptr, 1, 0, CompareKeys) == 0; if (!correct) std::fprintf(stderr, "heapsort failed\n"); return correct; } @@ -296,9 +455,10 @@ bool CheckHeapsort() { int main() { bool correct = true; correct &= ExpectOrder("insertion sort", {1, 0, 1, 0}, {1, 3, 0, 2}); - correct &= ExpectOrder("median of three", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {11, 3, 6, 8, 10, 1, 7, 4, 9, 5, 0, 2}); + correct &= ExpectOrder("insertion sort below 33 elements", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {3, 6, 8, 11, 1, 4, 7, 10, 0, 2, 5, 9}); correct &= ExpectOrderS("insertion sort", {1, 0, 1, 0}, {1, 3, 0, 2}); - correct &= ExpectOrderS("median of three", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {11, 3, 6, 8, 10, 1, 7, 4, 9, 5, 0, 2}); + correct &= ExpectOrderS("insertion sort below 33 elements", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {3, 6, 8, 11, 1, 4, 7, 10, 0, 2, 5, 9}); + correct &= ExpectSelfSwap(); correct &= ExpectOrderR("insertion sort", {1, 0, 1, 0}, {1, 3, 0, 2}); correct &= ExpectOrderR("median of three", {2, 1, 2, 0, 1, 2, 0, 1, 0, 2, 1, 0}, {11, 3, 6, 8, 10, 1, 7, 4, 9, 5, 0, 2}); correct &= ExpectReverseOrderR(); @@ -307,9 +467,15 @@ int main() { correct &= ExpectQsortSConstraints(); std::vector keys; for (int index = 0; index < 48; ++index) keys.push_back((index * 7 + 3) % 5); - correct &= ExpectOrder("pseudomedian of nine", keys, - {21, 11, 36, 6, 1, 31, 16, 46, 41, 26, 29, 4, 24, 44, 9, 39, 34, 14, 19, 12, 2, 7, 17, 22, - 27, 32, 37, 42, 47, 5, 40, 30, 15, 35, 25, 20, 0, 45, 10, 28, 33, 13, 38, 8, 3, 23, 43, 18}); + const std::vector partitioned{6, 1, 46, 21, 36, 11, 41, 31, 16, 26, 19, 4, 44, 29, 39, 9, 34, 14, 24, 2, 12, 7, 17, 22, + 47, 27, 32, 37, 42, 20, 15, 0, 10, 25, 40, 30, 5, 45, 35, 18, 23, 33, 13, 38, 8, 28, 43, 3}; + correct &= ExpectOrder("median of nine partition", keys, partitioned); + correct &= ExpectOrderS("median of nine partition", keys, partitioned); + std::vector manyKeys; + for (int index = 0; index < 1000; ++index) manyKeys.push_back((index * 37 + 11) % 7); + correct &= ExpectOrderHash("nested partitions", manyKeys, 0xefee58d9u); + correct &= ExpectConsoleEdges(); + correct &= ExpectShortArraysUntouched(); for (const std::size_t size : {1, 3, 8, 24}) for (const std::size_t count : {0, 1, 2, 6, 7, 8, 40, 41, 1000}) correct &= ExpectSorted(size, count); diff --git a/core/libs/tests/GuestRaiseException.cpp b/core/libs/tests/GuestRaiseException.cpp index deffa7dac0..5079190b40 100644 --- a/core/libs/tests/GuestRaiseException.cpp +++ b/core/libs/tests/GuestRaiseException.cpp @@ -3,6 +3,7 @@ #include "prx/libkernel/Semaphore/include/Semaphore.hpp" #include "prx/libkernel/Pthread/include/Rwlock.hpp" #include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Time/include/TimedWait.hpp" #include "SceTypes.hpp" #include #include @@ -272,6 +273,51 @@ static void* APS5_VABI Vectors(void* arg) { } #endif +#ifdef _WIN32 +static constexpr int DrainRounds = 10; +static std::atomic drainingRound{0}; +static std::uint32_t drainWord = 0; + +static void* APS5_VABI Churning(void* arg) { + auto& worker = *static_cast(arg); + worker.id = std::this_thread::get_id(); + const std::string source(64u << 20u, 'a'); + std::vector converted(64u << 20u); + std::vector moved(64u << 20u); + for (int round = 0; round < DrainRounds; ++round) { + worker.started.store(true); + while (drainingRound.load() == round) { + MultiByteToWideChar(CP_UTF8, 0, source.data(), static_cast(source.size()), converted.data(), static_cast(converted.size())); + std::memmove(moved.data(), source.data(), moved.size()); + std::this_thread::yield(); + } + Require(sceKernelSyncOnAddressWake(&drainWord, 1) >= 0); + } + return nullptr; +} + +static constexpr int ExitRounds = 10; +static std::atomic exitingRelease{false}; + +static void* APS5_VABI ChurnUntil(void* arg) { + auto& worker = *static_cast(arg); + worker.id = std::this_thread::get_id(); + const std::string source(64u << 20u, 'a'); + std::vector converted(64u << 20u); + worker.started.store(true); + while (!exitingRelease.load()) MultiByteToWideChar(CP_UTF8, 0, source.data(), static_cast(source.size()), converted.data(), static_cast(converted.size())); + return nullptr; +} + +static std::atomic postEntered{0}; +static std::atomic postHold{false}; + +static void DeliveryPostHook() { + postEntered.fetch_add(1); + while (postHold.load()) std::this_thread::yield(); +} +#endif + static std::atomic spinningId; static void* APS5_VABI Spinning(void* arg) { @@ -701,6 +747,81 @@ int main() { Require(scePthreadJoin(vectorsThread, nullptr) == 0); Require(std::memcmp(vectorPattern, vectorResult, VectorBytes) == 0); } + + Worker churning; + Pthread churningThread = nullptr; + TimedWait::SetDeliveryDrainPostHook(&DeliveryPostHook); + const int postsBeforeChurn = postEntered.load(); + Require(scePthreadCreate(&churningThread, nullptr, Churning, &churning, "churn then drain") == 0); + for (int round = 0; round < DrainRounds; ++round) { + while (!churning.started.load()) std::this_thread::yield(); + churning.started.store(false); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + const int before = calls.load(); + Require(sceKernelRaiseException(churningThread, GUEST_SIGUSR1) == 0); + drainingRound.store(round + 1); + ExpectDelivery(before, churning.id); + } + Require(scePthreadJoin(churningThread, nullptr) == 0); + Require(postEntered.load() > postsBeforeChurn); + + for (int round = 0; round < ExitRounds; ++round) { + exitingRelease.store(false); + Worker exiting; + Pthread exitingThread = nullptr; + Require(scePthreadCreate(&exitingThread, nullptr, ChurnUntil, &exiting, "exit after churn") == 0); + while (!exiting.started.load()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + const int result = sceKernelRaiseException(exitingThread, GUEST_SIGUSR1); + Require(result == 0 || result == SCE_KERNEL_ERROR_ESRCH); + exitingRelease.store(true); + Require(scePthreadJoin(exitingThread, nullptr) == 0); + Require(TimedWait::DeliveryDrainPending() == 0); + } + + exitingRelease.store(false); + postHold.store(true); + const int postsBeforeHeld = postEntered.load(); + Worker heldExit; + Pthread heldExitThread = nullptr; + Require(scePthreadCreate(&heldExitThread, nullptr, ChurnUntil, &heldExit, "exit while posted") == 0); + while (!heldExit.started.load()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + std::atomic raiseResult{-1}; + bool posted = false; + for (int attempt = 0; attempt < 50 && !posted; ++attempt) { + raiseResult.store(-1); + std::thread raiser([&] { raiseResult.store(sceKernelRaiseException(heldExitThread, GUEST_SIGUSR1)); }); + const auto entered = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (postEntered.load() == postsBeforeHeld && raiseResult.load() == -1 && std::chrono::steady_clock::now() < entered) std::this_thread::yield(); + posted = postEntered.load() > postsBeforeHeld; + if (posted) { + exitingRelease.store(true); + Require(scePthreadJoin(heldExitThread, nullptr) == 0); + postHold.store(false); + } + raiser.join(); + } + Require(posted); + Require(raiseResult.load() == 0 || raiseResult.load() == SCE_KERNEL_ERROR_ESRCH); + const auto drained = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (TimedWait::DeliveryDrainPending() != 0 && std::chrono::steady_clock::now() < drained) std::this_thread::yield(); + Require(TimedWait::DeliveryDrainPending() == 0); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + const int settledCalls = calls.load(); + drainingRound.store(0); + Worker settled; + Pthread settledThread = nullptr; + Require(scePthreadCreate(&settledThread, nullptr, Churning, &settled, "settled") == 0); + for (int round = 0; round < DrainRounds; ++round) { + while (!settled.started.load()) std::this_thread::yield(); + settled.started.store(false); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + Require(sceKernelRaiseException(settledThread, GUEST_SIGUSR1) == 0); + drainingRound.store(round + 1); + ExpectDelivery(settledCalls + round, settled.id); + } + Require(scePthreadJoin(settledThread, nullptr) == 0); #endif SpinState spin; diff --git a/core/libs/tests/GuestReallocalign.cpp b/core/libs/tests/GuestReallocalign.cpp index ff924916d5..06cf0862ab 100644 --- a/core/libs/tests/GuestReallocalign.cpp +++ b/core/libs/tests/GuestReallocalign.cpp @@ -49,5 +49,21 @@ int main() { Require(empty != nullptr); free_nid_postfix(empty); + void* aligned[16]{}; + void* unaligned = nullptr; + for (int index = 0; index < 16 && unaligned == nullptr; ++index) { + void* candidate = reallocalign_nid_postfix(nullptr, 24, 64); + Require(candidate != nullptr && (reinterpret_cast(candidate) % 64) == 0); + if ((reinterpret_cast(candidate) % 256) != 0) unaligned = candidate; + else aligned[index] = candidate; + } + Require(unaligned != nullptr); + *__error_nid_postfix() = 1234; + void* moved = reallocalign_nid_postfix(unaligned, 0, 256); + Require(moved != nullptr && moved != unaligned && (reinterpret_cast(moved) % 256) == 0); + Require(malloc_usable_size_nid_postfix(moved) == 0 && *__error_nid_postfix() == 1234); + free_nid_postfix(moved); + for (void* block : aligned) free_nid_postfix(block); + return 0; } diff --git a/core/libs/tests/GuestRtc.cpp b/core/libs/tests/GuestRtc.cpp index 58c7cf5934..f18ebfc20d 100644 --- a/core/libs/tests/GuestRtc.cpp +++ b/core/libs/tests/GuestRtc.cpp @@ -90,6 +90,21 @@ int main(int argc, char** argv) { char text[64]; RtcTick tick{std::numeric_limits::max()}; Require(sceRtcFormatRFC3339(text, &tick, 1) == static_cast(0x80B50003)); + Require(sceRtcFormatRFC3339Precise(text, &tick, 60, 6) == static_cast(0x80B50003)); + const RtcTick year65536{2068084742400000000ull}, year65537{2068116364800000000ull}, year75535{2383622726400000000ull}, distant{4239646359910459728ull}; + Require(sceRtcFormatRFC3339(text, &year65536, 0) == static_cast(0x80B50008)); + Require(sceRtcFormatRFC2822(text, &year65536, 0) == static_cast(0x80B50008)); + Require(sceRtcFormatRFC3339Precise(text, &year65536, 0, 6) == static_cast(0x80B50008)); + Require(sceRtcFormatRFC3339(text, &year65537, 60) == 0 && std::strcmp(text, "0001-01-01T01:00:00.00+01:00") == 0); + Require(sceRtcFormatRFC2822(text, &year65537, 0) == 0 && std::strcmp(text, "Mon, 01 Jan 0001 00:00:00 +0000") == 0); + Require(sceRtcFormatRFC3339Precise(text, &year65537, 60, 6) == 0 && std::strcmp(text, "0001-01-01T01:00:00.000000+01:00") == 0); + Require(sceRtcFormatRFC3339(text, &year75535, -60) == 0 && std::strcmp(text, "9998-12-31T23:00:00.00-01:00") == 0); + Require(sceRtcFormatRFC2822(text, &year75535, 0) == 0 && std::strcmp(text, "Fri, 01 Jan 9999 00:00:00 +0000") == 0); + Require(sceRtcFormatRFC3339Precise(text, &year75535, -60, 6) == 0 && std::strcmp(text, "9998-12-31T23:00:00.000000-01:00") == 0); + Require(sceRtcFormatRFC2822(text, &distant, -821) == 0 && std::strcmp(text, "Mon, 17 Jan 3278 10:44:10 -1341") == 0); + const RtcTick westBound{3600000000ull}, westBelow{3599999999ull}; + Require(sceRtcFormatRFC3339Precise(text, &westBound, -60, 6) == 0 && std::strcmp(text, "0001-01-01T00:00:00.000000-01:00") == 0); + Require(sceRtcFormatRFC3339Precise(text, &westBelow, -60, 6) == static_cast(0x80B50003)); return 0; } EnterTitleDirectory("CUSA00000"); @@ -991,6 +1006,15 @@ int main(int argc, char** argv) { Require(sceRtcFormatRFC3339(ranges, &shifted, -1) == 0 && std::strcmp(ranges, "9999-12-31T23:59:00.00-00:01") == 0); Require(sceRtcInit() == 0); Require(sceRtcEnd() == 0); + Require(sceRtcGetCurrentTick(reinterpret_cast(0x10)) == invalidPointer); + Require(sceRtcGetCurrentNetworkTick(reinterpret_cast(0x10)) == invalidPointer); + Require(sceRtcGetCurrentClock(reinterpret_cast(0x10), 0) == invalidPointer); + Require(sceRtcGetCurrentClockLocalTime(reinterpret_cast(0x10)) == invalidPointer); + Require(sceRtcGetCurrentClockUtc(reinterpret_cast(0x10)) == invalidPointer); + Require(sceRtcGetCurrentRawNetworkTick(reinterpret_cast(0x10)) == invalidPointer); + Require(sceRtcGetCurrentAdNetworkTick(reinterpret_cast(0x10)) == invalidPointer); + Require(sceRtcGetCurrentDebugNetworkTick(reinterpret_cast(0x10)) == invalidPointer); + Require(sceRtcSetCurrentTick(reinterpret_cast(0x10)) == invalidPointer); for (const std::uint64_t value : {maxTick + 1, std::uint64_t{1} << 63, std::numeric_limits::max()}) { const RtcTick outOfRange{value}; Require(sceRtcSetCurrentTick(&outOfRange) == 0); diff --git a/core/libs/tests/GuestRtprioThread.cpp b/core/libs/tests/GuestRtprioThread.cpp index a9ec33d1f7..7670d84ecc 100644 --- a/core/libs/tests/GuestRtprioThread.cpp +++ b/core/libs/tests/GuestRtprioThread.cpp @@ -16,6 +16,9 @@ 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 std::uint16_t RTP_PRIO_IDLE = 4; +static constexpr std::uint16_t RTP_PRIO_IDLE_FIFO = 12; +static constexpr int GUEST_EPERM = 1; static constexpr int GUEST_ESRCH = 3; static constexpr int GUEST_EFAULT = 14; @@ -68,9 +71,7 @@ static void CheckThread(int policy, int priority, std::uint16_t expectedType) { 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); + Require(LookupError(RTP_SET, report.tid.load(), &fromCreator) == GUEST_EPERM); report.release.store(true, std::memory_order_release); Require(scePthreadJoin(thread, nullptr) == SCE_OK); Require(LookupError(RTP_LOOKUP, report.tid.load(), &fromCreator) == GUEST_ESRCH); @@ -105,7 +106,14 @@ int main() { Require(readOnly->type == 7 && readOnly->prio == 7); 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); + Require(LookupError(RTP_SET, 0, &self) == GUEST_EPERM); + Require(LookupError(RTP_SET, scePthreadGetthreadid(), &self) == GUEST_EPERM); + for (const std::uint16_t type : {RTP_PRIO_REALTIME, RTP_PRIO_IDLE, RTP_PRIO_FIFO, RTP_PRIO_IDLE_FIFO}) { + for (const std::uint16_t prio : {0, 15, 31, 32, 999}) { + GuestRtPrio request{type, prio}; + Require(LookupError(RTP_SET, 0, &request) == GUEST_EPERM); + Require(request.type == type && request.prio == prio); + } + } + Require(LookupError(RTP_SET, 999999, &self) == GUEST_ESRCH); } diff --git a/core/libs/tests/GuestRudpContextLifecycle.cpp b/core/libs/tests/GuestRudpContextLifecycle.cpp index 087748d507..b4985b0d06 100644 --- a/core/libs/tests/GuestRudpContextLifecycle.cpp +++ b/core/libs/tests/GuestRudpContextLifecycle.cpp @@ -167,15 +167,6 @@ int main() { 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, 5, 0, 2) == 0); Require(sceRudpSetOption(ctx, 0, nullptr, 0) == invalidOption); diff --git a/core/libs/tests/GuestSanitizerReplaceTables.cpp b/core/libs/tests/GuestSanitizerReplaceTables.cpp new file mode 100644 index 0000000000..3b6ea582f0 --- /dev/null +++ b/core/libs/tests/GuestSanitizerReplaceTables.cpp @@ -0,0 +1,26 @@ +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include + +static void Require(bool value) { + if (!value) std::abort(); +} + +template +static void RequireEmpty(const Table* table, std::uint64_t size) { + Require(table != nullptr); + Require(table->size == size); + const auto* entries = reinterpret_cast(table) + sizeof(table->size); + for (std::size_t index = 0; index < sizeof(Table) - sizeof(table->size); index++) Require(entries[index] == 0); +} + +int main() { + const auto* mallocTable = sceKernelGetSanitizerMallocReplaceExternal(); + RequireEmpty(mallocTable, 0x70); + Require(sceKernelGetSanitizerMallocReplaceExternal() == mallocTable); + const auto* newTable = sceKernelGetSanitizerNewReplaceExternal(); + RequireEmpty(newTable, 0x68); + Require(sceKernelGetSanitizerNewReplaceExternal() == newTable); +} diff --git a/core/libs/tests/GuestSaveDataDialog.cpp b/core/libs/tests/GuestSaveDataDialog.cpp index e3f2ef95bf..b1a4977232 100644 --- a/core/libs/tests/GuestSaveDataDialog.cpp +++ b/core/libs/tests/GuestSaveDataDialog.cpp @@ -34,6 +34,7 @@ int main() { Require(sceSaveDataDialogGetResult(&uninitResult) == SAVE_DATA_DIALOG_ERROR_NOT_FINISHED, "get result before init"); Require(sceSaveDataDialogProgressBarInc(0, 10) == SAVE_DATA_DIALOG_ERROR_NOT_RUNNING, "progress inc before init"); Require(sceSaveDataDialogProgressBarSetValue(0, 50) == SAVE_DATA_DIALOG_ERROR_NOT_RUNNING, "progress set value before init"); + Require(sceSaveDataDialogIsReadyToDisplay() == 1, "isReadyToDisplay before init"); Require(sceSaveDataDialogInitialize() == 0, "initialize"); Require(sceSaveDataDialogInitialize() == SAVE_DATA_DIALOG_ERROR_ALREADY_INITIALIZED, "double init"); @@ -129,4 +130,5 @@ int main() { 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"); + Require(sceSaveDataDialogIsReadyToDisplay() == 1, "isReadyToDisplay after terminate"); } diff --git a/core/libs/tests/GuestSaveDataEventCallback.cpp b/core/libs/tests/GuestSaveDataEventCallback.cpp new file mode 100644 index 0000000000..ea1d0f208a --- /dev/null +++ b/core/libs/tests/GuestSaveDataEventCallback.cpp @@ -0,0 +1,467 @@ +#include "prx/libSceSaveData/include/Api.hpp" +#include "prx/libSceSaveData.native/include/Api.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int SaveDataErrorParameter = -2137063424; +constexpr int SaveDataErrorNotInitialized = -2137063423; +constexpr int SaveDataErrorBusy = -2137063421; +constexpr int SaveDataErrorNotFound = -2137063416; +constexpr std::uint32_t MountReadWrite = 2; +constexpr std::uint32_t MountCreate2 = 32; +constexpr std::int32_t User = 7; + +int failures = 0; + +void Check(bool condition, const std::string& what) { + if (condition) return; + std::fprintf(stderr, "savedata event callback check failed: %s\n", what.c_str()); + ++failures; +} + +struct Call { + int callback; + SaveDataEvent event; + void* userdata; + std::thread::id thread; + int terminated; +}; + +std::mutex callsMutex; +std::condition_variable callsChanged; +std::vector calls; + +void Record(int callback, const SaveDataEvent* event, void* userdata, int terminated = 0) { + std::lock_guard lock(callsMutex); + calls.push_back({callback, *event, userdata, std::this_thread::get_id(), terminated}); + callsChanged.notify_all(); +} + +void APS5_VABI First(const SaveDataEvent* event, void* userdata) { Record(1, event, userdata); } +void APS5_VABI Second(const SaveDataEvent* event, void* userdata) { Record(2, event, userdata); } +void APS5_VABI Terminating(const SaveDataEvent* event, void* userdata) { Record(3, event, userdata, sceSaveDataTerminate()); } +void APS5_VABI Stale(const SaveDataEvent* event, void* userdata) { Record(4, event, userdata); } +void APS5_VABI Fence(const SaveDataEvent* event, void* userdata) { Record(5, event, userdata); } + +std::mutex holdMutex; +std::condition_variable holdChanged; +bool holding = false; +bool released = false; + +void APS5_VABI Holding(const SaveDataEvent*, void*) { + std::unique_lock lock(holdMutex); + holding = true; + holdChanged.notify_all(); + holdChanged.wait(lock, [] { return released; }); +} + +std::size_t CallCount() { + std::lock_guard lock(callsMutex); + return calls.size(); +} + +bool WaitForCalls(std::size_t count) { + std::unique_lock lock(callsMutex); + return callsChanged.wait_for(lock, std::chrono::seconds(5), [&] { return calls.size() >= count; }); +} + +Call CallAt(std::size_t index) { + std::lock_guard lock(callsMutex); + return calls.at(index); +} + +std::size_t CallsOf(int callback, std::size_t from) { + std::lock_guard lock(callsMutex); + std::size_t count = 0; + for (std::size_t index = from; index < calls.size(); ++index) count += calls[index].callback == callback ? 1u : 0u; + return count; +} + +bool WaitForCallback(int callback, std::size_t from) { + std::unique_lock lock(callsMutex); + return callsChanged.wait_for(lock, std::chrono::seconds(5), [&] { + for (std::size_t index = from; index < calls.size(); ++index) { + if (calls[index].callback == callback) return true; + } + return false; + }); +} + +class Watchdog { +public: + explicit Watchdog(const char* what) : what(what), thread([this] { + std::unique_lock lock(mutex); + if (!changed.wait_for(lock, std::chrono::seconds(10), [this] { return done; })) { + std::fprintf(stderr, "savedata event callback check failed: %s did not return within 10 s\n", this->what); + std::_Exit(1); + } + }) {} + + ~Watchdog() { + { + std::lock_guard lock(mutex); + done = true; + } + changed.notify_all(); + thread.join(); + } + + Watchdog(const Watchdog&) = delete; + Watchdog& operator=(const Watchdog&) = delete; + +private: + const char* what; + std::mutex mutex; + std::condition_variable changed; + bool done = false; + std::thread thread; +}; + +#ifdef __linux__ +std::size_t TaskCount() { + return static_cast(std::distance(std::filesystem::directory_iterator("/proc/self/task"), std::filesystem::directory_iterator{})); +} + +bool WaitForTaskCount(std::size_t count) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (TaskCount() != count) { + if (std::chrono::steady_clock::now() > deadline) return false; + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + return true; +} +#endif + +void WriteText(const std::filesystem::path& path, const std::string& text) { + std::ofstream file(path, std::ios::binary | std::ios::trunc); + file << text; + Check(static_cast(file), "writes " + path.string()); +} + +SceSaveDataDirName DirName(const char* name) { + SceSaveDataDirName dirName{}; + std::strncpy(dirName.data, name, sizeof(dirName.data) - 1); + return dirName; +} + +SceSaveDataTitleId TitleId(const char* name) { + SceSaveDataTitleId titleId{}; + std::strncpy(titleId.data, name, sizeof(titleId.data) - 1); + return titleId; +} + +int BackUp(const char* name) { + const auto dirName = DirName(name); + const auto titleId = TitleId("PPSA00000"); + SaveDataBackup backup{}; + backup.user_id = User; + backup.title_id = &titleId; + backup.dir_name = &dirName; + return sceSaveDataBackup(&backup); +} + +SaveDataEvent Poll(const std::string& what) { + SaveDataEvent event{}; + Check(sceSaveDataGetEventResult(nullptr, &event) == 0, what + " is polled"); + return event; +} + +bool SameEvent(const SaveDataEvent& left, const SaveDataEvent& right) { + return left.type == right.type && left.error_code == right.error_code && left.user_id == right.user_id && std::strncmp(left.title_id.data, right.title_id.data, sizeof(left.title_id.data)) == 0 && std::strncmp(left.dir_name.data, right.dir_name.data, sizeof(left.dir_name.data)) == 0; +} + +void CheckDelivered(std::size_t index, int callback, void* userdata, std::uint32_t type, const char* dir, const std::string& what) { + const auto call = CallAt(index); + Check(call.callback == callback, what + " reaches callback " + std::to_string(callback) + ", got " + std::to_string(call.callback)); + Check(call.userdata == userdata, what + " passes the registered userdata"); + Check(call.event.type == type, what + " has type " + std::to_string(type) + ", got " + std::to_string(call.event.type)); + Check(call.event.error_code == 0, what + " has error 0, got " + std::to_string(call.event.error_code)); + Check(call.event.user_id == User, what + " names the user"); + Check(std::strncmp(call.event.title_id.data, "PPSA00000", sizeof(call.event.title_id.data)) == 0, what + " names the title"); + Check(std::strncmp(call.event.dir_name.data, dir, sizeof(call.event.dir_name.data)) == 0, what + " names the save " + dir); + Check(call.thread != std::this_thread::get_id(), what + " runs on another thread than the caller"); + const auto polled = Poll(what); + Check(SameEvent(polled, call.event), what + " is the event the poll returns"); +} + +void CheckSessionsWithoutRegistration() { +#ifdef __linux__ + const auto before = TaskCount(); +#endif + for (int session = 0; session < 2; ++session) { + const auto label = "session " + std::to_string(session) + " without a registration"; + Check(sceSaveDataInitialize3(nullptr) == 0, label + ": SaveData initializes"); +#ifdef __linux__ + Check(TaskCount() == before, label + ": Initialize3 starts no thread, " + std::to_string(before) + " threads before and " + std::to_string(TaskCount()) + " after"); +#endif + Check(sceSaveDataTerminate() == 0, label + ": SaveData terminates"); + } +} + +#ifdef __linux__ +void CheckDeliveryThreadStartsAtRegistration() { + const auto before = TaskCount(); + const auto counted = [&](const std::string& what) { return what + ", " + std::to_string(before) + " threads before and " + std::to_string(TaskCount()) + " now"; }; + int tag = 0; + Check(sceSaveDataInitialize3(nullptr) == 0, "SaveData initializes for the delivery thread check"); + Check(TaskCount() == before, counted("Initialize3 starts no delivery thread")); + Check(sceSaveDataRegisterEventCallback(nullptr, &tag) == SaveDataErrorParameter, "a null callback is PARAMETER in the delivery thread check"); + Check(TaskCount() == before, counted("a refused registration starts no delivery thread")); + Check(sceSaveDataRegisterEventCallback(First, &tag) == 0, "the first callback registers in the delivery thread check"); + Check(TaskCount() == before + 1, counted("the first registration starts one delivery thread")); + Check(sceSaveDataRegisterEventCallback(Second, &tag) == 0, "the second callback registers in the delivery thread check"); + Check(sceSaveDataUnregisterEventCallback() == 0, "the second callback unregisters in the delivery thread check"); + Check(sceSaveDataRegisterEventCallback(First, &tag) == 0, "the first callback registers again in the delivery thread check"); + Check(TaskCount() == before + 1, counted("a replacing registration and one after an unregister reuse the delivery thread")); + { + Watchdog watchdog("the terminate that joins the delivery thread"); + Check(sceSaveDataTerminate() == 0, "SaveData terminates after the delivery thread check"); + } + Check(WaitForTaskCount(before), counted("the final terminate joins the delivery thread within 5 s")); +} +#endif + +void CheckRegistration() { + int tag = 0; + Check(sceSaveDataRegisterEventCallback(First, &tag) == SaveDataErrorNotInitialized, "register before initialize is NOT_INITIALIZED"); + Check(sceSaveDataUnregisterEventCallback() == 0, "unregister before initialize succeeds"); + Check(sceSaveDataInitialize3(nullptr) == 0, "SaveData initializes"); + Check(sceSaveDataRegisterEventCallback(nullptr, &tag) == SaveDataErrorParameter, "a null callback is PARAMETER"); +} + +void CheckBackupDelivery(int* firstTag) { + Check(sceSaveDataRegisterEventCallback(First, firstTag) == 0, "the first callback registers"); + Check(BackUp("backed") == 0, "the backup succeeds"); + Check(WaitForCalls(1), "the backup's completion reaches the callback within 5 s"); + Check(CallCount() == 1, "the backup is delivered once"); + if (CallCount() >= 1) CheckDelivered(0, 1, firstTag, 2, "backed", "the backup event"); + SaveDataEvent none{}; + Check(sceSaveDataGetEventResult(nullptr, &none) == SaveDataErrorNotFound, "the backup posts one event to poll"); +} + +void CheckUmountWithBackupDelivery(int* firstTag) { + std::filesystem::create_directories("app0/sce_sys"); + WriteText("app0/sce_sys/param.json", "{\"titleId\":\"PPSA00000\",\"localizedParameters\":{\"en-US\":{\"titleName\":\"Example\"}}}"); + auto dirName = DirName("mounted"); + SaveDataMount3 mount{}; + mount.user_id = User; + mount.dir_name = &dirName; + mount.mount_mode = MountReadWrite | MountCreate2; + SaveDataMountResult mounted{}; + Check(sceSaveDataMount3(&mount, &mounted) == 0, "the save mounts"); + const auto before = CallCount(); + Check(sceSaveDataUmountWithBackup(&mounted.mount_point) == 0, "umountWithBackup succeeds"); + Check(WaitForCalls(before + 1), "umountWithBackup's completion reaches the callback within 5 s"); + Check(CallCount() == before + 1, "umountWithBackup is delivered once"); + if (CallCount() >= before + 1) CheckDelivered(before, 1, firstTag, 1, "mounted", "the umountWithBackup event"); +} + +void CheckUnregister(int* firstTag, int* secondTag) { + Check(sceSaveDataUnregisterEventCallback() == 0, "the first callback unregisters"); + const auto before = CallCount(); + Check(BackUp("unwatched") == 0, "a backup with no callback succeeds"); + const auto unwatched = Poll("the backup with no callback"); + Check(unwatched.type == 2 && std::strncmp(unwatched.dir_name.data, "unwatched", sizeof(unwatched.dir_name.data)) == 0, "the backup with no callback still posts its event"); + Check(sceSaveDataRegisterEventCallback(Second, secondTag) == 0, "the second callback registers"); + Check(BackUp("watched") == 0, "the second backup succeeds"); + Check(WaitForCalls(before + 1), "the second backup reaches the second callback within 5 s"); + Check(CallCount() == before + 1, "only the second backup is delivered, got " + std::to_string(CallCount() - before) + " calls"); + if (CallCount() >= before + 1) CheckDelivered(before, 2, secondTag, 2, "watched", "the event after the callback was unregistered"); + Check(sceSaveDataRegisterEventCallback(First, firstTag) == 0, "the first callback registers again"); + int replacedTag = 0; + Check(sceSaveDataRegisterEventCallback(Second, &replacedTag) == 0, "the second callback replaces the first"); + Check(BackUp("replaced") == 0, "the backup after the replacement succeeds"); + Check(WaitForCalls(before + 2), "the backup after the replacement is delivered within 5 s"); + Check(CallCount() == before + 2, "the backup after the replacement is delivered once"); + if (CallCount() >= before + 2) CheckDelivered(before + 1, 2, &replacedTag, 2, "replaced", "the event after the replacement"); +} + +void CheckTerminateFromCallback() { + const auto before = CallCount(); + int tag = 0; + { + Watchdog watchdog("a terminate from inside the callback"); + Check(sceSaveDataRegisterEventCallback(Terminating, &tag) == 0, "the terminating callback registers"); + Check(BackUp("terminating") == 0, "the backup for the terminating callback succeeds"); + Check(WaitForCalls(before + 1), "the terminating callback runs within 5 s"); + } + if (CallCount() >= before + 1) Check(CallAt(before).terminated == SaveDataErrorBusy, "a terminate from inside the callback is BUSY, got " + std::to_string(CallAt(before).terminated)); + Poll("the terminating callback's backup"); + Check(sceSaveDataUnregisterEventCallback() == 0, "the terminating callback unregisters"); +} + +void CheckIdleTerminateAndRestart(int* firstTag) { + { + Watchdog watchdog("an idle terminate"); + Check(sceSaveDataTerminate() == 0, "SaveData terminates with the delivery thread idle"); + } + Check(sceSaveDataRegisterEventCallback(First, firstTag) == SaveDataErrorNotInitialized, "register after terminate is NOT_INITIALIZED"); + Check(sceSaveDataInitialize3(nullptr) == 0, "SaveData initializes again"); + const auto before = CallCount(); + Check(BackUp("restarted") == 0, "a backup after the restart succeeds"); + Poll("the backup after the restart"); + Check(sceSaveDataRegisterEventCallback(First, firstTag) == 0, "the first callback registers after the restart"); + Check(BackUp("redelivered") == 0, "a backup with a callback after the restart succeeds"); + Check(WaitForCalls(before + 1), "the backup after the restart is delivered within 5 s"); + Check(CallCount() == before + 1, "terminate dropped the callback: the restart delivers only after registering again"); + if (CallCount() >= before + 1) CheckDelivered(before, 1, firstTag, 2, "redelivered", "the event after the restart"); + { + Watchdog watchdog("the final terminate"); + Check(sceSaveDataTerminate() == 0, "SaveData terminates again"); + } +} + +struct Race { + std::atomic start{false}; + int staleTag = 0; + int registered = 0; + int terminated = 0; +}; + +struct RaceOutcomes { + int notInitialized = 0; + int registered = 0; +}; + +void* APS5_VABI RaceRegister(void* argument) { + auto& race = *static_cast(argument); + while (!race.start.load(std::memory_order_acquire)) std::this_thread::yield(); + race.registered = sceSaveDataRegisterEventCallback(Stale, &race.staleTag); + return nullptr; +} + +void* APS5_VABI RaceTerminate(void* argument) { + auto& race = *static_cast(argument); + while (!race.start.load(std::memory_order_acquire)) std::this_thread::yield(); + race.terminated = sceSaveDataTerminate(); + return nullptr; +} + +Pthread Spawn(PthreadEntry entry, Race& race, const char* name) { + Pthread thread = nullptr; + if (scePthreadCreate(&thread, nullptr, entry, &race, name) != 0) { + std::fprintf(stderr, "savedata event callback check failed: scePthreadCreate failed for %s\n", name); + std::_Exit(1); + } + return thread; +} + +bool WaitForFinalTerminate() { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (sceSaveDataDeleteTransactionResource(0) != SaveDataErrorNotInitialized) { + if (std::chrono::steady_clock::now() > deadline) return false; + std::this_thread::yield(); + } + return sceSaveDataTerminate() == SaveDataErrorNotInitialized; +} + +bool RaceRegisterWithFinalTerminate(int iteration, bool hold, RaceOutcomes& outcomes) { + const auto failuresBefore = failures; + const auto label = "iteration " + std::to_string(iteration) + (hold ? " with the delivery thread held" : " without a held delivery thread"); + Check(sceSaveDataInitialize3(nullptr) == 0, label + ": SaveData initializes"); + int holdTag = 0; + if (hold) { + { + std::lock_guard lock(holdMutex); + holding = false; + released = false; + } + Check(sceSaveDataRegisterEventCallback(Holding, &holdTag) == 0, label + ": the holding callback registers"); + Check(BackUp("held") == 0, label + ": the backup for the holding callback succeeds"); + std::unique_lock lock(holdMutex); + Check(holdChanged.wait_for(lock, std::chrono::seconds(5), [] { return holding; }), label + ": the holding callback runs within 5 s"); + } + Race race; + const auto registering = Spawn(RaceRegister, race, "SaveDataRaceRegister"); + const auto terminating = Spawn(RaceTerminate, race, "SaveDataRaceTerminate"); + race.start.store(true, std::memory_order_release); + if (hold) { + Check(WaitForFinalTerminate(), label + ": the racing terminate reaches zero initializations within 5 s"); + { + std::lock_guard lock(holdMutex); + released = true; + } + holdChanged.notify_all(); + } + Check(scePthreadJoin(registering, nullptr) == 0, label + ": the registering thread joins"); + Check(scePthreadJoin(terminating, nullptr) == 0, label + ": the terminating thread joins"); + Check(race.terminated == 0, label + ": the racing final terminate succeeds, got " + std::to_string(race.terminated)); + Check(race.registered == 0 || race.registered == SaveDataErrorNotInitialized, label + ": the racing registration is OK or NOT_INITIALIZED, got " + std::to_string(race.registered)); + if (race.registered == SaveDataErrorNotInitialized) ++outcomes.notInitialized; + if (race.registered == 0) ++outcomes.registered; + const auto before = CallCount(); + Check(sceSaveDataInitialize3(nullptr) == 0, label + ": SaveData initializes again"); + Check(BackUp("raced") == 0, label + ": the backup after the race succeeds"); + int fenceTag = 0; + Check(sceSaveDataRegisterEventCallback(Fence, &fenceTag) == 0, label + ": the fence callback registers"); + Check(BackUp("fenced") == 0, label + ": the fenced backup succeeds"); + Check(WaitForCallback(5, before), label + ": the fenced backup reaches the fence callback within 5 s"); + Check(CallsOf(4, before) == 0, label + ": the callback whose registration returned " + std::to_string(race.registered) + " beside the final terminate is called after Initialize3, " + std::to_string(CallsOf(4, before)) + " time(s)"); + Check(sceSaveDataUnregisterEventCallback() == 0, label + ": the fence callback unregisters"); + SaveDataEvent drained{}; + while (sceSaveDataGetEventResult(nullptr, &drained) == 0) { + } + Check(sceSaveDataTerminate() == 0, label + ": SaveData terminates after the race"); + return failures == failuresBefore; +} + +void CheckRegisterRacingFinalTerminate() { + constexpr int Iterations = 200; + RaceOutcomes held; + RaceOutcomes plain; + int ran = 0; + while (ran < Iterations) { + Watchdog watchdog("a registration racing the final terminate"); + const bool hold = ran % 2 == 0; + const bool passed = RaceRegisterWithFinalTerminate(ran, hold, hold ? held : plain); + ++ran; + if (!passed) break; + } + std::printf("register against the final terminate: %d of %d iterations; held delivery thread: %d NOT_INITIALIZED, %d OK; plain race: %d NOT_INITIALIZED, %d OK\n", ran, Iterations, held.notInitialized, held.registered, plain.notInitialized, plain.registered); +} + +} + +int main() { + const auto root = std::filesystem::temp_directory_path() / ("anyps5-savedata-event-callback-" + std::to_string(std::random_device{}())); + std::filesystem::create_directories(root); + const auto previous = std::filesystem::current_path(); + std::filesystem::current_path(root); + int firstTag = 0; + int secondTag = 0; + CheckSessionsWithoutRegistration(); +#ifdef __linux__ + CheckDeliveryThreadStartsAtRegistration(); +#endif + CheckRegistration(); + CheckBackupDelivery(&firstTag); + CheckUmountWithBackupDelivery(&firstTag); + CheckUnregister(&firstTag, &secondTag); + CheckTerminateFromCallback(); + CheckIdleTerminateAndRestart(&firstTag); + CheckRegisterRacingFinalTerminate(); + std::filesystem::current_path(previous); + std::error_code error; + std::filesystem::remove_all(root, error); + if (failures != 0) return 1; + std::printf("savedata event callback tests passed\n"); + return 0; +} diff --git a/core/libs/tests/GuestSaveDataMountStatus.cpp b/core/libs/tests/GuestSaveDataMountStatus.cpp index a4b1e81ae6..e9bed03241 100644 --- a/core/libs/tests/GuestSaveDataMountStatus.cpp +++ b/core/libs/tests/GuestSaveDataMountStatus.cpp @@ -7,12 +7,16 @@ #include #include #include +#include +#include +#include #include #include #include #include #ifdef _WIN32 #include +#include #endif namespace { @@ -25,6 +29,7 @@ constexpr int SaveDataErrorNotInitialized = -2137063423; constexpr int SaveDataErrorNotMounted = -2137063420; constexpr int SaveDataErrorBusy = -2137063421; constexpr int SaveDataErrorNotFound = -2137063416; +constexpr int SaveDataErrorInternal = -2137063413; constexpr int SaveDataErrorMountFull = -2137063412; int failures = 0; @@ -63,6 +68,17 @@ 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"); + SaveDataMountPoint unterminatedPoint{}; + std::memset(unterminatedPoint.data, 'a', sizeof(unterminatedPoint.data)); + Check(sceSaveDataUmount2(0, &unterminatedPoint) == SaveDataErrorParameter, "umount2 unterminated point is PARAMETER"); + Check(sceSaveDataLoadIcon(&unterminatedPoint, &icon) == SaveDataErrorParameter, "loadIcon unterminated point is PARAMETER"); + Check(sceSaveDataSaveIcon(&unterminatedPoint, &icon) == SaveDataErrorParameter, "saveIcon unterminated point is PARAMETER"); + Check(sceSaveDataSaveIconByPath(&unterminatedPoint, "test.png") == SaveDataErrorParameter, "saveIconByPath unterminated point is PARAMETER"); + Check(sceSaveDataGetMountInfo(&unterminatedPoint, &info) == SaveDataErrorParameter, "getMountInfo unterminated point is PARAMETER"); + Check(sceSaveDataGetParam(&unterminatedPoint, 1, buf, sizeof(buf), &gotSize) == SaveDataErrorParameter, "getParam unterminated point is PARAMETER"); + Check(sceSaveDataSetParam(&unterminatedPoint, 1, buf, sizeof(buf)) == SaveDataErrorParameter, "setParam unterminated point is PARAMETER"); + unterminatedPoint.data[sizeof(unterminatedPoint.data) - 1] = '\0'; + Check(sceSaveDataUmount2(0, &unterminatedPoint) == SaveDataErrorNotMounted, "umount2 terminated 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"); @@ -89,6 +105,15 @@ void CheckUnmountedCalls() { Check(backupEvent.user_id == 0x10000000, "backup event user_id matches"); Check(std::strcmp(backupEvent.dir_name.data, "valid_backup") == 0, "backup event dir_name matches"); Check(sceSaveDataGetEventResult(nullptr, &emptyEvent) == SaveDataErrorNotFound, "event queue is empty after retrieving backup event"); + SceSaveDataDirName dirName{}; + std::memcpy(dirName.data, "fresh", sizeof("fresh")); + SaveDataCheckBackupData check{}; + check.dir_name = &dirName; + Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorNotInitialized, "checkBackupData before initialize is NOT_INITIALIZED"); + SaveDataRestoreBackupData restore{}; + restore.dir_name = &dirName; + Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorNotInitialized, "restoreBackupData before initialize is NOT_INITIALIZED"); + Check(sceSaveDataUmountWithBackup(&point) == SaveDataErrorNotInitialized, "umountWithBackup before initialize is NOT_INITIALIZED"); } void CheckMountInfoAndParam(const SaveDataMountPoint* point) { @@ -131,6 +156,15 @@ void CheckMountInfoAndParam(const SaveDataMountPoint* point) { Check(sceSaveDataSetParam(point, 1, newTitle, sizeof(newTitle)) == 0, "setParam on mounted save succeeds"); Check(sceSaveDataGetParam(point, 1, readTitle, sizeof(readTitle), &gotSize) == 0, "getParam reads back set title"); Check(std::strcmp(readTitle, newTitle) == 0, "readTitle matches newTitle"); + SaveDataMountPoint unterminatedMounted{}; + std::memset(unterminatedMounted.data, 'a', sizeof(unterminatedMounted.data)); + Check(sceSaveDataUmount2(0, &unterminatedMounted) == SaveDataErrorParameter, "umount2 unterminated point is PARAMETER on mounted save"); + Check(sceSaveDataLoadIcon(&unterminatedMounted, &icon) == SaveDataErrorParameter, "loadIcon unterminated point is PARAMETER on mounted save"); + Check(sceSaveDataSaveIcon(&unterminatedMounted, &icon) == SaveDataErrorParameter, "saveIcon unterminated point is PARAMETER on mounted save"); + Check(sceSaveDataSaveIconByPath(&unterminatedMounted, "icon.png") == SaveDataErrorParameter, "saveIconByPath unterminated point is PARAMETER on mounted save"); + Check(sceSaveDataGetMountInfo(&unterminatedMounted, &info) == SaveDataErrorParameter, "getMountInfo unterminated point is PARAMETER on mounted save"); + Check(sceSaveDataGetParam(&unterminatedMounted, 1, buf, sizeof(buf), &gotSize) == SaveDataErrorParameter, "getParam unterminated point is PARAMETER on mounted save"); + Check(sceSaveDataSetParam(&unterminatedMounted, 1, newTitle, sizeof(newTitle)) == SaveDataErrorParameter, "setParam unterminated point is PARAMETER on mounted save"); } void MountAndCheck(const char* name, std::uint32_t mode, std::uint32_t expectedStatus, const std::string& what) { @@ -263,6 +297,475 @@ void CheckInvalidNamesAreRejected() { MountAndCheck("after", MountReadWrite | MountCreate2, 1u, "CREATE2 after the rejected names"); } +void WriteText(const std::filesystem::path& path, const std::string& text) { + std::ofstream file(path, std::ios::binary | std::ios::trunc); + file << text; + Check(static_cast(file), "writes " + path.string()); +} + +std::string ReadText(const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + return std::string(std::istreambuf_iterator(file), std::istreambuf_iterator()); +} + +bool MountSave(SceSaveDataDirName* dirName, std::uint32_t mode, SaveDataMountResult* result) { + SaveDataMount3 mount{}; + mount.user_id = 7; + mount.dir_name = dirName; + mount.mount_mode = mode; + return sceSaveDataMount3(&mount, result) == 0; +} + +std::map Snapshot(const std::filesystem::path& save) { + std::map files; + for (const char* side : {".param", ".blocks"}) { + std::filesystem::path path = save; + path += side; + if (std::filesystem::exists(path)) files[side] = ReadText(path); + } + if (!std::filesystem::is_directory(save)) return files; + files["/"] = ""; + for (const auto& entry : std::filesystem::recursive_directory_iterator(save)) { + const std::string relative = std::filesystem::relative(entry.path(), save).generic_string(); + if (entry.is_directory()) { + files[relative + "/"] = ""; + } else { + files[relative] = ReadText(entry.path()); + } + } + return files; +} + +int UmountWithBackup(const std::string& name, const SaveDataMountPoint* point) { + Check(sceSaveDataUmountWithBackup(point) == 0, name + " unmounts with a backup"); + SaveDataEvent event{}; + Check(sceSaveDataGetEventResult(nullptr, &event) == 0, name + "'s backup posts an event"); + return event.error_code; +} + +int BackUp(SceSaveDataDirName* dirName, const std::string& data, const char* title) { + const std::string name = dirName->data; + SaveDataMountResult mounted{}; + if (!MountSave(dirName, MountReadWrite | MountCreate2, &mounted)) { + Check(false, name + " mounts to be backed up"); + return SaveDataErrorNotMounted; + } + WriteText(std::filesystem::path("_sd") / name / "data.bin", data); + Check(sceSaveDataSetParam(&mounted.mount_point, 1, title, std::strlen(title) + 1) == 0, name + " takes the title " + title); + return UmountWithBackup(name, &mounted.mount_point); +} + +int RemountWithBackup(SceSaveDataDirName* dirName) { + const std::string name = dirName->data; + SaveDataMountResult mounted{}; + if (!MountSave(dirName, MountReadWrite, &mounted)) { + Check(false, name + " mounts to be backed up again"); + return SaveDataErrorNotMounted; + } + return UmountWithBackup(name, &mounted.mount_point); +} + +void CheckBackups() { + const auto paramJson = ResolvePath_nid_no_patch("/app0/sce_sys/param.json"); + std::filesystem::create_directories(paramJson.parent_path()); + WriteText(paramJson, "{\"titleId\":\"PPSA00000\",\"localizedParameters\":{\"en-US\":{\"titleName\":\"Example\"}}}"); + SceSaveDataDirName kept{}; + std::memcpy(kept.data, "kept", sizeof("kept")); + SceSaveDataDirName fresh{}; + std::memcpy(fresh.data, "fresh", sizeof("fresh")); + SaveDataCheckBackupData check{}; + SaveDataRestoreBackupData restore{}; + Check(sceSaveDataCheckBackupData(nullptr) == SaveDataErrorParameter, "checkBackupData null block is PARAMETER"); + Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorParameter, "checkBackupData null dir_name is PARAMETER"); + Check(sceSaveDataRestoreBackupData(nullptr) == SaveDataErrorParameter, "restoreBackupData null block is PARAMETER"); + Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorParameter, "restoreBackupData null dir_name is PARAMETER"); + Check(sceSaveDataUmountWithBackup(nullptr) == SaveDataErrorParameter, "umountWithBackup null point is PARAMETER"); + check.dir_name = &fresh; + restore.dir_name = &fresh; + Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorNotFound, "checkBackupData without a backup is NOT_FOUND"); + Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorNotFound, "restoreBackupData without a backup is NOT_FOUND"); + + SaveDataMountResult mounted{}; + Check(MountSave(&kept, MountReadWrite | MountCreate2, &mounted), "the backed-up save mounts"); + const auto data = std::filesystem::path("_sd") / "kept" / "data.bin"; + WriteText(data, "first"); + const char backedUpTitle[] = "Backed Up"; + Check(sceSaveDataSetParam(&mounted.mount_point, 1, backedUpTitle, sizeof(backedUpTitle)) == 0, "the backed-up save takes a title"); + check.dir_name = &kept; + restore.dir_name = &kept; + Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorBusy, "checkBackupData of a mounted save is BUSY"); + Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorBusy, "restoreBackupData of a mounted save is BUSY"); + Check(sceSaveDataUmountWithBackup(&mounted.mount_point) == 0, "umountWithBackup succeeds"); + SaveDataMountInfo info{}; + Check(sceSaveDataGetMountInfo(&mounted.mount_point, &info) == SaveDataErrorNotMounted, "umountWithBackup unmounts the save"); + + SaveDataEvent event{}; + Check(sceSaveDataGetEventResult(nullptr, &event) == 0, "umountWithBackup posts an event"); + Check(event.type == 1, "the event is UMOUNT_BACKUP, got type " + std::to_string(event.type)); + Check(event.error_code == 0, "the backup succeeded, error " + std::to_string(event.error_code)); + Check(event.user_id == 7, "the event names the mounting user"); + Check(std::strncmp(event.title_id.data, "PPSA00000", sizeof(event.title_id.data)) == 0, "the event names the running title"); + Check(std::strncmp(event.dir_name.data, "kept", sizeof(event.dir_name.data)) == 0, "the event names the save"); + + SaveDataParam param{}; + check.param = ¶m; + Check(sceSaveDataCheckBackupData(&check) == 0, "checkBackupData finds the backup"); + Check(std::strcmp(param.title, backedUpTitle) == 0, "checkBackupData fills param from the backup"); + SceSaveDataTitleId running{}; + std::memcpy(running.data, "PPSA00000", sizeof("PPSA00000")); + check.title_id = &running; + Check(sceSaveDataCheckBackupData(&check) == 0, "checkBackupData with the running title's id finds the backup"); + + WriteText(data, "second"); + WriteText(std::filesystem::path("_sd") / "kept" / "added.bin", "added"); + Check(MountSave(&kept, MountReadWrite, &mounted), "the save mounts again after the backup"); + const char changedTitle[] = "Changed"; + Check(sceSaveDataSetParam(&mounted.mount_point, 1, changedTitle, sizeof(changedTitle)) == 0, "the title changes after the backup"); + Check(sceSaveDataUmount2(0, &mounted.mount_point) == 0, "the changed save unmounts"); + Check(sceSaveDataRestoreBackupData(&restore) == 0, "restoreBackupData succeeds"); + Check(ReadText(data) == "first", "restoreBackupData puts back a file changed after the backup"); + Check(!std::filesystem::exists(std::filesystem::path("_sd") / "kept" / "added.bin"), "restoreBackupData removes a file added after the backup"); + char title[128]{}; + Check(MountSave(&kept, MountReadWrite, &mounted), "the restored save mounts"); + Check(sceSaveDataGetParam(&mounted.mount_point, 1, title, sizeof(title), nullptr) == 0 && std::strcmp(title, backedUpTitle) == 0, "restoreBackupData puts back the backed-up title"); + Check(sceSaveDataUmount2(0, &mounted.mount_point) == 0, "the restored save unmounts"); + SaveDataDelete del{}; + del.dir_name = &kept; + Check(sceSaveDataDelete(&del) == 0, "the backed-up save is deleted"); + Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorNotFound, "deleting a save deletes its backup"); +} + +const char* failingStep = nullptr; +int failingCall = 0; +int stepCalls = 0; + +bool FailingStep(const char* step, const char*) { + if (failingStep == nullptr || std::strcmp(step, failingStep) != 0) return true; + return ++stepCalls != failingCall; +} + +void FailStep(const char* step, int call) { + failingStep = step; + failingCall = call; + stepCalls = 0; + SaveDataSetFileStepHook_nid_no_patch(FailingStep); +} + +void ClearFailingStep() { + SaveDataSetFileStepHook_nid_no_patch(nullptr); + failingStep = nullptr; +} + +struct FailedStep { + const char* step; + int call; +}; + +bool LeavesCopies(const std::string& name) { + for (const char* root : {"_sd_backup_tmp", "_sd_backup_old", "_sd_restore_tmp", "_sd_restore_old"}) { + if (std::filesystem::exists(std::filesystem::path(root) / name)) return true; + } + return false; +} + +template +void CheckCorruptBackup(const std::string& label, Damage damage) { + SceSaveDataDirName name{}; + std::memcpy(name.data, "corrupt", sizeof("corrupt")); + Check(BackUp(&name, "backed up", "Corrupt") == 0, label + ": the save is backed up"); + WriteText(std::filesystem::path("_sd") / "corrupt" / "data.bin", "live"); + damage(std::filesystem::path("_sd_backup") / "corrupt"); + const auto live = std::filesystem::path("_sd") / "corrupt"; + const auto before = Snapshot(live); + SaveDataCheckBackupData check{}; + check.dir_name = &name; + Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorInternal, label + ": checkBackupData without param is INTERNAL"); + SaveDataParam param; + std::memset(¶m, 0xA5, sizeof(param)); + const SaveDataParam untouched = param; + check.param = ¶m; + Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorInternal, label + ": checkBackupData with param is INTERNAL"); + Check(std::memcmp(¶m, &untouched, sizeof(param)) == 0, label + ": checkBackupData leaves param as it was"); + SaveDataRestoreBackupData restore{}; + restore.dir_name = &name; + Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorInternal, label + ": restoreBackupData is INTERNAL"); + Check(Snapshot(live) == before, label + ": restoreBackupData leaves the save byte for byte"); +} + +void CheckCorruptBackups() { + CheckCorruptBackup("a backup without its .param", [](const std::filesystem::path& entry) { std::filesystem::remove(entry / "save.param"); }); + CheckCorruptBackup("an empty backup directory", [](const std::filesystem::path& entry) { + std::filesystem::remove_all(entry); + std::filesystem::create_directory(entry); + }); + CheckCorruptBackup("a backup with a short .param", [](const std::filesystem::path& entry) { + const std::string bytes = ReadText(entry / "save.param"); + WriteText(entry / "save.param", bytes.substr(0, bytes.size() - 1)); + }); + CheckCorruptBackup("a backup with a long .param", [](const std::filesystem::path& entry) { WriteText(entry / "save.param", ReadText(entry / "save.param") + "x"); }); +} + +void CheckStagedRestore() { + SceSaveDataDirName name{}; + std::memcpy(name.data, "staged", sizeof("staged")); + const auto live = std::filesystem::path("_sd") / "staged"; + std::filesystem::create_directories(live / "sub"); + WriteText(live / "sub" / "deep.bin", "deep"); + WriteText(std::filesystem::path("_sd") / "staged.blocks", "96"); + Check(BackUp(&name, "backed up", "Staged") == 0, "the staged save is backed up"); + const auto backup = Snapshot(std::filesystem::path("_sd_backup") / "staged" / "save"); + WriteText(live / "data.bin", "live"); + WriteText(live / "added.bin", "added"); + WriteText(std::filesystem::path("_sd") / "staged.blocks", "128"); + const auto before = Snapshot(live); + SaveDataRestoreBackupData restore{}; + restore.dir_name = &name; + const FailedStep failures[] = {{"restore.copy", 1}, {"restore.copy", 3}, {"restore.copy", 6}, {"restore.aside", 1}, {"restore.aside", 2}, {"restore.place", 1}, {"restore.place", 3}}; + for (const FailedStep& failure : failures) { + const std::string what = std::string("a restore whose ") + failure.step + " step " + std::to_string(failure.call) + " fails"; + FailStep(failure.step, failure.call); + Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorInternal, what + " is INTERNAL"); + ClearFailingStep(); + Check(Snapshot(live) == before, what + " leaves the save as it was"); + Check(!LeavesCopies("staged"), what + " leaves no staged or old copy"); + } + Check(sceSaveDataRestoreBackupData(&restore) == 0, "a restore with no failing step succeeds"); + Check(Snapshot(live) == backup, "the restored save matches its backup byte for byte"); + Check(!LeavesCopies("staged"), "a restore leaves no staged or old copy"); + std::filesystem::remove_all(live); + Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorInternal, "a restore into a missing save directory is INTERNAL"); + SaveDataCheckBackupData check{}; + check.dir_name = &name; + Check(sceSaveDataCheckBackupData(&check) == 0, "the backup outlives a restore into a missing save directory"); +} + +void CheckBackupSwap() { + SceSaveDataDirName name{}; + std::memcpy(name.data, "rotated", sizeof("rotated")); + const auto backup = std::filesystem::path("_sd_backup") / "rotated" / "save"; + SaveDataParam param{}; + SaveDataCheckBackupData check{}; + check.dir_name = &name; + check.param = ¶m; + const char* titles[] = {"First", "Second", "Third"}; + for (int i = 0; i < 3; ++i) { + const std::string what = "backup " + std::to_string(i + 1) + " of three"; + const std::string data = "rotation " + std::to_string(i + 1); + Check(BackUp(&name, data, titles[i]) == 0, what + " succeeds"); + Check(ReadText(backup / "data.bin") == data, what + " holds the save it was made from"); + Check(sceSaveDataCheckBackupData(&check) == 0 && std::strcmp(param.title, titles[i]) == 0, what + " replaces the last one's param"); + Check(!LeavesCopies("rotated"), what + " leaves no staged or old copy"); + } + const auto previous = Snapshot(backup); + const FailedStep failures[] = {{"backup.aside", 1}, {"backup.place", 1}}; + for (const FailedStep& failure : failures) { + const std::string what = std::string("a backup whose ") + failure.step + " step fails"; + FailStep(failure.step, failure.call); + const int error = BackUp(&name, "unsaved", "Unsaved"); + ClearFailingStep(); + Check(error == SaveDataErrorInternal, what + " gives the event error INTERNAL, got " + std::to_string(error)); + Check(Snapshot(backup) == previous, what + " keeps the previous backup byte for byte"); + param = {}; + Check(sceSaveDataCheckBackupData(&check) == 0 && std::strcmp(param.title, "Third") == 0, what + " leaves the previous backup usable"); + Check(!LeavesCopies("rotated"), what + " leaves no staged or old copy"); + } + SaveDataRestoreBackupData restore{}; + restore.dir_name = &name; + Check(sceSaveDataRestoreBackupData(&restore) == 0, "the backup kept by the failed backups restores"); + Check(ReadText(std::filesystem::path("_sd") / "rotated" / "data.bin") == "rotation 3", "the restore puts back the third backup"); +} + +#ifdef _WIN32 +bool Junction(const std::filesystem::path& link, const std::filesystem::path& target) { + std::error_code error; + if (!std::filesystem::create_directory(link, error)) return false; + 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{}; + if (substituteName.size() + printName.size() + 2 > std::size(data.names)) return false; + 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 HANDLE handle = CreateFileW(link.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr); + bool set = false; + if (handle != INVALID_HANDLE_VALUE) { + DWORD returned = 0; + set = DeviceIoControl(handle, FSCTL_SET_REPARSE_POINT, &data, 8 + data.dataLength, nullptr, 0, &returned, nullptr) != 0; + CloseHandle(handle); + } + if (!set) RemoveDirectoryW(link.c_str()); + return set; +} +#endif + +bool LinkDirectory(const std::filesystem::path& link, const std::filesystem::path& target) { +#ifdef _WIN32 + return Junction(link, target); +#else + std::error_code error; + std::filesystem::create_directory_symlink(std::filesystem::absolute(target), link, error); + return !error; +#endif +} + +bool LinkFile(const std::filesystem::path& link, const std::filesystem::path& target) { +#ifdef _WIN32 + return CreateSymbolicLinkW(link.c_str(), std::filesystem::absolute(target).c_str(), SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) != 0; +#else + std::error_code error; + std::filesystem::create_symlink(std::filesystem::absolute(target), link, error); + return !error; +#endif +} + +void Unlink(const std::filesystem::path& link) { +#ifdef _WIN32 + const DWORD attributes = GetFileAttributesW(link.c_str()); + const bool removed = attributes != INVALID_FILE_ATTRIBUTES && ((attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 ? RemoveDirectoryW(link.c_str()) != 0 : DeleteFileW(link.c_str()) != 0); +#else + std::error_code error; + const bool removed = std::filesystem::remove(link, error); +#endif + Check(removed, "the link " + link.string() + " is removed"); +} + +void CheckLinkedPaths() { + const auto outside = std::filesystem::absolute("outside"); + std::filesystem::create_directories(outside / "sentinel"); + WriteText(outside / "sentinel" / "keep.bin", "sentinel"); + std::filesystem::create_directories(outside / "old" / "escape"); + WriteText(outside / "old" / "escape" / "keep.bin", "sentinel"); + const auto sentinel = Snapshot(outside / "sentinel"); + const auto oldSentinel = Snapshot(outside / "old" / "escape"); + SceSaveDataDirName name{}; + std::memcpy(name.data, "escape", sizeof("escape")); + Check(BackUp(&name, "inside", "Inside") == 0, "the escape save is backed up"); + const auto backup = std::filesystem::path("_sd_backup") / "escape" / "save"; + const auto kept = Snapshot(backup); + const auto live = std::filesystem::path("_sd") / "escape"; + SaveDataRestoreBackupData restore{}; + restore.dir_name = &name; + const auto survives = [&](const std::string& what) { + Check(Snapshot(outside / "sentinel") == sentinel && Snapshot(outside / "old" / "escape") == oldSentinel, what + ": the outside directory and its files survive"); + Check(Snapshot(backup) == kept, what + ": the backup is kept"); + }; + const auto restoreRefused = [&](const std::string& what) { + Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorInternal, what + ": restoreBackupData is INTERNAL"); + survives(what); + }; + const auto backupRefused = [&](const std::string& what) { + Check(RemountWithBackup(&name) == SaveDataErrorInternal, what + ": the backup gives the event error INTERNAL"); + survives(what); + }; + + std::filesystem::remove_all(live); + if (LinkDirectory(live, outside / "sentinel")) { + restoreRefused("a save directory that links outside"); + backupRefused("a save directory that links outside"); + Unlink(live); + } else { + std::fprintf(stderr, "skipped the linked save directory: the link could not be created\n"); + } + std::filesystem::create_directories(live); + WriteText(live / "data.bin", "inside"); + + if (LinkFile(live / "link.bin", outside / "sentinel" / "keep.bin")) { + restoreRefused("a save with a file that links outside"); + backupRefused("a save with a file that links outside"); + Unlink(live / "link.bin"); + } else { + std::fprintf(stderr, "skipped the linked file in a save: the link could not be created\n"); + } + const auto inside = Snapshot(live); + + for (const char* root : {"_sd_backup_old", "_sd_restore_old"}) { + std::error_code error; + std::filesystem::remove_all(root, error); + const std::string what = std::string("a ") + root + " root that links outside"; + if (!LinkDirectory(root, outside / "old")) { + std::fprintf(stderr, "skipped %s: the link could not be created\n", what.c_str()); + continue; + } + if (std::strcmp(root, "_sd_backup_old") == 0) { + backupRefused(what); + } else { + restoreRefused(what); + } + Unlink(root); + } + + Check(Snapshot(live) == inside, "the refused calls leave the save as it was"); + Check(sceSaveDataRestoreBackupData(&restore) == 0, "the save restores once its paths are plain again"); + Check(ReadText(live / "data.bin") == "inside", "the restore puts back the kept backup"); +} + +void CheckBackupGuestErrors() { + SceSaveDataDirName name{}; + std::memcpy(name.data, "errors", sizeof("errors")); + SaveDataParam param{}; + SaveDataCheckBackupData check{}; + check.dir_name = &name; + check.param = ¶m; + Check(BackUp(&name, "first", "First") == 0, "the first backup of a save succeeds"); + Check(BackUp(&name, "second", "Second") == 0, "a second sceSaveDataUmountWithBackup succeeds"); + Check(sceSaveDataCheckBackupData(&check) == 0 && std::strcmp(param.title, "Second") == 0, "the second backup replaces the first one's param"); + Check(ReadText(std::filesystem::path("_sd_backup") / "errors" / "save" / "data.bin") == "second", "the second backup replaces the first one's files"); + + SaveDataMountResult mounted{}; + Check(MountSave(&name, MountReadWrite, &mounted), "the save mounts for a backup whose copy fails"); + FailStep("backup.copy", 1); + Check(sceSaveDataUmountWithBackup(&mounted.mount_point) == 0, "umountWithBackup returns 0 when the backup copy fails"); + ClearFailingStep(); + SaveDataEvent event{}; + Check(sceSaveDataGetEventResult(nullptr, &event) == 0 && event.error_code == SaveDataErrorInternal, "a failed backup copy gives the event error INTERNAL, got " + std::to_string(event.error_code)); + SaveDataMountInfo info{}; + Check(sceSaveDataGetMountInfo(&mounted.mount_point, &info) == SaveDataErrorNotMounted, "a failed backup copy still unmounts the save"); + param = {}; + Check(sceSaveDataCheckBackupData(&check) == 0 && std::strcmp(param.title, "Second") == 0, "a failed backup copy keeps the previous backup"); + + SceSaveDataTitleId other{}; + std::memcpy(other.data, "PPSA99999", sizeof("PPSA99999")); + check.title_id = &other; + Check(sceSaveDataCheckBackupData(&check) == SaveDataErrorNotFound, "checkBackupData with another title's id is NOT_FOUND"); + SaveDataRestoreBackupData restore{}; + restore.dir_name = &name; + restore.title_id = &other; + Check(sceSaveDataRestoreBackupData(&restore) == SaveDataErrorNotFound, "restoreBackupData with another title's id is NOT_FOUND"); + + for (const char* invalid : {"..", "a/b"}) { + const std::string label = std::string("\"") + invalid + "\""; + SceSaveDataDirName bad{}; + std::memcpy(bad.data, invalid, std::strlen(invalid) + 1); + SaveDataCheckBackupData badCheck{}; + badCheck.dir_name = &bad; + Check(sceSaveDataCheckBackupData(&badCheck) == SaveDataErrorParameter, "checkBackupData of " + label + " is PARAMETER"); + SaveDataRestoreBackupData badRestore{}; + badRestore.dir_name = &bad; + Check(sceSaveDataRestoreBackupData(&badRestore) == SaveDataErrorParameter, "restoreBackupData of " + label + " is PARAMETER"); + SaveDataMount3 mount{}; + mount.dir_name = &bad; + mount.mount_mode = MountReadWrite | MountCreate2; + SaveDataMountResult result{}; + Check(sceSaveDataMount3(&mount, &result) == SaveDataErrorParameter, "a save named " + label + " cannot be mounted to be backed up"); + SaveDataMountPoint point{}; + std::memcpy(point.data, invalid, std::strlen(invalid) + 1); + Check(sceSaveDataUmountWithBackup(&point) == SaveDataErrorNotMounted, "umountWithBackup of the mount point " + label + " is NOT_MOUNTED"); + } +} + void CheckMountCapacityAndLifecycle() { Check(sceSaveDataAbort() == 0, "abort after init succeeds"); Check(sceSaveDataClearProgress() == 0, "clearProgress after init succeeds"); @@ -396,6 +899,12 @@ int main() { CheckFailedMountKeepsResult(); CheckEquivalentMounts(); CheckInvalidNamesAreRejected(); + CheckBackups(); + CheckCorruptBackups(); + CheckStagedRestore(); + CheckBackupSwap(); + CheckLinkedPaths(); + CheckBackupGuestErrors(); Check(sceSaveDataTerminate() == 0, "SaveData terminates"); Check(sceSaveDataAbort() == SaveDataErrorNotInitialized, "abort post-terminate returns NOT_INITIALIZED"); Check(sceSaveDataClearProgress() == SaveDataErrorNotInitialized, "clearProgress post-terminate returns NOT_INITIALIZED"); diff --git a/core/libs/tests/GuestSaveDataPs4.cpp b/core/libs/tests/GuestSaveDataPs4.cpp index d35c27146d..fe933e11bc 100644 --- a/core/libs/tests/GuestSaveDataPs4.cpp +++ b/core/libs/tests/GuestSaveDataPs4.cpp @@ -2,21 +2,34 @@ #include "prx/libSceSaveData.native/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" +#include #include +#include #include #include #include #include #include #include +#ifdef _WIN32 +#include +#else +#include +#endif + +static_assert(offsetof(SaveDataMount2, dir_name) == 8 && offsetof(SaveDataMount2, blocks) == 16 && offsetof(SaveDataMount2, mount_mode) == 24 && sizeof(SaveDataMount2) == 64, "SaveDataMount2 has the PS4 layout"); namespace { -constexpr int SaveDataErrorNotFound = -2137063416; -constexpr int SaveDataErrorNotInitialized = -2137063423; constexpr int SaveDataErrorParameter = -2137063424; +constexpr int SaveDataErrorNotInitialized = -2137063423; +constexpr int SaveDataErrorNotFound = -2137063416; constexpr int SaveDataErrorNotMounted = -2137063420; +constexpr int SaveDataErrorBusy = -2137063421; +constexpr int SaveDataErrorInvalidLoginUser = -2137063407; constexpr std::uint32_t MountReadWrite = 2; +constexpr std::uint32_t MountCreate = 4; +constexpr std::uint32_t MountCreate2 = 32; int failures = 0; @@ -71,8 +84,80 @@ void TestDirNameSearchPs4() { Check(sceSaveDataDirNameSearchPs4(&cond, nullptr) == SaveDataErrorParameter, "null result returns PARAMETER in sceSaveDataDirNameSearchPs4"); } +void TestMount2() { + SceSaveDataDirName created{}; + std::strcpy(created.data, "mount2-new"); + SaveDataMount2 mount{}; + mount.user_id = 1; + mount.dir_name = &created; + mount.blocks = 96; + mount.mount_mode = MountReadWrite | MountCreate2; + SaveDataMountResult result{}; + Check(sceSaveDataMount2(&mount, &result) == SaveDataErrorNotInitialized, "Mount2 before initialization is NOT_INITIALIZED"); + Check(sceSaveDataInitialize3(nullptr) == 0, "SaveData initializes"); + Check(sceSaveDataMount2(&mount, &result) == 0, "Mount2 creates a missing save"); + Check(result.mount_status == 1u, "Mount2 reports the save as created"); + Check(result.mount_point.data[0] != '\0', "Mount2 reports a mount point"); + Check(std::filesystem::is_directory(std::filesystem::path("_sd") / "mount2-new"), "Mount2 creates the save directory"); + SaveDataMountResult busy{}; + Check(sceSaveDataMount2(&mount, &busy) == SaveDataErrorBusy, "Mount2 of a mounted save is BUSY"); + Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the Mount2 mount point unmounts"); + + SceSaveDataDirName kept{}; + std::strcpy(kept.data, "kept"); + mount.dir_name = &kept; + mount.mount_mode = MountReadWrite; + Check(sceSaveDataMount2(&mount, &result) == 0, "Mount2 opens an existing save"); + Check(result.mount_status == 0u, "Mount2 reports the existing save as opened"); + Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the opened save unmounts"); + mount.blocks = 0; + mount.mount_mode = MountReadWrite | MountCreate2; + Check(sceSaveDataMount2(&mount, &result) == 0, "Mount2 CREATE2 with 0 blocks opens an existing save"); + Check(result.mount_status == 0u, "Mount2 CREATE2 reports the existing save as opened"); + Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the CREATE2 mount point unmounts"); + mount.blocks = 96; + mount.mount_mode = MountReadWrite; + + SceSaveDataDirName absent{}; + std::strcpy(absent.data, "absent"); + mount.dir_name = &absent; + Check(sceSaveDataMount2(&mount, &result) == SaveDataErrorNotFound, "Mount2 open of a missing save is NOT_FOUND"); + Check(sceSaveDataMount2(nullptr, &result) == SaveDataErrorParameter, "Mount2 with a null mount is PARAMETER"); + Check(sceSaveDataMount2(&mount, nullptr) == SaveDataErrorParameter, "Mount2 with a null result is PARAMETER"); + + SceSaveDataDirName small{}; + std::strcpy(small.data, "mount2-small"); + mount.dir_name = &small; + mount.mount_mode = 0; + Check(sceSaveDataMount2(&mount, &result) == SaveDataErrorParameter, "Mount2 with mount mode 0 is PARAMETER"); + mount.blocks = 1; + mount.mount_mode = MountReadWrite | MountCreate; + Check(sceSaveDataMount2(&mount, &result) == SaveDataErrorParameter, "Mount2 creating a save of fewer than 96 blocks is PARAMETER"); + Check(!std::filesystem::exists(std::filesystem::path("_sd") / "mount2-small"), "a refused Mount2 creates no directory"); + + mount.blocks = 96; + for (const char* name : {"survey_test", "SURVEY_TEST"}) { + SceSaveDataDirName underscored{}; + std::strcpy(underscored.data, name); + mount.dir_name = &underscored; + Check(sceSaveDataMount2(&mount, &result) == SaveDataErrorParameter, std::string("Mount2 of ") + name + " is PARAMETER"); + Check(!std::filesystem::exists(std::filesystem::path("_sd") / name), std::string("a refused Mount2 of ") + name + " creates no directory"); + } + SceSaveDataDirName mixed{}; + std::strcpy(mixed.data, "surveyTestAuto0"); + mount.dir_name = &mixed; + Check(sceSaveDataMount2(&mount, &result) == 0, "Mount2 creates a mixed-case save"); + Check(sceSaveDataUmount2(0, &result.mount_point) == 0, "the mixed-case save unmounts"); + for (const int user : {-1, 0}) { + mount.user_id = user; + Check(sceSaveDataMount2(&mount, &result) == SaveDataErrorInvalidLoginUser, "Mount2 of user " + std::to_string(user) + " is INVALID_LOGIN_USER"); + } +} + void TestSaveDataMemoryV1() { Check(sceSaveDataSetupSaveDataMemory(7, 0x1000, nullptr) == SaveDataErrorNotInitialized, "v1 SetupSaveDataMemory before v1 Initialize"); + Check(sceSaveDataGetSaveDataMemory(7, nullptr, 0, 0) == SaveDataErrorNotInitialized, "v1 GetSaveDataMemory before v1 Initialize"); + Check(sceSaveDataSetSaveDataMemory(7, nullptr, 0, 0) == SaveDataErrorNotInitialized, "v1 SetSaveDataMemory before v1 Initialize"); Check(sceSaveDataInitialize(nullptr) == 0, "v1 Initialize"); Check(sceSaveDataSetupSaveDataMemory(7, 0x1000, nullptr) == 0, "v1 SetupSaveDataMemory"); char written[16] = "settings-v1"; @@ -80,6 +165,14 @@ void TestSaveDataMemoryV1() { 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"); + Check(sceSaveDataGetSaveDataMemory(7, read, sizeof(read), -1) == SaveDataErrorParameter, "v1 GetSaveDataMemory negative offset returns PARAMETER"); + Check(sceSaveDataSetSaveDataMemory(7, written, sizeof(written), -1) == SaveDataErrorParameter, "v1 SetSaveDataMemory negative offset returns PARAMETER"); + Check(sceSaveDataGetSaveDataMemory(7, read, sizeof(read), 0x1001) == SaveDataErrorParameter, "v1 GetSaveDataMemory offset beyond size returns PARAMETER"); + Check(sceSaveDataSetSaveDataMemory(7, written, sizeof(written), 0x1001) == SaveDataErrorParameter, "v1 SetSaveDataMemory offset beyond size returns PARAMETER"); + Check(sceSaveDataGetSaveDataMemory(7, nullptr, sizeof(read), 0x100) == SaveDataErrorParameter, "v1 GetSaveDataMemory null buffer returns PARAMETER"); + Check(sceSaveDataSetSaveDataMemory(7, nullptr, sizeof(written), 0x100) == SaveDataErrorParameter, "v1 SetSaveDataMemory null buffer returns PARAMETER"); + Check(sceSaveDataGetSaveDataMemory(7, nullptr, 0, 0x100) == 0, "v1 GetSaveDataMemory zero length is a no-op"); + Check(sceSaveDataSetSaveDataMemory(7, nullptr, 0, 0x100) == 0, "v1 SetSaveDataMemory zero length is a no-op"); std::ifstream slot("_sd_mem/u7/slot0.bin", std::ios::binary); slot.seekg(0x100); char stored[16] = {}; @@ -87,6 +180,25 @@ void TestSaveDataMemoryV1() { Check(sceSaveDataTerminate() == 0, "Terminate after v1 Initialize"); } +void TestInitialize2() { + using Initialize2 = int (APS5_VABI*)(const void*); +#ifdef _WIN32 + void* symbol = reinterpret_cast(GetProcAddress(GetModuleHandleA("libSceSaveData.native.prx"), "sceSaveDataInitialize2")); +#else + void* symbol = dlsym(RTLD_DEFAULT, "sceSaveDataInitialize2"); +#endif + if (!symbol) { + std::fprintf(stderr, "sceSaveDataInitialize2 is not exported by libSceSaveData.native\n"); + std::abort(); + } + const auto initialize2 = reinterpret_cast(symbol); + Check(sceSaveDataSetupSaveDataMemory(8, 0x1000, nullptr) == SaveDataErrorNotInitialized, "SetupSaveDataMemory before Initialize2"); + Check(initialize2(nullptr) == 0, "Initialize2"); + Check(sceSaveDataSetupSaveDataMemory(8, 0x1000, nullptr) == 0, "SetupSaveDataMemory after Initialize2"); + Check(sceSaveDataTerminate() == 0, "Terminate after Initialize2"); + Check(sceSaveDataSetupSaveDataMemory(8, 0x1000, nullptr) == SaveDataErrorNotInitialized, "SetupSaveDataMemory after the Initialize2 session ended"); +} + void TestMountPs4() { SaveDataMountResult result{}; Check(sceSaveDataMount(nullptr, &result) == SaveDataErrorParameter, "null mount is PARAMETER"); @@ -115,7 +227,9 @@ int main() { TestTransferringMountPs4(); TestDirNameSearchPs4(); TestSaveDataMemoryV1(); + TestInitialize2(); TestMountPs4(); + TestMount2(); std::filesystem::current_path(previous); std::error_code error; diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index fa16b7831e..471bbe4eb9 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -359,6 +359,129 @@ static void CheckIfName() { Require(missing != nullptr && missing[0] == '\0' && *sceNetErrnoLoc() == 22); } +static void CheckExForwards() { + const int internal = sceNetSocketInternal("internal", 2, 2, 17); + Require(internal >= 0); + const int enabled = 1; + Require(sceNetSetsockoptEx(internal, 0xffff, 0x4, &enabled, sizeof(enabled)) == 0); + int option = 0; + std::uint32_t optionSize = sizeof(option); + Require(sceNetGetsockoptEx(internal, 0xffff, 0x4, &option, &optionSize) == 0); + int publicOption = 0; + std::uint32_t publicSize = sizeof(publicOption); + Require(sceNetGetsockopt(internal, 0xffff, 0x4, &publicOption, &publicSize) == 0); + Require(option == publicOption && option != 0 && optionSize == publicSize); + const KernelTimeval timeout{0, 250000}; + Require(sceNetSetsockoptEx(internal, 0xffff, 0x1006, &timeout, sizeof(timeout)) == 0); + KernelTimeval read{}; + std::uint32_t readSize = sizeof(read); + Require(sceNetGetsockoptEx(internal, 0xffff, 0x1006, &read, &readSize) == 0); + Require(readSize == sizeof(read) && read.tv_sec == 0 && read.tv_usec == 250000); + Require(sceNetSocketClose(internal) == 0); + for (const int closed : {internal, -1}) { + optionSize = sizeof(option); + const int ex = sceNetGetsockoptEx(closed, 0xffff, 0x4, &option, &optionSize); + const int exError = *sceNetErrnoLoc(); + Require(ex < 0 && ex == sceNetGetsockopt(closed, 0xffff, 0x4, &option, &optionSize) && exError == *sceNetErrnoLoc()); + const int set = sceNetSetsockoptEx(closed, 0xffff, 0x4, &enabled, sizeof(enabled)); + const int setError = *sceNetErrnoLoc(); + Require(set < 0 && set == sceNetSetsockopt(closed, 0xffff, 0x4, &enabled, sizeof(enabled)) && setError == *sceNetErrnoLoc()); + } + for (const auto& [family, type] : {std::pair{1, 2}, std::pair{2, 9}}) { + const int internalResult = sceNetSocketInternal(nullptr, family, type, 0); + const int internalError = *sceNetErrnoLoc(); + Require(internalResult < 0 && internalResult == sceNetSocket(nullptr, family, type, 0) && internalError == *sceNetErrnoLoc()); + } +} + +static void CheckSysctl() { + const std::array pageSize{6, 7}; + int kernelValue = 0; + std::size_t kernelSize = sizeof(kernelValue); + Require(sysctl_nid_postfix(pageSize.data(), 2, &kernelValue, &kernelSize, nullptr, 0) == 0); + int netValue = 0; + std::size_t netSize = sizeof(netValue); + Require(sceNetSysctl(pageSize.data(), 2, &netValue, &netSize, nullptr, 0) == 0); + Require(netValue == kernelValue && netSize == kernelSize && netValue > 0); + std::size_t probe = 0; + Require(sceNetSysctl(pageSize.data(), 2, nullptr, &probe, nullptr, 0) == 0 && probe == sizeof(int)); + Require(Failed(sceNetSysctl(pageSize.data(), 1, &netValue, &netSize, nullptr, 0), 22)); + Require(Failed(sceNetSysctl(nullptr, 2, &netValue, &netSize, nullptr, 0), 14)); + Require(Failed(sceNetSysctl(pageSize.data(), 2, &netValue, &netSize, &netValue, sizeof(netValue)), 1)); + std::uint8_t truncated = 0; + std::size_t truncatedSize = sizeof(truncated); + Require(Failed(sceNetSysctl(pageSize.data(), 2, &truncated, &truncatedSize, nullptr, 0), 12) && truncatedSize == sizeof(truncated)); +} + +static void CheckScopeIdText() { + std::array plain{}; + Require(sceNetInetPton(28, "fe80::1", plain.data()) == 1); + std::array address{}; + std::uint32_t scope = 0; + Require(sceNetInetPtonWithScopeId(28, "fe80::1%lo0", address.data(), &scope) == 1 && scope != 0 && address == plain); + std::array text{}; + Require(sceNetInetNtopWithScopeId(28, address.data(), text.data(), text.size(), scope) == text.data()); + Require(std::strcmp(text.data(), "fe80::1%lo0") == 0); + text.fill('x'); + *sceNetErrnoLoc() = 123; + Require(sceNetInetNtopWithScopeId(28, address.data(), text.data(), 11, scope) == nullptr && *sceNetErrnoLoc() == 28 && text[0] == 'x'); + Require(sceNetInetNtopWithScopeId(28, address.data(), text.data(), 12, scope) == text.data() && std::strcmp(text.data(), "fe80::1%lo0") == 0); + Require(sceNetInetNtopWithScopeId(28, address.data(), text.data(), text.size(), 0) == text.data() && std::strcmp(text.data(), "fe80::1") == 0); + for (const char* rejected : {"fe80::1%2", "ff02::1%4294967295", "fe80::1%", "fe80::1%eth9", "fe80::g%lo0", "1.2.3.4%lo0"}) { + address.fill(0xa5); + scope = 0xa5a5a5a5u; + Require(sceNetInetPtonWithScopeId(28, rejected, address.data(), &scope) == 0); + Require(scope == 0xa5a5a5a5u && address[0] == 0xa5 && address[15] == 0xa5); + } + address.fill(0xa5); + Require(sceNetInetPtonWithScopeId(28, "fe80::1", address.data(), nullptr) == 1 && address == plain); + address.fill(0xa5); + Require(sceNetInetPtonWithScopeId(28, "fe80::1%lo0", address.data(), nullptr) == 0 && address[0] == 0xa5 && address[15] == 0xa5); + Require(Failed(sceNetInetPtonWithScopeId(2, "fe80::1", address.data(), &scope), 47)); + Require(Failed(sceNetInetPtonWithScopeId(28, nullptr, address.data(), &scope), 22)); + bool threw = false; + try { + sceNetInetPtonWithScopeId(28, "fe80::1", nullptr, &scope); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw); + *sceNetErrnoLoc() = 123; + Require(sceNetInetNtopWithScopeId(28, nullptr, text.data(), text.size(), 0) == nullptr && *sceNetErrnoLoc() == 28); + *sceNetErrnoLoc() = 123; + Require(sceNetInetNtopWithScopeId(28, plain.data(), nullptr, 32, 0) == nullptr && *sceNetErrnoLoc() == 28); + *sceNetErrnoLoc() = 123; + Require(sceNetInetNtopWithScopeId(2, plain.data(), text.data(), text.size(), 0) == nullptr && *sceNetErrnoLoc() == 47); +} + +static void CheckDefaultRoute() { + std::uint32_t gateway = 0xa5a5a5a5u; + Require(sceNetTerm() == 0); + Require(Failed(sceNetConfigGetDefaultRoute(&gateway), 200) && gateway == 0xa5a5a5a5u); + Require(Failed(sceNetConfigGetDefaultRoute(nullptr), 200)); + Require(sceNetInit_nid_postfix() == 0); + NetCtlInfo info{}; + const int result = sceNetCtlGetInfo(16, &info); + const char* route = reinterpret_cast(info.opaque); + if (result != 0 || std::strcmp(route, "0.0.0.0") == 0) { + Require(result == 0 || result == static_cast(0x80412108)); + Require(sceNetConfigGetDefaultRoute(&gateway) == static_cast(0x8041130Cu) && gateway == 0xa5a5a5a5u); + Require(sceNetConfigGetDefaultRoute(nullptr) == static_cast(0x8041130Cu)); + return; + } + Require(sceNetConfigGetDefaultRoute(&gateway) == 1); + std::array text{}; + Require(sceNetInetNtop(2, &gateway, text.data(), text.size()) == text.data()); + Require(std::strcmp(text.data(), route) == 0); + bool threw = false; + try { + sceNetConfigGetDefaultRoute(nullptr); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw); +} + static void CheckRandom() { Require(Failed(sceNetGetRandom(nullptr), 22)); std::array values{}; @@ -665,6 +788,9 @@ static void CheckRejectedArguments() { Require(std::memcmp(mac.data, untouched.data(), untouched.size()) == 0); } Require(Failed(sceNetGetMacAddress(nullptr, 1), 22)); + Require(Failed(sceNetGetMacAddress(reinterpret_cast(std::uintptr_t{0x10}), 0), 14)); + Require(sceNetGetMacAddress(&mac, 0) == 0); + Require(std::memcmp(mac.data, "\x02\x50\x53\x35\x00\x01", sizeof(mac.data)) == 0); for (const int flags : {1, 0x10000, -1}) Require(Failed(sceNetResolverCreate("rejected-resolver", 0, flags), 22)); @@ -1052,7 +1178,9 @@ static void CheckIoctl() { 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, 0x8004667e, reinterpret_cast(std::uintptr_t{0x10})), 14)); Require(Failed(sceNetIoctl(socket, 0x4004667f, nullptr), 14)); + Require(Failed(sceNetIoctl(socket, 0x4004667f, reinterpret_cast(std::uintptr_t{0x10})), 14)); Require(Failed(sceNetIoctl(socket, 123, &readable), 25)); Require(Failed(sceNetIoctl(socket, 123, nullptr), 25)); Require(sceNetSocketClose(socket) == 0); @@ -1955,6 +2083,20 @@ static void CheckSockInfo() { Require(sceNetGetsockname(client, clientAddress.data(), &size) == 0 && size == 16); RequireSockInfo(client, 1, 1, clientAddress, listening); RequireSockInfo(accepted, 1, 1, listening, clientAddress); + const char queued[] = "queued"; + constexpr auto queuedLength = static_cast(sizeof(queued)); + Require(sceNetSend(client, queued, sizeof(queued), 0) == queuedLength); + std::array pending{}; + std::int32_t pendingLength = 0; + for (int attempt = 0; attempt < 500 && pendingLength != queuedLength; ++attempt) { + Require(sceNetGetSockInfo(accepted, pending.data(), 1, 0) == 1); + std::memcpy(&pendingLength, pending.data() + 0x2c, sizeof(pendingLength)); + if (pendingLength != queuedLength) std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + Require(pendingLength == queuedLength); + char drained[sizeof(queued)] = {}; + Require(sceNetRecv(accepted, drained, sizeof(drained), 0) == queuedLength && std::memcmp(drained, queued, sizeof(queued)) == 0); + RequireSockInfo(accepted, 1, 1, listening, clientAddress); Require(sceNetConnect(datagram, listening.data(), listening.size()) == 0); RequireSockInfo(datagram, 2, 1, datagramAddress, listening); const int nonblocking = 1; @@ -2243,6 +2385,9 @@ int main() { CheckPoolErrors(); CheckStatisticsInfo(); CheckIfName(); + CheckExForwards(); + CheckSysctl(); + CheckScopeIdText(); CheckRandom(); CheckByteOrderConversion(); CheckNetMemory(); @@ -2665,6 +2810,7 @@ int main() { Require(sceNetResolverAbort(other, 0) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9); Require(sceNetResolverAbort(-1, 1) == static_cast(0x80410109) && *sceNetErrnoLoc() == 9); Require(sceNetResolverGetError(resolver, nullptr) == static_cast(0x80410116) && *sceNetErrnoLoc() == 22); + Require(Failed(sceNetResolverGetError(resolver, reinterpret_cast(std::uintptr_t{0x10})), 14)); Require(sceNetClearDnsCache(0) == 0); ipv4.fill(0xA5); Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0 && ipv4[0] == 127); @@ -2697,6 +2843,7 @@ int main() { Require(sceNetCtlGetState(&state) == 0); Require(state == 0 || state == 3); Require(sceNetCtlGetIfStat(nullptr) == static_cast(0x80412107)); + CheckDefaultRoute(); { const char* counterPath = "guest_sce_net_byte_counter.txt"; std::FILE* counter = std::fopen(counterPath, "w"); diff --git a/core/libs/tests/GuestSceV2BinaryScaling.cpp b/core/libs/tests/GuestSceV2BinaryScaling.cpp new file mode 100644 index 0000000000..7e7383846f --- /dev/null +++ b/core/libs/tests/GuestSceV2BinaryScaling.cpp @@ -0,0 +1,299 @@ +#include "prx/libc/libc.hpp" +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +struct ScaleCase { std::uint64_t input; long long exponent; short code; std::uint64_t result; short extendedCode; std::uint64_t extendedResult; }; +struct FloatScaleCase { std::uint32_t input; long long exponent; short code; std::uint32_t result; }; +struct UnscaleCase { std::uint64_t input; short code; short exponent; std::uint64_t result; short extendedCode; short extendedExponent; std::uint64_t extendedResult; }; +struct FloatUnscaleCase { std::uint32_t input; short code; short exponent; std::uint32_t result; }; + +static const ScaleCase kScales[] = { + {0x0000000000000000ull, 0ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000000ull, 52ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000000ull, -1022ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000000ull, -1075ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000000ull, 2047ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000000ull, -9223372036854775807ll - 1, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x8000000000000000ull, -1ll, 0, 0x8000000000000000ull, 0, 0x8000000000000000ull}, + {0x8000000000000000ull, 1023ll, 0, 0x8000000000000000ull, 0, 0x8000000000000000ull}, + {0x8000000000000000ull, -1074ll, 0, 0x8000000000000000ull, 0, 0x8000000000000000ull}, + {0x8000000000000000ull, 1024ll, 0, 0x8000000000000000ull, 0, 0x8000000000000000ull}, + {0x8000000000000000ull, 9223372036854775807ll, 0, 0x8000000000000000ull, 0, 0x8000000000000000ull}, + {0x0000000000000001ull, 1ll, -1, 0x0000000000000002ull, -1, 0x0000000000000002ull}, + {0x0000000000000001ull, -52ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000001ull, -1023ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000001ull, -1076ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000001ull, -2100ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x8000000000000003ull, 0ll, -1, 0x8000000000000003ull, -1, 0x8000000000000003ull}, + {0x8000000000000003ull, 52ll, -1, 0x8028000000000000ull, -1, 0x8028000000000000ull}, + {0x8000000000000003ull, -1022ll, 0, 0x8000000000000000ull, 0, 0x8000000000000000ull}, + {0x8000000000000003ull, -1075ll, 0, 0x8000000000000000ull, 0, 0x8000000000000000ull}, + {0x8000000000000003ull, 2047ll, -1, 0xfcd8000000000000ull, -1, 0xfcd8000000000000ull}, + {0x8000000000000003ull, -9223372036854775807ll - 1, 0, 0x8000000000000000ull, 0, 0x8000000000000000ull}, + {0x000fffffffffffffull, -1ll, -1, 0x0008000000000000ull, -1, 0x0008000000000000ull}, + {0x000fffffffffffffull, 1023ll, -1, 0x3ffffffffffffffeull, -1, 0x3ffffffffffffffeull}, + {0x000fffffffffffffull, -1074ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x000fffffffffffffull, 1024ll, -1, 0x400ffffffffffffeull, -1, 0x400ffffffffffffeull}, + {0x000fffffffffffffull, 9223372036854775807ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x0010000000000000ull, 1ll, -1, 0x0020000000000000ull, -1, 0x0020000000000000ull}, + {0x0010000000000000ull, -52ll, -1, 0x0000000000000001ull, -1, 0x0000000000000001ull}, + {0x0010000000000000ull, -1023ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0010000000000000ull, -1076ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0010000000000000ull, -2100ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x3ff0000000000000ull, 0ll, -1, 0x3ff0000000000000ull, -1, 0x3ff0000000000000ull}, + {0x3ff0000000000000ull, 52ll, -1, 0x4330000000000000ull, -1, 0x4330000000000000ull}, + {0x3ff0000000000000ull, -1022ll, -1, 0x0010000000000000ull, -1, 0x0010000000000000ull}, + {0x3ff0000000000000ull, -1075ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x3ff0000000000000ull, 2047ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x3ff0000000000000ull, -9223372036854775807ll - 1, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0xbff8000000000000ull, -1ll, -1, 0xbfe8000000000000ull, -1, 0xbfe8000000000000ull}, + {0xbff8000000000000ull, 1023ll, -1, 0xffe8000000000000ull, -1, 0xffe8000000000000ull}, + {0xbff8000000000000ull, -1074ll, -1, 0x8000000000000002ull, -1, 0x8000000000000002ull}, + {0xbff8000000000000ull, 1024ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0xbff8000000000000ull, 9223372036854775807ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0x3ff0000000000001ull, 1ll, -1, 0x4000000000000001ull, -1, 0x4000000000000001ull}, + {0x3ff0000000000001ull, -52ll, -1, 0x3cb0000000000001ull, -1, 0x3cb0000000000001ull}, + {0x3ff0000000000001ull, -1023ll, -1, 0x0008000000000000ull, -1, 0x0008000000000000ull}, + {0x3ff0000000000001ull, -1076ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x3ff0000000000001ull, -2100ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x3ff8000000000000ull, 0ll, -1, 0x3ff8000000000000ull, -1, 0x3ff8000000000000ull}, + {0x3ff8000000000000ull, 52ll, -1, 0x4338000000000000ull, -1, 0x4338000000000000ull}, + {0x3ff8000000000000ull, -1022ll, -1, 0x0018000000000000ull, -1, 0x0018000000000000ull}, + {0x3ff8000000000000ull, -1075ll, -1, 0x0000000000000001ull, -1, 0x0000000000000001ull}, + {0x3ff8000000000000ull, 2047ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x3ff8000000000000ull, -9223372036854775807ll - 1, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x3ff4000000000000ull, -1ll, -1, 0x3fe4000000000000ull, -1, 0x3fe4000000000000ull}, + {0x3ff4000000000000ull, 1023ll, -1, 0x7fe4000000000000ull, -1, 0x7fe4000000000000ull}, + {0x3ff4000000000000ull, -1074ll, -1, 0x0000000000000001ull, -1, 0x0000000000000001ull}, + {0x3ff4000000000000ull, 1024ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x3ff4000000000000ull, 9223372036854775807ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x3ffc000000000000ull, 1ll, -1, 0x400c000000000000ull, -1, 0x400c000000000000ull}, + {0x3ffc000000000000ull, -52ll, -1, 0x3cbc000000000000ull, -1, 0x3cbc000000000000ull}, + {0x3ffc000000000000ull, -1023ll, -1, 0x000e000000000000ull, -1, 0x000e000000000000ull}, + {0x3ffc000000000000ull, -1076ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x3ffc000000000000ull, -2100ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x7fefffffffffffffull, 0ll, -1, 0x7fefffffffffffffull, -1, 0x7fefffffffffffffull}, + {0x7fefffffffffffffull, 52ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x7fefffffffffffffull, -1022ll, -1, 0x400fffffffffffffull, -1, 0x400fffffffffffffull}, + {0x7fefffffffffffffull, -1075ll, -1, 0x3cbfffffffffffffull, -1, 0x3cbfffffffffffffull}, + {0x7fefffffffffffffull, 2047ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x7fefffffffffffffull, -9223372036854775807ll - 1, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0xffefffffffffffffull, -1ll, -1, 0xffdfffffffffffffull, -1, 0xffdfffffffffffffull}, + {0xffefffffffffffffull, 1023ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0xffefffffffffffffull, -1074ll, -1, 0xbccfffffffffffffull, -1, 0xbccfffffffffffffull}, + {0xffefffffffffffffull, 1024ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0xffefffffffffffffull, 9223372036854775807ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0x7ff0000000000000ull, 1ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x7ff0000000000000ull, -52ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x7ff0000000000000ull, -1023ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x7ff0000000000000ull, -1076ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x7ff0000000000000ull, -2100ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0xfff0000000000000ull, 0ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0xfff0000000000000ull, 52ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0xfff0000000000000ull, -1022ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0xfff0000000000000ull, -1075ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0xfff0000000000000ull, 2047ll, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0xfff0000000000000ull, -9223372036854775807ll - 1, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0x7ff8000000000000ull, -1ll, 2, 0x7ff8000000000000ull, 2, 0x7ff8000000000000ull}, + {0x7ff8000000000000ull, 1023ll, 2, 0x7ff8000000000000ull, 2, 0x7ff8000000000000ull}, + {0x7ff8000000000000ull, -1074ll, 2, 0x7ff8000000000000ull, 2, 0x7ff8000000000000ull}, + {0x7ff8000000000000ull, 1024ll, 2, 0x7ff8000000000000ull, 2, 0x7ff8000000000000ull}, + {0x7ff8000000000000ull, 9223372036854775807ll, 2, 0x7ff8000000000000ull, 2, 0x7ff8000000000000ull}, + {0x7ff0000000000001ull, 1ll, 2, 0x7ff0000000000001ull, 2, 0x7ff0000000000001ull}, + {0x7ff0000000000001ull, -52ll, 2, 0x7ff0000000000001ull, 2, 0x7ff0000000000001ull}, + {0x7ff0000000000001ull, -1023ll, 2, 0x7ff0000000000001ull, 2, 0x7ff0000000000001ull}, + {0x7ff0000000000001ull, -1076ll, 2, 0x7ff0000000000001ull, 2, 0x7ff0000000000001ull}, + {0x7ff0000000000001ull, -2100ll, 2, 0x7ff0000000000001ull, 2, 0x7ff0000000000001ull}, + {0x405edd2f1a9fbe77ull, 0ll, -1, 0x405edd2f1a9fbe77ull, -1, 0x405edd2f1a9fbe77ull}, + {0x405edd2f1a9fbe77ull, 52ll, -1, 0x439edd2f1a9fbe77ull, -1, 0x439edd2f1a9fbe77ull}, + {0x405edd2f1a9fbe77ull, -1022ll, -1, 0x007edd2f1a9fbe77ull, -1, 0x007edd2f1a9fbe77ull}, + {0x405edd2f1a9fbe77ull, -1075ll, -1, 0x000000000000003eull, -1, 0x000000000000003eull}, + {0x405edd2f1a9fbe77ull, 2047ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x405edd2f1a9fbe77ull, -9223372036854775807ll - 1, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0008000000000000ull, -1ll, -1, 0x0004000000000000ull, -1, 0x0004000000000000ull}, + {0x0008000000000000ull, 1023ll, -1, 0x3ff0000000000000ull, -1, 0x3ff0000000000000ull}, + {0x0008000000000000ull, -1074ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0008000000000000ull, 1024ll, -1, 0x4000000000000000ull, -1, 0x4000000000000000ull}, + {0x0008000000000000ull, 9223372036854775807ll, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x0000000000000400ull, 1ll, -1, 0x0000000000000800ull, -1, 0x0000000000000800ull}, + {0x0000000000000400ull, -52ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000400ull, -1023ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000400ull, -1076ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x0000000000000400ull, -2100ll, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, +}; + +static const FloatScaleCase kFloatScales[] = { + {0x00000000u, 0ll, 0, 0x00000000u}, + {0x00000000u, 23ll, 0, 0x00000000u}, + {0x00000000u, -127ll, 0, 0x00000000u}, + {0x00000000u, -151ll, 0, 0x00000000u}, + {0x00000000u, 9223372036854775807ll, 0, 0x00000000u}, + {0x80000000u, 1ll, 0, 0x80000000u}, + {0x80000000u, 127ll, 0, 0x80000000u}, + {0x80000000u, -149ll, 0, 0x80000000u}, + {0x80000000u, 255ll, 0, 0x80000000u}, + {0x80000000u, -9223372036854775807ll - 1, 0, 0x80000000u}, + {0x00000001u, 23ll, -1, 0x00800000u}, + {0x00000001u, -126ll, 0, 0x00000000u}, + {0x00000001u, -151ll, 0, 0x00000000u}, + {0x00000001u, -300ll, 0, 0x00000000u}, + {0x80000003u, 1ll, -1, 0x80000006u}, + {0x80000003u, -23ll, 0, 0x80000000u}, + {0x80000003u, -149ll, 0, 0x80000000u}, + {0x80000003u, 128ll, -1, 0xb5c00000u}, + {0x80000003u, -9223372036854775807ll - 1, 0, 0x80000000u}, + {0x007fffffu, -1ll, -1, 0x00400000u}, + {0x007fffffu, -126ll, 0, 0x00000000u}, + {0x007fffffu, -150ll, 0, 0x00000000u}, + {0x007fffffu, -300ll, 0, 0x00000000u}, + {0x00800000u, 0ll, -1, 0x00800000u}, + {0x00800000u, -23ll, -1, 0x00000001u}, + {0x00800000u, -127ll, 0, 0x00000000u}, + {0x00800000u, 128ll, -1, 0x40800000u}, + {0x00800000u, 9223372036854775807ll, 1, 0x7f800000u}, + {0x3f800000u, -1ll, -1, 0x3f000000u}, + {0x3f800000u, 127ll, -1, 0x7f000000u}, + {0x3f800000u, -150ll, 0, 0x00000000u}, + {0x3f800000u, 255ll, 1, 0x7f800000u}, + {0xbfc00000u, 0ll, -1, 0xbfc00000u}, + {0xbfc00000u, 23ll, -1, 0xcb400000u}, + {0xbfc00000u, -127ll, -1, 0x80600000u}, + {0xbfc00000u, -151ll, 0, 0x80000000u}, + {0xbfc00000u, 9223372036854775807ll, 1, 0xff800000u}, + {0x3f800001u, 1ll, -1, 0x40000001u}, + {0x3f800001u, 127ll, -1, 0x7f000001u}, + {0x3f800001u, -149ll, -1, 0x00000001u}, + {0x3f800001u, 255ll, 1, 0x7f800000u}, + {0x3f800001u, -9223372036854775807ll - 1, 0, 0x00000000u}, + {0x3fc00000u, 23ll, -1, 0x4b400000u}, + {0x3fc00000u, -126ll, -1, 0x00c00000u}, + {0x3fc00000u, -151ll, 0, 0x00000000u}, + {0x3fc00000u, -300ll, 0, 0x00000000u}, + {0x3fa00000u, 1ll, -1, 0x40200000u}, + {0x3fa00000u, -23ll, -1, 0x34200000u}, + {0x3fa00000u, -149ll, -1, 0x00000001u}, + {0x3fa00000u, 128ll, 1, 0x7f800000u}, + {0x3fa00000u, -9223372036854775807ll - 1, 0, 0x00000000u}, + {0x3fe00000u, -1ll, -1, 0x3f600000u}, + {0x3fe00000u, -126ll, -1, 0x00e00000u}, + {0x3fe00000u, -150ll, -1, 0x00000001u}, + {0x3fe00000u, -300ll, 0, 0x00000000u}, + {0x7f7fffffu, 0ll, -1, 0x7f7fffffu}, + {0x7f7fffffu, -23ll, -1, 0x73ffffffu}, + {0x7f7fffffu, -127ll, -1, 0x3fffffffu}, + {0x7f7fffffu, 128ll, 1, 0x7f800000u}, + {0x7f7fffffu, 9223372036854775807ll, 1, 0x7f800000u}, + {0xff7fffffu, -1ll, -1, 0xfeffffffu}, + {0xff7fffffu, 127ll, 1, 0xff800000u}, + {0xff7fffffu, -150ll, -1, 0xb47fffffu}, + {0xff7fffffu, 255ll, 1, 0xff800000u}, + {0x7f800000u, 0ll, 1, 0x7f800000u}, + {0x7f800000u, 23ll, 1, 0x7f800000u}, + {0x7f800000u, -127ll, 1, 0x7f800000u}, + {0x7f800000u, -151ll, 1, 0x7f800000u}, + {0x7f800000u, 9223372036854775807ll, 1, 0x7f800000u}, + {0xff800000u, 1ll, 1, 0xff800000u}, + {0xff800000u, 127ll, 1, 0xff800000u}, + {0xff800000u, -149ll, 1, 0xff800000u}, + {0xff800000u, 255ll, 1, 0xff800000u}, + {0xff800000u, -9223372036854775807ll - 1, 1, 0xff800000u}, + {0x7fc00000u, 23ll, 2, 0x7fc00000u}, + {0x7fc00000u, -126ll, 2, 0x7fc00000u}, + {0x7fc00000u, -151ll, 2, 0x7fc00000u}, + {0x7fc00000u, -300ll, 2, 0x7fc00000u}, + {0x7f800001u, 1ll, 2, 0x7f800001u}, + {0x7f800001u, -23ll, 2, 0x7f800001u}, + {0x7f800001u, -149ll, 2, 0x7f800001u}, + {0x7f800001u, 128ll, 2, 0x7f800001u}, + {0x7f800001u, -9223372036854775807ll - 1, 2, 0x7f800001u}, + {0x42f6e979u, -1ll, -1, 0x4276e979u}, + {0x42f6e979u, -126ll, -1, 0x03f6e979u}, + {0x42f6e979u, -150ll, -1, 0x0000003eu}, + {0x42f6e979u, -300ll, 0, 0x00000000u}, + {0x00400000u, 0ll, -1, 0x00400000u}, + {0x00400000u, -23ll, 0, 0x00000000u}, + {0x00400000u, -127ll, 0, 0x00000000u}, + {0x00400000u, 128ll, -1, 0x40000000u}, + {0x00400000u, 9223372036854775807ll, 1, 0x7f800000u}, + {0x00000400u, -1ll, -1, 0x00000200u}, + {0x00000400u, 127ll, -1, 0x39800000u}, + {0x00000400u, -150ll, 0, 0x00000000u}, + {0x00000400u, 255ll, -1, 0x79800000u}, +}; + +static const UnscaleCase kUnscales[] = { + {0x0000000000000000ull, 0, 0, 0x0000000000000000ull, 0, 0, 0x0000000000000000ull}, + {0x8000000000000000ull, 0, 0, 0x8000000000000000ull, 0, 0, 0x8000000000000000ull}, + {0x0000000000000001ull, -1, -1073, 0x3fe0000000000000ull, -1, -1073, 0x3fe0000000000000ull}, + {0x8000000000000003ull, -1, -1072, 0xbfe8000000000000ull, -1, -1072, 0xbfe8000000000000ull}, + {0x000fffffffffffffull, -1, -1022, 0x3feffffffffffffeull, -1, -1022, 0x3feffffffffffffeull}, + {0x0010000000000000ull, -1, -1021, 0x3fe0000000000000ull, -1, -1021, 0x3fe0000000000000ull}, + {0x3ff0000000000000ull, -1, 1, 0x3fe0000000000000ull, -1, 1, 0x3fe0000000000000ull}, + {0xbff8000000000000ull, -1, 1, 0xbfe8000000000000ull, -1, 1, 0xbfe8000000000000ull}, + {0x3ff0000000000001ull, -1, 1, 0x3fe0000000000001ull, -1, 1, 0x3fe0000000000001ull}, + {0x3ff8000000000000ull, -1, 1, 0x3fe8000000000000ull, -1, 1, 0x3fe8000000000000ull}, + {0x3ff4000000000000ull, -1, 1, 0x3fe4000000000000ull, -1, 1, 0x3fe4000000000000ull}, + {0x3ffc000000000000ull, -1, 1, 0x3fec000000000000ull, -1, 1, 0x3fec000000000000ull}, + {0x7fefffffffffffffull, -1, 1024, 0x3fefffffffffffffull, -1, 1024, 0x3fefffffffffffffull}, + {0xffefffffffffffffull, -1, 1024, 0xbfefffffffffffffull, -1, 1024, 0xbfefffffffffffffull}, + {0x7ff0000000000000ull, 1, 0, 0x7ff0000000000000ull, 1, 0, 0x7ff0000000000000ull}, + {0xfff0000000000000ull, 1, 0, 0xfff0000000000000ull, 1, 0, 0xfff0000000000000ull}, + {0x7ff8000000000000ull, 2, 0, 0x7ff8000000000000ull, 2, 0, 0x7ff8000000000000ull}, + {0x7ff0000000000001ull, 2, 0, 0x7ff0000000000001ull, 2, 0, 0x7ff0000000000001ull}, + {0x405edd2f1a9fbe77ull, -1, 7, 0x3feedd2f1a9fbe77ull, -1, 7, 0x3feedd2f1a9fbe77ull}, + {0x0008000000000000ull, -1, -1022, 0x3fe0000000000000ull, -1, -1022, 0x3fe0000000000000ull}, + {0x0000000000000400ull, -1, -1063, 0x3fe0000000000000ull, -1, -1063, 0x3fe0000000000000ull}, +}; + +static const FloatUnscaleCase kFloatUnscales[] = { + {0x00000000u, 0, 0, 0x00000000u}, + {0x80000000u, 0, 0, 0x80000000u}, + {0x00000001u, -1, -148, 0x3f000000u}, + {0x80000003u, -1, -147, 0xbf400000u}, + {0x007fffffu, -1, -126, 0x3f7ffffeu}, + {0x00800000u, -1, -125, 0x3f000000u}, + {0x3f800000u, -1, 1, 0x3f000000u}, + {0xbfc00000u, -1, 1, 0xbf400000u}, + {0x3f800001u, -1, 1, 0x3f000001u}, + {0x3fc00000u, -1, 1, 0x3f400000u}, + {0x3fa00000u, -1, 1, 0x3f200000u}, + {0x3fe00000u, -1, 1, 0x3f600000u}, + {0x7f7fffffu, -1, 128, 0x3f7fffffu}, + {0xff7fffffu, -1, 128, 0xbf7fffffu}, + {0x7f800000u, 1, 0, 0x7f800000u}, + {0xff800000u, 1, 0, 0xff800000u}, + {0x7fc00000u, 2, 0, 0x7fc00000u}, + {0x7f800001u, 2, 0, 0x7f800001u}, + {0x42f6e979u, -1, 7, 0x3f76e979u}, + {0x00400000u, -1, -126, 0x3f000000u}, + {0x00000400u, -1, -138, 0x3f000000u}, +}; + +int main() { + for (const auto& c : kScales) { + double value = std::bit_cast(c.input); + Require(_ZNSt8__sce_v27_DscaleEPdl_nid_postfix(&value, c.exponent) == c.code && std::bit_cast(value) == c.result); + value = std::bit_cast(c.input); + Require(_ZNSt8__sce_v28_LDscaleEPel_nid_postfix(&value, c.exponent) == c.extendedCode && std::bit_cast(value) == c.extendedResult); + } + for (const auto& c : kFloatScales) { + float value = std::bit_cast(c.input); + Require(_ZNSt8__sce_v28_FDscaleEPfl_nid_postfix(&value, c.exponent) == c.code && std::bit_cast(value) == c.result); + } + for (const auto& c : kUnscales) { + double value = std::bit_cast(c.input); + short exponent = 77; + Require(_ZNSt8__sce_v29_DunscaleEPsPd_nid_postfix(&exponent, &value) == c.code && exponent == c.exponent && std::bit_cast(value) == c.result); + value = std::bit_cast(c.input); + exponent = 77; + Require(_ZNSt8__sce_v210_LDunscaleEPsPe_nid_postfix(&exponent, &value) == c.extendedCode && exponent == c.extendedExponent && std::bit_cast(value) == c.extendedResult); + } + for (const auto& c : kFloatUnscales) { + float value = std::bit_cast(c.input); + short exponent = 77; + Require(_ZNSt8__sce_v210_FDunscaleEPsPf_nid_postfix(&exponent, &value) == c.code && exponent == c.exponent && std::bit_cast(value) == c.result); + } +} diff --git a/core/libs/tests/GuestSceV2Exceptions.cpp b/core/libs/tests/GuestSceV2Exceptions.cpp index b0caf0f942..980c24d2c9 100644 --- a/core/libs/tests/GuestSceV2Exceptions.cpp +++ b/core/libs/tests/GuestSceV2Exceptions.cpp @@ -106,7 +106,7 @@ static void CheckThrowers() { {"_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"}, + "St9bad_alloc|bad allocation"}, {"_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, diff --git a/core/libs/tests/GuestSceV2Filesystem.cpp b/core/libs/tests/GuestSceV2Filesystem.cpp new file mode 100644 index 0000000000..d12d9de1e2 --- /dev/null +++ b/core/libs/tests/GuestSceV2Filesystem.cpp @@ -0,0 +1,132 @@ +#include "AppArchiveFixture.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int NoSuchFile = 2; +constexpr int Fault = 14; +constexpr int TypeNotFound = -1; +constexpr int TypeNone = 0; +constexpr int TypeRegular = 1; +constexpr int TypeDirectory = 2; +constexpr int TypeUnknown = 8; +constexpr int Loop = 62; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "sce v2 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(_ZNSt8__sce_v210filesystem5_StatEPKcPNS0_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(_ZNSt8__sce_v210filesystem5_StatEPKcPNS0_5permsEPi_nid_postfix(root.c_str(), nullptr, &error) == TypeDirectory && error == 0, "directory"); + perms = 1234; + Require(_ZNSt8__sce_v210filesystem5_StatEPKcPNS0_5permsEPi_nid_postfix(At("missing").c_str(), &perms, &error) == TypeNotFound && error == NoSuchFile && perms == 1234, "missing path"); +} + +void TestStatInvalidPath() { + int perms = 1234; + int error = -1; + Require(_ZNSt8__sce_v210filesystem5_StatEPKcPNS0_5permsEPi_nid_postfix(nullptr, &perms, &error) == TypeNone && error == Fault && perms == 1234, "a null path"); + error = -1; + Require(_ZNSt8__sce_v210filesystem5_StatEPKcPNS0_5permsEPi_nid_postfix("", &perms, &error) == TypeNotFound && error == NoSuchFile && perms == 1234, "an empty path"); +} + +void TestStatError() { + const auto loop = At("loop"); + std::error_code failure; + std::filesystem::create_symlink("loop", loop, failure); + if (failure) return; + std::error_code probe; + (void)std::filesystem::status(loop, probe); + if (probe != std::errc::too_many_symbolic_link_levels) return; + int perms = 1234; + int error = -1; + Require(_ZNSt8__sce_v210filesystem5_StatEPKcPNS0_5permsEPi_nid_postfix(loop.c_str(), &perms, &error) == TypeNone && error == Loop && perms == 1234, "a symlink loop is not reported as not found"); +} + +void TestArchivedStat() { + int perms = -1; + int error = -1; + Require(_ZNSt8__sce_v210filesystem5_StatEPKcPNS0_5permsEPi_nid_postfix("/app0/Readme.TXT", &perms, &error) == TypeRegular && error == 0 && perms == 0444, "archived file"); + FileStat status{}; + Require(stat_nid_postfix("/app0/Readme.TXT", &status) == 0 && perms == (status.st_mode & 07777), "archived file permissions"); + perms = -1; + Require(_ZNSt8__sce_v210filesystem5_StatEPKcPNS0_5permsEPi_nid_postfix("/app0/Data", &perms, &error) == TypeDirectory && error == 0 && perms == 0555, "archived directory"); + Require(stat_nid_postfix("/app0/Data", &status) == 0 && perms == (status.st_mode & 07777), "archived directory permissions"); +} + +void TestDirectories() { + std::filesystem::create_directory(At("made")); + char name[260]; + int error = -1; + int type = -1; + Require(_ZNSt8__sce_v210filesystem9_Open_dirERA260_cPKcRiRNS0_9file_typeE_nid_postfix(name, At("made").c_str(), &error, &type) == nullptr && error == 0 && name[0] == '\0', "an empty directory gives no handle"); + Require(_ZNSt8__sce_v210filesystem9_Open_dirERA260_cPKcRiRNS0_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 = _ZNSt8__sce_v210filesystem9_Open_dirERA260_cPKcRiRNS0_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(_ZNSt8__sce_v210filesystem9_Read_dirERA260_cPvRNS0_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(_ZNSt8__sce_v210filesystem9_Read_dirERA260_cPvRNS0_9file_typeE_nid_postfix(name, handle, &type)[0] == '\0' && type == TypeUnknown, "end of the directory"); + Require(_ZNSt8__sce_v210filesystem10_Close_dirEPv_nid_postfix(handle) == 0, "close a directory"); +} + +void TestCurrent() { + char current[260]; + std::memset(current, 0x5a, sizeof(current)); + Require(_ZNSt8__sce_v210filesystem12_Current_getERA260_c_nid_postfix(current), "current directory"); + Require(std::strcmp(current, "/") == 0, "current directory is the sandbox root"); + Require(static_cast(current[2]) == 0x5a, "current directory writes two bytes"); +} + +} + +int main() { + const auto directory = AppArchiveFixture::EnterFreshRoot("anyps5-sce-v2-filesystem"); + root = "."; + AppArchiveFixture::WriteArchive(directory / "app0.zar", {{"Readme.TXT", {'r', 'e', 'a', 'd'}}, {"Data/Sub.txt", {'s'}}}); + TestStat(); + TestStatInvalidPath(); + TestStatError(); + TestArchivedStat(); + TestDirectories(); + TestCurrent(); + std::error_code ignored; + std::filesystem::current_path(std::filesystem::temp_directory_path(), ignored); + std::filesystem::remove_all(directory, ignored); + return 0; +} diff --git a/core/libs/tests/GuestSceV2FilesystemCalls.cpp b/core/libs/tests/GuestSceV2FilesystemCalls.cpp new file mode 100644 index 0000000000..261447acf1 --- /dev/null +++ b/core/libs/tests/GuestSceV2FilesystemCalls.cpp @@ -0,0 +1,193 @@ +#include "AppArchiveFixture.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr int TypeNotFound = -1; +constexpr int TypeRegular = 1; +constexpr int TypeDirectory = 2; +constexpr int NoSuchFile = 2; +constexpr int NotDirectory = 20; +constexpr int IsDirectory = 21; +constexpr int Invalid = 22; +constexpr int NameTooLong = 63; +constexpr int NoSystemCall = 78; +constexpr int NotSupported = 45; +constexpr std::uint32_t KernelNoSuchFile = 0x80020002u; +constexpr std::uint32_t KernelNotDirectory = 0x80020014u; + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "sce v2 filesystem calls: %s\n", message); + std::abort(); +} + +void Write(const char* path, std::size_t bytes, unsigned seed) { + std::ofstream file(path, std::ios::binary | std::ios::trunc); + for (std::size_t index = 0; index < bytes; ++index) file.put(static_cast((index * 131 + seed) & 0xff)); +} + +std::string Read(const char* path) { + std::ifstream file(path, std::ios::binary); + return {std::istreambuf_iterator(file), {}}; +} + +FileStat Status(const char* path) { + FileStat status{}; + Require(stat_nid_postfix(path, &status) == 0, "stat of a test file"); + return status; +} + +void Prepare() { + std::error_code ignored; + for (const char* name : {"f", "g", "big", "d", "e", "ro", "new", "made", "copy", "renamed"}) { + std::filesystem::permissions(name, std::filesystem::perms::owner_all, std::filesystem::perm_options::add, ignored); + std::filesystem::remove_all(name, ignored); + } + Write("f", 7, 1); + Write("g", 3, 2); + Write("big", 200000, 4); + std::filesystem::create_directories("d/sub"); + Write("d/sub/h", 5, 5); + std::filesystem::create_directory("e"); + Write("ro", 4, 6); + std::filesystem::permissions("ro", std::filesystem::perms::owner_read | std::filesystem::perms::group_read | std::filesystem::perms::others_read); +} + +void TestStatus() { + Prepare(); + int perms = -1; + int error = -1; + Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix("f", &perms, &error) == TypeRegular && error == 0, "a regular file"); + Require(perms == (Status("f").st_mode & 0xfff), "the permission bits of a regular file"); + Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix("d", nullptr, nullptr) == TypeDirectory, "a directory without outputs"); + Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsE_nid_postfix("d/", &perms) == TypeDirectory && perms == (Status("d").st_mode & 0xfff), "a directory with a trailing slash"); + const struct { + const char* path; + int error; + } missing[] = {{"missing", NoSuchFile}, {"f/x", NotDirectory}, {"", NoSuchFile}}; + for (const auto& entry : missing) { + perms = 0x1234; + Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix(entry.path, &perms, &error) == TypeNotFound && error == entry.error && perms == 0x1234, "a path that is not found"); + Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsE_nid_postfix(entry.path, &perms) == TypeNotFound && perms == 0x1234, "a path that is not found without an error output"); + } + const std::string longName(300, 'n'); + Require(_ZNSt8__sce_v210filesystem6_LstatEPKcPNS0_5permsEPi_nid_postfix(longName.c_str(), &perms, &error) == TypeNotFound && error == NameTooLong, "a name that is too long"); +} + +void TestSizes() { + Prepare(); + int error = 0x5678; + Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix("big", &error) == 200000 && error == 0x5678, "the size of a file leaves the error output alone"); + Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKc_nid_postfix("f") == 7, "the size of a file without an error output"); + Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix("missing", &error) == UINT64_MAX && static_cast(error) == KernelNoSuchFile, "a missing file reports the kernel error"); + Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix("f/x", &error) == UINT64_MAX && static_cast(error) == KernelNotDirectory, "a file used as a directory reports the kernel error"); + Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKcPi_nid_postfix("missing", nullptr) == UINT64_MAX, "a missing file without an error output"); + Require(_ZNSt8__sce_v210filesystem10_File_sizeEPKc_nid_postfix("missing") == UINT64_MAX, "a missing file through the short form"); +} + +void TestTimes() { + Prepare(); + const struct { + std::int64_t microseconds, seconds, nanoseconds; + } times[] = {{-1500001, -2, 499999000}, {1700000000123456, 1700000000, 123456000}, {999999, 0, 999999000}, {-1000000, -1, 0}, {-1, -1, 999999000}}; + for (const auto& time : times) { + Require(_ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix("f", time.microseconds), "set the write time of a file"); + std::int64_t seconds = 0; + std::int64_t nanoseconds = 0; + Require(_ZNSt8__sce_v210filesystem16_Last_write_timeEPKcPlS3__nid_postfix("f", &seconds, &nanoseconds) == 0, "read the write time of a file"); + Require(seconds == time.seconds && nanoseconds == time.nanoseconds, "the write time is floored to whole seconds and microseconds"); + Require(_ZNSt8__sce_v210filesystem16_Last_write_timeEPKc_nid_postfix("f") == time.seconds, "the write time in seconds"); + } + Require(_ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix("d", 1), "set the write time of a directory"); + Require(!_ZNSt8__sce_v210filesystem20_Set_last_write_timeEPKcl_nid_postfix("missing", 0), "a missing file has no write time to set"); + std::int64_t seconds = 0x1111; + std::int64_t nanoseconds = 0x2222; + Require(_ZNSt8__sce_v210filesystem16_Last_write_timeEPKcPlS3__nid_postfix("missing", &seconds, &nanoseconds) == NoSuchFile && seconds == 0x1111 && nanoseconds == 0x2222, "a missing file leaves the time outputs alone"); + Require(_ZNSt8__sce_v210filesystem16_Last_write_timeEPKc_nid_postfix("missing") == -1, "a missing file has no write time"); +} + +void TestDirectories() { + Prepare(); + Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("made", nullptr) == 1 && std::filesystem::is_directory("made"), "make a directory"); + Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("made", nullptr) == 0, "an existing directory is not made again"); + Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("f", nullptr) == 0, "an existing file is reported as existing"); + Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("missing/x", nullptr) == -NoSuchFile, "a missing parent"); + Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("f/x", nullptr) == -NotDirectory, "a file used as a parent"); + Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("new", "missing") == -NoSuchFile && !std::filesystem::exists("new"), "a missing template stops before making the directory"); + Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix("new", "d") == 1 && std::filesystem::is_directory("new"), "make a directory from a template"); + const std::string longName(300, 'n'); + Require(_ZNSt8__sce_v210filesystem9_Make_dirEPKcS2__nid_postfix(longName.c_str(), nullptr) == -NameTooLong, "a name that is too long"); + Require(_ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix("made") && !std::filesystem::exists("made"), "remove an empty directory"); + Require(!_ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix("d") && std::filesystem::exists("d/sub/h"), "a directory that is not empty stays"); + Require(!_ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix("f") && std::filesystem::exists("f"), "a file is not removed as a directory"); + Require(!_ZNSt8__sce_v210filesystem11_Remove_dirEPKc_nid_postfix("missing"), "a missing directory"); +} + +void TestEntries() { + Prepare(); + Require(_ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix("f/x") == NotDirectory, "unlink below a file"); + Require(_ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix("f") == 0 && !std::filesystem::exists("f"), "unlink a file"); + Require(_ZNSt8__sce_v210filesystem7_UnlinkEPKc_nid_postfix("missing") == NoSuchFile, "unlink a missing file"); + Require(_ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix("g", "renamed") == 0 && Read("renamed").size() == 3 && !std::filesystem::exists("g"), "rename a file"); + Require(_ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix("missing", "new") == NoSuchFile, "rename a missing file"); + Require(_ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix("renamed", "d") == IsDirectory && std::filesystem::exists("renamed"), "rename a file over a directory"); + Require(_ZNSt8__sce_v210filesystem7_RenameEPKcS2__nid_postfix("renamed", "missing/x") == NoSuchFile, "rename into a missing directory"); + Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("renamed", 100000) == 0 && Status("renamed").st_size == 100000, "grow a file"); + Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("renamed", 2) == 0 && Status("renamed").st_size == 2, "shrink a file"); + Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("renamed", 0x8000000000000000ull) == Invalid, "a size above the signed range"); + Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("d", 0) == IsDirectory, "resize a directory"); + Require(_ZNSt8__sce_v210filesystem7_ResizeEPKcm_nid_postfix("missing", 0) == NoSuchFile, "resize a missing file"); + Require(_ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix("renamed", 0444) == 0 && (Status("renamed").st_mode & 0222) == 0, "make a file read only"); + Require(_ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix("renamed", 0x1ffff) == 0 && (Status("renamed").st_mode & 0200) != 0, "only the low sixteen permission bits are passed"); + Require(_ZNSt8__sce_v210filesystem6_ChmodEPKcNS0_5permsE_nid_postfix("missing", 0444) == NoSuchFile, "change a missing file"); +} + +void TestCopy() { + Prepare(); + const auto contents = Read("big"); + Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("big", "copy") == 0 && Read("copy") == contents, "copy a file larger than one block"); + Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("g", "copy") == 0 && Read("copy") == Read("g"), "copy over a longer file truncates it"); + Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("ro", "new") == 0 && Read("new") == Read("ro"), "copy a read-only file"); + Require((Status("new").st_mode & 07777) == (Status("ro").st_mode & 07777), "the copy takes the permissions of its source"); + Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("missing", "made") == NoSuchFile && !std::filesystem::exists("made"), "a missing source creates nothing"); + Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("f", "missing/x") == NoSuchFile, "a target in a missing directory"); + Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("f", "d") == IsDirectory, "a directory target"); + Require(_ZNSt8__sce_v210filesystem10_Copy_fileEPKcS2__nid_postfix("d", "made") == IsDirectory, "a directory source"); +} + +void TestUnsupported() { + std::uint64_t space[4] = {1, 2, 3, 4}; + Require(_ZNSt8__sce_v210filesystem8_StatvfsEPKcRNS0_10space_infoE_nid_postfix("f", space) == NoSystemCall, "statvfs is not supported"); + Require(space[0] == UINT64_MAX && space[1] == UINT64_MAX && space[2] == UINT64_MAX && space[3] == 4, "statvfs reports unknown space and writes three fields"); + Require(_ZNSt8__sce_v210filesystem13_Current_set2EPKc_nid_postfix("/") == 0, "the root stays the current directory"); + for (const char* path : {"", ".", "d", "//", "/d", "f/"}) Require(_ZNSt8__sce_v210filesystem13_Current_set2EPKc_nid_postfix(path) == NotSupported, "any other current directory is not supported"); +} + +} + +int main() { + const auto directory = AppArchiveFixture::EnterFreshRoot("anyps5-sce-v2-filesystem-calls"); + TestStatus(); + TestSizes(); + TestTimes(); + TestDirectories(); + TestEntries(); + TestCopy(); + TestUnsupported(); + Prepare(); + std::error_code ignored; + std::filesystem::current_path(std::filesystem::temp_directory_path(), ignored); + std::filesystem::remove_all(directory, ignored); + return 0; +} diff --git a/core/libs/tests/GuestSceV2FloatHelpers.cpp b/core/libs/tests/GuestSceV2FloatHelpers.cpp new file mode 100644 index 0000000000..c1a121b8f1 --- /dev/null +++ b/core/libs/tests/GuestSceV2FloatHelpers.cpp @@ -0,0 +1,327 @@ +#include "prx/libc/libc.hpp" +#include +#include +#include + +static void Require(bool value) { if (!value) std::abort(); } + +struct TestCase { std::uint64_t input; short code; short extendedCode; }; +struct FloatTestCase { std::uint32_t input; short code; }; +struct IntCase { std::uint64_t input; short scale; short code; std::uint64_t result; short extendedCode; std::uint64_t extendedResult; }; +struct FloatIntCase { std::uint32_t input; short scale; short code; std::uint32_t result; }; +struct NormCase { std::uint64_t input; short code; std::uint64_t result; }; +struct FloatNormCase { std::uint32_t input; short code; std::uint32_t result; }; + +static const TestCase kTests[] = { + {0x0000000000000000ull, 0, 0}, + {0x0000000000000001ull, -2, -2}, + {0x3fefffffffffffffull, -1, -1}, + {0x3fe0000000000000ull, -1, -1}, + {0x3fe0000000000001ull, -1, -1}, + {0xc000000000000000ull, -1, -1}, + {0x4008000000000000ull, -1, -1}, + {0x4007ffffffffffffull, -1, -1}, + {0xc024000000000000ull, -1, -1}, + {0x4024000000000001ull, -1, -1}, + {0x3fb9999999999999ull, -1, -1}, + {0x81a56e1fc2f8f359ull, -1, -1}, + {0x01a56e1fc2f8f35aull, -1, -1}, + {0x7e37e43c8800759bull, -1, -1}, + {0x4086280000000000ull, -1, -1}, + {0x4086280000000001ull, -1, -1}, + {0xc0862e3d70a3d70aull, -1, -1}, + {0x4086300000000000ull, -1, -1}, + {0x40862fffffffffffull, -1, -1}, + {0xc08633c28f5c28f6ull, -1, -1}, + {0x408633d70a3d70a4ull, -1, -1}, + {0x408633d70a3d70a3ull, -1, -1}, + {0xc086380000000000ull, -1, -1}, + {0x4086380000000001ull, -1, -1}, + {0x40873fffffffffffull, -1, -1}, + {0x4087480000000000ull, -1, -1}, + {0x4087480000000001ull, -1, -1}, + {0xc087500000000000ull, -1, -1}, + {0x3e45798ee2308c3aull, -1, -1}, + {0x3e45798ee2308c3bull, -1, -1}, + {0xbc9cd2b297d889bcull, -1, -1}, + {0x4036000000000000ull, -1, -1}, + {0x4035ffffffffffffull, -1, -1}, + {0xc044000000000000ull, -1, -1}, + {0x4044000000000001ull, -1, -1}, + {0x4055ffffffffffffull, -1, -1}, + {0x4056400000000000ull, -1, -1}, + {0x4056400000000001ull, -1, -1}, + {0x408ff7ffffffffffull, -1, -1}, + {0x4090000000000000ull, -1, -1}, + {0x4090000000000001ull, -1, -1}, + {0xc090cc0000000000ull, -1, -1}, + {0x4202a05f20000000ull, -1, -1}, + {0x4202a05f1fffffffull, -1, -1}, + {0xd4b249ad2594c37dull, -1, -1}, + {0x54b249ad2594c37eull, -1, -1}, + {0x0010000000000000ull, -1, -1}, + {0x0010000000000001ull, -1, -1}, + {0xffefffffffffffffull, -1, -1}, + {0x3cb0000000000000ull, -1, -1}, + {0x3cafffffffffffffull, -1, -1}, + {0x7ff8000000000000ull, 2, 2}, + {0x7ff0000000000001ull, 2, 2}, + {0x4006000000000000ull, -1, -1}, + {0x405edd2f1a9fbe77ull, -1, -1}, + {0x800abcdef0123456ull, -2, -2}, +}; + +static const FloatTestCase kFloatTests[] = { + {0x00000000u, 0}, + {0x80000000u, 0}, + {0xbf800000u, -1}, + {0xbf000000u, -1}, + {0xc0000000u, -1}, + {0xc0400000u, -1}, + {0xc1200000u, -1}, + {0x3dcccccdu, -1}, + {0x7f800000u, 1}, + {0x44314000u, -1}, + {0x443171ecu, -1}, + {0x44318000u, -1}, + {0x44319e14u, -1}, + {0x44319eb8u, -1}, + {0xc4319eb8u, -1}, + {0xc431c000u, -1}, + {0xc43a0000u, -1}, + {0xc43a4000u, -1}, + {0xc43a8000u, -1}, + {0xb22bcc77u, -1}, + {0xa4e69595u, -1}, + {0x41b00000u, -1}, + {0x42200000u, -1}, + {0x42b00000u, -1}, + {0x42b20000u, -1}, + {0x447fc000u, -1}, + {0x44800000u, -1}, + {0x44866000u, -1}, + {0xc4866000u, -1}, + {0xd01502f9u, -1}, + {0xa5800000u, -1}, + {0xffc00000u, 2}, + {0x7fffffffu, 2}, + {0xc2b170a4u, -1}, + {0xc2b175c3u, -1}, + {0x42b2d1ecu, -1}, + {0x42b2d70au, -1}, + {0x42ce0000u, -1}, + {0x42d00000u, -1}, + {0x006ce3eeu, -2}, + {0x00000001u, -2}, + {0x7f7fc99eu, -1}, + {0xff7fc99eu, -1}, + {0x7fa00000u, 2}, + {0x40300000u, -1}, + {0x42f6e979u, -1}, + {0x00400000u, -2}, + {0x4b7fffffu, -1}, +}; + +static const IntCase kInts[] = { + {0x0000000000000000ull, 0, 0, 0x0000000000000000ull, 0, 0x0000000000000000ull}, + {0x8000000000000000ull, 1100, 0, 0x8000000000000000ull, 0, 0x8000000000000000ull}, + {0x3ff0000000000000ull, 3, 0, 0x3ff0000000000000ull, 0, 0x3ff0000000000000ull}, + {0x3fefffffffffffffull, 0, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x3ff0000000000001ull, -32000, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0xbfe0000000000000ull, 52, 0, 0xbfe0000000000000ull, 0, 0xbfe0000000000000ull}, + {0x3fe0000000000001ull, 1, -1, 0x3fe0000000000000ull, -1, 0x3fe0000000000000ull}, + {0x4000000000000000ull, 32000, 0, 0x4000000000000000ull, 0, 0x4000000000000000ull}, + {0x3fffffffffffffffull, 53, 0, 0x3fffffffffffffffull, 0, 0x3fffffffffffffffull}, + {0x4008000000000000ull, -1, -1, 0x4000000000000000ull, -1, 0x4000000000000000ull}, + {0x4007ffffffffffffull, 0, -1, 0x4000000000000000ull, -1, 0x4000000000000000ull}, + {0x4008000000000001ull, 1100, 0, 0x4008000000000001ull, 0, 0x4008000000000001ull}, + {0xc024000000000000ull, 3, 0, 0xc024000000000000ull, 0, 0xc024000000000000ull}, + {0x4024000000000001ull, 1, -1, 0x4024000000000000ull, -1, 0x4024000000000000ull}, + {0x3fb999999999999aull, -32000, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x3fb9999999999999ull, 52, -1, 0x3fb9999999999990ull, -1, 0x3fb9999999999990ull}, + {0x01a56e1fc2f8f359ull, -1, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x81a56e1fc2f8f359ull, 32000, 0, 0x81a56e1fc2f8f359ull, 0, 0x81a56e1fc2f8f359ull}, + {0x01a56e1fc2f8f35aull, 53, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0xfe37e43c8800759cull, 3, 0, 0xfe37e43c8800759cull, 0, 0xfe37e43c8800759cull}, + {0x7e37e43c8800759dull, 0, 0, 0x7e37e43c8800759dull, 0, 0x7e37e43c8800759dull}, + {0x4086280000000000ull, 1100, 0, 0x4086280000000000ull, 0, 0x4086280000000000ull}, + {0x408627ffffffffffull, 52, 0, 0x408627ffffffffffull, 0, 0x408627ffffffffffull}, + {0x40862e3d70a3d70aull, 1, -1, 0x40862c0000000000ull, -1, 0x40862c0000000000ull}, + {0xc0862e3d70a3d70aull, -32000, -1, 0x8000000000000000ull, -1, 0x8000000000000000ull}, + {0x40862e3d70a3d70bull, 53, 0, 0x40862e3d70a3d70bull, 0, 0x40862e3d70a3d70bull}, + {0xc086300000000000ull, -1, 0, 0xc086300000000000ull, 0, 0xc086300000000000ull}, + {0x40862fffffffffffull, 32000, 0, 0x40862fffffffffffull, 0, 0x40862fffffffffffull}, + {0x408633c28f5c28f6ull, 1100, 0, 0x408633c28f5c28f6ull, 0, 0x408633c28f5c28f6ull}, + {0x408633c28f5c28f5ull, 3, -1, 0x4086330000000000ull, -1, 0x4086330000000000ull}, + {0x408633d70a3d70a4ull, 0, -1, 0x4086300000000000ull, -1, 0x4086300000000000ull}, + {0xc08633d70a3d70a4ull, 1100, 0, 0xc08633d70a3d70a4ull, 0, 0xc08633d70a3d70a4ull}, + {0x408633d70a3d70a5ull, 52, 0, 0x408633d70a3d70a5ull, 0, 0x408633d70a3d70a5ull}, + {0xc086380000000000ull, 1, 0, 0xc086380000000000ull, 0, 0xc086380000000000ull}, + {0x408637ffffffffffull, -32000, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x4087400000000000ull, 53, 0, 0x4087400000000000ull, 0, 0x4087400000000000ull}, + {0x40873fffffffffffull, -1, -1, 0x4087300000000000ull, -1, 0x4087300000000000ull}, + {0x4087400000000001ull, 32000, 0, 0x4087400000000001ull, 0, 0x4087400000000001ull}, + {0xc087480000000000ull, 1100, 0, 0xc087480000000000ull, 0, 0xc087480000000000ull}, + {0x4087480000000001ull, 3, -1, 0x4087480000000000ull, -1, 0x4087480000000000ull}, + {0xc087500000000000ull, 0, 0, 0xc087500000000000ull, 0, 0xc087500000000000ull}, + {0x40874fffffffffffull, -32000, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x3e45798ee2308c3aull, 52, -1, 0x3e45798ee0000000ull, -1, 0x3e45798ee0000000ull}, + {0x3e45798ee2308c39ull, 1, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x3e45798ee2308c3bull, 32000, 0, 0x3e45798ee2308c3bull, 0, 0x3e45798ee2308c3bull}, + {0xbc9cd2b297d889bcull, 53, -1, 0x8000000000000000ull, -1, 0x8000000000000000ull}, + {0x3c9cd2b297d889bdull, -1, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0xc036000000000000ull, 0, 0, 0xc036000000000000ull, 0, 0xc036000000000000ull}, + {0x4035ffffffffffffull, 1100, 0, 0x4035ffffffffffffull, 0, 0x4035ffffffffffffull}, + {0x4044000000000000ull, 3, 0, 0x4044000000000000ull, 0, 0x4044000000000000ull}, + {0x4043ffffffffffffull, 1, -1, 0x4043c00000000000ull, -1, 0x4043c00000000000ull}, + {0x4044000000000001ull, -32000, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0xc056000000000000ull, 52, 0, 0xc056000000000000ull, 0, 0xc056000000000000ull}, + {0x4056000000000001ull, -1, -1, 0x4056000000000000ull, -1, 0x4056000000000000ull}, + {0x4056400000000000ull, 32000, 0, 0x4056400000000000ull, 0, 0x4056400000000000ull}, + {0x40563fffffffffffull, 53, 0, 0x40563fffffffffffull, 0, 0x40563fffffffffffull}, + {0x408ff80000000000ull, 3, 0, 0x408ff80000000000ull, 0, 0x408ff80000000000ull}, + {0x408ff7ffffffffffull, 0, -1, 0x408ff00000000000ull, -1, 0x408ff00000000000ull}, + {0x408ff80000000001ull, 1100, 0, 0x408ff80000000001ull, 0, 0x408ff80000000001ull}, + {0xc090000000000000ull, 3, 0, 0xc090000000000000ull, 0, 0xc090000000000000ull}, + {0x4090000000000001ull, 1, -1, 0x4090000000000000ull, -1, 0x4090000000000000ull}, + {0x4090cc0000000000ull, -32000, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x4090cbffffffffffull, 52, 0, 0x4090cbffffffffffull, 0, 0x4090cbffffffffffull}, + {0x4202a05f20000000ull, -1, 0, 0x4202a05f20000000ull, 0, 0x4202a05f20000000ull}, + {0xc202a05f20000000ull, 32000, 0, 0xc202a05f20000000ull, 0, 0xc202a05f20000000ull}, + {0x4202a05f20000001ull, 53, 0, 0x4202a05f20000001ull, 0, 0x4202a05f20000001ull}, + {0xd4b249ad2594c37dull, 3, 0, 0xd4b249ad2594c37dull, 0, 0xd4b249ad2594c37dull}, + {0x54b249ad2594c37eull, 0, 0, 0x54b249ad2594c37eull, 0, 0x54b249ad2594c37eull}, + {0x8000000000000001ull, 1100, 0, 0x8000000000000001ull, 0, 0x8000000000000001ull}, + {0x0010000000000000ull, 52, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x000fffffffffffffull, 1, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x0010000000000001ull, -32000, 0, 0x0010000000000001ull, 0, 0x0010000000000001ull}, + {0xffefffffffffffffull, 53, 0, 0xffefffffffffffffull, 0, 0xffefffffffffffffull}, + {0x7ff0000000000000ull, -1, 1, 0x7ff0000000000000ull, 1, 0x7ff0000000000000ull}, + {0x3cb0000000000000ull, 32000, 0, 0x3cb0000000000000ull, 0, 0x3cb0000000000000ull}, + {0x3cafffffffffffffull, 1100, 0, 0x3cafffffffffffffull, 0, 0x3cafffffffffffffull}, + {0xfff0000000000000ull, 3, 1, 0xfff0000000000000ull, 1, 0xfff0000000000000ull}, + {0xfff8000000000000ull, 0, 2, 0xfff8000000000000ull, 2, 0xfff8000000000000ull}, + {0x7ff0000000000001ull, -32000, 2, 0x7ff0000000000001ull, 2, 0x7ff0000000000001ull}, + {0x7fffffffffffffffull, 52, 2, 0x7fffffffffffffffull, 2, 0x7fffffffffffffffull}, + {0xc006000000000000ull, 1, -1, 0xc004000000000000ull, -1, 0xc004000000000000ull}, + {0x405edd2f1a9fbe77ull, 32000, 0, 0x405edd2f1a9fbe77ull, 0, 0x405edd2f1a9fbe77ull}, + {0x0008000000000000ull, 53, -1, 0x0000000000000000ull, -1, 0x0000000000000000ull}, + {0x433fffffffffffffull, -1, -1, 0x433ffffffffffffeull, -1, 0x433ffffffffffffeull}, +}; + +static const FloatIntCase kFloatInts[] = { + {0x00000000u, 0, 0, 0x00000000u}, + {0x80000000u, 23, 0, 0x80000000u}, + {0x3f800000u, 32700, 0, 0x3f800000u}, + {0x3f000000u, 23, 0, 0x3f000000u}, + {0xbf000000u, 32700, 0, 0xbf000000u}, + {0xc0000000u, 3, 0, 0xc0000000u}, + {0x40400000u, 32700, 0, 0x40400000u}, + {0x41200000u, 3, 0, 0x41200000u}, + {0xc1200000u, -32700, -1, 0x80000000u}, + {0xbdcccccdu, 3, -1, 0x80000000u}, + {0x7f800000u, -32700, 1, 0x7f800000u}, + {0x44314000u, -1, -1, 0x44310000u}, + {0xc4314000u, -32700, -1, 0x80000000u}, + {0xc43171ecu, -1, -1, 0xc4310000u}, + {0x44318000u, 200, 0, 0x44318000u}, + {0x44319e14u, -1, -1, 0x44318000u}, + {0xc4319e14u, 200, 0, 0xc4319e14u}, + {0xc4319eb8u, 1, -1, 0xc4318000u}, + {0x4431c000u, 200, 0, 0x4431c000u}, + {0x443a0000u, 1, 0, 0x443a0000u}, + {0xc43a0000u, 200, 0, 0xc43a0000u}, + {0xc43a4000u, 1, 0, 0xc43a4000u}, + {0x443a8000u, 24, 0, 0x443a8000u}, + {0x322bcc77u, 1, -1, 0x00000000u}, + {0xb22bcc77u, 24, -1, 0x80000000u}, + {0xa4e69595u, 0, -1, 0x80000000u}, + {0x41b00000u, 24, 0, 0x41b00000u}, + {0x42200000u, 0, 0, 0x42200000u}, + {0xc2200000u, 23, 0, 0xc2200000u}, + {0xc2b00000u, 0, 0, 0xc2b00000u}, + {0x42b20000u, 23, 0, 0x42b20000u}, + {0xc2b20000u, 32700, 0, 0xc2b20000u}, + {0xc47fc000u, 23, 0, 0xc47fc000u}, + {0x44800000u, 32700, 0, 0x44800000u}, + {0x44866000u, 3, 0, 0x44866000u}, + {0xc4866000u, 32700, 0, 0xc4866000u}, + {0xd01502f9u, 3, 0, 0xd01502f9u}, + {0x25800000u, -32700, 0, 0x25800000u}, + {0x7fc00000u, 3, 2, 0x7fc00000u}, + {0xffc00000u, -32700, 2, 0xffc00000u}, + {0x7fffffffu, 3, 2, 0x7fffffffu}, + {0x42b170a4u, -32700, -1, 0x00000000u}, + {0x42b175c3u, -1, -1, 0x42b00000u}, + {0xc2b175c3u, -32700, -1, 0x80000000u}, + {0xc2b2d1ecu, -1, -1, 0xc2b00000u}, + {0x42b2d70au, 200, 0, 0x42b2d70au}, + {0x42ce0000u, -1, -1, 0x42cc0000u}, + {0xc2ce0000u, 200, 0, 0xc2ce0000u}, + {0xc2d00000u, 1, 0, 0xc2d00000u}, + {0x006ce3eeu, 200, 0, 0x006ce3eeu}, + {0x00000001u, 1, -1, 0x00000000u}, + {0x80000001u, 24, -1, 0x80000000u}, + {0xff7fc99eu, 1, 0, 0xff7fc99eu}, + {0x7f800001u, 24, 2, 0x7f800001u}, + {0x007fffffu, 0, -1, 0x00000000u}, + {0x40300000u, 24, 0, 0x40300000u}, + {0x42f6e979u, 0, -1, 0x42f60000u}, + {0x3e800000u, 23, 0, 0x3e800000u}, + {0x803456ffu, 0, -1, 0x80000000u}, + {0x4b7fffffu, 23, 0, 0x4b7fffffu}, +}; + +static const NormCase kNorms[] = { + {0x0000000000000001ull, -51, 0x0000000000000000ull}, + {0x0000000000000003ull, -50, 0x0008000000000000ull}, + {0x0008000000000000ull, 0, 0x0000000000000000ull}, + {0x000fffffffffffffull, 0, 0x000ffffffffffffeull}, + {0x8000000000000001ull, -51, 0x8000000000000000ull}, + {0x800abcdef0123456ull, 0, 0x800579bde02468acull}, + {0x0000123456789abcull, -7, 0x00023456789abc00ull}, + {0x0010000000000000ull, 1, 0x0000000000000000ull}, + {0x3ff0000000000000ull, 1, 0x0000000000000000ull}, +}; + +static const FloatNormCase kFloatNorms[] = { + {0x00000001u, -22, 0x00000000u}, + {0x00000003u, -21, 0x00400000u}, + {0x00400000u, 0, 0x00000000u}, + {0x007fffffu, 0, 0x007ffffeu}, + {0x80000001u, -22, 0x80000000u}, + {0x803456ffu, -1, 0x80515bfcu}, + {0x00012345u, -6, 0x0011a280u}, + {0x00800000u, 1, 0x00000000u}, + {0x3f800000u, 1, 0x00000000u}, +}; + +int main() { + for (const auto& c : kTests) { + double value = std::bit_cast(c.input); + Require(_ZNSt8__sce_v26_DtestEPd_nid_postfix(&value) == c.code && std::bit_cast(value) == c.input); + Require(_ZNSt8__sce_v27_LDtestEPe_nid_postfix(&value) == c.extendedCode && std::bit_cast(value) == c.input); + } + for (const auto& c : kFloatTests) { + float value = std::bit_cast(c.input); + Require(_ZNSt8__sce_v27_FDtestEPf_nid_postfix(&value) == c.code && std::bit_cast(value) == c.input); + } + for (const auto& c : kInts) { + double value = std::bit_cast(c.input); + Require(_ZNSt8__sce_v25_DintEPds_nid_postfix(&value, c.scale) == c.code && std::bit_cast(value) == c.result); + value = std::bit_cast(c.input); + Require(_ZNSt8__sce_v26_LDintEPes_nid_postfix(&value, c.scale) == c.extendedCode && std::bit_cast(value) == c.extendedResult); + } + for (const auto& c : kFloatInts) { + float value = std::bit_cast(c.input); + Require(_ZNSt8__sce_v26_FDintEPfs_nid_postfix(&value, c.scale) == c.code && std::bit_cast(value) == c.result); + } + for (const auto& c : kNorms) { + std::uint64_t words = c.input; + Require(_ZNSt8__sce_v26_DnormEPNS_5_DvalE_nid_postfix(reinterpret_cast(&words)) == c.code && words == c.result); + } + for (const auto& c : kFloatNorms) { + std::uint32_t words = c.input; + Require(_ZNSt8__sce_v27_FDnormEPNS_5_FvalE_nid_postfix(reinterpret_cast(&words)) == c.code && words == c.result); + } +} diff --git a/core/libs/tests/GuestSceV2Locale.cpp b/core/libs/tests/GuestSceV2Locale.cpp new file mode 100644 index 0000000000..5fbed784b2 --- /dev/null +++ b/core/libs/tests/GuestSceV2Locale.cpp @@ -0,0 +1,150 @@ +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/Api.hpp" +#include "prx/libc/include/GuestLocale.hpp" +#include +#include +#include +#include + +extern "C" { +extern std::uint64_t _ZNSt8__sce_v25ctypeIcE2idE_nid_postfix; +extern std::uint64_t _ZNSt8__sce_v25ctypeIwE2idE_nid_postfix; +extern std::uint64_t _ZNSt8__sce_v28numpunctIcE2idE_nid_postfix; +extern std::uint64_t _ZNSt8__sce_v27num_putIcNS_19ostreambuf_iteratorIcNS_11char_traitsIcEEEEE2idE_nid_postfix; +extern std::uint64_t _ZNSt8__sce_v27codecvtIwc9_MbstatetE2idE_nid_postfix; +extern std::int32_t _ZNSt8__sce_v26locale2id7_Id_cntE_nid_postfix; +extern GuestLocale::Implementation* _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; +extern unsigned char _ZNSt8__sce_v24coutE_nid_postfix[]; +extern unsigned char _ZNSt8__sce_v24cerrE_nid_postfix[]; +extern unsigned char _ZNSt8__sce_v25wcoutE_nid_postfix[]; +extern unsigned char _ZNSt8__sce_v25wcerrE_nid_postfix[]; +extern unsigned char _ZNSt8__sce_v23cinE_nid_postfix[]; +extern unsigned char _ZNSt8__sce_v24wcinE_nid_postfix[]; +} + +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 ? 0 : -1; +} + +static void Require(bool value) { if (!value) std::abort(); } + +static int gEraseCalls = 0; +static bool gEraseSawStreamNodes = false; + +void APS5_VABI RecordErase(int, void* self, std::int32_t) { + ++gEraseCalls; + gEraseSawStreamNodes = reinterpret_cast(self)->streamNodes != nullptr; +} + +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()); + + Require(_ZNSt8__sce_v25ctypeIcE2idE_nid_postfix == 0); + Require(_ZNSt8__sce_v25ctypeIwE2idE_nid_postfix == 0); + Require(_ZNSt8__sce_v28numpunctIcE2idE_nid_postfix == 0); + Require(_ZNSt8__sce_v27num_putIcNS_19ostreambuf_iteratorIcNS_11char_traitsIcEEEEE2idE_nid_postfix == 0); + Require(_ZNSt8__sce_v27codecvtIwc9_MbstatetE2idE_nid_postfix == 0); + Require(_ZNSt8__sce_v26locale2id7_Id_cntE_nid_postfix == 0); + + unsigned char* streams[] { + _ZNSt8__sce_v24coutE_nid_postfix, + _ZNSt8__sce_v24cerrE_nid_postfix, + _ZNSt8__sce_v25wcoutE_nid_postfix, + _ZNSt8__sce_v25wcerrE_nid_postfix, + _ZNSt8__sce_v23cinE_nid_postfix, + _ZNSt8__sce_v24wcinE_nid_postfix, + }; + for (std::size_t index = 0; index < 6; ++index) { + Require(streams[index][0] == 0); + for (std::size_t other = 0; other < index; ++other) Require(streams[index] != streams[other]); + } + + auto* locale = _ZNSt8__sce_v26locale5_InitEb_nid_postfix(true); + Require(locale != nullptr); + Require(locale == _ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix); + Require(locale == _ZNSt8__sce_v26locale16_GetgloballocaleEv_nid_postfix()); + Require(_ZNSt8__sce_v26locale5_InitEb_nid_postfix(false) == locale); + + GuestLocale::Facet facet{}; + _ZNSt8__sce_v215_Facet_RegisterEPNS_11_Facet_baseE_nid_postfix(&facet); + + auto* copy = _ZNSt8__sce_v26locale7_Locimp11_New_LocimpERKS1__nid_postfix(locale); + Require(copy != nullptr && copy != locale); + Require(copy->facetCount == locale->facetCount); + + GuestLocale::Facet added{}; + _ZNSt8__sce_v26locale7_Locimp14_Locimp_AddfacEPS1_PNS0_5facetEm_nid_postfix(copy, &added, 41); + Require(copy->facetCount > 41); + Require(copy->facets[41] == &added); + + alignas(16) unsigned char classicStorage[sizeof(GuestLocale::SceV2IosBase)]{}; + auto* classic = reinterpret_cast(classicStorage); + classic->locale = &_ZNSt8__sce_v221_sceLibcClassicLocaleE_nid_postfix; + _ZNSt8__sce_v28ios_base7_AddstdEPS0__nid_postfix(classic); + Require(classic->standardStream >= 1 && classic->standardStream <= 8); + _ZNSt8__sce_v28ios_base14_Ios_base_dtorEPS0__nid_postfix(classic); + Require(classic->locale == nullptr); + + { + alignas(16) unsigned char storage[sizeof(GuestLocale::SceV2IosBase)]{}; + auto* stream = reinterpret_cast(storage); + auto* streamNode = static_cast(std::malloc(0x20)); + auto* callbackNode = static_cast(std::malloc(0x18)); + Require(streamNode != nullptr && callbackNode != nullptr); + std::memset(streamNode, 0, 0x20); + std::memset(callbackNode, 0, 0x18); + *reinterpret_cast(callbackNode + 0x10) = reinterpret_cast(&RecordErase); + *reinterpret_cast(callbackNode + 0x08) = 7; + stream->streamNodes = streamNode; + stream->callbackNodes = callbackNode; + _ZNSt8__sce_v28ios_base14_Ios_base_dtorEPS0__nid_postfix(stream); + Require(gEraseCalls == 1); + Require(gEraseSawStreamNodes); + Require(stream->streamNodes == nullptr && stream->callbackNodes == nullptr); + } + + copy->base.vtable->retain(©->base); + Require(copy->base.references == 2); + + GuestLocale::Implementation* firstSlot = copy; + GuestLocale::Implementation* secondSlot = copy; + auto** firstLocale = &firstSlot; + auto** secondLocale = &secondSlot; + + alignas(16) unsigned char firstStorage[sizeof(GuestLocale::SceV2IosBase)]{}; + alignas(16) unsigned char secondStorage[sizeof(GuestLocale::SceV2IosBase)]{}; + auto* first = reinterpret_cast(firstStorage); + auto* second = reinterpret_cast(secondStorage); + first->locale = firstLocale; + second->locale = secondLocale; + _ZNSt8__sce_v28ios_base7_AddstdEPS0__nid_postfix(first); + _ZNSt8__sce_v28ios_base7_AddstdEPS0__nid_postfix(second); + Require(first->standardStream >= 1 && first->standardStream <= 8); + Require(second->standardStream >= 1 && second->standardStream <= 8); + Require(first->standardStream != second->standardStream); + + _ZNSt8__sce_v28ios_base14_Ios_base_dtorEPS0__nid_postfix(first); + Require(first->locale == nullptr); + Require(copy->base.references == 1); + Require(copy->facets[41] == &added); + + _ZNSt8__sce_v28ios_base14_Ios_base_dtorEPS0__nid_postfix(second); + Require(second->locale == nullptr); + Require(copy->base.references == 0); + Require(copy->facets[41] == &added); +} diff --git a/core/libs/tests/GuestSchedPriority.cpp b/core/libs/tests/GuestSchedPriority.cpp index 8aaec24a56..21ff12de2b 100644 --- a/core/libs/tests/GuestSchedPriority.cpp +++ b/core/libs/tests/GuestSchedPriority.cpp @@ -82,7 +82,15 @@ int main() { RequireError(-1, self, 22); RequireSet(PrioProcess, 0, 0); RequireSet(PrioProcess, self, 5); - RequireSet(PrioProcess, self, -20); + RequireSetError(PrioProcess, self, -20, 13); + RequireSetError(PrioProcess, 0, -1, 13); + RequireSetError(PrioGroup, 0, -1, 13); + RequireSetError(PrioGroup, self, -20, 13); + RequireSetError(PrioUser, 0, -5, 13); + RequireSetError(PrioUser, 0, -99, 13); + RequireSetError(PrioProcess, self + 1, -5, 3); + RequireSetError(3, 0, -5, 22); + RequireSet(PrioProcess, self, 0); RequireSetError(PrioProcess, self + 1, 0, 3); RequireSetError(PrioProcess, -1, 0, 3); RequireSetError(PrioGroup, self + 1, 0, 3); @@ -90,14 +98,15 @@ int main() { RequireSetError(3, 0, 0, 22); RequireSetError(-1, self, 0, 22); for (const int nice : {-25, -5, 5, 10, 25, 99, -99}) { - Require(setpriority_nid_postfix(PrioProcess, 0, nice) == 0 && getpriority_nid_postfix(PrioProcess, 0) == 0); + *__error_nid_postfix() = 0; + Require(nice < 0 ? setpriority_nid_postfix(PrioProcess, 0, nice) == -1 && *__error_nid_postfix() == 13 : setpriority_nid_postfix(PrioProcess, 0, nice) == 0); + Require(getpriority_nid_postfix(PrioProcess, 0) == 0); Require(getpriority_nid_postfix(PrioProcess, self) == 0 && getpriority_nid_postfix(PrioGroup, 0) == 0 && getpriority_nid_postfix(PrioUser, 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(1, 0, &rtprio); } catch (const std::exception&) { rtprioThrew = true; } - Require(rtprioThrew); + *__error_nid_postfix() = 0; + Require(rtprio_thread_nid_postfix(1, 0, &rtprio) == -1 && *__error_nid_postfix() == 1); *__error_nid_postfix() = 0; Require(rtprio_thread_nid_postfix(2, 0, &rtprio) == -1 && *__error_nid_postfix() == 3); *__error_nid_postfix() = 0; diff --git a/core/libs/tests/GuestScreenShot.cpp b/core/libs/tests/GuestScreenShot.cpp index d947dd67e1..2773bb0347 100644 --- a/core/libs/tests/GuestScreenShot.cpp +++ b/core/libs/tests/GuestScreenShot.cpp @@ -66,6 +66,7 @@ void TestDisableState() { value = 7; Require(sceScreenShotIsDisabled(-1, &value) == ErrorArgument && value == 7, "id -1 is rejected"); Require(sceScreenShotIsDisabled(0, nullptr) == ErrorArgument, "null result is rejected"); + Require(sceScreenShotIsDisabled(0, reinterpret_cast(0x10)) == ErrorArgument, "invalid pointer is rejected"); Require(sceScreenShotDisableNotification() == 0 && sceScreenShotEnableNotification() == 0, "notification toggles"); Require(sceScreenShotIsDisabled(1, &value) == 0 && value == 0, "positive id 1 is accepted"); Require(sceScreenShotIsDisabled(2, &value) == 0 && value == 0, "positive id 2 is accepted"); diff --git a/core/libs/tests/GuestSdecVersion.cpp b/core/libs/tests/GuestSdecVersion.cpp new file mode 100644 index 0000000000..caa541ff28 --- /dev/null +++ b/core/libs/tests/GuestSdecVersion.cpp @@ -0,0 +1,45 @@ +#include "prx/libSceVdecSavc/include/Api.hpp" +#include "prx/libSceVdecSavc2/include/Api.hpp" +#include "prx/libSceVdecShevc/include/Api.hpp" +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "%s\n", message); + std::abort(); +} + +using GetVersion = void (APS5_VABI*)(std::uint32_t*); + +void CheckVersion(GetVersion getVersion, std::uint32_t expected, const char* name) { + for (unsigned char fill : {0xA5, 0x5A}) { + alignas(std::uint32_t) unsigned char buffer[12]; + std::memset(buffer, fill, sizeof(buffer)); + getVersion(reinterpret_cast(buffer)); + std::uint32_t version = 0; + std::memcpy(&version, buffer, sizeof(version)); + if (version != expected) { + std::fprintf(stderr, "%s wrote 0x%08x, expected 0x%08x\n", name, version, expected); + std::abort(); + } + for (std::size_t i = sizeof(version); i < sizeof(buffer); ++i) + Require(buffer[i] == fill, "GetVersion writes only four bytes"); + } + getVersion(nullptr); +} + +} + +int main() { + CheckVersion(sceSdecGetVersionSw, 0u, "sceSdecGetVersionSw"); + CheckVersion(sceSdecGetVersionSw2, 0x13590001u, "sceSdecGetVersionSw2"); + CheckVersion(sceSdecGetVersionSwHevc, 0x13590001u, "sceSdecGetVersionSwHevc"); + std::puts("sdec version tests passed"); + return 0; +} diff --git a/core/libs/tests/GuestSdma.cpp b/core/libs/tests/GuestSdma.cpp new file mode 100644 index 0000000000..7f9e15b437 --- /dev/null +++ b/core/libs/tests/GuestSdma.cpp @@ -0,0 +1,209 @@ +#include "prx/libSceSdma/libSceSdma.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +namespace { + +std::uint64_t Address(const void* pointer) { return reinterpret_cast(pointer); } + +constexpr int NotQueueMode = static_cast(0x80990002); +constexpr int InvalidArgument = static_cast(0x80990005); +constexpr int Busy = static_cast(0x80990006); +constexpr int RingFull = static_cast(0x80990064); +constexpr int FencePending = static_cast(0x80990065); + +void Require(bool value) { + if (!value) std::abort(); +} + +struct Engine { + std::vector buffer; + void* handle = nullptr; + Engine(std::uint32_t engine, std::uint32_t size, std::uint64_t* writeBack = nullptr) : buffer(size) { + SdmaOpenParam param{engine, 0, 0, reinterpret_cast(writeBack), 0, writeBack ? 0x10u : 0u}; + Require(sceSdmaOpen(&handle, buffer.data(), size, ¶m) == 0 && handle); + } + ~Engine() { Require(sceSdmaClose(handle) == 0); } +}; + +std::vector Pattern(std::size_t size, std::uint8_t seed) { + std::vector bytes(size); + for (std::size_t index = 0; index < size; ++index) bytes[index] = static_cast(index * 31 + seed); + return bytes; +} + +void RequireOpen() { + std::vector buffer(0x8000); + SdmaOpenParam param{12, 0, 0, 0, 0, 0}; + void* handle = nullptr; + Require(sceSdmaOpen(&handle, buffer.data(), 0x8000, nullptr) == InvalidArgument); + Require(sceSdmaOpen(&handle, buffer.data(), 0x7fff, ¶m) == InvalidArgument); + Require(sceSdmaOpen(&handle, nullptr, 0x8000, ¶m) == InvalidArgument); + for (std::uint32_t engine : {0u, 11u, 20u, 0xffffffffu}) { + param.engine = engine; + Require(sceSdmaOpen(&handle, buffer.data(), 0x8000, ¶m) == InvalidArgument); + } + Require(handle == nullptr); + for (std::uint32_t engine = 12; engine < 20; ++engine) { + param.engine = engine; + void* first = nullptr; + void* second = nullptr; + Require(sceSdmaOpen(&first, buffer.data(), 0x8000, ¶m) == 0 && first); + Require(sceSdmaOpen(&second, buffer.data(), 0x8000, ¶m) == Busy && !second); + Require(sceSdmaClose(first) == 0); + Require(sceSdmaOpen(&second, buffer.data(), 0x8000, ¶m) == 0 && second == first); + Require(sceSdmaClose(second) == 0); + } +} + +void RequireLinear() { + Engine engine(13, 0x8000); + const auto source = Pattern(0x400000 + 0x1234, 7); + std::vector destination(source.size() + 16, 0xEE); + Require(sceSdmaCopyLinear(engine.handle, Address(source.data()), Address(destination.data() + 8), static_cast(source.size())) == 0); + Require(std::memcmp(destination.data() + 8, source.data(), source.size()) == 0); + Require(destination[7] == 0xEE && destination[source.size() + 8] == 0xEE); + Require(sceSdmaCopyLinear(engine.handle, Address(nullptr), Address(nullptr), 0) == 0); + std::vector small(64, 0); + Require(sceSdmaCopyLinearNonBlocking(engine.handle, Address(source.data()), Address(small.data()), 64) == 0); + Require(std::memcmp(small.data(), source.data(), 64) == 0); + Require(sceSdmaFlushAll(engine.handle) == 0); +} + +void RequireFill() { + Engine engine(14, 0x8000); + std::vector bytes(0x400000 + 0x40, 0x11); + Require(sceSdmaConstantFill(engine.handle, 0, Address(bytes.data() + 1), 0x400000 + 0x3e, 0) == 0); + Require(bytes[0] == 0x11 && bytes[0x400000 + 0x3f] == 0x11 && bytes[1] == 0 && bytes[0x400000 + 0x3e] == 0); + Require(sceSdmaConstantFillNonBlocking(engine.handle, 0x5a5a5a5au, Address(bytes.data() + 3), 5, 0) == 0); + Require(bytes[2] == 0 && bytes[3] == 0x5a && bytes[7] == 0x5a && bytes[8] == 0); + alignas(4) std::uint32_t words[6]{}; + Require(sceSdmaConstantFill(engine.handle, 0x11223344u, Address(words + 1), 16, 1) == 0); + Require(words[0] == 0 && words[1] == 0x11223344u && words[4] == 0x11223344u && words[5] == 0); +} + +void RequireWindow() { + Engine engine(15, 0x8000); + const std::uint32_t sourcePitch = 40, destinationPitch = 24; + auto source = Pattern(sourcePitch * 20 * 4, 3); + std::vector destination(destinationPitch * 16 * 4, 0xCC); + const auto before = destination; + Require(sceSdmaCopyWindowL2L(engine.handle, Address(source.data()), Address(destination.data()), 32, 5, 3, sourcePitch, 20, 7, 4, destinationPitch, 16, 2, 9) == 0); + for (std::uint32_t y = 0; y < 16; ++y) + for (std::uint32_t x = 0; x < destinationPitch; ++x) { + const std::uint8_t* got = destination.data() + (y * destinationPitch + x) * 4; + const bool inside = x >= 2 && x < 7 && y >= 9 && y < 12; + const std::uint8_t* want = inside ? source.data() + ((y - 9 + 4) * sourcePitch + (x - 2 + 7)) * 4 : before.data() + (y * destinationPitch + x) * 4; + Require(std::memcmp(got, want, 4) == 0); + } + std::vector narrow(64, 0); + Require(sceSdmaCopyWindowL2LNonBlocking(engine.handle, Address(source.data()), Address(narrow.data()), 24, 3, 2, 8, 2, 1, 0, 8, 2, 0, 1) == 0); + Require(narrow[8] == source[1] && narrow[10] == source[3] && narrow[11] == 0 && narrow[16] == source[9] && narrow[0] == 0); + Require(sceSdmaCopyWindowL2L(engine.handle, Address(source.data()), Address(narrow.data()), 128, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0) == 0); + Require(std::memcmp(narrow.data(), source.data(), 16) == 0); +} + +void RequireQueue() { + std::uint64_t writeBack = 0xdeadbeef; + Engine engine(16, 0x8000, &writeBack); + const auto source = Pattern(64, 9); + std::vector destination(64, 0); + Require(sceSdmaCancelQueue(engine.handle) == NotQueueMode); + Require(sceSdmaFlushAll(engine.handle) == 0 && writeBack == 0xdeadbeef); + Require(sceSdmaCopyLinear(engine.handle, Address(source.data()), Address(destination.data()), 16) == 0); + Require(writeBack == (7 + 4) * 4); + + Require(sceSdmaBeginQueueMode(engine.handle) == 0); + Require(writeBack == (7 + 4 + 4) * 4); + Require(sceSdmaCopyLinearNonBlocking(engine.handle, Address(source.data() + 16), Address(destination.data() + 16), 16) == 0); + Require(sceSdmaFenceNonBlocking(engine.handle) == 0); + Require(destination[16] == 0 && sceSdmaTryFlush(engine.handle) == FencePending); + Require(sceSdmaKickQueue(engine.handle) == 0); + Require(std::memcmp(destination.data() + 16, source.data() + 16, 16) == 0); + Require(writeBack == (15 + 7 + 4) * 4); + Require(sceSdmaTryFlush(engine.handle) == 0 && sceSdmaTryFlush(engine.handle) == 0); + + Require(sceSdmaConstantFillNonBlocking(engine.handle, 0xffffffffu, Address(destination.data() + 32), 8, 0) == 0); + Require(sceSdmaFenceNonBlocking(engine.handle) == 0); + Require(sceSdmaCancelQueue(engine.handle) == 0); + Require(destination[32] == 0 && sceSdmaTryFlush(engine.handle) == 0); + Require(sceSdmaKickQueue(engine.handle) == 0 && destination[32] == 0); + Require(writeBack == (26) * 4); + + Require(sceSdmaCopyLinearNonBlocking(engine.handle, Address(source.data() + 32), Address(destination.data() + 32), 8) == 0); + Require(sceSdmaCopyLinear(engine.handle, Address(source.data() + 40), Address(destination.data() + 40), 8) == 0); + Require(std::memcmp(destination.data() + 32, source.data() + 32, 16) == 0); + + Require(sceSdmaCopyLinearNonBlocking(engine.handle, Address(source.data() + 48), Address(destination.data() + 48), 16) == 0); + Require(destination[48] == 0); + Require(sceSdmaEndQueueMode(engine.handle) == 0); + Require(std::memcmp(destination.data() + 48, source.data() + 48, 16) == 0); + Require(sceSdmaCancelQueue(engine.handle) == NotQueueMode); +} + +void RequireRing() { + Engine engine(17, 0x8000); + std::uint8_t source[8]{1, 2, 3, 4, 5, 6, 7, 8}; + std::uint8_t destination[8]{}; + Require(sceSdmaBeginQueueMode(engine.handle) == 0); + for (int index = 0; index < 582; ++index) Require(sceSdmaCopyLinearNonBlocking(engine.handle, Address(source), Address(destination), 8) == 0); + Require(sceSdmaCopyLinearNonBlocking(engine.handle, Address(source), Address(destination), 8) == RingFull); + Require(sceSdmaCopyLinearNonBlocking(engine.handle, Address(source), Address(destination), 0) == RingFull); + Require(sceSdmaConstantFillNonBlocking(engine.handle, 0, Address(destination), 0, 0) == 0); + Require(sceSdmaCopyWindowL2LNonBlocking(engine.handle, Address(source), Address(destination), 8, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0) == RingFull); + Require(sceSdmaConstantFillNonBlocking(engine.handle, 0, Address(destination), 8, 0) == 0); + Require(sceSdmaFenceNonBlocking(engine.handle) == 0); + Require(sceSdmaFenceNonBlocking(engine.handle) == 0); + Require(sceSdmaFenceNonBlocking(engine.handle) == RingFull); + Require(sceSdmaTryFlush(engine.handle) == FencePending); + Require(destination[0] == 0); + Require(sceSdmaCopyLinear(engine.handle, Address(source), Address(destination), 8) == 0); + Require(std::memcmp(destination, source, 8) == 0); + Require(sceSdmaTryFlush(engine.handle) == 0); + Require(sceSdmaCopyLinearNonBlocking(engine.handle, Address(source), Address(destination), 8) == 0); + Require(sceSdmaEndQueueMode(engine.handle) == 0); + + Engine large(18, 0x40000); + for (int index = 0; index < 4095; ++index) Require(sceSdmaFenceNonBlocking(large.handle) == 0); + Require(sceSdmaFenceNonBlocking(large.handle) == RingFull); + Require(sceSdmaTryFlush(large.handle) == 0); + Require(sceSdmaFenceNonBlocking(large.handle) == 0); + Require(sceSdmaFlushAll(large.handle) == 0); + + { + Engine exact(19, 0x8000); + std::vector big(0x400000, 0x3c), target(0x400000, 0); + Require(sceSdmaBeginQueueMode(exact.handle) == 0); + for (int index = 0; index < 581; ++index) Require(sceSdmaCopyLinearNonBlocking(exact.handle, Address(source), Address(destination), 1) == 0); + Require(sceSdmaCopyLinearNonBlocking(exact.handle, Address(big.data()), Address(target.data()), 0x400000) == RingFull); + Require(sceSdmaCopyLinearNonBlocking(exact.handle, Address(big.data()), Address(target.data()), 0x3fffff) == 0); + Require(target[0] == 0); + Require(sceSdmaEndQueueMode(exact.handle) == 0); + Require(target[0x3ffffe] == 0x3c && target[0x3fffff] == 0); + } + + { + std::uint64_t writeBack = 0; + Engine wrapped(19, 0x80000, &writeBack); + Require(sceSdmaBeginQueueMode(wrapped.handle) == 0); + for (int index = 0; index < 18137; ++index) Require(sceSdmaCopyLinearNonBlocking(wrapped.handle, Address(source), Address(destination), 1) == 0); + Require(sceSdmaCopyLinearNonBlocking(wrapped.handle, Address(source), Address(destination), 1) == 0); + Require(sceSdmaEndQueueMode(wrapped.handle) == 0); + Require(writeBack == (18138 * 7 + 4) * 4); + } +} + +} + +int main() { + RequireOpen(); + RequireLinear(); + RequireFill(); + RequireWindow(); + RequireQueue(); + RequireRing(); +} diff --git a/core/libs/tests/GuestSelect.cpp b/core/libs/tests/GuestSelect.cpp index 1c1f48d9dd..e28074413b 100644 --- a/core/libs/tests/GuestSelect.cpp +++ b/core/libs/tests/GuestSelect.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "SceTypes.hpp" #include +#include #include #include #include @@ -109,9 +110,34 @@ static void Kqueues() { Require(close_nid_postfix(kq) == 0); } +static void ClosesDuringAWait() { + for (int attempt = 0; attempt < 200; ++attempt) { + int pair[2]{-1, -1}; + Require(pipe_nid_postfix(pair) == 0); + int result = 0; + int error = 0; + std::atomic started{false}; + std::thread waiter([&] { + GuestDescriptorSet read{}; + read.Add(pair[0]); + GuestTimeval limit{0, 20000}; + started = true; + result = select_nid_postfix(pair[0] + 1, read.words.data(), nullptr, nullptr, &limit); + error = *__error_nid_postfix(); + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::microseconds(1000 + (attempt % 20) * 100)); + Require(close_nid_postfix(pair[0]) == 0); + waiter.join(); + Require(close_nid_postfix(pair[1]) == 0); + Require(result == 0 || (result == -1 && error == GuestEbadf)); + } +} + int main() { for (const int descriptor : {0, 1, 2}) StandardDescriptorFile(descriptor); Kqueues(); + ClosesDuringAWait(); int descriptors[2]{-1, -1}; Require(pipe_nid_postfix(descriptors) == 0); diff --git a/core/libs/tests/GuestSemGetvalueUninit.cpp b/core/libs/tests/GuestSemGetvalueUninit.cpp new file mode 100644 index 0000000000..29e0b8c76c --- /dev/null +++ b/core/libs/tests/GuestSemGetvalueUninit.cpp @@ -0,0 +1,49 @@ +#include "prx/libc/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "SceTypes.hpp" +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::abort(); + } +} + +int main() { + std::uintptr_t sem = 0; + Require(sem_init_nid_postfix(&sem, 0, 1) == 0); + + *__error_nid_postfix() = 0; + Require(sem_getvalue_nid_postfix(&sem, nullptr) == -1); + Require(*__error_nid_postfix() == 22); + + int value = -1; + Require(sem_getvalue_nid_postfix(&sem, &value) == 0); + Require(value == 1); + + Require(sem_destroy_nid_postfix(&sem) == 0); + + std::uintptr_t uninitSem = 0; + *__error_nid_postfix() = 0; + Require(sem_getvalue_nid_postfix(&uninitSem, nullptr) == -1); + Require(*__error_nid_postfix() == 22); + + *__error_nid_postfix() = 0; + Require(sem_getvalue_nid_postfix(nullptr, nullptr) == -1); + Require(*__error_nid_postfix() == 22); + + std::intptr_t ksemId = -1; + Require(ksem_init_nid_postfix(&ksemId, 1) == 0); + + *__error_nid_postfix() = 0; + Require(ksem_getvalue_nid_postfix(ksemId, nullptr) == -1); + Require(*__error_nid_postfix() == 14); + + *__error_nid_postfix() = 0; + Require(ksem_getvalue_nid_postfix(-1, nullptr) == -1); + Require(*__error_nid_postfix() == 22); + + Require(ksem_destroy_nid_postfix(ksemId) == 0); + return 0; +} diff --git a/core/libs/tests/GuestSemaphore.cpp b/core/libs/tests/GuestSemaphore.cpp index 4ef7bdb093..dc09ddadd5 100644 --- a/core/libs/tests/GuestSemaphore.cpp +++ b/core/libs/tests/GuestSemaphore.cpp @@ -1,3 +1,4 @@ +#include "prx/libc/include/Api.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Semaphore/include/Semaphore.hpp" @@ -56,6 +57,8 @@ static void RequireInvalidArguments() { Require(sceKernelSignalSema(sem, 3) == SCE_OK); Require(sceKernelPollSema(sem, 3) == SCE_OK); Require(sceKernelDeleteSema(sem) == SCE_OK); + Require(ksem_wait_nid_postfix(-1) == -1 && *__error_nid_postfix() == 22); + Require(ksem_trywait_nid_postfix(-1) == -1 && *__error_nid_postfix() == 22); } int main() { @@ -113,9 +116,15 @@ int main() { remaining = 5000000; Require(sceKernelWaitSema(sem, 1, &remaining) == SCE_OK && remaining > 0 && remaining <= 5000000); + Require(sceKernelWaitSema(sem, 4, nullptr) == SCE_KERNEL_ERROR_EINVAL); + remaining = 1000; + Require(sceKernelWaitSema(sem, 4, &remaining) == SCE_KERNEL_ERROR_EINVAL && remaining == 1000); + Require(sceKernelSignalSema(sem, 1) == SCE_OK); Require(sceKernelWaitSema(sem, 1, nullptr) == SCE_OK); Require(sceKernelDeleteSema(sem) == SCE_OK); + Require(ksem_wait_nid_postfix(-1) == -1 && *__error_nid_postfix() == 22); + Require(ksem_trywait_nid_postfix(-1) == -1 && *__error_nid_postfix() == 22); RequireInvalidArguments(); } diff --git a/core/libs/tests/GuestSemaphoreHandoff.cpp b/core/libs/tests/GuestSemaphoreHandoff.cpp new file mode 100644 index 0000000000..24b04abb4a --- /dev/null +++ b/core/libs/tests/GuestSemaphoreHandoff.cpp @@ -0,0 +1,146 @@ +#include "prx/libkernel/Semaphore/include/Semaphore.hpp" +#include "prx/libkernel/include/Api.hpp" +#include +#include +#include +#include +#include + +static constexpr int SCE_OK = 0; +static constexpr std::uint32_t SemaFifo = 1; +static constexpr std::uint32_t SemaPriority = 2; + +static void Require(bool value) { if (!value) std::abort(); } + +struct Waiter { + KernelSema sem = nullptr; + int result = -1; +}; + +static void* APS5_VABI Wait(void* arg) { + auto& waiter = *static_cast(arg); + KernelUseconds timeout = 1000000; + waiter.result = sceKernelWaitSema(waiter.sem, 1, &timeout); + return nullptr; +} + +static std::atomic released{0}; + +static int QueuedCount(KernelSema sem) { + std::lock_guard lock(sem->mutex); + return static_cast(sem->queue.size()); +} + +struct QueuedWaiter { + KernelSema sem = nullptr; + int need = 1; + int priority = 0; + int result = -1; + std::atomic order{0}; +}; + +static void* APS5_VABI WaitInQueue(void* arg) { + auto& waiter = *static_cast(arg); + if (waiter.priority != 0) Require(scePthreadSetprio(scePthreadSelf(), waiter.priority) == SCE_OK); + KernelUseconds timeout = 5000000; + waiter.result = sceKernelWaitSema(waiter.sem, waiter.need, &timeout); + waiter.order = ++released; + return nullptr; +} + +static void WaitForQueue(KernelSema sem, int count) { + for (int i = 0; i < 5000 && QueuedCount(sem) != count; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(QueuedCount(sem) == count); +} + +static void WaitForReleased(int count) { + for (int i = 0; i < 5000 && released.load() < count; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + Require(released.load() == count); +} + +static Pthread StartQueued(QueuedWaiter& waiter, int queued) { + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, WaitInQueue, &waiter, nullptr) == SCE_OK); + WaitForQueue(waiter.sem, queued); + return thread; +} + +static void CheckPassesOverALargerNeed() { + released = 0; + KernelSema sem = nullptr; + Require(sceKernelCreateSema(&sem, "handoff-skip", SemaFifo, 0, 4, nullptr) == SCE_OK); + QueuedWaiter large{sem, 2}; + QueuedWaiter small{sem, 1}; + const Pthread largeThread = StartQueued(large, 1); + const Pthread smallThread = StartQueued(small, 2); + Require(sceKernelSignalSema(sem, 1) == SCE_OK); + WaitForReleased(1); + Require(small.order == 1 && small.result == SCE_OK); + Require(QueuedCount(sem) == 1 && sceKernelPollSema(sem, 1) == SCE_KERNEL_ERROR_EBUSY); + Require(sceKernelSignalSema(sem, 2) == SCE_OK); + WaitForReleased(2); + Require(large.order == 2 && large.result == SCE_OK); + Require(scePthreadJoin(largeThread, nullptr) == SCE_OK && scePthreadJoin(smallThread, nullptr) == SCE_OK); + Require(sceKernelPollSema(sem, 1) == SCE_KERNEL_ERROR_EBUSY); + Require(sceKernelDeleteSema(sem) == SCE_OK); +} + +static void CheckDeletedRefusesFreeTokens() { + KernelSema sem = nullptr; + Require(sceKernelCreateSema(&sem, "handoff-deleted", SemaFifo, 2, 2, nullptr) == SCE_OK); + { + std::lock_guard lock(sem->mutex); + sem->deleted = true; + } + Require(sceKernelWaitSema(sem, 1, nullptr) == SCE_KERNEL_ERROR_EACCES); + { + std::lock_guard lock(sem->mutex); + Require(sem->tokenCount == 2); + sem->deleted = false; + } + Require(sceKernelDeleteSema(sem) == SCE_OK); +} + +static void CheckServesInOrder(std::uint32_t attr, int firstPriority, int secondPriority, bool firstServedFirst) { + released = 0; + KernelSema sem = nullptr; + Require(sceKernelCreateSema(&sem, "handoff-order", attr, 0, 2, nullptr) == SCE_OK); + QueuedWaiter first{sem, 1, firstPriority}; + QueuedWaiter second{sem, 1, secondPriority}; + const Pthread firstThread = StartQueued(first, 1); + const Pthread secondThread = StartQueued(second, 2); + Require(sceKernelSignalSema(sem, 1) == SCE_OK); + WaitForReleased(1); + Require((firstServedFirst ? first.order : second.order) == 1); + Require(QueuedCount(sem) == 1); + Require(sceKernelSignalSema(sem, 1) == SCE_OK); + WaitForReleased(2); + Require(first.result == SCE_OK && second.result == SCE_OK); + Require(scePthreadJoin(firstThread, nullptr) == SCE_OK && scePthreadJoin(secondThread, nullptr) == SCE_OK); + Require(sceKernelDeleteSema(sem) == SCE_OK); +} + +int main() { + for (int i = 0; i < 20; ++i) { + KernelSema sem = nullptr; + Require(sceKernelCreateSema(&sem, "handoff", 1, 0, 1, nullptr) == SCE_OK); + Waiter waiter{sem}; + Pthread thread = nullptr; + Require(scePthreadCreate(&thread, nullptr, Wait, &waiter, nullptr) == SCE_OK); + WaitForQueue(sem, 1); + Require(sceKernelSignalSema(sem, 1) == SCE_OK); + Require(sceKernelPollSema(sem, 1) == SCE_KERNEL_ERROR_EBUSY); + Require(scePthreadJoin(thread, nullptr) == SCE_OK); + Require(waiter.result == SCE_OK); + Require(sceKernelPollSema(sem, 1) == SCE_KERNEL_ERROR_EBUSY); + Require(sceKernelDeleteSema(sem) == SCE_OK); + } + CheckPassesOverALargerNeed(); + CheckDeletedRefusesFreeTokens(); + CheckServesInOrder(SemaFifo, 700, 700, true); + CheckServesInOrder(SemaFifo, 767, 256, true); + CheckServesInOrder(SemaPriority, 767, 256, false); + CheckServesInOrder(SemaPriority, 256, 767, true); + CheckServesInOrder(SemaPriority, 700, 700, true); + return 0; +} diff --git a/core/libs/tests/GuestShare.cpp b/core/libs/tests/GuestShare.cpp index 3bf336086b..476fa2a06b 100644 --- a/core/libs/tests/GuestShare.cpp +++ b/core/libs/tests/GuestShare.cpp @@ -75,10 +75,12 @@ int main() { Require(sceShareGetCurrentStatus(0, reinterpret_cast(status)) == invalidParam); Require(status[0] == 0x5a); Require(sceShareGetCurrentStatus(1, nullptr) == invalidParam); + Require(sceShareGetCurrentStatus(1, reinterpret_cast(0x10)) == invalidParam); std::uint32_t running[2]{0xffffffffu, 0x5a5a5a5au}; Require(sceShareGetRunningStatus(running) == 0); Require(running[0] == 0 && running[1] == 0x5a5a5a5au); Require(sceShareGetRunningStatus(nullptr) == invalidParam); + Require(sceShareGetRunningStatus(reinterpret_cast(0x10)) == invalidParam); Require(sceShareSetContentParamForApplicationTitle("title") == 0); Require(sceShareSetContentParamForApplicationTitle(nullptr) == invalidParam); Require(sceShareSetContentParamForApplicationTitle("") == invalidParam); diff --git a/core/libs/tests/GuestSigvec.cpp b/core/libs/tests/GuestSigvec.cpp new file mode 100644 index 0000000000..1680cac705 --- /dev/null +++ b/core/libs/tests/GuestSigvec.cpp @@ -0,0 +1,171 @@ +#include "prx/libc/libc.hpp" +#include "prx/libc/include/GuestArena.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "sigvec: %s\n", message); + std::abort(); +} + +GuestSignalSet Mask() { + GuestSignalSet value{}; + Require(_sigprocmask_nid_postfix(0, nullptr, &value) == 0, "query mask"); + return value; +} + +bool Equal(const GuestSignalSet& first, const GuestSignalSet& second) { + for (int word = 0; word < 4; ++word) if (first.bits[word] != second.bits[word]) return false; + return true; +} + +bool Equal(const GuestSignalVector& first, const GuestSignalVector& second) { + return first.handler == second.handler && first.mask == second.mask && first.flags == second.flags; +} + +int received = 0; +int signalReceived = 0; + +void APS5_VABI Handler(int signal) { + ++received; + signalReceived = signal; +} + +void Vectors() { + constexpr int signal = 30; + const auto handler = reinterpret_cast(&Handler); + GuestSigaction initial{}; + Require(sigaction_nid_postfix(signal, nullptr, &initial) == 0, "save original action"); + for (int flags = 0; flags < 128; ++flags) { + const GuestSignalVector replacement{handler, std::bit_cast(0x80000005u), flags}; + Require(sigvec_nid_disambig1(signal, &replacement, nullptr) == 0, "install each supported flag combination"); + GuestSigaction actual{}; + Require(sigaction_nid_postfix(signal, nullptr, &actual) == 0, "query modern action"); + Require(actual.handler == handler && actual.flags == (flags ^ 2), "legacy interrupt flag reverses restart flag"); + Require(Equal(actual.mask, GuestSignalSet{{0x80000005, 0, 0, 0}}), "legacy vector zero extends mask"); + GuestSignalVector queried{}; + Require(sigvec_nid_disambig1(signal, nullptr, &queried) == 0 && Equal(queried, replacement), "query converts flags and mask back"); + } + const GuestSigaction extended{handler, 0x13, {{0x87654321, 0x12345678, 0x80000001, 0x7fffffff}}}; + Require(sigaction_nid_postfix(signal, &extended, nullptr) == 0, "install action with extended mask"); + GuestSignalVector queried{}; + *__error_nid_postfix() = 81; + Require(sigvec_nid_disambig1(signal, nullptr, &queried) == 0, "query extended action"); + Require(Equal(queried, GuestSignalVector{handler, std::bit_cast(0x87654321u), 0x11}), "query truncates modern mask to low word"); + Require(*__error_nid_postfix() == 81, "successful vector preserves errno"); + GuestSignalVector alias{handler, 7, 0}; + Require(sigvec_nid_disambig1(signal, &alias, &alias) == 0 && Equal(alias, queried), "aliased input and output returns old action"); + GuestSignalVector newState{}; + Require(sigvec_nid_disambig1(signal, nullptr, &newState) == 0 && Equal(newState, GuestSignalVector{handler, 7, 0}), "alias installs original input"); + const GuestSignalVector sentinel{0x12345678, -123, 0x345}; + const GuestSignalVector invalid{handler, -1, 0x80}; + for (const int number : std::array{-1, 0, 129, signal}) { + GuestSignalVector output = sentinel; + *__error_nid_postfix() = 0; + Require(sigvec_nid_disambig1(number, &invalid, &output) == -1, "invalid signal or flags fail"); + Require(*__error_nid_postfix() == 22 && Equal(output, sentinel), "failure preserves previous output and exposes errno"); + } + Require(sigvec_nid_disambig1(signal, nullptr, &queried) == 0 && Equal(queried, newState), "failed update leaves disposition unchanged"); + for (const int number : std::array{9, 17}) { + GuestSignalVector output = sentinel; + Require(sigvec_nid_disambig1(number, &newState, &output) == -1 && Equal(output, sentinel), "uninstallable handlers preserve output"); + } + Require(sigvec_nid_disambig1(signal, nullptr, nullptr) == 0, "null input and output query is accepted"); + const GuestSignalSet saved = Mask(); + const GuestSignalSet empty{}; + Require(_sigprocmask_nid_postfix(3, &empty, nullptr) == 0, "clear mask for delivery"); + received = 0; + const GuestSignalSet blocked{{1u << (signal - 1), 0, 0, 0}}; + Require(_sigprocmask_nid_postfix(1, &blocked, nullptr) == 0, "block installed handler"); + Require(raise_nid_postfix(signal) == 0 && received == 0, "blocked signal becomes pending"); + GuestSignalSet previous{}; + Require(_sigprocmask_nid_postfix(3, &empty, &previous) == 0 && previous.bits[0] == (1u << (signal - 1)), "unblock returns prior mask"); + Require(received == 1 && signalReceived == signal, "unblock delivers pending signal through guest handler"); + Require(raise_nid_postfix(signal) == 0 && received == 2, "unblocked delivery retains vector handler"); + Require(sigaction_nid_postfix(signal, &initial, nullptr) == 0, "restore disposition"); + Require(_sigprocmask_nid_postfix(3, &saved, nullptr) == 0, "restore mask after delivery"); +} + +void GuestBackedOutput() { + constexpr std::size_t page = 0x4000; + constexpr std::size_t bytes = page * 2; + std::int64_t physical = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, bytes, 0, 0, &physical) == 0, "allocate guest signal storage"); + void* mapping = nullptr; + Require(sceKernelMapDirectMemory(&mapping, bytes, 3, 0, physical, 0) == 0 && mapping != nullptr, "map guest signal storage"); + Require(GuestArena::GuestArenaContains_nid_postfix(mapping, bytes), "output is not backed by the guest arena"); + auto* memory = static_cast(mapping); + { + GuestArena::HostWrite access(mapping, bytes); + Require(access.Open(), "initialize guest signal storage"); + std::memset(mapping, 0xA5, bytes); + } + constexpr int signal = 30; + GuestSigaction initial{}; + Require(sigaction_nid_postfix(signal, nullptr, &initial) == 0, "save guest-output disposition"); + const GuestSignalVector replacement{reinterpret_cast(&Handler), -123, 0}; + auto* output = reinterpret_cast(memory + page - 8); + Require(sigvec_nid_disambig1(signal, &replacement, output) == 0, "write complete vector across guest pages"); + Require(Equal(*output, GuestSignalVector{initial.handler, std::bit_cast(initial.mask.bits[0]), initial.flags ^ 2}), "guest vector conversion"); + Require(memory[page - 9] == 0xA5 && memory[page + 8] == 0xA5, "guest vector output escaped its bounds"); + *output = replacement; + Require(sigvec_nid_disambig1(signal, output, output) == 0 && Equal(*output, replacement), "guest input-output alias conversion"); +#ifdef _WIN32 + const GuestSignalVector sentinel{0x12345678, -123, 0x345}; + const GuestSignalVector changed{replacement.handler, 5, 2}; + const auto reject = [&](GuestSignalVector* destination, void* protectedPage) { + *destination = sentinel; + Require(sceKernelMprotect(protectedPage, page, 1) == 0, "protect guest vector output"); + { + GuestArena::HostWrite access(destination, sizeof(*destination)); + Require(!access.Open(), "read-only guest output unexpectedly opened"); + } + *__error_nid_postfix() = 73; + Require(sigvec_nid_disambig1(signal, &changed, destination) == -1 && *__error_nid_postfix() == 14, "read-only guest vector was not rejected"); + Require(Equal(*destination, sentinel), "rejected guest vector was partially overwritten"); + GuestSignalVector current{}; + Require(sigvec_nid_disambig1(signal, nullptr, ¤t) == 0 && Equal(current, replacement), "rejected output changed the disposition"); + Require(sigvec_nid_disambig1(signal, destination, destination) == -1 && *__error_nid_postfix() == 14, "read-only aliased vector was not rejected before mutation"); + Require(Equal(*destination, sentinel), "rejected aliased vector was changed"); + Require(sigvec_nid_disambig1(signal, nullptr, ¤t) == 0 && Equal(current, replacement), "rejected alias changed the disposition"); + Require(sceKernelMprotect(protectedPage, page, 3) == 0, "restore guest vector protection"); + }; + reject(reinterpret_cast(memory + 128), mapping); + reject(output, memory + page); +#endif + Require(sigaction_nid_postfix(signal, &initial, nullptr) == 0, "restore guest-output disposition"); + Require(sceKernelMunmap(reinterpret_cast(mapping), bytes) == 0, "unmap guest signal storage"); + Require(sceKernelReleaseDirectMemory(physical, bytes) == 0, "release guest signal storage"); +} + +void MissingBackend() { + const auto saved = Mask(); + const GuestSignalVector sentinel{0x12345678, -123, 0x345}; + GuestSignalVector output = sentinel; + const GuestSignalVector replacement{reinterpret_cast(&Handler), 7, 0}; + RegisterSigvec_nid_no_patch(nullptr); + bool threw = false; + try { + sigvec_nid_disambig1(30, &replacement, &output); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "missing backend throws"); + Require(Equal(output, sentinel) && Equal(Mask(), saved), "missing backend leaves output and masks untouched"); + RegisterSigvec_nid_no_patch(sigaction_nid_postfix); +} + +} + +int main() { + Vectors(); + GuestBackedOutput(); + MissingBackend(); +} diff --git a/core/libs/tests/GuestSigvecModules.cpp b/core/libs/tests/GuestSigvecModules.cpp new file mode 100644 index 0000000000..6a4c33fe01 --- /dev/null +++ b/core/libs/tests/GuestSigvecModules.cpp @@ -0,0 +1,237 @@ +#include "prx/libc/include/LegacySignals.hpp" +#include "prx/libc/include/GuestErrno.hpp" +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +namespace { + +void Require(bool value, const char* message) { + if (value) return; + std::fprintf(stderr, "sigvec modules: %s\n", message); + std::abort(); +} + +class Module { +public: + explicit Module(const std::filesystem::path& path) { +#ifdef _WIN32 + handle = LoadLibraryExW(path.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + if (handle == nullptr) std::fprintf(stderr, "LoadLibraryExW(%ls) failed: %lu\n", path.c_str(), GetLastError()); +#else + handle = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (handle == nullptr) std::fprintf(stderr, "%s\n", dlerror()); +#endif + Require(handle != nullptr, "load actual patched module"); +#ifdef _WIN32 + HMODULE pinned = nullptr; + Require(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + reinterpret_cast(handle), &pinned) != 0 && pinned == handle, "pin actual module until process exit"); +#endif + } + + ~Module() { +#ifdef _WIN32 + FreeLibrary(handle); +#else + dlclose(handle); +#endif + } + + Module(const Module&) = delete; + Module& operator=(const Module&) = delete; + + void* Find(const char* name) const { +#ifdef _WIN32 + auto* result = reinterpret_cast(GetProcAddress(handle, name)); +#else + auto* result = dlsym(handle, name); +#endif + return result; + } + + void* Symbol(const char* name) const { + auto* result = Find(name); + Require(result != nullptr, "lookup actual native export"); + return result; + } + + void RequireOwner(void* address) const { +#ifdef _WIN32 + HMODULE owner = nullptr; + Require(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(address), &owner) != 0 && owner == handle, "NID resolved outside requested DLL"); +#else + link_map* image = nullptr; + Dl_info info{}; + Require(dlinfo(handle, RTLD_DI_LINKMAP, &image) == 0 && image != nullptr, "read requested module identity"); + Require(dladdr(address, &info) != 0 && info.dli_fbase == reinterpret_cast(image->l_addr), "NID resolved through an ELF dependency"); +#endif + } + + template + T Function(const char* name) const { + return reinterpret_cast(Symbol(Nid::ComputeNid(name, "").c_str())); + } + +private: +#ifdef _WIN32 + HMODULE handle = nullptr; +#else + void* handle = nullptr; +#endif +}; + +using Sigvec = decltype(&sigvec_nid_postfix); +GuestSigaction recorded{}; +GuestSigaction answer{0x12345678, 0x13, {{0x87654321, 0x11111111, 0x22222222, 0x33333333}}}; +int calls = 0; +int result = 0; +bool sawReplacement = false; +bool sawPrevious = false; +int requestedSignal = 0; + +int APS5_VABI RecordAction(int signal, const GuestSigaction* replacement, GuestSigaction* previous) { + ++calls; + requestedSignal = signal; + sawReplacement = replacement != nullptr; + sawPrevious = previous != nullptr; + if (replacement != nullptr) recorded = *replacement; + if (result == 0 && previous != nullptr) *previous = answer; + return result; +} + +bool Equal(const GuestSignalVector& first, const GuestSignalVector& second) { + return first.handler == second.handler && first.mask == second.mask && first.flags == second.flags; +} + +void CheckSharedDisposition(const std::array& exports, const Module& libc) { + const auto error = libc.Function("__error"); + GuestSignalVector original{}; + Require(exports[0](30, nullptr, &original) == 0, "save actual kernel disposition"); + const GuestSignalVector first{1, std::bit_cast(0x80000005u), 3}; + const GuestSignalVector second{1, -123, 2}; + GuestSignalVector queried{}; + Require(exports[1](30, &first, nullptr) == 0 && exports[0](30, nullptr, &queried) == 0 && Equal(queried, first), "Internal install is not shared with libc"); + Require(exports[0](30, &second, nullptr) == 0 && exports[1](30, nullptr, &queried) == 0 && Equal(queried, second), "libc install is not shared with Internal"); + for (const auto function : exports) { + queried = first; + Require(function(0, &second, &queried) == -1 && *error() == 22 && Equal(queried, first), "actual backend failure changed module output"); + } + Require(exports[0](30, &original, nullptr) == 0, "restore actual kernel disposition"); +} + +void CheckConversions(const std::array& exports) { + const GuestSignalVector expected{answer.handler, std::bit_cast(answer.mask.bits[0]), answer.flags ^ 2}; + for (const auto function : exports) { + for (const int flags : {0, 1, 2, 3, -1}) { + const GuestSignalVector replacement{0x34567890, -1, flags}; + GuestSignalVector previous{}; + result = 0; + const int before = calls; + Require(function(30, &replacement, &previous) == 0 && calls == before + 1, "module wrapper did not call shared core once"); + Require(requestedSignal == 30 && sawReplacement && sawPrevious, "forwarded argument identity"); + Require(recorded.handler == replacement.handler && recorded.flags == (flags ^ 2), "firmware handler/flag conversion"); + Require(recorded.mask.bits[0] == 0xffffffffu && recorded.mask.bits[1] == 0 && recorded.mask.bits[2] == 0 && recorded.mask.bits[3] == 0, "firmware low mask conversion"); + Require(Equal(previous, expected), "firmware previous vector conversion"); + GuestSignalVector alias = replacement; + Require(function(30, &alias, &alias) == 0 && Equal(alias, expected), "module-owned alias conversion"); + Require(recorded.handler == replacement.handler && recorded.flags == (flags ^ 2) && recorded.mask.bits[0] == 0xffffffffu, "alias input changed before forwarding"); + } + result = 0; + Require(function(30, nullptr, nullptr) == 0 && !sawReplacement && !sawPrevious, "both null pointers were not forwarded"); + GuestSignalVector previous{}; + Require(function(30, nullptr, &previous) == 0 && !sawReplacement && sawPrevious && Equal(previous, expected), "null replacement query"); + const GuestSignalVector replacement{0x34567890, 7, 0}; + Require(function(30, &replacement, nullptr) == 0 && sawReplacement && !sawPrevious, "null previous output"); + for (const int failure : {-7, -1, 1}) { + result = failure; + previous = replacement; + Require(function(17, &replacement, &previous) == failure && Equal(previous, replacement), "forward failure changed output or result"); + } + } +} + +void CheckGuestOutput(const std::array& exports, const Module& kernel, const Module& libc) { + const auto allocate = kernel.Function("sceKernelAllocateDirectMemory"); + const auto map = kernel.Function("sceKernelMapDirectMemory"); + const auto unmap = kernel.Function("sceKernelMunmap"); + const auto release = kernel.Function("sceKernelReleaseDirectMemory"); + constexpr std::size_t page = 0x4000; + std::int64_t physical = 0; + Require(allocate(0, 0x7fffffffffll, page * 2, 0, 0, &physical) == 0, "allocate real guest module output"); + void* mapping = nullptr; + Require(map(&mapping, page * 2, 3, 0, physical, 0) == 0 && mapping != nullptr, "map real guest module output"); + auto* output = reinterpret_cast(static_cast(mapping) + page - 8); + const GuestSignalVector expected{answer.handler, std::bit_cast(answer.mask.bits[0]), answer.flags ^ 2}; + result = 0; + for (const auto function : exports) Require(function(30, nullptr, output) == 0 && Equal(*output, expected), "owned NID guest-backed output"); +#ifdef _WIN32 + const auto protect = kernel.Function("sceKernelMprotect"); + const auto error = libc.Function("__error"); + const GuestSignalVector sentinel{0x23456789, 5, 2}; + *output = sentinel; + Require(protect(static_cast(mapping) + page, page, 1) == 0, "protect module output tail"); + for (const auto function : exports) { + const int before = calls; + Require(function(30, &sentinel, output) == -1 && *error() == 14 && calls == before, "rejected module output mutated backend"); + Require(Equal(*output, sentinel), "rejected module output partially written"); + } + Require(protect(static_cast(mapping) + page, page, 3) == 0, "restore module output protection"); +#else + static_cast(libc); +#endif + Require(unmap(reinterpret_cast(mapping), page * 2) == 0, "unmap module output"); + Require(release(physical, page * 2) == 0, "release module output"); +} + +} + +int main(int argc, char** argv) { + Require(argc == 3 || (argc == 4 && std::string(argv[3]) == "libc-first"), "expected patched module directory, exception probe and optional load order"); + const auto directory = std::filesystem::absolute(argv[1]); + const Module first(directory / (argc == 4 ? "libc.prx" : "libSceLibcInternal.prx")); + const Module libc(directory / "libc.prx"); + const Module kernel(directory / "libkernel.prx"); + const Module internal(directory / "libSceLibcInternal.prx"); + auto* libcAddress = libc.Symbol("yhxKO9LYc8A"); + auto* internalAddress = internal.Symbol("yhxKO9LYc8A"); + libc.RequireOwner(libcAddress); + internal.RequireOwner(internalAddress); + Require(libcAddress != internalAddress, "both modules resolve to the same wrapper"); + std::array exports{reinterpret_cast(libcAddress), reinterpret_cast(internalAddress)}; + for (const auto function : exports) Require(function(30, nullptr, nullptr) == 0, "cold kernel registration missing"); + CheckSharedDisposition(exports, libc); + auto* registrationAddress = libc.Symbol("RegisterSigvec_nid_no_patch"); + libc.RequireOwner(registrationAddress); + auto* coreAddress = libc.Symbol("LegacySigvec_nid_no_patch"); + libc.RequireOwner(coreAddress); +#ifdef _WIN32 + Require(internal.Find("LegacySigvec_nid_no_patch") == nullptr, "Internal duplicated the private core"); +#else + Require(internal.Find("LegacySigvec_nid_no_patch") == coreAddress, "Internal does not use libc private core"); +#endif + const auto registration = reinterpret_cast(registrationAddress); + const Module exceptionProbe(directory / argv[2]); + auto* exceptionAddress = exceptionProbe.Symbol("SigvecMissingBackend_nid_no_patch"); + exceptionProbe.RequireOwner(exceptionAddress); + const auto checkMissingBackend = reinterpret_cast(exceptionAddress); + registration(nullptr); + Require(checkMissingBackend(exports[0], exports[1], coreAddress), "module wrapper has duplicate or stale callback state"); + registration(RecordAction); + CheckConversions(exports); + CheckGuestOutput(exports, kernel, libc); + registration(kernel.Function("sigaction")); + for (const auto function : exports) Require(function(30, nullptr, nullptr) == 0, "actual kernel registration restore"); + std::printf("sigvec NID ownership, shared callback/core, firmware conversions and guest output passed (%s first)\n", argc == 4 ? "libc" : "Internal"); +} diff --git a/core/libs/tests/GuestSocialScreenDialog.cpp b/core/libs/tests/GuestSocialScreenDialog.cpp index c86884d5f1..40138bb298 100644 --- a/core/libs/tests/GuestSocialScreenDialog.cpp +++ b/core/libs/tests/GuestSocialScreenDialog.cpp @@ -2,6 +2,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include #include +#include namespace { @@ -15,12 +16,14 @@ constexpr int kErrAlreadyInitialized = static_cast(0x80B80004); constexpr int kErrNotFinished = static_cast(0x80B80005); constexpr int kErrArgNull = static_cast(0x80B8000D); constexpr int kResultUserCanceled = 1; +constexpr std::int32_t kSentinel = static_cast(0x5a5a5a5a); } int main() { std::int64_t param = 0; - std::int32_t result[2] = {-1, -1}; + std::int32_t result[2]; + std::memset(result, 0x5a, sizeof(result)); Require(sceSocialScreenDialogTerminate() == kErrNotInitialized); Require(sceSocialScreenDialogOpen(nullptr) == kErrNotInitialized); @@ -37,7 +40,11 @@ int main() { Require(sceSocialScreenDialogUpdateStatus() == kStatusInitialized); Require(sceSocialScreenDialogOpen(nullptr) == kErrArgNull); + std::memset(result, 0x5a, sizeof(result)); + Require(sceSocialScreenDialogGetResult(nullptr) == kErrArgNull); Require(sceSocialScreenDialogGetResult(result) == kErrNotFinished); + Require(result[0] == kSentinel); + Require(result[1] == kSentinel); Require(sceSocialScreenDialogOpen(¶m) == 0); Require(sceSocialScreenDialogGetStatus() == kStatusFinished); @@ -46,7 +53,7 @@ int main() { Require(sceSocialScreenDialogGetResult(nullptr) == kErrArgNull); Require(sceSocialScreenDialogGetResult(result) == 0); Require(result[0] == kResultUserCanceled); - Require(result[1] == -1); + Require(result[1] == kSentinel); Require(sceSocialScreenDialogClose() == 0); Require(sceSocialScreenDialogTerminate() == 0); @@ -60,9 +67,10 @@ int main() { Require(sceSocialScreenDialogUpdateStatus() == kStatusInitialized); Require(sceSocialScreenDialogClose() == 0); Require(sceSocialScreenDialogUpdateStatus() == kStatusFinished); - result[0] = -1; + std::memset(result, 0x5a, sizeof(result)); Require(sceSocialScreenDialogGetResult(result) == 0); Require(result[0] == kResultUserCanceled); + Require(result[1] == kSentinel); Require(sceSocialScreenDialogTerminate() == 0); Require(sceSocialScreenDialogTerminate() == kErrNotInitialized); Require(sceSocialScreenDialogOpen(nullptr) == kErrNotInitialized); diff --git a/core/libs/tests/GuestSocketAddressLength.cpp b/core/libs/tests/GuestSocketAddressLength.cpp index 7c6f77cade..5a45abe4be 100644 --- a/core/libs/tests/GuestSocketAddressLength.cpp +++ b/core/libs/tests/GuestSocketAddressLength.cpp @@ -205,6 +205,72 @@ void LongMappedAddressesBindOnlyDualStackDatagramSockets() { Require(close_nid_postfix(datagram) == 0, "close failed"); } +std::array Inet(std::array host, std::uint16_t port) { + std::array address{16, 2, static_cast(port >> 8), static_cast(port)}; + std::memcpy(address.data() + 4, host.data(), host.size()); + return address; +} + +std::array Inet6(std::uint8_t scope, std::uint16_t port) { + std::array address{28, 28, static_cast(port >> 8), static_cast(port)}; + address[8] = 0xff; + address[9] = scope; + address[23] = 1; + return address; +} + +void StreamSocketsRefuseMulticastAddresses() { + constexpr int ErrorOperationNotSupported = 45; + constexpr int ErrorConnected = 56; + constexpr int GetFlags = 3; + constexpr int SetFlags = 4; + constexpr int NonBlockingFlag = 4; + const auto newStream = [](int family, bool nonblocking) { + const int descriptor = socket_nid_postfix(family, Stream, 0); + Require(descriptor >= 0, "socket failed"); + if (nonblocking) Require(fcntl_nid_postfix(descriptor, SetFlags, fcntl_nid_postfix(descriptor, GetFlags) | NonBlockingFlag) == 0, "fcntl(F_SETFL, O_NONBLOCK) failed"); + return descriptor; + }; + const auto group = Inet({239, 1, 2, 3}, 0); + const auto group6 = Inet6(0x0e, 0); + int descriptor = newStream(2, false); + Require(Fails(bind_nid_postfix(descriptor, group.data(), group.size()), ErrorAddressFamilyNotSupported), "tcp_usr_bind refuses an IPv4 multicast address, so bind to 239.1.2.3 must fail with EAFNOSUPPORT (47)"); + Require(close_nid_postfix(descriptor) == 0, "close failed"); + descriptor = newStream(28, false); + Require(Fails(bind_nid_postfix(descriptor, group6.data(), group6.size()), ErrorAddressFamilyNotSupported), "tcp6_usr_bind refuses an IPv6 multicast address, so bind to ff0e::1 must fail with EAFNOSUPPORT (47)"); + Require(close_nid_postfix(descriptor) == 0, "close failed"); + descriptor = socket_nid_postfix(2, Datagram, 0); + Require(descriptor >= 0, "socket failed"); + Require(!Fails(bind_nid_postfix(descriptor, group.data(), group.size()), ErrorAddressFamilyNotSupported), "the multicast check is in tcp_usr_bind only, so a datagram bind to 239.1.2.3 must not fail with EAFNOSUPPORT (47)"); + Require(close_nid_postfix(descriptor) == 0, "close failed"); + for (const bool nonblocking : {false, true}) { + const auto destination = Inet({224, 0, 0, 1}, 8080); + const auto destination6 = Inet6(0x02, 8080); + descriptor = newStream(2, nonblocking); + Require(Fails(connect_nid_postfix(descriptor, destination.data(), destination.size()), ErrorAddressFamilyNotSupported), "tcp_usr_connect refuses an IPv4 multicast address, so connect to 224.0.0.1:8080 must fail with EAFNOSUPPORT (47)"); + Require(close_nid_postfix(descriptor) == 0, "close failed"); + descriptor = newStream(28, nonblocking); + Require(Fails(connect_nid_postfix(descriptor, destination6.data(), destination6.size()), ErrorAddressFamilyNotSupported), "tcp6_usr_connect refuses an IPv6 multicast address, so connect to ff02::1 port 8080 must fail with EAFNOSUPPORT (47)"); + Require(close_nid_postfix(descriptor) == 0, "close failed"); + } + const auto portZero = Inet({224, 0, 0, 1}, 0); + descriptor = newStream(2, false); + Require(Fails(connect_nid_postfix(descriptor, portZero.data(), portZero.size()), ErrorAddressFamilyNotSupported), "tcp_usr_connect checks multicast before tcp_connect looks at the port, so connect to 224.0.0.1:0 must fail with EAFNOSUPPORT (47)"); + const auto loopback = Loopback(); + Require(bind_nid_postfix(descriptor, loopback.data(), loopback.size()) == 0, "bind to 127.0.0.1 failed"); + Require(Fails(bind_nid_postfix(descriptor, group.data(), group.size()), ErrorAddressFamilyNotSupported), "tcp_usr_bind checks multicast before in_pcbbind, so a bound socket binding 239.1.2.3 must fail with EAFNOSUPPORT (47), not EINVAL (22)"); + Require(listen_nid_postfix(descriptor, 1) == 0, "listen failed"); + const auto destination = Inet({224, 0, 0, 1}, 8080); + Require(Fails(connect_nid_postfix(descriptor, destination.data(), destination.size()), ErrorOperationNotSupported), "soconnect refuses a listening socket before tcp_usr_connect, so connect to 224.0.0.1 must fail with EOPNOTSUPP (45)"); + auto address = loopback; + std::uint32_t size = address.size(); + Require(getsockname_nid_postfix(descriptor, address.data(), &size) == 0, "getsockname failed"); + const int client = newStream(2, false); + Require(connect_nid_postfix(client, address.data(), address.size()) == 0, "connect to the loopback listener failed"); + Require(Fails(connect_nid_postfix(client, destination.data(), destination.size()), ErrorConnected), "soconnect refuses a connected stream socket before tcp_usr_connect, so connect to 224.0.0.1 must fail with EISCONN (56)"); + Require(close_nid_postfix(client) == 0 && close_nid_postfix(descriptor) == 0, "close failed"); +} + } int main() { @@ -219,5 +285,6 @@ int main() { LongAddressesAreInvalid(28, Stream); LongAddressesAreInvalid(28, Datagram); LongMappedAddressesBindOnlyDualStackDatagramSockets(); + StreamSocketsRefuseMulticastAddresses(); return 0; } diff --git a/core/libs/tests/GuestSocketOnNonSocket.cpp b/core/libs/tests/GuestSocketOnNonSocket.cpp index 49c9d54ea0..481e201061 100644 --- a/core/libs/tests/GuestSocketOnNonSocket.cpp +++ b/core/libs/tests/GuestSocketOnNonSocket.cpp @@ -9,6 +9,7 @@ namespace { constexpr int ErrorBadDescriptor = 9; +constexpr int ErrorInvalid = 22; constexpr int ErrorNotSocket = 38; void Require(bool condition, const char* what) { @@ -50,10 +51,51 @@ void ClosedAndInvalidDescriptorsAreBad() { Require(Fails(connect_nid_postfix(1 << 20, address.data(), address.size()), ErrorBadDescriptor), "connect on a descriptor that was never open must fail with EBADF (9)"); } +void AddressAndTransferCallsOnAPipeAreNotSocketCalls() { + int ends[2]; + Require(pipe_nid_postfix(ends) == 0, "pipe failed"); + std::array address{16, 2}; + std::uint32_t size = address.size(); + char buffer[4]; + Require(Fails(getpeername_nid_postfix(ends[0], address.data(), &size), ErrorNotSocket), "getpeername on a pipe must fail with ENOTSOCK (38)"); + Require(Fails(sendto_nid_postfix(ends[1], "x", 1, 0, nullptr, 0), ErrorNotSocket), "sendto on a pipe must fail with ENOTSOCK (38)"); + Require(Fails(recvfrom_nid_postfix(ends[0], buffer, sizeof(buffer), 0, nullptr, nullptr), ErrorNotSocket), "recvfrom on a pipe must fail with ENOTSOCK (38)"); + Require(close_nid_postfix(ends[0]) == 0 && close_nid_postfix(ends[1]) == 0, "close failed"); +} + +void SocketCallsOnMinusOneAreBad() { + std::array address{16, 2}; + std::uint32_t size = address.size(); + char buffer[4]; + int option = 0; + std::uint32_t optionSize = sizeof(option); + Require(Fails(accept_nid_postfix(-1, nullptr, nullptr), ErrorBadDescriptor), "accept on -1 must fail with EBADF (9)"); + Require(Fails(shutdown_nid_postfix(-1, 1), ErrorBadDescriptor), "shutdown on -1 must fail with EBADF (9)"); + Require(Fails(shutdown_nid_postfix(-1, 3), ErrorBadDescriptor), "shutdown on -1 must report EBADF (9) before an invalid how"); + Require(Fails(getsockname_nid_postfix(-1, address.data(), &size), ErrorBadDescriptor), "getsockname on -1 must fail with EBADF (9)"); + Require(Fails(getpeername_nid_postfix(-1, address.data(), &size), ErrorBadDescriptor), "getpeername on -1 must fail with EBADF (9)"); + Require(Fails(getsockopt_nid_postfix(-1, 0xffff, 4, &option, &optionSize), ErrorBadDescriptor), "getsockopt on -1 must fail with EBADF (9)"); + Require(Fails(send_nid_postfix(-1, "x", 1, 0), ErrorBadDescriptor), "send on -1 must fail with EBADF (9)"); + Require(Fails(recv_nid_postfix(-1, buffer, sizeof(buffer), 0), ErrorBadDescriptor), "recv on -1 must fail with EBADF (9)"); + Require(Fails(sendto_nid_postfix(-1, "x", 1, 0, nullptr, 0), ErrorBadDescriptor), "sendto on -1 must fail with EBADF (9)"); + Require(Fails(recvfrom_nid_postfix(-1, buffer, sizeof(buffer), 0, nullptr, nullptr), ErrorBadDescriptor), "recvfrom on -1 must fail with EBADF (9)"); +} + +void ShutdownRejectsAnInvalidHow() { + const int datagram = socket_nid_postfix(2, 2, 0); + Require(datagram >= 0, "socket failed"); + for (const int how : {-1, 3}) + Require(Fails(shutdown_nid_postfix(datagram, how), ErrorInvalid), "shutdown with an invalid how must fail with EINVAL (22)"); + Require(close_nid_postfix(datagram) == 0, "close failed"); +} + } int main() { CallsOnAnOpenPipeAreNotSocketCalls(); ClosedAndInvalidDescriptorsAreBad(); + AddressAndTransferCallsOnAPipeAreNotSocketCalls(); + SocketCallsOnMinusOneAreBad(); + ShutdownRejectsAnInvalidHow(); return 0; } diff --git a/core/libs/tests/GuestSocketOptions.cpp b/core/libs/tests/GuestSocketOptions.cpp index e33092f87d..c0b35651fa 100644 --- a/core/libs/tests/GuestSocketOptions.cpp +++ b/core/libs/tests/GuestSocketOptions.cpp @@ -7,6 +7,12 @@ #include #include +#ifdef _WIN32 +#include +#else +#include +#endif + namespace { constexpr int Stream = 1; @@ -306,6 +312,8 @@ void IpLevelFollowsThePs5() { 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"); + Require(SetIp(datagram, IpTypeOfService, -1) && GetIp(datagram, IpTypeOfService) == 255, "IP_TOS of -1 reads back as 255"); + Require(SetFails(datagram, 0, IpTypeOfService, &byte, 1, ErrorInvalid), "a one-byte IP_TOS must fail with EINVAL (22)"); 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; @@ -404,6 +412,95 @@ void UnknownRequestsFail() { } +constexpr int ErrorFault = 14; +constexpr int SoReceiveTimeout = 0x1006; +constexpr int SoNoSigpipe2 = 0x800; + +void* MapPage(bool readable) { +#ifdef _WIN32 + void* page = VirtualAlloc(nullptr, 4096, MEM_COMMIT | MEM_RESERVE, readable ? PAGE_READONLY : PAGE_NOACCESS); + Require(page != nullptr, "VirtualAlloc failed"); +#else + void* page = mmap(nullptr, 4096, readable ? PROT_READ : PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(page != MAP_FAILED, "mmap failed"); +#endif + return page; +} + +void UnmapPage(void* page) { +#ifdef _WIN32 + VirtualFree(page, 0, MEM_RELEASE); +#else + munmap(page, 4096); +#endif +} + +bool GetFails(int descriptor, int level, int option, void* value, std::uint32_t* size, int error) { + return getsockopt_nid_postfix(descriptor, level, option, value, size) == -1 && *__error_nid_postfix() == error; +} + +void BadPointersFail() { + void* unreadable = MapPage(false); + void* readOnly = MapPage(true); + const int stream = socket_nid_postfix(2, Stream, 0); + const int datagram = socket_nid_postfix(2, Datagram, 0); + Require(stream >= 0 && datagram >= 0, "socket failed"); + for (void* bad : {reinterpret_cast(0x10), unreadable}) { + Require(SetFails(stream, SolSocket, SoKeepAlive, bad, sizeof(int), ErrorFault), "setsockopt with an unreadable value must fail with EFAULT (14)"); + Require(GetInt(stream, SoKeepAlive) == 0, "a faulting setsockopt must not change the option"); + Require(SetFails(stream, SolSocket, SoLinger, bad, sizeof(Linger), ErrorFault), "SO_LINGER with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, SolSocket, SoReceiveTimeout, bad, 16, ErrorFault), "SO_RCVTIMEO with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, SolSocket, SoNoSigpipe2, bad, sizeof(int), ErrorFault), "SO_NOSIGPIPE with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, SolSocket, SoSendBuffer, bad, sizeof(int), ErrorFault), "SO_SNDBUF with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, 6, 1, bad, sizeof(int), ErrorFault), "TCP_NODELAY with an unreadable value must fail with EFAULT"); + Require(SetFails(stream, 0, 4, bad, sizeof(int), ErrorFault), "IP_TTL with an unreadable value must fail with EFAULT"); + Require(SetFails(datagram, 0, 12, bad, 8, ErrorFault), "IP_ADD_MEMBERSHIP with an unreadable value must fail with EFAULT"); + Require(SetFails(datagram, 0, 9, bad, 4, ErrorFault), "IP_MULTICAST_IF with an unreadable value must fail with EFAULT"); + Require(SetFails(datagram, 0, 9, bad, 12, ErrorFault), "IP_MULTICAST_IF with an unreadable ip_mreqn must fail with EFAULT"); + Require(SetFails(datagram, 0, 10, bad, 1, ErrorFault), "IP_MULTICAST_TTL with an unreadable byte must fail with EFAULT"); + Require(SetFails(datagram, 0, 10, bad, 4, ErrorFault), "IP_MULTICAST_TTL with an unreadable int must fail with EFAULT"); + Require(SetFails(stream, SolSocket, SoKeepAlive, bad, 2, ErrorInvalid), "a short length is rejected before the value is read"); + Require(SetFails(stream, SolSocket, 0x7777, bad, sizeof(int), ErrorNoProtocolOption), "an unknown option is rejected before the value is read"); + Require(SetFails(stream, 99, SoKeepAlive, bad, sizeof(int), ErrorInvalid), "an unknown level is rejected before the value is read"); + Require(SetFails(-1, SolSocket, SoKeepAlive, bad, sizeof(int), ErrorBadDescriptor), "the descriptor is checked before the value is read"); + } + const int on = 1; + Require(SetFails(stream, SolSocket, SoKeepAlive, &on, 0x80000000u, ErrorInvalid), "a negative length must fail with EINVAL (22)"); + + for (void* bad : {reinterpret_cast(0x10), unreadable, readOnly}) { + std::uint32_t size = sizeof(int); + Require(GetFails(stream, SolSocket, SoType, bad, &size, ErrorFault) && size == sizeof(int), "getsockopt into an unwritable value must fail with EFAULT and keep the length"); + Require(GetFails(stream, SolSocket, SoLinger, bad, &size, ErrorFault), "SO_LINGER into an unwritable value must fail with EFAULT"); + size = 16; + Require(GetFails(stream, SolSocket, SoReceiveTimeout, bad, &size, ErrorFault), "SO_RCVTIMEO into an unwritable value must fail with EFAULT"); + size = sizeof(int); + Require(GetFails(stream, 6, 1, bad, &size, ErrorFault), "TCP_NODELAY into an unwritable value must fail with EFAULT"); + Require(GetFails(stream, 0, 4, bad, &size, ErrorFault), "IP_TTL into an unwritable value must fail with EFAULT"); + Require(GetFails(datagram, 0, 9, bad, &size, ErrorFault), "IP_MULTICAST_IF into an unwritable value must fail with EFAULT"); + Require(GetFails(stream, SolSocket, 0x7777, bad, &size, ErrorNoProtocolOption), "an unknown option is rejected before the value is written"); + Require(GetFails(-1, SolSocket, SoType, bad, &size, ErrorBadDescriptor), "the descriptor is checked before the value is written"); + std::uint32_t zero = 0; + Require(getsockopt_nid_postfix(stream, SolSocket, SoType, bad, &zero) == 0 && zero == 0, "a zero length never touches the value"); + } + + for (std::uint32_t* bad : {reinterpret_cast(0x10), static_cast(unreadable), static_cast(nullptr)}) { + int value = 0; + Require(GetFails(stream, SolSocket, SoType, &value, bad, ErrorFault), "getsockopt with an unreadable length must fail with EFAULT"); + Require(GetFails(-1, SolSocket, SoType, &value, bad, ErrorFault), "the length is read before the descriptor is checked"); + Require(GetFails(stream, SolSocket, 0x7777, &value, bad, ErrorFault), "the length is read before the option is checked"); + } + for (std::uint32_t* bad : {reinterpret_cast(0x10), static_cast(unreadable), static_cast(readOnly), static_cast(nullptr)}) { + Require(GetFails(stream, SolSocket, SoType, nullptr, bad, ErrorFault), "the final length store failing must give EFAULT"); + Require(GetFails(stream, SolSocket, 0x7777, nullptr, bad, ErrorNoProtocolOption), "an option error is reported before the length store"); + } + int value = 0; + std::uint32_t size = sizeof(int); + Require(getsockopt_nid_postfix(stream, SolSocket, SoType, &value, &size) == 0 && value == Stream && size == sizeof(int), "valid getsockopt still works"); + Require(close_nid_postfix(stream) == 0 && close_nid_postfix(datagram) == 0, "close failed"); + UnmapPage(unreadable); + UnmapPage(readOnly); +} + int main(int argc, char** argv) { if (argc > 1 && std::strcmp(argv[1], "multicast") == 0) { MulticastOnTheHost(); @@ -417,6 +514,7 @@ int main(int argc, char** argv) { LengthsFollowFreeBsd(); BuffersFollowThePs5(); NullValues(); + BadPointersFail(); IpLevelFollowsThePs5(); MulticastMembership(); UnknownRequestsFail(); diff --git a/core/libs/tests/GuestSocketPair.cpp b/core/libs/tests/GuestSocketPair.cpp index b760914fab..7fdfc4da27 100644 --- a/core/libs/tests/GuestSocketPair.cpp +++ b/core/libs/tests/GuestSocketPair.cpp @@ -159,6 +159,11 @@ static void CheckReceiveMessageFaults() { message = {readOnly + 128, 0, readOnlyVector, 1, nullptr, 0, -1}; Require(Queue(pair[0], pair[1], "gh") && recvmsg_nid_postfix(pair[1], &message, 0) == 2); Require(std::memcmp(data, "gh", 2) == 0 && message.nameLength == 0); + message = {nullptr, 0, &good, 1, nullptr, 0, -1}; + for (const int flag : {1, 4, 0x100, 0x40000, -1, Peek | 1, 0x80 | 4}) { + *__error_nid_postfix() = 0; + Require(recvmsg_nid_postfix(pair[1], &message, flag) == -1 && *__error_nid_postfix() == 45); + } Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0); Require(sceKernelMprotect(memory, page * 3, 3) == 0 && sceKernelMunmap(reinterpret_cast(memory), page * 3) == 0); diff --git a/core/libs/tests/GuestSocketPortZero.cpp b/core/libs/tests/GuestSocketPortZero.cpp index f450f75277..513cf5c7af 100644 --- a/core/libs/tests/GuestSocketPortZero.cpp +++ b/core/libs/tests/GuestSocketPortZero.cpp @@ -88,10 +88,57 @@ static void CheckStream(int family) { Require(close_nid_postfix(client) == 0 && close_nid_postfix(listener) == 0, "close"); } +static void CheckUnspecified(int type) { + const int server = socket_nid_postfix(28, type, 0); + if (server < 0 && *__error_nid_postfix() == 47) return; + Require(server >= 0, "server"); + auto address = Loopback(28, 28); + const int bound = bind_nid_postfix(server, address.data(), 28); + if (bound < 0 && *__error_nid_postfix() == 49) { + Require(close_nid_postfix(server) == 0, "close"); + return; + } + Require(bound == 0, "bind server"); + if (type == 1) Require(listen_nid_postfix(server, 1) == 0, "listen"); + std::uint32_t length = address.size(); + Require(getsockname_nid_postfix(server, address.data(), &length) == 0, "server name"); + auto unspecified = address; + std::memset(unspecified.data() + 8, 0, 16); + const int client = socket_nid_postfix(28, type, 0); + Require(client >= 0, "client"); + Address any{}; + any[0] = 28; + any[1] = 28; + Require(connect_nid_postfix(client, any.data(), 28) == -1 && *__error_nid_postfix() == 49, + "connect to port 0 of the unspecified AF_INET6 address must fail with EADDRNOTAVAIL (49)"); + Require(connect_nid_postfix(client, unspecified.data(), 28) == 0, + "connect to the unspecified AF_INET6 address must reach ::1 on the same port"); + Address peer{}; + length = peer.size(); + Require(getpeername_nid_postfix(client, peer.data(), &length) == 0 && length == 28 && peer[1] == 28 + && std::memcmp(peer.data() + 2, address.data() + 2, 2) == 0 && std::memcmp(peer.data() + 8, address.data() + 8, 16) == 0, + "the peer of a connect to the unspecified AF_INET6 address must be ::1 on the same port"); + if (type == 1) { + const int accepted = accept_nid_postfix(server, nullptr, nullptr); + Require(accepted >= 0, "accept"); + Require(send_nid_postfix(client, "x", 1, 0) == 1, "send"); + char byte = 0; + Require(recv_nid_postfix(accepted, &byte, 1, 0) == 1 && byte == 'x', "receive on the accepted socket"); + Require(close_nid_postfix(accepted) == 0, "close"); + } else { + Require(send_nid_postfix(client, "y", 1, 0) == 1, "send"); + char byte = 0; + Require(recv_nid_postfix(server, &byte, 1, 0) == 1 && byte == 'y', "receive on ::1"); + } + Require(close_nid_postfix(client) == 0 && close_nid_postfix(server) == 0, "close"); +} + int main() { CheckDatagram(2); CheckDatagram(28); CheckStream(2); CheckStream(28); + CheckUnspecified(1); + CheckUnspecified(2); return 0; } diff --git a/core/libs/tests/GuestSocketRights.cpp b/core/libs/tests/GuestSocketRights.cpp new file mode 100644 index 0000000000..2a03901039 --- /dev/null +++ b/core/libs/tests/GuestSocketRights.cpp @@ -0,0 +1,304 @@ +#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 +#include + +static void Require(bool value) { if (!value) std::abort(); } + +constexpr int Unix = 1; +constexpr int Stream = 1; +constexpr int Datagram = 2; +constexpr int SocketLevel = 0xffff; +constexpr int Rights = 1; +constexpr int ControlTruncated = 0x20; +constexpr int ControlCloseOnExec = 0x40000; +constexpr int Peek = 0x2; +constexpr int GetDescriptorFlags = 1; +constexpr std::size_t Header = 16; + +static std::size_t Space(std::size_t bytes) { return Header + ((bytes + 7u) & ~std::size_t{7}); } + +static std::array RightsControl(std::initializer_list descriptors, std::size_t& length) { + std::array control{}; + const auto bytes = descriptors.size() * sizeof(int); + const auto recordLength = static_cast(Header + bytes); + std::memcpy(control.data(), &recordLength, sizeof(recordLength)); + std::memcpy(control.data() + 4, &SocketLevel, sizeof(SocketLevel)); + std::memcpy(control.data() + 8, &Rights, sizeof(Rights)); + std::size_t at = Header; + for (const int descriptor : descriptors) { + std::memcpy(control.data() + at, &descriptor, sizeof(descriptor)); + at += sizeof(descriptor); + } + length = Space(bytes); + return control; +} + +static std::int64_t SendWith(int socket, const char* text, unsigned char* control, std::size_t controlLength) { + KernelIovec vector{const_cast(text), std::strlen(text)}; + GuestMsghdr message{}; + message.iov = &vector; + message.iovLength = 1; + message.control = control; + message.controlLength = static_cast(controlLength); + return sendmsg_nid_postfix(socket, &message, 0); +} + +struct Received { + std::int64_t bytes = -1; + std::array data{}; + std::array control{}; + std::uint32_t controlLength = 0; + int flags = 0; +}; + +static Received ReceiveWith(int socket, std::size_t controlCapacity, int flags = 0) { + Received result; + KernelIovec vector{result.data.data(), result.data.size()}; + GuestMsghdr message{}; + message.iov = &vector; + message.iovLength = 1; + message.control = controlCapacity != 0 ? result.control.data() : nullptr; + message.controlLength = static_cast(controlCapacity); + result.bytes = recvmsg_nid_postfix(socket, &message, flags); + result.controlLength = message.controlLength; + result.flags = message.flags; + return result; +} + +static void RequireRecord(const Received& received, std::size_t count) { + std::uint32_t length = 0; + int level = 0; + int type = 0; + std::memcpy(&length, received.control.data(), sizeof(length)); + std::memcpy(&level, received.control.data() + 4, sizeof(level)); + std::memcpy(&type, received.control.data() + 8, sizeof(type)); + Require(length == Header + count * sizeof(int) && level == SocketLevel && type == Rights); +} + +static int ReceivedDescriptor(const Received& received, std::size_t index) { + int descriptor = -1; + std::memcpy(&descriptor, received.control.data() + Header + index * sizeof(int), sizeof(descriptor)); + return descriptor; +} + +static void CheckPassedPipe(const int (&pair)[2]) { + int pipe[2] = {-1, -1}; + Require(pipe_nid_postfix(pipe) == 0); + std::size_t length = 0; + auto control = RightsControl({pipe[1]}, length); + Require(SendWith(pair[0], "pipe", control.data(), length) == 4); + Require(close_nid_postfix(pipe[1]) == 0); + const auto received = ReceiveWith(pair[1], Space(sizeof(int))); + Require(received.bytes == 4 && std::memcmp(received.data.data(), "pipe", 4) == 0); + Require(received.flags == 0 && received.controlLength == Space(sizeof(int))); + RequireRecord(received, 1); + const int writer = ReceivedDescriptor(received, 0); + Require(writer >= 0 && writer != pipe[0]); + Require(fcntl_nid_postfix(writer, GetDescriptorFlags) == 0); + Require(write_nid_postfix(writer, "moved", 5) == 5); + char text[8] = {}; + Require(read_nid_postfix(pipe[0], text, sizeof(text)) == 5 && std::memcmp(text, "moved", 5) == 0); + Require(close_nid_postfix(writer) == 0); + Require(read_nid_postfix(pipe[0], text, sizeof(text)) == 0); + Require(close_nid_postfix(pipe[0]) == 0); + + int closing[2] = {-1, -1}; + Require(pipe_nid_postfix(closing) == 0); + control = RightsControl({closing[1]}, length); + Require(SendWith(pair[0], "exec", control.data(), length) == 4); + Require(close_nid_postfix(closing[1]) == 0); + const auto marked = ReceiveWith(pair[1], Space(sizeof(int)), ControlCloseOnExec); + Require(marked.bytes == 4 && marked.controlLength == Space(sizeof(int))); + RequireRecord(marked, 1); + const int markedWriter = ReceivedDescriptor(marked, 0); + Require(fcntl_nid_postfix(markedWriter, GetDescriptorFlags) == 1); + Require(close_nid_postfix(markedWriter) == 0 && close_nid_postfix(closing[0]) == 0); +} + +static void CheckPassedSockets(const int (&pair)[2]) { + int first[2] = {-1, -1}; + int second[2] = {-1, -1}; + Require(socketpair_nid_postfix(Unix, Stream, 0, first) == 0); + Require(socketpair_nid_postfix(Unix, Datagram, 0, second) == 0); + std::size_t length = 0; + auto control = RightsControl({first[0], second[0]}, length); + Require(SendWith(pair[0], "two", control.data(), length) == 3); + Require(close_nid_postfix(first[0]) == 0 && close_nid_postfix(second[0]) == 0); + const auto received = ReceiveWith(pair[1], Space(2 * sizeof(int)), ControlCloseOnExec); + Require(received.bytes == 3 && received.flags == ControlCloseOnExec && received.controlLength == Space(2 * sizeof(int))); + RequireRecord(received, 2); + const int stream = ReceivedDescriptor(received, 0); + const int datagram = ReceivedDescriptor(received, 1); + Require(fcntl_nid_postfix(stream, GetDescriptorFlags) == 1 && fcntl_nid_postfix(datagram, GetDescriptorFlags) == 1); + char text[8] = {}; + Require(send_nid_postfix(stream, "over", 4, 0) == 4); + Require(recv_nid_postfix(first[1], text, sizeof(text), 0) == 4 && std::memcmp(text, "over", 4) == 0); + Require(send_nid_postfix(second[1], "back", 4, 0) == 4); + Require(recv_nid_postfix(datagram, text, sizeof(text), 0) == 4 && std::memcmp(text, "back", 4) == 0); + Require(close_nid_postfix(stream) == 0 && close_nid_postfix(datagram) == 0); + Require(close_nid_postfix(first[1]) == 0 && close_nid_postfix(second[1]) == 0); +} + +static std::size_t OpenHostDescriptors() { + std::size_t open = 0; + for (int descriptor = 0; descriptor < 4096; ++descriptor) { + if (::fcntl(descriptor, F_GETFD) != -1) ++open; + } + return open; +} + +static void CheckTruncatedControl(const int (&pair)[2]) { + int spare[2] = {-1, -1}; + Require(pipe_nid_postfix(spare) == 0); + std::size_t length = 0; + auto control = RightsControl({spare[0]}, length); + const auto before = OpenHostDescriptors(); + Require(SendWith(pair[0], "cut", control.data(), length) == 3); + const auto received = ReceiveWith(pair[1], Header); + Require(received.bytes == 3 && (received.flags & ControlTruncated) != 0 && received.controlLength == 0); + Require(OpenHostDescriptors() == before); + std::size_t twoLength = 0; + auto two = RightsControl({spare[0], spare[1]}, twoLength); + Require(SendWith(pair[0], "two", two.data(), twoLength) == 3); + const auto oneRoom = ReceiveWith(pair[1], Header + sizeof(int)); + Require(oneRoom.bytes == 3 && (oneRoom.flags & ControlTruncated) != 0 && oneRoom.controlLength == 0); + Require(OpenHostDescriptors() == before); + Require(SendWith(pair[0], "none", control.data(), length) == 4); + const auto plain = ReceiveWith(pair[1], 0); + Require(plain.bytes == 4 && plain.flags == 0 && plain.controlLength == 0); + Require(close_nid_postfix(spare[0]) == 0 && close_nid_postfix(spare[1]) == 0); +} + +static void CheckFaultedReceive(const int (&pair)[2]) { + int spare[2] = {-1, -1}; + Require(pipe_nid_postfix(spare) == 0); + std::size_t length = 0; + auto control = RightsControl({spare[0], spare[1]}, length); + void* page = ::mmap(nullptr, 4096, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + Require(page != MAP_FAILED); + const auto before = OpenHostDescriptors(); + Require(SendWith(pair[0], "ro", control.data(), length) == 2); + std::array received{}; + KernelIovec vector{page, 16}; + GuestMsghdr message{}; + message.iov = &vector; + message.iovLength = 1; + message.control = received.data(); + message.controlLength = static_cast(received.size()); + Require(recvmsg_nid_postfix(pair[1], &message, 0) == -1 && *__error_nid_postfix() == 14); + Require(OpenHostDescriptors() == before); + Require(::munmap(page, 4096) == 0); + Require(close_nid_postfix(spare[0]) == 0 && close_nid_postfix(spare[1]) == 0); +} + +static void CheckFaultedHeader(const int (&pair)[2]) { + int spare[2] = {-1, -1}; + Require(pipe_nid_postfix(spare) == 0); + std::size_t length = 0; + auto control = RightsControl({spare[0], spare[1]}, length); + auto* page = static_cast(::mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(page != MAP_FAILED); + std::array data{}; + std::array received{}; + auto* vector = reinterpret_cast(page + 64); + *vector = KernelIovec{data.data(), data.size()}; + auto* message = reinterpret_cast(page); + *message = GuestMsghdr{}; + message->iov = vector; + message->iovLength = 1; + message->control = received.data(); + message->controlLength = static_cast(received.size()); + Require(::mprotect(page, 4096, PROT_READ) == 0); + const auto before = OpenHostDescriptors(); + Require(SendWith(pair[0], "hdr", control.data(), length) == 3); + Require(recvmsg_nid_postfix(pair[1], message, 0) == -1 && *__error_nid_postfix() == 14); + Require(std::memcmp(data.data(), "hdr", 3) == 0); + Require(OpenHostDescriptors() == before); + Require(::munmap(page, 4096) == 0); + Require(close_nid_postfix(spare[0]) == 0 && close_nid_postfix(spare[1]) == 0); +} + +static void CheckFailedInstallation(const int (&pair)[2], bool routeFirst) { + int local[2] = {-1, -1}; + Require(socketpair_nid_postfix(Unix, Stream, 0, local) == 0); + const int route = ::socket(AF_ROUTE, SOCK_RAW, 0); + Require(route >= 0); + const auto before = OpenHostDescriptors(); + std::size_t length = 0; + auto control = routeFirst ? RightsControl({route, local[0]}, length) : RightsControl({local[0], route}, length); + Require(SendWith(pair[0], "own", control.data(), length) == 3); + Received result; + try { + result = ReceiveWith(pair[1], Space(2 * sizeof(int))); + } catch (const std::exception&) { + } + if (result.bytes == 3 && result.controlLength == Space(2 * sizeof(int))) { + Require(close_nid_postfix(ReceivedDescriptor(result, 0)) == 0 && close_nid_postfix(ReceivedDescriptor(result, 1)) == 0); + } + Require(OpenHostDescriptors() == before); + Require(::close(route) == 0); + Require(close_nid_postfix(local[0]) == 0 && close_nid_postfix(local[1]) == 0); +} + +static void CheckPlainPeek(const int (&pair)[2]) { + Require(SendWith(pair[0], "peek", nullptr, 0) == 4); + const auto peeked = ReceiveWith(pair[1], Space(sizeof(int)), Peek); + Require(peeked.bytes == 4 && std::memcmp(peeked.data.data(), "peek", 4) == 0 && peeked.controlLength == 0); + const auto read = ReceiveWith(pair[1], Space(sizeof(int))); + Require(read.bytes == 4 && std::memcmp(read.data.data(), "peek", 4) == 0 && read.controlLength == 0); +} + +static void CheckRefusedControl(const int (&pair)[2]) { + int closed[2] = {-1, -1}; + Require(pipe_nid_postfix(closed) == 0); + Require(close_nid_postfix(closed[0]) == 0 && close_nid_postfix(closed[1]) == 0); + std::size_t length = 0; + auto control = RightsControl({closed[0]}, length); + Require(SendWith(pair[0], "bad", control.data(), length) == -1 && *__error_nid_postfix() == 9); + int level = 6; + std::memcpy(control.data() + 4, &level, sizeof(level)); + Require(SendWith(pair[0], "bad", control.data(), length) == -1 && *__error_nid_postfix() == 22); + auto oversized = RightsControl({0}, length); + const std::uint32_t tooLong = 64; + std::memcpy(oversized.data(), &tooLong, sizeof(tooLong)); + Require(SendWith(pair[0], "bad", oversized.data(), length) == -1 && *__error_nid_postfix() == 22); +} + +static void CheckRefusedKqueue(const int (&pair)[2]) { + const int queue = kqueue_nid_postfix(); + Require(queue >= 0); + std::size_t length = 0; + auto control = RightsControl({queue}, length); + Require(SendWith(pair[0], "kq", control.data(), length) == -1 && *__error_nid_postfix() == 45); + Require(SendWith(pair[0], "next", nullptr, 0) == 4); + const auto received = ReceiveWith(pair[1], Space(sizeof(int))); + Require(received.bytes == 4 && std::memcmp(received.data.data(), "next", 4) == 0 && received.flags == 0 && received.controlLength == 0); + Require(close_nid_postfix(queue) == 0); +} + +int main() { + int pair[2] = {-1, -1}; + Require(socketpair_nid_postfix(Unix, Stream, 0, pair) == 0); + CheckPassedPipe(pair); + CheckPassedSockets(pair); + CheckTruncatedControl(pair); + CheckFaultedReceive(pair); + CheckFaultedHeader(pair); + CheckFailedInstallation(pair, false); + CheckFailedInstallation(pair, true); + CheckPlainPeek(pair); + CheckRefusedControl(pair); + CheckRefusedKqueue(pair); + Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0); +} diff --git a/core/libs/tests/GuestSpanProtected.cpp b/core/libs/tests/GuestSpanProtected.cpp new file mode 100644 index 0000000000..00b677dc72 --- /dev/null +++ b/core/libs/tests/GuestSpanProtected.cpp @@ -0,0 +1,92 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestSpanProtected check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + unsigned char* At(std::size_t count) { + Require(count <= page - 32); + return allocation + page * 2 - (std::max)(count, std::size_t{1}); + } +}; + +int main() { + Buffer textBuffer, setBuffer; + for (unsigned selected = 0; selected < 256; ++selected) { + std::string members; + std::array present{}; + for (unsigned value = 1; value < 256; ++value) { + if ((value + selected) % 7 == 0 || value == selected) { + members.push_back(static_cast(value)); + present[value] = true; + } + } + if (selected == 0) { members.clear(); present.fill(false); } + auto* set = reinterpret_cast(setBuffer.At(members.size() + 1)); + std::memcpy(set, members.c_str(), members.size() + 1); + for (std::size_t length : std::array{0, 1, 7, 15, 16, 31, 32, 63, 127}) { + std::string input; + for (std::size_t i = 0; i < length; ++i) input.push_back(static_cast(1 + (i * 37 + selected) % 255)); + auto* text = reinterpret_cast(textBuffer.At(length + 1)); + std::memcpy(text, input.c_str(), length + 1); + std::size_t accepted = 0, rejected = 0; + while (accepted < length && present[static_cast(input[accepted])]) ++accepted; + while (rejected < length && !present[static_cast(input[rejected])]) ++rejected; + Require(strspn_nid_postfix(text, set) == accepted); + Require(strcspn_nid_postfix(text, set) == rejected); + Require(std::memcmp(text, input.c_str(), length + 1) == 0); + Require(std::memcmp(set, members.c_str(), members.size() + 1) == 0); + } + } +} diff --git a/core/libs/tests/GuestStrcmpBoundary.cpp b/core/libs/tests/GuestStrcmpBoundary.cpp new file mode 100644 index 0000000000..876031921a --- /dev/null +++ b/core/libs/tests/GuestStrcmpBoundary.cpp @@ -0,0 +1,99 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestStrcmpBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +static int Sign(int value) { + return (value > 0) - (value < 0); +} + +int main() { + Buffer first, second; + for (std::size_t count = 0; count <= 128; ++count) { + auto* left = first.At(count + 1); + auto* right = second.At(count + 1); + for (std::size_t index = 0; index < count; ++index) left[index] = static_cast(1 + (index * 73 + count) % 255); + left[count] = 0; + const std::vector saved(left, left + count + 1); + for (std::size_t position = 0; position <= count; ++position) { + for (unsigned char value : {0, 1, 127, 128, 255}) { + if (position == count && value != 0) continue; + std::copy(saved.begin(), saved.end(), right); + right[position] = value; + int expected = 0; + for (std::size_t index = 0; index <= count; ++index) { + if (left[index] != right[index]) { expected = static_cast(left[index]) - right[index]; break; } + if (left[index] == 0) break; + } + const std::vector other(right, right + count + 1); + Require(Sign(strcmp_nid_postfix(reinterpret_cast(left), reinterpret_cast(right))) == Sign(expected)); + Require(std::equal(saved.begin(), saved.end(), left)); + Require(std::equal(other.begin(), other.end(), right)); + } + } + } +} diff --git a/core/libs/tests/GuestStrcollBoundary.cpp b/core/libs/tests/GuestStrcollBoundary.cpp new file mode 100644 index 0000000000..b51eddef21 --- /dev/null +++ b/core/libs/tests/GuestStrcollBoundary.cpp @@ -0,0 +1,91 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestStrcollBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +static int Sign(int value) { + return (value > 0) - (value < 0); +} + +int main() { + Buffer first, second; + for (std::size_t count = 0; count <= 128; ++count) { + auto* left = first.At(count + 1); + auto* right = second.At(count + 1); + for (std::size_t index = 0; index < count; ++index) left[index] = static_cast(1 + (index * 73 + count) % 255); + left[count] = 0; + const std::vector saved(left, left + count + 1); + for (std::size_t position = 0; position <= count; ++position) { + for (unsigned char value : {0, 1, 127, 128, 255}) { + if (position == count && value != 0) continue; + std::copy(saved.begin(), saved.end(), right); + right[position] = value; + int expected = 0; + for (std::size_t index = 0; index <= count; ++index) { + if (left[index] != right[index]) { expected = static_cast(left[index]) - right[index]; break; } + if (left[index] == 0) break; + } + const std::vector other(right, right + count + 1); + Require(Sign(strcoll_nid_postfix(reinterpret_cast(left), reinterpret_cast(right))) == Sign(expected)); + Require(std::equal(saved.begin(), saved.end(), left)); + Require(std::equal(other.begin(), other.end(), right)); + } + } + } +} diff --git a/core/libs/tests/GuestStrcpyExtent.cpp b/core/libs/tests/GuestStrcpyExtent.cpp new file mode 100644 index 0000000000..c7527f9c58 --- /dev/null +++ b/core/libs/tests/GuestStrcpyExtent.cpp @@ -0,0 +1,84 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestStrcpyExtent check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +int main() { + Buffer input, output; + for (std::size_t count = 0; count <= 192; ++count) { + auto* source = input.At(count + 1); + auto* destination = output.At(count + 1); + for (std::size_t index = 0; index < count; ++index) source[index] = static_cast(1 + (index * 97 + count) % 255); + source[count] = 0; + const std::string saved(source, count + 1); + std::memset(destination - 16, 0xa5, count + 17); + Require(strcpy_nid_postfix(destination, source) == destination); + Require(std::equal(saved.begin(), saved.end(), destination)); + Require(std::equal(saved.begin(), saved.end(), source)); + Prefix(reinterpret_cast(destination)); + } +} diff --git a/core/libs/tests/GuestStreamClose.cpp b/core/libs/tests/GuestStreamClose.cpp new file mode 100644 index 0000000000..bfd1f21311 --- /dev/null +++ b/core/libs/tests/GuestStreamClose.cpp @@ -0,0 +1,183 @@ +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/File/include/File.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 +#include +#include +#include +#include +#include +#include + +static void Check(bool condition, int line) { + if (!condition) { + std::fprintf(stderr, "stream close check failed at line %d\n", line); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) + +static constexpr const char* Appended = "guest_stream_close_appended.txt"; +static constexpr const char* Reused = "guest_stream_close_reused.txt"; +static constexpr const char* Racing = "guest_stream_close_racing.bin"; +static constexpr int ReadWrite = 0x0002; +static constexpr int CreateReadWrite = 0x0202; +static constexpr std::int64_t BadDescriptor = static_cast(0x80020009u); +static constexpr int Append = 0x0008; +static constexpr int CreateWriteAppend = 0x0209; +static constexpr int Truncate = 0x0400; +static constexpr int CloseOnExec = 0x00100000; +static constexpr int Directory = 0x00020000; +static constexpr int GetDescriptorFlags = 1; +static constexpr int GetStatusFlags = 3; +static constexpr int FromStart = 0; + +static void Store(const char* path, const std::string& contents) { + std::ofstream file(path, std::ios::binary | std::ios::trunc); + file << contents; +} + +static std::string Contents(const char* path) { + std::ifstream file(path, std::ios::binary); + return std::string(std::istreambuf_iterator(file), std::istreambuf_iterator()); +} + +static FileStream* Reopen(int closed, const char* mode) { + auto* stream = fopen_nid_postfix(Reused, mode); + Require(stream != nullptr); + Require(fileno_nid_postfix(stream) == closed); + return stream; +} + +static void CloseWaitsForPositionedWrites() { + const std::vector data(std::size_t{8} << 20, 'p'); + for (int round = 0; round < 16; ++round) { + const int descriptor = sceKernelOpen(Racing, CreateReadWrite | Truncate, 0644); + Require(descriptor >= 0); + auto* stream = fdopen_nid_postfix(descriptor, "r+"); + Require(stream != nullptr); + std::atomic started = false; + std::int64_t written = 0; + std::thread writer([&] { + started = true; + written = sceKernelPwrite(descriptor, data.data(), data.size(), 0); + }); + while (!started) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(round % 4)); + if (round % 2 != 0) stream = freopen_nid_postfix(Reused, "r", stream); + Require(stream != nullptr && fclose_nid_postfix(stream) == 0); + writer.join(); + Require(written == static_cast(data.size()) || written == BadDescriptor); + } + std::remove(Racing); +} + +static void CloseWaitsForALockedStream() { + const int descriptor = sceKernelOpen(Racing, CreateReadWrite | Truncate, 0644); + Require(descriptor >= 0); + auto* stream = fdopen_nid_postfix(descriptor, "r+"); + Require(stream != nullptr); + std::atomic locked = false; + std::int64_t position = -1; + std::thread holder([&] { + flockfile_nid_postfix(stream); + locked = true; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + position = sceKernelLseek(descriptor, 0, FromStart); + funlockfile_nid_postfix(stream); + }); + while (!locked) std::this_thread::yield(); + Require(fclose_nid_postfix(stream) == 0); + holder.join(); + Require(position == 0); + std::remove(Racing); +} + +static void ReopenWhileStreamsAreCreated() { + std::atomic done = false; + std::thread opener([&] { + while (!done) { + auto* other = fopen_nid_postfix(Appended, "r"); + Require(other != nullptr && fclose_nid_postfix(other) == 0); + } + }); + auto* stream = fopen_nid_postfix(Reused, "r"); + Require(stream != nullptr); + for (int round = 0; round < 500; ++round) { + stream = freopen_nid_postfix(Reused, "r", stream); + Require(stream != nullptr); + } + done = true; + opener.join(); + Require(fclose_nid_postfix(stream) == 0); +} + +int main() { + Store(Reused, "0123456789"); + const int appending = sceKernelOpen(Appended, CreateWriteAppend | Truncate | CloseOnExec, 0644); + Require(appending >= 0); + Require((fcntl_nid_postfix(appending, GetStatusFlags) & Append) != 0); + Require(fcntl_nid_postfix(appending, GetDescriptorFlags) == 1); + auto* stream = fdopen_nid_postfix(appending, "a"); + Require(stream != nullptr); + Require(fclose_nid_postfix(stream) == 0); + + stream = Reopen(appending, "r+"); + Require(fcntl_nid_postfix(appending, GetStatusFlags) == ReadWrite); + Require(fcntl_nid_postfix(appending, GetDescriptorFlags) == 0); + Require(sceKernelPwrite(appending, "AB", 2, 2) == 2); + Require(sceKernelLseek(appending, 4, FromStart) == 4); + Require(sceKernelWrite(appending, "CD", 2) == 2); + Require(fclose_nid_postfix(stream) == 0); + Require(Contents(Reused) == "01ABCD6789"); + + const int random = sceKernelOpen("/dev/urandom", 0, 0); + Require(random >= 0); + stream = fdopen_nid_postfix(random, "r"); + Require(stream != nullptr); + Require(fclose_nid_postfix(stream) == 0); + + stream = Reopen(random, "r"); + char read[16]{}; + Require(sceKernelRead(random, read, sizeof(read)) == 10); + Require(std::string(read, 10) == "01ABCD6789"); + Require(fclose_nid_postfix(stream) == 0); + + const int directory = sceKernelOpen(".", Directory, 0); + Require(directory >= 0); + stream = fdopen_nid_postfix(directory, "r"); + Require(stream != nullptr); + Require(fclose_nid_postfix(stream) == 0); + + stream = Reopen(directory, "r"); + Require(sceKernelRead(directory, read, sizeof(read)) == 10); + Require(std::string(read, 10) == "01ABCD6789"); + Require(fclose_nid_postfix(stream) == 0); + + const int replaced = sceKernelOpen(Appended, CreateWriteAppend | CloseOnExec, 0644); + Require(replaced >= 0); + stream = fdopen_nid_postfix(replaced, "a"); + Require(stream != nullptr); + stream = freopen_nid_postfix(Reused, "r+", stream); + Require(stream != nullptr); + Require(fileno_nid_postfix(stream) == replaced); + Require(fcntl_nid_postfix(replaced, GetStatusFlags) == ReadWrite); + Require(fcntl_nid_postfix(replaced, GetDescriptorFlags) == 0); + Require(sceKernelPwrite(replaced, "EF", 2, 8) == 2); + Require(fclose_nid_postfix(stream) == 0); + Require(Contents(Reused) == "01ABCD67EF"); + + CloseWaitsForPositionedWrites(); + Require(Contents(Reused) == "01ABCD67EF"); + CloseWaitsForALockedStream(); + ReopenWhileStreamsAreCreated(); + + std::remove(Appended); + std::remove(Reused); +} diff --git a/core/libs/tests/GuestStrings.cpp b/core/libs/tests/GuestStrings.cpp index 99ab8fcedf..f1b18a34e6 100644 --- a/core/libs/tests/GuestStrings.cpp +++ b/core/libs/tests/GuestStrings.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include static void Require(bool condition) { @@ -74,7 +75,7 @@ static void CheckStandardStrings() { 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); + Require(bcmp_nid_postfix(bytes, same, 6) == 1 && bcmp_nid_postfix(same, bytes, 6) == 1 && 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); } @@ -440,6 +441,31 @@ int main() { end = stpcpy_nid_postfix(end, "cd"); Require(end == chained + 4 && std::strcmp(chained, "abcd") == 0); Require(stpcpy_nid_postfix(end, "") == end && chained[5] == 'z'); + char shortBuffer[8]; + std::memset(shortBuffer, 'z', sizeof(shortBuffer)); + char* shortEnd = stpncpy_nid_postfix(shortBuffer, "ab", 5); + Require(shortEnd == shortBuffer + 2); + Require(shortBuffer[0] == 'a' && shortBuffer[1] == 'b'); + Require(shortBuffer[2] == '\0' && shortBuffer[3] == '\0' && shortBuffer[4] == '\0'); + Require(shortBuffer[5] == 'z' && shortBuffer[6] == 'z' && shortBuffer[7] == 'z'); + char equalBuffer[8]; + std::memset(equalBuffer, 'z', sizeof(equalBuffer)); + char* equalEnd = stpncpy_nid_postfix(equalBuffer, "abcde", 5); + Require(equalEnd == equalBuffer + 5); + Require(std::memcmp(equalBuffer, "abcde", 5) == 0); + Require(equalBuffer[5] == 'z' && equalBuffer[6] == 'z' && equalBuffer[7] == 'z'); + char longerBuffer[8]; + std::memset(longerBuffer, 'z', sizeof(longerBuffer)); + char* longerEnd = stpncpy_nid_postfix(longerBuffer, "abcdefgh", 3); + Require(longerEnd == longerBuffer + 3); + Require(std::memcmp(longerBuffer, "abc", 3) == 0); + Require(longerBuffer[3] == 'z' && longerBuffer[4] == 'z' && longerBuffer[5] == 'z' && longerBuffer[6] == 'z' && longerBuffer[7] == 'z'); + char zeroBuffer[8]; + std::memset(zeroBuffer, 'z', sizeof(zeroBuffer)); + const char originalZero[8] = {'z', 'z', 'z', 'z', 'z', 'z', 'z', 'z'}; + char* zeroEnd = stpncpy_nid_postfix(zeroBuffer, "test", 0); + Require(zeroEnd == zeroBuffer); + Require(std::memcmp(zeroBuffer, originalZero, sizeof(zeroBuffer)) == 0); Require(strncat_nid_postfix(buffer, "xyz", 2) == buffer); Require(std::strcmp(buffer, "xy") == 0); char transformed[8] = "zzzzzzz"; @@ -475,6 +501,21 @@ int main() { Require(inetAton("1.2.3.4 trailing", "1.2.3.4")); Require(inetAton("1.2.3.4\n", "1.2.3.4")); Require(__inet_aton_nid_postfix("1.2.3.4", nullptr) == 1); + bool atonNullThrew = false; + try { + unsigned char address[4] = {0xA5, 0xA5, 0xA5, 0xA5}; + __inet_aton_nid_postfix(nullptr, address); + } catch (const std::invalid_argument&) { + atonNullThrew = true; + } + Require(atonNullThrew); + bool addrNullThrew = false; + try { + __inet_addr_nid_postfix(nullptr); + } catch (const std::invalid_argument&) { + addrNullThrew = true; + } + Require(addrNullThrew); for (const char* invalid : {"", " 1.2.3.4", "1.2.3.4.5", "256.1.1.1", "1.2.3.256", "1.2.65536", "08", "1..2", "a.b.c.d", "1.2.3.4x", "0x", "1.2.3.", "-1"}) { unsigned char address[4] = {0xA5, 0xA5, 0xA5, 0xA5}; @@ -579,6 +620,21 @@ int main() { const unsigned char highB[] = {0x00, 0x7f}; Require(timingsafe_memcmp_nid_postfix(highA, highB, 2) > 0); Require(timingsafe_memcmp_nid_postfix(highB, highA, 2) < 0); + const char safeD[] = "xbcdef"; + const char safeMid[] = "abxdef"; + Require(timingsafe_bcmp_nid_postfix(safeA, safeC, 6) == 0); + Require(timingsafe_bcmp_nid_postfix(safeA, safeB, 6) != 0); + Require(timingsafe_bcmp_nid_postfix(safeB, safeA, 6) != 0); + Require(timingsafe_bcmp_nid_postfix(safeA, safeD, 6) != 0); + Require(timingsafe_bcmp_nid_postfix(safeD, safeA, 6) != 0); + Require(timingsafe_bcmp_nid_postfix(safeA, safeMid, 6) != 0); + Require(timingsafe_bcmp_nid_postfix(safeMid, safeA, 6) != 0); + Require(timingsafe_bcmp_nid_postfix(safeA, safeB, 5) == 0); + Require(timingsafe_bcmp_nid_postfix(safeA, safeB, 0) == 0); + Require(timingsafe_bcmp_nid_postfix(highA, highB, 2) != 0); + Require(timingsafe_bcmp_nid_postfix(highB, highA, 2) != 0); + Require(timingsafe_bcmp_nid_postfix(safeA, safeB, 6) == 1); + Require(timingsafe_bcmp_nid_postfix(highA, highB, 2) == 1); CheckSecureCopySizeLimits(); CheckSecureStringSizeLimits(); return CheckMemcpyOverlap() ? 0 : 1; diff --git a/core/libs/tests/GuestStrncmpBoundary.cpp b/core/libs/tests/GuestStrncmpBoundary.cpp new file mode 100644 index 0000000000..5960fd7099 --- /dev/null +++ b/core/libs/tests/GuestStrncmpBoundary.cpp @@ -0,0 +1,99 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestStrncmpBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +static int Sign(int value) { + return (value > 0) - (value < 0); +} + +int main() { + Buffer first, second; + for (std::size_t count = 0; count <= 128; ++count) { + auto* left = first.At(count); + auto* right = second.At(count); + const auto extent = (std::max)(count, std::size_t{1}); + for (int mode = 0; mode < 7; ++mode) { + for (std::size_t index = 0; index < extent; ++index) left[index] = right[index] = static_cast(1 + (index * 131 + count) % 255); + if (count != 0) { + const std::size_t position = mode % 3 == 0 ? 0 : mode % 3 == 1 ? count / 2 : count - 1; + if (mode < 3) { left[position] = 255; right[position] = 1; } + if (mode >= 3 && mode < 6) { left[position] = right[position] = 0; } + if (mode != 6 && position + 1 < count) { left[position + 1] = 1; right[position + 1] = 255; } + } + int expected = 0; + for (std::size_t index = 0; index < count; ++index) { + if (left[index] != right[index]) { expected = static_cast(left[index]) - right[index]; break; } + if (left[index] == 0) break; + } + const std::vector a(left, left + extent), b(right, right + extent); + Require(Sign(strncmp_nid_postfix(reinterpret_cast(left), reinterpret_cast(right), count)) == Sign(expected)); + Require(Sign(strncmp_nid_postfix(reinterpret_cast(right), reinterpret_cast(left), count)) == -Sign(expected)); + Require(std::equal(a.begin(), a.end(), left) && std::equal(b.begin(), b.end(), right)); + } + } +} diff --git a/core/libs/tests/GuestStrnstrBoundary.cpp b/core/libs/tests/GuestStrnstrBoundary.cpp new file mode 100644 index 0000000000..4455cc05b6 --- /dev/null +++ b/core/libs/tests/GuestStrnstrBoundary.cpp @@ -0,0 +1,100 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestStrnstrBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +int main() { + Buffer input, pattern; + for (std::size_t length = 0; length <= 128; ++length) { + auto* source = input.At(length); + const auto extent = (std::max)(length, std::size_t{1}); + for (std::size_t size = 0; size <= 17; ++size) { + auto* needle = pattern.At(size + 1); + std::fill(needle, needle + size, 'a'); + if (size) needle[size - 1] = 'b'; + needle[size] = 0; + const std::string wanted(needle, size); + for (int mode = 0; mode < 4; ++mode) { + std::fill(source, source + extent, 'a'); + if (size && size <= length) { + const auto position = mode == 0 ? 0 : length - size; + std::copy(wanted.begin(), wanted.end(), source + position); + if (mode == 2) source[position + size - 1] = 'c'; + } + if (mode == 3 && length) source[length / 2] = 0; + const std::string saved(source, extent); + const auto zero = saved.find(char{0}); + const auto available = (std::min)(length, zero == std::string::npos ? length : zero); + const auto found = saved.substr(0, available).find(wanted); + const auto* expected = found == std::string::npos ? nullptr : source + found; + Require(strnstr_nid_postfix(source, needle, length) == expected); + Require(std::equal(saved.begin(), saved.end(), source)); + Require(std::equal(wanted.begin(), wanted.end(), needle) && needle[size] == 0); + } + } + } +} diff --git a/core/libs/tests/GuestStrxfrmBoundary.cpp b/core/libs/tests/GuestStrxfrmBoundary.cpp new file mode 100644 index 0000000000..c68765430b --- /dev/null +++ b/core/libs/tests/GuestStrxfrmBoundary.cpp @@ -0,0 +1,88 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestStrxfrmBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +int main() { + Buffer input, output; + for (std::size_t length = 0; length <= 128; ++length) { + auto* source = input.At(length + 1); + for (std::size_t i = 0; i < length; ++i) source[i] = static_cast(1 + (i * 73 + length) % 255); + source[length] = 0; + const std::vector saved(source, source + length + 1); + for (std::size_t capacity : {std::size_t{0}, std::size_t{1}, length, length + 1, length + 7}) { + auto* destination = output.At(capacity); + const auto extent = (std::max)(capacity, std::size_t{1}); + std::memset(destination - 8, 0xa5, (extent + 8) * sizeof(char)); + Require(strxfrm_nid_postfix(destination, source, capacity) == length); + if (length < capacity) Require(std::equal(saved.begin(), saved.end(), destination)); + if (capacity == 0) Require(destination[0] == static_cast(static_cast(0xa5))); + Prefix(reinterpret_cast(destination)); + Require(std::equal(saved.begin(), saved.end(), source)); + } + } +} diff --git a/core/libs/tests/GuestSysUtilNotification.cpp b/core/libs/tests/GuestSysUtilNotification.cpp index aa60f22ae7..37afa71a77 100644 --- a/core/libs/tests/GuestSysUtilNotification.cpp +++ b/core/libs/tests/GuestSysUtilNotification.cpp @@ -90,6 +90,8 @@ int main() { Require(hello == 0, "a text notification was not sent"); Require(empty == 0, "a null message was not sent as an empty text"); Require(truncated == 0, "a long message was not sent"); + Require(sceSysUtilSendSystemNotificationWithText(0, reinterpret_cast(0x10)) == static_cast(0x8002000Eu), "an unreadable message pointer did not return EFAULT"); + Require(sceSysUtilSendSystemNotificationWithText(0, reinterpret_cast(~std::uintptr_t{0xfff})) == static_cast(0x8002000Eu), "an unreadable message pointer did not return EFAULT"); const std::string expected = "[notification] hello\n[notification] \n[notification] " + longText.substr(0, 179) + "\n"; if (output != expected) { std::fprintf(stderr, "notification output was:\n%s", output.c_str()); diff --git a/core/libs/tests/GuestSystemConfiguration.cpp b/core/libs/tests/GuestSystemConfiguration.cpp index 35db1a61ad..ae00f17297 100644 --- a/core/libs/tests/GuestSystemConfiguration.cpp +++ b/core/libs/tests/GuestSystemConfiguration.cpp @@ -352,11 +352,20 @@ static void CheckProcessListSysctl() { const int mib[] = {1, 14, 8}; std::size_t length = 0; Require(sysctl_nid_postfix(mib, 3, nullptr, &length, nullptr, 0) == 0); + const std::size_t size = length; + *__error_nid_postfix() = 13; + length = 0; + Require(sysctlbyname_nid_postfix("kern.proc.proc", nullptr, &length, nullptr, 0) == 0 && length == size && *__error_nid_postfix() == 13); unsigned char buffer[1100]; std::memset(buffer, 0xaa, sizeof(buffer)); length = sizeof(buffer); Require(sysctl_nid_postfix(mib, 3, buffer, &length, nullptr, 0) == 0 && length == 1088); + unsigned char named[1100]; + std::memset(named, 0xaa, sizeof(named)); + length = sizeof(named); + Require(sysctlbyname_nid_postfix("kern.proc.proc", named, &length, nullptr, 0) == 0 && length == 1088 && *__error_nid_postfix() == 13); + Require(std::memcmp(named, buffer, sizeof(buffer)) == 0); std::int32_t structSize = 0; std::int32_t pid = 0; std::memcpy(&structSize, buffer, sizeof(structSize)); @@ -377,6 +386,13 @@ static void CheckProcessListSysctl() { length = sizeof(buffer); Require(sysctl_nid_postfix(mib, 3, buffer, &length, buffer, sizeof(buffer)) == -1 && *__error_nid_postfix() == 1); + + *__error_nid_postfix() = 13; + length = 100; + Require(sysctlbyname_nid_postfix("kern.proc.proc", buffer, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 12 && length == 100); + *__error_nid_postfix() = 13; + length = sizeof(buffer); + Require(sysctlbyname_nid_postfix("kern.proc.proc", buffer, &length, buffer, sizeof(buffer)) == -1 && *__error_nid_postfix() == 1); } struct SystemString { int mib[2]; @@ -775,15 +791,16 @@ static void CheckOvercommitSysctl() { length = 2; Require(sysctlbyname_nid_postfix("vm.overcommit", bytes, &length, nullptr, 0) == -1 && *__error_nid_postfix() == 12); Require(length == 2 && bytes[2] == 0xaa && bytes[3] == 0xaa); - bool threw = false; + int written = 1; + value = -1; length = sizeof(value); - try { - sysctlbyname_nid_postfix("vm.overcommit", &value, &length, &value, sizeof(value)); - - } catch (const std::runtime_error&) { - threw = true; - } - Require(threw); + *__error_nid_postfix() = 13; + Require(sysctlbyname_nid_postfix("vm.overcommit", &value, &length, &written, sizeof(written)) == -1 && *__error_nid_postfix() == 1); + Require(value == -1 && length == sizeof(value)); + Require(sysctlbyname_nid_postfix("vm.overcommit", nullptr, nullptr, &written, sizeof(written)) == -1 && *__error_nid_postfix() == 1); + Require(sysctlbyname_nid_postfix("vm.overcommit", nullptr, nullptr, &written, 0) == -1 && *__error_nid_postfix() == 1); + Require(sysctlbyname_nid_postfix("vm.overcommit", nullptr, nullptr, reinterpret_cast(0x10), 1) == -1 && *__error_nid_postfix() == 1); + Require(sysctlbyname_nid_postfix("vm.overcommit", &value, &length, nullptr, 0) == 0 && value == 0); } static void CheckSdkVersionSysctl() { *__error_nid_postfix() = 13; @@ -970,6 +987,7 @@ static void CheckNameToMib() { {"kern.boottime", {1, 21}, 2}, {"kern.rng_pseudo", {1, 50}, 2}, {"kern.proc.pathname", {1, 14, 12}, 3}, + {"kern.proc.proc", {1, 14, 8}, 3}, }; *__error_nid_postfix() = 13; for (const auto& entry : expected) { diff --git a/core/libs/tests/GuestSystemFont.cpp b/core/libs/tests/GuestSystemFont.cpp index 63988f6972..f847fdaa5e 100644 --- a/core/libs/tests/GuestSystemFont.cpp +++ b/core/libs/tests/GuestSystemFont.cpp @@ -497,7 +497,10 @@ int main() { 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); + Require(sceFontRenderCharGlyphImageHorizontal(mixed, 'A', &measuring, 10.0f, 100.0f, &measured, &measuredResult) == SCE_FONT_OK); + Require(measured.Horizontal.advance == 60.0f); + Require(std::any_of(untouched.begin(), untouched.begin() + 128, [](std::uint8_t value) { return value != 0; })); + Require(std::all_of(untouched.begin() + 128, untouched.end(), [](std::uint8_t value) { return value == 0; })); sceFontRenderSurfaceInit(&measuring, nullptr, 0, 1, 0, 31); Require(sceFontRenderCharGlyphImageHorizontal(mixed, 'A', &measuring, 10.0f, 100.0f, &measured, &measuredResult) == SCE_FONT_ERROR_NO_SUPPORT_SURFACE); diff --git a/core/libs/tests/GuestSystemGesture.cpp b/core/libs/tests/GuestSystemGesture.cpp index 09829b0cad..6b41eb618e 100644 --- a/core/libs/tests/GuestSystemGesture.cpp +++ b/core/libs/tests/GuestSystemGesture.cpp @@ -169,6 +169,9 @@ static void TestPrimitiveTouchEvents(std::int32_t handle, int invalidArgument, i Require(UpdateWithReports(handle + 1, &pair, 1) == invalidHandle); Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(handle, nullptr) == invalidArgument); + SystemGestureTouchPadData withoutBuffer{}; + withoutBuffer.report_number = 1; + Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(handle, &withoutBuffer) == invalidArgument); Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == 2); const std::array sequence{TouchReport(7000, {{3, 110, 200}}), TouchReport(8000, {{3, 130, 210}})}; @@ -193,6 +196,58 @@ static void TestPrimitiveTouchEvents(std::int32_t handle, int invalidArgument, i Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == 1); } +static void TestCreateTouchRecognizer(std::int32_t handle, int invalidArgument, int invalidHandle) { + constexpr std::int32_t gestureType = 5; + const SystemGestureRectangle area{1.0f, 2.0f, 3.0f, 4.0f, {}}; + SystemGestureTouchRecognizer recognizer{}; + std::memset(&recognizer, 0x5a, sizeof(recognizer)); + SystemGestureTouchRecognizer untouched = recognizer; + Require(sceSystemGestureCreateTouchRecognizer(handle + 1, &recognizer, gestureType, &area, nullptr) == invalidHandle); + Require(std::memcmp(&recognizer, &untouched, sizeof(recognizer)) == 0); + Require(sceSystemGestureCreateTouchRecognizer(handle, nullptr, gestureType, &area, nullptr) == invalidArgument); + Require(sceSystemGestureCreateTouchRecognizer(handle + 1, nullptr, gestureType, &area, nullptr) == invalidHandle); + Require(std::memcmp(&recognizer, &untouched, sizeof(recognizer)) == 0); + Require(sceSystemGestureGetTouchEventsCount(handle, &recognizer) == 0); + Require(sceSystemGestureGetTouchEventsCount(handle + 1, &recognizer) == invalidHandle); +} + +static void TestNotInitialized(std::int32_t handle) { + constexpr int notInitialized = static_cast(0x80890003); + SystemGestureTouchRecognizer recognizer{}; + SystemGestureRectangle rectangle{}; + SystemGestureTouchEvent event{}; + SystemGesturePrimitiveTouchEvent primitive{}; + SystemGestureTouchRecognizerInformation information{}; + SystemGestureTouchPadData input{}; + std::uint32_t count = 7; + Require(sceSystemGestureFinalizePrimitiveTouchRecognizer() == 0); + Require(sceSystemGestureOpen(0, nullptr) == notInitialized); + Require(sceSystemGestureOpen(1, nullptr) == notInitialized); + Require(sceSystemGestureClose(handle) == notInitialized); + Require(sceSystemGestureCreateTouchRecognizer(handle, &recognizer, 0, &rectangle, nullptr) == notInitialized); + Require(sceSystemGestureAppendTouchRecognizer(handle, &recognizer) == notInitialized); + Require(sceSystemGestureRemoveTouchRecognizer(handle, &recognizer) == notInitialized); + Require(sceSystemGestureResetTouchRecognizer(handle, &recognizer) == notInitialized); + Require(sceSystemGestureUpdateTouchRecognizer(handle, &recognizer) == notInitialized); + Require(sceSystemGestureUpdateTouchRecognizerRectangle(handle, &recognizer, &rectangle) == notInitialized); + Require(sceSystemGestureUpdateAllTouchRecognizer(handle) == notInitialized); + Require(sceSystemGestureResetPrimitiveTouchRecognizer(handle) == notInitialized); + Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(handle, &input) == notInitialized); + Require(sceSystemGestureUpdatePrimitiveTouchRecognizer(0, nullptr) == notInitialized); + Require(sceSystemGestureGetPrimitiveTouchEventByIndex(handle, 0, &primitive) == notInitialized); + Require(sceSystemGestureGetPrimitiveTouchEventByPrimitiveID(handle, 0, &primitive) == notInitialized); + Require(sceSystemGestureGetPrimitiveTouchEvents(handle, &primitive, 1, &count) == notInitialized); + Require(count == 7); + Require(sceSystemGestureGetPrimitiveTouchEventsCount(handle) == notInitialized); + Require(sceSystemGestureGetTouchEventByEventID(handle, &recognizer, 0, &event) == notInitialized); + Require(sceSystemGestureGetTouchEventByIndex(handle, &recognizer, 0, &event) == notInitialized); + Require(sceSystemGestureGetTouchEvents(handle, &recognizer, &event, 1, &count) == notInitialized); + Require(count == 7); + Require(sceSystemGestureGetTouchEventsCount(handle, &recognizer) == notInitialized); + Require(sceSystemGestureGetTouchRecognizerInformation(handle, &recognizer, &information) == notInitialized); + Require(sceSystemGestureInitializePrimitiveTouchRecognizer() == 0); +} + int main() { constexpr int invalidArgument = static_cast(0x80890001); constexpr int invalidHandle = static_cast(0x80890006); @@ -263,6 +318,7 @@ int main() { Require(sceSystemGestureDebugGetVersion() == 0x6a00); TestTouchRecognizerInformation(handle, invalidArgument, invalidHandle); TestPrimitiveTouchEvents(handle, invalidArgument, invalidHandle, indexOutOfArray); + TestCreateTouchRecognizer(handle, invalidArgument, invalidHandle); std::array otherHandles{}; for (auto& otherHandle : otherHandles) { otherHandle = sceSystemGestureOpen(0, nullptr); @@ -278,6 +334,7 @@ int main() { Require(sceSystemGestureGetPrimitiveTouchEventsCount(otherHandles[0]) == invalidHandle); Require(sceSystemGestureClose(replacement) == 0); for (std::size_t index = 1; index < otherHandles.size(); ++index) Require(sceSystemGestureClose(otherHandles[index]) == 0); + TestNotInitialized(handle); Require(sceSystemGestureClose(handle) == 0); Require(sceSystemGestureFinalizePrimitiveTouchRecognizer() == 0); Require(sceSystemGestureOpen(0, nullptr) == static_cast(0x80890003)); diff --git a/core/libs/tests/GuestSystemService.cpp b/core/libs/tests/GuestSystemService.cpp index 6a7e4a671f..bef25b3b82 100644 --- a/core/libs/tests/GuestSystemService.cpp +++ b/core/libs/tests/GuestSystemService.cpp @@ -10,6 +10,8 @@ #include #include #include +#include +#include namespace { @@ -117,6 +119,57 @@ void TestPlayerDialogParam() { Require(sceSystemServiceLaunchPlayerDialog(buffer) == SYSTEM_SERVICE_OK); } +void TestSuspendConfirmationDialog() { + const int runningAppId = sceSystemServiceGetAppIdOfRunningBigApp(); + Require(sceShellCoreUtilGetSuspendConfirmationDialogFlag(runningAppId)); + Require(sceSystemServiceDisableSuspendConfirmationDialog() == SYSTEM_SERVICE_OK); + Require(!sceShellCoreUtilGetSuspendConfirmationDialogFlag(runningAppId)); + Require(sceSystemServiceDisableSuspendConfirmationDialog() == SYSTEM_SERVICE_OK); + Require(!sceShellCoreUtilGetSuspendConfirmationDialogFlag(runningAppId)); + Require(sceSystemServiceEnableSuspendConfirmationDialog() == SYSTEM_SERVICE_OK); + Require(sceShellCoreUtilGetSuspendConfirmationDialogFlag(runningAppId)); + std::vector threads; + for (unsigned index = 0; index < 4; ++index) { + threads.emplace_back([index] { + for (unsigned round = 0; round < 1000; ++round) { + const int result = ((index + round) & 1u) ? sceSystemServiceEnableSuspendConfirmationDialog() : sceSystemServiceDisableSuspendConfirmationDialog(); + Require(result == SYSTEM_SERVICE_OK); + } + }); + } + for (std::thread& thread : threads) thread.join(); + Require(sceSystemServiceEnableSuspendConfirmationDialog() == SYSTEM_SERVICE_OK); + Require(sceShellCoreUtilGetSuspendConfirmationDialogFlag(runningAppId)); +} + +void TestGpuLoadEmulationMode() { + const int runningAppId = sceSystemServiceGetAppIdOfRunningBigApp(); + Require(sceSystemServiceGetGpuLoadEmulationMode() == 1); + Require(sceShellCoreUtilGetGpuLoadEmulationMode() == 1); + Require(sceSystemServiceSetGpuLoadEmulationMode(0) == SYSTEM_SERVICE_OK); + Require(sceSystemServiceGetGpuLoadEmulationMode() == 0); + Require(sceShellCoreUtilGetGpuLoadEmulationMode() == 0); + Require(sceShellCoreUtilGetGpuLoadEmulationModeByAppId(runningAppId) == 0); + Require(sceShellCoreUtilSetGpuLoadEmulationMode(1) == SYSTEM_SERVICE_OK); + Require(sceSystemServiceGetGpuLoadEmulationMode() == 1); + Require(sceShellCoreUtilSetGpuLoadEmulationModeByAppId(runningAppId, 0) == SYSTEM_SERVICE_OK); + Require(sceSystemServiceGetGpuLoadEmulationMode() == 0); + Require(sceShellCoreUtilGetGpuLoadEmulationModeByAppId(runningAppId) == 0); + Require(sceSystemServiceGetGpuLoadEmulationMode() == 0); + std::vector threads; + for (unsigned index = 0; index < 4; ++index) { + threads.emplace_back([index] { + for (unsigned round = 0; round < 1000; ++round) { + Require(sceSystemServiceSetGpuLoadEmulationMode((index + round) & 1u) == SYSTEM_SERVICE_OK); + const int mode = sceSystemServiceGetGpuLoadEmulationMode(); + Require(mode == 0 || mode == 1); + } + }); + } + for (std::thread& thread : threads) thread.join(); + Require(sceSystemServiceSetGpuLoadEmulationMode(1) == SYSTEM_SERVICE_OK); +} + } int main() { @@ -218,4 +271,6 @@ int main() { TestRunningAppState(); TestRemoteSharePlayCpuBudget(); TestPlayerDialogParam(); + TestGpuLoadEmulationMode(); + TestSuspendConfirmationDialog(); } diff --git a/core/libs/tests/GuestSystemServiceLanguageAuto.cpp b/core/libs/tests/GuestSystemServiceLanguageAuto.cpp new file mode 100644 index 0000000000..0889d916ec --- /dev/null +++ b/core/libs/tests/GuestSystemServiceLanguageAuto.cpp @@ -0,0 +1,95 @@ +#include "prx/libSceSystemService/include/SystemService.hpp" +#include "SceTypes.hpp" +#include "prx/libSceSystemService/SystemService.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, const char* what) { + if (value) return; + std::fprintf(stderr, "check failed: %s\n", what); + std::abort(); +} + +void SetVariable(const char* name, std::string_view value) { + if (value == "-") Require(::unsetenv(name) == 0, name); + else Require(::setenv(name, std::string(value).c_str(), 1) == 0, name); +} + +int Query(int paramId) { + int value = -1; + Require(sceSystemServiceParamGetInt(paramId, &value) == SYSTEM_SERVICE_OK, "query"); + return value; +} + +bool Rejected(const char* value, std::string& message) { + SetVariable("ANYPS5_LANGUAGE", value); + int language = -1; + try { + static_cast(sceSystemServiceParamGetInt(SYSTEM_SERVICE_PARAM_ID_LANG, &language)); + } catch (const std::runtime_error& error) { + message = error.what(); + return language == -1; + } + return false; +} + +std::string ReadAll(std::FILE* file) { + std::string text; + std::rewind(file); + for (int c = std::fgetc(file); c != EOF; c = std::fgetc(file)) text += static_cast(c); + return text; +} + +int CountNotices(const std::string& text) { + int count = 0; + for (std::size_t at = text.find("ANYPS5_LANGUAGE=auto:"); at != std::string::npos; at = text.find("ANYPS5_LANGUAGE=auto:", at + 1)) ++count; + return count; +} + +} + +int main(int argc, char** argv) { + Require(argc == 8, "arguments: expected LC_ALL LC_MESSAGES LANG LANGUAGE spelling notices"); + const int expected = std::atoi(argv[1]); + const int expectedNotices = std::atoi(argv[7]); + std::FILE* captured = std::tmpfile(); + Require(captured != nullptr && ::dup2(::fileno(captured), 2) == 2, "capture stderr"); + SetVariable("LC_ALL", argv[2]); + SetVariable("LC_MESSAGES", argv[3]); + SetVariable("LANG", argv[4]); + SetVariable("LANGUAGE", argv[5]); + SetVariable("ANYPS5_LANGUAGE", argv[6]); + Require(Query(SYSTEM_SERVICE_PARAM_ID_LANG) == expected, "parameter 1"); + Require(Query(SYSTEM_SERVICE_PARAM_ID_400) == expected, "parameter 400"); + Require(SystemServiceConsoleLanguage("test") == expected, "SystemServiceConsoleLanguage"); + SetVariable("LC_ALL", "ja_JP.UTF-8"); + SetVariable("LC_MESSAGES", "ja_JP.UTF-8"); + SetVariable("LANG", "ja_JP.UTF-8"); + SetVariable("LANGUAGE", "ja"); + Require(Query(SYSTEM_SERVICE_PARAM_ID_LANG) == expected, "the host locale is resolved once"); + Require(SystemServiceConsoleLanguage("test") == expected, "the host locale is resolved once for every caller"); + Require(CountNotices(ReadAll(captured)) == expectedNotices, "number of notices"); + SetVariable("ANYPS5_LANGUAGE", "17"); + Require(Query(SYSTEM_SERVICE_PARAM_ID_LANG) == 17, "an integer after auto"); + SetVariable("ANYPS5_LANGUAGE", "0"); + Require(Query(SYSTEM_SERVICE_PARAM_ID_400) == 0, "integer 0 after auto"); + SetVariable("ANYPS5_LANGUAGE", argv[6]); + Require(Query(SYSTEM_SERVICE_PARAM_ID_LANG) == expected, "auto again gives the same answer"); + SetVariable("ANYPS5_LANGUAGE", ""); + Require(Query(SYSTEM_SERVICE_PARAM_ID_LANG) == SYSTEM_SERVICE_PARAM_LANG_ENGLISH_US, "empty after auto"); + SetVariable("ANYPS5_LANGUAGE", "-"); + Require(Query(SYSTEM_SERVICE_PARAM_ID_LANG) == SYSTEM_SERVICE_PARAM_LANG_ENGLISH_US, "unset after auto"); + for (const char* value : {"auto ", " auto", "autoo", "aut", "a", "au to", "auto\n", "automatic", "-auto", "+auto", "31", "-1", "21a", "0x15", "arabic"}) { + std::string message; + Require(Rejected(value, message), value); + Require(message.find("ANYPS5_LANGUAGE") != std::string::npos, "the error names the variable"); + Require(message.find("0 to 30 or auto") != std::string::npos, "the error lists auto"); + } + Require(CountNotices(ReadAll(captured)) == expectedNotices, "no notice after the first resolution"); +} diff --git a/core/libs/tests/GuestSystemServiceStatus.cpp b/core/libs/tests/GuestSystemServiceStatus.cpp new file mode 100644 index 0000000000..5ccffbe354 --- /dev/null +++ b/core/libs/tests/GuestSystemServiceStatus.cpp @@ -0,0 +1,140 @@ +#include "SceTypes.hpp" +#include "prx/libSceSystemService/SystemService.hpp" +#include "prx/libSceSystemService/libSceSystemService.hpp" +#include "prx/libkernel/KernelErrors.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr auto LOGGED_IN_USER = static_cast(USER_SERVICE_INITIAL_USER_ID); + +int failures = 0; + +template +void Case(const char* name, TCall call) { + try { + if (call()) return; + std::printf("%s: wrong answer\n", name); + } catch (const std::exception& error) { + std::printf("%s: %s\n", name, error.what()); + } + ++failures; +} + +void TestSuspend() { + Case("sceLncUtilBlockAppSuspend", [] { + return sceLncUtilBlockAppSuspend("saving") == SYSTEM_SERVICE_OK && sceLncUtilBlockAppSuspend("saving") == SYSTEM_SERVICE_OK; + }); + Case("sceLncUtilUnblockAppSuspend", [] { + return sceLncUtilUnblockAppSuspend("saving") == SYSTEM_SERVICE_OK && sceLncUtilUnblockAppSuspend("saving") == SYSTEM_SERVICE_OK; + }); + Case("sceLncUtilUnblockAppSuspend with no block left", [] { + return sceLncUtilUnblockAppSuspend("saving") == SCE_KERNEL_ERROR_EBUSY && sceLncUtilUnblockAppSuspend(nullptr) == SCE_KERNEL_ERROR_EBUSY && + sceLncUtilBlockAppSuspend("saving") == SYSTEM_SERVICE_OK && sceLncUtilUnblockAppSuspend("saving") == SYSTEM_SERVICE_OK && + sceLncUtilUnblockAppSuspend("saving") == SCE_KERNEL_ERROR_EBUSY; + }); + Case("sceLncUtilEnableSuspendNotification", [] { + return sceLncUtilEnableSuspendNotification() == SYSTEM_SERVICE_OK && sceLncUtilEnableSuspendNotification() == SYSTEM_SERVICE_OK; + }); + Case("sceLncUtilDisableSuspendNotification", [] { + return sceLncUtilDisableSuspendNotification() == SYSTEM_SERVICE_OK && sceLncUtilDisableSuspendNotification() == SYSTEM_SERVICE_OK; + }); + Case("sceSystemServiceEnableSuspendNotification", [] { + return sceSystemServiceEnableSuspendNotification() == sceLncUtilEnableSuspendNotification(); + }); + Case("sceSystemServiceDisableSuspendNotification", [] { + return sceSystemServiceDisableSuspendNotification() == sceLncUtilDisableSuspendNotification(); + }); +} + +template +bool ReportsInactive(TCall call) { + bool active = true; + return call(&active) == SYSTEM_SERVICE_OK && !active; +} + +template +bool RejectsNullOutput(TCall call, int error) { + return call(nullptr) == error; +} + +void TestSessions() { + Case("sceShellCoreUtilGetRemotePlayStatus", [] { return ReportsInactive(sceShellCoreUtilGetRemotePlayStatus); }); + Case("sceShellCoreUtilGetRemotePlayStatus null output", [] { + return RejectsNullOutput(sceShellCoreUtilGetRemotePlayStatus, SHELL_CORE_UTIL_ERROR_INVALID_ARGUMENT); + }); + Case("sceSystemServiceGetRemoteplayStatus", [] { return ReportsInactive(sceSystemServiceGetRemoteplayStatus); }); + Case("sceSystemServiceGetRemoteplayStatus null output", [] { + return RejectsNullOutput(sceSystemServiceGetRemoteplayStatus, SHELL_CORE_UTIL_ERROR_INVALID_ARGUMENT); + }); + Case("sceShellCoreUtilGetSharePlayStatus", [] { return ReportsInactive(sceShellCoreUtilGetSharePlayStatus); }); + Case("sceShellCoreUtilGetSharePlayStatus null output", [] { + return RejectsNullOutput(sceShellCoreUtilGetSharePlayStatus, SHELL_CORE_UTIL_ERROR_INVALID_ARGUMENT); + }); + Case("sceShellCoreUtilGetGameLiveStreamingStatus", [] { return ReportsInactive(sceShellCoreUtilGetGameLiveStreamingStatus); }); + Case("sceShellCoreUtilGetGameLiveStreamingStatus null output", [] { + return RejectsNullOutput(sceShellCoreUtilGetGameLiveStreamingStatus, SHELL_CORE_UTIL_ERROR_INVALID_ARGUMENT); + }); + Case("sceShellCoreUtilIsGameLiveStreamingOnAir", [] { return !sceShellCoreUtilIsGameLiveStreamingOnAir(); }); + Case("sceSystemServiceIsGameLiveStreamingOnAir", [] { + return !sceSystemServiceIsGameLiveStreamingOnAir() && sceSystemServiceIsGameLiveStreamingOnAir() == sceShellCoreUtilIsGameLiveStreamingOnAir(); + }); +} + +template +bool ReportsNotAdjusted(TCall call) { + const auto user = LOGGED_IN_USER; + std::uint8_t adjusted = 0xff; + return call(user, &adjusted) == SYSTEM_SERVICE_OK && adjusted == 0; +} + +void TestShell() { + Case("sceLncUtilIsShellUiFgAndGameBgCpuMode", [] { return !sceLncUtilIsShellUiFgAndGameBgCpuMode(); }); + Case("sceSystemServiceIsShellUiFgAndGameBgCpuMode", [] { + return !sceSystemServiceIsShellUiFgAndGameBgCpuMode() && sceSystemServiceIsShellUiFgAndGameBgCpuMode() == sceLncUtilIsShellUiFgAndGameBgCpuMode(); + }); + Case("sceShellCoreUtilIsImposeScreenOverlaid", [] { return !sceShellCoreUtilIsImposeScreenOverlaid(); }); + Case("sceShellCoreUtilIsScreenSaverOn", [] { return !sceShellCoreUtilIsScreenSaverOn(); }); + Case("sceSystemServiceIsScreenSaverOn", [] { + return !sceSystemServiceIsScreenSaverOn() && sceSystemServiceIsScreenSaverOn() == sceShellCoreUtilIsScreenSaverOn(); + }); + Case("sceSystemStateMgrIsShellUIShutdownInProgress", [] { return !sceSystemStateMgrIsShellUIShutdownInProgress(); }); + Case("sceShellCoreUtilGetSocialScreenStatus", [] { return ReportsInactive(sceShellCoreUtilGetSocialScreenStatus); }); + Case("sceShellCoreUtilIsEyeToEyeDistanceAdjusted", [] { return ReportsNotAdjusted(sceShellCoreUtilIsEyeToEyeDistanceAdjusted); }); + Case("sceSystemServiceIsEyeToEyeDistanceAdjusted", [] { + return ReportsNotAdjusted(sceSystemServiceIsEyeToEyeDistanceAdjusted) && + sceSystemServiceIsEyeToEyeDistanceAdjusted(LOGGED_IN_USER, nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER; + }); +} + +void TestDevices() { + Case("sceSystemStateMgrIsBdDriveReady", [] { return !sceSystemStateMgrIsBdDriveReady(); }); + Case("sceShellCoreUtilIsUsbMassStorageMounted", [] { + for (std::uint32_t device = 0; device < 8; ++device) { + if (sceShellCoreUtilIsUsbMassStorageMounted(device)) return false; + } + return true; + }); + Case("sceShellCoreUtilIsExternalStorageAppMoveInProgress", [] { return !sceShellCoreUtilIsExternalStorageAppMoveInProgress(); }); + Case("sceLncUtilIsPs2Emu", [] { return ReportsInactive(sceLncUtilIsPs2Emu); }); + Case("sceLncUtilIsPs2Emu null output", [] { return RejectsNullOutput(sceLncUtilIsPs2Emu, LNC_UTIL_ERROR_INVALID_ARGUMENT); }); +} + +} + +int main(int argc, char** argv) { + const std::string_view mode = argc > 1 ? argv[1] : ""; + if (mode == "suspend") TestSuspend(); + else if (mode == "sessions") TestSessions(); + else if (mode == "shell") TestShell(); + else if (mode == "devices") TestDevices(); + else return 2; + if (failures == 0) std::printf("all cases passed\n"); + return failures == 0 ? 0 : 1; +} diff --git a/core/libs/tests/GuestTcp.cpp b/core/libs/tests/GuestTcp.cpp index 91c3bf185c..79f9077cdb 100644 --- a/core/libs/tests/GuestTcp.cpp +++ b/core/libs/tests/GuestTcp.cpp @@ -219,8 +219,54 @@ static void ReceiveAnsweredWithoutTheHost() { for (const int descriptor : {unconnected, client, accepted, listener}) Require(close_nid_postfix(descriptor) == 0); } +static void SocketNameFaults() { + constexpr std::size_t page = 0x4000; + void* memory = nullptr; + Require(sceKernelMapFlexibleMemory(&memory, page, 3, 0) == 0 && memory != nullptr); + auto* readOnly = static_cast(memory); + Require(sceKernelMprotect(memory, page, 1) == 0); + const auto fails = [](int result) { return result == -1 && *__error_nid_postfix() == 14; }; + const auto unreadable = reinterpret_cast(std::uintptr_t{0x10}); + std::array address{16, 2, 0, 0, 127, 0, 0, 1}; + const int listener = socket_nid_postfix(2, 1, 0); + 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); + const int unconnected = socket_nid_postfix(2, 1, 0); + for (const auto call : {getsockname_nid_postfix, getpeername_nid_postfix}) { + std::array out{}; + size = out.size(); + Require(fails(call(client, out.data(), static_cast(unreadable)))); + Require(fails(call(client, out.data(), nullptr))); + Require(fails(call(-1, out.data(), static_cast(unreadable)))); + Require(call(-1, out.data(), &size) == -1 && *__error_nid_postfix() == 9); + Require(fails(call(client, unreadable, &size)) && size == out.size()); + Require(fails(call(client, nullptr, &size)) && size == out.size()); + Require(fails(call(client, readOnly, &size)) && size == out.size()); + std::uint32_t* readOnlySize = reinterpret_cast(readOnly + 64); + Require(sceKernelMprotect(memory, page, 3) == 0); + *readOnlySize = out.size(); + Require(sceKernelMprotect(memory, page, 1) == 0); + Require(fails(call(client, out.data(), readOnlySize)) && out[0] == 16 && out[1] == 2); + size = 0; + Require(call(client, nullptr, &size) == 0 && size == 0); + Require(call(client, unreadable, &size) == 0 && size == 0); + size = out.size(); + Require(call(client, out.data(), &size) == 0 && size == 16); + } + size = address.size(); + Require(getpeername_nid_postfix(unconnected, address.data(), &size) == -1 && *__error_nid_postfix() == 57); + Require(fails(getpeername_nid_postfix(unconnected, address.data(), static_cast(unreadable)))); + for (const int descriptor : {unconnected, accepted, client, listener}) Require(close_nid_postfix(descriptor) == 0); +} + int main() { StreamReceiveReportsNoSender(); + SocketNameFaults(); ListenersShareAPortAcrossFamilies(); AcceptedSocketsFollowListenerBlocking(); WaitAllReceivesTheWholeRequest(); diff --git a/core/libs/tests/GuestTerminal.cpp b/core/libs/tests/GuestTerminal.cpp index 8fabd1454f..df9a0bb2ce 100644 --- a/core/libs/tests/GuestTerminal.cpp +++ b/core/libs/tests/GuestTerminal.cpp @@ -48,13 +48,15 @@ static void RequireControlFails(int descriptor, int error) { } } -static void RequireProcessGroupFails(int descriptor, int error) { +static void RequireProcessGroupAndSessionFail(int descriptor, int error) { *__error_nid_postfix() = 0; Require(Fails(tcgetpgrp_nid_postfix(descriptor), error)); for (const int group : {0, 1}) { *__error_nid_postfix() = 0; Require(Fails(tcsetpgrp_nid_postfix(descriptor, group), error)); } + *__error_nid_postfix() = 0; + Require(Fails(tcgetsid_nid_postfix(descriptor), error)); } template static bool Throws(TCall call) { @@ -86,10 +88,10 @@ int main() { const int file = open_nid_postfix(name.c_str(), 0x202, 0644); Require(file >= 0); RequireNotTerminal(file); - RequireProcessGroupFails(file, NotTerminal); + RequireProcessGroupAndSessionFail(file, NotTerminal); for (const int stream : {0, 1, 2}) { RequireNotTerminal(stream); - RequireProcessGroupFails(stream, NotTerminal); + RequireProcessGroupAndSessionFail(stream, NotTerminal); } int ends[2] = {-1, -1}; @@ -98,6 +100,8 @@ int main() { RequireNotTerminal(end); Require(Throws([end] { tcgetpgrp_nid_postfix(end); })); Require(Throws([end] { tcsetpgrp_nid_postfix(end, 0); })); + *__error_nid_postfix() = 0; + Require(Fails(tcgetsid_nid_postfix(end), NotTerminal)); } const int socket = socket_nid_postfix(2, 2, 0); @@ -110,14 +114,14 @@ int main() { Require(std::memcmp(probe, untouched, sizeof(probe)) == 0); Require(Fails(tcsetattr_nid_postfix(socket, Flush, probe), AddressNotAvailable)); RequireControlFails(socket, AddressNotAvailable); - RequireProcessGroupFails(socket, AddressNotAvailable); + RequireProcessGroupAndSessionFail(socket, AddressNotAvailable); int pair[2] = {-1, -1}; Require(socketpair_nid_postfix(1, 1, 0, pair) == 0); for (const int end : pair) { Require(Fails(tcgetattr_nid_postfix(end, probe), OperationNotSupported)); RequireControlFails(end, OperationNotSupported); - RequireProcessGroupFails(end, OperationNotSupported); + RequireProcessGroupAndSessionFail(end, OperationNotSupported); } Require(close_nid_postfix(pair[0]) == 0 && close_nid_postfix(pair[1]) == 0); @@ -126,7 +130,7 @@ int main() { const int directory = open_nid_postfix(directoryName.c_str(), 0x00020000, 0); Require(directory >= 0); RequireNotTerminal(directory); - RequireProcessGroupFails(directory, NotTerminal); + RequireProcessGroupAndSessionFail(directory, NotTerminal); Require(close_nid_postfix(directory) == 0); RequireControlFails(directory, BadDescriptor); std::filesystem::remove(directoryName); @@ -151,21 +155,21 @@ int main() { } } RequireControlFails(-1, BadDescriptor); - RequireProcessGroupFails(-1, BadDescriptor); + RequireProcessGroupAndSessionFail(-1, BadDescriptor); Require(close_nid_postfix(socket) == 0); Require(Fails(tcgetattr_nid_postfix(socket, attributes), BadDescriptor)); Require(Fails(tcsetattr_nid_postfix(socket, Now, attributes), BadDescriptor)); RequireControlFails(socket, BadDescriptor); - RequireProcessGroupFails(socket, BadDescriptor); + RequireProcessGroupAndSessionFail(socket, BadDescriptor); Require(close_nid_postfix(ends[0]) == 0 && close_nid_postfix(ends[1]) == 0); RequireControlFails(ends[0], BadDescriptor); - RequireProcessGroupFails(ends[0], BadDescriptor); + RequireProcessGroupAndSessionFail(ends[0], BadDescriptor); Require(close_nid_postfix(file) == 0); Require(Fails(tcgetattr_nid_postfix(file, attributes), BadDescriptor)); Require(Fails(tcsetattr_nid_postfix(file, Flush, attributes), BadDescriptor)); RequireControlFails(file, BadDescriptor); - RequireProcessGroupFails(file, BadDescriptor); + RequireProcessGroupAndSessionFail(file, BadDescriptor); std::filesystem::remove(name); return 0; } diff --git a/core/libs/tests/GuestTimingsafeBoundary.cpp b/core/libs/tests/GuestTimingsafeBoundary.cpp new file mode 100644 index 0000000000..58e8330d06 --- /dev/null +++ b/core/libs/tests/GuestTimingsafeBoundary.cpp @@ -0,0 +1,88 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestTimingsafeBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +static int Sign(int value) { + return (value > 0) - (value < 0); +} + +int main() { + Buffer first, second; + for (std::size_t length = 0; length <= 128; ++length) { + auto* left = first.At(length); + auto* right = second.At(length); + const auto extent = (std::max)(length, std::size_t{1}); + for (int mode = 0; mode < 4; ++mode) { + for (std::size_t i = 0; i < extent; ++i) left[i] = right[i] = static_cast(i * 131 + length); + if (length && mode < 3) { + const auto position = mode == 0 ? 0 : mode == 1 ? length / 2 : length - 1; + left[position] = 0xff; right[position] = 1; + if (position + 1 < length) { left[position + 1] = 1; right[position + 1] = 0xff; } + } + int expected = 0; + for (std::size_t i = 0; i < length; ++i) if (left[i] != right[i]) { expected = left[i] > right[i] ? 1 : -1; break; } + const std::vector a(left, left + extent), b(right, right + extent); + Require(Sign(timingsafe_memcmp_nid_postfix(left, right, length)) == expected); + Require(Sign(timingsafe_memcmp_nid_postfix(right, left, length)) == -expected); + Require(std::equal(a.begin(), a.end(), left) && std::equal(b.begin(), b.end(), right)); + } + } +} diff --git a/core/libs/tests/GuestUltSync.cpp b/core/libs/tests/GuestUltSync.cpp index 601110ab9c..dd896bdc7a 100644 --- a/core/libs/tests/GuestUltSync.cpp +++ b/core/libs/tests/GuestUltSync.cpp @@ -84,6 +84,8 @@ void OptParams() { using Initializer = decltype(&sceUltSemaphoreOptParamInitialize); for (const Initializer initialize : {sceUltSemaphoreOptParamInitialize, sceUltQueueOptParamInitialize, sceUltQueueDataResourcePoolOptParamInitialize, sceUltWaitingQueueResourcePoolOptParamInitialize, sceUltUlthreadOptParamInitialize, sceUltReaderWriterLockOptParamInitialize, + _sceUltSemaphoreOptParamInitialize, _sceUltQueueOptParamInitialize, _sceUltQueueDataResourcePoolOptParamInitialize, + _sceUltWaitingQueueResourcePoolOptParamInitialize, _sceUltUlthreadOptParamInitialize, _sceUltReaderWriterLockOptParamInitialize, sceUltConditionVariableOptParamInitialize, _sceUltConditionVariableOptParamInitialize}) { alignas(UltOptParam) std::uint8_t bytes[136]; std::memset(bytes, 0xa5, sizeof(bytes)); @@ -238,6 +240,31 @@ void ReaderWriterFinalize() { CHECK(sceUltInitialize() == Ok); } +void ReaderWriterUnderscored() { + Object pool{}; + Object rwlock{}; + std::memset(&rwlock, 0x5a, sizeof(rwlock)); + alignas(UltOptParam) std::uint8_t optBytes[128]; + auto* optParam = reinterpret_cast(optBytes); + std::uint8_t workArea[2048]; + CHECK(_sceUltReaderWriterLockOptParamInitialize(optParam, 0) == Ok); + 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) == 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); + CHECK(sceUltReaderWriterLockLockRead(&rwlock) == Ok); + CHECK(sceUltReaderWriterLockTryLockWrite(&rwlock) == Busy); + CHECK(sceUltReaderWriterLockDestroy(&rwlock) == Busy); + CHECK(sceUltReaderWriterLockUnlockRead(&rwlock) == Ok); + CHECK(sceUltReaderWriterLockLockWrite(&rwlock) == Ok); + CHECK(sceUltReaderWriterLockUnlockWrite(&rwlock) == Ok); + CHECK(sceUltReaderWriterLockDestroy(&rwlock) == Ok); + CHECK(sceUltWaitingQueueResourcePoolDestroy(&pool) == Ok); +} + struct alignas(8) UlthreadObject { std::uint8_t bytes[512]; }; @@ -703,6 +730,7 @@ int main(int argc, char** argv) { else if (std::strcmp(argv[1], "rwlock-writer") == 0) ReaderWriterWriter(); else if (std::strcmp(argv[1], "rwlock-pool") == 0) ReaderWriterPool(); else if (std::strcmp(argv[1], "rwlock-finalize") == 0) ReaderWriterFinalize(); + else if (std::strcmp(argv[1], "rwlock-underscored") == 0) ReaderWriterUnderscored(); else if (std::strcmp(argv[1], "ulthreads") == 0) Ulthreads(); else if (std::strcmp(argv[1], "get-info") == 0) GetInfoTests(); else if (std::strcmp(argv[1], "queue-waiters") == 0) QueueWaiters(); diff --git a/core/libs/tests/GuestUltThread.cpp b/core/libs/tests/GuestUltThread.cpp index 23b0e358fb..d0c561bd1e 100644 --- a/core/libs/tests/GuestUltThread.cpp +++ b/core/libs/tests/GuestUltThread.cpp @@ -1,4 +1,6 @@ #include "prx/libSceUlt/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" #include "SceTypes.hpp" #include #include @@ -8,6 +10,7 @@ #include #include #include +#include struct UltConditionVariableInfo; struct UltWaitingThreadInfo; @@ -46,6 +49,85 @@ static std::int32_t APS5_VABI Immediate(std::uint64_t arg) { struct alignas(8) Runtime { std::uint8_t bytes[4096]{}; }; struct alignas(8) Thread { std::uint8_t bytes[512]{}; }; +static std::binary_semaphore gTargetStarted{0}; +static std::binary_semaphore gTargetRelease{0}; +static std::binary_semaphore gJoinStart{0}; + +static std::int32_t APS5_VABI GatedTarget(std::uint64_t argument) { + gTargetStarted.release(); + CHECK(gTargetRelease.try_acquire_for(std::chrono::seconds(4))); + return static_cast(argument); +} + +static void* APS5_VABI JoinTarget(void* argument) { + CHECK(gJoinStart.try_acquire_for(std::chrono::seconds(4))); + std::int32_t status = -1; + CHECK(sceUltUlthreadJoin(argument, &status) == Ok && status == 91); + return nullptr; +} + +static void AwaitJoinWait(Pthread caller) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + for (;;) { + { + std::lock_guard lock(caller->cancelLock); + if (caller->cancelWait != nullptr) return; + } + CHECK(std::chrono::steady_clock::now() < deadline); + std::this_thread::yield(); + } +} + +static void CanceledJoin(bool pending) { + Runtime runtime; + Thread thread; + CHECK(sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == Ok); + CHECK(sceUltUlthreadCreate(&thread, "target", GatedTarget, 91, nullptr, 0, &runtime, nullptr, 0) == Ok); + CHECK(gTargetStarted.try_acquire_for(std::chrono::seconds(2))); + Pthread caller = nullptr; + CHECK(scePthreadCreate(&caller, nullptr, JoinTarget, &thread, "joiner") == 0); + if (!pending) { + gJoinStart.release(); + AwaitJoinWait(caller); + } + CHECK(scePthreadCancel(caller) == 0); + if (pending) gJoinStart.release(); + void* canceled = nullptr; + CHECK(scePthreadJoin(caller, &canceled) == 0); + CHECK(canceled == reinterpret_cast(std::uintptr_t{1})); + Pthread replacement = nullptr; + CHECK(scePthreadCreate(&replacement, nullptr, JoinTarget, &thread, "replacement") == 0); + gJoinStart.release(); + AwaitJoinWait(replacement); + CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == State); + CHECK(sceUltFinalize() == Busy); + gTargetRelease.release(); + CHECK(scePthreadJoin(replacement, nullptr) == 0); + CHECK(sceUltUlthreadJoin(&thread, nullptr) == State); + CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == Ok); + CHECK(sceUltFinalize() == Ok); +} + +static std::int32_t APS5_VABI SelfJoin(std::uint64_t) { + void* self = nullptr; + CHECK(sceUltUlthreadGetSelf(&self) == Ok); + std::int32_t status = -1; + CHECK(sceUltUlthreadJoin(self, &status) == State && status == -1); + CHECK(sceUltUlthreadTryJoin(self, &status) == Again && status == -1); + return 91; +} + +static void SelfJoin() { + Runtime runtime; + Thread thread; + CHECK(sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == Ok); + CHECK(sceUltUlthreadCreate(&thread, "self", SelfJoin, 0, nullptr, 0, &runtime, nullptr, 0) == Ok); + std::int32_t status = -1; + CHECK(sceUltUlthreadJoin(&thread, &status) == Ok && status == 91); + CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == Ok); + CHECK(sceUltFinalize() == Ok); +} + static void Outstanding() { Runtime runtime; Thread thread; @@ -269,5 +351,8 @@ int main(int argc, char** argv) { else if (std::strcmp(argv[1], "tryjoin") == 0) TryJoin(); else if (std::strcmp(argv[1], "destroy") == 0) Destroy(); else if (std::strcmp(argv[1], "underscoredcreate") == 0) UnderscoredCreate(); + else if (std::strcmp(argv[1], "cancel-blocked") == 0) CanceledJoin(false); + else if (std::strcmp(argv[1], "cancel-pending") == 0) CanceledJoin(true); + else if (std::strcmp(argv[1], "self-join") == 0) SelfJoin(); else CHECK(false); } diff --git a/core/libs/tests/GuestUmtxOp.cpp b/core/libs/tests/GuestUmtxOp.cpp index ee4befc68f..a91732e33c 100644 --- a/core/libs/tests/GuestUmtxOp.cpp +++ b/core/libs/tests/GuestUmtxOp.cpp @@ -13,7 +13,18 @@ static constexpr int UMTX_OP_WAIT = 2; static constexpr int UMTX_OP_WAKE = 3; +static constexpr int UMTX_OP_MUTEX_TRYLOCK = 4; static constexpr int UMTX_OP_MUTEX_LOCK = 5; +static constexpr int UMTX_OP_MUTEX_UNLOCK = 6; +static constexpr int UMTX_OP_SET_CEILING = 7; +static constexpr int UMTX_OP_CV_WAIT = 8; +static constexpr int UMTX_OP_CV_SIGNAL = 9; +static constexpr int UMTX_OP_CV_BROADCAST = 10; +static constexpr int UMTX_OP_RW_RDLOCK = 12; +static constexpr int UMTX_OP_RW_WRLOCK = 13; +static constexpr int UMTX_OP_RW_UNLOCK = 14; +static constexpr int UMTX_OP_MUTEX_WAIT = 17; +static constexpr int UMTX_OP_MUTEX_WAKE = 18; static constexpr int UMTX_OP_WAIT_UINT = 11; static constexpr int UMTX_OP_WAIT_UINT_PRIVATE = 15; static constexpr int UMTX_OP_WAKE_PRIVATE = 16; @@ -25,11 +36,43 @@ static constexpr int UMTX_OP_MAX = 27; static constexpr std::uint32_t UMTX_ABSTIME = 1; static constexpr int CLOCK_MONOTONIC_ID = 4; +static constexpr int GUEST_EPERM = 1; static constexpr int GUEST_EFAULT = 14; +static constexpr int GUEST_EBUSY = 16; static constexpr int GUEST_EINVAL = 22; static constexpr int GUEST_EOPNOTSUPP = 45; static constexpr int GUEST_ETIMEDOUT = 60; +static constexpr std::uint32_t CONTESTED = 0x80000000u; +static constexpr std::uint32_t PRIO_INHERIT = 0x4; +static constexpr std::uint32_t PRIO_PROTECT = 0x8; +static constexpr std::uint32_t WRITE_OWNER = 0x80000000u; +static constexpr std::uint32_t WRITE_WAITERS = 0x40000000u; +static constexpr std::uint32_t READ_WAITERS = 0x20000000u; +static constexpr std::uint32_t PREFER_READER = 0x2; + +struct Umutex { + std::uint32_t owner = 0; + std::uint32_t flags = 0; + std::uint32_t ceilings[2]{}; + std::uint32_t spare[4]{}; +}; + +struct Ucond { + std::uint32_t hasWaiters = 0; + std::uint32_t flags = 0; + std::uint32_t clockId = 0; + std::uint32_t spare = 0; +}; + +struct Urwlock { + std::uint32_t state = 0; + std::uint32_t flags = 0; + std::uint32_t blockedReaders = 0; + std::uint32_t blockedWriters = 0; + std::uint32_t spare[4]{}; +}; + static constexpr auto SETTLE = std::chrono::milliseconds(100); struct UmtxTime { @@ -342,16 +385,201 @@ static void RejectsBadObjectsAndOperations() { Require(word == owner); } + Require(FailsWith(Umtx(&value, 7, 0), GUEST_EINVAL)); bool threw = false; - try { Umtx(&value, UMTX_OP_MUTEX_LOCK, 0); } - catch (const std::runtime_error&) { threw = true; } - Require(threw); - threw = false; try { Umtx(&value, UMTX_OP_SEM2_WAIT, 0); } catch (const std::runtime_error&) { threw = true; } Require(threw); } +static std::uint32_t Self() { return static_cast(scePthreadGetthreadid()); } + +static UmtxTime ShortTimeout() { return {{0, 1000000}, 0, CLOCK_MONOTONIC_ID}; } + +static void MutexOwnershipFollowsTheConsole() { + Umutex mutex; + Require(Umtx(&mutex, UMTX_OP_MUTEX_TRYLOCK, 0) == 0 && mutex.owner == Self()); + Require(Umtx(&mutex, UMTX_OP_MUTEX_TRYLOCK, 0) == 0 && mutex.owner == Self()); + UmtxTime timeout = ShortTimeout(); + Require(Umtx(&mutex, UMTX_OP_MUTEX_LOCK, 0, SizeOf(sizeof(timeout)), &timeout) == 0 && mutex.owner == Self()); + Require(Umtx(&mutex, UMTX_OP_MUTEX_UNLOCK, 0) == 0 && mutex.owner == 0); + Require(FailsWith(Umtx(&mutex, UMTX_OP_MUTEX_UNLOCK, 0), GUEST_EPERM) && mutex.owner == 0); + Require(Umtx(&mutex, UMTX_OP_MUTEX_LOCK, 0) == 0 && mutex.owner == Self()); + Require(Umtx(&mutex, UMTX_OP_MUTEX_UNLOCK, 0) == 0); + + mutex.owner = 12345; + Require(FailsWith(Umtx(&mutex, UMTX_OP_MUTEX_UNLOCK, 0), GUEST_EPERM) && mutex.owner == 12345); + Require(FailsWith(Umtx(&mutex, UMTX_OP_MUTEX_TRYLOCK, 0), GUEST_EBUSY) && mutex.owner == 12345); + timeout = ShortTimeout(); + Require(FailsWith(Umtx(&mutex, UMTX_OP_MUTEX_LOCK, 0, SizeOf(sizeof(timeout)), &timeout), GUEST_ETIMEDOUT) && mutex.owner == (CONTESTED | 12345)); + timeout = ShortTimeout(); + Require(FailsWith(Umtx(&mutex, UMTX_OP_MUTEX_WAIT, 0, SizeOf(sizeof(timeout)), &timeout), GUEST_ETIMEDOUT)); + + mutex.owner = 0; + Require(Umtx(&mutex, UMTX_OP_MUTEX_WAIT, 0) == 0 && mutex.owner == 0); + mutex.owner = CONTESTED; + Require(Umtx(&mutex, UMTX_OP_MUTEX_WAKE, 0) == 0 && mutex.owner == 0); + mutex.owner = CONTESTED; + Require(Umtx(&mutex, UMTX_OP_MUTEX_TRYLOCK, 0) == 0 && mutex.owner == (CONTESTED | Self())); + Require(Umtx(&mutex, UMTX_OP_MUTEX_UNLOCK, 0) == 0 && mutex.owner == 0); + + Umutex inherit; + inherit.flags = PRIO_INHERIT; + Require(Umtx(&inherit, UMTX_OP_MUTEX_TRYLOCK, 0) == 0 && inherit.owner == Self()); + Require(Umtx(&inherit, UMTX_OP_MUTEX_UNLOCK, 0) == 0 && inherit.owner == 0); + Umutex protect; + protect.flags = PRIO_PROTECT; + Require(FailsWith(Umtx(&protect, UMTX_OP_MUTEX_TRYLOCK, 0), GUEST_EINVAL) && protect.owner == 0); + Require(FailsWith(Umtx(&mutex, UMTX_OP_SET_CEILING, 0), GUEST_EINVAL)); + Require(FailsWith(Umtx(nullptr, UMTX_OP_MUTEX_TRYLOCK, 0), GUEST_EFAULT)); +} + +static void MutexLockWaitsForTheOwnerToUnlock() { + Umutex mutex; + Require(Umtx(&mutex, UMTX_OP_MUTEX_LOCK, 0) == 0); + std::atomic state{0}; + std::thread waiter([&] { + state.store(1); + Require(Umtx(&mutex, UMTX_OP_MUTEX_LOCK, 0) == 0); + state.store(2); + Require(Umtx(&mutex, UMTX_OP_MUTEX_UNLOCK, 0) == 0); + }); + AwaitCount(state, 1); + std::this_thread::sleep_for(SETTLE); + Require(state.load() == 1 && (std::atomic_ref(mutex.owner).load() & CONTESTED) != 0); + Require(Umtx(&mutex, UMTX_OP_MUTEX_UNLOCK, 0) == 0); + AwaitCount(state, 2); + waiter.join(); + Require(mutex.owner == 0); +} + +static void ConditionWaitReleasesTheMutexAndWakes() { + Ucond condition; + Require(Umtx(&condition, UMTX_OP_CV_SIGNAL, 0) == 0 && condition.hasWaiters == 0); + Require(Umtx(&condition, UMTX_OP_CV_BROADCAST, 0) == 0 && condition.hasWaiters == 0); + + Umutex mutex; + Require(Umtx(&mutex, UMTX_OP_MUTEX_TRYLOCK, 0) == 0); + KernelTimespec timeout{0, 1000000}; + Require(FailsWith(Umtx(&condition, UMTX_OP_CV_WAIT, 0, &mutex, &timeout), GUEST_ETIMEDOUT)); + Require(condition.hasWaiters == 0 && mutex.owner == 0); + Require(FailsWith(Umtx(&condition, UMTX_OP_CV_WAIT, 0, &mutex, &timeout), GUEST_EPERM) && condition.hasWaiters == 0); + + std::atomic state{0}; + std::vector waiters; + for (int index = 0; index < 2; ++index) { + waiters.emplace_back([&] { + Umutex own; + Require(Umtx(&own, UMTX_OP_MUTEX_TRYLOCK, 0) == 0); + KernelTimespec failsafe = FailsafeTimeout(); + state.fetch_add(1); + Require(Umtx(&condition, UMTX_OP_CV_WAIT, 0, &own, &failsafe) == 0 && own.owner == 0); + state.fetch_add(10); + }); + } + AwaitCount(state, 2); + std::this_thread::sleep_for(SETTLE); + Require(condition.hasWaiters == 1); + Require(Umtx(&condition, UMTX_OP_CV_SIGNAL, 0) == 0); + AwaitCount(state, 12); + Require(condition.hasWaiters == 1); + Require(Umtx(&condition, UMTX_OP_CV_BROADCAST, 0) == 0); + AwaitCount(state, 22); + for (auto& waiter : waiters) waiter.join(); + Require(condition.hasWaiters == 0); +} + +static void RwlockStateFollowsTheConsole() { + Urwlock rwlock; + Require(Umtx(&rwlock, UMTX_OP_RW_RDLOCK, 0) == 0 && rwlock.state == 1); + UmtxTime timeout = ShortTimeout(); + Require(FailsWith(Umtx(&rwlock, UMTX_OP_RW_WRLOCK, 0, SizeOf(sizeof(timeout)), &timeout), GUEST_ETIMEDOUT)); + Require(rwlock.state == 1 && rwlock.blockedWriters == 0); + Require(Umtx(&rwlock, UMTX_OP_RW_RDLOCK, 0) == 0 && rwlock.state == 2); + Require(Umtx(&rwlock, UMTX_OP_RW_UNLOCK, 0) == 0 && Umtx(&rwlock, UMTX_OP_RW_UNLOCK, 0) == 0 && rwlock.state == 0); + + Require(Umtx(&rwlock, UMTX_OP_RW_WRLOCK, 0) == 0 && rwlock.state == WRITE_OWNER); + timeout = ShortTimeout(); + Require(FailsWith(Umtx(&rwlock, UMTX_OP_RW_RDLOCK, 0, SizeOf(sizeof(timeout)), &timeout), GUEST_ETIMEDOUT)); + Require(rwlock.state == (WRITE_OWNER | READ_WAITERS)); + Require(Umtx(&rwlock, UMTX_OP_RW_UNLOCK, 0) == 0 && rwlock.state == READ_WAITERS); + rwlock.state = 0; + Require(FailsWith(Umtx(&rwlock, UMTX_OP_RW_UNLOCK, 0), GUEST_EPERM) && rwlock.state == 0); + Require(FailsWith(Umtx(nullptr, UMTX_OP_RW_RDLOCK, 0), GUEST_EFAULT)); +} + +static void RwlockReaderPreferenceFollowsTheConsole() { + const auto readLock = [](std::uint32_t state, std::uint32_t flags, std::uint64_t value, std::uint32_t& after) { + Urwlock rwlock; + rwlock.state = state; + rwlock.flags = flags; + UmtxTime timeout = ShortTimeout(); + const int result = Umtx(&rwlock, UMTX_OP_RW_RDLOCK, value, SizeOf(sizeof(timeout)), &timeout); + after = rwlock.state; + return result; + }; + std::uint32_t after = 0; + Require(FailsWith(readLock(WRITE_WAITERS, 0, 0, after), GUEST_ETIMEDOUT) && after == (WRITE_WAITERS | READ_WAITERS)); + Require(readLock(WRITE_WAITERS, 0, PREFER_READER, after) == 0 && after == (WRITE_WAITERS | 1)); + Require(readLock(WRITE_WAITERS, PREFER_READER, 0, after) == 0 && after == (WRITE_WAITERS | 1)); + Require(FailsWith(readLock(WRITE_OWNER, 0, PREFER_READER, after), GUEST_ETIMEDOUT) && after == (WRITE_OWNER | READ_WAITERS)); +} + +static void RwlockUnlockWakesTheBlockedWriter() { + Urwlock rwlock; + Require(Umtx(&rwlock, UMTX_OP_RW_RDLOCK, 0) == 0); + std::atomic state{0}; + std::thread writer([&] { + state.store(1); + Require(Umtx(&rwlock, UMTX_OP_RW_WRLOCK, 0) == 0); + state.store(2); + Require(Umtx(&rwlock, UMTX_OP_RW_UNLOCK, 0) == 0); + }); + AwaitCount(state, 1); + std::this_thread::sleep_for(SETTLE); + Require(state.load() == 1 && (std::atomic_ref(rwlock.state).load() & WRITE_WAITERS) != 0); + Require(Umtx(&rwlock, UMTX_OP_RW_UNLOCK, 0) == 0); + AwaitCount(state, 2); + writer.join(); + Require(rwlock.state == 0 && rwlock.blockedWriters == 0); +} + +static void RwlockDoesNotLoseAWakeupUnderContention() { + Urwlock rwlock; + constexpr int threadCount = 8; + constexpr int rounds = 6000; + std::vector workers; + for (int index = 0; index < threadCount; ++index) { + workers.emplace_back([&, index] { + for (int round = 0; round < rounds; ++round) { + const bool write = (round % threadCount) == index % threadCount; + KernelTimespec failsafe = FailsafeTimeout(); + Require(Umtx(&rwlock, write ? UMTX_OP_RW_WRLOCK : UMTX_OP_RW_RDLOCK, 0, SizeOf(sizeof(failsafe)), &failsafe) == 0); + Require(Umtx(&rwlock, UMTX_OP_RW_UNLOCK, 0) == 0); + } + }); + } + for (auto& worker : workers) worker.join(); + Require(rwlock.state == 0 && rwlock.blockedReaders == 0 && rwlock.blockedWriters == 0); +} + +static void ConditionWaitRejectsAPriorityProtectedMutexWithoutLeakingAWaiter() { + Ucond condition; + Umutex protect; + protect.flags = PRIO_PROTECT; + KernelTimespec timeout{0, 1000000}; + try { + Umtx(&condition, UMTX_OP_CV_WAIT, 0, &protect, &timeout); + } catch (const std::runtime_error&) { + } + Require(condition.hasWaiters == 0); + + Umutex mutex; + Require(Umtx(&mutex, UMTX_OP_MUTEX_TRYLOCK, 0) == 0); + Require(FailsWith(Umtx(&condition, UMTX_OP_CV_WAIT, 0, &mutex, &timeout), GUEST_ETIMEDOUT)); + Require(condition.hasWaiters == 0); +} + int main() { ReturnsWhenTheValueDiffers(); WaitComparesALongAndWaitUintAnInt(); @@ -367,4 +595,12 @@ int main() { SemWakeReleasesTheWaiterAndClearsTheFlag(); SemWakeWithoutWaitersSucceeds(); RejectsBadObjectsAndOperations(); + MutexOwnershipFollowsTheConsole(); + MutexLockWaitsForTheOwnerToUnlock(); + ConditionWaitReleasesTheMutexAndWakes(); + RwlockStateFollowsTheConsole(); + RwlockUnlockWakesTheBlockedWriter(); + RwlockReaderPreferenceFollowsTheConsole(); + RwlockDoesNotLoseAWakeupUnderContention(); + ConditionWaitRejectsAPriorityProtectedMutexWithoutLeakingAWaiter(); } diff --git a/core/libs/tests/GuestUsbd.cpp b/core/libs/tests/GuestUsbd.cpp index 316fcc1319..2c8645c10f 100644 --- a/core/libs/tests/GuestUsbd.cpp +++ b/core/libs/tests/GuestUsbd.cpp @@ -25,6 +25,7 @@ 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; +constexpr std::int32_t timedOut = static_cast(0x80240007); struct InterruptEndpoint { std::uint8_t address; @@ -57,6 +58,128 @@ void APS5_VABI MarkDoneThenSignal(UsbdTransfer* transfer) { notified->store(true, std::memory_order_release); } +void FollowerWakesAfterPass() { + UsbdTimeval brief{0, 20000}; + sceUsbdLockEvents(); + std::atomic finished{false}; + std::atomic waiterResult{-1}; + std::atomic waited{0}; + std::thread follower([&] { + sceUsbdLockEventWaiters(); + UsbdTimeval patience{5, 0}; + const auto start = std::chrono::steady_clock::now(); + waiterResult.store(sceUsbdHandleEventsLocked(&patience)); + waited.store(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()); + sceUsbdUnlockEventWaiters(); + finished.store(true); + }); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (!finished.load() && std::chrono::steady_clock::now() < deadline) Require(sceUsbdHandleEventsLocked(&brief) == 0, "the handler failed while another thread waited"); + Require(finished.load(), "a thread that is not the handler did not return from sceUsbdHandleEventsLocked while the handler released the event lock"); + follower.join(); + Require(waiterResult.load() == 0 && waited.load() < 2000, "a thread that is not the handler was not woken when a pass of event handling ended"); + sceUsbdUnlockEvents(); + Require(sceUsbdEventHandlerActive() == 0, "the handler stayed active at the end"); +} + +void FollowerTimesOutDuringPass() { + UsbdTimeval longPass{2, 0}; + sceUsbdLockEvents(); + std::atomic finished{false}; + std::atomic waiterResult{0}; + std::thread follower([&] { + sceUsbdLockEventWaiters(); + UsbdTimeval shortWait{0, 30000}; + waiterResult.store(sceUsbdHandleEventsLocked(&shortWait)); + sceUsbdUnlockEventWaiters(); + finished.store(true); + }); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (!finished.load() && std::chrono::steady_clock::now() < deadline) Require(sceUsbdHandleEventsLocked(&longPass) == 0, "the handler failed while another thread waited"); + Require(finished.load(), "a thread that is not the handler did not return from sceUsbdHandleEventsLocked during a pass of event handling"); + follower.join(); + Require(waiterResult.load() == timedOut, "a thread that is not the handler did not time out in sceUsbdHandleEventsLocked during a longer pass of event handling"); + sceUsbdUnlockEvents(); + Require(sceUsbdEventHandlerActive() == 0, "the handler stayed active at the end"); +} + +void FollowerReturnsAfterHeldLock() { + UsbdTimeval brief{0, 20000}; + sceUsbdLockEvents(); + std::atomic entered{false}; + std::atomic finished{false}; + std::thread follower([&] { + sceUsbdLockEventWaiters(); + entered.store(true); + UsbdTimeval shortWait{0, 30000}; + sceUsbdHandleEventsLocked(&shortWait); + sceUsbdUnlockEventWaiters(); + finished.store(true); + }); + while (!entered.load()) Require(sceUsbdHandleEventsLocked(&brief) == 0, "the handler failed while another thread queued"); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(10); + while (!finished.load() && std::chrono::steady_clock::now() < deadline) Require(sceUsbdHandleEventsLocked(&brief) == 0, "the handler failed while another thread waited"); + Require(finished.load(), "a thread that is not the handler did not return from sceUsbdHandleEventsLocked after the handler held the event lock past its timeout"); + follower.join(); + sceUsbdUnlockEvents(); + Require(sceUsbdEventHandlerActive() == 0, "the handler stayed active at the end"); +} + +void EventLocks() { + Require(sceUsbdEventHandlerActive() == 0 && sceUsbdEventHandlingOk() == 0, "an event handler was active before any lock"); + sceUsbdLockEvents(); + Require(sceUsbdEventHandlingOk() == 1 && sceUsbdEventHandlerActive() == 1, "the thread that locked events is not the handler"); + Require(sceUsbdTryLockEvents() == 1, "the holder of the event lock took it again"); + Require(Throws([] { sceUsbdLockEvents(); }), "one thread took the event lock twice"); + Require(Throws([] { sceUsbdLockEventWaiters(); }), "the holder of the event lock took it again as a waiter"); + std::thread([] { + Require(sceUsbdTryLockEvents() == 1, "the event lock was taken while another thread held it"); + Require(sceUsbdEventHandlingOk() == 0 && sceUsbdEventHandlerActive() == 1, "a thread without the lock saw itself as the handler"); + }).join(); + sceUsbdUnlockEvents(); + Require(sceUsbdEventHandlerActive() == 0 && sceUsbdEventHandlingOk() == 0, "the handler stayed active after unlocking events"); + UsbdTimeval brief{0, 20000}; + Require(Throws([] { sceUsbdUnlockEvents(); }) && Throws([] { sceUsbdUnlockEventWaiters(); }), "an event lock that was not held was released"); + Require(Throws([&] { sceUsbdWaitForEvent(&brief); }) && Throws([&] { sceUsbdHandleEventsLocked(&brief); }), "waited or handled events without the event lock"); + Require(sceUsbdTryLockEvents() == 0 && sceUsbdEventHandlingOk() == 1, "a free event lock was not taken"); + UsbdTimeval negative{-1, 0}; + Require(Throws([&] { sceUsbdHandleEventsLocked(&negative); }) && Throws([&] { sceUsbdWaitForEvent(&negative); }), "a negative timeout was accepted"); + const auto start = std::chrono::steady_clock::now(); + Require(sceUsbdHandleEventsLocked(&brief) == 0, "the handler failed to handle events"); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15), "the handler returned before the timeout"); + sceUsbdUnlockEvents(); + + sceUsbdLockEventWaiters(); + Require(sceUsbdEventHandlingOk() == 0, "locking the event waiters made this thread the handler"); + Require(sceUsbdWaitForEvent(&brief) == 1, "waiting without a handler did not time out"); + Require(sceUsbdEventHandlingOk() == 1, "a waiter did not take over handling after a timeout with no handler"); + sceUsbdUnlockEvents(); + Require(sceUsbdEventHandlerActive() == 0, "the waiter kept handling after unlocking events"); + + sceUsbdLockEvents(); + std::atomic waiting{false}; + std::atomic woken{-1}; + std::atomic handling{-1}; + std::thread waiter([&] { + sceUsbdLockEventWaiters(); + UsbdTimeval patience{5, 0}; + waiting.store(true); + woken.store(sceUsbdWaitForEvent(&patience)); + handling.store(sceUsbdEventHandlingOk()); + sceUsbdUnlockEvents(); + }); + while (!waiting.load()) Require(sceUsbdHandleEventsLocked(&brief) == 0, "the handler failed while a waiter queued"); + sceUsbdUnlockEvents(); + waiter.join(); + Require(woken.load() == 0 && handling.load() == 1, "a waiter was not woken and handed the events when the handler unlocked"); + Require(sceUsbdEventHandlerActive() == 0, "the handler stayed active after the woken waiter unlocked"); + + FollowerWakesAfterPass(); + FollowerTimesOutDuringPass(); + FollowerReturnsAfterHeldLock(); +} + void Pump(const int& done) { const UsbdTimeval slice{0, 100000}; for (int round = 0; round < 50 && done == 0; ++round) Require(sceUsbdHandleEventsTimeout(&slice) == 0, "event handling failed while a transfer was in flight"); @@ -203,6 +326,20 @@ void Device() { 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"); + done = 0; + Require(sceUsbdSubmitTransfer(transfer) == 0, "resubmitting the control transfer failed"); + while (done == 0) Require(sceUsbdHandleEvents() == 0, "sceUsbdHandleEvents failed while a transfer was in flight"); + Require(transfer->status == 0 && transfer->actualLength == 18, "the control transfer did not complete through sceUsbdHandleEvents"); + done = 0; + sceUsbdLockEvents(); + Require(sceUsbdEventHandlingOk() == 1, "the thread that locked events cannot handle them"); + Require(sceUsbdSubmitTransfer(transfer) == 0, "submitting the control transfer under the event lock failed"); + { + UsbdTimeval slice{0, 100000}; + for (int round = 0; round < 50 && done == 0; ++round) Require(sceUsbdHandleEventsLocked(&slice) == 0, "sceUsbdHandleEventsLocked failed while a transfer was in flight"); + } + sceUsbdUnlockEvents(); + Require(done == 1 && transfer->status == 0 && transfer->actualLength == 18, "the control transfer did not complete through sceUsbdHandleEventsLocked"); Require(std::memcmp(setup + 8, descriptor, 18) == 0, "asynchronous device descriptor differs from the cached one"); const UsbdTimeval slice{0, 200}; std::vector blink; @@ -319,6 +456,7 @@ int main(int argc, char** argv) { sceUsbdFreeDeviceList(list, 1); Require(Throws([&] { sceUsbdFreeDeviceList(list, 1); }), "a device list was freed twice"); + EventLocks(); Require(sceUsbdGetDeviceList(nullptr) == invalidArgument, "null list accepted"); Require(sceUsbdHandleEventsTimeout(nullptr) == invalidArgument, "null timeout accepted"); const UsbdTimeval invalid{0, 1000000}; @@ -409,7 +547,7 @@ int main(int argc, char** argv) { Require(sceUsbdOpenDeviceWithVidPid(0, 0) == nullptr, "a device with vendor and product 0 was opened"); sceUsbdFreeTransfer(nullptr); - Require(sceUsbdEventHandlingOk() == 1, "event handling not allowed"); + Require(sceUsbdEventHandlingOk() == 0, "a thread without the event lock may handle events"); sceUsbdFreeConfigDescriptor(nullptr); Require(Throws([] { sceUsbdFillControlSetup(nullptr, 0x80, 6, 0x0100, 0, 18); }), "null control setup buffer accepted"); diff --git a/core/libs/tests/GuestUserContext.cpp b/core/libs/tests/GuestUserContext.cpp index 46fb68c1d5..e87831a5b7 100644 --- a/core/libs/tests/GuestUserContext.cpp +++ b/core/libs/tests/GuestUserContext.cpp @@ -118,6 +118,7 @@ asm(".text\n" namespace { +constexpr int GuestEfault = 14; constexpr int GuestEinval = 22; constexpr int GuestSigBlock = 1; constexpr int GuestSigSetmask = 3; @@ -218,7 +219,24 @@ void CheckErrors() { Require(getcontext_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == GuestEinval, "getcontext rejects a null context"); *__error_nid_postfix() = 0; Require(setcontext_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == GuestEinval, "setcontext rejects a null context"); + *__error_nid_postfix() = 0; + Require(getcontext_nid_postfix(reinterpret_cast(0x10)) == -1 && *__error_nid_postfix() == GuestEfault, "getcontext rejects an invalid destination pointer"); + *__error_nid_postfix() = 0; + Require(setcontext_nid_postfix(reinterpret_cast(0x10)) == -1 && *__error_nid_postfix() == GuestEfault, "setcontext rejects an invalid source pointer"); + *__error_nid_postfix() = 0; + Require(SigreturnContext_nid_no_patch(nullptr) == -1 && *__error_nid_postfix() == GuestEfault, "sigreturn rejects a null context"); + *__error_nid_postfix() = 0; + Require(SigreturnContext_nid_no_patch(reinterpret_cast(0x10)) == -1 && *__error_nid_postfix() == GuestEfault, "sigreturn rejects an invalid context pointer"); + *__error_nid_postfix() = 0; + Require(swapcontext_nid_postfix(reinterpret_cast(0x10), nullptr) == -1 && *__error_nid_postfix() == GuestEinval, "swapcontext rejects a null next pointer"); UserUcontext context{}; + Require(getcontext_nid_postfix(&context) == 0, "capture a valid context"); + *__error_nid_postfix() = 0; + Require(swapcontext_nid_postfix(reinterpret_cast(0x10), &context) == -1 && *__error_nid_postfix() == GuestEfault, "swapcontext rejects an invalid saved pointer"); + UserUcontext savedContext{}; + *__error_nid_postfix() = 0; + Require(swapcontext_nid_postfix(&savedContext, reinterpret_cast(0x10)) == -1 && *__error_nid_postfix() == GuestEfault, "swapcontext rejects an invalid next pointer"); + Require(savedContext.mcontext.len == sizeof(UserMcontext) && savedContext.mcontext.rip != 0, "swapcontext writes saved context before failing next"); Require(getcontext_nid_postfix(&context) == 0, "capture a context to corrupt"); context.mcontext.len = 0; *__error_nid_postfix() = 0; diff --git a/core/libs/tests/GuestUserService.cpp b/core/libs/tests/GuestUserService.cpp index 852b8d6da0..2281482617 100644 --- a/core/libs/tests/GuestUserService.cpp +++ b/core/libs/tests/GuestUserService.cpp @@ -9,6 +9,8 @@ namespace { +constexpr int SCE_USER_SERVICE_ERROR_INVALID_USER_ID = USER_SERVICE_ERROR_UNKNOWN_USER; + void Require(bool value) { if (!value) std::abort(); } void SetLanguage(const char* value) { @@ -53,6 +55,17 @@ int main() { int color = -1; Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetUserColor(123, &color) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(0, &color) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(USER_SERVICE_USER_ID_INVALID, &color) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID - 1, &color) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID + USER_SERVICE_MAX_USERS, &color) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(0x20000000, &color) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(0, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(USER_SERVICE_USER_ID_INVALID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID - 1, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID + USER_SERVICE_MAX_USERS, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(sceUserServiceGetUserColor(0x20000000, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + Require(color == -1); Require(sceUserServiceGetUserColor(USER_SERVICE_INITIAL_USER_ID, &color) == USER_SERVICE_OK); SetLanguage(nullptr); @@ -95,6 +108,16 @@ int main() { Require(sceUserServiceGetKeyboardType(123, &untouched) == USER_SERVICE_ERROR_UNKNOWN_USER && untouched == 0x5A5A); Require(sceUserServiceGetJapaneseInputType(USER_SERVICE_INITIAL_USER_ID, &untouched) == USER_SERVICE_OK && untouched == 0); } +#if defined(__linux__) + for (const char* name : {"LC_MESSAGES", "LANG", "LANGUAGE"}) Require(::unsetenv(name) == 0); + Require(::setenv("LC_ALL", "de_DE.UTF-8", 1) == 0); + SetLanguage("auto"); + std::int32_t german = 0x5A5A; + Require(sceUserServiceGetKeyboardType(USER_SERVICE_INITIAL_USER_ID, &german) == USER_SERVICE_OK && german == keyboardTypeByLanguage[4]); + Require(::setenv("LC_ALL", "ja_JP.UTF-8", 1) == 0); + german = 0x5A5A; + Require(sceUserServiceGetKeyboardType(USER_SERVICE_INITIAL_USER_ID, &german) == USER_SERVICE_OK && german == keyboardTypeByLanguage[4]); +#endif SetLanguage(nullptr); Require(color == 0); @@ -102,7 +125,7 @@ int main() { Require(sceUserServiceGetNpAccountId(USER_SERVICE_INITIAL_USER_ID, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetNpAccountId(123, &account_id) == USER_SERVICE_ERROR_UNKNOWN_USER); Require(account_id == 0xdeadbeefu); - Require(sceUserServiceGetNpAccountId(123, nullptr) == USER_SERVICE_ERROR_UNKNOWN_USER); + Require(sceUserServiceGetNpAccountId(123, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); Require(sceUserServiceGetNpAccountId(USER_SERVICE_INITIAL_USER_ID, &account_id) == USER_SERVICE_OK); Require(account_id == 0); @@ -120,7 +143,7 @@ int main() { account_id = 0xdeadbeefu; Require(sceUserServiceGetNpAccountId(user_id, &account_id) == USER_SERVICE_ERROR_UNKNOWN_USER); Require(account_id == 0xdeadbeefu); - Require(sceUserServiceGetNpAccountId(user_id, nullptr) == USER_SERVICE_ERROR_UNKNOWN_USER); + Require(sceUserServiceGetNpAccountId(user_id, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); } Require(sceUserServiceGetAgeLevel(USER_SERVICE_INITIAL_USER_ID, &age_level) == USER_SERVICE_OK); Require(age_level == 0xffffffffu); @@ -154,7 +177,11 @@ int main() { 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); + for (int user_id : absent_users) { + Require(sceUserServiceGetGamePresets(user_id, &presets) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + Require(presets.difficulty == 0x5a); + Require(sceUserServiceGetGamePresets(user_id, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + } Require(sceUserServiceGetGamePresets(USER_SERVICE_INITIAL_USER_ID, &presets) == USER_SERVICE_OK && presets.difficulty == 0); std::int32_t accessibilityValue = 0; bool threwKeyremap = false; @@ -176,6 +203,10 @@ int main() { 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(0x10000004, name, sizeof(name)) == SCE_USER_SERVICE_ERROR_INVALID_USER_ID); + Require(sceUserServiceGetUserName(static_cast(0xFFFFFFFFu), name, sizeof(name)) == SCE_USER_SERVICE_ERROR_INVALID_USER_ID); + Require(sceUserServiceGetUserName(USER_SERVICE_INITIAL_USER_ID + USER_SERVICE_MAX_USERS, name, sizeof(name)) == USER_SERVICE_ERROR_UNKNOWN_USER); + Require(sceUserServiceGetUserName(USER_SERVICE_USER_ID_INVALID, 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); @@ -202,13 +233,17 @@ int main() { account_id = 0xdeadbeefu; Require(sceUserServiceGetNpAccountId(USER_SERVICE_INITIAL_USER_ID + 1, &account_id) == USER_SERVICE_ERROR_UNKNOWN_USER); Require(account_id == 0xdeadbeefu); - Require(sceUserServiceGetNpAccountId(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_UNKNOWN_USER); + Require(sceUserServiceGetNpAccountId(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); 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); } + presets.difficulty = 0x5a; + Require(sceUserServiceGetGamePresets(USER_SERVICE_INITIAL_USER_ID + 1, &presets) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + Require(presets.difficulty == 0x5a); + Require(sceUserServiceGetGamePresets(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); UserServiceSetUserConnected_nid_postfix(1, true); Require(sceUserServiceGetRegisteredUserIdList(®istered) == USER_SERVICE_OK); @@ -232,6 +267,7 @@ int main() { Require(setting.get(USER_SERVICE_INITIAL_USER_ID + 1, &value) == USER_SERVICE_OK); Require(value == setting.value); } + Require(sceUserServiceGetGamePresets(USER_SERVICE_INITIAL_USER_ID + 1, &presets) == USER_SERVICE_OK && presets.difficulty == 0); UserServiceSetUserConnected_nid_postfix(1, false); Require(sceUserServiceGetRegisteredUserIdList(®istered) == USER_SERVICE_OK); @@ -248,11 +284,15 @@ int main() { account_id = 0xdeadbeefu; Require(sceUserServiceGetNpAccountId(USER_SERVICE_INITIAL_USER_ID + 1, &account_id) == USER_SERVICE_ERROR_UNKNOWN_USER); Require(account_id == 0xdeadbeefu); - Require(sceUserServiceGetNpAccountId(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_UNKNOWN_USER); + Require(sceUserServiceGetNpAccountId(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); 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); } + presets.difficulty = 0x5a; + Require(sceUserServiceGetGamePresets(USER_SERVICE_INITIAL_USER_ID + 1, &presets) == USER_SERVICE_ERROR_NOT_LOGGED_IN); + Require(presets.difficulty == 0x5a); + Require(sceUserServiceGetGamePresets(USER_SERVICE_INITIAL_USER_ID + 1, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); } diff --git a/core/libs/tests/GuestUserServiceUserKind.cpp b/core/libs/tests/GuestUserServiceUserKind.cpp new file mode 100644 index 0000000000..1332473d4b --- /dev/null +++ b/core/libs/tests/GuestUserServiceUserKind.cpp @@ -0,0 +1,55 @@ +#include "SceTypes.hpp" +#include "prx/libSceUserService/UserService.hpp" +#include +#include +#include + +namespace { + +void Require(bool value) { if (!value) std::abort(); } + +std::int32_t Id(std::uint32_t value) { + std::int32_t id = 0; + std::memcpy(&id, &value, sizeof(id)); + return id; +} + +void CheckGuestUser() { + const std::uint32_t guests[] = {0x20000000u, 0x20000001u, 0x2abcdef0u, 0x2fffffffu, 0x30000000u, 0x3fffffffu}; + const std::uint32_t others[] = {0u, 0x10000000u, 0x10000003u, 0x1fffffffu, 0x40000000u, 0x60000000u, + 0xa0000000u, 0xe0000000u, 0xffffffffu}; + for (const std::uint32_t id : guests) Require(sceUserServiceIsGuestUser(Id(id)) == 1); + for (const std::uint32_t id : others) Require(sceUserServiceIsGuestUser(Id(id)) == 0); + Require(sceUserServiceIsGuestUser(USER_SERVICE_INITIAL_USER_ID) == 0); + Require(sceUserServiceIsGuestUser(USER_SERVICE_USER_ID_INVALID) == 0); +} + +void CheckSharePlayClientUser() { + const std::uint32_t clients[] = {0x30000000u, 0x30000001u, 0x3abcdef0u, 0x3fffffffu}; + const std::uint32_t others[] = {0u, 0x10000000u, 0x20000000u, 0x2fffffffu, 0x40000000u, 0x70000000u, + 0xb0000000u, 0xf0000000u, 0xffffffffu}; + for (const std::uint32_t id : clients) Require(sceUserServiceIsSharePlayClientUser(Id(id)) == 1); + for (const std::uint32_t id : others) Require(sceUserServiceIsSharePlayClientUser(Id(id)) == 0); + Require(sceUserServiceIsSharePlayClientUser(USER_SERVICE_INITIAL_USER_ID) == 0); +} + +void CheckSpartacusEnabled() { + const std::int32_t ids[] = {USER_SERVICE_INITIAL_USER_ID, USER_SERVICE_USER_ID_INVALID, 0, 123, Id(0x20000000u)}; + for (const std::int32_t id : ids) { + Require(sceUserServiceGetIsSpartacusEnabled(id, nullptr) == USER_SERVICE_ERROR_INVALID_ARGUMENT); + unsigned char bytes[2] = {0x5a, 0x5a}; + bool* enabled = reinterpret_cast(bytes); + Require(sceUserServiceGetIsSpartacusEnabled(id, enabled) == USER_SERVICE_OK); + Require(bytes[0] == 1); + Require(bytes[1] == 0x5a); + } +} + +} + +int main() { + CheckGuestUser(); + CheckSharePlayClientUser(); + CheckSpartacusEnabled(); + return 0; +} diff --git a/core/libs/tests/GuestVdecCore.cpp b/core/libs/tests/GuestVdecCore.cpp index e62fbd0248..a7ab8641e5 100644 --- a/core/libs/tests/GuestVdecCore.cpp +++ b/core/libs/tests/GuestVdecCore.cpp @@ -1,10 +1,14 @@ #include "prx/libSceVdecCore/include/Api.hpp" #include "prx/libSceVdecCore/include/Decoder.hpp" +#include "H264Fixture.hpp" +#include #include +#include #include #include #include +#include #include #ifdef _WIN32 @@ -17,6 +21,15 @@ namespace { constexpr int InvalidArgument = static_cast(0x80C00001); constexpr int InvalidCodecType = static_cast(0x80C00003); +constexpr int NoPicture = static_cast(0x80C00010); +constexpr int DecodeRunning = static_cast(0x80C00018); +constexpr std::int64_t PtsInvalid = -1; +constexpr std::uint64_t SlotSize = 0x10000; +constexpr std::uint32_t OutputPitch = 64; +constexpr std::array VisiblePictureHashes{ + 0x5871fdc85dfa274eull, 0x26ce4afb974cffc4ull, 0xd2e739633733d5baull, + 0x54feb3d290e028fdull, 0xfa1342a4ba1819c8ull, 0x870125dd7f079a4cull, +}; constexpr std::uint32_t CodecAvc = 0; constexpr std::uint32_t CodecHevc = 4; constexpr std::uint32_t DecoderVdec2 = 2; @@ -174,6 +187,322 @@ void CheckMapMemoryBlock() { Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the mapped decoder"); } +void* CreateAvc(std::int32_t width) { + const VdecCoreConfigInfo config = Config(width, static_cast(H264Fixture::Height), 256, 0); + void* decoder = nullptr; + Require(sceVdecCoreCreateDecoder(&config, nullptr, nullptr, &decoder) == 0 && decoder != nullptr, "create an AVC decoder"); + return decoder; +} + +const std::vector>& Units() { + static const std::vector> units = H264Fixture::accessUnits(H264Fixture::Stream.data(), H264Fixture::Stream.size(), false); + Require(units.size() == H264Fixture::PictureHashes.size(), "the fixture's access units"); + return units; +} + +void Feed(void* decoder, std::size_t unit, std::int64_t pts, std::int64_t dts, std::uint64_t userData) { + const auto& data = Units()[unit]; + const VdecCoreInputData input{data.data(), data.size(), pts, dts, userData, 0, 0}; + std::array status{}; + Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, status.data()) == 0, "set an access unit as decode input"); + Require(status[0] == 1 && status[1] == 1, "the input status of an accepted access unit"); +} + +void FeedAll(void* decoder, std::int64_t ptsBase, std::int64_t dtsBase, std::uint64_t userBase) { + for (std::size_t unit = 0; unit < Units().size(); ++unit) { + const auto offset = static_cast(unit); + Feed(decoder, unit, ptsBase == PtsInvalid ? PtsInvalid : ptsBase + offset, dtsBase == PtsInvalid ? PtsInvalid : dtsBase + offset, userBase + unit); + } +} + +std::uint32_t Flush(void* decoder) { + std::uint32_t count = 0xffffffffu; + Require(sceVdecCoreFlushDecodeOutput(decoder, &count, nullptr) == 0, "flush the decode output"); + return count; +} + +VdecCoreOutputInfo Output(void* decoder, VdecCoreFrameBuffer* frameBuffer) { + VdecCoreOutputInfo output{}; + Require(sceVdecCoreGetDecodeOutput(decoder, frameBuffer, 0, &output, nullptr) == 0, "get a decoded picture"); + return output; +} + +std::uint64_t VisibleHash(const std::uint8_t* buffer, std::uint32_t pitch, std::uint32_t frameHeight) { + std::uint64_t hash = 0xcbf29ce484222325ull; + const auto mix = [&](const std::uint8_t* row) { + for (std::uint32_t column = 0; column < H264Fixture::Width; ++column) hash = (hash ^ row[column]) * 0x100000001b3ull; + }; + for (std::uint32_t row = 0; row < H264Fixture::Height; ++row) mix(buffer + static_cast(row) * pitch); + const auto* chroma = buffer + static_cast(pitch) * frameHeight; + for (std::uint32_t row = 0; row < H264Fixture::Height / 2; ++row) mix(chroma + static_cast(row) * pitch); + return hash; +} + +bool ExtendsLastRows(const std::uint8_t* buffer, std::uint32_t pitch, std::uint32_t frameHeight) { + const auto* chroma = buffer + static_cast(pitch) * frameHeight; + const auto* lastLuma = buffer + static_cast(pitch) * (H264Fixture::Height - 1); + const auto* lastChroma = chroma + static_cast(pitch) * (H264Fixture::Height / 2 - 1); + for (std::uint32_t row = H264Fixture::Height; row < frameHeight; ++row) { + if (std::memcmp(buffer + static_cast(row) * pitch, lastLuma, H264Fixture::Width) != 0) return false; + } + for (std::uint32_t row = H264Fixture::Height / 2; row < frameHeight / 2; ++row) { + if (std::memcmp(chroma + static_cast(row) * pitch, lastChroma, H264Fixture::Width) != 0) return false; + } + return true; +} + +const VdecCoreHwPictureInfo& HwInfo(const VdecCoreOutputInfo& output, std::uint64_t allocatedSize) { + return *reinterpret_cast(static_cast(output.frameBuffer) + allocatedSize - sizeof(VdecCoreHwPictureInfo)); +} + +void RequirePicture(const VdecCoreOutputInfo& output, std::size_t picture, std::uint64_t allocatedSize, std::uint64_t pts, std::uint64_t userData, const char* message) { + const std::uint32_t height = H264Fixture::CodedHeight; + auto* buffer = static_cast(output.frameBuffer); + Require(output.pictureCount == 1 && buffer != nullptr && output.frameFormat == 0 && output.reserved0 == 0, message); + Require(output.frameWidth == H264Fixture::Width && output.framePitch == OutputPitch && output.frameHeight == height && output.framePitchInBytes == OutputPitch, message); + Require(output.lumaBuffer == buffer && output.chromaBuffer == buffer + static_cast(OutputPitch) * height, message); + const VdecCorePicture& first = output.pictures[0]; + Require(first.flags == 0 && first.reserved0 == 0 && first.pictureInfo == nullptr && first.frameBuffer == buffer && first.frameBufferSize == OutputPitch * height * 3 / 2 && first.framePitch == OutputPitch && first.reserved1 == 0, message); + Require(output.pictures[1].frameBuffer == nullptr, message); + Require(first.pts == pts && first.userData == userData, message); + Require(output.aspectRatio == 1 && output.sarWidth == 1 && output.videoFormat == 5 && output.colourPrimaries == 1 && output.transferCharacteristics == 1 && output.videoFullRangeFlag == 0 && output.colourDescriptionPresentFlag == 1, message); + Require(VisibleHash(buffer, output.framePitch, output.frameHeight) == VisiblePictureHashes[picture], message); + Require(ExtendsLastRows(buffer, output.framePitch, output.frameHeight), message); + const VdecCoreHwPictureInfo& info = HwInfo(output, allocatedSize); + Require(info.codecType == 0 && info.userData == userData && info.pts == pts && info.dts == first.dts && info.valid == 1, message); + Require(info.width == H264Fixture::Width && info.height == height && info.cropLeft == 0 && info.cropRight == 0 && info.cropTop == 0 && info.cropBottom == (height - H264Fixture::Height) / 2, message); + Require(info.sarWidth == 1 && info.sarHeight == 1 && info.flags == 0x03, message); +} + +void CheckAccessUnitLength() { + const std::uint64_t limit = VdecCoreMaxAccessUnitSize_nid_no_patch(); + Require(limit > 0 && limit < 0x7fffffffull, "the largest access unit leaves room for the packet padding in an int"); + Require(VdecCoreAccessUnitLength_nid_no_patch(0) < 0, "an empty access unit has no length"); + Require(VdecCoreAccessUnitLength_nid_no_patch(1) == 1, "a one-byte access unit keeps its length"); + Require(VdecCoreAccessUnitLength_nid_no_patch(limit) == static_cast(limit), "the largest access unit keeps its length"); + Require(VdecCoreAccessUnitLength_nid_no_patch(limit + 1) < 0, "an access unit one past the packet limit has no length"); + Require(VdecCoreAccessUnitLength_nid_no_patch(0x7fffffffull) < 0, "an access unit of the largest int has no length"); + Require(VdecCoreAccessUnitLength_nid_no_patch((1ull << 32) + 16) < 0, "an access unit whose low 32 bits look small has no length"); + Require(VdecCoreAccessUnitLength_nid_no_patch(~0ull) < 0, "the largest 64-bit size has no length"); +} + +void CheckDecodeArguments() { + void* decoder = CreateAvc(static_cast(H264Fixture::Width)); + const auto& unit = Units()[0]; + VdecCoreInputData input{unit.data(), unit.size(), 0, 0, 0, 0, 0}; + std::array status{7, 7}; + Require(sceVdecCoreSetDecodeInput(nullptr, &input, nullptr, status.data()) == InvalidArgument, "set input without a decoder"); + Require(sceVdecCoreSetDecodeInput(decoder, nullptr, nullptr, status.data()) == InvalidArgument, "set input without input data"); + Require(status[0] == 7 && status[1] == 7, "an input refused for a missing decoder or input leaves the status alone"); + input.auData = nullptr; + Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, status.data()) == InvalidArgument, "set input without access unit data"); + Require(status[0] == 1 && status[1] == 1, "the status is written before the access unit is checked"); + status = {7, 7}; + input.auData = unit.data(); + input.auSize = 0; + Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, status.data()) == InvalidArgument, "set input with an empty access unit"); + Require(status[0] == 1 && status[1] == 1, "the status is written before the access unit size is checked"); + const std::uint64_t limit = VdecCoreMaxAccessUnitSize_nid_no_patch(); + const std::array oversized{1ull << 31, limit + 1, (1ull << 32) + unit.size(), ~0ull}; + for (const auto size : oversized) { + status = {7, 7}; + input.auSize = size; + Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, status.data()) == InvalidArgument, "set input with an access unit past the packet limit"); + Require(status[0] == 1 && status[1] == 1, "the status is written before an oversized access unit is refused"); + } + input.auSize = unit.size(); + Require(sceVdecCoreSetDecodeInput(decoder, &input, nullptr, nullptr) == 0, "set input without a status"); + + std::uint32_t count = 7; + std::uint64_t pointers[2] = {7, 7}; + VdecCoreOutputInfo output{}; + VdecCoreFrameBuffer frameBuffer{}; + Require(sceVdecCoreResetDecoder(nullptr, nullptr) == InvalidArgument, "reset without a decoder"); + Require(sceVdecCoreSyncDecode(nullptr, &count) == InvalidArgument, "sync without a decoder"); + Require(sceVdecCoreSyncDecodeWptr(nullptr, &count, &pointers[0], &pointers[1]) == InvalidArgument, "write-pointer sync without a decoder"); + Require(sceVdecCoreTrySyncDecode(nullptr, &count) == InvalidArgument, "try-sync without a decoder"); + Require(sceVdecCoreTrySyncDecodeWptr(nullptr, &count, &pointers[0], &pointers[1]) == InvalidArgument, "write-pointer try-sync without a decoder"); + Require(sceVdecCoreGetDecodeOutput(nullptr, &frameBuffer, 0, &output, nullptr) == InvalidArgument, "get output without a decoder"); + Require(sceVdecCoreGetDecodeOutput(decoder, &frameBuffer, 0, nullptr, nullptr) == InvalidArgument, "get output without output info"); + Require(sceVdecCoreFlushDecodeOutput(nullptr, &count, nullptr) == InvalidArgument, "flush without a decoder"); + Require(count == 7 && pointers[0] == 7 && pointers[1] == 7, "refused calls leave their outputs alone"); + + Require(sceVdecCoreSyncDecode(decoder, nullptr) == 0, "sync without a count"); + Require(sceVdecCoreFlushDecodeOutput(decoder, nullptr, nullptr) == 0, "flush without a count"); + std::uint32_t held = 7; + Require(sceVdecCoreTrySyncDecode(decoder, nullptr) == 0 && sceVdecCoreTrySyncDecode(decoder, &held) == 0 && held == 1, "try-sync after a flush counts the held picture"); + Require(sceVdecCoreResetDecoder(decoder, nullptr) == 0, "reset"); + Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the argument decoder"); +} + +void CheckEmptyQueue() { + void* decoder = CreateAvc(100); + std::uint32_t count = 7; + Require(sceVdecCoreTrySyncDecode(decoder, &count) == 0 && count == 0, "try-sync on an idle decoder"); + count = 7; + Require(sceVdecCoreSyncDecode(decoder, &count) == 0 && count == 0, "sync on an idle decoder"); + VdecCoreOutputInfo output; + std::memset(&output, 0xab, sizeof(output)); + Require(sceVdecCoreGetDecodeOutput(decoder, nullptr, 0, &output, nullptr) == NoPicture, "get output from an empty queue"); + Require(output.frameWidth == 100 && output.framePitch == 128 && output.frameHeight == H264Fixture::Height, "an empty queue reports the maximum width, its 64-byte pitch and the maximum height"); + Require(output.pictureCount == 0 && output.frameBuffer == nullptr && output.frameFormat == 0 && output.reserved0 == 0, "an empty queue reports no picture"); + Require(output.pictures[0].frameBuffer == nullptr && output.pictures[1].frameBuffer == nullptr && output.lumaBuffer == nullptr && output.chromaBuffer == nullptr, "an empty queue reports no buffers"); + Require(output.pictures[0].pts == 0xababababababababull && output.aspectRatio == 0xababababu && output.colourDescriptionPresentFlag == 0xababababu, "an empty queue leaves the other fields alone"); + Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the idle decoder"); +} + +void CheckDecodeIntoSlots() { + void* decoder = CreateAvc(static_cast(H264Fixture::Width)); + std::vector memory(SlotSize * 4); + const VdecCoreMemoryBlock block{memory.data(), memory.size(), 256, 0}; + Require(sceVdecCoreMapMemoryBlock(decoder, &block, 1) == 0, "map four frame buffer slots"); + const std::vector slots = Slots(decoder); + Require(slots.size() == 4, "four slots"); + + std::size_t pictures = 0; + std::vector dts; + const auto take = [&](std::uint32_t available) { + for (std::uint32_t index = 0; index < available; ++index) { + const VdecCoreOutputInfo output = Output(decoder, nullptr); + Require(pictures < H264Fixture::PictureHashes.size(), "more pictures than access units"); + Require(output.frameBuffer == slots[pictures % slots.size()], "pictures go to the mapped slots in turn"); + const auto unit = H264Fixture::DisplayOrderUnits[pictures]; + RequirePicture(output, pictures, SlotSize, 1000 + unit, 3000 + unit, "a picture decoded into a slot"); + dts.push_back(output.pictures[0].dts); + ++pictures; + } + }; + for (std::size_t unit = 0; unit < Units().size(); ++unit) { + Feed(decoder, unit, 1000 + static_cast(unit), 2000 + static_cast(unit), 3000 + unit); + std::uint32_t tried = 0xffffffffu; + const int result = sceVdecCoreTrySyncDecode(decoder, &tried); + Require(result == 0 || (result == DecodeRunning && tried == 0), "try-sync during a decode"); + std::uint32_t count = 0xffffffffu; + Require(sceVdecCoreSyncDecode(decoder, &count) == 0, "sync after an access unit"); + take(count); + } + take(Flush(decoder)); + Require(pictures == H264Fixture::PictureHashes.size(), "every access unit gives a picture"); + for (const auto value : dts) Require(value >= 2000 && value < 2000 + Units().size(), "a picture's dts is one of the inputs'"); + VdecCoreOutputInfo output{}; + Require(sceVdecCoreGetDecodeOutput(decoder, nullptr, 0, &output, nullptr) == NoPicture, "no picture after the flushed ones"); + Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the slot decoder"); +} + +void CheckInvalidPts() { + void* decoder = CreateAvc(static_cast(H264Fixture::Width)); + FeedAll(decoder, PtsInvalid, PtsInvalid, 5000); + Require(Flush(decoder) == H264Fixture::PictureHashes.size(), "a flush holds every picture"); + for (std::size_t picture = 0; picture < H264Fixture::PictureHashes.size(); ++picture) { + const VdecCoreOutputInfo output = Output(decoder, nullptr); + RequirePicture(output, picture, SlotSize, ~std::uint64_t{0}, 5000 + H264Fixture::DisplayOrderUnits[picture], "a picture without a pts"); + Require(output.pictures[0].dts == ~std::uint64_t{0}, "a picture without a dts takes the last input's invalid dts"); + } + Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the invalid pts decoder"); +} + +void CheckDestinations() { + void* decoder = CreateAvc(static_cast(H264Fixture::Width)); + FeedAll(decoder, 1000, 2000, 3000); + Require(Flush(decoder) == H264Fixture::PictureHashes.size(), "a flush holds every picture for the fallback"); + const VdecCoreOutputInfo fallback = Output(decoder, nullptr); + RequirePicture(fallback, 0, SlotSize, 1000, 3000, "a picture in the fallback buffer"); + const VdecCoreOutputInfo again = Output(decoder, nullptr); + Require(again.frameBuffer == fallback.frameBuffer, "the fallback buffer is reused"); + RequirePicture(again, 1, SlotSize, 1000 + H264Fixture::DisplayOrderUnits[1], 3000 + H264Fixture::DisplayOrderUnits[1], "a second picture in the fallback buffer"); + + std::vector slotMemory(SlotSize); + const VdecCoreMemoryBlock block{slotMemory.data(), slotMemory.size(), 256, 0}; + Require(sceVdecCoreMapMemoryBlock(decoder, &block, 1) == 0, "map one slot"); + std::vector caller(0x2000); + VdecCoreFrameBuffer frameBuffer{caller.data(), caller.size()}; + const VdecCoreOutputInfo supplied = Output(decoder, &frameBuffer); + Require(supplied.frameBuffer == caller.data(), "a caller frame buffer comes before the slots"); + RequirePicture(supplied, 2, caller.size(), 1000 + H264Fixture::DisplayOrderUnits[2], 3000 + H264Fixture::DisplayOrderUnits[2], "a picture in the caller's frame buffer"); + VdecCoreFrameBuffer unaddressed{nullptr, caller.size()}; + const VdecCoreOutputInfo slot = Output(decoder, &unaddressed); + Require(slot.frameBuffer == slotMemory.data(), "a frame buffer without an address leaves the picture to the slots"); + RequirePicture(slot, 3, SlotSize, 1000 + H264Fixture::DisplayOrderUnits[3], 3000 + H264Fixture::DisplayOrderUnits[3], "a picture in a slot keeps the slot's size, not the caller's"); + Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the destination decoder"); +} + +void CheckOutputStaysInItsDestination() { + void* decoder = CreateAvc(static_cast(H264Fixture::Width)); + FeedAll(decoder, 1000, 2000, 3000); + Require(Flush(decoder) == H264Fixture::PictureHashes.size(), "a flush holds every picture for the destination bounds"); + constexpr std::uint8_t Guard = 0x5a; + std::vector memory(SlotSize * 2, Guard); + const VdecCoreMemoryBlock block{memory.data(), SlotSize, 256, 0}; + Require(sceVdecCoreMapMemoryBlock(decoder, &block, 1) == 0, "map one slot followed by guard bytes"); + VdecCoreFrameBuffer larger{nullptr, SlotSize * 2}; + const VdecCoreOutputInfo slot = Output(decoder, &larger); + Require(slot.frameBuffer == memory.data(), "a frame buffer with no address and a larger size goes to the slot"); + RequirePicture(slot, 0, SlotSize, 1000, 3000, "a picture in a slot under a larger caller size"); + Require(std::all_of(memory.begin() + static_cast(SlotSize), memory.end(), [](std::uint8_t byte) { return byte == Guard; }), "a picture in a slot under a larger caller size wrote past the slot"); + const std::uint32_t pictureSize = OutputPitch * H264Fixture::CodedHeight * 3 / 2; + std::vector tight(pictureSize + 0x200, Guard); + VdecCoreFrameBuffer small{tight.data(), tight.size()}; + const VdecCoreOutputInfo caller = Output(decoder, &small); + Require(caller.frameBuffer == tight.data(), "a caller frame buffer with room for the picture only"); + const auto unit = H264Fixture::DisplayOrderUnits[1]; + Require(caller.pictures[0].pts == 1000 + unit && caller.pictures[0].userData == 3000 + unit, "the picture in a caller frame buffer with room for the picture only"); + Require(VisibleHash(tight.data(), caller.framePitch, caller.frameHeight) == VisiblePictureHashes[1] && ExtendsLastRows(tight.data(), caller.framePitch, caller.frameHeight), "picture information too large for the room after a picture overwrote the picture"); + Require(std::all_of(tight.begin() + static_cast(pictureSize), tight.end(), [](std::uint8_t byte) { return byte == Guard; }), "picture information was written into a caller frame buffer with no room for it after the picture"); + Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the bounds decoder"); +} + +void CheckFlushResetAndSync() { + void* decoder = CreateAvc(static_cast(H264Fixture::Width)); + std::vector memory(SlotSize * 3); + const VdecCoreMemoryBlock block{memory.data(), memory.size(), 256, 0}; + Require(sceVdecCoreMapMemoryBlock(decoder, &block, 1) == 0, "map three slots"); + const std::vector slots = Slots(decoder); + + FeedAll(decoder, 1000, 2000, 3000); + const std::uint32_t held = Flush(decoder); + Require(held == H264Fixture::PictureHashes.size(), "a flush returns every held picture"); + std::uint32_t count = 0; + Require(sceVdecCoreSyncDecode(decoder, &count) == 0 && count == held, "sync after a flush counts the held pictures"); + std::uint32_t tried = 0; + Require(sceVdecCoreTrySyncDecode(decoder, &tried) == 0 && tried == held, "try-sync after a sync counts the held pictures"); + std::uint64_t pointers[2] = {7, 7}; + std::uint32_t forwarded = 0; + Require(sceVdecCoreSyncDecodeWptr(decoder, &forwarded, &pointers[0], &pointers[1]) == 0 && forwarded == held, "the write-pointer sync forwards to the sync"); + forwarded = 0; + Require(sceVdecCoreTrySyncDecodeWptr(decoder, &forwarded, &pointers[0], &pointers[1]) == 0 && forwarded == held, "the write-pointer try-sync forwards to the try-sync"); + Require(pointers[0] == 7 && pointers[1] == 7, "the write-pointer variants leave the pointers alone"); + + Require(Output(decoder, nullptr).frameBuffer == slots[0], "the first picture goes to the first slot"); + Require(Output(decoder, nullptr).frameBuffer == slots[1], "the second picture goes to the second slot"); + + FeedAll(decoder, 1000, 2000, 3000); + Require(sceVdecCoreResetDecoder(decoder, nullptr) == 0, "reset with queued input"); + count = 7; + Require(sceVdecCoreSyncDecode(decoder, &count) == 0 && count == 0, "a reset drops the queued input and its pictures"); + tried = 7; + Require(sceVdecCoreTrySyncDecode(decoder, &tried) == 0 && tried == 0, "try-sync after a reset"); + VdecCoreOutputInfo output{}; + Require(sceVdecCoreGetDecodeOutput(decoder, nullptr, 0, &output, nullptr) == NoPicture, "no picture after a reset"); + Require(Flush(decoder) == 0, "a flush after a reset holds nothing"); + + FeedAll(decoder, 1000, 2000, 3000); + Require(Flush(decoder) == H264Fixture::PictureHashes.size(), "the decoder decodes again after a reset"); + const VdecCoreOutputInfo restarted = Output(decoder, nullptr); + Require(restarted.frameBuffer == slots[0], "a reset starts the slots again"); + RequirePicture(restarted, 0, SlotSize, 1000, 3000, "the first picture after a reset"); + Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete the flushed decoder"); +} + +void CheckDeleteWithQueuedInput() { + void* decoder = CreateAvc(static_cast(H264Fixture::Width)); + for (int round = 0; round < 8; ++round) FeedAll(decoder, 1000, 2000, 3000); + Require(sceVdecCoreDeleteDecoder(decoder) == 0, "delete a decoder with queued input"); + void* held = CreateAvc(static_cast(H264Fixture::Width)); + FeedAll(held, 1000, 2000, 3000); + Require(Flush(held) == H264Fixture::PictureHashes.size(), "a flush before the delete holds every picture"); + Require(sceVdecCoreDeleteDecoder(held) == 0, "delete a decoder with held pictures"); +} + } int main() { @@ -183,5 +512,14 @@ int main() { CheckComputeResourceInfo(); CheckCreateAndDelete(); CheckMapMemoryBlock(); + CheckAccessUnitLength(); + CheckDecodeArguments(); + CheckEmptyQueue(); + CheckDecodeIntoSlots(); + CheckInvalidPts(); + CheckDestinations(); + CheckOutputStaysInItsDestination(); + CheckFlushResetAndSync(); + CheckDeleteWithQueuedInput(); return 0; } diff --git a/core/libs/tests/GuestVideoOutHdr.cpp b/core/libs/tests/GuestVideoOutHdr.cpp index 08fc189878..8cac7fabcb 100644 --- a/core/libs/tests/GuestVideoOutHdr.cpp +++ b/core/libs/tests/GuestVideoOutHdr.cpp @@ -87,7 +87,7 @@ int main() { AgcDriverSetSurfaceHdr10_nid_postfix(true); Require(OutputModeEventArrives(queue, 2'000'000), "HDR output becoming available does not fire the output mode event"); status = Status(handle); - Require(status.dynamicRange == 2 && status.flags == 1, "HDR output is not reported"); + Require(status.dynamicRange == 2 && status.flags == 0, "HDR output is not reported with flags 0"); Require(status.resolution != 0 && status.refreshRate == 3 && status.reserved[0] == 0 && status.reserved[1] == 0 && status.reserved[2] == 0, "the rest of the output status changed with HDR"); luminance = ToneMap(); Require(luminance.max_tone_map_luminance == 1015.0f && luminance.max_full_frame_tone_map_luminance == 640.0f && luminance.min_tone_map_luminance == 0.005f, "the display's luminance is not the tone-map luminance"); diff --git a/core/libs/tests/GuestVideoRecording.cpp b/core/libs/tests/GuestVideoRecording.cpp index ff7b12233e..930b42e6fb 100644 --- a/core/libs/tests/GuestVideoRecording.cpp +++ b/core/libs/tests/GuestVideoRecording.cpp @@ -10,6 +10,10 @@ namespace { constexpr int StatusNone = 0; +constexpr int InvalidArgument = static_cast(0x80a80003); +constexpr int NotOpen = static_cast(0x80a80006); +constexpr int NoVideoFormat = static_cast(0x80a80008); +constexpr int MinimumHeap = 0x1000; constexpr std::uint8_t Sentinel = 0xA5; void Require(bool condition, const char* message) { @@ -29,33 +33,109 @@ bool Throws(TCall call) { return false; } +VideoRecordingParam RecordingParam(std::uint32_t format) { + VideoRecordingParam param{}; + param.size = sizeof(param); + param.duration = 3600; + param.mode = 0; + param.affinityMask = 0x1fff; + param.threadPriority = 700; + param.option = 1; + param.format = format; + return param; +} + +int Query(const VideoRecordingParam& param) { + return sceVideoRecordingQueryMemSize(¶m); +} + } int main() { Require(sceVideoRecordingGetStatus() == StatusNone, "a recorder that was never opened reported a status"); Require(sceVideoRecordingGetStatus() == StatusNone, "the status changed between two reads"); + Require(sceVideoRecordingStart() == NotOpen, "the encoder started without a recorder"); + Require(sceVideoRecordingStop() == NotOpen, "the encoder stopped without a recorder"); + Require(sceVideoRecordingClose(0) == NotOpen, "a recorder that was never opened closed"); + Require(sceVideoRecordingClose(1) == NotOpen, "a recorder that was never opened closed with discard"); - std::array param{}; - param.fill(Sentinel); - std::array heap{}; + std::array query{}; + query.fill(Sentinel); + Require(Throws([&] { _sceVideoRecordingQueryParam_nid_postfix(reinterpret_cast(query.data())); }), "the default parameters were produced"); + + Require(sceVideoRecordingQueryMemSize(nullptr) == InvalidArgument, "a heap size was reported for no parameters"); + for (std::uint32_t format = 0x3; format <= 0x44; ++format) { + if (format == 0x10 || format == 0x11 || format == 0x28 || format == 0x40 || format == 0x41) continue; + Require(Query(RecordingParam(format)) == InvalidArgument, "an unknown video format was accepted"); + } + Require(Query(RecordingParam(0)) == NoVideoFormat, "the option without a video format was accepted"); + for (const std::uint64_t size : {0x0ull, 0x8ull, 0xfull}) { + VideoRecordingParam param = RecordingParam(1); + param.size = size; + Require(Query(param) == InvalidArgument, "a block shorter than its duration and mode was accepted"); + } + for (const std::uint64_t size : {0x10ull, 0x11ull, 0x18ull, 0x1cull, 0x1dull, 0x20ull}) { + VideoRecordingParam param = RecordingParam(1); + param.size = size; + Require(Query(param) == MinimumHeap, "a block without option and format did not ask for 0x1000 bytes"); + } + for (const std::uint64_t size : {0x21ull, 0x24ull}) { + VideoRecordingParam param = RecordingParam(1); + param.size = size; + Require(Query(param) == NoVideoFormat, "a block that ends before its format was accepted"); + } + for (const std::uint32_t duration : {0u, 3601u, 7200u, 0xffffffffu}) { + VideoRecordingParam param = RecordingParam(1); + param.duration = duration; + Require(Query(param) == InvalidArgument, "a duration outside 1 to 3600 was accepted"); + } + for (const std::uint32_t mode : {2u, 3u}) { + VideoRecordingParam param = RecordingParam(1); + param.mode = mode; + Require(Query(param) == InvalidArgument, "a mode above 1 was accepted"); + } + for (const std::uint32_t option : {2u, 3u}) { + VideoRecordingParam param = RecordingParam(1); + param.option = option; + Require(Query(param) == InvalidArgument, "an option above 1 was accepted"); + param.format = 0; + Require(Query(param) == InvalidArgument, "an option above 1 without a format was accepted"); + } + VideoRecordingParam minimal = RecordingParam(0); + minimal.option = 0; + Require(Query(minimal) == MinimumHeap, "a block without option and format did not ask for 0x1000 bytes"); + for (const std::uint32_t value : {0u, 1u, 2u, 3u}) { + VideoRecordingParam param = RecordingParam(0); + param.option = 0; + param.outputMode = static_cast(value); + param.threadPriority = static_cast(value); + param.affinityMask = value; + param.reserved40 = value; + param.reserved44 = value; + Require(Query(param) == MinimumHeap, "fields the heap size does not depend on changed it"); + } + + std::array heap{}; heap.fill(Sentinel); - - 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", 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"); - Require(Throws([&] { sceVideoRecordingClose(1); }), "the recorder closed with discard"); - - for (const std::uint8_t value : param) Require(value == Sentinel, "the parameters were written"); + const VideoRecordingParam video = RecordingParam(1); + VideoRecordingParam refused = RecordingParam(1); + refused.duration = 0; + Require(sceVideoRecordingOpen(nullptr, &video, heap.data(), static_cast(heap.size())) == InvalidArgument, "the recorder opened without a path"); + Require(sceVideoRecordingOpen("clip.mp4", nullptr, heap.data(), static_cast(heap.size())) == InvalidArgument, "the recorder opened without parameters"); + Require(sceVideoRecordingOpen("clip.mp4", &video, nullptr, static_cast(heap.size())) == InvalidArgument, "the recorder opened without a heap"); + Require(sceVideoRecordingOpen("clip.mp4", &video, heap.data(), -1) == InvalidArgument, "the recorder opened with a negative heap size"); + Require(sceVideoRecordingOpen("clip.mp4", &refused, heap.data(), 0x7fffffff) == InvalidArgument, "the recorder opened with parameters QueryMemSize refuses"); + Require(sceVideoRecordingOpen("clip.mp4", &minimal, heap.data(), MinimumHeap - 1) == InvalidArgument, "the recorder opened with a heap smaller than QueryMemSize asks"); + VideoRecordingParam optionOnly = RecordingParam(0); + Require(sceVideoRecordingOpen("clip.mp4", &optionOnly, heap.data(), static_cast(heap.size())) == NoVideoFormat, "the recorder opened with the option and no format"); for (const std::uint8_t value : heap) Require(value == Sentinel, "the heap was written"); Require(sceVideoRecordingGetStatus() == StatusNone, "a failed open changed the status"); + Require(sceVideoRecordingStart() == NotOpen, "a failed open left a recorder to start"); + Require(sceVideoRecordingClose(1) == NotOpen, "a failed open left a recorder to close"); - 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"); + Require(sceVideoRecordingGetInfo(0xa001, nullptr, 0) == NotOpen, "info 0xa001 validation"); + Require(sceVideoRecordingGetInfo(0xa005, nullptr, 0) == NotOpen, "info 0xa005 validation"); + Require(sceVideoRecordingGetInfo(0, nullptr, 0) == InvalidArgument, "unknown info validation"); std::puts("VideoRecording tests passed"); return 0; } diff --git a/core/libs/tests/GuestVideodec2HevcAliases.cpp b/core/libs/tests/GuestVideodec2HevcAliases.cpp new file mode 100644 index 0000000000..2d13a8be37 --- /dev/null +++ b/core/libs/tests/GuestVideodec2HevcAliases.cpp @@ -0,0 +1,81 @@ +#include "prx/libSceVideodec2/include/Api.hpp" +#include "H264Fixture.hpp" +#include "HevcFixture.hpp" +#include +#include +#include +#include +#include +#include + +using namespace SceVideodec2; + +static void Require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} + +static auto Fields(const DecoderMemoryInfo& memory) { + return std::tie(memory.thisSize, memory.cpuMemorySize, memory.cpuMemory, memory.gpuMemorySize, memory.gpuMemory, + memory.cpuGpuMemorySize, memory.cpuGpuMemory, memory.maxFrameBufferSize, memory.frameBufferAlignment); +} + +template +static std::string Failure(TAction action) { + try { + action(); + } catch (const std::runtime_error& error) { + return error.what(); + } + throw std::runtime_error("invalid argument did not throw"); +} + +int main() { + try { + const DecoderConfigInfo config{sizeof(DecoderConfigInfo), 1, 974921, 1, 0, H264Fixture::Width, H264Fixture::Height, 4, 1}; + DecoderMemoryInfo generic{sizeof(DecoderMemoryInfo)}, alias{sizeof(DecoderMemoryInfo)}; + const auto expected = sceVideodec2QueryDecoderMemoryInfo_nid_postfix(&config, &generic); + Require(sceVideodec2QueryHevcDecoderMemoryInfo(&config, &alias) == expected && expected == 0, "memory query result differs"); + Require(Fields(alias) == Fields(generic), "memory query fields differ"); + Require(Failure([&] { sceVideodec2QueryHevcDecoderMemoryInfo(nullptr, &alias); }) == + Failure([&] { sceVideodec2QueryDecoderMemoryInfo_nid_postfix(nullptr, &generic); }), "query argument error differs"); + std::uint64_t handle = 0, reference = 0; + Require(Failure([&] { sceVideodec2CreateHevcDecoder(&config, nullptr, &handle); }) == + Failure([&] { sceVideodec2CreateDecoder_nid_postfix(&config, nullptr, &reference); }), "create argument error differs"); + Require(sceVideodec2CreateHevcDecoder(&config, &alias, &handle) == 0 && handle != 0, "alias creation failed"); + Require(sceVideodec2CreateDecoder_nid_postfix(&config, &generic, &reference) == 0, "generic creation failed"); + std::vector storage; + const auto buffer = HevcFixture::AlignedFrameStorage(storage, alias.maxFrameBufferSize, alias.frameBufferAlignment); + FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false}; + OutputInfo output{sizeof(OutputInfo)}; + const InputData empty{sizeof(InputData), nullptr, 0, 0, 0, 0}; + const auto error = sceVideodec2Decode_nid_postfix(reference, &empty, &frame, &output); + Require(error != 0 && sceVideodec2Decode_nid_postfix(handle, &empty, &frame, &output) == error, "decoder error code differs"); + Require(sceVideodec2DeleteDecoder_nid_postfix(reference) == 0, "generic deletion failed"); + std::size_t pictures = 0; + const auto take = [&] { + Require(frame.isAccepted == output.isValid, "frame acceptance differs"); + if (!output.isValid) return false; + Require(pictures < HevcFixture::HevcHashes.size(), "too many pictures"); + Require(output.codecType == config.codecType && !output.isErrorFrame && output.frameWidth == H264Fixture::Width && + output.frameHeight == H264Fixture::Height && output.framePitch == 256 && output.frameBuffer == buffer.data(), "picture geometry differs"); + Require(H264Fixture::hashNv12(buffer.data(), output.framePitch, output.frameHeight) == HevcFixture::HevcHashes[pictures], "picture hash differs"); + ++pictures; + return true; + }; + const auto units = HevcFixture::hevcUnits(HevcFixture::HevcStream.data(), HevcFixture::HevcStream.size()); + for (const auto& unit : units) { + const InputData input{sizeof(InputData), unit.data(), unit.size(), 0, 0, 0}; + Require(sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output) == 0, "decode failed"); + take(); + } + do { + Require(sceVideodec2Flush_nid_postfix(handle, &frame, &output) == 0, "flush failed"); + } while (take()); + Require(pictures == HevcFixture::HevcHashes.size(), "missing pictures"); + Require(sceVideodec2DeleteDecoder_nid_postfix(handle) == 0, "alias deletion failed"); + return 0; + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + return 1; + } +} diff --git a/core/libs/tests/GuestVideodec2Unimplemented.cpp b/core/libs/tests/GuestVideodec2Unimplemented.cpp index 090fdf8327..2cdb779066 100644 --- a/core/libs/tests/GuestVideodec2Unimplemented.cpp +++ b/core/libs/tests/GuestVideodec2Unimplemented.cpp @@ -29,7 +29,5 @@ int main() { passed = ReportsNotImplemented("sceVideodec2PresetDecoderConfigInfoHevc", [] { return sceVideodec2PresetDecoderConfigInfoHevc(); }) && passed; passed = ReportsNotImplemented("sceVideodec2PresetDecoderConfigInfoVp9", [] { return sceVideodec2PresetDecoderConfigInfoVp9(); }) && passed; passed = ReportsNotImplemented("sceVideodec2CreateDecoderBid", [] { return sceVideodec2CreateDecoderBid(); }) && passed; - passed = ReportsNotImplemented("sceVideodec2CreateHevcDecoder", [] { return sceVideodec2CreateHevcDecoder(); }) && passed; - passed = ReportsNotImplemented("sceVideodec2QueryHevcDecoderMemoryInfo", [] { return sceVideodec2QueryHevcDecoderMemoryInfo(); }) && passed; return passed ? 0 : 1; } diff --git a/core/libs/tests/GuestVoice.cpp b/core/libs/tests/GuestVoice.cpp index 112adf8c4d..2c0e4c440f 100644 --- a/core/libs/tests/GuestVoice.cpp +++ b/core/libs/tests/GuestVoice.cpp @@ -37,6 +37,9 @@ void TestNotInitialized() { Require(sceVoiceCreatePort(&id, ¶m) == NotInitialized); Require(id == 0xA5A5A5A5u); Require(sceVoiceGetPortInfo(0, nullptr) == NotInitialized); + Require(sceVoiceGetPortInfo(128, nullptr) == NotInitialized); + Require(sceVoiceReadFromOPort(0, nullptr, nullptr) == NotInitialized); + Require(sceVoiceReadFromOPort(128, nullptr, nullptr) == NotInitialized); VoicePortInfo info{}; std::memset(&info, 0xA5, sizeof(info)); VoicePortInfo sentinel = info; @@ -207,6 +210,8 @@ int main() { VoicePortInfo after{}; Require(sceVoiceGetPortInfo(input, &after) == 0); + Require(sceVoiceGetPortInfo(input, nullptr) == invalidArgument); + Require(sceVoiceGetPortInfo(output, nullptr) == invalidArgument); Require(after.port_type == before.port_type && after.state == before.state && after.frame_size == before.frame_size); float volume = 0; bool muted = false; @@ -214,6 +219,9 @@ int main() { Require(sceVoiceGetMuteFlag(output, &muted) == 0 && muted); std::uint8_t data[64]{}; std::uint32_t size = sizeof(data); + Require(sceVoiceReadFromOPort(output, nullptr, &size) == invalidArgument); + Require(sceVoiceReadFromOPort(output, data, nullptr) == invalidArgument); + Require(sceVoiceReadFromOPort(output, nullptr, nullptr) == invalidArgument); Require(sceVoiceReadFromOPort(output, data, &size) == 0 && size == 0); Require(sceVoiceDisconnectIPortFromOPort(input, output) == 0); @@ -423,6 +431,14 @@ int main() { Require(sceVoiceSetVolume(128, 1.0f) == invalidPort); Require(sceVoiceSetMuteFlag(128, 1) == invalidPort); Require(sceVoiceDeletePort(128) == invalidPort); + Require(sceVoiceGetPortInfo(128, nullptr) == invalidPort); + Require(sceVoiceReadFromOPort(128, nullptr, nullptr) == invalidPort); + Require(sceVoiceReadFromOPort(128, data, nullptr) == invalidPort); + Require(sceVoiceReadFromOPort(128, nullptr, &size) == invalidPort); + Require(sceVoiceGetPortInfo(127, nullptr) == invalidArgument); + Require(sceVoiceReadFromOPort(127, nullptr, nullptr) == invalidArgument); + Require(sceVoiceReadFromOPort(127, data, nullptr) == invalidArgument); + Require(sceVoiceReadFromOPort(127, nullptr, &size) == invalidArgument); Require(sceVoiceGetVolume(127, nullptr) == invalidArgument); Require(sceVoiceGetBitRate(127, nullptr) == invalidArgument); Require(sceVoiceGetMuteFlag(127, nullptr) == invalidArgument); diff --git a/core/libs/tests/GuestVoicePortAttr.cpp b/core/libs/tests/GuestVoicePortAttr.cpp index 3bdcc5e6de..b7ff03dbaf 100644 --- a/core/libs/tests/GuestVoicePortAttr.cpp +++ b/core/libs/tests/GuestVoicePortAttr.cpp @@ -49,7 +49,13 @@ int main() { 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)) { + if (type == 0 && attr == 1001) { + buffer = original; + Require(sceVoiceGetPortAttr(port, attr, buffer.data() + 1, size) == 0); + Require(buffer[0] == original[0] && buffer[1] == 0 && buffer[2] == original[2]); + Require(buffer[3] == original[3] && buffer[4] == original[4] && buffer[5] == original[5]); + buffer = original; + } else 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); diff --git a/core/libs/tests/GuestWcscmpBoundary.cpp b/core/libs/tests/GuestWcscmpBoundary.cpp new file mode 100644 index 0000000000..156b9b92cf --- /dev/null +++ b/core/libs/tests/GuestWcscmpBoundary.cpp @@ -0,0 +1,94 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestWcscmpBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +static int Sign(int value) { + return (value > 0) - (value < 0); +} + +int main() { + Buffer first, second; + for (std::size_t length = 0; length <= 128; ++length) { + auto* left = first.At(length + 1); + auto* right = second.At(length + 1); + for (std::size_t i = 0; i < length; ++i) left[i] = static_cast(0x100 + i * 257); + left[length] = 0; + const std::u16string saved(left, length + 1); + for (std::size_t position = 0; position <= length; ++position) { + for (char16_t value : {0, 1, 0xff, 0x100, 0x8000, 0xd800, 0xffff}) { + if (position == length && value != 0) continue; + std::copy(saved.begin(), saved.end(), right); + right[position] = value; + int expected = 0; + for (std::size_t i = 0; i <= length; ++i) { + if (left[i] != right[i]) { expected = left[i] > right[i] ? 1 : -1; break; } + if (left[i] == 0) break; + } + const std::u16string other(right, length + 1); + Require(Sign(wcscmp_nid_postfix(left, right)) == expected); + Require(Sign(wcscmp_nid_postfix(right, left)) == -expected); + Require(std::equal(saved.begin(), saved.end(), left) && std::equal(other.begin(), other.end(), right)); + } + } + } +} diff --git a/core/libs/tests/GuestWcslenBoundary.cpp b/core/libs/tests/GuestWcslenBoundary.cpp new file mode 100644 index 0000000000..af78a54e6b --- /dev/null +++ b/core/libs/tests/GuestWcslenBoundary.cpp @@ -0,0 +1,75 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestWcslenBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +int main() { + Buffer input; + const std::array values{0x100, 1, 0x8000, 0xd800, 0xdc00, 0xffff}; + for (std::size_t length = 0; length <= 256; ++length) { + auto* source = input.At(length + 1); + for (std::size_t i = 0; i < length; ++i) source[i] = values[(i + length) % values.size()]; + source[length] = 0; + const std::u16string saved(source, length + 1); + Require(wcslen_nid_postfix(source) == length); + Require(std::equal(saved.begin(), saved.end(), source)); + } +} diff --git a/core/libs/tests/GuestWcsncatExtent.cpp b/core/libs/tests/GuestWcsncatExtent.cpp new file mode 100644 index 0000000000..a4d00e44ea --- /dev/null +++ b/core/libs/tests/GuestWcsncatExtent.cpp @@ -0,0 +1,93 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestWcsncatExtent check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +int main() { + Buffer input, output; + for (std::size_t limit = 0; limit <= 128; ++limit) { + for (std::size_t length : {std::size_t{0}, std::size_t{1}, limit, limit + 3}) { + auto* source = input.At(length + 1); + for (std::size_t i = 0; i < length; ++i) source[i] = static_cast(0x100 + i * 257); + source[length] = 0; + const std::u16string saved(source, length + 1); + const auto copied = (std::min)(length, limit); + for (std::size_t prefix : {0u, 1u, 15u, 31u}) { + auto* destination = output.At(prefix + copied + 1); + std::memset(destination - 8, 0xa5, (prefix + copied + 9) * sizeof(char16_t)); + const std::u16string start(prefix, 0xd800); + std::copy(start.begin(), start.end(), destination); + destination[prefix] = 0; + const auto expected = start + saved.substr(0, copied) + std::u16string(1, 0); + Require(wcsncat_nid_postfix(destination, source, limit) == destination); + Require(std::equal(expected.begin(), expected.end(), destination)); + Require(std::equal(saved.begin(), saved.end(), source)); + Prefix(destination); + } + } + } +} diff --git a/core/libs/tests/GuestWideConversion.cpp b/core/libs/tests/GuestWideConversion.cpp index db18242a8a..073fa64be7 100644 --- a/core/libs/tests/GuestWideConversion.cpp +++ b/core/libs/tests/GuestWideConversion.cpp @@ -56,8 +56,46 @@ static void CheckStoul() { CheckStoul(u" 5", 10, 0, 0, 0, "non-ASCII space is not skipped"); CheckStoul(u"", 10, 0, 0, 0, "empty string"); Require(_WStoul_nid_postfix(u"9", nullptr, 10) == 9, "_WStoul accepts a null end pointer"); - Require(Throws([] { _WStoul_nid_postfix(u"1", nullptr, 1); }), "_WStoul throws for base 1"); - Require(Throws([] { _WStoul_nid_postfix(u"1", nullptr, 37); }), "_WStoul throws for base 37"); + CheckStoul(u" 1", 1, 0, 0, 0, "rejects base 1 without consuming or setting errno"); + CheckStoul(u" 1", 37, 0, 0, 0, "rejects base 37 without consuming or setting errno"); + CheckStoul(u" 1", -2, 0, 0, 0, "rejects a negative base without consuming or setting errno"); +} + +template +static void CheckConversion(TConvert convert, const char16_t* text, long argument, TValue value, std::size_t consumed, int error, const char* message) { + char16_t* end = nullptr; + errno = 0; + const TValue result = convert(text, &end, argument); + if (result != value || end != text + consumed || errno != error) { + std::fprintf(stderr, "%s: consumed %lld, errno %d\n", message, static_cast(end - text), errno); + ++failures; + } +} + +static void CheckDinkumwareConversions() { + constexpr std::uint64_t unsignedMax = std::numeric_limits::max(); + constexpr long long signedMax = std::numeric_limits::max(); + constexpr long long signedMin = std::numeric_limits::min(); + const auto stoull = [](const char16_t* text, char16_t** end, long base) { return _WStoull_nid_postfix(text, end, static_cast(base)); }; + const auto stoll = [](const char16_t* text, char16_t** end, long base) { return _WStoll_nid_postfix(text, end, static_cast(base)); }; + CheckConversion(stoull, u" -0x10", 16, 18446744073709551600ULL, 6, 0, "_WStoull negates after the prefix"); + CheckConversion(stoull, u"0x", 0, 0, 0, 0, "_WStoull prefix without digits converts nothing"); + CheckConversion(stoull, u"-18446744073709551616", 10, unsignedMax, 21, ERANGE, "_WStoull overflow ignores the sign"); + CheckConversion(stoull, u"0000000000000000000000001", 10, 1, 25, 0, "_WStoull does not count leading zeros"); + CheckConversion(stoll, u"9223372036854775807", 10, signedMax, 19, 0, "_WStoll maximum"); + CheckConversion(stoll, u"9223372036854775808", 10, signedMax, 19, ERANGE, "_WStoll positive overflow"); + CheckConversion(stoll, u"-9223372036854775808", 10, signedMin, 20, 0, "_WStoll minimum"); + CheckConversion(stoll, u"-9223372036854775809", 10, signedMin, 20, ERANGE, "_WStoll negative overflow"); + CheckConversion(stoll, u"- 5", 10, -5, 3, 0, "_WStoll skips white space after the sign"); + CheckConversion(stoll, u"--1", 10, signedMin, 3, ERANGE, "_WStoll second sign overflows"); + CheckConversion(stoll, u" +0x7f", 0, 127, 7, 0, "_WStoll base detection"); + CheckConversion(stoll, u"", 2, 0, 0, 0, "_WStoll empty string"); + CheckConversion(_WStod_nid_postfix, u" 1.5", 2, 150.0, 4, 0, "_WStod scales by a power of ten"); + CheckConversion(_WStod_nid_postfix, u"25x", -1, 2.5, 2, 0, "_WStod stops at a non-digit"); + CheckConversion(_WStod_nid_postfix, u"1e400", 0, std::numeric_limits::infinity(), 5, ERANGE, "_WStod overflow"); + CheckConversion(_WStod_nid_postfix, u"\u30001", 0, 0.0, 0, 0, "_WStod does not skip a non-ASCII space"); + CheckConversion(_WStof_nid_postfix, u" 1.5", 2, 150.0f, 4, 0, "_WStof scales by a power of ten"); + CheckConversion(_WStold_nid_postfix, u"-2.5e1", 3, -25000.0L, 6, 0, "_WStold scales by a power of ten"); } static void CheckClassification() { @@ -260,6 +298,7 @@ static void CheckUnsizedScan() { int main() { CheckStoul(); + CheckDinkumwareConversions(); CheckClassification(); CheckScan(); CheckUnsizedScan(); diff --git a/core/libs/tests/GuestWideFormatting.cpp b/core/libs/tests/GuestWideFormatting.cpp index 4fa77c247d..45bed871ec 100644 --- a/core/libs/tests/GuestWideFormatting.cpp +++ b/core/libs/tests/GuestWideFormatting.cpp @@ -3,6 +3,7 @@ #include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestTime.hpp" #include +#include #include #include #include @@ -113,7 +114,12 @@ static void CheckPointer() { buffer == std::u16string(u"[ 1234][1234 ][00001234][0000001a]"), "vswprintf %p width and precision"); Require(Format(buffer, 64, u"[%.0p][%p]", address(0), address(0)) == 5 && buffer == std::u16string(u"[][0]"), "vswprintf %p with a zero precision prints nothing"); - Require(Format(buffer, 64, u"[%lp]", address(0x1234)) == 6 && buffer == std::u16string(u"[1234]"), "vswprintf %lp ignores the l"); + Require(Format(buffer, 64, u"[%lp]", address(0x1234)) == 6 && buffer == std::u16string(u"[1234]"), "vswprintf %lp prints the pointer as %p does"); + Require(Format(buffer, 64, u"[%lp]", address(0)) == 3 && buffer == std::u16string(u"[0]"), "vswprintf %lp of a null pointer"); + Require(Format(buffer, 64, u"[%5.0p][%05.3p][%.0p]", address(0), address(0x1f), address(0x1f)) == 18 && + buffer == std::u16string(u"[ ][ 01f][1f]"), "vswprintf %p width beside a zero precision, 0 beside a precision, and %.0p of a pointer"); + Require(snwprintf_s_nid_postfix(buffer, 16, u"[%lp]", address(0x1f)) == 4 && buffer == std::u16string(u"[1f]"), "snwprintf_s %lp prints the pointer as %p does"); + Require(snwprintf_s_nid_postfix(buffer, 16, u"[%lp]", address(0)) == 3 && buffer == std::u16string(u"[0]"), "snwprintf_s %lp of a null pointer"); } static void CheckCount() { @@ -121,7 +127,7 @@ static void CheckCount() { int count = -7; Require(Format(buffer, 16, u"ab%ncd", &count) == 4 && buffer == std::u16string(u"abcd") && count == 2, "vswprintf %n"); count = -7; - Require(Format(buffer, 16, u"%s%n!", "\xf0\x9f\x98\x80", &count) == 3 && count == 2, "vswprintf %n counts UTF-16 units"); + Require(Format(buffer, 16, u"%s%n!", "\xed\xa0\xbd\xed\xb8\x80", &count) == 3 && count == 2, "vswprintf %n counts UTF-16 units"); count = -7; Require(Format(buffer, 16, u"%n", &count) == 0 && buffer[0] == 0 && count == 0, "vswprintf %n at start"); unsigned char bytes[2] = {0xaa, 0xaa}; @@ -208,8 +214,25 @@ static void CheckSwprintf() { Require(buffer == std::u16string(u"42-ab") && buffer[6] == u'x', "swprintf output"); Require(swprintf_nid_postfix(buffer, 8, u"中%ls%s", u"文", "\xc3\xa9") == 3, "swprintf non-ASCII length"); Require(buffer == std::u16string(u"中文é"), "swprintf non-ASCII output"); - Require(swprintf_nid_postfix(buffer, 8, u"%s", "\xf0\x9f\x98\x80") == 2, "swprintf surrogate pair length"); - Require(buffer == std::u16string(u"\U0001f600"), "swprintf surrogate pair output"); + Require(swprintf_nid_postfix(buffer, 8, u"%s", "\xed\xa0\xbd\xed\xb8\x80") == 2 && buffer == std::u16string(u"\U0001f600"), + "swprintf decodes each 3-byte surrogate"); + errno = 0; + Require(swprintf_nid_postfix(buffer, 8, u"[%s]", "\xf0\x9f\x98\x80") == -1 && errno == 86 && buffer == std::u16string(u"["), + "swprintf rejects a 4-byte sequence and keeps the output before it"); + for (const char* invalid : {"\x80", "\xc3", "\xc3x", "\xe2\x82", "\xff", "\xf8\x88\x80\x80\x80", "\xc3\xa9\xc3"}) { + errno = 0; + Require(swprintf_nid_postfix(buffer, 8, u"x%sy", invalid) == -1 && errno == 86 && buffer == std::u16string(u"x"), "swprintf rejects invalid UTF-8"); + } + Require(swprintf_nid_postfix(buffer, 8, u"[%s]", "\xc0\x80" "A") == 2 && buffer == std::u16string(u"[]"), "an overlong NUL ends the string"); + Require(swprintf_nid_postfix(buffer, 8, u"[%s|%s]", "\xc1\x81", "\xe0\x81\x81") == 5 && buffer == std::u16string(u"[A|A]"), "overlong forms decode"); + Require(swprintf_nid_postfix(buffer, 8, u"[%.1s]", "A\x80") == 3 && buffer == std::u16string(u"[A]"), "the precision stops before an invalid byte"); + errno = 0; + Require(swprintf_nid_postfix(buffer, 8, u"[%.2s]", "A\x80") == -1 && errno == 86, "the precision reaches an invalid byte"); + Require(swprintf_nid_postfix(buffer, 8, u"[%.0s]", "AB") == 2 && buffer == std::u16string(u"[]"), "precision zero writes nothing"); + Require(swprintf_nid_postfix(buffer, 8, u"[%3s|%-3.0s]", "", "AB") == 3 && buffer == std::u16string(u"[|]"), "an empty narrow field is not padded"); + Require(swprintf_nid_postfix(buffer, 8, u"[%3s]", "A\xc0\x80") == 5 && buffer == std::u16string(u"[ A]"), "a field with characters is padded"); + errno = 0; + Require(swprintf_nid_postfix(buffer, 8, u"[%.0s]", "\x80") == -1 && errno == 86, "precision zero still decodes the first character"); Require(swprintf_nid_postfix(buffer, 8, u"") == 0 && buffer[0] == 0, "swprintf empty format"); for (auto& unit : buffer) unit = u'x'; Require(swprintf_nid_postfix(buffer, 3, u"%ls", u"abcd") < 0, "swprintf truncation result"); @@ -242,7 +265,84 @@ static bool WcsftimeThrows(char16_t* buffer, const char16_t* format, const Guest return false; } +static void CheckWcsftimeCapacities(const char16_t* format, const std::u16string& expected, const GuestTm& time) { + for (std::size_t capacity = 0; capacity <= expected.size() + 2; ++capacity) { + std::u16string storage(expected.size() + 5, u'\uffff'); + int& error = *__error_nid_postfix(); + error = 5; + const std::size_t result = wcsftime_nid_postfix(reinterpret_cast(storage.data() + 1), capacity, reinterpret_cast(format), &time); + Require(error == 5 && result < (capacity ? capacity : 1), "wcsftime bounded result and errno"); + Require(storage[0] == u'\uffff', "wcsftime leading guard"); + if (capacity > expected.size()) { + Require(result == expected.size(), "wcsftime complete result"); + for (std::size_t index = 0; index < expected.size(); ++index) + Require(storage[index + 1] == expected[index], "wcsftime complete output"); + Require(storage[expected.size() + 1] == 0, "wcsftime terminator"); + } + for (std::size_t index = capacity + 1; index < storage.size(); ++index) + Require(storage[index] == u'\uffff', "wcsftime trailing guard"); + } +} + +static void CheckWcsftimeFirmwarePieces() { + GuestTm time{}; + time.tm_year = 124; + time.tm_mon = 1; + time.tm_mday = 29; + time.tm_wday = 4; + time.tm_yday = 59; + time.tm_hour = 13; + time.tm_min = 5; + time.tm_sec = 9; + struct Case { const char16_t* format; std::size_t capacity; std::size_t result; std::u16string units; }; + const Case cases[] = { + {u"%Y-%m-%d", 0, 0, u""}, {u"%Y-%m-%d", 1, 0, u"-"}, + {u"%Y-%m-%d", 2, 0, u"--"}, {u"%Y-%m-%d", 3, 0, u"-02"}, + {u"%Y-%m-%d", 4, 0, u"2024"}, {u"%Y-%m-%d", 5, 0, u"2024-"}, + {u"%Y-%m-%d", 6, 0, u"2024--"}, {u"%Y-%m-%d", 7, 0, u"2024-02"}, + {u"%Y-%m-%d", 8, 0, u"2024-02-"}, + {u"%Y-%m-%d", 9, 8, std::u16string(u"2024-02-\0", 9)}, + {u"%Y-%m-%d", 10, 0, u"2024-02-29"}, + {u"%Y-%m-%d", 11, 10, std::u16string(u"2024-02-29\0", 11)}, + {u"%F", 9, 0, std::u16string(u"\0" u"024-02-2", 9)}, + {u"%F|%S|", 5, 4, std::u16string(u"|09|\0", 5)}, + {u"ab%Ycd", 5, 4, std::u16string(u"abcd\0", 5)}, + {u"%OYx", 3, 1, std::u16string(u"x\0" u"2", 3)}, + {u"abc", 2, 0, u"ab"}, {u"", 1, 0, std::u16string(1, 0)}, + {u"%Q", 1, 0, std::u16string(1, 0)}, + {u"\U0001f600%Y", 3, 2, std::u16string(u"\U0001f600\0", 3)}, + {u"%Y\U0001f600", 3, 2, std::u16string(u"\U0001f600\0", 3)} + }; + for (const auto& item : cases) { + std::u16string storage(item.capacity + 4, u'\ua5a5'); + int& error = *__error_nid_postfix(); + error = 5; + const auto result = wcsftime_nid_postfix(reinterpret_cast(storage.data() + 1), item.capacity, + reinterpret_cast(item.format), &time); + Require(result == item.result && error == 5, "wcsftime firmware piece result and errno"); + Require(storage[0] == u'\ua5a5', "wcsftime firmware leading guard"); + for (std::size_t index = 0; index < item.units.size(); ++index) + Require(storage[index + 1] == item.units[index], "wcsftime firmware piece units"); + for (std::size_t index = item.units.size() + 1; index < storage.size(); ++index) + Require(storage[index] == u'\ua5a5', "wcsftime firmware untouched units"); + } + time.tm_mon = 8; + time.tm_wday = 5; + char16_t buffer[12]; + for (auto& unit : buffer) unit = u'\ua5a5'; + int& error = *__error_nid_postfix(); + error = 5; + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 9, + reinterpret_cast(u"%A %B"), &time) == 7 && error == 5 + && std::u16string(buffer, 9) == std::u16string(u"Friday \0e", 9) && buffer[9] == u'\ua5a5', "wcsftime firmware trailing partial conversion"); +} + static void CheckWcsftime() { + const char* oldZone = getenv_nid_postfix("TZ"); + const bool hadZone = oldZone != nullptr; + const std::string savedZone = hadZone ? oldZone : ""; + Require(setenv_nid_postfix("TZ", "UTC0", 1) == 0, "wcsftime fixed guest time zone"); + CheckWcsftimeFirmwarePieces(); GuestTm time{}; time.tm_sec = 9; time.tm_min = 5; @@ -262,6 +362,8 @@ static void CheckWcsftime() { {u"%a %b %j %p %Z", "%a %b %j %p %Z"}, {u"%%", "%%"}, {u"%k|%l|%v|%+", "%k|%l|%v|%+"}, + {u"%A|%B|%h|%C|%e|%I|%n|%t|%U|%u|%V|%G|%g|%W|%w|%y|%z", "%A|%B|%h|%C|%e|%I|%n|%t|%U|%u|%V|%G|%g|%W|%w|%y|%z"}, + {u"%s", "%s"}, {u"\u00e9t\u00e9 %Y", "\xe9t\xe9 %Y"}, }; for (const auto& item : cases) { @@ -272,7 +374,19 @@ static void CheckWcsftime() { 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"); + CheckWcsftimeCapacities(item.wide, std::u16string(wide, length), time); } + CheckWcsftimeCapacities(u"", u"", time); + CheckWcsftimeCapacities(u"abc", u"abc", time); + CheckWcsftimeCapacities(u"\u4e2d\u20ac\U0001f600%F!", u"\u4e2d\u20ac\U0001f6002024-02-29!", time); + const char16_t surrogates[] = {0xd800, u'%', u'Y', 0xdc00, 0}; + const char16_t surrogateOutput[] = {0xd800, u'2', u'0', u'2', u'4', 0xdc00, 0}; + CheckWcsftimeCapacities(surrogates, surrogateOutput, time); + CheckWcsftimeCapacities(u"%c|%D|%F|%r|%R|%T|%x|%X", u"Thu Feb 29 13:05:09 2024|02/29/24|2024-02-29|01:05:09 PM|13:05|13:05:09|02/29/24|13:05:09", time); + CheckWcsftimeCapacities(u"%Ea|%OX|%EEY|%-d|%_m|%0e|%--d|%q|%\u4e2d|%", u"Thu|13:05:09|%EY|%-d|%_m|%0e|%--d|%q|%\u4e2d|%", time); + GuestTm early = time; + early.tm_year = -1901; + CheckWcsftimeCapacities(u"%Y/%C/%y", u"0000/00/99", early); char16_t buffer[32]; 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"); @@ -284,21 +398,30 @@ static void CheckWcsftime() { int& error = *__error_nid_postfix(); for (auto& unit : buffer) unit = u'x'; error = 5; - 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(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), 19, reinterpret_cast(u"%Y-%m-%d %H:%M:%S"), &time) == 0 && error == 5 + && std::u16string(buffer, 19) == u"2024-02-29 13:05:09" && buffer[19] == u'x', "wcsftime overflow"); + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 1, reinterpret_cast(u"%Y"), &time) == 0 && error == 5 && buffer[0] == 0, "wcsftime one unit"); + buffer[0] = u'x'; 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(reinterpret_cast(buffer), 32, reinterpret_cast(u""), &time) == 0 && error == 5 && buffer[0] == 0, "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"); + "wcsftime copies a character above 0xff"); + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), 7, reinterpret_cast(u"%Y \u20ac"), &time) == 6, "wcsftime counts wide units"); for (auto& unit : buffer) unit = u'x'; error = 5; - Require(wcsftime_nid_postfix(reinterpret_cast(buffer), SIZE_MAX, reinterpret_cast(u"%Y"), &time) == 0 && error == 22 && buffer[0] == u'x', - "wcsftime oversized buffer"); + const std::size_t oversized = SIZE_MAX / static_cast(_Getmbcurmax_nid_postfix()); + for (const std::size_t capacity : {oversized, oversized + 1, SIZE_MAX}) { + error = 5; + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), capacity, reinterpret_cast(u"%Y"), &time) == 0 && error == 22 && buffer[0] == u'x', + "wcsftime oversized buffer"); + } + error = 5; + Require(wcsftime_nid_postfix(reinterpret_cast(buffer), oversized - 1, reinterpret_cast(u"%Y"), &time) == 4 && error == 5 + && buffer == std::u16string(u"2024") && buffer[5] == u'x', "wcsftime largest accepted capacity"); Require(WcsftimeThrows(buffer, nullptr, &time), "wcsftime null format"); Require(WcsftimeThrows(buffer, u"%Y", nullptr), "wcsftime null time"); + Require((hadZone ? setenv_nid_postfix("TZ", savedZone.c_str(), 1) : unsetenv_nid_postfix("TZ")) == 0, "wcsftime restore guest time zone"); } static void CheckAlternateOctalZero() { @@ -353,9 +476,9 @@ int main() { Check(u"%.2s", "abcd", u"ab"); Check(u"%4.2s", "\xc3\xa9\xc3\xa8", u" \u00e9\u00e8"); Check(u"%-4.2s", "\xc3\xa9\xc3\xa8", u"\u00e9\u00e8 "); - Check(u"%.2s", "\xf0\x9f\x98\x80x", u"\U0001f600"); - Check(u"%.1s", "\xf0\x9f\x98\x80x", u""); - Check(u"%.3s", "\xf0\x9f\x98\x80x", u"\U0001f600x"); + Check(u"%.2s", "\xed\xa0\xbd\xed\xb8\x80x", u"\U0001f600"); + Check(u"%.1s", "\xed\xa0\xbd\xed\xb8\x80x", u"\xd83d"); + Check(u"%.3s", "\xed\xa0\xbd\xed\xb8\x80x", u"\U0001f600x"); CheckGroupingFlag(); CheckUnsignedSignFlags(); } diff --git a/core/libs/tests/GuestWideLengthProtected.cpp b/core/libs/tests/GuestWideLengthProtected.cpp new file mode 100644 index 0000000000..bf22b8d3f0 --- /dev/null +++ b/core/libs/tests/GuestWideLengthProtected.cpp @@ -0,0 +1,78 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestWideLengthProtected check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + unsigned char* At(std::size_t count) { + Require(count <= page - 32); + return allocation + page * 2 - (std::max)(count, std::size_t{1}); + } +}; + +int main() { + Buffer input; + constexpr std::array values = {0x100, 0xff00, 0xd800, 0xdc00, 0xffff}; + for (std::size_t limit = 0; limit <= 256; ++limit) { + const auto extent = (std::max)(limit, std::size_t{1}); + auto* source = reinterpret_cast(input.At(extent * sizeof(char16_t))); + for (std::size_t stop : {limit, std::size_t{0}, limit / 2}) { + for (std::size_t i = 0; i < extent; ++i) source[i] = values[i % values.size()]; + if (stop < limit) source[stop] = 0; + const std::vector before(source, source + extent); + Require(wcsnlen_s_nid_postfix(source, limit) == stop); + Require(std::equal(before.begin(), before.end(), source)); + } + } +} diff --git a/core/libs/tests/GuestWideStreamScan.cpp b/core/libs/tests/GuestWideStreamScan.cpp index 1f4f1971d0..b2cbc20865 100644 --- a/core/libs/tests/GuestWideStreamScan.cpp +++ b/core/libs/tests/GuestWideStreamScan.cpp @@ -2,10 +2,12 @@ #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include "tests/TemporaryFile.hpp" +#include #include #include #include #include +#include #include extern "C" { @@ -55,6 +57,27 @@ static int APS5_VABI ScanInputSized(const char16_t* format, ...) { return result; } +static int APS5_VABI ScanNarrow(FileStream* stream, const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vfscanf_nid_postfix(stream, format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + +static int APS5_VABI ScanNarrowSized(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 ScanNarrowInput(const char* format, ...) { + APS5_VA_BEGIN(format); + const int result = vscanf_nid_postfix(format, reinterpret_cast(args)); + APS5_VA_END(); + return result; +} + static FileStream* Open(const char* text) { auto* stream = new FileStream(TemporaryFile::Open()); Require(std::fputs(text, stream->GetHandle()) >= 0, "seed the stream"); @@ -67,6 +90,61 @@ static void Close(FileStream* stream) { delete stream; } +static void CheckNarrowFormatWideFields() { + std::int32_t value = 0; + std::int64_t count = -1; + char16_t word[8]{}; + char16_t letter = u'!'; + char narrow[8]{}; + + FileStream* stream = Open(" zlu ku 7"); + Require(fscanf_nid_postfix(stream, "%ls %lc", word, &letter) == 2 && std::u16string(word) == u"zlu" && letter == u'k', "fscanf %ls and %lc"); + Require(ScanNarrow(stream, "%S%ln %d", word, &count, &value) == 2 && std::u16string(word) == u"u" && count == 1 && value == 7, "vfscanf %S with %ln counting bytes"); + Require(fgetc_nid_postfix(stream) == EOF, "the whole input is consumed"); + Close(stream); + + stream = Open("12 ab cd"); + Require(fscanf_nid_postfix(stream, "%d %ls %7s", &value, word, narrow) == 3 && value == 12 && std::u16string(word) == u"ab" && std::strcmp(narrow, "cd") == 0, "wide and narrow fields share the stream"); + Close(stream); + + stream = Open("ab\xc3\xa9t rest"); + Require(fscanf_nid_postfix(stream, "%l[a-c]", word) == 1 && std::u16string(word) == u"ab", "a scanset stops at a byte outside it"); + *__error_nid_postfix() = 0; + Require(fscanf_nid_postfix(stream, "%l[^ ] %7s", word, narrow) == 0 && *__error_nid_postfix() == 86, "a byte after a UTF-8 lead byte fails the field"); + Require(fgetc_nid_postfix(stream) == 0xc3 && fgetc_nid_postfix(stream) == 0xa9, "the whole invalid multibyte character stays in the stream"); + Close(stream); + + char16_t three[5] = {u'!', u'!', u'!', u'!', u'!'}; + stream = Open("xyzw"); + Require(fscanf_nid_postfix(stream, "%3lc", three) == 1 && three[0] == u'x' && three[1] == u'y' && three[2] == u'z' && three[3] == u'!', "%3lc stores three units without a terminator"); + Require(fgetc_nid_postfix(stream) == 'w', "the character after %3lc stays in the stream"); + Close(stream); + + stream = Open("xyz"); + Require(fscanf_nid_postfix(stream, "%l[a-c]", word) == 0, "a scanset matching nothing"); + Require(fgetc_nid_postfix(stream) == 'x', "the rejected character stays in the stream"); + Close(stream); + + stream = Open(" "); + Require(fscanf_nid_postfix(stream, "%ls", word) == EOF, "only white space before %ls"); + Close(stream); + + stream = Open("hello world"); + char16_t fits[6]{}; + char16_t small[4] = {u'!', u'!', u'!', u'!'}; + Require(fscanf_s_nid_postfix(stream, "%ls", fits, 6u) == 1 && std::u16string(fits) == u"hello", "fscanf_s %ls that fits"); + Require(ScanNarrowSized(stream, "%ls", small, 4u) == 0 && small[0] == 0, "vfscanf_s rejects a too small buffer"); + Close(stream); + + FileStream* const standardInput = __stdinp_nid_postfix; + stream = Open("ab c"); + __stdinp_nid_postfix = stream; + Require(scanf_nid_postfix("%ls", word) == 1 && std::u16string(word) == u"ab", "scanf %ls reads the guest standard input"); + Require(ScanNarrowInput(" %lc", &letter) == 1 && letter == u'c', "vscanf %lc reads the guest standard input"); + __stdinp_nid_postfix = standardInput; + Close(stream); +} + static void CheckConsumption() { std::int32_t value = -1; std::int64_t count = -1; @@ -162,9 +240,73 @@ static void CheckStandardInput() { Close(stream); } +static void CheckNarrowWideFailurePosition() { + struct Case { + const char* input; + const char* format; + int result; + long position; + int next; + int error; + int count; + std::array output; + const char* remaining; + }; + const Case cases[]{ + {"\xc3\xa9", "%ls%n", 0, 0, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\xa9"}, + {"\x61\xc3\xa9", "%ls%n", 0, 1, 195, 86, -77, {0x61, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\xa9"}, + {"\x61\x62\x80", "%ls%n", 0, 2, 128, 86, -77, {0x61, 0x62, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\x80"}, + {"\xc3\x20\x61", "%ls%n", -1, 1, 32, 97, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\x20\x61"}, + {"\xc3", "%ls%n", -1, 1, -1, 97, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, ""}, + {"\x80", "%ls%n", -1, 0, 128, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\x80"}, + {"\x61\x80", "%ls%n", 0, 1, 128, 86, -77, {0x61, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\x80"}, + {"\xc3\xa9", "%1ls%n", -1, 1, 169, 97, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xa9"}, + {"\x61\xc3\xa9", "%2ls%n", -1, 2, 169, 97, -77, {0x61, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xa9"}, + {"\x61\xc3\xa9", "%3ls%n", 0, 1, 195, 86, -77, {0x61, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\xa9"}, + {"\xc3\xa9", "%2lc%n", 0, 0, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\xa9"}, + {"\x61\xc3\xa9", "%l[^ ]%n", 0, 1, 195, 86, -77, {0x61, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\xa9"}, + {"\xc3\xa9", "%S%n", 0, 0, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\xa9"}, + {"\x61\xc3\xa9", "%lS%n", 0, 1, 195, 86, -77, {0x61, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\xa9"}, + {"\xff\x7a", "%ls%n", -1, 1, 122, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\x7a"}, + {"\x61\xff\x7a", "%ls%n", 0, 2, 122, 86, -77, {0x61, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\x7a"}, + {"\xc3\xff\x7a", "%ls%n", 0, 1, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\x7a"}, + {"\xc3\x80\x7a", "%ls%n", 0, 0, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\x80\x7a"}, + {"\xc3\xc3\x7a", "%ls%n", 0, 0, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\xc3\x7a"}, + {"\xc3\xff\x7a", "%2lc%n", 0, 1, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\x7a"}, + {"\xc3\xff\x7a", "%3ls%n", 0, 1, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\x7a"}, + {"\xc3\xff\x7a", "%l[^ ]%n", 0, 1, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\x7a"}, + {"\xff\x7a", "%S%n", -1, 1, 122, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\x7a"}, + {"\xc3\xff\x7a", "%lS%n", 0, 1, 195, 86, -77, {0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5, 0xa5a5}, "\xc3\x7a"}, + }; + for (const auto& item : cases) { + for (const bool secure : {false, true}) { + for (const bool variadic : {false, true}) { + auto* stream = Open(item.input); + std::array output; + output.fill(0xa5a5); + int count = -77; + *__error_nid_postfix() = 97; + const int result = secure ? (variadic ? ScanNarrowSized(stream, item.format, output.data(), 8u, &count) : fscanf_s_nid_postfix(stream, item.format, output.data(), 8u, &count)) : (variadic ? ScanNarrow(stream, item.format, output.data(), &count) : fscanf_nid_postfix(stream, item.format, output.data(), &count)); + Require(result == item.result && count == item.count && output == item.output, "wide field failure keeps measured result, count and stored units"); + Require(*__error_nid_postfix() == item.error, "wide field failure keeps measured errno"); + Require(ftell_nid_postfix(stream) == item.position, "wide field failure keeps the measured stream position"); + const int next = fgetc_nid_postfix(stream); + Require(next == item.next, "wide field failure leaves the measured next input byte"); + std::string remaining; + if (next != EOF) remaining += static_cast(next); + for (int byte; (byte = fgetc_nid_postfix(stream)) != EOF;) remaining += static_cast(byte); + Require(remaining == item.remaining, "wide field failure keeps the measured remaining input"); + Close(stream); + } + } + } +} + int main() { + CheckNarrowWideFailurePosition(); CheckConsumption(); CheckWideText(); CheckStandardInput(); + CheckNarrowFormatWideFields(); return 0; } diff --git a/core/libs/tests/GuestWideTokenBoundary.cpp b/core/libs/tests/GuestWideTokenBoundary.cpp new file mode 100644 index 0000000000..0b826277fb --- /dev/null +++ b/core/libs/tests/GuestWideTokenBoundary.cpp @@ -0,0 +1,105 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestWideTokenBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +int main() { + Buffer input, delimiters, other; + auto* set = delimiters.At(3); + set[0] = 0x100; set[1] = 0xd800; set[2] = 0; + const std::u16string separator(set, 2); + for (std::size_t length = 0; length <= 128; ++length) { + auto* source = input.At(length + 1); + const std::array values{0x100, 1, 0x8000, 0xd800, 0xffff, 0x100, 0xdc00}; + for (std::size_t i = 0; i < length; ++i) source[i] = values[(i + length) % values.size()]; + source[length] = 0; + const std::u16string saved(source, length + 1); + std::vector expected; + std::u16string field; + for (std::size_t i = 0; i < length; ++i) { + if (separator.find(saved[i]) != std::u16string::npos) { + if (!field.empty()) { expected.push_back(field); field.clear(); } + } else field.push_back(saved[i]); + } + if (!field.empty()) expected.push_back(field); + auto* alternate = other.At(4); + alternate[0] = 7; alternate[1] = 0x100; alternate[2] = 9; alternate[3] = 0; + char16_t* state = nullptr; + char16_t* otherState = nullptr; + Require(wcstok_nid_postfix(alternate, set, &otherState) == alternate && alternate[0] == 7 && alternate[1] == 0); + for (std::size_t i = 0; i < expected.size(); ++i) { + auto* token = wcstok_nid_postfix(i == 0 ? source : nullptr, set, &state); + Require(token != nullptr && std::u16string(token) == expected[i]); + } + Require(wcstok_nid_postfix(expected.empty() ? source : nullptr, set, &state) == nullptr); + Require(wcstok_nid_postfix(nullptr, set, &state) == nullptr); + auto* last = wcstok_nid_postfix(nullptr, set, &otherState); + Require(last == alternate + 2 && last[0] == 9 && last[1] == 0); + for (std::size_t i = 0; i <= length; ++i) if (separator.find(saved[i]) == std::u16string::npos) Require(source[i] == saved[i]); + Require(set[0] == 0x100 && set[1] == 0xd800 && set[2] == 0); + } +} diff --git a/core/libs/tests/GuestWmemchrBoundary.cpp b/core/libs/tests/GuestWmemchrBoundary.cpp new file mode 100644 index 0000000000..889e712372 --- /dev/null +++ b/core/libs/tests/GuestWmemchrBoundary.cpp @@ -0,0 +1,85 @@ +#include "prx/libc/include/Api.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void Require(bool condition) { + if (!condition) { + std::fputs("GuestWmemchrBoundary check failed\n", stderr); + std::abort(); + } +} + +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +class Buffer { + unsigned char* allocation; + std::size_t page; +public: + Buffer() { +#ifdef _WIN32 + SYSTEM_INFO info{}; + GetSystemInfo(&info); + page = info.dwPageSize; + allocation = static_cast(VirtualAlloc(nullptr, page * 3, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS)); + Require(allocation != nullptr); + DWORD previous; + Require(VirtualProtect(allocation + page, page, PAGE_READWRITE, &previous) != 0); +#else + const long size = sysconf(_SC_PAGESIZE); + Require(size > 0); + page = static_cast(size); + allocation = static_cast(mmap(nullptr, page * 3, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + Require(allocation != MAP_FAILED); + Require(mprotect(allocation + page, page, PROT_READ | PROT_WRITE) == 0); +#endif + std::memset(allocation + page, 0xa5, page); + } + ~Buffer() { +#ifdef _WIN32 + Require(VirtualFree(allocation, 0, MEM_RELEASE) != 0); +#else + Require(munmap(allocation, page * 3) == 0); +#endif + } + Buffer(const Buffer&) = delete; + Buffer& operator=(const Buffer&) = delete; + template + T* At(std::size_t count) { + Require(count <= (page - 32) / sizeof(T)); + return reinterpret_cast(allocation + page * 2 - (std::max)(count, std::size_t{1}) * sizeof(T)); + } +}; + +template +static void Prefix(const T* data) { + for (int index = 1; index <= 8; ++index) Require(data[-index] == static_cast(sizeof(T) == 1 ? 0xa5 : 0xa5a5)); +} + +int main() { + Buffer input; + const std::array values{0, 1, 0xff, 0x100, 0x8000, 0xd800, 0xffff}; + for (std::size_t length = 0; length <= 192; ++length) { + auto* source = input.At(length); + const auto extent = (std::max)(length, std::size_t{1}); + for (std::size_t i = 0; i < extent; ++i) source[i] = values[(i + length) % values.size()]; + const std::vector saved(source, source + extent); + for (char16_t target : {0, 1, 0xff, 0x100, 0x8000, 0xd800, 0xdc00, 0xffff}) { + const auto found = std::find(saved.begin(), saved.begin() + length, target); + const auto* expected = found == saved.begin() + length ? nullptr : source + (found - saved.begin()); + Require(wmemchr_nid_postfix(source, target, length) == expected); + Require(std::equal(saved.begin(), saved.end(), source)); + } + } +} diff --git a/core/libs/tests/JsonCopyFailure.cpp b/core/libs/tests/JsonCopyFailure.cpp index b8e2562656..00ec3ef4ce 100644 --- a/core/libs/tests/JsonCopyFailure.cpp +++ b/core/libs/tests/JsonCopyFailure.cpp @@ -1,5 +1,6 @@ #include "prx/libSceJson2/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "JsonTestAllocator.hpp" #include #include #include @@ -98,7 +99,7 @@ static bool Parse(const char* document, bool valid, bool callback, const char* e } catch (const std::bad_alloc&) { } allocationBudget = -1; - CheckValue(result, completed && valid ? (expected ? expected : document) : "\"previous\""); + CheckValue(result, completed ? (expected ? expected : document) : "\"previous\""); CheckHandler(handler); _ZN3sce4Json5ValueD1Ev(&result); _ZN3sce4Json5ValueD1Ev(&handler.fallback); @@ -113,7 +114,8 @@ static bool ObjectEntry(const char* keyText) { _ZN3sce4Json6StringC1EPKc(&oldKey, "kept"); _ZN3sce4Json6StringC1EPKc(&newKey, keyText); Value* kept = _ZN3sce4Json6ObjectixERKNS0_6StringE(&object, &oldKey); - Require(_ZN3sce4Json5Value3setEPKc(kept, "previous") == 0, "prepare entry failed"); + _ZN3sce4Json5Value3setEPKc(kept, "previous"); + CheckValue(*kept, "\"previous\""); _ZN3sce4Json5ValueC1El(&handler.fallback, 77); handler.parent = kept; Require(_ZN3sce4Json5Value22setTypeMismatchHandlerEPFRKS1_NS0_9ValueTypeEPS2_S5_PvES6_(kept, OnTypeMismatch, &handler) == 0, "set handler failed"); @@ -230,6 +232,11 @@ static bool Run(Operation operation, const Value& source, const char* expected, } int main() { + JsonTest::Allocator allocator; + struct { void* allocator; void* context; std::size_t bufferSize; std::int32_t mode; std::uint32_t reserved[3]; } parameter{&allocator, nullptr, 0, 2, {}}; + std::uint8_t initializer = 0; + _ZN3sce4Json11InitializerC1Ev(&initializer); + Require(_ZN3sce4Json11Initializer10initializeEPKNS0_14InitParameter2E(&initializer, reinterpret_cast(¶meter)) == 0, "Initialize copy failure contract"); constexpr const char* objectJson = "{\"long\":\"a sufficiently long string to require its own allocation\",\"nested\":[{\"leaf\":\"another sufficiently long string\"},[true,null,17]]}"; constexpr const char* arrayJson = "[1,{\"long\":\"a sufficiently long string to require its own allocation\"},[false,null]]"; constexpr const char* stringJson = "\"a sufficiently long string to require its own allocation\""; @@ -265,7 +272,7 @@ int main() { {"set object", Operation::SetObject, &objectValue, objectJson}, {"set empty array", Operation::SetTypeArray, &objectValue, "[]"}, {"set empty object", Operation::SetTypeObject, &objectValue, "{}"}, - {"set empty string", Operation::SetTypeString, &objectValue, "\"\""}, + {"retain string type", Operation::SetTypeString, &objectValue, "\"previous\""}, {"pair copy", Operation::PairCopy, &objectValue, objectJson}, }; unsigned failures = 0; @@ -285,7 +292,7 @@ int main() { TIterator source{}; if constexpr (objectIterator) _ZNK3sce4Json6Object5beginEv(&source, object); else _ZNK3sce4Json5Array5beginEv(&source, array); - for (activeBudget = 0; activeBudget <= 1; ++activeBudget) { + for (activeBudget = 0; activeBudget < 1; ++activeBudget) { TIterator copy{}; bool succeeded = false; allocationBudget = activeBudget; @@ -297,7 +304,7 @@ int main() { ++failures; } allocationBudget = -1; - Require(succeeded == (activeBudget == 1), "iterator allocation failure ignored"); + Require(succeeded, "Iterator copies must use the guest allocator without host allocations"); if (succeeded) Require(copy.state != source.state, "iterator copy shares owned storage"); if constexpr (objectIterator) _ZN3sce4Json6Object8iteratorD1Ev(©); else _ZN3sce4Json5Array8iteratorD1Ev(©); @@ -346,11 +353,11 @@ int main() { {"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 invalid array", "[1,{\"a\":[true,null]}", false, false, "[1,{\"a\":[true,null]}]"}, + {"parse invalid object", "{\"a\":[{\"b\":\"a sufficiently long string allocation\"},[true,null,17]]", false, false, "{\"a\":[{\"b\":\"a sufficiently long string allocation\"},[true,null,17]]}"}, {"parse trailing input", "{\"a\":[1]}x", true, false, "{\"a\":[1]}"}, {"parse callback", objectJson, true, true}, - {"parse invalid callback", "[{\"a\":[1]}", false, true}, + {"parse invalid callback", "[{\"a\":[1]}", false, true, "[{\"a\":[1]}]"}, }; for (const auto& test : parseCases) failures += CheckFailures(test.name, [&] { return Parse(test.document, test.valid, test.callback, test.expected); }); diff --git a/core/libs/tests/JsonTestAllocator.hpp b/core/libs/tests/JsonTestAllocator.hpp new file mode 100644 index 0000000000..ba8a562536 --- /dev/null +++ b/core/libs/tests/JsonTestAllocator.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +namespace JsonTest { + +inline void* APS5_VABI Allocate(void*, std::size_t bytes, void*) { + return std::malloc(bytes); +} + +inline void APS5_VABI Free(void*, void* pointer, void*) { + std::free(pointer); +} + +inline void APS5_VABI Notify(void*, int error, std::size_t bytes, void*) { + std::fprintf(stderr, "Unexpected Json2 allocation error 0x%x for %zu bytes\n", error, bytes); + std::abort(); +} + +inline void* const AllocatorVtable[] = { + nullptr, nullptr, reinterpret_cast(Allocate), reinterpret_cast(Free), reinterpret_cast(Notify) +}; + +struct Allocator { + void* const* vtable = AllocatorVtable; +}; + +} diff --git a/core/libs/tests/ResolverRaw.cpp b/core/libs/tests/ResolverRaw.cpp index 0411d8b5b4..73a91266fe 100644 --- a/core/libs/tests/ResolverRaw.cpp +++ b/core/libs/tests/ResolverRaw.cpp @@ -58,6 +58,15 @@ int main() { Address("::1", 28, protocol, flags); } } + for (int flags : {2, 6}) { + GuestResolver::AddressInfo scoped{}; + scoped.flags = flags; + scoped.socketType = 3; + GuestResolver::AddressInfo* named = nullptr; + Require(getaddrinfo_nid_postfix("fe80::1%1", nullptr, &scoped, &named) == 0 && named, "scoped raw address resolution failed"); + Require(named->canonicalName && std::strcmp(named->canonicalName, "fe80::1%1") == 0, "scoped numeric canonical name differs"); + freeaddrinfo_nid_postfix(named); + } GuestResolver::AddressInfo hints{}; hints.flags = 4; hints.socketType = 3; diff --git a/core/libs/tests/ResolverSocketTypes.cpp b/core/libs/tests/ResolverSocketTypes.cpp new file mode 100644 index 0000000000..56e06515ce --- /dev/null +++ b/core/libs/tests/ResolverSocketTypes.cpp @@ -0,0 +1,105 @@ +#include "prx/libScePosixForWebKit/libScePosixForWebKit.hpp" +#include +#include +#include +#include + +namespace { + +void Require(bool condition, const char* message) { + if (condition) return; + std::fprintf(stderr, "resolver socket types: %s\n", message); + std::abort(); +} + +void Resolve(const char* node, const char* service, int family, int type, int protocol, bool noHints) { + const auto check = [&](bool condition, const char* message) { + if (!condition) + std::fprintf(stderr, "query: node=%s service=%s family=%d type=%d protocol=%d nullHints=%d\n", + node, service ? service : "(null)", family, type, protocol, noHints); + Require(condition, message); + }; + GuestResolver::AddressInfo hints{}; + hints.flags = 12; + hints.family = family; + hints.socketType = type; + hints.protocol = protocol; + GuestResolver::AddressInfo* result = nullptr; + check(getaddrinfo_nid_postfix(node, service, noHints ? nullptr : &hints, &result) == 0 && result, + "numeric lookup failed"); + bool stream = false; + bool datagram = false; + for (const auto* entry = result; entry; entry = entry->next) { + check(entry->socketType == 1 || entry->socketType == 2, "lookup returned a raw or invalid socket type"); + check(entry->protocol == (entry->socketType == 1 ? 6 : 17), "transport protocol differs from socket type"); + check(!type || entry->socketType == type, "explicit socket type changed"); + check(!protocol || entry->protocol == protocol, "explicit protocol changed"); + stream |= entry->socketType == 1; + datagram |= entry->socketType == 2; + const int expectedFamily = node[0] == ':' ? 28 : 2; + check(entry->family == expectedFamily, "address family changed"); + check(entry->addressLength == (expectedFamily == 2 ? 16u : 28u), "address length changed"); + const auto* bytes = static_cast(entry->address); + check(bytes && bytes[0] == entry->addressLength && bytes[1] == expectedFamily, "guest sockaddr prefix differs"); + const unsigned port = service ? static_cast(std::strtoul(service, nullptr, 10)) : 0; + check(bytes[2] == port / 256 && bytes[3] == port % 256, "transport port changed"); + if (expectedFamily == 2) { + constexpr std::array address{127, 0, 0, 1}; + check(std::memcmp(bytes + 4, address.data(), address.size()) == 0, "IPv4 bytes changed"); + } else { + constexpr std::array address{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + check(std::memcmp(bytes + 8, address.data(), address.size()) == 0, "IPv6 bytes changed"); + } + } + if (!type && !protocol) check(stream && datagram, "unspecified query lost a transport"); + check(hints.flags == 12 && hints.family == family && hints.socketType == type && hints.protocol == protocol, + "lookup modified hints"); + freeaddrinfo_nid_postfix(result); +} + +void ResolveService(const char* node) { + GuestResolver::AddressInfo hints{}; + hints.flags = 4; + GuestResolver::AddressInfo* result = nullptr; + Require(getaddrinfo_nid_postfix(node, "http", &hints, &result) == 0 && result, + "named service lookup failed"); + bool stream = false; + for (const auto* entry = result; entry; entry = entry->next) { + Require(entry->socketType == 1 || entry->socketType == 2, "named service returned an invalid socket type"); + Require(entry->protocol == (entry->socketType == 1 ? 6 : 17), "named service protocol differs"); + const auto* bytes = static_cast(entry->address); + Require(bytes && bytes[2] == 0 && bytes[3] == 80, "named service port changed"); + stream |= entry->socketType == 1; + } + Require(stream, "named service lost TCP results"); + freeaddrinfo_nid_postfix(result); +} + +} + +int main() { +#ifdef __linux__ + for (const char* node : {"127.0.0.1", "::1"}) { + GuestResolver::AddressInfo hints{}; + hints.flags = 4; + hints.protocol = node[0] == ':' ? 58 : 1; + auto* result = reinterpret_cast(1); + Require(getaddrinfo_nid_postfix(node, nullptr, &hints, &result) == 4 && !result, + "raw-only host results did not fail with EAI_FAIL and clear output"); + } +#endif + for (const char* node : {"127.0.0.1", "::1"}) { + ResolveService(node); + for (const char* service : {static_cast(nullptr), "0", "27910", "65535"}) + Resolve(node, service, 0, 0, 0, true); + for (const int family : {0, node[0] == ':' ? 28 : 2}) { + for (const char* service : {static_cast(nullptr), "0", "27910", "65535"}) { + for (const int protocol : {0, 6, 17}) Resolve(node, service, family, 0, protocol, false); + Resolve(node, service, family, 1, 0, false); + Resolve(node, service, family, 2, 0, false); + Resolve(node, service, family, 1, 6, false); + Resolve(node, service, family, 2, 17, false); + } + } + } +} diff --git a/core/libs/tests/ScalarCall.cpp b/core/libs/tests/ScalarCall.cpp index 7870d3ebfb..2837866b2f 100644 --- a/core/libs/tests/ScalarCall.cpp +++ b/core/libs/tests/ScalarCall.cpp @@ -409,6 +409,32 @@ static std::vector HoistedTableCaller(HoistedShape shape) { return code; } +static void CheckTableCalleeProgramCounter() { + const SwappcInfo swappc{false, 0u, 16u}; + const auto caller = TableCaller(); + const auto program = RdnaInstructionDecoder{}.Decode(caller); + const auto calls = FindTableCalls(program, swappc); + Require(calls.size() == 1u); + const std::vector callee{0xbf820001u, 0xbe901f00u, 0xbe80200eu}; + ExpectThrow("reads its program counter", [&] { static_cast(UserDataCalleeLength(callee, 14u)); }); + Require(UserDataCalleeLength(callee, 14u, true) == 3u); + const std::vector> callees{{{0x1122334400ull, callee}}}; + const auto linkedCalls = LinkCalls(program, {}, {}, calls, callees); + const auto& linked = linkedCalls.code; + const std::vector expanded{0xbf820004u, 0xbe9003ffu, 0x22334408u, 0xbe9103ffu, 0x00000011u, 0xbe80200eu}; + Require(std::search(linked.begin(), linked.end(), expanded.begin(), expanded.end()) != linked.end()); + Require(std::search(linked.begin(), linked.end(), callee.begin(), callee.end()) == linked.end()); + SwappcInfo linkedSwappc = swappc; + linkedSwappc.linkedCallFallbacks = linkedCalls.fallbacks; + static_cast(BuildCode(linked, linkedSwappc)); + static_cast(TranslateCode(linked, linkedSwappc)); + const std::vector> plain{{{0x1122334400ull, std::vector{0x4a020285u, 0xbe80200eu}}}}; + const auto unchanged = LinkCalls(program, {}, {}, calls, plain).code; + Require(std::search(unchanged.begin(), unchanged.end(), plain[0][0].code.begin(), plain[0][0].code.end()) != unchanged.end()); + const std::vector> vcc{{{0x1122334400ull, std::vector{0xbeea1f00u, 0xbe80200eu}}}}; + ExpectThrow("into another operand than an SGPR pair", [&] { static_cast(LinkCalls(program, {}, {}, calls, vcc)); }); +} + static void CheckHoistedTableCalls() { const std::vector callee{0x4a0202c0u, 0xbe80200eu}; const std::vector> callees{{{0x1122334455ull, callee}}}; @@ -549,6 +575,7 @@ int main(int argc, char** argv) { CheckRejections(); CheckUserDataCalls(); CheckTableCalls(); + CheckTableCalleeProgramCounter(); CheckHoistedTableCalls(); CheckGuards(""); } else { diff --git a/core/libs/tests/SemaphoreTrace.cpp b/core/libs/tests/SemaphoreTrace.cpp index e49947b56e..c8488fd53f 100644 --- a/core/libs/tests/SemaphoreTrace.cpp +++ b/core/libs/tests/SemaphoreTrace.cpp @@ -5,6 +5,8 @@ #include extern "C" void KernelTraceWait_nid_postfix(const char*, const void*, std::uint64_t, bool) {} +extern "C" Pthread APS5_VABI scePthreadSelf() { return nullptr; } +extern "C" int APS5_VABI scePthreadGetprio(Pthread, int*) { return -1; } int main() { KernelSema sem = nullptr; diff --git a/core/libs/tests/SigvecModules.cmake b/core/libs/tests/SigvecModules.cmake new file mode 100644 index 0000000000..4a9f172f26 --- /dev/null +++ b/core/libs/tests/SigvecModules.cmake @@ -0,0 +1,59 @@ +cmake_policy(SET CMP0054 NEW) +math(EXPR testExecutableIndex "${CMAKE_ARGC} - 1") +math(EXPR separatorIndex "${CMAKE_ARGC} - 2") +if(NOT CMAKE_ARGV${separatorIndex} STREQUAL "--") + message(FATAL_ERROR "Expected the test executable after --") +endif() +set(TEST_EXECUTABLE "${CMAKE_ARGV${testExecutableIndex}}") + +file(MAKE_DIRECTORY "${WORK_DIR}") +execute_process(COMMAND "${PATCHER}" --write-export-list "${LIBC}" "${WORK_DIR}/libc.exports" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) +if(NOT status STREQUAL "0") + message(FATAL_ERROR "libc export list failed: ${output}${error}") +endif() +foreach(module LIBC KERNEL INTERNAL) + get_filename_component(name "${${module}}" NAME) + get_filename_component(library "${${module}}" NAME_WE) + file(COPY_FILE "${${module}}" "${WORK_DIR}/${name}") + if(module STREQUAL "LIBC") + execute_process(COMMAND "${PATCHER}" "${library}" "${WORK_DIR}/${name}" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) + else() + execute_process(COMMAND "${PATCHER}" "${library}" --preserve-export-list "${WORK_DIR}/libc.exports" "${WORK_DIR}/${name}" + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) + endif() + if(NOT status STREQUAL "0") + message(FATAL_ERROR "${library} patch failed: ${output}${error}") + endif() +endforeach() +get_filename_component(probeName "${EXCEPTION_PROBE}" NAME) +file(COPY_FILE "${EXCEPTION_PROBE}" "${WORK_DIR}/${probeName}") +if(WIN32) + set(searchDirectories "${WORK_DIR}") + foreach(binary LIBC KERNEL INTERNAL EXCEPTION_PROBE TEST_EXECUTABLE) + get_filename_component(directory "${${binary}}" DIRECTORY) + list(APPEND searchDirectories "${directory}") + endforeach() + if(ALLOCATOR_DIRECTORY) + list(APPEND searchDirectories "${ALLOCATOR_DIRECTORY}") + endif() + list(REMOVE_DUPLICATES searchDirectories) + list(JOIN searchDirectories ";" searchPath) + set(environment "PATH=${searchPath};$ENV{PATH}") +else() + set(environment "LD_LIBRARY_PATH=${WORK_DIR}:$ENV{LD_LIBRARY_PATH}") +endif() +foreach(order Internal-first libc-first) + set(arguments "${WORK_DIR}" "${probeName}") + if(order STREQUAL "libc-first") + list(APPEND arguments libc-first) + endif() + execute_process(COMMAND "${CMAKE_COMMAND}" -E env "${environment}" + "${TEST_EXECUTABLE}" ${arguments} + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE error) + if(NOT status STREQUAL "0") + message(FATAL_ERROR "sigvec ${order} lookup failed: ${output}${error}") + endif() + message("${output}") +endforeach() diff --git a/core/libs/tests/SigvecModulesExceptions.cpp b/core/libs/tests/SigvecModulesExceptions.cpp new file mode 100644 index 0000000000..79f1fb0abd --- /dev/null +++ b/core/libs/tests/SigvecModulesExceptions.cpp @@ -0,0 +1,27 @@ +#include "prx/libc/include/LegacySignals.hpp" +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +extern "C" bool SigvecMissingBackend_nid_no_patch(decltype(&sigvec_nid_postfix) first, + decltype(&sigvec_nid_postfix) second, const void* core) { +#ifdef _WIN32 + const auto libc = GetModuleHandleW(L"libc.prx"); + if (libc == nullptr || reinterpret_cast(GetProcAddress(libc, "LegacySigvec_nid_no_patch")) != core) return false; +#else + if (reinterpret_cast(&LegacySigvec_nid_no_patch) != core) return false; +#endif + for (const auto function : std::array{first, second}) { + bool threw = false; + try { + function(30, nullptr, nullptr); + } catch (const std::runtime_error& error) { + threw = std::strcmp(error.what(), "sigvec: libkernel has not registered sigaction") == 0; + } + if (!threw) return false; + } + return true; +} diff --git a/core/libs/tests/SystemServiceLanguageLocale.cpp b/core/libs/tests/SystemServiceLanguageLocale.cpp new file mode 100644 index 0000000000..257556e9cd --- /dev/null +++ b/core/libs/tests/SystemServiceLanguageLocale.cpp @@ -0,0 +1,307 @@ +#include "prx/libSceSystemService/LanguageLocale.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void Require(bool value, std::string_view what) { + if (value) return; + std::fprintf(stderr, "check failed: %.*s\n", static_cast(what.size()), what.data()); + std::abort(); +} + +struct ParsedTag { + std::string_view text; + std::string_view language; + std::string_view script; + std::string_view region; +}; + +void RequireBcp47Parsing() { + constexpr ParsedTag accepted[] = { + {"en", "en", "", ""}, + {"en-US", "en", "", "US"}, + {"EN-us", "en", "", "US"}, + {"pt-BR", "pt", "", "BR"}, + {"es-419", "es", "", "419"}, + {"zh-Hant-TW", "zh", "Hant", "TW"}, + {"zh-hans-cn", "zh", "Hans", "CN"}, + {"ZH-HANT", "zh", "Hant", ""}, + {"sr-Latn-RS", "sr", "Latn", "RS"}, + {"fil", "fil", "", ""}, + {"en-US-u-ca-gregory", "en", "", "US"}, + {"de-DE-1996", "de", "", "DE"}, + {"ca-ES-valencia", "ca", "", "ES"}, + {"en-x-private", "en", "", ""}, + {"en-Latn-US-x-a", "en", "Latn", "US"}, + }; + for (const auto& expected : accepted) { + SystemServiceLocale::Tag tag; + Require(SystemServiceLocale::ParseTag(expected.text, tag), expected.text); + Require(tag.language == expected.language, expected.text); + Require(tag.script == expected.script, expected.text); + Require(tag.region == expected.region, expected.text); + } + for (std::string_view rejected : {"", "-", "en-", "-en", "en--US", "e", "english", "e1", "12", "en_US", "en-US.UTF-8", "en US", "en-U$", "en-abcdefghi", "C", "x-foo"}) { + SystemServiceLocale::Tag tag{"keep", "keep", "keep"}; + const bool parsed = SystemServiceLocale::ParseTag(rejected, tag); + Require(!parsed, rejected); + Require(tag.language == "keep" && tag.script == "keep" && tag.region == "keep", rejected); + } +} + +void RequirePosixNormalisation() { + constexpr std::pair names[] = { + {"en_US.UTF-8", "en-US"}, + {"en_GB.UTF-8@euro", "en-GB"}, + {"de_DE.ISO-8859-15@euro", "de-DE"}, + {"pt_BR", "pt-BR"}, + {"zh_TW", "zh-TW"}, + {"zh_Hant_TW", "zh-Hant-TW"}, + {"ca_ES@valencia", "ca-ES"}, + {"sr_RS@latin", "sr-Latn-RS"}, + {"sr@latin", "sr-Latn"}, + {"sr_Latn_RS@latin", "sr-Latn-RS"}, + {"uz_UZ@Cyrillic", "uz-Cyrl-UZ"}, + {"fr", "fr"}, + {"en-US", "en-US"}, + {"EN_us.utf8", "EN-us"}, + {"C", ""}, + {"POSIX", ""}, + {"c", ""}, + {"posix", ""}, + {"C.UTF-8", ""}, + {"C.utf8@euro", ""}, + {"", ""}, + {"@latin", ""}, + {".UTF-8", ""}, + }; + for (const auto& [name, expected] : names) { + Require(SystemServiceLocale::NormalizePosixLocale(name) == expected, name); + } +} + +void RequireEitherForm() { + constexpr ParsedTag locales[] = { + {"en_US.UTF-8", "en", "", "US"}, + {"en-US", "en", "", "US"}, + {"zh_Hant_TW.UTF-8", "zh", "Hant", "TW"}, + {"zh-Hans-CN", "zh", "Hans", "CN"}, + {"sr_RS@latin", "sr", "Latn", "RS"}, + {"pt_BR.utf8", "pt", "", "BR"}, + {"es_419", "es", "", "419"}, + {"fr", "fr", "", ""}, + }; + for (const auto& expected : locales) { + SystemServiceLocale::Tag tag; + Require(SystemServiceLocale::ParseLocale(expected.text, tag), expected.text); + Require(tag.language == expected.language && tag.script == expected.script && tag.region == expected.region, expected.text); + } + for (std::string_view none : {"", "C", "POSIX", "C.UTF-8", "@latin", "garbage!", "english", "e", "en__US", "_US", "123"}) { + SystemServiceLocale::Tag tag{"keep", "keep", "keep"}; + Require(!SystemServiceLocale::ParseLocale(none, tag), none); + Require(tag.language == "keep" && tag.script == "keep" && tag.region == "keep", none); + } +} + +struct LocaleLanguage { + std::string_view locale; + int id; +}; + +int LanguageOf(std::string_view locale) { + SystemServiceLocale::Tag tag; + if (!SystemServiceLocale::ParseLocale(locale, tag)) return SystemServiceLocale::NoConsoleLanguage; + return SystemServiceLocale::ConsoleLanguageForTag(tag); +} + +void RequireLanguages(const LocaleLanguage* first, const LocaleLanguage* last) { + for (const LocaleLanguage* entry = first; entry != last; ++entry) Require(LanguageOf(entry->locale) == entry->id, entry->locale); +} + +template +void RequireLanguages(const LocaleLanguage (&entries)[N]) { + RequireLanguages(entries, entries + N); +} + +void RequireLatinEuropeanRows() { + using namespace SystemServiceLocale; + constexpr LocaleLanguage entries[] = { + {"en", EnglishUs}, {"en-US", EnglishUs}, {"en_US.UTF-8", EnglishUs}, {"EN-us", EnglishUs}, {"en-CA", EnglishUs}, {"en-IN", EnglishUs}, + {"en-GB", EnglishUk}, {"en_GB.UTF-8@euro", EnglishUk}, {"en-IE", EnglishUk}, {"en-AU", EnglishUk}, {"en-NZ", EnglishUk}, {"en-Latn-GB", EnglishUk}, + {"fr", French}, {"fr-FR", French}, {"fr_BE.UTF-8", French}, {"fr-CH", French}, {"fr-CA", FrenchCanada}, {"fr_CA.UTF-8", FrenchCanada}, + {"de", German}, {"de-DE", German}, {"de_AT.UTF-8", German}, {"de-CH", German}, + {"it", Italian}, {"it-IT", Italian}, {"it_CH", Italian}, + {"nl", Dutch}, {"nl-NL", Dutch}, {"nl_BE.UTF-8", Dutch}, + {"ro", Romanian}, {"ro-RO", Romanian}, {"mo", Romanian}, + }; + RequireLanguages(entries); +} + +void RequireIberianRows() { + using namespace SystemServiceLocale; + constexpr LocaleLanguage entries[] = { + {"es", Spanish}, {"es-ES", Spanish}, {"es_ES.UTF-8@euro", Spanish}, {"ES-es", Spanish}, {"es-GQ", Spanish}, {"es-Latn-ES", Spanish}, + {"es-419", SpanishLatinAmerica}, {"es_MX.UTF-8", SpanishLatinAmerica}, {"es-AR", SpanishLatinAmerica}, {"es-CO", SpanishLatinAmerica}, + {"es_CL", SpanishLatinAmerica}, {"es-PE", SpanishLatinAmerica}, {"es-VE", SpanishLatinAmerica}, {"es-US", SpanishLatinAmerica}, + {"es_UY.utf8", SpanishLatinAmerica}, {"es-PR", SpanishLatinAmerica}, {"es-CU", SpanishLatinAmerica}, {"es-DO", SpanishLatinAmerica}, + {"pt", PortugueseBrazil}, {"pt-BR", PortugueseBrazil}, {"pt_BR.UTF-8", PortugueseBrazil}, {"PT-br", PortugueseBrazil}, + {"pt-PT", PortuguesePortugal}, {"pt_PT.UTF-8", PortuguesePortugal}, {"pt-AO", PortuguesePortugal}, {"pt-MZ", PortuguesePortugal}, + {"pt-CV", PortuguesePortugal}, {"pt-TL", PortuguesePortugal}, + }; + RequireLanguages(entries); + constexpr const char* latinAmerica[] = {"MX", "AR", "BO", "CL", "CO", "CR", "CU", "DO", "EC", "GT", "HN", "NI", "PA", "PE", "PR", "PY", "SV", "US", "UY", "VE", "419"}; + for (const char* region : latinAmerica) Require(LanguageOf(std::string("es-") + region) == SpanishLatinAmerica, region); +} + +void RequireChineseRows() { + using namespace SystemServiceLocale; + constexpr LocaleLanguage entries[] = { + {"zh", ChineseSimplified}, {"zh-CN", ChineseSimplified}, {"zh_CN.UTF-8", ChineseSimplified}, {"zh-SG", ChineseSimplified}, {"zh_SG.utf8", ChineseSimplified}, + {"zh-Hans", ChineseSimplified}, {"zh-Hans-CN", ChineseSimplified}, {"zh-hans-sg", ChineseSimplified}, {"zh-Latn", ChineseSimplified}, {"zh-MY", ChineseSimplified}, + {"zh-Hant", ChineseTraditional}, {"zh-Hant-TW", ChineseTraditional}, {"zh_Hant_TW.UTF-8", ChineseTraditional}, {"zh-TW", ChineseTraditional}, {"zh_TW.UTF-8", ChineseTraditional}, + {"zh-HK", ChineseTraditional}, {"zh_HK.UTF-8", ChineseTraditional}, {"zh-MO", ChineseTraditional}, {"zh-Hant-HK", ChineseTraditional}, {"zh-Hant-MO", ChineseTraditional}, + {"zh-Hans-TW", ChineseSimplified}, {"zh-Hans-HK", ChineseSimplified}, {"zh-Hant-CN", ChineseTraditional}, + {"cmn", ChineseSimplified}, {"cmn-Hans-CN", ChineseSimplified}, {"cmn-Hant", ChineseTraditional}, {"cmn-TW", ChineseTraditional}, + {"yue", ChineseTraditional}, {"yue-HK", ChineseTraditional}, {"yue-Hans-CN", ChineseSimplified}, + }; + RequireLanguages(entries); +} + +void RequireJapaneseKoreanRows() { + using namespace SystemServiceLocale; + constexpr LocaleLanguage entries[] = { + {"ja", Japanese}, {"ja-JP", Japanese}, {"ja_JP.UTF-8", Japanese}, {"JA-jp", Japanese}, {"ja-Jpan-JP", Japanese}, {"ja_JP.eucJP", Japanese}, + {"ko", Korean}, {"ko-KR", Korean}, {"ko_KR.UTF-8", Korean}, {"ko-KP", Korean}, {"ko-Kore-KR", Korean}, + }; + RequireLanguages(entries); +} + +void RequireSlavicNordicRows() { + using namespace SystemServiceLocale; + constexpr LocaleLanguage entries[] = { + {"ru", Russian}, {"ru-RU", Russian}, {"ru_RU.UTF-8", Russian}, {"ru-UA", Russian}, {"ru-BY", Russian}, + {"uk", Ukrainian}, {"uk-UA", Ukrainian}, {"uk_UA.UTF-8", Ukrainian}, + {"pl", Polish}, {"pl-PL", Polish}, {"pl_PL.UTF-8", Polish}, + {"cs", Czech}, {"cs-CZ", Czech}, {"cs_CZ.UTF-8", Czech}, + {"fi", Finnish}, {"fi-FI", Finnish}, {"fi_FI.UTF-8@euro", Finnish}, + {"sv", Swedish}, {"sv-SE", Swedish}, {"sv_FI.UTF-8", Swedish}, + {"da", Danish}, {"da-DK", Danish}, {"da_DK.UTF-8", Danish}, + {"no", Norwegian}, {"no-NO", Norwegian}, {"nb", Norwegian}, {"nb-NO", Norwegian}, {"nb_NO.UTF-8", Norwegian}, {"nn", Norwegian}, {"nn-NO", Norwegian}, {"nn_NO.UTF-8", Norwegian}, + }; + RequireLanguages(entries); + for (std::string_view unmapped : {"lt-LT", "lv-LV", "et-EE", "sk-SK", "sl-SI", "bg-BG", "sr-RS", "sr_RS@latin", "hr-HR", "be-BY", "is-IS"}) { + Require(LanguageOf(unmapped) == NoConsoleLanguage, unmapped); + } +} + +void RequireOtherRows() { + using namespace SystemServiceLocale; + constexpr LocaleLanguage entries[] = { + {"ar", Arabic}, {"ar-AE", Arabic}, {"ar-SA", Arabic}, {"ar_EG.UTF-8", Arabic}, {"ar-MA", Arabic}, {"ar-001", Arabic}, + {"th", Thai}, {"th-TH", Thai}, {"th_TH.UTF-8", Thai}, + {"vi", Vietnamese}, {"vi-VN", Vietnamese}, {"vi_VN.utf8", Vietnamese}, + {"id", Indonesian}, {"id-ID", Indonesian}, {"id_ID.UTF-8", Indonesian}, {"in", Indonesian}, {"in_ID", Indonesian}, + {"tr", Turkish}, {"tr-TR", Turkish}, {"tr_TR.UTF-8", Turkish}, {"tr-CY", Turkish}, + {"el", Greek}, {"el-GR", Greek}, {"el_GR.UTF-8", Greek}, {"el-CY", Greek}, + {"hu", Hungarian}, {"hu-HU", Hungarian}, {"hu_HU.UTF-8", Hungarian}, + }; + RequireLanguages(entries); +} + +void RequireFallbackRules() { + using namespace SystemServiceLocale; + constexpr const char* perLanguage[31] = { + "ja-JP", "en-US", "fr-FR", "es-ES", "de-DE", "it-IT", "nl-NL", "pt-PT", "ru-RU", "ko-KR", "zh-Hant", "zh-Hans", "fi-FI", "sv-SE", "da-DK", "no-NO", + "pl-PL", "pt-BR", "en-GB", "tr-TR", "es-419", "ar-AE", "fr-CA", "cs-CZ", "hu-HU", "el-GR", "ro-RO", "th-TH", "vi-VN", "id-ID", "uk-UA"}; + for (int id = 0; id <= 30; ++id) { + const Match match = MatchLocale(perLanguage[id]); + Require(match.recognised && match.id == id, perLanguage[id]); + } + for (std::string_view unrecognised : {"", "C", "POSIX", "C.UTF-8", "c", "garbage", "xx", "xx-YY", "x-foo", "en_", "_US", "lt-LT", "sw-KE", "he-IL", "hi-IN", "sr-Latn-RS", "ca-ES", "eu", "gl-ES", "qaa", "en--US", "12345", "\t"}) { + const Match match = MatchLocale(unrecognised); + Require(!match.recognised && match.id == EnglishUs, unrecognised); + } + Require(MatchLocale("en-US").recognised && MatchLocale("en-ZZ").recognised && MatchLocale("en-ZZ").id == EnglishUs, "en-ZZ"); + Require(MatchLocale("fr-ZZ").recognised && MatchLocale("fr-ZZ").id == French, "fr-ZZ"); + Require(MatchLocale("zh-ZZ").recognised && MatchLocale("zh-ZZ").id == ChineseSimplified, "zh-ZZ"); + Require(MatchLocale("pt-ZZ").recognised && MatchLocale("pt-ZZ").id == PortugueseBrazil, "pt-ZZ"); + Require(MatchLocale("es-ZZ").recognised && MatchLocale("es-ZZ").id == Spanish, "es-ZZ"); + Require(MatchLocale("de-Cyrl-RU").recognised && MatchLocale("de-Cyrl-RU").id == German, "de-Cyrl-RU"); +} + +std::vector Posix(const std::map& variables) { + return SystemServiceLocale::PosixHostLocales([&variables](const char* name) -> const char* { + const auto found = variables.find(name); + return found == variables.end() ? nullptr : found->second.c_str(); + }); +} + +void RequirePosixSource() { + using Locales = std::vector; + Require(Posix({}).empty(), "nothing set"); + Require(Posix({{"LANG", ""}, {"LC_ALL", ""}}).empty(), "all empty"); + Require(Posix({{"LANG", "de_DE.UTF-8"}}) == Locales{"de_DE.UTF-8"}, "LANG alone"); + Require(Posix({{"LC_MESSAGES", "fr_FR.UTF-8"}, {"LANG", "de_DE.UTF-8"}}) == Locales{"fr_FR.UTF-8"}, "LC_MESSAGES over LANG"); + Require(Posix({{"LC_ALL", "it_IT.UTF-8"}, {"LC_MESSAGES", "fr_FR.UTF-8"}, {"LANG", "de_DE.UTF-8"}}) == Locales{"it_IT.UTF-8"}, "LC_ALL over the rest"); + Require(Posix({{"LC_ALL", ""}, {"LC_MESSAGES", ""}, {"LANG", "nl_NL"}}) == Locales{"nl_NL"}, "empty values are skipped"); + Require(Posix({{"LC_ALL", ""}, {"LC_MESSAGES", "es_ES"}, {"LANG", "nl_NL"}}) == Locales{"es_ES"}, "empty LC_ALL is skipped"); + Require(Posix({{"LC_CTYPE", "ja_JP.UTF-8"}, {"LC_TIME", "ko_KR"}}).empty(), "other categories are not read"); + Require(Posix({{"LANG", "de_DE.UTF-8"}, {"LANGUAGE", "es:fr"}}) == Locales{"es", "fr", "de_DE.UTF-8"}, "LANGUAGE precedes a real locale"); + Require(Posix({{"LANG", "de_DE.UTF-8"}, {"LANGUAGE", ":pt_BR::en:"}}) == Locales{"pt_BR", "en", "de_DE.UTF-8"}, "empty LANGUAGE entries are skipped"); + Require(Posix({{"LANG", "de_DE.UTF-8"}, {"LANGUAGE", ""}}) == Locales{"de_DE.UTF-8"}, "empty LANGUAGE"); + Require(Posix({{"LANG", "C"}, {"LANGUAGE", "es"}}) == Locales{"C"}, "LANGUAGE is ignored under C"); + Require(Posix({{"LC_ALL", "POSIX"}, {"LANG", "de_DE"}, {"LANGUAGE", "es"}}) == Locales{"POSIX"}, "LANGUAGE is ignored under POSIX"); + Require(Posix({{"LANG", "C.UTF-8"}, {"LANGUAGE", "es"}}) == Locales{"C.UTF-8"}, "LANGUAGE is ignored under C.UTF-8"); + Require(Posix({{"LANGUAGE", "es"}}).empty(), "LANGUAGE alone is ignored"); + Require(Posix({{"LC_ALL", "C"}, {"LANG", "de_DE"}}) == Locales{"C"}, "LC_ALL=C overrides LANG"); +} + +int Resolve(const std::vector& locales, std::vector& notices) { + return SystemServiceLocale::ResolveHostLanguage([&locales] { return locales; }, [¬ices](const std::string& line) { notices.push_back(line); }); +} + +void RequireHostResolution() { + using namespace SystemServiceLocale; + std::vector notices; + Require(Resolve({"de_DE.UTF-8"}, notices) == German && notices.empty(), "one recognised locale"); + Require(Resolve({"pt_BR"}, notices) == PortugueseBrazil && notices.empty(), "pt_BR"); + Require(Resolve({"lt", "uk", "fr"}, notices) == Ukrainian && notices.empty(), "first recognised entry wins"); + Require(Resolve({"es", "fr", "de_DE.UTF-8"}, notices) == Spanish && notices.empty(), "LANGUAGE order"); + Require(Resolve({"en-GB", "fr"}, notices) == EnglishUk && notices.empty(), "first entry before later ones"); + Require(Resolve({"zh-Hant-TW"}, notices) == ChineseTraditional && notices.empty(), "zh-Hant-TW"); + Require(Resolve({"lt_LT.UTF-8"}, notices) == EnglishUs && notices.size() == 1, "unlisted language"); + Require(notices[0] == "ANYPS5_LANGUAGE=auto: the host locale \"lt_LT.UTF-8\" has no console language, using English (United States), language number 1", notices[0]); + notices.clear(); + Require(Resolve({"C"}, notices) == EnglishUs && notices.size() == 1, "C"); + Require(notices[0].find("\"C\"") != std::string::npos && notices[0].find("English (United States)") != std::string::npos, notices[0]); + notices.clear(); + Require(Resolve({"lt", "lv"}, notices) == EnglishUs && notices.size() == 1 && notices[0].find("\"lt\"") != std::string::npos, "first unrecognised entry is named"); + notices.clear(); + Require(Resolve({}, notices) == EnglishUs && notices.size() == 1, "no locale"); + Require(notices[0] == "ANYPS5_LANGUAGE=auto: the host reports no locale, using English (United States), language number 1", notices[0]); + notices.clear(); + Require(Resolve({"garbage!", "ja_JP"}, notices) == Japanese && notices.empty(), "malformed entry is skipped"); +} + +} + +int main() { + RequireBcp47Parsing(); + RequirePosixNormalisation(); + RequireEitherForm(); + RequireLatinEuropeanRows(); + RequireIberianRows(); + RequireChineseRows(); + RequireJapaneseKoreanRows(); + RequireSlavicNordicRows(); + RequireOtherRows(); + RequireFallbackRules(); + RequirePosixSource(); + RequireHostResolution(); +} diff --git a/core/libs/tests/TextureFormatLookup.cpp b/core/libs/tests/TextureFormatLookup.cpp index da6cbdc1d0..3f92ea1b5c 100644 --- a/core/libs/tests/TextureFormatLookup.cpp +++ b/core/libs/tests/TextureFormatLookup.cpp @@ -15,12 +15,27 @@ int main() { if (FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_UNORM, 4) != 56) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_SNORM, 4) != 57) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_SRGB, 4) != 130) std::abort(); + if (FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_SRGB, 4) != FindGuestTextureFormat(VK_FORMAT_R8G8B8A8_SRGB, 4)) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_A2R10G10B10_UNORM_PACK32, 4) != 50) std::abort(); + if (FindGuestColorTargetFormat(VK_FORMAT_A2R10G10B10_UNORM_PACK32, 4) != FindGuestTextureFormat(VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4)) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_R8G8B8A8_UNORM, 4) != 56) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_R16G16B16A16_SFLOAT, 8) != 71) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_UNORM, 8)) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_SNORM, 8)) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_SRGB, 1)) std::abort(); + if (FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_SRGB, 2)) std::abort(); + if (FindGuestColorTargetFormat(VK_FORMAT_B8G8R8A8_SRGB, 8)) std::abort(); + if (FindGuestColorTargetFormat(VK_FORMAT_A2R10G10B10_UNORM_PACK32, 1)) std::abort(); + if (FindGuestColorTargetFormat(VK_FORMAT_A2R10G10B10_UNORM_PACK32, 2)) std::abort(); + if (FindGuestColorTargetFormat(VK_FORMAT_A2R10G10B10_UNORM_PACK32, 8)) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_BC1_RGBA_UNORM_BLOCK, 8)) std::abort(); if (FindGuestColorTargetFormat(VK_FORMAT_UNDEFINED, 4)) std::abort(); + for (auto uncompressed : {1u, 7u, 14u, 22u, 50u, 56u, 71u, 128u, 130u}) { + if (IsBlockCompressed(uncompressed)) std::abort(); + if (BlockWidth(uncompressed) != 1u || BlockHeight(uncompressed) != 1u) std::abort(); + } + for (auto compressed : {169u, 170u, 171u, 172u, 173u, 174u, 175u, 176u, 177u, 178u, 179u, 180u, 181u, 182u}) { + if (!IsBlockCompressed(compressed)) std::abort(); + if (BlockWidth(compressed) != 4u || BlockHeight(compressed) != 4u) std::abort(); + } } diff --git a/core/libs/tests/UltThreadLifecycle.cpp b/core/libs/tests/UltThreadLifecycle.cpp new file mode 100644 index 0000000000..c111d2ce48 --- /dev/null +++ b/core/libs/tests/UltThreadLifecycle.cpp @@ -0,0 +1,365 @@ +#include "prx/libSceUlt/include/Api.hpp" +#include "prx/libkernel/include/Api.hpp" +#include "prx/libkernel/Pthread/include/Cancel.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../prx/libSceUlt/Export.cpp" + +namespace { + +void Require(bool value, int line) { + if (value) return; + std::fprintf(stderr, "ULT lifecycle check failed at line %d\n", line); + std::abort(); +} + +#define CHECK(value) Require((value), __LINE__) + +struct alignas(8) Runtime { std::byte bytes[4096]{}; }; +struct alignas(8) Thread { std::byte bytes[512]{}; }; + +struct HostThread { + std::thread thread; + void* result = nullptr; + std::uint64_t generation = 0; +}; + +std::atomic entryFinished{false}; +std::atomic joinCalls{0}; +std::atomic hostThreads{0}; +std::binary_semaphore joining{0}; +std::binary_semaphore releaseJoin{0}; +bool gateJoin = false; +int createFailure = 0; +int joinFailure = 0; +int sentinelExecutions = 0; + +void APS5_VABI Sentinel(void*) { + ++sentinelExecutions; +} + +#ifdef ULT_LIFECYCLE_INTERPOSE +thread_local int actor = 0; +std::atomic pauseSelection{false}; +std::atomic failCleanupSetup{false}; +std::atomic originalGeneration{0}; +std::binary_semaphore selected{0}; +std::binary_semaphore releaseSelection{0}; +std::binary_semaphore creating{0}; +std::binary_semaphore releaseCreate{0}; +std::binary_semaphore registryAttempt{0}; +std::binary_semaphore releaseRegistryAttempt{0}; +std::binary_semaphore replacementStarted{0}; +std::binary_semaphore releaseReplacement{0}; +bool gateCreate = false; +#endif + +void Await(std::binary_semaphore& gate) { + CHECK(gate.try_acquire_for(std::chrono::seconds(2))); +} + +std::int32_t APS5_VABI Entry(std::uint64_t argument) { + void* self = nullptr; + CHECK(sceUltUlthreadGetSelf(&self) == ULT_OK && self != nullptr); + CHECK(sceUltUlthreadYield() == ULT_OK); + return static_cast(argument); +} + +void Create(Runtime& runtime, Thread& thread) { + CHECK(sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == ULT_OK); + CHECK(sceUltUlthreadCreate(&thread, "thread", Entry, 73, nullptr, 0, &runtime, nullptr, 0) == ULT_OK); + entryFinished.wait(false); +} + +void Finish(Runtime& runtime, Thread& thread) { + std::int32_t status = -1; + CHECK(sceUltUlthreadJoin(&thread, &status) == ULT_OK && status == 73); + CHECK(hostThreads.load() == 0); + CHECK(sceUltUlthreadJoin(&thread, &status) == ULT_ERROR_STATE && status == 73); + CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == ULT_OK); + CHECK(sceUltFinalize() == ULT_OK); +} + +void CreateFailure(int failure) { + Runtime runtime; + Thread thread; + CHECK(sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == ULT_OK); + createFailure = failure; + bool threw = false; + int result = ULT_OK; + try { + result = sceUltUlthreadCreate(&thread, "thread", Entry, 73, nullptr, 0, &runtime, nullptr, 0); + } catch (const std::runtime_error&) { + threw = true; + } + CHECK(threw == (failure != 1)); + if (failure == 1) CHECK(result == ULT_ERROR_AGAIN); + if (failure == 3) { + CHECK(sceUltFinalize() == ULT_ERROR_BUSY); + CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == ULT_ERROR_STATE); + Finish(runtime, thread); + return; + } + CHECK(hostThreads.load() == 0 && !entryFinished.load()); + CHECK(sceUltUlthreadTryJoin(&thread, nullptr) == ULT_ERROR_STATE); + CHECK(sceUltUlthreadCreate(&thread, "retry", Entry, 73, nullptr, 0, &runtime, nullptr, 0) == ULT_OK); + Finish(runtime, thread); +} + +void Ownership(bool nonblocking) { + Runtime runtime; + Thread thread; + Create(runtime, thread); + gateJoin = true; + std::int32_t firstStatus = -1; + int firstResult = ULT_ERROR_STATE; + std::thread first([&] { + firstResult = nonblocking ? sceUltUlthreadTryJoin(&thread, &firstStatus) : sceUltUlthreadJoin(&thread, &firstStatus); + }); + Await(joining); + std::int32_t secondStatus = -2; + CHECK(sceUltUlthreadJoin(&thread, &secondStatus) == ULT_ERROR_STATE && secondStatus == -2); + CHECK(sceUltUlthreadTryJoin(&thread, &secondStatus) == ULT_ERROR_STATE && secondStatus == -2); + CHECK(joinCalls.load() == 1); + CHECK(sceUltUlthreadCreate(&thread, "replacement", Entry, 74, nullptr, 0, &runtime, nullptr, 0) == ULT_ERROR_STATE); + CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == ULT_ERROR_STATE); + CHECK(sceUltFinalize() == ULT_ERROR_BUSY); + releaseJoin.release(); + first.join(); + CHECK(firstResult == ULT_OK && firstStatus == 73 && hostThreads.load() == 0); + CHECK(sceUltUlthreadTryJoin(&thread, &secondStatus) == ULT_ERROR_STATE && secondStatus == -2); + gateJoin = false; + entryFinished.store(false); + CHECK(sceUltUlthreadCreate(&thread, "replacement", Entry, 73, nullptr, 0, &runtime, nullptr, 0) == ULT_OK); + Finish(runtime, thread); +} + +void JoinFailure(int failure) { + Runtime runtime; + Thread thread; + Create(runtime, thread); + void* const before = ThreadCancel::CleanupMark(); + if (failure == 3) pthread_cleanup_push_nid_postfix(Sentinel, nullptr); + joinFailure = failure; + std::int32_t status = -1; + bool threw = false; + int result = ULT_OK; + try { + result = sceUltUlthreadJoin(&thread, &status); + } catch (const std::runtime_error&) { + threw = true; + } + CHECK(threw == (failure >= 2)); + if (!threw) CHECK(result == ULT_ERROR_STATE); + CHECK(status == -1 && hostThreads.load() == 1); + if (failure == 3) { + CHECK(sentinelExecutions == 1 && ThreadCancel::CleanupMark() == before); + std::thread replacement([&] { Finish(runtime, thread); }); + replacement.join(); + } else Finish(runtime, thread); +} + +#ifdef ULT_LIFECYCLE_INTERPOSE +void Publication() { + Runtime runtime; + Thread thread; + CHECK(sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == ULT_OK); + gateCreate = true; + std::thread creator([&] { + CHECK(sceUltUlthreadCreate(&thread, "thread", Entry, 73, nullptr, 0, &runtime, nullptr, 0) == ULT_OK); + }); + Await(creating); + std::int32_t status = -1; + std::thread joiner([&] { + actor = 2; + CHECK(sceUltUlthreadJoin(&thread, &status) == ULT_OK); + }); + Await(registryAttempt); + const bool registryAvailable = gMutex.try_lock(); + if (registryAvailable) gMutex.unlock(); + CHECK(!registryAvailable && joinCalls.load() == 0); + releaseRegistryAttempt.release(); + releaseCreate.release(); + creator.join(); + joiner.join(); + CHECK(status == 73 && hostThreads.load() == 0); + CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == ULT_OK); + CHECK(sceUltFinalize() == ULT_OK); +} + +std::int32_t APS5_VABI Replacement(std::uint64_t argument) { + replacementStarted.release(); + Await(releaseReplacement); + return static_cast(argument); +} + +void Generation() { + Runtime runtime; + Thread thread; + Create(runtime, thread); + pauseSelection.store(true); + std::int32_t originalStatus = -1; + int originalResult = ULT_ERROR_STATE; + std::thread original([&] { + actor = 1; + originalResult = sceUltUlthreadTryJoin(&thread, &originalStatus); + }); + Await(selected); + std::int32_t contenderStatus = -2; + const int contender = sceUltUlthreadJoin(&thread, &contenderStatus); + if (contender == ULT_OK) { + CHECK(contenderStatus == 73); + CHECK(sceUltUlthreadCreate(&thread, "replacement", Replacement, 92, nullptr, 0, &runtime, nullptr, 0) == ULT_OK); + Await(replacementStarted); + } else { + CHECK(contender == ULT_ERROR_STATE && contenderStatus == -2); + CHECK(sceUltUlthreadCreate(&thread, "replacement", Replacement, 92, nullptr, 0, &runtime, nullptr, 0) == ULT_ERROR_STATE); + } + releaseSelection.release(); + original.join(); + CHECK(originalGeneration.load() == 73 && originalResult == ULT_OK && originalStatus == 73); + CHECK(contender == ULT_ERROR_STATE && hostThreads.load() == 0); + CHECK(sceUltUlthreadCreate(&thread, "replacement", Replacement, 92, nullptr, 0, &runtime, nullptr, 0) == ULT_OK); + Await(replacementStarted); + CHECK(sceUltUlthreadTryJoin(&thread, &contenderStatus) == ULT_ERROR_AGAIN && contenderStatus == -2); + releaseReplacement.release(); + CHECK(sceUltUlthreadJoin(&thread, &contenderStatus) == ULT_OK && contenderStatus == 92); + CHECK(sceUltUlthreadRuntimeDestroy(&runtime) == ULT_OK); + CHECK(sceUltFinalize() == ULT_OK); +} + +void CleanupSetup() { + Runtime runtime; + Thread thread; + Create(runtime, thread); + failCleanupSetup.store(true); + std::int32_t status = -1; + bool threw = false; + try { + sceUltUlthreadJoin(&thread, &status); + } catch (const std::runtime_error&) { + threw = true; + } + CHECK(threw && status == -1 && joinCalls.load() == 0); + std::thread replacement([&] { Finish(runtime, thread); }); + replacement.join(); +} +#endif + +} + +extern "C" { + +#ifdef ULT_LIFECYCLE_INTERPOSE +int __real_pthread_mutex_lock(pthread_mutex_t* mutex); +int __real_pthread_mutex_unlock(pthread_mutex_t* mutex); +void APS5_VABI __real___pthread_cleanup_push_imp_nid_postfix(void (APS5_VABI* routine)(void*), void* argument, void* info); + +int __wrap_pthread_mutex_lock(pthread_mutex_t* mutex) { + if (actor == 2 && mutex == gMutex.native_handle()) { + actor = 0; + registryAttempt.release(); + Await(releaseRegistryAttempt); + } + return __real_pthread_mutex_lock(mutex); +} + +int __wrap_pthread_mutex_unlock(pthread_mutex_t* mutex) { + const int result = __real_pthread_mutex_unlock(mutex); + if (actor == 1 && mutex == gMutex.native_handle() && pauseSelection.exchange(false)) { + selected.release(); + Await(releaseSelection); + } + return result; +} + +void APS5_VABI __wrap___pthread_cleanup_push_imp_nid_postfix(void (APS5_VABI* routine)(void*), void* argument, void* info) { + if (failCleanupSetup.exchange(false)) throw std::runtime_error("Injected cleanup setup failure"); + __real___pthread_cleanup_push_imp_nid_postfix(routine, argument, info); +} +#endif + +int APS5_VABI scePthreadCreate(Pthread* value, const PthreadAttr*, PthreadEntry entry, void* argument, const char*) { + const int failure = createFailure; + createFailure = 0; + if (failure == 1) return 11; + if (failure == 2) throw std::runtime_error("Injected prepublication creation failure"); +#ifdef ULT_LIFECYCLE_INTERPOSE + if (gateCreate) { + gateCreate = false; + creating.release(); + Await(releaseCreate); + } +#endif + auto state = std::make_unique(); + state->generation = static_cast(argument)->_arg; + auto* raw = state.get(); + state->thread = std::thread([=] { + raw->result = entry(argument); + entryFinished.store(true); + entryFinished.notify_all(); + }); + ++hostThreads; + *value = reinterpret_cast(state.release()); + if (failure == 3) throw std::runtime_error("Injected postpublication creation failure"); + return 0; +} + +int APS5_VABI scePthreadJoin(Pthread value, void** result) { + const int call = joinCalls.fetch_add(1); +#ifdef ULT_LIFECYCLE_INTERPOSE + if (actor == 1) { + CHECK(value != nullptr); + const auto generation = reinterpret_cast(value)->generation; + originalGeneration.store(generation); + if (generation != 73) return 22; + } +#endif + if (gateJoin) { + if (call != 0) return 22; + joining.release(); + Await(releaseJoin); + } + const int failure = joinFailure; + joinFailure = 0; + if (failure == 1) return 22; + if (failure == 2) throw std::runtime_error("Injected precommit join failure"); + if (failure == 3) { + __pthread_cleanup_pop_imp_nid_postfix(1); + __pthread_cleanup_pop_imp_nid_postfix(1); + throw std::runtime_error("Injected failure after cancellation cleanup"); + } + CHECK(value != nullptr); + std::unique_ptr state(reinterpret_cast(value)); + state->thread.join(); + if (result != nullptr) *result = state->result; + --hostThreads; + return 0; +} + +} + +int main(int argc, char** argv) { + CHECK(argc == 2); + if (std::strcmp(argv[1], "create-error") == 0) CreateFailure(1); + else if (std::strcmp(argv[1], "create-exception") == 0) CreateFailure(2); + else if (std::strcmp(argv[1], "published-exception") == 0) CreateFailure(3); + else if (std::strcmp(argv[1], "join-owner") == 0) Ownership(false); + else if (std::strcmp(argv[1], "tryjoin-owner") == 0) Ownership(true); + else if (std::strcmp(argv[1], "join-error") == 0) JoinFailure(1); + else if (std::strcmp(argv[1], "join-exception") == 0) JoinFailure(2); + else if (std::strcmp(argv[1], "join-cleanup-exception") == 0) JoinFailure(3); +#ifdef ULT_LIFECYCLE_INTERPOSE + else if (std::strcmp(argv[1], "publication") == 0) Publication(); + else if (std::strcmp(argv[1], "generation") == 0) Generation(); + else if (std::strcmp(argv[1], "cleanup-setup") == 0) CleanupSetup(); +#endif + else CHECK(false); +} diff --git a/core/libs/tests/WindowsCrashThreadName.cpp b/core/libs/tests/WindowsCrashThreadName.cpp new file mode 100644 index 0000000000..e98974576a --- /dev/null +++ b/core/libs/tests/WindowsCrashThreadName.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc != 2) { + std::fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + SetUnhandledExceptionFilter([](EXCEPTION_POINTERS*) -> LONG { + TerminateProcess(GetCurrentProcess(), 3); + return EXCEPTION_EXECUTE_HANDLER; + }); + if (LoadLibraryExA(argv[1], nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) == nullptr) { + std::fprintf(stderr, "%s: LoadLibraryEx failed with error %lu\n", argv[1], GetLastError()); + return 1; + } + HANDLE worker = CreateThread(nullptr, 0, [](LPVOID) -> DWORD { + Sleep(INFINITE); + return 0; + }, nullptr, 0, nullptr); + if (worker == nullptr) { + std::fprintf(stderr, "CreateThread failed with error %lu\n", GetLastError()); + return 1; + } + const std::wstring mainName = std::wstring(126, L'a') + L'Z' + std::wstring(70, L'b'); + const std::wstring workerName = std::wstring(126, L'c') + L'Y' + std::wstring(70, L'd'); + const HRESULT mainResult = SetThreadDescription(GetCurrentThread(), mainName.c_str()); + const HRESULT workerResult = SetThreadDescription(worker, workerName.c_str()); + if (FAILED(mainResult) || FAILED(workerResult)) { + std::fprintf(stderr, "SetThreadDescription failed: 0x%08lx 0x%08lx\n", static_cast(mainResult), static_cast(workerResult)); + return 1; + } + std::fputs("faulting\n", stderr); + std::fflush(stderr); + const std::uintptr_t unmapped = 8; + asm volatile("movq (%0), %%rax" : : "r"(unmapped) : "rax", "memory"); + std::fputs("the read of address 8 did not fault\n", stderr); + return 1; +} diff --git a/core/libs/tests/WindowsIntegerOverflowReport.cpp b/core/libs/tests/WindowsIntegerOverflowReport.cpp new file mode 100644 index 0000000000..fc780ff73e --- /dev/null +++ b/core/libs/tests/WindowsIntegerOverflowReport.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +int main(int argc, char** argv) { + if (argc != 2) { + std::fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + SetUnhandledExceptionFilter([](EXCEPTION_POINTERS*) -> LONG { + TerminateProcess(GetCurrentProcess(), 3); + return EXCEPTION_EXECUTE_HANDLER; + }); + if (LoadLibraryExA(argv[1], nullptr, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) == nullptr) { + std::fprintf(stderr, "%s: LoadLibraryEx failed with error %lu\n", argv[1], GetLastError()); + return 1; + } + volatile int dividend = INT_MIN; + volatile int divisor = -1; + int quotient = dividend; + const int by = divisor; + std::fputs("dividing\n", stderr); + std::fflush(stderr); + asm volatile( + "cltd\n\t" + "idivl %1" + : "+a"(quotient) + : "r"(by) + : "rdx", "cc"); + std::fprintf(stderr, "the division did not fault: %d\n", quotient); + return 1; +} diff --git a/core/libs/tests/WriteWatch.cpp b/core/libs/tests/WriteWatch.cpp index a1b5dfc598..ed0d52a476 100644 --- a/core/libs/tests/WriteWatch.cpp +++ b/core/libs/tests/WriteWatch.cpp @@ -1,5 +1,6 @@ #include "prx/libc/include/GuestWriteWatch.hpp" +#include #include #include #include @@ -7,15 +8,18 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include +#include #include #include #include @@ -336,6 +340,383 @@ void CheckThreads() { Require(region.Collect().empty(), "writes are reported after the writers stopped"); } +int PageProtection(const void* page) { + mach_vm_address_t address = reinterpret_cast(page); + mach_vm_size_t size = 0; + vm_region_basic_info_data_64_t info{}; + mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object = MACH_PORT_NULL; + Require(mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast(&info), &count, &object) == KERN_SUCCESS && address <= reinterpret_cast(page), "cannot read the kernel protection of a page"); + return ((info.protection & VM_PROT_READ) ? PROT_READ : 0) | ((info.protection & VM_PROT_WRITE) ? PROT_WRITE : 0) | ((info.protection & VM_PROT_EXECUTE) ? PROT_EXEC : 0); +} + +struct TrapLog { + std::mutex mutex; + std::vector> calls; + std::vector threads; + std::atomic fill{0}; + std::atomic alias{0}; + std::atomic bracket{false}; +}; + +TrapLog& Traps() { + static TrapLog log; + return log; +} + +void ResolveTrap(std::uintptr_t begin, std::uintptr_t end) { + auto& log = Traps(); + { + const std::lock_guard lock(log.mutex); + log.calls.emplace_back(begin, end); + log.threads.push_back(std::this_thread::get_id()); + } + if (log.bracket) GuestWriteWatchBeginHostWrite_nid_postfix(reinterpret_cast(begin), end - begin); + std::memset(reinterpret_cast(begin + log.alias.load()), log.fill.load(), end - begin); + if (log.bracket) GuestWriteWatchEndHostWrite_nid_postfix(reinterpret_cast(begin), end - begin); + GuestWriteWatchReleaseReads_nid_postfix(begin, end - begin); +} + +std::vector> TakeTrapCalls(std::vector* threads = nullptr) { + auto& log = Traps(); + const std::lock_guard lock(log.mutex); + if (threads != nullptr) *threads = log.threads; + log.threads.clear(); + return std::exchange(log.calls, {}); +} + +std::uint8_t ReadOnThread(const std::uint8_t* address, std::thread::id* reader = nullptr) { + std::uint8_t value = 0; + std::thread([&] { + value = *reinterpret_cast(address); + if (reader != nullptr) *reader = std::this_thread::get_id(); + }).join(); + return value; +} + +void CheckReadTrap() { + ::alarm(20); + Region region(10); + Region unregistered(1); + GuestWriteWatchRegister_nid_postfix(region.base, region.bytes); + Require(region.Collect() == All(10), "a new range is not reported in full"); + const auto page = region.page; + Require(!GuestWriteWatchTrapReads_nid_postfix(region.Address(1), page), "a read trap was set without a resolver"); + Require(GuestWriteWatchSetReadTrapResolver_nid_postfix(&ResolveTrap), "cannot set the read trap resolver"); + Require(!GuestWriteWatchTrapReads_nid_postfix(unregistered.Address(0), page), "a read trap was set on unwatched memory"); + Require(!GuestWriteWatchTrapReads_nid_postfix(region.Address(1), 0), "an empty read trap was set"); + + auto& log = Traps(); + log.fill = 0xa5; + const auto begin = region.Address(1) + 100; + Require(GuestWriteWatchTrapReads_nid_postfix(begin, page), "cannot set a read trap"); + Require(PageProtection(region.base + page) == PROT_NONE && PageProtection(region.base + 2 * page) == PROT_NONE, "a read trap does not take the access to its pages"); + Require(PageProtection(region.base) == PROT_READ && PageProtection(region.base + 3 * page) == PROT_READ, "a read trap changes pages outside its range"); + std::thread::id reader; + Require(ReadOnThread(region.base + page + 105, &reader) == 0xa5, "a read of a trapped page does not see the resolver's bytes"); + std::vector threads; + Require(TakeTrapCalls(&threads) == std::vector>{{begin, begin + page}}, "a trapped read does not resolve its range exactly once"); + Require(threads.size() == 1 && threads[0] != reader && threads[0] != std::this_thread::get_id(), "a trapped read is not resolved on the resolver thread"); + Require(PageProtection(region.base + page) == (PROT_READ | PROT_WRITE) && PageProtection(region.base + 2 * page) == (PROT_READ | PROT_WRITE), "the resolver's writes to its resolved pages are not let through"); + Require(ReadOnThread(region.base + 2 * page) == 0xa5 && TakeTrapCalls().empty(), "a resolved read trap resolves again"); + Require(region.Collect() == std::set({1, 2}), "the resolver's writes are not reported"); + + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(3), page) && GuestWriteWatchTrapReads_nid_postfix(region.Address(3), page), "cannot set a read trap twice"); + GuestWriteWatchReleaseReads_nid_postfix(region.Address(3), page); + Require(PageProtection(region.base + 3 * page) == PROT_READ, "a released read trap set twice does not give back the watch's protection"); + Require(ReadOnThread(region.base + 3 * page) == 0 && TakeTrapCalls().empty(), "a released read trap resolves"); + + log.fill = 0x11; + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(4), page), "cannot set a read trap"); + region.base[4 * page] = 0x77; + Require(region.base[4 * page] == 0x77 && region.base[4 * page + 1] == 0x11, "a write to a trapped page does not land after the resolver's bytes"); + Require(TakeTrapCalls().size() == 1, "a write to a trapped page does not resolve it once"); + Require(region.Collect() == std::set({4}), "a write to a trapped page is not reported"); + + region.base[5 * page] = 1; + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(5), page), "cannot set a read trap"); + Require(region.Collect() == std::set({5}), "a written trapped page is not reported"); + Require(PageProtection(region.base + 5 * page) == PROT_NONE, "a collection gives back the access to a trapped page"); + Require(ReadOnThread(region.base + 5 * page) == 0x11 && TakeTrapCalls().size() == 1, "a trapped page read after a collection is not resolved"); + + log.fill = 0x22; + const auto half = page / 2; + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(6), half), "cannot set a read trap"); + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(6) + half, page), "cannot set a read trap"); + GuestWriteWatchReleaseReads_nid_postfix(region.Address(6), half); + Require(PageProtection(region.base + 6 * page) == PROT_NONE, "releasing one read trap gives back a page another one still traps"); + Require(ReadOnThread(region.base + 6 * page) == 0, "a page shared by two read traps does not keep the released trap's bytes"); + Require(TakeTrapCalls() == std::vector>{{region.Address(6) + half, region.Address(7) + half}}, "a read of a shared page does not resolve exactly the trap still set"); + + log.fill = 0x33; + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(8), page), "cannot set a read trap"); + GuestWriteWatchBeginHostWrite_nid_postfix(region.base + 8 * page, 8); + Require(TakeTrapCalls(&threads).size() == 1 && threads[0] == std::this_thread::get_id(), "a host write over a trapped page does not resolve it first on its own thread"); + Require(!GuestWriteWatchTrapReads_nid_postfix(region.Address(8), page), "a read trap was set under a host write"); + int descriptors[2]; + Require(::pipe(descriptors) == 0, "cannot create a pipe"); + const std::array payload{'t', 'r', 'a', 'p', 'p', 'e', 'd', '!'}; + Require(::write(descriptors[1], payload.data(), payload.size()) == static_cast(payload.size()), "cannot fill the pipe"); + Require(::read(descriptors[0], region.base + 8 * page, payload.size()) == static_cast(payload.size()), "a system call cannot write a page whose read trap a host write resolved"); + GuestWriteWatchEndHostWrite_nid_postfix(region.base + 8 * page, 8); + ::close(descriptors[0]); + ::close(descriptors[1]); + Require(std::memcmp(region.base + 8 * page, payload.data(), payload.size()) == 0 && region.base[8 * page + 8] == 0x33, "a host write over a resolved trap lost bytes"); + + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(9), page), "cannot set a read trap"); + Require(GuestWriteWatchProtect_nid_postfix(region.base + 9 * page, page, PROT_READ) == 0, "cannot protect a trapped page"); + Require(TakeTrapCalls().size() == 1 && PageProtection(region.base + 9 * page) == PROT_READ, "a protect of a trapped page does not resolve it and apply the new protection"); + + log.fill = 0x44; + log.bracket = true; + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(3), page), "cannot set a read trap"); + Require(ReadOnThread(region.base + 3 * page) == 0x44 && TakeTrapCalls().size() == 1, "a resolver that brackets its stores as a host write does not resolve its trap"); + log.bracket = false; + + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(0), page), "cannot set a read trap"); + Require(GuestWriteWatchUnregister_nid_postfix(region.base, region.bytes), "cannot unregister the test region"); + Require(PageProtection(region.base) == (PROT_READ | PROT_WRITE), "unregistering a trapped page does not give back its protection"); + Require(ReadOnThread(region.base) == 0 && TakeTrapCalls().empty(), "an unregistered read trap resolves"); + ::alarm(0); +} + +void CheckReadTrapFaults() { + const auto bytes = static_cast(::getpagesize()); + for (const bool resolved : {false, true}) { + const auto child = ::fork(); + Require(child >= 0, "cannot fork a faulting child"); + if (child == 0) { + ::alarm(2); + Region trapped(1); + GuestWriteWatchRegister_nid_postfix(trapped.base, trapped.bytes); + if (!GuestWriteWatchSetReadTrapResolver_nid_postfix(&ResolveTrap) || !GuestWriteWatchTrapReads_nid_postfix(trapped.Address(0), trapped.page)) ::_exit(2); + if (resolved) static_cast(*reinterpret_cast(trapped.base)); + char path[] = "/tmp/anyps5-write-watch-XXXXXX"; + const int file = ::mkstemp(path); + if (file < 0) ::_exit(2); + ::unlink(path); + if (::ftruncate(file, static_cast(bytes)) != 0) ::_exit(2); + auto* mapped = static_cast(::mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, file, 0)); + if (mapped == MAP_FAILED || ::ftruncate(file, 0) != 0) ::_exit(2); + ::close(file); + GuestWriteWatchRegister_nid_postfix(mapped, bytes); + static_cast(*reinterpret_cast(mapped)); + ::_exit(3); + } + int status = 0; + Require(::waitpid(child, &status, 0) == child, "cannot wait for a faulting child"); + Require(WIFSIGNALED(status) && (WTERMSIG(status) == SIGBUS || WTERMSIG(status) == SIGSEGV), "a read fault on a mapping with missing backing is not delivered while read traps are set"); + } +} + +void CheckReadTrapThreads() { + ::alarm(20); + constexpr std::size_t Threads = 4; + constexpr std::size_t PagesPerThread = 8; + Region region(Threads * PagesPerThread); + char path[] = "/tmp/anyps5-read-trap-XXXXXX"; + const int file = ::mkstemp(path); + Require(file >= 0, "cannot create the shared backing"); + ::unlink(path); + Require(::ftruncate(file, static_cast(region.bytes)) == 0, "cannot size the shared backing"); + Require(::mmap(region.base, region.bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, file, 0) == region.base, "cannot map the shared backing"); + auto* alias = static_cast(::mmap(nullptr, region.bytes, PROT_READ | PROT_WRITE, MAP_SHARED, file, 0)); + Require(alias != MAP_FAILED, "cannot map the second view of the shared backing"); + ::close(file); + Traps().alias = alias - region.base; + GuestWriteWatchRegister_nid_postfix(region.base, region.bytes); + static_cast(region.Collect()); + Require(GuestWriteWatchSetReadTrapResolver_nid_postfix(&ResolveTrap), "cannot set the read trap resolver"); + Traps().fill = 0x5a; + for (std::size_t index = 0; index < Threads * PagesPerThread; ++index) + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(index), region.page), "cannot set a read trap"); + std::atomic start{false}; + std::atomic wrong{0}; + std::array readers; + for (std::size_t thread = 0; thread < Threads; ++thread) { + readers[thread] = std::thread([&, thread] { + while (!start.load()) std::this_thread::yield(); + for (std::size_t index = 0; index < Threads * PagesPerThread; ++index) { + const auto page = (index + (thread % 2) * PagesPerThread) % (Threads * PagesPerThread); + if (*reinterpret_cast(region.base + page * region.page + thread) != 0x5a) wrong.fetch_add(1); + } + }); + } + start.store(true); + for (auto& reader : readers) reader.join(); + Require(wrong.load() == 0, "a concurrent read of a trapped page does not see the resolver's bytes"); + auto calls = TakeTrapCalls(); + std::sort(calls.begin(), calls.end()); + Require(calls.size() == Threads * PagesPerThread && std::adjacent_find(calls.begin(), calls.end()) == calls.end(), "concurrent reads do not resolve each trap exactly once"); + Traps().alias = 0; + ::munmap(alias, region.bytes); + ::alarm(0); +} + +void CheckReadTrapLateFaults() { + const pid_t child = ::fork(); + Require(child >= 0, "cannot fork a child for late read faults"); + if (child == 0) { + ::alarm(10); + try { + constexpr std::size_t Readers = 32; + Region region(1); + GuestWriteWatchRegister_nid_postfix(region.base, region.bytes); + static_cast(region.Collect()); + Require(GuestWriteWatchSetReadTrapResolver_nid_postfix(&ResolveTrap), "cannot set the read trap resolver"); + Traps().fill = 0x6b; + Require(GuestWriteWatchTrapReads_nid_postfix(region.Address(0), region.page), "cannot set a read trap"); + mach_port_t port = MACH_PORT_NULL; + Require(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port) == KERN_SUCCESS && mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND) == KERN_SUCCESS, "cannot create the exception port"); + std::atomic ready{0}; + std::atomic start{false}; + std::atomic wrong{0}; + std::array readers; + for (auto& reader : readers) { + reader = std::thread([&] { + if (thread_set_exception_ports(mach_thread_self(), EXC_MASK_BAD_ACCESS, port, EXCEPTION_DEFAULT, THREAD_STATE_NONE) != KERN_SUCCESS) wrong.fetch_add(1); + ready.fetch_add(1); + while (!start.load()) std::this_thread::yield(); + if (*reinterpret_cast(region.base) != 0x6b) wrong.fetch_add(1); + }); + } + while (ready.load() != Readers) std::this_thread::yield(); + start.store(true); + struct Request { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + NDR_record_t ndr; + exception_type_t exception; + mach_msg_type_number_t codes; + integer_t code[2]; + mach_msg_trailer_t trailer; + }; + std::vector held(Readers); + for (auto& request : held) { + Require(mach_msg(&request.header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(Request), port, 5000, MACH_PORT_NULL) == MACH_MSG_SUCCESS, "a reader of a trapped page did not fault"); + Require(request.exception == EXC_BAD_ACCESS, "a reader of a trapped page raised another exception"); + mach_port_deallocate(mach_task_self(), request.thread.name); + mach_port_deallocate(mach_task_self(), request.task.name); + } + Require(*reinterpret_cast(region.base) == 0x6b, "a read of a trapped page does not see the resolver's bytes"); + Require(PageProtection(region.base) & PROT_READ, "a resolved trap does not give back read access"); + for (const auto& request : held) { + struct { + mach_msg_header_t header; + NDR_record_t ndr; + kern_return_t result; + } reply{}; + reply.header.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.header.msgh_bits), 0); + reply.header.msgh_remote_port = request.header.msgh_remote_port; + reply.header.msgh_size = sizeof(reply); + reply.header.msgh_id = request.header.msgh_id + 100; + reply.ndr = NDR_record; + reply.result = KERN_FAILURE; + Require(mach_msg(&reply.header, MACH_SEND_MSG, sizeof(reply), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL) == MACH_MSG_SUCCESS, "cannot pass a held fault on as a signal"); + } + for (auto& reader : readers) reader.join(); + Require(wrong.load() == 0, "a reader whose fault arrived after its page was given back does not see the resolver's bytes"); + Require(TakeTrapCalls().size() == 1, "late read faults resolve the trap again"); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + ::_exit(1); + } + ::_exit(0); + } + int status = 0; + Require(::waitpid(child, &status, 0) == child, "cannot wait for the late read fault child"); + Require(WIFEXITED(status) && WEXITSTATUS(status) == 0, "32 read faults that arrive after their page was given back are not all retried"); +} + +void CheckReadTrapFaultBound() { + const pid_t child = ::fork(); + Require(child >= 0, "cannot fork a child for persistent read faults"); + if (child == 0) { + ::alarm(20); + try { + constexpr std::size_t Readers = 66; + constexpr std::size_t Rounds = 40; + const auto bytes = static_cast(::getpagesize()); + Region trapped(1); + GuestWriteWatchRegister_nid_postfix(trapped.base, trapped.bytes); + Require(GuestWriteWatchSetReadTrapResolver_nid_postfix(&ResolveTrap) && GuestWriteWatchTrapReads_nid_postfix(trapped.Address(0), trapped.page), "cannot set a read trap"); + char path[] = "/tmp/anyps5-write-watch-XXXXXX"; + const int file = ::mkstemp(path); + Require(file >= 0, "cannot create the backing file"); + ::unlink(path); + Require(::ftruncate(file, static_cast(bytes)) == 0, "cannot size the backing file"); + auto* mapped = static_cast(::mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, file, 0)); + Require(mapped != MAP_FAILED && ::ftruncate(file, 0) == 0, "cannot truncate a watched file mapping"); + ::close(file); + GuestWriteWatchRegister_nid_postfix(mapped, bytes); + mach_port_t port = MACH_PORT_NULL; + Require(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port) == KERN_SUCCESS && mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND) == KERN_SUCCESS, "cannot create the exception port"); + std::atomic ready{0}; + std::atomic start{false}; + std::vector readers; + for (std::size_t index = 0; index < Readers; ++index) { + readers.emplace_back([&] { + Require(thread_set_exception_ports(mach_thread_self(), EXC_MASK_BAD_ACCESS, port, EXCEPTION_DEFAULT, THREAD_STATE_NONE) == KERN_SUCCESS, "cannot set a thread exception port"); + ready.fetch_add(1); + while (!start.load()) std::this_thread::yield(); + static_cast(*reinterpret_cast(mapped)); + }); + } + while (ready.load() != Readers) std::this_thread::yield(); + struct Request { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t thread; + mach_msg_port_descriptor_t task; + NDR_record_t ndr; + exception_type_t exception; + mach_msg_type_number_t codes; + integer_t code[2]; + mach_msg_trailer_t trailer; + }; + const auto receive = [&](Request& request) { + Require(mach_msg(&request.header, MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0, sizeof(Request), port, 5000, MACH_PORT_NULL) == MACH_MSG_SUCCESS, "a reader of a truncated mapping did not fault"); + mach_port_deallocate(mach_task_self(), request.task.name); + }; + const auto pass = [](const Request& request) { + struct { + mach_msg_header_t header; + NDR_record_t ndr; + kern_return_t result; + } reply{}; + reply.header.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.header.msgh_bits), 0); + reply.header.msgh_remote_port = request.header.msgh_remote_port; + reply.header.msgh_size = sizeof(reply); + reply.header.msgh_id = request.header.msgh_id + 100; + reply.ndr = NDR_record; + reply.result = KERN_FAILURE; + Require(mach_msg(&reply.header, MACH_SEND_MSG, sizeof(reply), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL) == MACH_MSG_SUCCESS, "cannot pass a held fault on as a signal"); + }; + start.store(true); + std::vector held(Readers); + for (auto& request : held) receive(request); + for (std::size_t round = 0; round < Rounds; ++round) { + for (auto& request : held) { + const auto thread = request.thread.name; + pass(request); + receive(request); + Require(request.thread.name == thread, "a held reader faulted out of turn"); + mach_port_deallocate(mach_task_self(), thread); + } + } + ::_exit(3); + } catch (const std::exception& error) { + std::fprintf(stderr, "%s\n", error.what()); + ::_exit(1); + } + } + int status = 0; + Require(::waitpid(child, &status, 0) == child, "cannot wait for the persistent read fault child"); + Require(WIFSIGNALED(status) && (WTERMSIG(status) == SIGBUS || WTERMSIG(status) == SIGSEGV), "66 threads that keep faulting on a truncated mapping in turn are retried without bound"); +} + void CheckRaisedSignals() { Require(GuestWriteWatchAvailable_nid_postfix(), "write watch is unavailable"); for (const int signal : {SIGBUS, SIGSEGV}) { @@ -410,7 +791,7 @@ void CheckFaultAhead(bool enabled) { int main(int argc, char** argv) { try { - Require(argc == 2, "expected collect, fresh, armed, register, mapped, host, empty, protection, overlaps, threads, raised, fault-ahead or fault-ahead-off"); + Require(argc == 2, "expected collect, fresh, armed, register, mapped, host, empty, protection, overlaps, threads, raised, fault-ahead, fault-ahead-off, read-trap, read-trap-threads, read-trap-fault, read-trap-late or read-trap-fault-bound"); const std::string_view mode(argv[1]); if (mode == "collect") CheckCollect(); else if (mode == "fresh") CheckFresh(); @@ -427,6 +808,11 @@ int main(int argc, char** argv) { else if (mode == "raised") CheckRaisedSignals(); else if (mode == "fault-ahead") CheckFaultAhead(true); else if (mode == "fault-ahead-off") CheckFaultAhead(false); + else if (mode == "read-trap") CheckReadTrap(); + else if (mode == "read-trap-threads") CheckReadTrapThreads(); + else if (mode == "read-trap-fault") CheckReadTrapFaults(); + else if (mode == "read-trap-late") CheckReadTrapLateFaults(); + else if (mode == "read-trap-fault-bound") CheckReadTrapFaultBound(); else throw std::runtime_error("unknown write watch test"); std::printf("write watch %s passed\n", argv[1]); return 0; diff --git a/core/libs/tests/edge_watch/CMakeLists.txt b/core/libs/tests/edge_watch/CMakeLists.txt new file mode 100644 index 0000000000..db48881dc6 --- /dev/null +++ b/core/libs/tests/edge_watch/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.20) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + project(EdgeWatchTests LANGUAGES CXX) + enable_testing() + set(ANYPS5_TEST_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/tests") +endif() + +find_package(Threads REQUIRED) +add_executable(edge_watch_tests ../EdgeWatch.cpp ../../prx/libkernel/Equeue/src/EdgeWatch.cpp) +target_include_directories(edge_watch_tests PRIVATE ../.. ../../prx/libc/include) +target_compile_features(edge_watch_tests PRIVATE cxx_std_20) +target_link_libraries(edge_watch_tests PRIVATE Threads::Threads) +set_target_properties(edge_watch_tests PROPERTIES + CXX_EXTENSIONS OFF + RUNTIME_OUTPUT_DIRECTORY "$<1:${ANYPS5_TEST_OUTPUT_DIRECTORY}>") + +foreach(mode socket pipe write lifecycle errors) + add_test(NAME edge_watch_${mode} COMMAND edge_watch_tests ${mode}) + set_tests_properties(edge_watch_${mode} PROPERTIES TIMEOUT 30) +endforeach() diff --git a/core/libs/tests/test_public_headers.py b/core/libs/tests/test_public_headers.py index 6e99eb6ec8..f2d50c635e 100644 --- a/core/libs/tests/test_public_headers.py +++ b/core/libs/tests/test_public_headers.py @@ -20,7 +20,7 @@ DATA_DECLARATION = re.compile( r"^[ \t]*extern\s+(?:const\s+)?[A-Za-z_][\w:<>]*(?:\s+(?:const|volatile))?\s+\**\s*(\w+)\s*(?:\[[^;\]\n]*\])?\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*=|[{}]") +TYPE_TOKEN = re.compile(r"\bnamespace\s*([\w:]*)\s*\{|\bextern\s*\{|\b(?:struct|union|class)\s+(\w+)\s*(?:final\s*)?(?::(?!:)[^;{]*)?([;{])|\busing\s+(\w+)\s*=|[{}]") @cache diff --git a/core/libs/tests/write_watch/CMakeLists.txt b/core/libs/tests/write_watch/CMakeLists.txt index d6ecbdeccd..baefa3b44e 100644 --- a/core/libs/tests/write_watch/CMakeLists.txt +++ b/core/libs/tests/write_watch/CMakeLists.txt @@ -28,7 +28,7 @@ set_target_properties(write_watch_tests PROPERTIES CXX_EXTENSIONS OFF RUNTIME_OUTPUT_DIRECTORY "$<1:${ANYPS5_TEST_OUTPUT_DIRECTORY}>") -foreach(mode collect fresh armed register mapped host empty protection overlaps threads raised fault-ahead) +foreach(mode collect fresh armed register mapped host empty protection overlaps threads raised fault-ahead read-trap read-trap-threads read-trap-fault read-trap-late read-trap-fault-bound) add_test(NAME write_watch_${mode} COMMAND write_watch_tests ${mode}) set_tests_properties(write_watch_${mode} PROPERTIES TIMEOUT 30) endforeach() diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 0baa46d6a8..67acbb953a 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -54,6 +54,9 @@ add_library(relinker_core STATIC ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/ScalarCompareOperands.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/MaskedStoreLowering.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/AndnLowering.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/BlsLowering.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/ShiftLowering.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/BitfieldLowering.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/ElfReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/RelrRelocations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp @@ -95,6 +98,11 @@ if(WIN32) endif() if(BUILD_TESTING) + add_test_executable(console_progress_tests ${CMAKE_CURRENT_SOURCE_DIR}/cli/tests/ConsoleProgressTests.cpp) + target_include_directories(console_progress_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli/include ${CMAKE_CURRENT_SOURCE_DIR}/domain/include) + add_test(NAME console_progress COMMAND console_progress_tests) + set_tests_properties(console_progress PROPERTIES TIMEOUT 20) + add_test_executable(guest_load_ranges_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/GuestLoadRangesTests.cpp) target_link_libraries(guest_load_ranges_tests PRIVATE relinker_core) add_test(NAME guest_load_ranges COMMAND guest_load_ranges_tests) @@ -103,8 +111,28 @@ if(BUILD_TESTING) add_test_executable(andn_lowering_tests ${CMAKE_CURRENT_SOURCE_DIR}/codegen/tests/AndnLoweringTests.cpp) target_link_libraries(andn_lowering_tests PRIVATE relinker_core) add_test(NAME andn_lowering COMMAND andn_lowering_tests) + + add_test_executable(import_module_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/ImportModuleTests.cpp) + target_link_libraries(import_module_tests PRIVATE relinker_core) + add_test(NAME import_module COMMAND import_module_tests) + set_tests_properties(andn_lowering PROPERTIES TIMEOUT 20) + add_test_executable(bls_lowering_tests ${CMAKE_CURRENT_SOURCE_DIR}/codegen/tests/BlsLoweringTests.cpp) + target_link_libraries(bls_lowering_tests PRIVATE relinker_core) + add_test(NAME bls_lowering COMMAND bls_lowering_tests) + set_tests_properties(bls_lowering PROPERTIES TIMEOUT 20) + + add_test_executable(shift_lowering_tests ${CMAKE_CURRENT_SOURCE_DIR}/codegen/tests/ShiftLoweringTests.cpp) + target_link_libraries(shift_lowering_tests PRIVATE relinker_core) + add_test(NAME shift_lowering COMMAND shift_lowering_tests) + set_tests_properties(shift_lowering PROPERTIES TIMEOUT 60) + + add_test_executable(bitfield_lowering_tests ${CMAKE_CURRENT_SOURCE_DIR}/codegen/tests/BitfieldLoweringTests.cpp) + target_link_libraries(bitfield_lowering_tests PRIVATE relinker_core) + add_test(NAME bitfield_lowering COMMAND bitfield_lowering_tests) + set_tests_properties(bitfield_lowering PROPERTIES TIMEOUT 60) + add_test_executable(masked_store_lowering_tests ${CMAKE_CURRENT_SOURCE_DIR}/codegen/tests/MaskedStoreLoweringTests.cpp) target_link_libraries(masked_store_lowering_tests PRIVATE relinker_core) add_test(NAME masked_store_lowering COMMAND masked_store_lowering_tests) @@ -256,6 +284,9 @@ if(WIN32) ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/ScalarCompareOperands.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/MaskedStoreLowering.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/AndnLowering.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/BlsLowering.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/ShiftLowering.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/BitfieldLowering.cpp ) target_include_directories(amd64_only_windows_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include ${CMAKE_CURRENT_SOURCE_DIR}/domain/include ${CMAKE_CURRENT_SOURCE_DIR}/io/include ${CMAKE_CURRENT_SOURCE_DIR}/codegen/include ${CMAKE_CURRENT_SOURCE_DIR}/../libs/prx/libc/src/specifics/windows) if(BUILD_TESTING) @@ -545,6 +576,9 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/ScalarCompareOperands.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/MaskedStoreLowering.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/AndnLowering.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/BlsLowering.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/ShiftLowering.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/BitfieldLowering.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxElfPatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/general/SegmentFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/general/EntryStubBuilder.cpp @@ -699,6 +733,10 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_entry_argv.py $) add_test(NAME linux_fixed_base COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_fixed_base.py $) + add_test(NAME linux_main_stack_entry COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_main_stack_entry.py + $ $) + add_asan_test_options(linux_main_stack_entry verify_asan_link_order=0) add_test(NAME linux_program_header_order COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_program_header_order.py $) add_test(NAME linux_input_program_headers COMMAND ${Python3_EXECUTABLE} @@ -807,6 +845,14 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_absolute_symbols.py $) add_test(NAME guest_module_identity COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_identity.py $) + add_test(NAME guest_symbolic_modules COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_symbolic_modules.py $) + add_test(NAME linux_guest_zero_address_exports COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_guest_zero_address_exports.py + $ $) + set_property(TEST linux_guest_zero_address_exports APPEND PROPERTY LABELS relinker) + set_tests_properties(linux_guest_zero_address_exports PROPERTIES TIMEOUT 30) + add_asan_test_options(linux_guest_zero_address_exports verify_asan_link_order=0) add_test(NAME guest_unused_import_dependencies COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_unused_import_dependencies.py $ $) @@ -909,6 +955,11 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_output_collisions.py $) add_test(NAME secondary_output_collisions COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_secondary_output_collisions.py $) + foreach(dryRunCase IN ITEMS formats modules patches failures) + add_test(NAME dry_run_${dryRunCase} COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_dry_run.py $ ${dryRunCase}) + set_tests_properties(dry_run_${dryRunCase} PROPERTIES TIMEOUT 20) + endforeach() add_test(NAME input_file COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_input_file.py $) add_test(NAME null_symbol_relocations COMMAND ${Python3_EXECUTABLE} @@ -925,6 +976,10 @@ if(BUILD_TESTING) add_test(NAME long_paths COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_long_paths.py $) set_tests_properties(long_paths PROPERTIES SKIP_RETURN_CODE 77) + if(WIN32) + add_test(NAME windows_extended_paths COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_extended_paths.py $) + endif() add_test(NAME string_table_bounds COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_string_table_bounds.py $) add_test(NAME sce_code_extent COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/cli/include/Cli.hpp b/core/relinker/cli/include/Cli.hpp index 71ee3f85d4..f078c6c90c 100644 --- a/core/relinker/cli/include/Cli.hpp +++ b/core/relinker/cli/include/Cli.hpp @@ -22,7 +22,9 @@ struct Args { bool toWindows = false; bool toMacos = false; bool lazyBinding = false; + bool omitUnavailableDependencies = false; bool autorun = false; + bool dryRun = false; bool windowsDiagnostics = false; bool windowsGui = false; std::optional windowsFixedBase; diff --git a/core/relinker/cli/include/ConsoleProgress.hpp b/core/relinker/cli/include/ConsoleProgress.hpp index a05bf67795..5136137850 100644 --- a/core/relinker/cli/include/ConsoleProgress.hpp +++ b/core/relinker/cli/include/ConsoleProgress.hpp @@ -18,7 +18,7 @@ public: void IncludeFile(std::string_view path) { const auto separator = path.find_last_of("/\\"); const auto name = path.substr(separator == std::string_view::npos ? 0 : separator + 1); - nameWidth = std::max(nameWidth, textWidth(name)); + nameWidth = std::max(nameWidth, textWidth(displayName(name))); } void Report(const Domain::ProgressEvent& event) override { @@ -36,7 +36,7 @@ public: default: throw std::logic_error("Unknown progress stage"); } const auto separator = event.Input.find_last_of("/\\"); - const auto name = event.Input.substr(separator == std::string::npos ? 0 : separator + 1); + const auto name = displayName(event.Input.substr(separator == std::string::npos ? 0 : separator + 1)); if (event.Stage == Domain::ProgressStage::Executable) { if (event.Completed == 0) Message("Converting " + name + "\n"); else Finish(); @@ -92,6 +92,19 @@ public: } private: + static std::string displayName(std::string_view name) { + constexpr char hex[] = "0123456789ABCDEF"; + std::string result; + for (const unsigned char character : name) { + if (character < 0x20 || character == 0x7f) { + result += "\\x"; + result += hex[character >> 4]; + result += hex[character & 15]; + } else result += static_cast(character); + } + return result; + } + static std::size_t textWidth(std::string_view text) { return static_cast(std::count_if(text.begin(), text.end(), [](unsigned char character) { return (character & 0xc0) != 0x80; })); } diff --git a/core/relinker/cli/src/Autorun.cpp b/core/relinker/cli/src/Autorun.cpp index 95f8214038..0647c38018 100644 --- a/core/relinker/cli/src/Autorun.cpp +++ b/core/relinker/cli/src/Autorun.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -71,8 +72,11 @@ int Autorun(const std::string& absPath, bool toWindows) { std::cout << "\nRaw exit code: " << rawCode << "; Unpacked: " << exitCode << '\n'; #endif - std::cout << "\nPress Enter to exit...\n"; - std::cin.ignore((std::numeric_limits::max)(), '\n'); + const char* noPause = std::getenv("ANYPS5_AUTORUN_NO_PAUSE"); + if (!noPause || std::string_view(noPause) != "1") { + std::cout << "\nPress Enter to exit...\n"; + std::cin.ignore((std::numeric_limits::max)(), '\n'); + } return exitCode; } diff --git a/core/relinker/cli/src/CliArgs.cpp b/core/relinker/cli/src/CliArgs.cpp index 8a6f7b2aef..15886225fa 100644 --- a/core/relinker/cli/src/CliArgs.cpp +++ b/core/relinker/cli/src/CliArgs.cpp @@ -25,8 +25,11 @@ const char* Usage() { " --to-rosetta Convert as --to-intel and lower what Rosetta lacks too. Implies --to-intel.\n" " --frame-red-zone Windows: give leaf functions a frame so an exception on a guest thread can unwind.\n" " --rpath System library search path. Default: $ORIGIN/libs.\n" + " --omit-unavailable-dependencies Linux: leave out a module dependency that no module provides, no import uses\n" + " and no run path directory holds at conversion time.\n" " --registry Write .registry.json beside the output.\n" " --autorun Run the output after conversion and print its exit code.\n" + " --dry-run Convert in memory and list planned outputs without writing them.\n" "\n" "Layout:\n" " --sce-module-path Parent of the module directories. Default: the input file's directory.\n" @@ -58,6 +61,7 @@ const char* Usage() { "Other:\n" " --help Print this help and exit with code 0.\n" " --version Print the build revision and exit with code 0.\n" + " -- Treat all remaining arguments as paths.\n" " Exit codes: 0 success, help or version, 1 invalid arguments or a patch mismatch, 2 conversion failed.\n" "Example: relinker input.elf output.elf"; } @@ -65,9 +69,10 @@ const char* Usage() { namespace { constexpr std::string_view kVerifyRejectedOptions[] = { + "--dry-run", "--windows", "--macos", "--windows-gui", "--windows-diagnostics", "--windows-fixed-base", "--trace-imports", "--linux-fixed-base", "--macos-fixed-base", "--to-intel", "--to-rosetta", "--frame-red-zone", "--skip-syscall-check", "--trap-sdk-syscall-stubs", "unused-filter=", - "--rpath", "--registry", "--lazy-binding", "--autorun", "--patches", "--no-patches", + "--rpath", "--registry", "--omit-unavailable-dependencies", "--lazy-binding", "--autorun", "--patches", "--no-patches", }; constexpr std::string_view kModuleDiscoveryOptions[] = {"--sce-module-path", "--module-dir", "--exclude-sce-module", "--skip-sce-module"}; @@ -96,10 +101,25 @@ Args ParseArgs(int argc, char* argv[]) { bool unusedFilterSpecified = false; bool runPathSpecified = false; bool sceModulePathSpecified = false; + bool optionsEnded = false; std::vector givenOptions; + const auto addPositional = [&](const std::string& path) { + if (path.empty()) throw std::runtime_error("input and output paths must be nonempty"); + if (args.inputPath.empty()) args.inputPath = path; + else if (args.outputPath.empty()) args.outputPath = path; + else throw std::runtime_error("unexpected argument: " + path); + }; for (int i = 1; i < argc; ++i) { const std::string arg = argv[i]; + if (optionsEnded) { + addPositional(arg); + continue; + } + if (arg == "--") { + optionsEnded = true; + continue; + } if (arg.rfind("--", 0) == 0) givenOptions.push_back(arg); else if (arg.rfind("unused-filter=", 0) == 0) givenOptions.push_back("unused-filter="); if (arg == "--help") { @@ -182,8 +202,12 @@ Args ParseArgs(int argc, char* argv[]) { args.toMacos = true; } else if (arg == "--lazy-binding") { args.lazyBinding = true; + } else if (arg == "--omit-unavailable-dependencies") { + args.omitUnavailableDependencies = true; } else if (arg == "--autorun") { args.autorun = true; + } else if (arg == "--dry-run") { + args.dryRun = true; } else if (arg == "--windows-diagnostics") { args.windowsDiagnostics = true; } else if (arg == "--windows-gui") { @@ -192,18 +216,13 @@ Args ParseArgs(int argc, char* argv[]) { args.traceImports = true; } else if (arg.rfind("--", 0) == 0 || arg == "unused-filter") { throw std::runtime_error("unknown option: " + arg); - } else if (arg.empty()) { - throw std::runtime_error("input and output paths must be nonempty"); - } else if (args.inputPath.empty()) { - args.inputPath = arg; - } else if (args.outputPath.empty()) { - args.outputPath = arg; } else { - throw std::runtime_error("unexpected argument: " + arg); + addPositional(arg); } } const bool verifying = !args.verifyPatchesPath.empty(); + if (args.dryRun && args.autorun) throw std::runtime_error("--dry-run conflicts with --autorun"); if (args.verifyOutput && !verifying) throw std::runtime_error("--verify-output requires --verify-patches"); if (!args.patchesPath.empty() && args.noPatches) throw std::runtime_error("--patches conflicts with --no-patches"); if (verifying) { diff --git a/core/relinker/cli/tests/AutorunExitTests.cpp b/core/relinker/cli/tests/AutorunExitTests.cpp index 798fb129db..9bf9f3b6c6 100644 --- a/core/relinker/cli/tests/AutorunExitTests.cpp +++ b/core/relinker/cli/tests/AutorunExitTests.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #include #include #ifndef _WIN32 @@ -19,6 +21,26 @@ namespace { +void SetNoPause(const char* value) { +#ifdef _WIN32 + if (_putenv_s("ANYPS5_AUTORUN_NO_PAUSE", value ? value : "") != 0) throw std::runtime_error("Cannot set autorun environment"); +#else + if ((value ? setenv("ANYPS5_AUTORUN_NO_PAUSE", value, 1) : unsetenv("ANYPS5_AUTORUN_NO_PAUSE")) != 0) + throw std::runtime_error("Cannot set autorun environment"); +#endif +} + +class EnvironmentGuard { +public: + EnvironmentGuard() { + if (const auto* value = std::getenv("ANYPS5_AUTORUN_NO_PAUSE")) saved = value; + SetNoPause(nullptr); + } + ~EnvironmentGuard() { SetNoPause(saved ? saved->c_str() : nullptr); } +private: + std::optional saved; +}; + std::filesystem::path ExecutablePath(const char* argument) { #ifdef _WIN32 static_cast(argument); @@ -55,6 +77,7 @@ int main(int, char* argv[]) { #endif }; #endif + const EnvironmentGuard environment; std::istringstream input("\n\n\n"); auto* savedInput = std::cin.rdbuf(input.rdbuf()); int result = 0; @@ -66,6 +89,28 @@ int main(int, char* argv[]) { std::filesystem::create_symlink(executable, child); const auto changed = changeTime(executable); #endif + for (const char* value : {"1", "0", "yes", "", "10", "1 "}) { + SetNoPause(value); + std::istringstream pending("\nremaining"); + auto* originalInput = std::cin.rdbuf(pending.rdbuf()); + std::ostringstream output; + auto* originalOutput = std::cout.rdbuf(output.rdbuf()); + int code; + try { + code = Cli::Autorun(child.string(), true); + } catch (...) { + std::cin.rdbuf(originalInput); + std::cout.rdbuf(originalOutput); + throw; + } + std::cin.rdbuf(originalInput); + std::cout.rdbuf(originalOutput); + const bool skipped = std::string(value) == "1"; + if (code != 42 || (pending.tellg() == 0) != skipped || + (output.str().find("Press Enter") == std::string::npos) != skipped) + throw std::runtime_error("Autorun pause option changed the exit code, input position or prompt"); + } + SetNoPause(nullptr); std::vector suffixes{""}; #ifndef _WIN32 suffixes.insert(suffixes.end(), {" $HOME", " $(printf substituted)", " `printf substituted`", diff --git a/core/relinker/cli/tests/CliArgsTests.cpp b/core/relinker/cli/tests/CliArgsTests.cpp index b7e0a7d218..a998c86fd0 100644 --- a/core/relinker/cli/tests/CliArgsTests.cpp +++ b/core/relinker/cli/tests/CliArgsTests.cpp @@ -52,7 +52,7 @@ void defaults() { const auto args = parse({"input.elf", "output.elf"}); require(args.inputPath == "input.elf", "input path was not captured"); require(args.outputPath == "output.elf", "output path was not captured"); - require(!args.skipSyscallCheck && !args.trapSdkSyscallStubs && !args.skipSceModule && !args.toIntel && !args.writeRegistry && + require(!args.dryRun && !args.skipSyscallCheck && !args.trapSdkSyscallStubs && !args.skipSceModule && !args.toIntel && !args.writeRegistry && !args.toWindows && !args.lazyBinding && !args.autorun && !args.windowsDiagnostics && !args.windowsGui, "a flag defaulted to true"); require(args.unusedFilterLevel == 0u, "unused filter level did not default to 0"); @@ -94,11 +94,35 @@ void singleDashIsPositional() { require(args.inputPath == "-x" && args.outputPath == "-o", "a single-dash argument was not treated as a positional"); } +void optionTerminator() { + const auto paths = parse({"--", "--help", "--version"}); + require(paths.inputPath == "--help" && paths.outputPath == "--version" && !paths.showHelp && !paths.showVersion, + "options after the terminator were not treated as paths"); + const auto target = parse({"--windows", "--", "unused-filter=2", "--registry"}); + require(target.toWindows && target.inputPath == "unused-filter=2" && target.outputPath == "--registry" && + target.unusedFilterLevel == 0 && !target.writeRegistry, "the terminator changed preceding options or parsed a following one"); + const auto output = parse({"input.elf", "--", "--"}); + require(output.inputPath == "input.elf" && output.outputPath == "--", "a second terminator was not a literal path"); + const auto value = parse({"--rpath", "--", "input.elf", "output.elf"}); + require(value.runPath == "--" && value.outputPath == "output.elf", "an option value ended option parsing"); + const auto verify = parse({"--verify-patches", "p.toml", "--", "--windows"}); + require(verify.inputPath == "--windows" && !verify.toWindows, "a verification path was recorded as an option"); + expectUsage({"--"}); + expectUsage({"--", "input.elf"}); + expectThrows({"--", "", "output.elf"}, "input and output paths must be nonempty"); + expectThrows({"--", "input.elf", "output.elf", "--help"}, "unexpected argument: --help"); +} + void rpath() { const auto args = parse({"--rpath", "/custom/libs", "input.elf", "output.elf"}); require(args.runPath == "/custom/libs", "--rpath value was not captured"); } +void omitUnavailableDependencies() { + require(!parse({"input.elf", "output.elf"}).omitUnavailableDependencies, "dependencies were left out by default"); + require(parse({"--omit-unavailable-dependencies", "input.elf", "output.elf"}).omitUnavailableDependencies, "--omit-unavailable-dependencies was ignored"); +} + void rpathConsumesNextTokenVerbatim() { const auto args = parse({"--rpath", "--windows", "input.elf", "output.elf"}); require(args.runPath == "--windows", "--rpath did not consume the following token as its value"); @@ -172,7 +196,6 @@ void unusedFilterBareWordIsUnknown() { void unknownOption() { expectThrows({"--nonexistent", "input.elf", "output.elf"}, "unknown option: --nonexistent"); - expectThrows({"--", "input.elf", "output.elf"}, "unknown option: --"); } void emptyPositionals() { @@ -353,14 +376,31 @@ void verifyMode() { expectUsage({"--verify-patches", "p.toml"}); } +void dryRun() { + for (const auto* target : {"--windows", "--macos"}) { + const auto args = parse({"input.elf", "--dry-run", target, "--registry", "output"}); + require(args.dryRun && args.writeRegistry, "dry run must preserve conversion options"); + require(args.inputPath == "input.elf" && args.outputPath == "output", "dry run consumed a positional path"); + } + require(parse({"--dry-run", "input.elf", "output"}).dryRun, "Linux dry run was ignored"); + require(parse({"--dry-run", "--dry-run", "input.elf", "output"}).dryRun, "repeated dry run was rejected"); + expectThrows({"--dry-run", "--autorun", "input.elf", "output"}, "--dry-run conflicts with --autorun"); + expectThrows({"--autorun", "--dry-run", "input.elf", "output"}, "--dry-run conflicts with --autorun"); + expectThrows({"--verify-patches", "p.toml", "--dry-run", "input.elf"}, "--dry-run cannot be used with --verify-patches"); + expectUsage({"--dry-run", "input.elf"}); +} + int main() { try { defaults(); + dryRun(); booleanFlags(); trapSdkSyscallStubs(); positionalsInterleaveWithFlags(); singleDashIsPositional(); + optionTerminator(); rpath(); + omitUnavailableDependencies(); rpathConsumesNextTokenVerbatim(); rpathWithoutValue(); excludeSceModule(); diff --git a/core/relinker/cli/tests/ConsoleProgressTests.cpp b/core/relinker/cli/tests/ConsoleProgressTests.cpp new file mode 100644 index 0000000000..70ecd907b6 --- /dev/null +++ b/core/relinker/cli/tests/ConsoleProgressTests.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +int main() { + try { + std::ostringstream stream; + Cli::ConsoleProgress progress(stream); + const std::string name = std::string("title") + '\x1b' + "[2J" + '\n' + '\r' + '\t' + '\x7f' + ".prx"; + progress.IncludeFile(name); + progress.IncludeFile("中文.prx"); + progress.Report({Domain::ProgressStage::Executable, "folder/" + name, 0, 1}); + const auto initial = stream.str(); + if (initial != "Converting title\\x1B[2J\\x0A\\x0D\\x09\\x7F.prx\n") + throw std::runtime_error("Executable name was not escaped literally"); + stream.str(""); + progress.Report({Domain::ProgressStage::Modules, name, 1, 1}); + progress.Report({Domain::ProgressStage::Modules, "中文.prx", 1, 1}); + progress.Finish(); + const auto output = stream.str(); + if (output.find('\x1b') != std::string::npos || output.find('\t') != std::string::npos || output.find('\x7f') != std::string::npos) + throw std::runtime_error("Progress output contains filename controls"); + if (output.find("title\\x1B[2J\\x0A\\x0D\\x09\\x7F.prx | 100% (1/1)") == std::string::npos) + throw std::runtime_error("Escaped name or completion was lost"); + if (output.find("中文.prx" + std::string(26, ' ') + " | 100% (1/1)") == std::string::npos) + throw std::runtime_error("Escaped width or UTF-8 name was lost"); + for (unsigned byte = 0; byte < 128; ++byte) { + if (byte == '/' || byte == '\\') continue; + std::ostringstream single; + Cli::ConsoleProgress item(single); + item.Report({Domain::ProgressStage::Executable, std::string(1, static_cast(byte)), 0, 1}); + if ((byte < 32 || byte == 127) && single.str().find("\\x") == std::string::npos) + throw std::runtime_error("ASCII control was not escaped"); + } + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } + return 0; +} diff --git a/core/relinker/cli/tests/test_help.py b/core/relinker/cli/tests/test_help.py index 4c8386180a..e97ab03a36 100644 --- a/core/relinker/cli/tests/test_help.py +++ b/core/relinker/cli/tests/test_help.py @@ -45,6 +45,11 @@ def main(): assert result.returncode == 1 and "unknown option" in result.stderr result = run("--help", "--windows-gui") assert result.returncode == 1 and "requires --windows" in result.stderr + for name in ("--help", "--version", "--windows", "unused-filter=2", "--"): + result = run("--skip-sce-module", "--", name, str(output)) + assert result.returncode == 2 and "Cannot open file" in result.stderr, result.stderr + assert result.stdout == "", result.stdout + assert output.read_bytes() == b"preserve this output" for flag in ["--rpath", "--exclude-sce-module"]: result = run(flag, "--help", str(source), str(output)) assert result.returncode == 2 and "Cannot open file" in result.stderr diff --git a/core/relinker/codegen/include/codegen/x86/BitfieldLowering.hpp b/core/relinker/codegen/include/codegen/x86/BitfieldLowering.hpp new file mode 100644 index 0000000000..6765d45a9c --- /dev/null +++ b/core/relinker/codegen/include/codegen/x86/BitfieldLowering.hpp @@ -0,0 +1,29 @@ +#ifndef CODEGEN_X86_BITFIELDLOWERING_HPP +#define CODEGEN_X86_BITFIELDLOWERING_HPP + +#include +#include + +namespace Codegen { + +enum class BitfieldOperation : std::uint8_t { + ZeroHigh, + Extract, +}; + +struct BitfieldOperands { + BitfieldOperation Operation; + std::uint8_t Destination; + std::uint8_t Source; + std::uint8_t Control; + bool Wide; + std::optional Memory; +}; + +[[nodiscard]] const char* BitfieldName(BitfieldOperation operation); +[[nodiscard]] std::optional DecodeBitfield(const std::uint8_t* data, std::size_t length); +void EmitBitfield(StubBodyBuilder& body, const BitfieldOperands& operands); + +} + +#endif diff --git a/core/relinker/codegen/include/codegen/x86/BlsLowering.hpp b/core/relinker/codegen/include/codegen/x86/BlsLowering.hpp new file mode 100644 index 0000000000..6d74afe9f5 --- /dev/null +++ b/core/relinker/codegen/include/codegen/x86/BlsLowering.hpp @@ -0,0 +1,29 @@ +#ifndef CODEGEN_X86_BLSLOWERING_HPP +#define CODEGEN_X86_BLSLOWERING_HPP + +#include +#include + +namespace Codegen { + +enum class BlsOperation : std::uint8_t { + Reset = 1, + Mask = 2, + Isolate = 3, +}; + +struct BlsOperands { + BlsOperation Operation; + std::uint8_t Destination; + std::uint8_t Source; + bool Wide; + std::optional Memory; +}; + +[[nodiscard]] const char* BlsName(BlsOperation operation); +[[nodiscard]] std::optional DecodeBls(const std::uint8_t* data, std::size_t length); +void EmitBls(StubBodyBuilder& body, const BlsOperands& operands); + +} + +#endif diff --git a/core/relinker/codegen/include/codegen/x86/ScalarLowering.hpp b/core/relinker/codegen/include/codegen/x86/ScalarLowering.hpp new file mode 100644 index 0000000000..f510a32ad8 --- /dev/null +++ b/core/relinker/codegen/include/codegen/x86/ScalarLowering.hpp @@ -0,0 +1,102 @@ +#ifndef CODEGEN_X86_SCALARLOWERING_HPP +#define CODEGEN_X86_SCALARLOWERING_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Codegen::ScalarLowering { + +inline constexpr std::int32_t FrameSize = 144; +inline constexpr std::uint8_t Stack = 4; + +inline bool LegacyPrefix(std::uint8_t value) { + return value == 0x2E || value == 0x36 || value == 0x3E || value == 0x26 || value == 0x64 || value == 0x65 || value == 0x67; +} + +inline 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)); +} + +inline std::uint8_t Rex(bool wide, std::uint8_t reg, std::uint8_t rm) { + return static_cast(0x40 | (wide ? 8 : 0) | ((reg & 8) >> 1) | ((rm & 8) >> 3)); +} + +inline std::vector RegisterBytes(std::uint8_t opcode, bool wide, std::uint8_t reg, std::uint8_t rm) { + return {Rex(wide, reg, rm), opcode, static_cast(0xC0 | ((reg & 7) << 3) | (rm & 7))}; +} + +inline std::vector StackBytes(std::uint8_t opcode, bool wide, std::uint8_t reg, std::int32_t displacement) { + std::vector bytes{Rex(wide, reg, Stack), opcode, static_cast(0x84 | ((reg & 7) << 3)), 0x24}; + Append32(bytes, displacement); + return bytes; +} + +inline void Register(StubBodyBuilder& body, std::uint8_t opcode, bool wide, std::uint8_t reg, std::uint8_t rm) { + body.Raw(RegisterBytes(opcode, wide, reg, rm)); +} + +inline void StackSlot(StubBodyBuilder& body, std::uint8_t opcode, bool wide, std::uint8_t reg, std::int32_t displacement) { + body.Raw(StackBytes(opcode, wide, reg, displacement)); +} + +inline std::array UsedRegisters(std::initializer_list registers, const std::optional& memory) { + std::array used{}; + used[Stack] = true; + for (const auto reg : registers) { + if (reg >= used.size()) throw CodegenException("register index " + std::to_string(reg) + " is out of range"); + used[reg] = true; + } + if (!memory) return used; + if (memory->Rm == 4) { + const auto base = static_cast((memory->Sib & 7) | ((memory->RexIndexBase & 1) << 3)); + const auto index = static_cast(((memory->Sib >> 3) & 7) | ((memory->RexIndexBase & 2) << 2)); + if (memory->Mod != 0 || (memory->Sib & 7) != 5) used[base] = true; + if (index != Stack) used[index] = true; + } else if (!memory->RipRelative) { + used[memory->Rm | ((memory->RexIndexBase & 1) << 3)] = true; + } + return used; +} + +inline std::uint8_t Scratch(std::array& used, const char* instruction) { + for (const auto reg : {11, 10, 9, 8, 0, 1, 2, 3, 5, 6, 7, 12, 13, 14, 15}) { + if (used[reg]) continue; + used[reg] = true; + return static_cast(reg); + } + throw CodegenException(std::string(instruction) + " has no scratch register"); +} + +inline void Memory(StubBodyBuilder& body, std::uint8_t opcode, bool wide, const MemoryOperand& memory, std::uint8_t reg, const char* instruction, std::int32_t frame = FrameSize) { + auto displacement = static_cast(memory.Displacement); + if (memory.StackBase) displacement += frame; + const bool address32 = std::find(memory.Prefixes.begin(), memory.Prefixes.end(), 0x67) != memory.Prefixes.end(); + if (!address32 && displacement > std::numeric_limits::max()) + throw CodegenException(std::string(instruction) + " stack displacement does not fit after spilling"); + const auto mod = memory.StackBase ? 2 : memory.Mod; + auto bytes = memory.Prefixes; + bytes.push_back(static_cast(Rex(wide, reg, 0) | memory.RexIndexBase)); + bytes.push_back(opcode); + bytes.push_back(static_cast((mod << 6) | ((reg & 7) << 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); +} + +} + +#endif diff --git a/core/relinker/codegen/include/codegen/x86/ShiftLowering.hpp b/core/relinker/codegen/include/codegen/x86/ShiftLowering.hpp new file mode 100644 index 0000000000..abc086f8e1 --- /dev/null +++ b/core/relinker/codegen/include/codegen/x86/ShiftLowering.hpp @@ -0,0 +1,31 @@ +#ifndef CODEGEN_X86_SHIFTLOWERING_HPP +#define CODEGEN_X86_SHIFTLOWERING_HPP + +#include +#include + +namespace Codegen { + +enum class ShiftOperation : std::uint8_t { + Left, + LogicalRight, + ArithmeticRight, + RotateRight, +}; + +struct ShiftOperands { + ShiftOperation Operation; + std::uint8_t Destination; + std::uint8_t Source; + std::uint8_t Count; + bool Wide; + std::optional Memory; +}; + +[[nodiscard]] const char* ShiftName(ShiftOperation operation); +[[nodiscard]] std::optional DecodeShift(const std::uint8_t* data, std::size_t length); +void EmitShift(StubBodyBuilder& body, const ShiftOperands& operands); + +} + +#endif diff --git a/core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp b/core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp index f957478801..4749d20165 100644 --- a/core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp +++ b/core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -256,6 +259,12 @@ std::optional Amd64OnlyInstructionMatcher::_matchRosetta(const D return std::nullopt; if (const auto andn = DecodeAndn(instr.Data, instr.Length)) return _trampoline("ANDN", instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { EmitAndn(body, *andn); })); + if (const auto bls = DecodeBls(instr.Data, instr.Length)) + return _trampoline(BlsName(bls->Operation), instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { EmitBls(body, *bls); })); + if (const auto shift = DecodeShift(instr.Data, instr.Length)) + return _trampoline(ShiftName(shift->Operation), instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { EmitShift(body, *shift); })); + if (const auto bitfield = DecodeBitfield(instr.Data, instr.Length)) + return _trampoline(BitfieldName(bitfield->Operation), instr.Length, _outOfLine(instr.Length, trailing, [&](StubBodyBuilder& body) { EmitBitfield(body, *bitfield); })); if (instr.IsRdseed()) { std::vector replacement(instr.Data, instr.Data + instr.Length); replacement[instr.OpcodeOffset() + 2] &= static_cast(~(1u << X64OpcodeConstants::ModRmRegShift)); @@ -327,6 +336,18 @@ const char* Amd64OnlyInstructionMatcher::_emitOutOfLine(StubBodyBuilder& body, c EmitAndn(body, *andn); return "ANDN"; } + if (const auto bls = _target == Amd64OnlyTarget::Rosetta ? DecodeBls(instr.Data, instr.Length) : std::nullopt) { + EmitBls(body, *bls); + return BlsName(bls->Operation); + } + if (const auto shift = _target == Amd64OnlyTarget::Rosetta ? DecodeShift(instr.Data, instr.Length) : std::nullopt) { + EmitShift(body, *shift); + return ShiftName(shift->Operation); + } + if (const auto bitfield = _target == Amd64OnlyTarget::Rosetta ? DecodeBitfield(instr.Data, instr.Length) : std::nullopt) { + EmitBitfield(body, *bitfield); + return BitfieldName(bitfield->Operation); + } if (_target == Amd64OnlyTarget::Rosetta && instr.IsRdtscp()) { body.Raw(_rdtscpReplacement(instr)); return kRdtscp.Name; diff --git a/core/relinker/codegen/src/x86/AndnLowering.cpp b/core/relinker/codegen/src/x86/AndnLowering.cpp index a99b92e4f6..82213eab01 100644 --- a/core/relinker/codegen/src/x86/AndnLowering.cpp +++ b/core/relinker/codegen/src/x86/AndnLowering.cpp @@ -1,89 +1,13 @@ #include -#include -#include -#include -#include +#include namespace Codegen { -namespace { - -constexpr std::int32_t kFrameSize = 144; -constexpr std::uint8_t kStack = 4; - -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)); -} - -std::uint8_t _rex(bool wide, std::uint8_t reg, std::uint8_t rm) { - return static_cast(0x40 | (wide ? 8 : 0) | ((reg & 8) >> 1) | ((rm & 8) >> 3)); -} - -void _register(StubBodyBuilder& body, std::uint8_t opcode, bool wide, std::uint8_t reg, std::uint8_t rm) { - const std::array bytes{_rex(wide, reg, rm), opcode, static_cast(0xC0 | ((reg & 7) << 3) | (rm & 7))}; - body.Raw(bytes); -} - -void _stack(StubBodyBuilder& body, std::uint8_t opcode, bool wide, std::uint8_t reg, std::int32_t displacement) { - std::vector bytes{_rex(wide, reg, kStack), opcode, static_cast(0x84 | ((reg & 7) << 3)), 0x24}; - _append32(bytes, displacement); - body.Raw(bytes); -} - -std::uint8_t _scratch(const AndnOperands& operands) { - std::array used{}; - used[kStack] = used[operands.Destination] = used[operands.InvertedSource] = true; - if (!operands.Memory) { - used[operands.Source] = true; - } else { - const auto& memory = *operands.Memory; - if (memory.Rm == 4) { - const auto base = static_cast((memory.Sib & 7) | ((memory.RexIndexBase & 1) << 3)); - const auto index = static_cast(((memory.Sib >> 3) & 7) | ((memory.RexIndexBase & 2) << 2)); - if (memory.Mod != 0 || (memory.Sib & 7) != 5) used[base] = true; - if (index != kStack) used[index] = true; - } else if (!memory.RipRelative) { - used[memory.Rm | ((memory.RexIndexBase & 1) << 3)] = true; - } - } - for (const auto reg : {11, 10, 9, 8, 0, 1, 2, 3, 5, 6, 7, 12, 13, 14, 15}) - if (!used[reg]) return static_cast(reg); - throw CodegenException("ANDN has no scratch register"); -} - -void _memory(StubBodyBuilder& body, const AndnOperands& operands, std::uint8_t scratch) { - const auto& memory = *operands.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("ANDN stack displacement does not fit after spilling"); - const auto mod = memory.StackBase ? 2 : memory.Mod; - auto bytes = memory.Prefixes; - bytes.push_back(static_cast(_rex(operands.Wide, scratch, 0) | memory.RexIndexBase)); - bytes.push_back(0x23); - bytes.push_back(static_cast((mod << 6) | ((scratch & 7) << 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); -} - -} +using namespace ScalarLowering; std::optional DecodeAndn(const std::uint8_t* data, const std::size_t length) { std::size_t vex = 0; - while (vex < length && _prefix(data[vex])) ++vex; + while (vex < length && LegacyPrefix(data[vex])) ++vex; if (vex + 5 > length || data[vex] != 0xC4 || (data[vex + 1] & 0x1F) != 2 || data[vex + 3] != 0xF2) return std::nullopt; if ((data[vex + 2] & 7) != 0) @@ -107,32 +31,34 @@ std::optional DecodeAndn(const std::uint8_t* data, const std::size } void EmitAndn(StubBodyBuilder& body, const AndnOperands& operands) { - const auto scratch = _scratch(operands); - _stack(body, 0x8D, true, kStack, -kFrameSize); - _stack(body, 0x89, true, scratch, 0); - if (!operands.Memory && operands.Source == kStack) { - _stack(body, 0x8D, true, scratch, kFrameSize); - _stack(body, 0x89, true, scratch, 8); + auto used = operands.Memory ? UsedRegisters({operands.Destination, operands.InvertedSource}, operands.Memory) + : UsedRegisters({operands.Destination, operands.InvertedSource, operands.Source}, std::nullopt); + const auto scratch = Scratch(used, "ANDN"); + StackSlot(body, 0x8D, true, Stack, -FrameSize); + StackSlot(body, 0x89, true, scratch, 0); + if (!operands.Memory && operands.Source == Stack) { + StackSlot(body, 0x8D, true, scratch, FrameSize); + StackSlot(body, 0x89, true, scratch, 8); } - if (operands.InvertedSource == kStack) - _stack(body, 0x8D, true, scratch, kFrameSize); + if (operands.InvertedSource == Stack) + StackSlot(body, 0x8D, true, scratch, FrameSize); else - _register(body, 0x8B, operands.Wide, scratch, operands.InvertedSource); - _register(body, 0xF7, operands.Wide, 2, scratch); + Register(body, 0x8B, operands.Wide, scratch, operands.InvertedSource); + Register(body, 0xF7, operands.Wide, 2, scratch); if (operands.Memory) - _memory(body, operands, scratch); - else if (operands.Source == kStack) - _stack(body, 0x23, operands.Wide, scratch, 8); + Memory(body, 0x23, operands.Wide, *operands.Memory, scratch, "ANDN"); + else if (operands.Source == Stack) + StackSlot(body, 0x23, operands.Wide, scratch, 8); else - _register(body, 0x23, operands.Wide, scratch, operands.Source); - if (operands.Destination == kStack) - _stack(body, 0x89, operands.Wide, scratch, 8); + Register(body, 0x23, operands.Wide, scratch, operands.Source); + if (operands.Destination == Stack) + StackSlot(body, 0x89, operands.Wide, scratch, 8); else - _register(body, 0x8B, operands.Wide, operands.Destination, scratch); - _stack(body, 0x8B, true, scratch, 0); - _stack(body, 0x8D, true, kStack, kFrameSize); - if (operands.Destination == kStack) - _stack(body, 0x8B, operands.Wide, kStack, 8 - kFrameSize); + Register(body, 0x8B, operands.Wide, operands.Destination, scratch); + StackSlot(body, 0x8B, true, scratch, 0); + StackSlot(body, 0x8D, true, Stack, FrameSize); + if (operands.Destination == Stack) + StackSlot(body, 0x8B, operands.Wide, Stack, 8 - FrameSize); } } diff --git a/core/relinker/codegen/src/x86/BitfieldLowering.cpp b/core/relinker/codegen/src/x86/BitfieldLowering.cpp new file mode 100644 index 0000000000..bc9cf32679 --- /dev/null +++ b/core/relinker/codegen/src/x86/BitfieldLowering.cpp @@ -0,0 +1,159 @@ +#include +#include + +namespace Codegen { + +using namespace ScalarLowering; + +namespace { + +constexpr std::uint8_t kControlRegister = 1; +constexpr std::int32_t kResultSlot = 0; +constexpr std::int32_t kControlSlot = 8; +constexpr std::int32_t kMaskSlot = 16; +constexpr std::int32_t kStackResultSlot = 24; +constexpr std::int32_t kFrame = FrameSize + 16; +constexpr std::uint8_t kJumpBelow = 0x72; +constexpr std::uint8_t kJumpAboveOrEqual = 0x73; +constexpr std::uint8_t kJump = 0xEB; +constexpr std::uint8_t kSetCarry = 0xF9; + +std::uint8_t _bits(const BitfieldOperands& operands) { + return operands.Wide ? 64 : 32; +} + +std::vector _compareControlByte(const std::uint8_t rm, const std::uint8_t bits) { + return {0x80, static_cast(0xF8 | rm), bits}; +} + +std::vector _lowMask(const BitfieldOperands& operands, const std::uint8_t mask) { + auto bytes = RegisterBytes(0xD3, operands.Wide, 4, mask); + const auto invert = RegisterBytes(0xF7, operands.Wide, 2, mask); + bytes.insert(bytes.end(), invert.begin(), invert.end()); + return bytes; +} + +void _append(std::vector& bytes, const std::vector& more) { + bytes.insert(bytes.end(), more.begin(), more.end()); +} + +void _loadSource(StubBodyBuilder& body, const BitfieldOperands& operands, const std::uint8_t result, const char* name) { + if (operands.Memory) + Memory(body, 0x8B, operands.Wide, *operands.Memory, result, name, kFrame); + else if (operands.Source == Stack) + StackSlot(body, 0x8D, true, result, kFrame); + else + Register(body, 0x8B, operands.Wide, result, operands.Source); +} + +void _zeroHigh(StubBodyBuilder& body, const BitfieldOperands& operands, const std::uint8_t result, const std::uint8_t mask) { + auto high = operands.Wide ? std::vector{} : RegisterBytes(0x8B, false, result, result); + _append(high, RegisterBytes(0x85, operands.Wide, result, result)); + high.push_back(kSetCarry); + auto low = _lowMask(operands, mask); + _append(low, RegisterBytes(0x21, operands.Wide, mask, result)); + body.Raw(_compareControlByte(kControlRegister, _bits(operands))); + body.Raw(std::array{kJumpBelow, static_cast(high.size() + 2)}); + body.Raw(high); + body.Raw(std::array{kJump, static_cast(low.size())}); + body.Raw(low); +} + +void _extract(StubBodyBuilder& body, const BitfieldOperands& operands, const std::uint8_t result, const std::uint8_t mask) { + const auto bits = _bits(operands); + std::vector length{0x88, 0xE9}; + _append(length, _lowMask(operands, mask)); + const auto zero = RegisterBytes(0x31, operands.Wide, result, result); + auto shifted = RegisterBytes(0xD3, operands.Wide, 5, result); + _append(shifted, _compareControlByte(5, bits)); + shifted.push_back(kJumpAboveOrEqual); + shifted.push_back(static_cast(length.size() + 2 + zero.size())); + _append(shifted, length); + shifted.push_back(kJump); + shifted.push_back(static_cast(zero.size())); + body.Raw(_compareControlByte(kControlRegister, bits)); + body.Raw(std::array{kJumpAboveOrEqual, static_cast(shifted.size())}); + body.Raw(shifted); + body.Raw(zero); + Register(body, 0x21, operands.Wide, mask, result); +} + +} + +const char* BitfieldName(const BitfieldOperation operation) { + switch (operation) { + case BitfieldOperation::ZeroHigh: return "BZHI"; + case BitfieldOperation::Extract: return "BEXTR"; + } + throw CodegenException("unknown bitfield operation"); +} + +std::optional DecodeBitfield(const std::uint8_t* data, const std::size_t length) { + std::size_t vex = 0; + while (vex < length && LegacyPrefix(data[vex])) ++vex; + if (vex + 5 > length || data[vex] != 0xC4 || (data[vex + 1] & 0x1F) != 2 || (data[vex + 2] & 3) != 0) return std::nullopt; + BitfieldOperation operation; + if (data[vex + 3] == 0xF5) + operation = BitfieldOperation::ZeroHigh; + else if (data[vex + 3] == 0xF7) + operation = BitfieldOperation::Extract; + else + return std::nullopt; + const auto name = BitfieldName(operation); + if ((data[vex + 2] & 4) != 0) throw CodegenException(std::string(name) + " requires VEX.L=0"); + const auto extensions = static_cast(~data[vex + 1]); + const auto modrm = data[vex + 4]; + BitfieldOperands operands{ + operation, + static_cast(((modrm >> 3) & 7) | ((extensions & 0x80) >> 4)), + static_cast((modrm & 7) | ((extensions & 0x20) >> 2)), + static_cast((~data[vex + 2] >> 3) & 15), + (data[vex + 2] & 0x80) != 0, std::nullopt}; + std::size_t end = vex + 5; + if ((modrm >> 6) != 3) { + const auto rex = static_cast(((extensions & 0x20) >> 5) | ((extensions & 0x40) >> 5)); + operands.Memory = DecodeMemoryOperand(data, length, vex + 4, rex, {data, data + vex}); + end = vex + 4 + operands.Memory->EncodedSize; + } + if (end != length) throw CodegenException(std::string(name) + " has trailing bytes"); + return operands; +} + +void EmitBitfield(StubBodyBuilder& body, const BitfieldOperands& operands) { + const auto name = BitfieldName(operands.Operation); + auto used = operands.Memory ? UsedRegisters({operands.Destination, operands.Control, kControlRegister}, operands.Memory) + : UsedRegisters({operands.Destination, operands.Source, operands.Control, kControlRegister}, std::nullopt); + const auto result = Scratch(used, name); + const auto mask = Scratch(used, name); + StackSlot(body, 0x8D, true, Stack, -kFrame); + StackSlot(body, 0x89, true, result, kResultSlot); + StackSlot(body, 0x89, true, kControlRegister, kControlSlot); + StackSlot(body, 0x89, true, mask, kMaskSlot); + _loadSource(body, operands, result, name); + if (operands.Control == Stack) + StackSlot(body, 0x8D, true, kControlRegister, kFrame); + else if (operands.Control != kControlRegister) + Register(body, 0x8B, true, kControlRegister, operands.Control); + auto allOnes = RegisterBytes(0xC7, operands.Wide, 0, mask); + Append32(allOnes, -1); + body.Raw(allOnes); + if (operands.Operation == BitfieldOperation::ZeroHigh) + _zeroHigh(body, operands, result, mask); + else + _extract(body, operands, result, mask); + if (operands.Destination == Stack) + StackSlot(body, 0x89, operands.Wide, result, kStackResultSlot); + else if (operands.Destination == kControlRegister) + StackSlot(body, 0x89, true, result, kControlSlot); + else + Register(body, 0x8B, operands.Wide, operands.Destination, result); + StackSlot(body, 0x8B, true, mask, kMaskSlot); + StackSlot(body, 0x8B, true, kControlRegister, kControlSlot); + StackSlot(body, 0x8B, true, result, kResultSlot); + if (operands.Destination == Stack) + StackSlot(body, 0x8B, operands.Wide, Stack, kStackResultSlot); + else + StackSlot(body, 0x8D, true, Stack, kFrame); +} + +} diff --git a/core/relinker/codegen/src/x86/BlsLowering.cpp b/core/relinker/codegen/src/x86/BlsLowering.cpp new file mode 100644 index 0000000000..0b2f0f60b8 --- /dev/null +++ b/core/relinker/codegen/src/x86/BlsLowering.cpp @@ -0,0 +1,135 @@ +#include +#include + +namespace Codegen { + +using namespace ScalarLowering; + +namespace { + +constexpr std::uint8_t kSetCarry = 0xF9; + +void _append(std::vector& bytes, const std::vector& instruction) { + bytes.insert(bytes.end(), instruction.begin(), instruction.end()); +} + +std::vector _leaMinusOne(std::uint8_t destination, std::uint8_t base) { + std::vector bytes{Rex(true, destination, base), 0x8D, static_cast(0x40 | ((destination & 7) << 3) | (base & 7))}; + if ((base & 7) == 4) bytes.push_back(0x24); + bytes.push_back(0xFF); + return bytes; +} + +std::vector _nonzero(const BlsOperands& operands, std::uint8_t result, std::uint8_t temporary) { + std::vector bytes; + switch (operands.Operation) { + case BlsOperation::Reset: + _append(bytes, _leaMinusOne(temporary, result)); + _append(bytes, RegisterBytes(0x23, operands.Wide, result, temporary)); + break; + case BlsOperation::Mask: + _append(bytes, _leaMinusOne(temporary, result)); + _append(bytes, RegisterBytes(0x33, operands.Wide, result, temporary)); + break; + case BlsOperation::Isolate: + _append(bytes, RegisterBytes(0x8B, operands.Wide, temporary, result)); + _append(bytes, RegisterBytes(0xF7, operands.Wide, 3, temporary)); + _append(bytes, RegisterBytes(0x23, operands.Wide, result, temporary)); + bytes.push_back(kSetCarry); + break; + } + return bytes; +} + +std::vector _zero(const BlsOperands& operands, std::uint8_t result) { + std::vector bytes; + switch (operands.Operation) { + case BlsOperation::Reset: + _append(bytes, RegisterBytes(0x33, operands.Wide, result, result)); + bytes.push_back(kSetCarry); + break; + case BlsOperation::Mask: + _append(bytes, RegisterBytes(0x83, operands.Wide, 1, result)); + bytes.push_back(0xFF); + bytes.push_back(kSetCarry); + break; + case BlsOperation::Isolate: + _append(bytes, RegisterBytes(0x33, operands.Wide, result, result)); + break; + } + return bytes; +} + +} + +const char* BlsName(const BlsOperation operation) { + switch (operation) { + case BlsOperation::Reset: return "BLSR"; + case BlsOperation::Mask: return "BLSMSK"; + case BlsOperation::Isolate: return "BLSI"; + } + throw CodegenException("unknown BLS operation"); +} + +std::optional DecodeBls(const std::uint8_t* data, const std::size_t length) { + std::size_t vex = 0; + while (vex < length && LegacyPrefix(data[vex])) ++vex; + if (vex + 5 > length || data[vex] != 0xC4 || (data[vex + 1] & 0x1F) != 2 || data[vex + 3] != 0xF3) + return std::nullopt; + const auto modrm = data[vex + 4]; + const auto extension = static_cast((modrm >> 3) & 7); + if (extension < 1 || extension > 3) + return std::nullopt; + if ((data[vex + 2] & 7) != 0) + throw CodegenException(std::string(BlsName(static_cast(extension))) + " requires VEX.L=0 and VEX.pp=0"); + const auto extensions = static_cast(~data[vex + 1]); + BlsOperands operands{ + static_cast(extension), + static_cast((~data[vex + 2] >> 3) & 15), + static_cast((modrm & 7) | ((extensions & 0x20) >> 2)), + (data[vex + 2] & 0x80) != 0, std::nullopt}; + if ((modrm >> 6) == 3) { + if (vex + 5 != length) throw CodegenException(std::string(BlsName(operands.Operation)) + " register operand has trailing bytes"); + } else { + const auto rex = static_cast(((extensions & 0x20) >> 5) | ((extensions & 0x40) >> 5)); + operands.Memory = DecodeMemoryOperand(data, length, vex + 4, rex, {data, data + vex}); + if (vex + 4 + operands.Memory->EncodedSize != length) + throw CodegenException(std::string(BlsName(operands.Operation)) + " memory operand has trailing bytes"); + } + return operands; +} + +void EmitBls(StubBodyBuilder& body, const BlsOperands& operands) { + const auto name = BlsName(operands.Operation); + auto used = operands.Memory ? UsedRegisters({operands.Destination}, operands.Memory) : UsedRegisters({operands.Destination, operands.Source}, std::nullopt); + const auto result = Scratch(used, name); + const auto temporary = Scratch(used, name); + StackSlot(body, 0x8D, true, Stack, -FrameSize); + StackSlot(body, 0x89, true, result, 0); + StackSlot(body, 0x89, true, temporary, 8); + if (operands.Memory) + Memory(body, 0x8B, operands.Wide, *operands.Memory, result, name); + else if (operands.Source == Stack) + StackSlot(body, 0x8D, true, result, FrameSize); + else + Register(body, 0x8B, operands.Wide, result, operands.Source); + Register(body, 0x85, operands.Wide, result, result); + const auto nonzero = _nonzero(operands, result, temporary); + const auto zero = _zero(operands, result); + body.Raw(std::array{std::uint8_t{0x74}, static_cast(nonzero.size() + 2)}); + body.Raw(nonzero); + body.Raw(std::array{std::uint8_t{0xEB}, static_cast(zero.size())}); + body.Raw(zero); + StackSlot(body, 0x8B, true, temporary, 8); + if (operands.Destination == Stack) + StackSlot(body, 0x89, operands.Wide, result, 8); + else + Register(body, 0x8B, operands.Wide, operands.Destination, result); + StackSlot(body, 0x8B, true, result, 0); + if (operands.Destination == Stack) + StackSlot(body, 0x8B, operands.Wide, Stack, 8); + else + StackSlot(body, 0x8D, true, Stack, FrameSize); +} + +} diff --git a/core/relinker/codegen/src/x86/ShiftLowering.cpp b/core/relinker/codegen/src/x86/ShiftLowering.cpp new file mode 100644 index 0000000000..22f42615e3 --- /dev/null +++ b/core/relinker/codegen/src/x86/ShiftLowering.cpp @@ -0,0 +1,132 @@ +#include +#include + +namespace Codegen { + +using namespace ScalarLowering; + +namespace { + +constexpr std::uint8_t kCountRegister = 1; +constexpr std::int32_t kResultSlot = 0; +constexpr std::int32_t kCountSlot = 8; +constexpr std::int32_t kFlagsSlot = 16; +constexpr std::int32_t kStackResultSlot = 24; +constexpr std::uint8_t kPushFlags = 0x9C; +constexpr std::uint8_t kPopFlags = 0x9D; +constexpr std::int32_t kFrame = FrameSize + 16; + +std::uint8_t _shiftDigit(const ShiftOperation operation) { + switch (operation) { + case ShiftOperation::Left: return 4; + case ShiftOperation::LogicalRight: return 5; + case ShiftOperation::ArithmeticRight: return 7; + case ShiftOperation::RotateRight: return 1; + } + throw CodegenException("unknown shift operation"); +} + +void _loadSource(StubBodyBuilder& body, const ShiftOperands& operands, const std::uint8_t result, const char* name) { + if (operands.Memory) + Memory(body, 0x8B, operands.Wide, *operands.Memory, result, name, kFrame); + else if (operands.Source == Stack) + StackSlot(body, 0x8D, true, result, kFrame); + else + Register(body, 0x8B, operands.Wide, result, operands.Source); +} + +} + +const char* ShiftName(const ShiftOperation operation) { + switch (operation) { + case ShiftOperation::Left: return "SHLX"; + case ShiftOperation::LogicalRight: return "SHRX"; + case ShiftOperation::ArithmeticRight: return "SARX"; + case ShiftOperation::RotateRight: return "RORX"; + } + throw CodegenException("unknown shift operation"); +} + +std::optional DecodeShift(const std::uint8_t* data, const std::size_t length) { + std::size_t vex = 0; + while (vex < length && LegacyPrefix(data[vex])) ++vex; + if (vex + 5 > length || data[vex] != 0xC4) return std::nullopt; + const auto map = static_cast(data[vex + 1] & 0x1F); + const auto pp = static_cast(data[vex + 2] & 3); + ShiftOperation operation; + if (map == 2 && data[vex + 3] == 0xF7 && pp != 0) + operation = pp == 1 ? ShiftOperation::Left : pp == 2 ? ShiftOperation::ArithmeticRight : ShiftOperation::LogicalRight; + else if (map == 3 && data[vex + 3] == 0xF0 && pp == 3) + operation = ShiftOperation::RotateRight; + else + return std::nullopt; + const auto name = ShiftName(operation); + if ((data[vex + 2] & 4) != 0) throw CodegenException(std::string(name) + " requires VEX.L=0"); + const auto extensions = static_cast(~data[vex + 1]); + const auto modrm = data[vex + 4]; + const auto vvvv = static_cast((~data[vex + 2] >> 3) & 15); + if (operation == ShiftOperation::RotateRight && vvvv != 0) throw CodegenException("RORX requires VEX.vvvv=1111"); + ShiftOperands operands{ + operation, + static_cast(((modrm >> 3) & 7) | ((extensions & 0x80) >> 4)), + static_cast((modrm & 7) | ((extensions & 0x20) >> 2)), + vvvv, + (data[vex + 2] & 0x80) != 0, std::nullopt}; + std::size_t end = vex + 5; + if ((modrm >> 6) != 3) { + const auto rex = static_cast(((extensions & 0x20) >> 5) | ((extensions & 0x40) >> 5)); + operands.Memory = DecodeMemoryOperand(data, length, vex + 4, rex, {data, data + vex}); + end = vex + 4 + operands.Memory->EncodedSize; + } + if (operation == ShiftOperation::RotateRight) { + if (end >= length) throw CodegenException("RORX is missing its immediate"); + operands.Count = data[end]; + ++end; + } + if (end != length) throw CodegenException(std::string(name) + " has trailing bytes"); + return operands; +} + +void EmitShift(StubBodyBuilder& body, const ShiftOperands& operands) { + const auto name = ShiftName(operands.Operation); + const bool rotate = operands.Operation == ShiftOperation::RotateRight; + std::array used{}; + if (rotate) + used = operands.Memory ? UsedRegisters({operands.Destination}, operands.Memory) : UsedRegisters({operands.Destination, operands.Source}, std::nullopt); + else + used = operands.Memory ? UsedRegisters({operands.Destination, operands.Count, kCountRegister}, operands.Memory) + : UsedRegisters({operands.Destination, operands.Source, operands.Count, kCountRegister}, std::nullopt); + const auto result = Scratch(used, name); + StackSlot(body, 0x8D, true, Stack, -kFrame); + StackSlot(body, 0x89, true, result, kResultSlot); + if (!rotate) StackSlot(body, 0x89, true, kCountRegister, kCountSlot); + body.Raw(std::array{kPushFlags}); + StackSlot(body, 0x8F, false, 0, kFlagsSlot); + _loadSource(body, operands, result, name); + if (rotate) { + Register(body, 0xC1, operands.Wide, _shiftDigit(operands.Operation), result); + body.Raw(std::array{operands.Count}); + } else { + if (operands.Count == Stack) + StackSlot(body, 0x8D, true, kCountRegister, kFrame); + else if (operands.Count != kCountRegister) + Register(body, 0x8B, true, kCountRegister, operands.Count); + Register(body, 0xD3, operands.Wide, _shiftDigit(operands.Operation), result); + } + if (operands.Destination == Stack) + StackSlot(body, 0x89, operands.Wide, result, kStackResultSlot); + else if (!rotate && operands.Destination == kCountRegister) + StackSlot(body, 0x89, true, result, kCountSlot); + else + Register(body, 0x8B, operands.Wide, operands.Destination, result); + StackSlot(body, 0xFF, false, 6, kFlagsSlot); + body.Raw(std::array{kPopFlags}); + if (!rotate) StackSlot(body, 0x8B, true, kCountRegister, kCountSlot); + StackSlot(body, 0x8B, true, result, kResultSlot); + if (operands.Destination == Stack) + StackSlot(body, 0x8B, operands.Wide, Stack, kStackResultSlot); + else + StackSlot(body, 0x8D, true, Stack, kFrame); +} + +} diff --git a/core/relinker/codegen/tests/AndnLoweringTests.cpp b/core/relinker/codegen/tests/AndnLoweringTests.cpp index 6925e2e0b5..b035966455 100644 --- a/core/relinker/codegen/tests/AndnLoweringTests.cpp +++ b/core/relinker/codegen/tests/AndnLoweringTests.cpp @@ -1,20 +1,18 @@ +#include "LoweringExecution.hpp" #include #include #include #include #include #include -#include +#include #include #include #include -#if (defined(__linux__) || defined(__APPLE__)) && defined(__x86_64__) -#include -#endif namespace { -using Bytes = std::vector; +using LoweringExecution::Bytes; void require(bool condition, const char* message) { if (!condition) throw std::runtime_error(message); @@ -69,130 +67,27 @@ void matching() { require(rosetta->MatchSequence(sequence, {}).has_value(), "An ANDN sequence could not move out of line"); } -#if (defined(__linux__) || defined(__APPLE__)) && defined(__x86_64__) +#if LOWERING_EXECUTION_AVAILABLE -struct State { - std::array Input{}; - std::array Output{}; - std::uint64_t Flags = 0; - std::array RedZone{}; -}; +using LoweringExecution::Execution; +using LoweringExecution::State; -class Execution { -public: - Execution(const Bytes& site, std::uint64_t memoryValue) { - auto operands = Codegen::DecodeAndn(site.data(), site.size()); - require(operands.has_value(), "Execution site was not ANDN"); - for (auto reg : {3, 5, 12, 13, 14, 15}) push(static_cast(reg)); - memory(0x8D, 4, 4, -128); - rip({0x48, 0x89, 0x3D}, 0); - rip({0x48, 0x89, 0x25}, 8); - memory(0x89, 4, 7, 32); - bytes.insert(bytes.end(), {0x48, 0xB8}); - append64(kSentinel); - for (int displacement = -128; displacement < 0; displacement += 8) - memory(0x89, 0, 4, displacement); - memory(0x8B, 0, 7, 16 * 8); - memory(0x89, 0, 4, 64); - for (std::uint8_t reg = 0; reg < 16; ++reg) - if (reg != 4 && reg != 7) memory(0x8B, reg, 7, reg * 8); - memory(0x8B, 7, 7, 7 * 8); - const auto siteOffset = bytes.size(); - if (operands->Memory && operands->Memory->RipRelative) { - auto relocated = site; - const auto displacement = static_cast(4096 + 24 - (siteOffset + site.size())); - std::memcpy(relocated.data() + relocated.size() - 4, &displacement, sizeof(displacement)); - operands = Codegen::DecodeAndn(relocated.data(), relocated.size()); - } - Codegen::StubBodyBuilder builder; - Codegen::EmitAndn(builder, *operands); - auto body = builder.Finish(); - const auto stubOffset = bytes.size(); - Codegen::ApplyStubRelocations(body.Bytes, body.Relocations, siteOffset, stubOffset, 0); - bytes.insert(bytes.end(), body.Bytes.begin(), body.Bytes.begin() + static_cast(body.ReturnBranchOffset)); - rip({0x48, 0x89, 0x25}, 16); - rip({0x48, 0x8B, 0x25}, 8); - memory(0x8D, 4, 4, -256); - for (std::uint8_t reg = 0; reg < 16; ++reg) if (reg != 4) push(reg); - bytes.push_back(0x9C); - rip({0x48, 0x8B, 0x3D}, 0); - memory(0x8B, 0, 4, 0); - memory(0x89, 0, 7, 32 * 8); - int offset = 8; - for (int reg = 15; reg >= 0; --reg) { - if (reg == 4) continue; - memory(0x8B, 0, 4, offset); - memory(0x89, 0, 7, (16 + reg) * 8); - offset += 8; - } - rip({0x48, 0x8B, 0x05}, 16); - memory(0x89, 0, 7, 20 * 8); - for (int slot = 0; slot < 16; ++slot) { - memory(0x8B, 0, 4, 256 + 128 - 128 + slot * 8); - memory(0x89, 0, 7, (33 + slot) * 8); - } - rip({0x48, 0x8B, 0x25}, 8); - memory(0x8D, 4, 4, 128); - for (auto reg : {15, 14, 13, 12, 5, 3}) pop(static_cast(reg)); - bytes.push_back(0xC3); - require(bytes.size() < 4096, "Execution wrapper exceeded its code page"); - code = static_cast(mmap(nullptr, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - require(code != MAP_FAILED, "Cannot allocate execution test pages"); - std::memcpy(code, bytes.data(), bytes.size()); - std::memcpy(code + 4096 + 24, &memoryValue, sizeof(memoryValue)); - require(mprotect(code, 4096, PROT_READ | PROT_EXEC) == 0, "Cannot make ANDN test code executable"); - } - - ~Execution() { if (code != MAP_FAILED) munmap(code, 8192); } - - void Run(State& state) const { reinterpret_cast(code)(&state); } - const void* Memory() const { return code + 4096 + 24; } - static constexpr std::uint64_t kSentinel = 0xBADC0FFEE0DDF00Dull; - -private: - Bytes bytes; - std::uint8_t* code = reinterpret_cast(MAP_FAILED); - - void append32(std::int32_t value) { - for (unsigned shift = 0; shift < 32; shift += 8) bytes.push_back(static_cast(static_cast(value) >> shift)); - } - void append64(std::uint64_t value) { - for (unsigned shift = 0; shift < 64; shift += 8) bytes.push_back(static_cast(value >> shift)); - } - void rip(std::initializer_list opcode, int slot) { - bytes.insert(bytes.end(), opcode); - append32(static_cast(4096 + slot - (bytes.size() + 4))); - } - void push(std::uint8_t reg) { - if (reg >= 8) bytes.push_back(0x41); - bytes.push_back(static_cast(0x50 | (reg & 7))); - } - void pop(std::uint8_t reg) { - if (reg >= 8) bytes.push_back(0x41); - bytes.push_back(static_cast(0x58 | (reg & 7))); - } - void memory(std::uint8_t opcode, std::uint8_t reg, std::uint8_t base, int displacement) { - bytes.insert(bytes.end(), {static_cast(0x48 | ((reg & 8) >> 1) | ((base & 8) >> 3)), opcode, - static_cast(0x80 | ((reg & 7) << 3) | (base & 7))}); - if ((base & 7) == 4) bytes.push_back(0x24); - append32(displacement); - } -}; +Execution executable(const Bytes& site, std::uint64_t memoryValue) { + const auto operands = Codegen::DecodeAndn(site.data(), site.size()); + require(operands.has_value(), "Execution site was not ANDN"); + return Execution(site, memoryValue, operands->Memory && operands->Memory->RipRelative, [](Codegen::StubBodyBuilder& body, const Bytes& relocated) { + Codegen::EmitAndn(body, *Codegen::DecodeAndn(relocated.data(), relocated.size())); + }); +} void check(const State& state, const Codegen::AndnOperands& operands, std::uint64_t second) { const auto mask = operands.Wide ? ~std::uint64_t{0} : 0xFFFFFFFFull; const auto expected = (~state.Input[operands.InvertedSource] & second) & mask; for (std::size_t reg = 0; reg < 16; ++reg) require(state.Output[reg] == (reg == operands.Destination ? expected : state.Input[reg]), "ANDN changed a source/scratch register or produced an incorrect result"); - const auto flags = (expected == 0 ? 0x40u : 0u) | ((expected >> (operands.Wide ? 63 : 31)) != 0 ? 0x80u : 0u); - require((state.Flags & 0x8C1u) == flags, "ANDN did not preserve its defined flag semantics"); - for (const auto value : state.RedZone) require(value == Execution::kSentinel, "ANDN overwrote the caller's red zone"); -} - -State initialState() { - State state; - for (std::size_t reg = 0; reg < 16; ++reg) state.Input[reg] = 0xABCD123456789000ull + reg * 0x1234567ull; - return state; + const auto flags = (expected == 0 ? LoweringExecution::kZero : 0u) | ((expected >> (operands.Wide ? 63 : 31)) != 0 ? LoweringExecution::kSign : 0u); + require((state.Flags & LoweringExecution::kFlagMask) == flags, "ANDN did not preserve its defined flag semantics"); + for (const auto value : state.RedZone) require(value == LoweringExecution::kSentinel, "ANDN overwrote the caller's red zone"); } void execution() { @@ -200,12 +95,12 @@ void execution() { for (std::uint8_t dst = 0; dst < 16; ++dst) { for (const auto pair : {std::pair{1, 6}, {dst, 10}, {11, dst}, {dst, dst}, {4, 9}, {9, 4}}) { const auto site = instruction(wide, dst, pair.first, pair.second); - Execution executable(site, 0); + const auto run = executable(site, 0); for (int variant = 0; variant < 3; ++variant) { - auto state = initialState(); + auto state = LoweringExecution::InitialState(); if (variant == 1) state.Input.fill(0); if (variant == 2) state.Input.fill(~std::uint64_t{0}); - executable.Run(state); + run.Run(state); check(state, *Codegen::DecodeAndn(site.data(), site.size()), state.Input[pair.second]); } } @@ -214,14 +109,14 @@ void execution() { Bytes site{0xC4, 0xE2, static_cast(wide ? 0xF0 : 0x70), 0xF2}; site.insert(site.end(), tail.begin(), tail.end()); constexpr std::uint64_t value = 0xFEDCBA9876543210ull; - Execution executable(site, value); - auto state = initialState(); - state.Input[6] = reinterpret_cast(executable.Memory()) - (tail[0] == 0x46 ? 16 : 0); - state.Input[2] = reinterpret_cast(executable.Memory()) - 3 * 8; + const auto run = executable(site, value); + auto state = LoweringExecution::InitialState(); + state.Input[6] = reinterpret_cast(run.Memory()) - (tail[0] == 0x46 ? 16 : 0); + state.Input[2] = reinterpret_cast(run.Memory()) - 3 * 8; state.Input[1] = 3; - state.Input[5] = reinterpret_cast(executable.Memory()) - 16; + state.Input[5] = reinterpret_cast(run.Memory()) - 16; state.Output[0] = value; - executable.Run(state); + run.Run(state); check(state, *Codegen::DecodeAndn(site.data(), site.size()), value); } } diff --git a/core/relinker/codegen/tests/BitfieldLoweringTests.cpp b/core/relinker/codegen/tests/BitfieldLoweringTests.cpp new file mode 100644 index 0000000000..acfc306750 --- /dev/null +++ b/core/relinker/codegen/tests/BitfieldLoweringTests.cpp @@ -0,0 +1,211 @@ +#include "LoweringExecution.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using LoweringExecution::Bytes; +using Codegen::BitfieldOperation; + +constexpr std::array kOperations{BitfieldOperation::ZeroHigh, BitfieldOperation::Extract}; + +void require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +std::uint8_t opcode(BitfieldOperation operation) { + return operation == BitfieldOperation::ZeroHigh ? 0xF5 : 0xF7; +} + +Bytes instruction(BitfieldOperation operation, bool wide, std::uint8_t dst, std::uint8_t src, std::uint8_t control) { + return {0xC4, static_cast(0xE2 ^ ((dst & 8) << 4) ^ ((src & 8) << 2)), + static_cast((wide ? 0x80 : 0) | ((~control & 15) << 3)), opcode(operation), + static_cast(0xC0 | ((dst & 7) << 3) | (src & 7))}; +} + +Bytes memorySite(BitfieldOperation operation, bool wide, const Bytes& tail) { + Bytes site{0xC4, 0xE2, static_cast((wide ? 0x80 : 0) | 0x40), opcode(operation)}; + site.insert(site.end(), tail.begin(), tail.end()); + return site; +} + +void decoding() { + for (const auto operation : kOperations) { + for (bool wide : {false, true}) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (std::uint8_t src = 0; src < 16; ++src) { + for (std::uint8_t control = 0; control < 16; ++control) { + const auto bytes = instruction(operation, wide, dst, src, control); + const auto operands = Codegen::DecodeBitfield(bytes.data(), bytes.size()); + require(operands && operands->Operation == operation && operands->Wide == wide && operands->Destination == dst && operands->Source == src && operands->Control == control && !operands->Memory, "bitfield register decoding changed an operand"); + } + } + } + } + } + const Bytes memory{0x64, 0x67, 0xC4, 0x02, 0xA8, 0xF5, 0x8C, 0xAC, 0x10, 0, 0, 0}; + const auto operands = Codegen::DecodeBitfield(memory.data(), memory.size()); + require(operands && operands->Operation == BitfieldOperation::ZeroHigh && operands->Destination == 9 && operands->Control == 10 && operands->Memory && operands->Memory->RexIndexBase == 3 && operands->Memory->Prefixes == Bytes{0x64, 0x67}, "bitfield memory extensions or prefixes were lost"); + for (const Bytes bytes : {Bytes{0x90}, Bytes{0xC4, 0xE2, 0xF0}, Bytes{0xC4, 0xE1, 0xF0, 0xF5, 0xC0}, Bytes{0xC4, 0xE2, 0xF1, 0xF7, 0xC0}, Bytes{0xC4, 0xE2, 0xF3, 0xF5, 0xC0}, Bytes{0xC4, 0xE2, 0xF2, 0xF5, 0xC0}, Bytes{0xC4, 0xE2, 0xF3, 0xF6, 0xC0}, Bytes{0xC4, 0xE2, 0xF0, 0xF3, 0xC8}}) + require(!Codegen::DecodeBitfield(bytes.data(), bytes.size()), "Unrelated, truncated, SHLX, PDEP, PEXT, MULX or BLSR opcode matched a bitfield instruction"); + for (const Bytes bytes : {Bytes{0xC4, 0xE2, 0xF4, 0xF5, 0xC0}, Bytes{0xC4, 0xE2, 0xF0, 0xF7, 0xC0, 0x90}, Bytes{0xC4, 0xE2, 0xF0, 0xF5, 0x84}}) { + bool rejected = false; + try { (void)Codegen::DecodeBitfield(bytes.data(), bytes.size()); } + catch (const Codegen::CodegenException&) { rejected = true; } + require(rejected, "Malformed bitfield instruction was accepted"); + } + const Bytes overflow{0xC4, 0xE2, 0xF0, 0xF7, 0x8C, 0x24, 0xFF, 0xFF, 0xFF, 0x7F}; + bool rejected = false; + try { + Codegen::StubBodyBuilder body; + Codegen::EmitBitfield(body, *Codegen::DecodeBitfield(overflow.data(), overflow.size())); + } catch (const Codegen::CodegenException&) { rejected = true; } + require(rejected, "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 operation : kOperations) { + const auto bytes = instruction(operation, true, 0, 6, 2); + const auto name = Codegen::BitfieldName(operation); + require(!intel->Match(bytes.data(), bytes.size()), std::string("Intel mode rewrote ") + name); + const auto match = rosetta->Match(bytes.data(), bytes.size()); + require(match && match->InstructionName == name && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline && !match->Optional, std::string("Rosetta mode did not require a trampoline for ") + name); + const std::array, 2> sequence{bytes, bytes}; + require(rosetta->MatchSequence(sequence, {}).has_value(), std::string("A sequence could not move out of line for ") + name); + } +} + +#if LOWERING_EXECUTION_AVAILABLE + +using LoweringExecution::Execution; +using LoweringExecution::State; + +Execution executable(const Bytes& site, std::uint64_t memoryValue) { + const auto operands = Codegen::DecodeBitfield(site.data(), site.size()); + require(operands.has_value(), "Execution site was not a bitfield instruction"); + return Execution(site, memoryValue, operands->Memory && operands->Memory->RipRelative, [](Codegen::StubBodyBuilder& body, const Bytes& relocated) { + Codegen::EmitBitfield(body, *Codegen::DecodeBitfield(relocated.data(), relocated.size())); + }); +} + +struct Expected { + std::uint64_t Result; + std::uint64_t Flags; + std::uint64_t FlagMask; +}; + +Expected expected(const Codegen::BitfieldOperands& operands, std::uint64_t source, std::uint64_t control) { + const unsigned bits = operands.Wide ? 64 : 32; + const auto mask = operands.Wide ? ~std::uint64_t{0} : 0xFFFFFFFFull; + source &= mask; + const auto lowBits = [&](unsigned count) { return count >= bits ? mask : (std::uint64_t{1} << count) - 1; }; + if (operands.Operation == BitfieldOperation::ZeroHigh) { + const auto index = static_cast(control & 0xFF); + const auto result = index >= bits ? source : source & lowBits(index); + const auto flags = (result == 0 ? LoweringExecution::kZero : 0u) | (index >= bits ? LoweringExecution::kCarry : 0u) | ((result >> (bits - 1)) != 0 ? LoweringExecution::kSign : 0u); + return {result, flags, LoweringExecution::kFlagMask}; + } + const auto start = static_cast(control & 0xFF); + const auto length = static_cast((control >> 8) & 0xFF); + const auto result = (start >= bits ? 0 : source >> start) & lowBits(length); + return {result, result == 0 ? LoweringExecution::kZero : 0u, LoweringExecution::kFlagMask & ~LoweringExecution::kSign}; +} + +void check(const State& state, const Codegen::BitfieldOperands& operands, std::uint64_t source, std::uint64_t control) { + const auto name = Codegen::BitfieldName(operands.Operation); + const auto wanted = expected(operands, source, control); + const auto describe = [&] { + return std::string(name) + (operands.Wide ? " 64" : " 32") + " dst " + std::to_string(operands.Destination) + " src " + std::to_string(operands.Source) + " control " + std::to_string(operands.Control) + (operands.Memory ? " memory" : "") + " (source " + std::to_string(source) + ", control value " + std::to_string(control) + ")"; + }; + for (std::size_t reg = 0; reg < 16; ++reg) { + const auto value = reg == operands.Destination ? wanted.Result : state.Input[reg]; + if (state.Output[reg] != value) + throw std::runtime_error(describe() + ": register " + std::to_string(reg) + " is " + std::to_string(state.Output[reg]) + ", wanted " + std::to_string(value)); + } + if ((state.Flags & wanted.FlagMask) != wanted.Flags) + throw std::runtime_error(describe() + ": flags are " + std::to_string(state.Flags & wanted.FlagMask) + ", wanted " + std::to_string(wanted.Flags)); + for (const auto value : state.RedZone) require(value == LoweringExecution::kSentinel, std::string(name) + " overwrote the caller's red zone"); +} + +void run(const Execution& execution, const Codegen::BitfieldOperands& operands, State state, std::uint64_t memoryValue) { + for (const auto flags : {LoweringExecution::kBaseFlags | LoweringExecution::kFlagMask, LoweringExecution::kBaseFlags}) { + state.InputFlags = flags; + auto trial = state; + execution.Run(trial); + const auto source = operands.Memory ? memoryValue : trial.Input[operands.Source]; + check(trial, operands, source, trial.Input[operands.Control]); + } +} + +void execution() { + constexpr std::array values{0, 1, 0x80000000ull, 0x8000000000000000ull, 0xFFFFFFFFFFFFFFFFull, 0xABCD123456789000ull}; + constexpr std::array controls{0, 1, 31, 32, 63, 64, 0xFF, 0x100, 0x801, 0x1F05, 0x2000, 0x201F, 0x3F3F, 0x4000, 0x4020, 0xFF00, 0xFFFF, 0x0F10, 0x12345678, 0xFFFFFFFFFFFF0405ull}; + for (const auto operation : kOperations) { + for (bool wide : {false, true}) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (const auto pair : std::array, 9>{{{6, 2}, {dst, 10}, {11, dst}, {dst, dst}, {4, 9}, {9, 4}, {1, 6}, {6, 1}, {1, 1}}}) { + const auto site = instruction(operation, wide, dst, pair.first, pair.second); + const auto operands = *Codegen::DecodeBitfield(site.data(), site.size()); + const auto execution = executable(site, 0); + for (const auto value : values) { + for (const auto control : controls) { + auto state = LoweringExecution::InitialState(); + state.Input[pair.first] = value; + state.Input[pair.second] = control; + run(execution, operands, state, 0); + } + } + } + } + } + } + for (const auto operation : kOperations) { + for (bool wide : {false, true}) { + for (const Bytes tail : {Bytes{0x06}, Bytes{0x46, 0x10}, Bytes{0x84, 0x24, 0x40, 0, 0, 0}, Bytes{0x04, 0xCA}, Bytes{0x85, 0x10, 0, 0, 0}, Bytes{0x05, 0, 0, 0, 0}}) { + for (const auto value : std::array{0, 0xFEDCBA9876543210ull, 0xFFFFFFFF00000000ull}) { + for (const auto control : std::array{0, 0x0C04, 0x3F, 0x40}) { + const auto site = memorySite(operation, wide, tail); + const auto operands = *Codegen::DecodeBitfield(site.data(), site.size()); + const auto execution = executable(site, value); + auto state = LoweringExecution::InitialState(); + state.Input[6] = reinterpret_cast(execution.Memory()) - (tail[0] == 0x46 ? 16 : 0); + state.Input[2] = reinterpret_cast(execution.Memory()) - 3 * 8; + state.Input[1] = 3; + state.Input[5] = reinterpret_cast(execution.Memory()) - 16; + state.Input[7] = control; + state.Output[0] = value; + run(execution, operands, state, value); + } + } + } + } + } +} +#else +void execution() { std::cout << "bitfield execution checks require a Linux/macOS x86-64 build\n"; } +#endif + +} + +int main() { + try { + decoding(); + matching(); + execution(); + std::cout << "bitfield lowering checks passed\n"; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/relinker/codegen/tests/BlsLoweringTests.cpp b/core/relinker/codegen/tests/BlsLoweringTests.cpp new file mode 100644 index 0000000000..cbbbca28af --- /dev/null +++ b/core/relinker/codegen/tests/BlsLoweringTests.cpp @@ -0,0 +1,170 @@ +#include "LoweringExecution.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using LoweringExecution::Bytes; +using Codegen::BlsOperation; + +constexpr std::array kOperations{BlsOperation::Reset, BlsOperation::Mask, BlsOperation::Isolate}; + +void require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +std::uint8_t extension(BlsOperation operation) { + return static_cast(operation); +} + +Bytes instruction(BlsOperation operation, bool wide, std::uint8_t dst, std::uint8_t src) { + return {0xC4, static_cast(0xE2 ^ ((src & 8) << 2)), + static_cast((wide ? 0x80 : 0) | ((~dst & 15) << 3)), 0xF3, + static_cast(0xC0 | (extension(operation) << 3) | (src & 7))}; +} + +void decoding() { + for (const auto operation : kOperations) { + for (bool wide : {false, true}) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (std::uint8_t src = 0; src < 16; ++src) { + const auto bytes = instruction(operation, wide, dst, src); + const auto operands = Codegen::DecodeBls(bytes.data(), bytes.size()); + require(operands && operands->Operation == operation && operands->Wide == wide && operands->Destination == dst && operands->Source == src && !operands->Memory, "BLS register decoding changed an operand"); + } + } + } + const Bytes memory{0x64, 0x67, 0xC4, 0x02, 0xA8, 0xF3, static_cast(0x84 | (extension(operation) << 3)), 0xAC, 0x10, 0, 0, 0}; + const auto operands = Codegen::DecodeBls(memory.data(), memory.size()); + require(operands && operands->Operation == operation && operands->Destination == 10 && operands->Memory && operands->Memory->RexIndexBase == 3 && operands->Memory->Prefixes == Bytes{0x64, 0x67}, "BLS memory extensions or prefixes were lost"); + } + for (const Bytes bytes : {Bytes{0x90}, Bytes{0xC4, 0xE2, 0xF0}, Bytes{0xC4, 0xE1, 0xF0, 0xF3, 0xC8}, Bytes{0xC4, 0xE2, 0xF0, 0xF3, 0xC0}, Bytes{0xC4, 0xE2, 0xF0, 0xF3, 0xE0}, Bytes{0xC4, 0xE2, 0xF0, 0xF2, 0xC8}}) + require(!Codegen::DecodeBls(bytes.data(), bytes.size()), "Unrelated, truncated or non-BLS opcode matched BLS"); + for (const Bytes bytes : {Bytes{0xC4, 0xE2, 0xF4, 0xF3, 0xC8}, Bytes{0xC4, 0xE2, 0xF1, 0xF3, 0xD0}, Bytes{0xC4, 0xE2, 0xF0, 0xF3, 0xD8, 0x90}, Bytes{0xC4, 0xE2, 0xF0, 0xF3, 0x8C}}) { + bool rejected = false; + try { (void)Codegen::DecodeBls(bytes.data(), bytes.size()); } + catch (const Codegen::CodegenException&) { rejected = true; } + require(rejected, "Malformed BLS was accepted"); + } + const Bytes overflow{0xC4, 0xE2, 0xF0, 0xF3, 0x8C, 0x24, 0xFF, 0xFF, 0xFF, 0x7F}; + bool rejected = false; + try { + Codegen::StubBodyBuilder body; + Codegen::EmitBls(body, *Codegen::DecodeBls(overflow.data(), overflow.size())); + } catch (const Codegen::CodegenException&) { rejected = true; } + require(rejected, "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 operation : kOperations) { + const auto bytes = instruction(operation, true, 0, 6); + const auto name = Codegen::BlsName(operation); + require(!intel->Match(bytes.data(), bytes.size()), std::string("Intel mode rewrote ") + name); + const auto match = rosetta->Match(bytes.data(), bytes.size()); + require(match && match->InstructionName == name && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline && !match->Optional, std::string("Rosetta mode did not require a trampoline for ") + name); + const std::array, 2> sequence{bytes, bytes}; + require(rosetta->MatchSequence(sequence, {}).has_value(), std::string("A sequence could not move out of line for ") + name); + } +} + +#if LOWERING_EXECUTION_AVAILABLE + +using LoweringExecution::Execution; +using LoweringExecution::State; + +Execution executable(const Bytes& site, std::uint64_t memoryValue) { + const auto operands = Codegen::DecodeBls(site.data(), site.size()); + require(operands.has_value(), "Execution site was not BLS"); + return Execution(site, memoryValue, operands->Memory && operands->Memory->RipRelative, [](Codegen::StubBodyBuilder& body, const Bytes& relocated) { + Codegen::EmitBls(body, *Codegen::DecodeBls(relocated.data(), relocated.size())); + }); +} + +void check(const State& state, const Codegen::BlsOperands& operands, std::uint64_t source) { + const auto name = Codegen::BlsName(operands.Operation); + const auto mask = operands.Wide ? ~std::uint64_t{0} : 0xFFFFFFFFull; + source &= mask; + std::uint64_t expected = 0; + std::uint64_t flags = 0; + switch (operands.Operation) { + case BlsOperation::Reset: + expected = (source & (source - 1)) & mask; + flags = (expected == 0 ? LoweringExecution::kZero : 0u) | (source == 0 ? LoweringExecution::kCarry : 0u); + break; + case BlsOperation::Mask: + expected = (source ^ (source - 1)) & mask; + flags = source == 0 ? LoweringExecution::kCarry : 0u; + break; + case BlsOperation::Isolate: + expected = (source & (~source + 1)) & mask; + flags = (expected == 0 ? LoweringExecution::kZero : 0u) | (source != 0 ? LoweringExecution::kCarry : 0u); + break; + } + flags |= (expected >> (operands.Wide ? 63 : 31)) != 0 ? LoweringExecution::kSign : 0u; + for (std::size_t reg = 0; reg < 16; ++reg) + require(state.Output[reg] == (reg == operands.Destination ? expected : state.Input[reg]), std::string(name) + " changed a source/scratch register or produced an incorrect result"); + require((state.Flags & LoweringExecution::kFlagMask) == flags, std::string(name) + " did not produce its defined flags"); + for (const auto value : state.RedZone) require(value == LoweringExecution::kSentinel, std::string(name) + " overwrote the caller's red zone"); +} + +void execution() { + constexpr std::array values{0, 1, 0x6, 0x80000000ull, 0x8000000000000000ull, 0xFFFFFFFF00000000ull, 0xFFFFFFFFFFFFFFFFull, 0xABCD123456789000ull}; + for (const auto operation : kOperations) { + for (bool wide : {false, true}) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (const std::uint8_t src : std::array{6, dst, 4, 10}) { + const auto site = instruction(operation, wide, dst, src); + const auto run = executable(site, 0); + for (const auto value : values) { + auto state = LoweringExecution::InitialState(); + state.Input[src] = value; + run.Run(state); + check(state, *Codegen::DecodeBls(site.data(), site.size()), state.Input[src]); + } + } + } + for (const Bytes tail : {Bytes{0x06}, Bytes{0x46, 0x10}, Bytes{0x84, 0x24, 0x40, 0, 0, 0}, Bytes{0x04, 0xCA}, Bytes{0x85, 0x10, 0, 0, 0}, Bytes{0x05, 0, 0, 0, 0}}) { + for (const auto value : std::array{0, 0xFEDCBA9876543210ull, 0xFFFFFFFF00000000ull}) { + Bytes site{0xC4, 0xE2, static_cast(wide ? 0xF8 : 0x78), 0xF3, static_cast(tail[0] | (extension(operation) << 3))}; + site.insert(site.end(), tail.begin() + 1, tail.end()); + const auto run = executable(site, value); + auto state = LoweringExecution::InitialState(); + state.Input[6] = reinterpret_cast(run.Memory()) - (tail[0] == 0x46 ? 16 : 0); + state.Input[2] = reinterpret_cast(run.Memory()) - 3 * 8; + state.Input[1] = 3; + state.Input[5] = reinterpret_cast(run.Memory()) - 16; + state.Output[0] = value; + run.Run(state); + check(state, *Codegen::DecodeBls(site.data(), site.size()), value); + } + } + } + } +} +#else +void execution() { std::cout << "BLS execution checks require a Linux/macOS x86-64 build\n"; } +#endif + +} + +int main() { + try { + decoding(); + matching(); + execution(); + std::cout << "BLS lowering checks passed\n"; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/relinker/codegen/tests/LoweringExecution.hpp b/core/relinker/codegen/tests/LoweringExecution.hpp new file mode 100644 index 0000000000..28eb241865 --- /dev/null +++ b/core/relinker/codegen/tests/LoweringExecution.hpp @@ -0,0 +1,155 @@ +#ifndef CODEGEN_TESTS_LOWERINGEXECUTION_HPP +#define CODEGEN_TESTS_LOWERINGEXECUTION_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#if (defined(__linux__) || defined(__APPLE__)) && defined(__x86_64__) +#include +#define LOWERING_EXECUTION_AVAILABLE 1 +#else +#define LOWERING_EXECUTION_AVAILABLE 0 +#endif + +namespace LoweringExecution { + +using Bytes = std::vector; + +struct State { + std::array Input{}; + std::array Output{}; + std::uint64_t Flags = 0; + std::array RedZone{}; + std::uint64_t InputFlags = 0; +}; + +inline constexpr std::uint64_t kSentinel = 0xBADC0FFEE0DDF00Dull; +inline constexpr std::uint64_t kFlagMask = 0x8C1u; +inline constexpr std::uint64_t kCarry = 0x1u; +inline constexpr std::uint64_t kZero = 0x40u; +inline constexpr std::uint64_t kSign = 0x80u; + +inline constexpr std::uint64_t kBaseFlags = 0x202u; + +inline State InitialState() { + State state; + for (std::size_t reg = 0; reg < 16; ++reg) state.Input[reg] = 0xABCD123456789000ull + reg * 0x1234567ull; + state.InputFlags = kBaseFlags | kFlagMask; + return state; +} + +#if LOWERING_EXECUTION_AVAILABLE + +class Execution { +public: + using Emit = std::function; + + Execution(const Bytes& site, std::uint64_t memoryValue, bool ripRelative, const Emit& emit, std::size_t trailing = 0) { + for (auto reg : {3, 5, 12, 13, 14, 15}) push(static_cast(reg)); + memory(0x8D, 4, 4, -128); + rip({0x48, 0x89, 0x3D}, 0); + rip({0x48, 0x89, 0x25}, 8); + memory(0x89, 4, 7, 32); + memory(0xFF, 6, 7, 49 * 8); + bytes.push_back(0x9D); + bytes.insert(bytes.end(), {0x48, 0xB8}); + append64(kSentinel); + for (int displacement = -128; displacement < 0; displacement += 8) + memory(0x89, 0, 4, displacement); + memory(0x8B, 0, 7, 16 * 8); + memory(0x89, 0, 4, 64); + for (std::uint8_t reg = 0; reg < 16; ++reg) + if (reg != 4 && reg != 7) memory(0x8B, reg, 7, reg * 8); + memory(0x8B, 7, 7, 7 * 8); + const auto siteOffset = bytes.size(); + auto relocated = site; + if (ripRelative) { + const auto displacement = static_cast(4096 + 24 - (siteOffset + site.size())); + std::memcpy(relocated.data() + relocated.size() - 4 - trailing, &displacement, sizeof(displacement)); + } + Codegen::StubBodyBuilder builder; + emit(builder, relocated); + auto body = builder.Finish(); + const auto stubOffset = bytes.size(); + Codegen::ApplyStubRelocations(body.Bytes, body.Relocations, siteOffset, stubOffset, 0); + bytes.insert(bytes.end(), body.Bytes.begin(), body.Bytes.begin() + static_cast(body.ReturnBranchOffset)); + rip({0x48, 0x89, 0x25}, 16); + rip({0x48, 0x8B, 0x25}, 8); + memory(0x8D, 4, 4, -256); + for (std::uint8_t reg = 0; reg < 16; ++reg) if (reg != 4) push(reg); + bytes.push_back(0x9C); + rip({0x48, 0x8B, 0x3D}, 0); + memory(0x8B, 0, 4, 0); + memory(0x89, 0, 7, 32 * 8); + int offset = 8; + for (int reg = 15; reg >= 0; --reg) { + if (reg == 4) continue; + memory(0x8B, 0, 4, offset); + memory(0x89, 0, 7, (16 + reg) * 8); + offset += 8; + } + rip({0x48, 0x8B, 0x05}, 16); + memory(0x89, 0, 7, 20 * 8); + for (int slot = 0; slot < 16; ++slot) { + memory(0x8B, 0, 4, 256 + 128 - 128 + slot * 8); + memory(0x89, 0, 7, (33 + slot) * 8); + } + rip({0x48, 0x8B, 0x25}, 8); + memory(0x8D, 4, 4, 128); + for (auto reg : {15, 14, 13, 12, 5, 3}) pop(static_cast(reg)); + bytes.push_back(0xC3); + if (bytes.size() >= 4096) throw std::runtime_error("Execution wrapper exceeded its code page"); + code = static_cast(mmap(nullptr, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); + if (code == MAP_FAILED) throw std::runtime_error("Cannot allocate execution test pages"); + std::memcpy(code, bytes.data(), bytes.size()); + std::memcpy(code + 4096 + 24, &memoryValue, sizeof(memoryValue)); + if (mprotect(code, 4096, PROT_READ | PROT_EXEC) != 0) throw std::runtime_error("Cannot make lowering test code executable"); + } + + ~Execution() { if (code != MAP_FAILED) munmap(code, 8192); } + Execution(const Execution&) = delete; + Execution& operator=(const Execution&) = delete; + + void Run(State& state) const { reinterpret_cast(code)(&state); } + const void* Memory() const { return code + 4096 + 24; } + +private: + Bytes bytes; + std::uint8_t* code = reinterpret_cast(MAP_FAILED); + + void append32(std::int32_t value) { + for (unsigned shift = 0; shift < 32; shift += 8) bytes.push_back(static_cast(static_cast(value) >> shift)); + } + void append64(std::uint64_t value) { + for (unsigned shift = 0; shift < 64; shift += 8) bytes.push_back(static_cast(value >> shift)); + } + void rip(std::initializer_list opcode, int slot) { + bytes.insert(bytes.end(), opcode); + append32(static_cast(4096 + slot - (bytes.size() + 4))); + } + void push(std::uint8_t reg) { + if (reg >= 8) bytes.push_back(0x41); + bytes.push_back(static_cast(0x50 | (reg & 7))); + } + void pop(std::uint8_t reg) { + if (reg >= 8) bytes.push_back(0x41); + bytes.push_back(static_cast(0x58 | (reg & 7))); + } + void memory(std::uint8_t opcode, std::uint8_t reg, std::uint8_t base, int displacement) { + bytes.insert(bytes.end(), {static_cast(0x48 | ((reg & 8) >> 1) | ((base & 8) >> 3)), opcode, + static_cast(0x80 | ((reg & 7) << 3) | (base & 7))}); + if ((base & 7) == 4) bytes.push_back(0x24); + append32(displacement); + } +}; + +#endif + +} + +#endif diff --git a/core/relinker/codegen/tests/ShiftLoweringTests.cpp b/core/relinker/codegen/tests/ShiftLoweringTests.cpp new file mode 100644 index 0000000000..d0f3b04f2d --- /dev/null +++ b/core/relinker/codegen/tests/ShiftLoweringTests.cpp @@ -0,0 +1,248 @@ +#include "LoweringExecution.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using LoweringExecution::Bytes; +using Codegen::ShiftOperation; + +constexpr std::array kShifts{ShiftOperation::Left, ShiftOperation::LogicalRight, ShiftOperation::ArithmeticRight}; +constexpr std::array kOperations{ShiftOperation::Left, ShiftOperation::LogicalRight, ShiftOperation::ArithmeticRight, ShiftOperation::RotateRight}; + +void require(bool condition, const std::string& message) { + if (!condition) throw std::runtime_error(message); +} + +std::uint8_t pp(ShiftOperation operation) { + switch (operation) { + case ShiftOperation::Left: return 1; + case ShiftOperation::ArithmeticRight: return 2; + case ShiftOperation::LogicalRight: return 3; + case ShiftOperation::RotateRight: return 3; + } + return 0; +} + +Bytes shift(ShiftOperation operation, bool wide, std::uint8_t dst, std::uint8_t src, std::uint8_t count) { + return {0xC4, static_cast(0xE2 ^ ((dst & 8) << 4) ^ ((src & 8) << 2)), + static_cast((wide ? 0x80 : 0) | ((~count & 15) << 3) | pp(operation)), 0xF7, + static_cast(0xC0 | ((dst & 7) << 3) | (src & 7))}; +} + +Bytes rotate(bool wide, std::uint8_t dst, std::uint8_t src, std::uint8_t immediate) { + return {0xC4, static_cast(0xE3 ^ ((dst & 8) << 4) ^ ((src & 8) << 2)), + static_cast((wide ? 0x80 : 0) | 0x78 | 3), 0xF0, + static_cast(0xC0 | ((dst & 7) << 3) | (src & 7)), immediate}; +} + +Bytes memorySite(ShiftOperation operation, bool wide, const Bytes& tail, std::uint8_t immediate) { + const bool rorx = operation == ShiftOperation::RotateRight; + Bytes site{0xC4, static_cast(rorx ? 0xE3 : 0xE2), static_cast((wide ? 0x80 : 0) | (rorx ? 0x78 : 0x70) | pp(operation)), static_cast(rorx ? 0xF0 : 0xF7)}; + site.insert(site.end(), tail.begin(), tail.end()); + if (rorx) site.push_back(immediate); + return site; +} + +void decoding() { + for (const auto operation : kShifts) { + for (bool wide : {false, true}) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (std::uint8_t src = 0; src < 16; ++src) { + for (std::uint8_t count = 0; count < 16; ++count) { + const auto bytes = shift(operation, wide, dst, src, count); + const auto operands = Codegen::DecodeShift(bytes.data(), bytes.size()); + require(operands && operands->Operation == operation && operands->Wide == wide && operands->Destination == dst && operands->Source == src && operands->Count == count && !operands->Memory, "shift register decoding changed an operand"); + } + } + } + } + } + for (bool wide : {false, true}) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (std::uint8_t src = 0; src < 16; ++src) { + for (const std::uint8_t immediate : std::array{0, 1, 63, 255}) { + const auto bytes = rotate(wide, dst, src, immediate); + const auto operands = Codegen::DecodeShift(bytes.data(), bytes.size()); + require(operands && operands->Operation == ShiftOperation::RotateRight && operands->Wide == wide && operands->Destination == dst && operands->Source == src && operands->Count == immediate && !operands->Memory, "RORX register decoding changed an operand"); + } + } + } + } + const Bytes shiftMemory{0x64, 0x67, 0xC4, 0x02, 0xA9, 0xF7, 0x8C, 0xAC, 0x10, 0, 0, 0}; + auto operands = Codegen::DecodeShift(shiftMemory.data(), shiftMemory.size()); + require(operands && operands->Operation == ShiftOperation::Left && operands->Destination == 9 && operands->Count == 10 && operands->Memory && operands->Memory->RexIndexBase == 3 && operands->Memory->Prefixes == Bytes{0x64, 0x67}, "shift memory extensions or prefixes were lost"); + const Bytes rotateMemory{0xC4, 0x03, 0xFB, 0xF0, 0x8C, 0xAC, 0x10, 0, 0, 0, 0x21}; + operands = Codegen::DecodeShift(rotateMemory.data(), rotateMemory.size()); + require(operands && operands->Operation == ShiftOperation::RotateRight && operands->Destination == 9 && operands->Count == 0x21 && operands->Memory && operands->Memory->InstructionLength == rotateMemory.size(), "RORX memory operand or immediate was lost"); + for (const Bytes bytes : {Bytes{0x90}, Bytes{0xC4, 0xE2, 0xF1}, Bytes{0xC4, 0xE1, 0xF1, 0xF7, 0xC0}, Bytes{0xC4, 0xE2, 0xF0, 0xF7, 0xC0}, Bytes{0xC4, 0xE3, 0xFA, 0xF0, 0xC0, 0}, Bytes{0xC4, 0xE2, 0xFB, 0xF0, 0xC0, 0}}) + require(!Codegen::DecodeShift(bytes.data(), bytes.size()), "Unrelated, truncated, BEXTR or wrong-map opcode matched a shift"); + for (const Bytes bytes : {Bytes{0xC4, 0xE2, 0xF5, 0xF7, 0xC0}, Bytes{0xC4, 0xE2, 0xF1, 0xF7, 0xC0, 0x90}, Bytes{0xC4, 0xE2, 0xF1, 0xF7, 0x84}, Bytes{0xC4, 0xE3, 0xF3, 0xF0, 0xC0, 0}, Bytes{0xC4, 0xE3, 0xFB, 0xF0, 0xC0}, Bytes{0xC4, 0xE3, 0xFB, 0xF0, 0xC0, 0, 0}}) { + bool rejected = false; + try { (void)Codegen::DecodeShift(bytes.data(), bytes.size()); } + catch (const Codegen::CodegenException&) { rejected = true; } + require(rejected, "Malformed shift was accepted"); + } + const Bytes overflow{0xC4, 0xE2, 0xF1, 0xF7, 0x8C, 0x24, 0xFF, 0xFF, 0xFF, 0x7F}; + bool rejected = false; + try { + Codegen::StubBodyBuilder body; + Codegen::EmitShift(body, *Codegen::DecodeShift(overflow.data(), overflow.size())); + } catch (const Codegen::CodegenException&) { rejected = true; } + require(rejected, "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 operation : kOperations) { + const auto bytes = operation == ShiftOperation::RotateRight ? rotate(true, 0, 6, 7) : shift(operation, true, 0, 6, 2); + const auto name = Codegen::ShiftName(operation); + require(!intel->Match(bytes.data(), bytes.size()), std::string("Intel mode rewrote ") + name); + const auto match = rosetta->Match(bytes.data(), bytes.size()); + require(match && match->InstructionName == name && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline && !match->Optional, std::string("Rosetta mode did not require a trampoline for ") + name); + const std::array, 2> sequence{bytes, bytes}; + require(rosetta->MatchSequence(sequence, {}).has_value(), std::string("A sequence could not move out of line for ") + name); + } +} + +#if LOWERING_EXECUTION_AVAILABLE + +using LoweringExecution::Execution; +using LoweringExecution::State; + +Execution executable(const Bytes& site, std::uint64_t memoryValue) { + const auto operands = Codegen::DecodeShift(site.data(), site.size()); + require(operands.has_value(), "Execution site was not a shift"); + return Execution(site, memoryValue, operands->Memory && operands->Memory->RipRelative, [](Codegen::StubBodyBuilder& body, const Bytes& relocated) { + Codegen::EmitShift(body, *Codegen::DecodeShift(relocated.data(), relocated.size())); + }, operands->Operation == ShiftOperation::RotateRight ? 1 : 0); +} + +std::uint64_t expected(const Codegen::ShiftOperands& operands, std::uint64_t source, std::uint64_t count) { + const unsigned bits = operands.Wide ? 64 : 32; + const auto mask = operands.Wide ? ~std::uint64_t{0} : 0xFFFFFFFFull; + source &= mask; + const unsigned amount = static_cast(count & (bits - 1)); + switch (operands.Operation) { + case ShiftOperation::Left: return (source << amount) & mask; + case ShiftOperation::LogicalRight: return source >> amount; + case ShiftOperation::ArithmeticRight: { + const bool negative = (source >> (bits - 1)) != 0; + const auto shifted = source >> amount; + return negative && amount != 0 ? (shifted | (mask << (bits - amount))) & mask : shifted; + } + case ShiftOperation::RotateRight: return amount == 0 ? source : ((source >> amount) | (source << (bits - amount))) & mask; + } + return 0; +} + +void check(const State& state, const Codegen::ShiftOperands& operands, std::uint64_t source, std::uint64_t count) { + const auto name = Codegen::ShiftName(operands.Operation); + const auto result = expected(operands, source, count); + for (std::size_t reg = 0; reg < 16; ++reg) { + const auto wanted = reg == operands.Destination ? result : state.Input[reg]; + if (state.Output[reg] != wanted) + throw std::runtime_error(std::string(name) + (operands.Wide ? " 64" : " 32") + " dst " + std::to_string(operands.Destination) + " src " + std::to_string(operands.Source) + " count " + std::to_string(operands.Count) + (operands.Memory ? " memory" : "") + ": register " + std::to_string(reg) + " is " + std::to_string(state.Output[reg]) + ", wanted " + std::to_string(wanted) + " (source " + std::to_string(source) + ", count value " + std::to_string(count) + ")"); + } + require((state.Flags & LoweringExecution::kFlagMask) == (state.InputFlags & LoweringExecution::kFlagMask), std::string(name) + " did not preserve the flags"); + for (const auto value : state.RedZone) require(value == LoweringExecution::kSentinel, std::string(name) + " overwrote the caller's red zone"); +} + +void run(const Execution& execution, const Codegen::ShiftOperands& operands, State state, std::uint64_t memoryValue) { + for (const auto flags : {LoweringExecution::kBaseFlags | LoweringExecution::kFlagMask, LoweringExecution::kBaseFlags}) { + state.InputFlags = flags; + auto trial = state; + execution.Run(trial); + const auto source = operands.Memory ? memoryValue : trial.Input[operands.Source]; + const auto count = operands.Operation == ShiftOperation::RotateRight ? operands.Count : trial.Input[operands.Count]; + check(trial, operands, source, count); + } +} + +void execution() { + constexpr std::array values{0, 1, 0x80000000ull, 0x8000000000000000ull, 0xFFFFFFFFFFFFFFFFull, 0xABCD123456789000ull}; + constexpr std::array counts{0, 1, 31, 32, 63, 64, 0xFF}; + for (const auto operation : kShifts) { + for (bool wide : {false, true}) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (const auto pair : std::array, 9>{{{6, 2}, {dst, 10}, {11, dst}, {dst, dst}, {4, 9}, {9, 4}, {1, 6}, {6, 1}, {1, 1}}}) { + const auto site = shift(operation, wide, dst, pair.first, pair.second); + const auto operands = *Codegen::DecodeShift(site.data(), site.size()); + const auto execution = executable(site, 0); + for (const auto value : values) { + for (const auto count : counts) { + auto state = LoweringExecution::InitialState(); + state.Input[pair.first] = value; + state.Input[pair.second] = count; + run(execution, operands, state, 0); + } + } + } + } + } + } + for (bool wide : {false, true}) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (const std::uint8_t src : std::array{6, dst, 4, 10}) { + const bool every = (dst == 0 && src == 6) || dst == src; + for (int value = 0; value < 256; ++value) { + const auto immediate = static_cast(value); + if (!every && immediate > 1 && immediate != 31 && immediate != 32 && immediate != 63 && immediate != 255) continue; + const auto site = rotate(wide, dst, src, immediate); + const auto operands = *Codegen::DecodeShift(site.data(), site.size()); + const auto execution = executable(site, 0); + for (const auto value : values) { + auto state = LoweringExecution::InitialState(); + state.Input[src] = value; + run(execution, operands, state, 0); + } + } + } + } + } + for (const auto operation : kOperations) { + for (bool wide : {false, true}) { + for (const Bytes tail : {Bytes{0x06}, Bytes{0x46, 0x10}, Bytes{0x84, 0x24, 0x40, 0, 0, 0}, Bytes{0x04, 0xCA}, Bytes{0x85, 0x10, 0, 0, 0}, Bytes{0x05, 0, 0, 0, 0}}) { + for (const auto value : std::array{0, 0xFEDCBA9876543210ull, 0xFFFFFFFF00000000ull}) { + const auto site = memorySite(operation, wide, tail, 13); + const auto operands = *Codegen::DecodeShift(site.data(), site.size()); + const auto execution = executable(site, value); + auto state = LoweringExecution::InitialState(); + state.Input[6] = reinterpret_cast(execution.Memory()) - (tail[0] == 0x46 ? 16 : 0); + state.Input[2] = reinterpret_cast(execution.Memory()) - 3 * 8; + state.Input[1] = 3; + state.Input[5] = reinterpret_cast(execution.Memory()) - 16; + state.Output[0] = value; + run(execution, operands, state, value); + } + } + } + } +} +#else +void execution() { std::cout << "shift execution checks require a Linux/macOS x86-64 build\n"; } +#endif + +} + +int main() { + try { + decoding(); + matching(); + execution(); + std::cout << "shift lowering checks passed\n"; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/relinker/domain/include/domain/ImportModule.hpp b/core/relinker/domain/include/domain/ImportModule.hpp index 18b018f770..2e25f56c3c 100644 --- a/core/relinker/domain/include/domain/ImportModule.hpp +++ b/core/relinker/domain/include/domain/ImportModule.hpp @@ -58,13 +58,19 @@ inline std::string ImportModule(const std::string& symbol, const std::map BuildEntryStub( std::uint64_t stubVaddr, - std::uint64_t realEntryVaddr + std::uint64_t realEntryVaddr, + std::optional mainStackEntrySlotVaddr ) const override; }; diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/IEntryStubBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/IEntryStubBuilder.hpp index 6a4799e93a..391ddb635a 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/IEntryStubBuilder.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/IEntryStubBuilder.hpp @@ -2,6 +2,7 @@ #define ELFPATCHER_DOMAIN_IENTRYSTUBBUILDER_HPP #include +#include #include namespace Elfpatcher { @@ -12,7 +13,8 @@ public: virtual std::vector BuildEntryStub( std::uint64_t stubVaddr, - std::uint64_t realEntryVaddr + std::uint64_t realEntryVaddr, + std::optional mainStackEntrySlotVaddr ) const = 0; }; diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/ProgramHeaderLayoutRequest.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/ProgramHeaderLayoutRequest.hpp index 956a162a77..ec0da1750a 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/general/ProgramHeaderLayoutRequest.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/general/ProgramHeaderLayoutRequest.hpp @@ -19,6 +19,7 @@ namespace Elfpatcher { std::uint64_t DynamicSegmentSize; std::uint64_t InterpOffset; std::uint64_t InterpSize; + std::uint64_t MovedLoadAlignment; }; } diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp index c9436e4915..fa3c5dc370 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp @@ -54,6 +54,7 @@ struct PeImport { std::string Library; bool Weak = false; bool Function = false; + bool Lazy = false; }; struct PeRelocations { diff --git a/core/relinker/elfpatcher/src/general/EntryStubBuilder.cpp b/core/relinker/elfpatcher/src/general/EntryStubBuilder.cpp index bf5924d9a4..e92b99aa0e 100644 --- a/core/relinker/elfpatcher/src/general/EntryStubBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/EntryStubBuilder.cpp @@ -15,26 +15,34 @@ void _appendBytes(std::vector& s, const std::uint8_t* bytes, std:: std::vector EntryStubBuilder::BuildEntryStub( const std::uint64_t stubVaddr, - const std::uint64_t realEntryVaddr + const std::uint64_t realEntryVaddr, + const std::optional mainStackEntrySlotVaddr ) const { std::vector s; + const auto appendRel32 = [&](const std::uint64_t target) { + const std::uint64_t nextVaddr = stubVaddr + s.size() + sizeof(std::int32_t); + std::int64_t displacement = 0; + if (!SignedDistance(target, nextVaddr, displacement) || displacement < std::numeric_limits::min() || displacement > std::numeric_limits::max()) + throw Domain::RelinkerException("Entry stub call exceeds rel32 range"); + const auto rel32 = static_cast(static_cast(displacement)); + for (std::size_t shift = 0; shift < 32; shift += 8) + s.push_back(static_cast((rel32 >> shift) & 0xff)); + }; _appendBytes(s, kStubOpMovRdiRsp, sizeof(kStubOpMovRdiRsp)); _appendBytes(s, kStubOpAndRsp0xf0, sizeof(kStubOpAndRsp0xf0)); _appendBytes(s, kStubOpPushZero, sizeof(kStubOpPushZero)); _appendBytes(s, kStubOpPushZero, sizeof(kStubOpPushZero)); _appendBytes(s, kStubOpMovRbpRsp, sizeof(kStubOpMovRbpRsp)); - _appendBytes(s, kStubOpXorRsiRsi, sizeof(kStubOpXorRsiRsi)); - const std::uint64_t callInsnVaddr = stubVaddr + s.size(); - const std::uint64_t callNextVaddr = callInsnVaddr + kStubCallInstructionSize; - std::int64_t displacement = 0; - if (!SignedDistance(realEntryVaddr, callNextVaddr, displacement) || displacement < std::numeric_limits::min() || displacement > std::numeric_limits::max()) - throw Domain::RelinkerException("Entry stub call exceeds rel32 range"); - const auto rel32 = static_cast(displacement); - s.push_back(kStubOpCallRel32); - s.push_back(static_cast(rel32 & 0xff)); - s.push_back(static_cast((rel32 >> 8) & 0xff)); - s.push_back(static_cast((rel32 >> 16) & 0xff)); - s.push_back(static_cast((rel32 >> 24) & 0xff)); + if (mainStackEntrySlotVaddr) { + _appendBytes(s, kStubOpLeaRsiRip, sizeof(kStubOpLeaRsiRip)); + appendRel32(realEntryVaddr); + _appendBytes(s, kStubOpCallRipIndirect, sizeof(kStubOpCallRipIndirect)); + appendRel32(*mainStackEntrySlotVaddr); + } else { + _appendBytes(s, kStubOpXorRsiRsi, sizeof(kStubOpXorRsiRsi)); + s.push_back(kStubOpCallRel32); + appendRel32(realEntryVaddr); + } _appendBytes(s, kStubOpUd2, sizeof(kStubOpUd2)); return s; } diff --git a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp index a367752246..6521ade295 100644 --- a/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp +++ b/core/relinker/elfpatcher/src/general/ProgramHeaderLayoutBuilder.cpp @@ -229,6 +229,8 @@ std::uint16_t ProgramHeaderLayoutBuilder::WriteLayout( if (request.ExtraBlockOffset > buf.size() || request.ExtraBlockSize > buf.size() - request.ExtraBlockOffset) throw Domain::RelinkerException("Extra block exceeds the output file"); std::uint64_t headerBlockAlign = kDefaultLoadAlignment; + if (request.MovedLoadAlignment > headerBlockAlign && (request.MovedLoadAlignment & (request.MovedLoadAlignment - 1)) == 0) + headerBlockAlign = request.MovedLoadAlignment; for (const auto& ph : request.OriginalHeaders) { if (ph.Type == PT_LOAD && !_segmentFilter->ShouldSkip(ph) && ph.Alignment > headerBlockAlign && (ph.Alignment & (ph.Alignment - 1)) == 0) headerBlockAlign = ph.Alignment; diff --git a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp index ec5e95cd3a..c52b905dca 100644 --- a/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp +++ b/core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp @@ -125,7 +125,7 @@ std::uint64_t RelocationWidth(const std::vector& relocations, cons return type == R_X86_64_PC32 || type == R_X86_64_32 ? 4 : 8; } -void SeparateSharedLoadPages(std::vector& buf, std::vector& headers) { +std::uint64_t SeparateSharedLoadPages(std::vector& buf, std::vector& headers) { std::vector loads; for (std::size_t index = 0; index < headers.size(); ++index) { if (headers[index].Type == PT_LOAD && headers[index].MemorySize != 0) { @@ -174,10 +174,12 @@ void SeparateSharedLoadPages(std::vector& buf, std::vector previous.MemorySize) previous.FileSize = previous.MemorySize; } + std::uint64_t lowered = 0; for (std::size_t position = 0; position < loads.size(); ++position) { auto& load = headers[loads[position]]; const auto prefix = load.MappedAddress % kDefaultLoadAlignment; - if (prefix == 0 || load.FileSize == 0 || load.Offset % kDefaultLoadAlignment != prefix) + const bool congruent = load.Offset % kDefaultLoadAlignment == prefix; + if (load.FileSize == 0 || (congruent && prefix == 0)) continue; const auto pageStart = load.MappedAddress - prefix; std::vector image(static_cast(prefix), 0); @@ -192,20 +194,22 @@ void SeparateSharedLoadPages(std::vector& buf, std::vector(mapped) + index] != image[index]; if (!differs) continue; - const auto alignment = std::max(load.Alignment, kDefaultLoadAlignment); - while ((buf.size() + prefix) % alignment != load.MappedAddress % alignment) - buf.push_back(0); + Io::AlignBuffer(buf, static_cast(kDefaultLoadAlignment)); buf.insert(buf.end(), image.begin(), image.end()); const auto offset = static_cast(buf.size()); buf.resize(static_cast(offset + load.FileSize)); std::copy_n(buf.begin() + static_cast(load.Offset), static_cast(load.FileSize), buf.begin() + static_cast(offset)); load.Offset = offset; + if (load.Alignment > kDefaultLoadAlignment && (load.Alignment & (load.Alignment - 1)) == 0) + lowered = std::max(lowered, load.Alignment); + load.Alignment = std::min(load.Alignment, kDefaultLoadAlignment); } + return lowered; } struct TracedImport { @@ -293,7 +297,7 @@ Domain::SysVDynamicSection ImportsByNid(Domain::SysVDynamicSection dynSection) { 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 TracePrologue(const std::uint64_t vaddr, const std::uint64_t pages, 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) { code.insert(code.end(), opcode); @@ -302,14 +306,20 @@ std::vector TracePrologue(const std::uint64_t vaddr, const std::ui throw Domain::RelinkerException("Import trace table exceeds rel32 range"); Io::AppendU32(code, static_cast(displacement)); }; - rip({0x48, 0x8d, 0x1d}, table); + rip({0x48, 0x8d, 0x1d}, pages); + rip({0x4c, 0x8d, 0x25}, table); + const auto pageLoop = code.size(); + code.insert(code.end(), {0x4c, 0x39, 0xe3, 0x73, 0x00}); + const auto pageExit = code.size(); + code.insert(code.end(), {0x48, 0x8b, 0x3b, 0x48, 0x01, 0xdf, 0xbe, 0x00, 0x10, 0x00, 0x00, 0x8b, 0x53, 0x08, 0xb8, 0x0a, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0x83, 0xc3, 0x10, 0xeb}); + code.push_back(static_cast(static_cast(static_cast(pageLoop) - static_cast(code.size() + 1)))); + code[pageExit - 1] = static_cast(code.size() - pageExit); rip({0x4c, 0x8d, 0x25}, tableEnd); const auto loop = code.size(); code.insert(code.end(), {0x4c, 0x39, 0xe3, 0x73, 0x00}); const auto exit = code.size(); code.insert(code.end(), {0x48, 0x8b, 0x03, 0x48, 0x8b, 0x3c, 0x03, 0x48, 0x8b, 0x73, 0x08, 0x48, 0x01, 0xde, 0x48, 0x8b, 0x53, 0x10, 0x48, 0x01, 0xda}); rip({0xff, 0x15}, function); - code.insert(code.end(), {0x48, 0x8b, 0x3b, 0x48, 0x01, 0xdf, 0x48, 0x81, 0xe7, 0x00, 0xf0, 0xff, 0xff, 0xbe, 0x00, 0x10, 0x00, 0x00, 0xba, 0x07, 0x00, 0x00, 0x00, 0x50, 0xb8, 0x0a, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x58}); code.insert(code.end(), {0x48, 0x8b, 0x0b, 0x48, 0x89, 0x04, 0x0b, 0x48, 0x83, 0xc3, 0x18, 0xeb}); code.push_back(static_cast(static_cast(static_cast(loop) - static_cast(code.size() + 1)))); code[exit - 1] = static_cast(code.size() - exit); @@ -317,6 +327,11 @@ std::vector TracePrologue(const std::uint64_t vaddr, const std::ui return code; } +void RequireBaseAlignment(const std::uint64_t base, const std::uint64_t alignment) { + if (alignment > 1 && base % alignment != 0) + throw Domain::RelinkerException("Fixed Linux base " + Hex(base) + " is not a multiple of the PT_LOAD alignment " + Hex(alignment)); +} + void RebaseProgramHeaders(std::vector& buf, const std::uint64_t tableOffset, const std::uint16_t entrySize, const std::uint16_t count, const std::uint64_t base) { for (std::uint16_t index = 0; index < count; ++index) { const auto header = static_cast(tableOffset + static_cast(index) * entrySize); @@ -324,16 +339,13 @@ void RebaseProgramHeaders(std::vector& buf, const std::uint64_t ta if (type == PT_NULL || type == PT_GNU_STACK) continue; const auto address = Rebased(base, Io::ReadU64(buf, header + kPhdrVaddrOffset), "Program header address"); - const auto physical = Rebased(base, Io::ReadU64(buf, header + kPhdrPaddrOffset), "Program header physical address"); if (type == PT_LOAD) { - const auto alignment = Io::ReadU64(buf, header + kPhdrAlignOffset); - if (alignment > 1 && base % alignment != 0) - throw Domain::RelinkerException("Fixed Linux base " + Hex(base) + " is not a multiple of the PT_LOAD alignment " + Hex(alignment)); + RequireBaseAlignment(base, Io::ReadU64(buf, header + kPhdrAlignOffset)); if (Io::ReadU64(buf, header + kPhdrMemSizeOffset) > kLinuxUserAddressEnd - address) throw Domain::RelinkerException("PT_LOAD at " + Hex(address) + " ends outside the Linux user address space"); } Io::WriteU64(buf, header + kPhdrVaddrOffset, address); - Io::WriteU64(buf, header + kPhdrPaddrOffset, physical); + Io::WriteU64(buf, header + kPhdrPaddrOffset, address); } } @@ -456,9 +468,31 @@ std::vector LinuxElfPatcher::Patch( b.push_back(0); }; + const std::uint64_t extraBlockOff = static_cast(buf.size()); + const std::uint64_t extraBlockVaddr = _programHeaderLayoutBuilder->ComputeExtraBlockVaddr(originalHeaders, extraBlockOff); + + const auto vaddrOfExtraBlockOffset = [extraBlockOff, extraBlockVaddr](std::uint64_t fileOffset) -> std::uint64_t { + if (fileOffset < extraBlockOff) + throw Domain::RelinkerException("Extra block member offset lies before the extra block"); + return extraBlockVaddr + (fileOffset - extraBlockOff); + }; + + const auto& neededLibraries = dynSection.NeededLibraries; + const bool mainStackEntry = std::find(neededLibraries.begin(), neededLibraries.end(), kGuestMainStackEntryLibrary) != neededLibraries.end(); + std::optional mainStackSlotOff; + if (mainStackEntry) { + alignBuf(buf, sizeof(std::uint64_t)); + mainStackSlotOff = static_cast(buf.size()); + buf.resize(buf.size() + sizeof(std::uint64_t), 0); + } + const auto dynStrOff = static_cast(buf.size()); for (std::uint8_t b : dynSection.DynStrData) buf.push_back(b); + const std::uint64_t mainStackNameOff = static_cast(buf.size()) - dynStrOff; + if (mainStackEntry) + for (char c : kGuestMainStackEntrySymbol) + buf.push_back(static_cast(c)); const std::uint64_t runPathStrOff = static_cast(buf.size()) - dynStrOff; for (char c : runPath) buf.push_back(static_cast(c)); @@ -483,6 +517,17 @@ std::vector LinuxElfPatcher::Patch( Io::AppendU64(symbols, 0); Io::AppendU64(symbols, 0); } + std::uint64_t mainStackSymbol = 0; + if (mainStackEntry) { + RequireWholeEntries(symbols, kSymEntrySize, "Dynamic symbol table"); + if (symbols.empty()) + symbols.resize(kSymEntrySize, 0); + mainStackSymbol = symbols.size() / kSymEntrySize; + const auto mainStackSymbolOff = symbols.size(); + symbols.resize(symbols.size() + kSymEntrySize, 0); + Io::WriteU32(symbols, mainStackSymbolOff, static_cast(mainStackNameOff)); + symbols[mainStackSymbolOff + 4] = static_cast((STB_GLOBAL << 4) | STT_FUNC); + } for (std::uint8_t b : symbols) buf.push_back(b); alignBuf(buf, kDynSymAlignment); @@ -504,6 +549,12 @@ std::vector LinuxElfPatcher::Patch( Io::AppendU64(relocations, (static_cast(traceSymbol) << 32) | R_X86_64_GLOB_DAT); Io::AppendU64(relocations, 0); } + if (mainStackSlotOff) { + const auto mainStackRelocationOff = relocations.size(); + relocations.resize(relocations.size() + kRelaEntrySize, 0); + Io::WriteU64(relocations, mainStackRelocationOff, address(vaddrOfExtraBlockOffset(*mainStackSlotOff))); + Io::WriteU64(relocations, mainStackRelocationOff + 8, (mainStackSymbol << 32) | R_X86_64_GLOB_DAT); + } for (std::uint8_t b : relocations) buf.push_back(b); alignBuf(buf, kRelaAlignment); @@ -514,15 +565,6 @@ std::vector LinuxElfPatcher::Patch( buf.push_back(b); alignBuf(buf, kRelaPltAlignment); - const std::uint64_t extraBlockOff = dynStrOff; - const std::uint64_t extraBlockVaddr = _programHeaderLayoutBuilder->ComputeExtraBlockVaddr(originalHeaders, extraBlockOff); - - const auto vaddrOfExtraBlockOffset = [extraBlockOff, extraBlockVaddr](std::uint64_t fileOffset) -> std::uint64_t { - if (fileOffset < extraBlockOff) - throw Domain::RelinkerException("Extra block member offset lies before the extra block"); - return extraBlockVaddr + (fileOffset - extraBlockOff); - }; - std::vector dynSegBuf; for (std::uint8_t b : dynSection.DynamicSegmentData) dynSegBuf.push_back(b); @@ -553,8 +595,10 @@ std::vector LinuxElfPatcher::Patch( buf.push_back(b); std::uint64_t traceFunctionVaddr = 0; + std::uint64_t tracePagesVaddr = 0; std::uint64_t traceTableVaddr = 0; std::uint64_t traceTableEndVaddr = 0; + std::map tracePages; std::vector traceEntries; if (_traceImports) { alignBuf(buf, 8); @@ -562,6 +606,16 @@ std::vector LinuxElfPatcher::Patch( traceFunctionVaddr = vaddrOfExtraBlockOffset(functionOff); Io::AppendU64(buf, 0); Io::WriteU64(buf, static_cast(relaOff + traceRelocation), address(traceFunctionVaddr)); + tracePagesVaddr = vaddrOfExtraBlockOffset(buf.size()); + for (const auto& import : traced) { + tracePages.emplace(import.Slot & ~(kDefaultLoadAlignment - 1), 0); + tracePages.emplace((import.Slot + kPltGotEntryBytes - 1) & ~(kDefaultLoadAlignment - 1), 0); + } + for (auto& [page, offset] : tracePages) { + offset = buf.size(); + buf.resize(buf.size() + 16); + Io::WriteU64(buf, offset, page - vaddrOfExtraBlockOffset(offset)); + } traceTableVaddr = vaddrOfExtraBlockOffset(buf.size()); for (std::size_t index = 0; index < traced.size(); ++index) { traceEntries.push_back(buf.size()); @@ -591,11 +645,14 @@ std::vector LinuxElfPatcher::Patch( const auto stubOff = static_cast(buf.size()); const auto stubVaddr = vaddrOfExtraBlockOffset(stubOff); if (_traceImports) { - for (std::uint8_t b : TracePrologue(stubVaddr, traceTableVaddr, traceTableEndVaddr, traceFunctionVaddr)) + for (std::uint8_t b : TracePrologue(stubVaddr, tracePagesVaddr, traceTableVaddr, traceTableEndVaddr, traceFunctionVaddr)) buf.push_back(b); } const auto entryStubVaddr = vaddrOfExtraBlockOffset(buf.size()); - for (std::uint8_t b : _entryStubBuilder->BuildEntryStub(address(entryStubVaddr), address(realEntryVaddr))) + std::optional mainStackSlotVaddr; + if (mainStackSlotOff) + mainStackSlotVaddr = address(vaddrOfExtraBlockOffset(*mainStackSlotOff)); + for (std::uint8_t b : _entryStubBuilder->BuildEntryStub(address(entryStubVaddr), address(realEntryVaddr), mainStackSlotVaddr)) buf.push_back(b); const auto stubSize = static_cast(buf.size()) - stubOff; _byteWriter->WriteU64(buf, kEhdrEntryOffset, address(stubVaddr)); @@ -622,7 +679,11 @@ std::vector LinuxElfPatcher::Patch( const std::uint64_t extraBlockSize = static_cast(buf.size()) - extraBlockOff; auto headers = originalHeaders; - SeparateSharedLoadPages(buf, headers); + if (_fixedBase) + for (const auto& header : headers) + if (header.Type == PT_LOAD) + RequireBaseAlignment(*_fixedBase, header.Alignment); + const auto movedLoadAlignment = SeparateSharedLoadPages(buf, headers); ProgramHeaderLayoutRequest layoutRequest{}; layoutRequest.PhOff = tableOff; @@ -636,12 +697,40 @@ std::vector LinuxElfPatcher::Patch( layoutRequest.DynamicSegmentSize = dynSegBuf.size(); layoutRequest.InterpOffset = interpOff; layoutRequest.InterpSize = interpSize; + layoutRequest.MovedLoadAlignment = movedLoadAlignment; const std::uint16_t writtenPh = _programHeaderLayoutBuilder->WriteLayout(buf, layoutRequest); _byteWriter->WriteU16(buf, kEhdrPhNumOffset, writtenPh); if (_fixedBase) RebaseProgramHeaders(buf, tableOff, phEntSize, writtenPh, base); + std::uint64_t relroStart = 0; + std::uint64_t relroEnd = 0; + for (std::uint16_t index = 0; index < writtenPh; ++index) { + const auto header = static_cast(tableOff + static_cast(index) * phEntSize); + if (Io::ReadU32(buf, header + kPhdrTypeOffset) != PT_GNU_RELRO) + continue; + const auto start = Io::ReadU64(buf, header + kPhdrVaddrOffset); + const auto size = Io::ReadU64(buf, header + kPhdrMemSizeOffset); + relroStart = start & ~(kDefaultLoadAlignment - 1); + relroEnd = (start + size) & ~(kDefaultLoadAlignment - 1); + } + for (const auto& [page, offset] : tracePages) { + std::uint32_t flags = 0; + for (std::uint16_t index = 0; index < writtenPh; ++index) { + const auto header = static_cast(tableOff + static_cast(index) * phEntSize); + if (Io::ReadU32(buf, header + kPhdrTypeOffset) != PT_LOAD) + continue; + const auto start = Io::ReadU64(buf, header + kPhdrVaddrOffset); + const auto size = Io::ReadU64(buf, header + kPhdrMemSizeOffset); + if (size != 0 && address(page) >= (start & ~(kDefaultLoadAlignment - 1)) && address(page) < start + size) + flags = Io::ReadU32(buf, header + kPhdrFlagsOffset); + } + if (address(page) >= relroStart && address(page) < relroEnd) + flags &= ~PF_X; + Io::WriteU32(buf, offset + 8, 3 | ((flags & PF_X) != 0 ? 4 : 0)); + } + SectionHeaderTableRequest sectionRequest{}; sectionRequest.DynStrOffset = dynStrOff; sectionRequest.DynStrSize = dynStrSize; diff --git a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp index ab4dfbdd46..d349044c2b 100644 --- a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp +++ b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp @@ -64,6 +64,17 @@ std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestIma needsTlsResolver = true; } } + for (std::size_t index = 1; index < image.Symbols.size(); ++index) { + const auto& symbol = image.Symbols[index]; + if (symbol.Value != 0 || symbol.Section == 0 || symbol.Section >= 0xff00 || (symbol.Info >> 4) == 0 || + (symbol.Info & 15) != 2 || symbol.Visibility == 1 || symbol.Visibility == 2) continue; + const auto thunk = address(); + if (thunk > static_cast(std::numeric_limits::max()) - 5) + throw Domain::RelinkerException("Guest export at address 0 exceeds relative branch range"); + bytes.push_back(0xe9); + Io::AppendU32(bytes, static_cast(-static_cast(thunk + 5))); + Io::WriteU64(symbols, index * 24 + 8, thunk); + } std::vector needed; for (const auto& dependency : dependencies) needed.push_back(addString(dependency)); if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2")); diff --git a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp index b2545510c1..fd1abf4d68 100644 --- a/core/relinker/elfpatcher/src/macos/MacOsImage.cpp +++ b/core/relinker/elfpatcher/src/macos/MacOsImage.cpp @@ -265,6 +265,7 @@ void AppendPathCommand(std::vector& out, const std::uint32_t cmd, struct GsTrap { std::uint32_t Rva; std::size_t Length; + std::optional Entry; }; struct TlsAccess { @@ -291,7 +292,8 @@ struct TlsScan { TlsScan ScanTls(const std::vector& source, const std::vector& headers, const Windows::WindowsLoadImage& image, const std::function& message) { TlsScan scan; const Codegen::X64InstructionDecoder decoder; - const auto instructions = Relinker::CodeInstructionCollector().Collect(source, headers, {}, nullptr, message); + std::set entryRoots; + const auto instructions = Relinker::CodeInstructionCollector().Collect(source, headers, {}, &entryRoots, message); for (const auto& header : headers) { if (header.Type == 7) { if (scan.Tls != nullptr) throw Domain::RelinkerException("Multiple ELF TLS segments"); @@ -299,7 +301,7 @@ TlsScan ScanTls(const std::vector& source, const std::vector& source, const std::vector(header.Offset + *entry - header.MappedAddress) : std::nullopt}); + } continue; } const auto position = info.OpcodeOffset; @@ -335,6 +339,9 @@ TlsScan ScanTls(const std::vector& source, const std::vector WriteMacOsImage(MacOsImageInput& input) { 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 (trap.Entry) + throw Domain::RelinkerException("GS trap would overwrite an executable entry point", *trap.Entry); 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 d9be16b069..a8cc3bb237 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp @@ -498,6 +498,34 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con }; code.Emit({0x53, 0x55, 0x56, 0x57, 0x41, 0x54, 0x48, 0x83, 0xec, 0x60}); + code.Emit({0x31, 0xc9}); + code.Rip({0x48, 0x8d, 0x15}, programPath); + code.Emit({0x41, 0xb8}); + code.U32(PathCapacity); + call("GetModuleFileNameW"); + requireNonzero(0, 0xc000000du); + code.Emit({0x3d}); + code.U32(PathCapacity); + const auto pathFits = code.Branch({0x0f, 0x82}); + fail(1, 0xc0000106u); + code.PatchBranch(pathFits, code.GetRva()); + code.Emit({0x89, 0xc1}); + code.Rip({0x48, 0x8d, 0x35}, programPath); + code.Rip({0x48, 0x8d, 0x3d}, modulePath); + code.Emit({0xfc, 0x66, 0xf3, 0xa5, 0x49, 0x89, 0xfc}); + code.Rip({0x48, 0x8d, 0x1d}, modulePath); + const auto findSeparator = code.GetRva(); + code.Emit({0x49, 0x39, 0xdc}); + const auto hasDirectory = code.Branch({0x0f, 0x85}); + fail(2, 0xc000000du); + code.PatchBranch(hasDirectory, code.GetRva()); + code.Emit({0x49, 0x83, 0xec, 0x02, 0x66, 0x41, 0x83, 0x3c, 0x24, 0x5c}); + code.Rip({0x0f, 0x85}, findSeparator); + code.Emit({0x49, 0x83, 0xc4, 0x02}); + code.Emit({0x66, 0x41, 0xc7, 0x04, 0x24, 0x00, 0x00}); + logMissingHandle(0xfffffff5u); + logMissingHandle(0xfffffff4u); + code.Rip({0x48, 0x8d, 0x0d}, parentVariable); code.Rip({0x48, 0x8d, 0x15}, parentText); code.Emit({0x41, 0xb8, 17, 0, 0, 0}); @@ -541,33 +569,6 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con requireNonzero(parentError, 1); code.PatchBranch(noParent, code.GetRva()); - code.Emit({0x31, 0xc9}); - code.Rip({0x48, 0x8d, 0x15}, programPath); - code.Emit({0x41, 0xb8}); - code.U32(PathCapacity); - call("GetModuleFileNameW"); - requireNonzero(0, 0xc000000du); - code.Emit({0x3d}); - code.U32(PathCapacity); - const auto pathFits = code.Branch({0x0f, 0x82}); - fail(1, 0xc0000106u); - code.PatchBranch(pathFits, code.GetRva()); - code.Emit({0x89, 0xc1}); - code.Rip({0x48, 0x8d, 0x35}, programPath); - code.Rip({0x48, 0x8d, 0x3d}, modulePath); - code.Emit({0xfc, 0x66, 0xf3, 0xa5, 0x49, 0x89, 0xfc}); - code.Rip({0x48, 0x8d, 0x1d}, modulePath); - const auto findSeparator = code.GetRva(); - code.Emit({0x49, 0x39, 0xdc}); - const auto hasDirectory = code.Branch({0x0f, 0x85}); - fail(2, 0xc000000du); - code.PatchBranch(hasDirectory, code.GetRva()); - code.Emit({0x49, 0x83, 0xec, 0x02, 0x66, 0x41, 0x83, 0x3c, 0x24, 0x5c}); - code.Rip({0x0f, 0x85}, findSeparator); - code.Emit({0x49, 0x83, 0xc4, 0x02}); - code.Emit({0x66, 0x41, 0xc7, 0x04, 0x24, 0x00, 0x00}); - logMissingHandle(0xfffffff5u); - logMissingHandle(0xfffffff4u); code.Rip({0x48, 0x8d, 0x0d}, modulePath); call("SetCurrentDirectoryW"); requireNonzero(directoryError, 0xc000000du); @@ -756,7 +757,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con code.PatchBranch(weakDone, code.GetRva()); continue; } - if (lazyBinding) { + if (lazyBinding && imports[index].Lazy) { lazyUnresolvedImports.push_back(index); const auto skipGotWrite = code.Branch({0xe9}); patchResolved(); diff --git a/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp index 7a3794848e..ca987359d7 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp @@ -15,7 +15,6 @@ namespace Elfpatcher { std::vector GuestModuleWriter::WriteWindows(const Relinker::GuestImage& guest, Domain::GuestRuntime& runtime, const std::function& message) const { using namespace Windows; - if (guest.Symbolic) throw Domain::RelinkerException("DF_SYMBOLIC guest modules are not supported by the Windows writer"); WindowsLoadImage image(guest.Bytes, guest.Headers, false); std::vector relocations; std::vector> tlsModules; diff --git a/core/relinker/elfpatcher/src/windows/WindowsIconResourceBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsIconResourceBuilder.cpp index 4a547373bd..a075e442a7 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsIconResourceBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsIconResourceBuilder.cpp @@ -81,7 +81,7 @@ PeDirectory WindowsIconResourceBuilder::Build(const std::vector& p throw Domain::RelinkerException("Invalid Windows resource section RVA", nextRva); if (std::any_of(sections.begin(), sections.end(), [](const auto& section) { return section.Name == ".rsrc"; })) throw Domain::RelinkerException("Duplicate Windows resource section"); - if (!sections.empty() && nextRva < AlignRva(static_cast(sections.back().Rva) + sections.back().Data.size())) + if (!sections.empty() && nextRva < AlignRva(static_cast(sections.back().Rva) + sections.back().MemorySize())) throw Domain::RelinkerException("Windows resource section overlaps preceding section", nextRva); if (png.size() > 0x7fffffffu - 184) throw Domain::RelinkerException("Invalid Windows icon size"); validatePng(png, iconPath); diff --git a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp index 566dc2f13e..3512669e70 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsRelocationBuilder.cpp @@ -79,6 +79,7 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom result.Imports.push_back({name, rva, addend}); result.Imports.back().Weak = (dynamicSection.DynSymData[symbolOffset + 4] >> 4) == STB_WEAK; result.Imports.back().Function = type == 7 || (type == 6 && dynamicSection.FunctionImports.contains(target)); + result.Imports.back().Lazy = type == 7 || (dynamicSection.DynSymData[symbolOffset + 4] & 15) == STT_FUNC; if (const auto module = dynamicSection.ImportModules.find(target); module != dynamicSection.ImportModules.end()) result.Imports.back().Library = module->second; } diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp index 722584997a..a7f117644f 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp @@ -134,7 +134,7 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co if (header.Type != 1 || (header.Flags & 1) == 0) continue; std::uint64_t decodedEnd = 0; - for (auto instruction = instructions.lower_bound(header.MappedAddress); instruction != instructions.end() && *instruction - header.MappedAddress < header.FileSize; ++instruction) { + for (auto instruction = std::lower_bound(instructions.begin(), instructions.end(), header.MappedAddress); instruction != instructions.end() && *instruction - header.MappedAddress < header.FileSize; ++instruction) { if (scanned % 4096 == 0) report(7000 + 2000 * scanned / std::max(instructions.size(), 1)); ++scanned; const auto offset = *instruction - header.MappedAddress; diff --git a/core/relinker/elfpatcher/tests/EntryStubTests.cpp b/core/relinker/elfpatcher/tests/EntryStubTests.cpp index 282e8ff25c..80a4e7761b 100644 --- a/core/relinker/elfpatcher/tests/EntryStubTests.cpp +++ b/core/relinker/elfpatcher/tests/EntryStubTests.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -23,10 +24,33 @@ bool bytesAt(const std::vector& stub, std::size_t& offset, const s return true; } +void stubEntersThroughTheMainStackSlot() { + constexpr std::uint64_t stubVaddr = 0xb32db98; + constexpr std::uint64_t entryVaddr = 0x80; + constexpr std::uint64_t slotVaddr = 0xb32d000; + const auto stub = Elfpatcher::EntryStubBuilder().BuildEntryStub(stubVaddr, entryVaddr, slotVaddr); + std::size_t offset = 0; + const auto ripTarget = [&](const std::vector& opcode, const std::string& what) { + require(bytesAt(stub, offset, opcode), "the stub does not " + what); + std::int32_t rel32 = 0; + require(offset + sizeof(rel32) <= stub.size(), "the stub ends inside a displacement"); + std::memcpy(&rel32, stub.data() + offset, sizeof(rel32)); + offset += sizeof(rel32); + return stubVaddr + offset + static_cast(rel32); + }; + require(bytesAt(stub, offset, {0x48, 0x89, 0xe7}), "the stub does not pass the initial stack pointer in rdi"); + require(bytesAt(stub, offset, {0x48, 0x83, 0xe4, 0xf0}), "the stub does not align the stack to 16 bytes"); + require(bytesAt(stub, offset, {0x6a, 0x00, 0x6a, 0x00}), "the stub does not push a zero frame record"); + require(bytesAt(stub, offset, {0x48, 0x89, 0xe5}), "the stub does not point rbp at the zero frame record"); + require(ripTarget({0x48, 0x8d, 0x35}, "load the entry point into rsi") == entryVaddr, "the stub passes the wrong entry point"); + require(ripTarget({0xff, 0x15}, "call through the main stack entry slot") == slotVaddr, "the stub calls through the wrong slot"); + require(bytesAt(stub, offset, {0x0f, 0x0b}) && offset == stub.size(), "the stub does not end with ud2 after the call"); +} + void stubTerminatesTheFrameChain() { constexpr std::uint64_t stubVaddr = 0xb32db98; constexpr std::uint64_t entryVaddr = 0x80; - const auto stub = Elfpatcher::EntryStubBuilder().BuildEntryStub(stubVaddr, entryVaddr); + const auto stub = Elfpatcher::EntryStubBuilder().BuildEntryStub(stubVaddr, entryVaddr, std::nullopt); std::size_t offset = 0; require(bytesAt(stub, offset, {0x48, 0x89, 0xe7}), "the stub does not pass the initial stack pointer in rdi"); require(bytesAt(stub, offset, {0x48, 0x83, 0xe4, 0xf0}), "the stub does not align the stack to 16 bytes"); @@ -118,6 +142,16 @@ void windowsStubRejectsAnOverlongAllocatorPath() { throw std::runtime_error("an allocator path past the startup buffer was accepted"); } +void windowsStubWaitsForTheParentAfterOpeningTheLog(const std::string& runPath) { + const auto nativeImports = Elfpatcher::Windows::WindowsImportBuilder().Build(0x1000); + const auto stub = windowsStub(nativeImports, runPath, {"libc.prx"}); + const auto log = ripReference(stub.Code, {0xff, 0x15}, nativeImports.Functions.at("CreateFileW"), 0); + require(log != std::string::npos, runPath + ": the stub does not open anyps5.log"); + const auto parent = ripReference(stub.Code, {0xff, 0x15}, nativeImports.Functions.at("GetEnvironmentVariableW"), 0); + require(parent != std::string::npos, runPath + ": the stub does not read ANYPS5_LOADEXEC_PARENT"); + require(log < parent, runPath + ": the stub reads ANYPS5_LOADEXEC_PARENT before anyps5.log is opened"); +} + void windowsLibSceAmprObjectImportsFallBackToTheLibcProvider() { using namespace Elfpatcher::Windows; const auto nativeImports = WindowsImportBuilder().Build(0x1000); @@ -164,7 +198,7 @@ void windowsKernelVariantDependenciesLoadCanonicalKernel() { void stubRejectsAnEntryBeyondTheWrapBoundary() { bool threw = false; try { - static_cast(Elfpatcher::EntryStubBuilder().BuildEntryStub(0x1000, 0xFFFFFFFFFFFFF000)); + static_cast(Elfpatcher::EntryStubBuilder().BuildEntryStub(0x1000, 0xFFFFFFFFFFFFF000, std::nullopt)); } catch (const Domain::RelinkerException& error) { threw = std::string(error.what()).find("rel32") != std::string::npos; } @@ -175,6 +209,7 @@ void stubRejectsAnEntryBeyondTheWrapBoundary() { int main() { try { + stubEntersThroughTheMainStackSlot(); stubTerminatesTheFrameChain(); stubRejectsAnEntryBeyondTheWrapBoundary(); windowsStubLoadsTheAllocatorFirst("C:/Games/Title/libs", u"C:\\Games\\Title\\libs\\", {0x48, 0x8d, 0x0d}); @@ -182,6 +217,8 @@ int main() { windowsStubLoadsTheAllocatorOnRequest("C:/Games/Title/libs", u"C:\\Games\\Title\\libs\\", {0x48, 0x8d, 0x0d}); windowsStubLoadsTheAllocatorOnRequest("$ORIGIN/libs", u"libs\\", {0x48, 0x8d, 0x35}); windowsStubRejectsAnOverlongAllocatorPath(); + windowsStubWaitsForTheParentAfterOpeningTheLog("C:/Games/Title/libs"); + windowsStubWaitsForTheParentAfterOpeningTheLog("$ORIGIN/libs"); windowsLibSceAmprObjectImportsFallBackToTheLibcProvider(); windowsKernelVariantImportsSearchCanonicalKernelAfterTheRequestedVariant(); windowsKernelVariantDependenciesLoadCanonicalKernel(); diff --git a/core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp b/core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp index b99a198578..607dfc221f 100644 --- a/core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp +++ b/core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp @@ -61,3 +61,8 @@ extern "C" void __aps5_set_module_init_result_nid_no_patch(int result) { std::snprintf(line, sizeof(line), "result=%d\n", result); Write(line); } + +extern "C" [[noreturn]] void APS5_VABI EnterGuestMainStack_nid_no_patch(void* arguments, void (APS5_VABI* entry)(void*)) { + entry(arguments); + Fail("The guest entry point returned"); +} diff --git a/core/relinker/elfpatcher/tests/LinuxLoadPageTests.cpp b/core/relinker/elfpatcher/tests/LinuxLoadPageTests.cpp index 66b2b7138e..1a56c81395 100644 --- a/core/relinker/elfpatcher/tests/LinuxLoadPageTests.cpp +++ b/core/relinker/elfpatcher/tests/LinuxLoadPageTests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -52,17 +53,18 @@ Bytes Fixture(const std::size_t size) { return bytes; } -Domain::ProgramHeader Load(const std::uint64_t offset, const std::uint64_t vaddr, const std::uint64_t fileSize, const std::uint64_t memorySize, const std::uint32_t flags) { - return {1, flags, offset, vaddr, vaddr, fileSize, memorySize, 0x1000}; +Domain::ProgramHeader Load(const std::uint64_t offset, const std::uint64_t vaddr, const std::uint64_t fileSize, const std::uint64_t memorySize, const std::uint32_t flags, const std::uint64_t alignment = kPage) { + return {1, flags, offset, vaddr, vaddr, fileSize, memorySize, alignment}; } -Elfpatcher::Linux::LinuxElfPatcher Patcher() { +Elfpatcher::Linux::LinuxElfPatcher Patcher(const std::optional fixedBase = std::nullopt) { const auto byteWriter = std::make_shared(); return Elfpatcher::Linux::LinuxElfPatcher( std::make_shared(), std::make_shared(std::make_shared(), byteWriter), std::make_shared(byteWriter), - byteWriter); + byteWriter, + fixedBase); } std::vector OutputLoads(const Bytes& output) { @@ -121,6 +123,22 @@ std::uint64_t OutputOffset(const Bytes& output, const std::uint64_t vaddr) { throw std::runtime_error("linux load pages: no output PT_LOAD at the segment's address"); } +std::uint64_t OutputAlignment(const Bytes& output, const std::uint64_t vaddr) { + for (const auto& load : OutputLoads(output)) + if (load.MappedAddress == vaddr) return load.Alignment; + throw std::runtime_error("linux load pages: no output PT_LOAD at the segment's address"); +} + +std::uint64_t MaximumAlignment(const Bytes& output) { + std::uint64_t largest = kPage; + for (const auto& load : OutputLoads(output)) { + Require(load.Alignment <= 1 || load.Offset % load.Alignment == load.MappedAddress % load.Alignment, "PT_LOAD offset and address differ modulo its alignment"); + if (load.Alignment != 0 && (load.Alignment & (load.Alignment - 1)) == 0) + largest = std::max(largest, load.Alignment); + } + return largest; +} + void ZeroFillBelowTheNextSegment() { auto source = Fixture(0x3000); std::fill(source.begin() + 0x1000, source.begin() + 0x1100, std::uint8_t{0xAA}); @@ -226,6 +244,118 @@ void TrampolineDisplacementBeyondInt64Range() { Require(threw, "a trampoline whose site and body are more than 2^63 apart was accepted with a wrapped displacement"); } +void IncongruentSegmentAtPageStart() { + auto source = Fixture(0x2000); + std::fill(source.begin() + 0x1000, source.begin() + 0x1010, std::uint8_t{0x33}); + std::fill(source.begin() + 0x1010, source.begin() + 0x1110, std::uint8_t{0xAA}); + const std::vector headers{ + Load(0x400, 0x400, 0x10, 0x10, 5), + Load(0x1010, 0x1000, 0x100, 0x100, 6), + }; + const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {}); + RequireImage(output, source, headers, "segment at a page start with an offset inside a page"); +} + +void IncongruentSegmentInsideASharedPage() { + auto source = Fixture(0x2000); + std::fill(source.begin() + 0x1000, source.begin() + 0x1100, std::uint8_t{0xAA}); + std::fill(source.begin() + 0x1200, source.begin() + 0x1210, std::uint8_t{0x33}); + std::fill(source.begin() + 0x1210, source.begin() + 0x1310, std::uint8_t{0x5A}); + const std::vector headers{ + Load(0x400, 0x400, 0x10, 0x10, 5), + Load(0x1000, 0x1000, 0x100, 0x220, 6), + Load(0x1210, 0x1220, 0x100, 0x100, 4), + }; + const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {}); + RequireImage(output, source, headers, "segment in a shared page with an offset at another place in its page"); +} + +void IncongruentOffsetBelowThePagePrefix() { + auto source = Fixture(0x1000); + std::fill(source.begin() + 0x100, source.begin() + 0x180, std::uint8_t{0xBB}); + const std::vector headers{ + Load(0x400, 0x400, 0x10, 0x10, 5), + Load(0x100, 0x2300, 0x80, 0x80, 4), + }; + const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {}); + RequireImage(output, source, headers, "segment whose offset lies below its page prefix"); +} + +void LargeAlignmentMovesToThePage() { + for (const auto alignment : {std::uint64_t{0x80000000}, std::uint64_t{0x4000}, std::uint64_t{0x8000000000000000}}) { + auto source = Fixture(0x2000); + std::fill(source.begin() + 0x1000, source.begin() + 0x1010, std::uint8_t{0x33}); + std::fill(source.begin() + 0x1010, source.begin() + 0x1110, std::uint8_t{0xAA}); + const std::vector headers{ + Load(0x400, 0x400, 0x10, 0x10, 5), + Load(0x1010, 0x1000, 0x100, 0x100, 6, alignment), + }; + const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {}); + Require(output.size() <= source.size() + 3 * kPage, "output grew to " + std::to_string(output.size()) + " bytes for alignment " + std::to_string(alignment)); + RequireImage(output, source, headers, "incongruent segment with a large alignment"); + Require(OutputAlignment(output, 0x1000) == kPage, "moved segment declares an alignment its offset does not honour"); + Require(MaximumAlignment(output) == alignment, "the largest PT_LOAD alignment is " + std::to_string(MaximumAlignment(output)) + ", not " + std::to_string(alignment)); + } +} + +void LargeAlignmentOnASharedPage() { + auto source = Fixture(0x3000); + std::fill(source.begin() + 0x1000, source.begin() + 0x1100, std::uint8_t{0xAA}); + std::fill(source.begin() + 0x1100, source.begin() + 0x1c20, std::uint8_t{0x55}); + std::fill(source.begin() + 0x1c20, source.begin() + 0x2000, std::uint8_t{0x77}); + const std::vector headers{ + Load(0x400, 0x400, 0x10, 0x10, 5), + Load(0x1000, 0x1000, 0x100, 0x1c20, 6), + Load(0x1c20, 0x2c20, 0x3e0, 0x3e0, 0, std::uint64_t{0x80000000}), + }; + const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {}); + Require(output.size() <= source.size() + 3 * kPage, "output grew to " + std::to_string(output.size()) + " bytes on the shared page path"); + RequireImage(output, source, headers, "shared page with a large alignment"); + Require(OutputAlignment(output, 0x2c20) == kPage, "moved segment declares an alignment its offset does not honour"); + Require(MaximumAlignment(output) == 0x80000000, "the largest PT_LOAD alignment is " + std::to_string(MaximumAlignment(output))); +} + +void CongruentLargeAlignmentOnASharedPage() { + auto source = Fixture(0x3000); + std::fill(source.begin() + 0x1000, source.begin() + 0x1100, std::uint8_t{0xAA}); + std::fill(source.begin() + 0x1100, source.begin() + 0x1c20, std::uint8_t{0x55}); + std::fill(source.begin() + 0x1c20, source.begin() + 0x2000, std::uint8_t{0x77}); + const std::vector headers{ + Load(0x400, 0x400, 0x10, 0x10, 5), + Load(0x1000, 0x1000, 0x100, 0x4c20, 6), + Load(0x1c20, 0x5c20, 0x3e0, 0x3e0, 4, 0x4000), + }; + const auto output = Patcher().Patch(source, headers, {}, 0, "$ORIGIN/libs", true, false, {}); + Require(output.size() <= source.size() + 3 * kPage, "output grew to " + std::to_string(output.size()) + " bytes for a congruent alignment"); + RequireImage(output, source, headers, "congruent segment with a large alignment on a shared page"); + Require(OutputOffset(output, 0x5c20) != 0x1c20 && OutputOffset(output, 0x5c20) % kPage == 0xc20, "a segment whose page reads other bytes was not moved to a page-congruent offset"); + Require(OutputAlignment(output, 0x5c20) == kPage, "moved segment declares an alignment its offset does not honour"); + Require(MaximumAlignment(output) == 0x4000, "the largest PT_LOAD alignment fell to " + std::to_string(MaximumAlignment(output))); + const auto loads = OutputLoads(output); + const auto headerBlock = std::find_if(loads.begin(), loads.end(), [](const Domain::ProgramHeader& load) { return load.Offset == 0; }); + Require(headerBlock != loads.end() && headerBlock->Alignment == 0x4000 && headerBlock->MappedAddress % 0x4000 == 0, "the header block does not carry the moved segment's alignment"); + const auto fixed = Patcher(0x400000).Patch(source, headers, {}, 0, "$ORIGIN/libs", false, false, {}); + Require(MaximumAlignment(fixed) == 0x4000, "the largest PT_LOAD alignment at a fixed base fell to " + std::to_string(MaximumAlignment(fixed))); +} + +void LargeAlignmentAtAFixedBaseIsRefused() { + for (const auto alignment : {std::uint64_t{0x80000000}, std::uint64_t{0x8000000000000000}}) { + auto source = Fixture(0x2000); + std::fill(source.begin() + 0x1010, source.begin() + 0x1110, std::uint8_t{0xAA}); + const std::vector headers{ + Load(0x400, 0x400, 0x10, 0x10, 5), + Load(0x1010, 0x1000, 0x100, 0x100, 6, alignment), + }; + std::string message; + try { + Patcher(0x400000).Patch(source, headers, {}, 0, "$ORIGIN/libs", false, false, {}); + } catch (const std::exception& error) { + message = error.what(); + } + Require(message.find("is not a multiple of the PT_LOAD alignment") != std::string::npos, "fixed base accepted an alignment it is not a multiple of: " + message); + } +} + } int main() { @@ -235,6 +365,13 @@ int main() { ZeroFileBytesStayInPlace(); TrampolineDisplacementAcrossInt64Max(); TrampolineDisplacementBeyondInt64Range(); + IncongruentSegmentAtPageStart(); + IncongruentSegmentInsideASharedPage(); + IncongruentOffsetBelowThePagePrefix(); + LargeAlignmentMovesToThePage(); + LargeAlignmentOnASharedPage(); + CongruentLargeAlignmentOnASharedPage(); + LargeAlignmentAtAFixedBaseIsRefused(); } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; diff --git a/core/relinker/elfpatcher/tests/WindowsIconResourceTests.cpp b/core/relinker/elfpatcher/tests/WindowsIconResourceTests.cpp index a3813723aa..4dd0bc4aac 100644 --- a/core/relinker/elfpatcher/tests/WindowsIconResourceTests.cpp +++ b/core/relinker/elfpatcher/tests/WindowsIconResourceTests.cpp @@ -173,6 +173,21 @@ void run() { require(overlappingSections.size() == 1, "Section added on overlapping section"); } require(caughtOverlap, "Build succeeded with overlapping resource section"); + + std::vector virtualOverlap{ + {".data", 0x10000, Elfpatcher::Windows::SectionRead | 0x40u, std::vector(0x1000, 0), 0x41000} + }; + bool caughtVirtualOverlap = false; + try { + builder.Build(Png, iconPath, virtualOverlap, 0x50000); + } catch (const Domain::RelinkerException& error) { + caughtVirtualOverlap = true; + require(std::string(error.what()).find("Windows resource section overlaps preceding section") != std::string::npos, "Unexpected error for virtual overlap"); + require(virtualOverlap.size() == 1, "Section added on virtual overlap"); + } + require(caughtVirtualOverlap, "Build succeeded with a resource section overlapping virtual data"); + const auto following = builder.Build(Png, iconPath, virtualOverlap, 0x60000); + require(following.Rva == 0x60000 && virtualOverlap.size() == 2, "Build rejected a resource section after virtual data"); } } diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index 9ec903f5a3..9eb2f4c633 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -27,6 +27,12 @@ #include "RelinkerRevision.hpp" +#if defined(_WIN32) && defined(__MINGW32__) +extern "C" { +int _dowildcard = 0; +} +#endif + namespace { struct ConvertedExecutable { @@ -65,6 +71,7 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath, options.TrapSdkSyscallStubs = args.trapSdkSyscallStubs; options.UnusedFilterLevel = args.unusedFilterLevel; options.LazyBinding = args.lazyBinding; + options.OmitUnavailableDependencies = args.omitUnavailableDependencies; options.RunPath = args.runPath; options.WindowsGui = args.windowsGui; options.TraceImports = args.traceImports; @@ -128,11 +135,12 @@ bool SameOutputFile(const std::filesystem::path& first, const std::filesystem::p } bool SameOutputDestination(const std::filesystem::path& first, const std::filesystem::path& second, - std::filesystem::path& firstPath, std::filesystem::path& secondPath) { + std::filesystem::path& firstPath, std::filesystem::path& secondPath, const bool probeAliases) { 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); + if (!probeAliases) return false; #ifdef _WIN32 const auto firstName = firstPath.filename().native(); const auto secondName = secondPath.filename().native(); @@ -283,7 +291,14 @@ int Run(const int argc, char* argv[]) { throw Domain::RelinkerException("Executable output would overwrite the input executable"); if (Io::NativePath(args.outputPath).filename().empty() || std::filesystem::is_directory(Io::NativePath(args.outputPath))) throw Domain::RelinkerException("Executable output is a directory: " + args.outputPath); - const std::string absPath = Io::Utf8Path(std::filesystem::absolute(Io::NativePath(args.outputPath))); + const auto requestedOutput = Io::NativePath(args.outputPath); +#ifdef _WIN32 + const auto& nativeOutput = requestedOutput.native(); + const bool extendedOutput = nativeOutput.starts_with(L"\\\\?\\") || nativeOutput.starts_with(L"\\\\.\\"); +#else + const bool extendedOutput = false; +#endif + const std::string absPath = Io::Utf8Path(extendedOutput ? requestedOutput : std::filesystem::absolute(requestedOutput)); struct ExtraExecutable { std::string Argument; @@ -419,11 +434,22 @@ int Run(const int argc, char* argv[]) { for (std::size_t previous = 0; previous < index; ++previous) { const auto& first = outputPaths[index]; const auto& second = outputPaths[previous]; - if (SameOutputDestination(first, second, canonicalOutputPaths[index], canonicalOutputPaths[previous])) + if (SameOutputDestination(first, second, canonicalOutputPaths[index], canonicalOutputPaths[previous], !args.dryRun)) throw Domain::RelinkerException("Output paths collide: " + Io::Utf8Path(first) + " and " + Io::Utf8Path(second)); } } + if (args.dryRun) { + progress.Finish(); + std::cout << "\nDry run complete\n" + << "Converted executables: " << executables.size() << '\n' + << "Converted modules: " << guestArtifacts.size() << '\n' + << "External prx references: " << executables.front().Result.RegistryEntries.size() << '\n'; + for (const auto& path : outputPaths) std::cout << "Would write: " << Io::Utf8Path(path) << '\n'; + std::cout << "No output files were written. Destination write access and aliases of absent files were not checked.\n"; + return 0; + } + if (args.windowsDiagnostics) fileWriter.Write("windows-diagnostics-imports.txt", executables.front().Diagnostics); const auto outputCount = guestArtifacts.size() + executables.size(); progress.Report({Domain::ProgressStage::Write, absPath, 0, outputCount}); diff --git a/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp b/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp index cdd140af64..1e4b325291 100644 --- a/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp +++ b/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp @@ -5,12 +5,13 @@ #include #include #include +#include namespace Relinker { class CodeInstructionCollector { public: - std::set Collect(const std::vector& bytes, const std::vector& headers, const std::function& progress = {}, std::set* entryRoots = nullptr, const std::function& message = {}) const; + std::vector Collect(const std::vector& bytes, const std::vector& headers, const std::function& progress = {}, std::set* entryRoots = nullptr, const std::function& message = {}) const; }; } diff --git a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp index 2d6b51a189..35bc3d6959 100644 --- a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp +++ b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp @@ -85,7 +85,7 @@ std::vector> RemoveBacku class GuestModuleBuilder { public: - std::vector Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, bool windows, bool macos, bool toIntel, bool toRosetta, ISyscallScanner& syscallScanner, bool lazyBinding, const std::string& runPath, const std::set& excludedModules, const std::filesystem::path& root, const std::vector& runtimeDirectories, std::vector modules, Domain::IProgress& progress) const; + std::vector Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, bool windows, bool macos, bool toIntel, bool toRosetta, ISyscallScanner& syscallScanner, bool lazyBinding, bool omitUnavailableDependencies, const std::string& runPath, const std::set& excludedModules, const std::filesystem::path& root, const std::vector& runtimeDirectories, std::vector modules, Domain::IProgress& progress) const; }; } diff --git a/core/relinker/relinker/include/relinker/pipeline/Conversion.hpp b/core/relinker/relinker/include/relinker/pipeline/Conversion.hpp index 3a45a1ea10..8e4ddd63f9 100644 --- a/core/relinker/relinker/include/relinker/pipeline/Conversion.hpp +++ b/core/relinker/relinker/include/relinker/pipeline/Conversion.hpp @@ -19,6 +19,7 @@ struct ConversionOptions { bool SkipSyscallCheck = false; bool TrapSdkSyscallStubs = false; bool LazyBinding = false; + bool OmitUnavailableDependencies = false; bool WindowsGui = false; bool TraceImports = false; std::optional WindowsFixedBase; diff --git a/core/relinker/relinker/src/analysis/CallSiteResolver.cpp b/core/relinker/relinker/src/analysis/CallSiteResolver.cpp index a99edf14ee..ef491e1d10 100644 --- a/core/relinker/relinker/src/analysis/CallSiteResolver.cpp +++ b/core/relinker/relinker/src/analysis/CallSiteResolver.cpp @@ -19,10 +19,16 @@ std::int32_t readDisp32(const std::vector& text, std::size_t offse return static_cast(v); } -std::optional ripRelativeDispOffset(const std::vector& text, const Codegen::InstructionMatch& m) { +struct RelativeOperand { + std::size_t DisplacementOffset; + bool AddressSize32; +}; + +std::optional relativeOperand(const std::vector& text, const Codegen::InstructionMatch& m) { std::size_t cur = static_cast(m.Offset); std::size_t end = cur + m.Length; bool hasRexW = false; + bool addressSize32 = false; while (cur < end) { std::uint8_t b = text[cur]; if (b >= RexMin && b <= RexMax) { @@ -30,8 +36,9 @@ std::optional ripRelativeDispOffset(const std::vector ++cur; continue; } - if (b == PrefixRepne || b == PrefixSegDs) { + if (b == PrefixRepne || b == PrefixSegCs || b == PrefixSegDs || b == PrefixSegEs || b == PrefixSegSs || b == PrefixAddressSize) { hasRexW = false; + if (b == PrefixAddressSize) addressSize32 = true; ++cur; continue; } @@ -47,9 +54,9 @@ std::optional ripRelativeDispOffset(const std::vector if (mod != ModRmModIndirect || rm != ModRmRmRipRelative) return std::nullopt; const auto operation = (modrm >> ModRmRegShift) & ModRmRegMask; if (opcode == OneByteGrp5Rm && operation >= 2 && operation <= 6) - return cur + 2; + return RelativeOperand{cur + 2, addressSize32}; if (opcode == OneByteModRmRangeJMax && hasRexW) - return cur + 2; + return RelativeOperand{cur + 2, addressSize32}; return std::nullopt; } } @@ -79,11 +86,12 @@ std::vector CallSiteResolver::ResolveCallSites(const std::vector constexpr std::size_t MinRipRelativeInstructionBytes = 6; _cachedTargetToInstr.reserve(std::min(static_cast(candidateCount), textSection.size() / MinRipRelativeInstructionBytes)); scanner->VisitCodeSection(textSection, 0, textSection.size(), [&](const Codegen::InstructionMatch& m) { - const auto dispOff = ripRelativeDispOffset(textSection, m); - if (!dispOff || *dispOff + 4 > textSection.size()) return; - std::int32_t disp = readDisp32(textSection, *dispOff); + const auto operand = relativeOperand(textSection, m); + if (!operand || operand->DisplacementOffset + 4 > textSection.size()) return; + std::int32_t disp = readDisp32(textSection, operand->DisplacementOffset); VirtualAddress instrEnd = textSectionVAddr + m.Offset + static_cast(m.Length); VirtualAddress target = instrEnd + static_cast(static_cast(disp)); + if (operand->AddressSize32) target = static_cast(target); _cachedTargetToInstr.emplace_back(target, m.Offset); }); std::sort(_cachedTargetToInstr.begin(), _cachedTargetToInstr.end()); diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp index e0d20aa6b2..55e9d47efd 100644 --- a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,7 @@ #include #include #include +#include namespace Relinker { @@ -41,7 +43,7 @@ public: } -std::set CodeInstructionCollector::Collect(const std::vector& bytes, const std::vector& headers, const std::function& progress, std::set* entryRoots, const std::function& message) const { +std::vector CodeInstructionCollector::Collect(const std::vector& bytes, const std::vector& headers, const std::function& progress, std::set* entryRoots, const std::function& message) const { std::uint64_t lastProgress = 0; const auto report = [&](std::uint64_t completed) { if (completed <= lastProgress) return; @@ -353,6 +355,13 @@ std::set CodeInstructionCollector::Collect(const std::ve } } }; + std::vector relrTags; + for (const auto tag : {35, 36, 37}) + if (tags.contains(tag)) relrTags.push_back({tag, tags.at(tag)}); + for (const auto& [slot, target] : ReadRelrRelocations(bytes, headers, relrTags)) { + pointers.Values.emplace(slot, target); + addPointerRoot(target); + } readRelocations(relaOffset, relaSize, false); readRelocations(pltOffset, pltSize, true); for (const auto& [addressTag, sizeTag] : {std::pair{25u, 27u}, std::pair{26u, 28u}, std::pair{32u, 33u}}) { @@ -389,10 +398,10 @@ std::set CodeInstructionCollector::Collect(const std::ve std::erase_if(roots, inTable); } if (roots.empty()) throw Domain::RelinkerException("Code analysis: no code entry points"); - std::set instructions; + std::vector instructionList; const Codegen::X64InstructionDecoder decoder; std::vector> skipped; - std::set staticTargets; + std::vector staticTargets; std::uint64_t functionBytes = 0; for (const auto& [begin, end] : functions) functionBytes += end - begin; std::uint64_t decodedBytes = 0; @@ -419,11 +428,11 @@ std::set CodeInstructionCollector::Collect(const std::ve } if (info.Length == 0 || info.Length > end - address) throw Domain::RelinkerException("Code analysis: instruction crosses function boundary", address); - instructions.emplace_hint(instructions.end(), address); + instructionList.push_back(address); addAddressTakenRoot(address, bytes.data() + offset + address - begin, info); if (info.HasBranchTarget && !info.HasRipRelativeDisp) { const auto target = address + info.Length + static_cast(info.BranchDisp); - staticTargets.insert(target); + staticTargets.push_back(target); addRoot(target); } address += info.Length; @@ -499,6 +508,8 @@ std::set CodeInstructionCollector::Collect(const std::ve } return false; }; + std::sort(staticTargets.begin(), staticTargets.end()); + staticTargets.erase(std::unique(staticTargets.begin(), staticTargets.end()), staticTargets.end()); for (const auto target : staticTargets) { for (const auto& [skipBegin, skipEnd] : skipped) { if (skipBegin <= target && target < skipEnd && !decodesInSegment(target)) @@ -506,7 +517,7 @@ std::set CodeInstructionCollector::Collect(const std::ve } } std::size_t previousRoots = 0; - std::unordered_set decodedInstructions; + std::vector> decodedInstructions(codeHeaders.size()); std::vector> sectionReachable(codeHeaders.size()); do { previousRoots = roots.size(); @@ -518,12 +529,16 @@ std::set CodeInstructionCollector::Collect(const std::ve for (const auto root : roots) if (root >= header.MappedAddress && root - header.MappedAddress < header.FileSize) entries.push_back(root); if (entries.empty()) continue; const std::span text(bytes.data() + header.Offset, header.FileSize); + auto& decoded = decodedInstructions[i]; + if (decoded.empty()) decoded.resize(header.FileSize); auto& reached = sectionReachable[i]; if (reached.empty()) reached.resize(header.FileSize); for (const auto address : UnusedNidFilter::ExtendControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers, sceFunctions.empty(), reached, [&](std::uint64_t count) { report(6000 + 2500 * count / std::max(scanBytes, 1)); }, inTable)) { - if (!decodedInstructions.insert(address).second) continue; - instructions.insert(address); const auto offset = address - header.MappedAddress; + if (offset >= decoded.size()) throw Domain::RelinkerException("Code analysis: reachable address outside its section", address); + if (decoded[offset]) continue; + decoded[offset] = true; + instructionList.push_back(address); const auto info = decoder.DecodeInstruction(text.data() + offset, text.size() - offset); addAddressTakenRoot(address, text.data() + offset, info); if (info.HasBranchTarget && !info.HasRipRelativeDisp) addRoot(address + info.Length + static_cast(info.BranchDisp)); @@ -531,6 +546,9 @@ std::set CodeInstructionCollector::Collect(const std::ve } } while (roots.size() != previousRoots); report(8500); + std::sort(instructionList.begin(), instructionList.end()); + instructionList.erase(std::unique(instructionList.begin(), instructionList.end()), instructionList.end()); + const auto& instructions = instructionList; std::uint64_t checked = 0; std::uint64_t previousEnd = 0; const Domain::ProgramHeader* cachedHeader = nullptr; @@ -570,7 +588,7 @@ std::set CodeInstructionCollector::Collect(const std::ve } } if (!spanHasFsPrefix) { - const auto previous = instructions.lower_bound(address); + const auto previous = std::lower_bound(instructions.begin(), instructions.end(), address); if (previous != instructions.begin()) { const auto previousStart = *std::prev(previous); if (const auto* header = findCodeHeader(previousStart)) { @@ -599,7 +617,7 @@ std::set CodeInstructionCollector::Collect(const std::ve *entryRoots = std::move(roots); entryRoots->insert(dataTargets.begin(), dataTargets.end()); } - return instructions; + return instructionList; } } diff --git a/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp b/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp index 799cc59c20..0633b2dd3e 100644 --- a/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp +++ b/core/relinker/relinker/src/analysis/RelativeJumpTables.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -255,6 +256,7 @@ std::size_t FindTrailingRelativeJumpTable(std::span code, Vi } const Codegen::X64InstructionDecoder decoder; std::vector loads; + std::unordered_map> loadsAt; std::vector instructionStarts(code.size() + 1, false); bool flowEnded = false; bool sawIndirectJump = false; @@ -262,16 +264,18 @@ std::size_t FindTrailingRelativeJumpTable(std::span code, Vi for (std::size_t position = 0; position < code.size();) { instructionStarts[position] = true; if (flowEnded && sawIndirectJump) { - for (const auto& load : loads) { - if (load.Target != position || load.At >= lastIndirectJump) continue; - const auto end = TablesEnd(code, position, loads, instructionStarts); - if (end == 0) continue; - const auto aligned = std::min(code.size(), static_cast(((codeAddress + end + 15) & ~VirtualAddress{15}) - codeAddress)); - const auto checkedTo = std::max(aligned, std::min(code.size(), unwindEnd)); - const bool toRangeEnd = OnlyPadding(code, end, code.size(), decoder); - if (!toRangeEnd && !OnlyPadding(code, end, checkedTo, decoder)) continue; - if (tableEnd) *tableEnd = toRangeEnd ? code.size() : checkedTo; - return position; + if (const auto found = loadsAt.find(position); found != loadsAt.end()) { + for (const auto at : found->second) { + if (at >= lastIndirectJump) continue; + const auto end = TablesEnd(code, position, loads, instructionStarts); + if (end == 0) continue; + const auto aligned = std::min(code.size(), static_cast(((codeAddress + end + 15) & ~VirtualAddress{15}) - codeAddress)); + const auto checkedTo = std::max(aligned, std::min(code.size(), unwindEnd)); + const bool toRangeEnd = OnlyPadding(code, end, code.size(), decoder); + if (!toRangeEnd && !OnlyPadding(code, end, checkedTo, decoder)) continue; + if (tableEnd) *tableEnd = toRangeEnd ? code.size() : checkedTo; + return position; + } } } Codegen::DecodedInstructionInfo info; @@ -286,7 +290,10 @@ std::size_t FindTrailingRelativeJumpTable(std::span code, Vi std::int32_t displacement = 0; std::memcpy(&displacement, code.data() + next - 4, sizeof(displacement)); const auto target = static_cast(next) + displacement; - if (target > static_cast(next) && target < static_cast(code.size())) loads.push_back({position, static_cast(target)}); + if (target > static_cast(next) && target < static_cast(code.size())) { + loads.push_back({position, static_cast(target)}); + loadsAt[static_cast(target)].push_back(position); + } } if (info.FlowKind == Codegen::ControlFlowKind::IndirectJump) { sawIndirectJump = true; @@ -341,6 +348,7 @@ std::vector FindRelativeJumpTables(std::span pendingTargets(branchTargets.begin(), branchTargets.end()); std::set exploredGaps; + std::vector decoded(segment.size(), false); while (!pendingTargets.empty()) { const auto current = pendingTargets.back(); pendingTargets.pop_back(); @@ -350,7 +358,8 @@ std::vector FindRelativeJumpTables(std::spansecond; if (inKnownFunction || !exploredGaps.insert(current).second) continue; auto pos = static_cast(current - segmentVAddr); - while (pos < segment.size()) { + while (pos < segment.size() && !decoded[pos]) { + decoded[pos] = true; Codegen::DecodedInstructionInfo info; try { info = decoder.DecodeInstruction(segment.data() + pos, segment.size() - pos); diff --git a/core/relinker/relinker/src/analysis/SyscallScanner.cpp b/core/relinker/relinker/src/analysis/SyscallScanner.cpp index bbe6db0f4c..614ca7846a 100644 --- a/core/relinker/relinker/src/analysis/SyscallScanner.cpp +++ b/core/relinker/relinker/src/analysis/SyscallScanner.cpp @@ -78,6 +78,27 @@ static constexpr std::array SDK_MMAP_EXEC_INLINE{ 0x49, 0x89, 0xC7, 0x49, 0xF7, 0xDF, 0x84, 0xC9, 0x4C, 0x0F, 0x45, 0xF8, 0x4D, 0x85, 0xFF, }; +static constexpr std::array SDK_MOUNT_INLINE{ + 0x55, 0x48, 0x89, 0xE5, 0x41, 0x56, 0x53, 0x48, 0x63, 0xD2, 0x31, 0xDB, + 0xB8, 0x15, 0x00, 0x00, 0x00, 0x49, 0x89, 0xCA, 0x45, 0x31, 0xC0, 0x45, 0x31, 0xC9, + 0x0F, 0x05, 0x0F, 0x93, 0xC1, 0x49, 0x89, 0xC6, 0x49, 0xF7, 0xDE, 0x84, 0xC9, + 0x4C, 0x0F, 0x45, 0xF0, 0x4D, 0x85, 0xF6, +}; + +static constexpr std::array SDK_NMOUNT_INLINE{ + 0x55, 0x48, 0x89, 0xE5, 0x41, 0x56, 0x53, 0x89, 0xF6, 0x48, 0x63, 0xD2, 0x31, 0xDB, + 0xB8, 0x7A, 0x01, 0x00, 0x00, 0x45, 0x31, 0xD2, 0x45, 0x31, 0xC0, 0x45, 0x31, 0xC9, + 0x0F, 0x05, 0x0F, 0x93, 0xC1, 0x49, 0x89, 0xC6, 0x49, 0xF7, 0xDE, 0x84, 0xC9, + 0x4C, 0x0F, 0x45, 0xF0, 0x4D, 0x85, 0xF6, +}; + +static constexpr std::array SDK_UNMOUNT_INLINE{ + 0x55, 0x48, 0x89, 0xE5, 0x41, 0x56, 0x53, 0x48, 0x63, 0xF6, 0x31, 0xDB, + 0xB8, 0x16, 0x00, 0x00, 0x00, 0x31, 0xD2, 0x45, 0x31, 0xD2, 0x45, 0x31, 0xC0, 0x45, 0x31, 0xC9, + 0x0F, 0x05, 0x0F, 0x93, 0xC1, 0x49, 0x89, 0xC6, 0x49, 0xF7, 0xDE, 0x84, 0xC9, + 0x4C, 0x0F, 0x45, 0xF0, 0x4D, 0x85, 0xF6, +}; + struct RecentInstructions { static constexpr std::size_t Capacity = 64; std::array matches{}; @@ -108,7 +129,12 @@ static bool MatchesSdkInlineStub(const std::vector& code, std::siz } static bool IsSdkMmanSyscallStub(const std::vector& code, std::size_t syscallOffset, std::size_t limit, const RecentInstructions& recent, FileByteOffset codeOffset) { - return MatchesSdkInlineStub(code, syscallOffset, limit, SDK_MPROTECT_INLINE, 42, recent, codeOffset) || MatchesSdkInlineStub(code, syscallOffset, limit, SDK_MMAP_INLINE, 17, recent, codeOffset) || MatchesSdkInlineStub(code, syscallOffset, limit, SDK_MMAP_EXEC_INLINE, 23, recent, codeOffset); + return MatchesSdkInlineStub(code, syscallOffset, limit, SDK_MPROTECT_INLINE, 42, recent, codeOffset) + || MatchesSdkInlineStub(code, syscallOffset, limit, SDK_MMAP_INLINE, 17, recent, codeOffset) + || MatchesSdkInlineStub(code, syscallOffset, limit, SDK_MMAP_EXEC_INLINE, 23, recent, codeOffset) + || MatchesSdkInlineStub(code, syscallOffset, limit, SDK_MOUNT_INLINE, 26, recent, codeOffset) + || MatchesSdkInlineStub(code, syscallOffset, limit, SDK_NMOUNT_INLINE, 28, recent, codeOffset) + || MatchesSdkInlineStub(code, syscallOffset, limit, SDK_UNMOUNT_INLINE, 28, recent, codeOffset); } static ForbiddenInstruction ClassifyForbiddenInstruction(const std::vector& codeSection, const std::size_t opcodeOffset, const std::size_t limit) { diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp index 2b3fad01a2..7a188fe323 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp @@ -186,6 +186,7 @@ void walkGraph(std::span text, VirtualAddress textVaddr, Vir } void buildGraph(std::span text, VirtualAddress textVaddr, VirtualAddress entryVaddr, const std::vector& extraEntries, const IRelativeRelocationIndex& relativeRelocations, const std::vector* data, bool followCodeAddresses, std::unordered_set& reachable, const std::function& progress, const std::function& isData) { + if (reachable.empty()) reachable.reserve(text.size() / 4); if (text.size() > std::numeric_limits::max() - textVaddr) throw RelinkerException("Text section range exceeds the address space"); std::vector state(text.size(), 0); diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/EhFrameReader.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/EhFrameReader.cpp index 5045239694..2b6ca0a4a8 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/EhFrameReader.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/EhFrameReader.cpp @@ -35,10 +35,12 @@ public: if (position < segment.MappedAddress || position - segment.MappedAddress > segment.FileSize) throw RelinkerException("Strict filter: EH frame header exceeds segment", position); if (count > segment.FileSize / 2) throw RelinkerException("Strict filter: invalid EH frame count", position); + std::set> listed; for (std::uint64_t index = 0; index < count; ++index) { const auto begin = encoded(position, tableEncoding, segment.MappedAddress); const auto fde = encoded(position, tableEncoding, segment.MappedAddress); if (position < segment.MappedAddress || position - segment.MappedAddress > segment.FileSize) throw RelinkerException("Strict filter: EH frame table exceeds segment", position); + if (!listed.emplace(begin, fde).second) continue; auto region = readFde(begin, fde); if (region.End != region.Begin) result.push_back(std::move(region)); } diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp index 3a095c196c..f0acff2b1c 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp @@ -202,7 +202,7 @@ public: std::uint16_t shndx = read16(elfBytes, sPos + 6); std::uint64_t symVal = read64(elfBytes, sPos + 8); if ((stBind == STB_GLOBAL || stBind == STB_WEAK) && - stType == STT_FUNC && shndx != SHN_UNDEF) { + stType == STT_FUNC && shndx != SHN_UNDEF && shndx != SHN_ABS) { addIfInText(symVal); } } diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp index ad534c1ddc..fb12165781 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp @@ -154,6 +154,9 @@ std::unique_ptr BuildRelativeRelocationIndex( if (hasSceDynlibData) throw RelinkerException("Multiple PT_SCE_DYNLIBDATA segments", phPos); hasSceDynlibData = true; sceTableBase = read64(elfBytes, phPos + 8); + std::uint64_t sceSize = read64(elfBytes, phPos + 32); + if (sceTableBase > elfBytes.size() || sceSize > elfBytes.size() - sceTableBase) + throw RelinkerException("PT_SCE_DYNLIBDATA segment out of bounds", phPos); } } const auto headers = ElfReader(elfBytes).ReadProgramHeaders(); @@ -180,16 +183,22 @@ std::unique_ptr BuildRelativeRelocationIndex( std::optional relaVa; std::optional relaSz; + std::optional relaEnt; std::optional jmprelVa; std::optional pltrelsz; + std::optional pltrel; bool hasSysvRela = false; bool hasOsRela = false; bool hasSysvRelaSz = false; bool hasOsRelaSz = false; + bool hasSysvRelaEnt = false; + bool hasOsRelaEnt = false; bool hasSysvJmprel = false; bool hasOsJmprel = false; bool hasSysvPltrelsz = false; bool hasOsPltrelsz = false; + bool hasSysvPltrel = false; + bool hasOsPltrel = false; bool terminated = false; std::vector relrTags; @@ -202,6 +211,8 @@ std::unique_ptr BuildRelativeRelocationIndex( terminated = true; break; } + if (tag == DT_REL || tag == DT_RELSZ || tag == DT_RELENT) + throw RelinkerException("DT_REL relocations are not supported"); if (tag == DT_RELR || tag == DT_RELRSZ || tag == DT_RELRENT) relrTags.push_back({tag, val}); if (tag == DT_RELA) { if (hasOsRela) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELA"); @@ -227,6 +238,18 @@ std::unique_ptr BuildRelativeRelocationIndex( hasOsRelaSz = true; relaSz = val; } + if (tag == DT_RELAENT) { + if (hasOsRelaEnt) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELAENT"); + if (hasSysvRelaEnt && relaEnt.value() != val) throw RelinkerException("Conflicting duplicate DT_RELAENT tags"); + hasSysvRelaEnt = true; + relaEnt = val; + } + if (tag == DT_OS_RELAENT) { + if (hasSysvRelaEnt) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_RELAENT"); + if (hasOsRelaEnt && relaEnt.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_RELAENT tags"); + hasOsRelaEnt = true; + relaEnt = val; + } if (tag == DT_JMPREL) { if (hasOsJmprel) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_JMPREL"); if (hasSysvJmprel && jmprelVa.value() != val) throw RelinkerException("Conflicting duplicate DT_JMPREL tags"); @@ -251,19 +274,39 @@ std::unique_ptr BuildRelativeRelocationIndex( hasOsPltrelsz = true; pltrelsz = val; } + if (tag == DT_PLTREL) { + if (hasOsPltrel) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_PLTREL"); + if (hasSysvPltrel && pltrel.value() != val) throw RelinkerException("Conflicting duplicate DT_PLTREL tags"); + hasSysvPltrel = true; + pltrel = val; + } + if (tag == DT_OS_PLTREL) { + if (hasSysvPltrel) throw RelinkerException("Both DT_OS_ and DT_ variants present for DT_PLTREL"); + if (hasOsPltrel && pltrel.value() != val) throw RelinkerException("Conflicting duplicate DT_OS_PLTREL tags"); + hasOsPltrel = true; + pltrel = val; + } } if (!terminated) throw RelinkerException("Unterminated dynamic segment", segOff); if ((hasSysvRela && hasOsRelaSz) || (hasOsRela && hasSysvRelaSz)) throw RelinkerException("Mismatched DT_RELA and DT_RELASZ tag variants"); + if ((hasSysvRela && hasOsRelaEnt) || (hasOsRela && hasSysvRelaEnt)) + throw RelinkerException("Mismatched DT_RELA and DT_RELAENT tag variants"); if (relaVa.has_value() != relaSz.has_value()) throw RelinkerException("Incomplete relocation table metadata"); + if (relaEnt.has_value() && *relaEnt != RelaEntSize) + throw RelinkerException("Unsupported DT_RELAENT value: " + std::to_string(*relaEnt) + "; expected 24 bytes"); if ((hasSysvJmprel && hasOsPltrelsz) || (hasOsJmprel && hasSysvPltrelsz)) throw RelinkerException("Mismatched DT_JMPREL and DT_PLTRELSZ tag variants"); + if ((hasSysvJmprel && hasOsPltrel) || (hasOsJmprel && hasSysvPltrel)) + throw RelinkerException("Mismatched DT_JMPREL and DT_PLTREL tag variants"); if (jmprelVa.has_value() != pltrelsz.has_value()) throw RelinkerException("Incomplete PLT relocation table metadata"); + if (pltrel.has_value() && *pltrel != static_cast(DT_RELA)) + throw RelinkerException("Unsupported DT_PLTREL type: " + std::to_string(*pltrel) + "; expected DT_RELA (7)"); if (relaVa.has_value()) collectRelativeEntries(elfBytes, loads, *relaVa, *relaSz, hasOsRela, sceTableBase, targets); diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp index 26b90ad1b7..c9131e9b4c 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,7 @@ public: terminated = true; break; } + if (tag == 17 || tag == 18 || tag == 19) throw RelinkerException("Strict filter: DT_REL relocations are not supported"); if (tag == 1 || (tag >= 0x61000000 && tag != 0x6100002F && tag != 0x61000031 && tag != 0x61000033 && tag != 0x61000039 && tag != 0x6100003B && tag != 0x6100003F)) continue; if (!tags.emplace(tag, read(segment.Offset + offset + 8)).second) throw RelinkerException("Strict filter: duplicate dynamic tag", segment.Offset + offset); @@ -70,6 +72,8 @@ public: const auto rela = table(7, 0x6100002F); const auto relaSize = value(8, 0x61000031); if (rela.has_value() != relaSize.has_value()) throw RelinkerException("Strict filter: incomplete relocation table metadata"); + if (const auto entrySize = value(9, 0x61000033); entrySize && *entrySize != 24) + throw RelinkerException("Strict filter: invalid RELA entry size"); if (rela) { if (value(9, 0x61000033) != 24 || *relaSize % 24 != 0) throw RelinkerException("Strict filter: invalid RELA entry size"); @@ -133,7 +137,7 @@ public: const auto info = read(position + 4); const auto type = info & 15; if (type == 10) throw RelinkerException("Strict filter: IFUNC resolver is not modeled", position); - if (section == 0 || (info >> 4) == 0 || type != 2) continue; + if (section == 0 || section == Elfpatcher::SHN_ABS || (info >> 4) == 0 || type != 2) continue; input.Entries.push_back(read(position + 8)); } } diff --git a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp index 63b7b6cf43..9b17dfa924 100644 --- a/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp +++ b/core/relinker/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp @@ -166,7 +166,7 @@ private: region.Edges.insert(target); } if (info.HasRipRelativeDisp) { - std::int32_t displacement; + std::int32_t displacement = 0; std::memcpy(&displacement, input.Text.data() + offset + info.RipRelativeDispOffset, sizeof(displacement)); auto target = next + static_cast(static_cast(displacement)); if (info.AddressSize32) target = static_cast(target); diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp index 6c35130ecd..df2363ef25 100644 --- a/core/relinker/relinker/src/guest/GuestImageReader.cpp +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -36,6 +37,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector const Domain::ProgramHeader* dynamic = nullptr; const Domain::ProgramHeader* dynlib = nullptr; const Domain::ProgramHeader* tls = nullptr; + const Domain::ProgramHeader* ehFrame = nullptr; std::map loads; constexpr std::uint32_t sceComment = 0x6fffff00; constexpr std::uint32_t sceLibVersion = 0x6fffff01; @@ -60,6 +62,10 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if (dynlib != nullptr) fail("Duplicate SCE dynamic data"); dynlib = &header; } + if (header.Type == 0x6474e550) { + if (ehFrame != nullptr) fail("Duplicate EH frame segment"); + ehFrame = &header; + } if (header.Type == 7) { if (tls != nullptr || header.FileSize > header.MemorySize || (header.Alignment > 1 && !std::has_single_bit(header.Alignment))) fail("Invalid TLS segment"); tls = &header; @@ -85,10 +91,10 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector } } if (!terminated) fail("Unterminated dynamic segment"); - for (const auto unsupported : {15u, 16u, 17u, 18u, 19u, 22u}) { + for (const auto unsupported : {15u, 16u, 22u}) { if (tags.contains(unsupported)) fail("Unsupported dynamic tag " + std::to_string(unsupported)); } - if (tags.contains(30) && (tags.at(30) & ~(2ull | 8ull)) != 0) fail("Unsupported guest dynamic flags"); + if (tags.contains(30) && (tags.at(30) & ~(2ull | 8ull | 16ull)) != 0) fail("Unsupported guest dynamic flags"); image.Symbolic = tags.contains(30) && (tags.at(30) & 2ull) != 0; const auto mapped = [&](std::uint64_t address, std::uint64_t size, std::uint32_t flags) { for (const auto& header : image.Headers) { @@ -96,6 +102,10 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector } fail("Unmapped or inaccessible guest address " + std::to_string(address)); }; + const auto relativeAddress = [&](std::uint64_t address) { + if (address < loads.begin()->first || address > loads.rbegin()->second) + fail("Relative relocation addend is outside the ELF image " + std::to_string(address)); + }; const auto translate = [&](std::uint64_t address, std::uint64_t size) { for (const auto& header : image.Headers) { if (header.Type == 1 && address >= header.MappedAddress && address - header.MappedAddress <= header.FileSize && size <= header.FileSize - (address - header.MappedAddress)) return header.Offset + address - header.MappedAddress; @@ -240,12 +250,13 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if (size % 24 != 0) fail("Invalid relocation table size"); return {table(addressTag, sceAddressTag, size), size}; }; - if (tags.contains(7) || tags.contains(0x6100002f)) { + if (tags.contains(17) || tags.contains(18) || tags.contains(19)) fail("DT_REL relocations are not supported"); + if (tags.contains(7) || tags.contains(0x6100002f) || tags.contains(9) || tags.contains(0x61000033)) { if (get(9, 0x61000033) != 24) fail("Invalid RELA entry size"); } const auto [relaOffset, relaSize] = relocationRange(7, 0x6100002f, 8, 0x61000031); const auto [pltOffset, pltSize] = relocationRange(23, 0x61000029, 2, 0x6100002d); - if (pltSize != 0 && get(20, 0x6100002b) != 7) fail("Unsupported PLT relocation format"); + if ((pltSize != 0 || tags.contains(20) || tags.contains(0x6100002b)) && get(20, 0x6100002b) != 7) fail("Unsupported PLT relocation format"); if (relaSize != 0 && pltSize != 0 && relaOffset < pltOffset + pltSize && pltOffset < relaOffset + relaSize && relaOffset % 24 != pltOffset % 24) fail("Unaligned overlapping relocation tables"); @@ -255,7 +266,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if (tag >= 35 && tag <= 37) relrTags.push_back({static_cast(tag), value}); for (const auto& [target, addend] : ReadRelrRelocations(bytes, image.Headers, relrTags)) { mapped(target, 8, 2); - mapped(addend, 0, 0); + relativeAddress(addend); relocationTargets.emplace(target, target + 8); Io::AppendU64(image.Dynamic.RelaData, target); Io::AppendU64(image.Dynamic.RelaData, 8); @@ -278,7 +289,7 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector mapped(target, 8, 2); const bool relative = !plt && relocation == 8 && (info >> 32) == 0; if (const auto earlier = relativeEntries.find(target); relative && earlier != relativeEntries.end()) { - mapped(addend, 0, 0); + relativeAddress(addend); Io::WriteU64(output, earlier->second + 16, addend); continue; } @@ -293,20 +304,21 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector if (symbol.Value != 0) fail("RELATIVE relocation has a symbol"); } else if (symbol.Section == 0 || addend > static_cast(std::numeric_limits::max()) || symbol.Value != addend) fail("RELATIVE relocation has a symbol"); - mapped(addend, 0, 0); + relativeAddress(addend); Io::AppendU64(output, target); Io::AppendU64(output, relocation); Io::AppendU64(output, addend); continue; } - mapped(addend, 0, 0); + relativeAddress(addend); } else if (relocation == 1 || relocation == 6 || relocation == 7) { const auto& symbol = image.Symbols[info >> 32]; 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]; - 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 bool hasTlsBlock = tls != nullptr && tls->MemorySize != 0; + ownTlsBlock = (info >> 32) != 0 && (relocation == 16 || relocation == 17) && symbol.Info == 3 && symbol.Section == 0 && symbol.Value == 0 && hasTlsBlock; + if (((info >> 32) == 0 && !hasTlsBlock) || ((info >> 32) != 0 && (symbol.Info & 15) != 6 && !ownTlsBlock) || ((symbol.Info & 15) == 6 && symbol.Section != 0 && !hasTlsBlock) || (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); @@ -336,11 +348,15 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector const auto address = tags.at(addressTag); mapped(address, size, 4); const auto fileOffset = translate(address, size); + std::unordered_map*, std::size_t>>> relocationsAt; + for (const auto* table : {&image.Dynamic.RelaData, &image.Dynamic.RelaPltData}) + for (std::size_t position = 0; position < table->size(); position += 24) + relocationsAt[Io::ReadU64(*table, position)].emplace_back(table, position); for (std::uint64_t offset = 0; offset < size; offset += 8) { bool relocated = false; - for (const auto* table : {&image.Dynamic.RelaData, &image.Dynamic.RelaPltData}) { - for (std::size_t position = 0; position < table->size(); position += 24) { - if (Io::ReadU64(*table, position) != address + offset) continue; + const auto found = relocationsAt.find(address + offset); + if (found != relocationsAt.end()) { + for (const auto& [table, position] : found->second) { relocated = true; const auto info = Io::ReadU64(*table, position + 8); const auto addend = Io::ReadU64(*table, position + 16); diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index deac4a9d8a..cd2273f081 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -90,7 +90,7 @@ std::string ModuleStem(std::string name, const bool windows) { } -std::vector GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool macos, const bool toIntel, const bool toRosetta, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath, const std::set& excludedModules, const std::filesystem::path& root, const std::vector& runtimeDirectories, std::vector modules, Domain::IProgress& progress) const { +std::vector GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool macos, const bool toIntel, const bool toRosetta, ISyscallScanner& syscallScanner, const bool lazyBinding, const bool omitUnavailableDependencies, const std::string& runPath, const std::set& excludedModules, const std::filesystem::path& root, const std::vector& runtimeDirectories, std::vector modules, Domain::IProgress& progress) const { std::vector paths; std::map discovered; progress.Report({Domain::ProgressStage::Modules, {}, 0, modules.size()}); @@ -110,13 +110,20 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path const auto neededNames = ReadNeededNames(dynamic); std::set missingNeeded; std::map neededAliases; + std::map ambiguousAliases; for (const auto& name : neededNames) { if (excludedModules.contains(name)) continue; if (std::any_of(paths.begin(), paths.end(), [&](const auto& path) { return Io::Utf8Path(path.filename()) == name || (windows && FoldFilename(Io::Utf8Path(path.filename())) == FoldFilename(name)); })) continue; const auto stem = ModuleStem(name, windows); - const auto alias = stem.empty() ? paths.end() : std::find_if(paths.begin(), paths.end(), [&](const auto& path) { return ModuleStem(Io::Utf8Path(path.filename()), windows) == stem; }); - if (alias != paths.end()) neededAliases.emplace(name, *alias); - else missingNeeded.insert(name); + const auto matchesStem = [&](const auto& path) { return ModuleStem(Io::Utf8Path(path.filename()), windows) == stem; }; + const auto alias = stem.empty() ? paths.end() : std::find_if(paths.begin(), paths.end(), matchesStem); + if (alias == paths.end()) { + missingNeeded.insert(name); + continue; + } + neededAliases.emplace(name, *alias); + const auto other = std::find_if(std::next(alias), paths.end(), matchesStem); + if (other != paths.end()) ambiguousAliases.emplace(name, *other); } const auto matchesIdentity = [](const std::string& name, const std::vector& identities) { const auto stem = std::string_view(name).substr(0, name.size() - Domain::ModuleSuffix(name).size()); @@ -169,6 +176,8 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path } for (const auto& [name, path] : neededAliases) { if (guestNames.contains(name)) continue; + if (const auto other = ambiguousAliases.find(name); other != ambiguousAliases.end()) + throw Domain::RelinkerException("Ambiguous needed module: " + name + " (" + Io::Utf8Path(path) + " and " + Io::Utf8Path(other->second) + ")"); const auto image = std::find_if(images.begin(), images.end(), [&](const auto& candidate) { return candidate.SourcePath == path; }); if (image == images.end()) throw Domain::RelinkerException("Needed module alias has no guest image: " + name); const auto index = static_cast(image - images.begin()); @@ -317,7 +326,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path for (const auto provider : executableProviders) require(provider); for (std::size_t index = 0; index < images.size(); ++index) { if (!isRuntime(index)) require(index); - if (windows) for (const auto dependency : dependencies[index]) if (standardDirectory(dependency)) require(dependency); + if (windows) for (const auto dependency : dependencies[index]) require(dependency); } const auto deferred = [&](const std::size_t index) { return !required[index] && isRuntime(index); @@ -353,6 +362,39 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path std::vector> moduleHostLibraries(images.size()); std::set uniqueHosts; const auto hostLibrary = [&](const std::string& name) { return windows || macos ? name : Domain::NeededLibraryName(name); }; + std::vector runDirectories; + bool runDirectoriesExist = !windows && !macos; + for (std::size_t begin = 0; runDirectoriesExist && begin <= runPath.size();) { + const auto end = std::min(runPath.find(':', begin), runPath.size()); + const auto component = std::string_view(runPath).substr(begin, end - begin); + std::filesystem::path directory; + if (component == "$ORIGIN") directory = outputPath.parent_path(); + else if (component.starts_with("$ORIGIN/")) directory = outputPath.parent_path() / std::string(component.substr(8)); + else if (component.starts_with('/')) directory = std::string(component); + std::error_code error; + runDirectoriesExist = !directory.empty() && std::filesystem::is_directory(directory, error); + runDirectories.push_back(std::move(directory)); + begin = end + 1; + } + std::set> reportedDependencies; + const auto unavailableUnused = [&](std::size_t index, const std::string& dependency) { + if (!runDirectoriesExist || findGuest(dependency) != guestNames.end()) return false; + const auto stem = ModuleStem(dependency, windows); + if (stem.empty()) return false; + const auto& symbols = images[index].Symbols; + if (std::any_of(symbols.begin(), symbols.end(), [&](const auto& symbol) { return symbol.Section == 0 && !symbol.Library.empty() && (symbol.Library == dependency || symbol.Library == stem || ModuleStem(symbol.Library, windows) == stem); })) return false; + const auto file = hostLibrary(dependency); + for (const auto& directory : runDirectories) { + std::error_code error; + const bool exists = std::filesystem::exists(directory / file, error); + if (exists || error) return false; + } + if (reportedDependencies.emplace(index, dependency).second) { + const auto subject = Io::Utf8Path(images[index].SourcePath) + " needs " + dependency + ", which no module or run path library provides and from which it imports nothing"; + progress.Message(omitUnavailableDependencies ? "Warning: " + subject + "; it is left out of the needed libraries" : "Note: " + subject + "; --omit-unavailable-dependencies leaves it out"); + } + return omitUnavailableDependencies; + }; 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); @@ -362,6 +404,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path for (std::size_t index = 0; index < images.size(); ++index) { auto& hosts = moduleHostLibraries[index]; for (const auto& dependency : images[index].Dependencies) { + if (unavailableUnused(index, dependency)) continue; addHost(dependency, windows || !deferred(index)); if (findGuest(dependency) == guestNames.end()) hosts.push_back(hostLibrary(dependency)); } @@ -484,7 +527,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(hostLibrary(name)); + if (findGuest(name) == guestNames.end() && !unavailableUnused(index, name)) 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/parsing/ElfReader.cpp b/core/relinker/relinker/src/parsing/ElfReader.cpp index f112da1bea..f4a874884a 100644 --- a/core/relinker/relinker/src/parsing/ElfReader.cpp +++ b/core/relinker/relinker/src/parsing/ElfReader.cpp @@ -125,6 +125,10 @@ std::vector ElfReader::ReadProgramHeaders() const { if (header.ProgramHeaderCount != 0 && header.ProgramHeaderEntrySize != 56) { throw RelinkerException("Invalid ELF program header entry size: expected 56 bytes", 0x36); } + const ByteCount tableSize = static_cast(header.ProgramHeaderCount) * header.ProgramHeaderEntrySize; + if (!_rangeFits(header.ProgramHeaderOffset, tableSize, _fileBuffer.size())) { + throw RelinkerException("FileByteOffset out of bounds", header.ProgramHeaderOffset); + } std::vector headers; FileByteOffset offset = header.ProgramHeaderOffset; @@ -220,6 +224,10 @@ std::vector ElfReader::ReadSectionHeaders() const { sh.Info = _readU32At(offset + 0x2c); sh.EntrySize = _readU64At(offset + kShdrEntSizeOffset); + if (sh.Type == SHT_SYMTAB && sh.EntrySize != kSymEntrySize) { + throw RelinkerException("Invalid symbol table entry size", 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); diff --git a/core/relinker/relinker/src/pipeline/Conversion.cpp b/core/relinker/relinker/src/pipeline/Conversion.cpp index 628ec27254..7669e351cd 100644 --- a/core/relinker/relinker/src/pipeline/Conversion.cpp +++ b/core/relinker/relinker/src/pipeline/Conversion.cpp @@ -147,7 +147,7 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti std::vector guestArtifacts; if (!input.Modules.empty()) { - guestArtifacts = Relinker::GuestModuleBuilder().Build(input.InputPath, input.OutputPath, result.DynamicSection, windows, macos, options.ToIntel, options.ToRosetta, *syscallScanner, options.LazyBinding, options.RunPath, options.ExcludedModules, input.ModuleRoot, input.RuntimeDirectories, std::move(input.Modules), inputProgress); + guestArtifacts = Relinker::GuestModuleBuilder().Build(input.InputPath, input.OutputPath, result.DynamicSection, windows, macos, options.ToIntel, options.ToRosetta, *syscallScanner, options.LazyBinding, options.OmitUnavailableDependencies, options.RunPath, options.ExcludedModules, input.ModuleRoot, input.RuntimeDirectories, std::move(input.Modules), inputProgress); } auto byteWriter = std::make_shared(); diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index a04f455857..aa81d36ca0 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -87,6 +87,8 @@ RelinkResult RelinkerPipeline::Relink() { ByteCount gotSize = 0; for (const auto& ph : programHeaders) { + if (ph.Type == PT_LOAD && (ph.Flags & ~7u) != 0) + throw RelinkerException("Unsupported PT_LOAD permission flags", ph.Offset); if (ph.Type == PT_LOAD && (ph.Flags & PF_X) != 0) { auto segment = elfReader->ReadSegment(ph); if (!segment.empty()) { @@ -126,6 +128,15 @@ RelinkResult RelinkerPipeline::Relink() { if (!hasDynamicSegment) throw RelinkerException("No PT_DYNAMIC segment found"); + bool hasEhFrameSegment = false; + for (const auto& ph : programHeaders) { + if (ph.Type != PT_GNU_EH_FRAME) + continue; + if (hasEhFrameSegment) + throw RelinkerException("Multiple PT_GNU_EH_FRAME segments", ph.Offset); + hasEhFrameSegment = true; + } + auto hasTag = [&](const std::int64_t tag) { for (const auto& t : dynTags) if (t.Tag == tag) @@ -347,9 +358,12 @@ RelinkResult RelinkerPipeline::Relink() { const FileByteOffset symOff = symbolEntryPos(symIdx); std::uint16_t section = 0; std::memcpy(§ion, raw.data() + symOff + 6, sizeof(section)); + const auto type = raw[symOff + 4] & 15; + if (section == SHN_UNDEF && type != STT_TLS) + throw RelinkerException("TLS relocation names an undefined symbol that is not STT_TLS", pos); if (section == SHN_UNDEF) return std::nullopt; - if ((raw[symOff + 4] & 15) != STT_TLS) + if (type != STT_TLS) throw RelinkerException("TLS relocation names a defined symbol that is not STT_TLS", pos); if (section >= SHN_LORESERVE) throw RelinkerException("Unsupported executable TLS symbol section", pos); @@ -526,7 +540,7 @@ RelinkResult RelinkerPipeline::Relink() { } exportedSymbols.emplace_back(off, std::move(name)); const auto binding = symbol[4] >> 4; - if ((binding == STB_GLOBAL || binding == STB_WEAK) && type == STT_FUNC) + if ((binding == STB_GLOBAL || binding == STB_WEAK) && type == STT_FUNC && section != SHN_ABS) exportedFunctions.push_back(value); } } diff --git a/core/relinker/relinker/tests/CallSiteResolverTests.cpp b/core/relinker/relinker/tests/CallSiteResolverTests.cpp index 16ca224079..5bbf57ba02 100644 --- a/core/relinker/relinker/tests/CallSiteResolverTests.cpp +++ b/core/relinker/relinker/tests/CallSiteResolverTests.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -12,14 +13,48 @@ void require(bool condition, const std::string& name) { if (!condition) throw std::runtime_error(name); } -void check(const std::string& name, std::initializer_list instruction, bool expected) { +void check(const std::string& name, const std::vector& text, bool expected) { auto resolver = Relinker::MakeCallSiteResolver(); - const std::vector text(instruction); const auto sites = resolver->ResolveCallSites(text, 0x1000, 0x1100, 8); require(sites == (expected ? std::vector{0x1000} : std::vector{}), name); require(resolver->ResolveCallSites(text, 0x1000, 0x1200, 8).empty(), name + " unexpected address"); } +void checkAddressSize() { + for (const auto prefix : {std::vector{0x67}, {0xf2, 0x67}, {0x3e, 0x67}, {0x67, 0x67}, {0x2e, 0x67}, {0x67, 0x26}, {0x36, 0x67}}) { + for (const auto opcode : {std::vector{0xff, 0x15}, {0xff, 0x25}, {0xff, 0x35}, {0x48, 0x8b, 0x05}}) { + for (const std::uint64_t address : {0x1000ULL, 0xfffff000ULL, 0x100001000ULL}) { + auto resolver = Relinker::MakeCallSiteResolver(); + auto text = prefix; + text.insert(text.end(), opcode.begin(), opcode.end()); + text.resize(text.size() + 4); + constexpr std::uint64_t slot = 0x2800; + const auto displacement = static_cast(slot - address - text.size()); + std::memcpy(text.data() + text.size() - 4, &displacement, 4); + const std::vector expected{address}; + require(resolver->ResolveCallSites(text, address, slot, 8) == expected, "EIP-relative import access missing"); + require(resolver->ResolveCallSites(text, address, slot + 0x100000000ULL, 8).empty(), "EIP-relative access used the unwrapped slot"); + require(resolver->ResolveCallSites(text, address, slot, 0).empty(), "Empty EIP-relative target range matched"); + require(resolver->ResolveCallSites(text, address, slot - 1, 1).empty(), "EIP-relative target matched range end"); + require(resolver->ResolveCallSites(text, address, slot - 1, 2) == expected, "EIP-relative target missing inside range"); + } + } + } + const std::vector rip{0x48, 0x8b, 0x05, 0xf9, 0x17, 0, 0}; + auto resolver = Relinker::MakeCallSiteResolver(); + require(resolver->ResolveCallSites(rip, 0x100001000ULL, 0x100002800ULL, 8) == std::vector{0x100001000ULL}, "RIP-relative access was truncated"); + require(resolver->ResolveCallSites(rip, 0x100001000ULL, 0x2800, 8).empty(), "RIP-relative access matched a wrapped slot"); +} + +std::vector ripInstruction(std::vector prefixes, std::uint8_t opcode, std::uint8_t modrm) { + prefixes.push_back(opcode); + prefixes.push_back(modrm); + const auto displacement = static_cast(0x100 - (prefixes.size() + 4)); + for (unsigned shift = 0; shift < 32; shift += 8) + prefixes.push_back(static_cast(displacement >> shift)); + return prefixes; +} + } int main() { @@ -50,7 +85,11 @@ int main() { 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("address-size override is an EIP-relative reference", {0x3e, 0x67, 0xff, 0x15, 0xf8, 0, 0, 0}, true); + check("address-size prefix after REX.W leaves a narrow load", {0x48, 0x67, 0x8b, 0x05, 0xf8, 0, 0, 0}, false); + check("REX.W after address-size prefix widens a load", {0x67, 0x48, 0x8b, 0x05, 0xf8, 0, 0, 0}, true); + check("FS EIP-relative call is not a flat GOT reference", {0x64, 0x67, 0xff, 0x15, 0xf8, 0, 0, 0}, false); + check("GS EIP-relative load is not a flat GOT reference", {0x67, 0x65, 0x48, 0x8b, 0x05, 0xf7, 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); @@ -60,6 +99,26 @@ int main() { check("increment memory", {0xff, 0x05, 0xfa, 0, 0, 0}, false); check("decrement memory", {0xff, 0x0d, 0xfa, 0, 0, 0}, false); check("push memory", {0xff, 0x35, 0xfa, 0, 0, 0}, true); + checkAddressSize(); + for (const std::uint8_t segment : {0x2e, 0x26, 0x36, 0x3e}) { + const auto name = "segment " + std::to_string(segment); + for (const std::uint8_t modrm : {0x15, 0x1d, 0x25, 0x2d, 0x35}) + check(name + " indirect access " + std::to_string(modrm), ripInstruction({segment}, 0xff, modrm), true); + check(name + " pointer load", ripInstruction({segment, 0x48}, 0x8b, 0x05), true); + check(name + " before BND call", ripInstruction({segment, 0xf2}, 0xff, 0x15), true); + check(name + " after BND jump", ripInstruction({0xf2, segment}, 0xff, 0x25), true); + check(name + " after REX.W leaves a narrow load", ripInstruction({0x48, segment}, 0x8b, 0x05), false); + check(name + " final REX.W restores pointer width", ripInstruction({0x48, segment, 0x48}, 0x8b, 0x05), true); + check(name + " final narrow REX", ripInstruction({segment, 0x48, 0x40}, 0x8b, 0x05), false); + check(name + " increment", ripInstruction({segment}, 0xff, 0x05), false); + check(name + " decrement", ripInstruction({segment}, 0xff, 0x0d), false); + check(name + " narrow load", ripInstruction({segment}, 0x8b, 0x05), false); + for (const std::uint8_t unsupported : {0x64, 0x65, 0x66, 0xf0, 0xf3}) { + const auto suffix = " unsupported prefix " + std::to_string(unsupported); + check(name + " before" + suffix, ripInstruction({segment, unsupported}, 0xff, 0x15), false); + check(name + " after" + suffix, ripInstruction({unsupported, segment}, 0xff, 0x15), false); + } + } std::cout << "Call-site resolver tests passed\n"; return 0; } catch (const std::exception& error) { diff --git a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp index 2e2674124b..aa96305059 100644 --- a/core/relinker/relinker/tests/ControlFlowGraphTests.cpp +++ b/core/relinker/relinker/tests/ControlFlowGraphTests.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -220,6 +221,66 @@ void CheckAddressSizeOverride() { RequireOffsets(*BuildAddressTakenControlFlowGraph(lea, TextAddress, TextAddress, {}, missing, {}), {0, 8}); } +void CheckRipDisplacementBounds() { + constexpr auto low = VirtualAddress{0x10}; + constexpr auto high = std::numeric_limits::max() - 16; + const auto build = [](Bytes prefix, std::int32_t displacement, VirtualAddress base, VirtualAddress slot) { + Bytes text(16, 0xc3); + std::copy(prefix.begin(), prefix.end(), text.begin()); + std::memcpy(text.data() + prefix.size(), &displacement, sizeof(displacement)); + Pointers pointers; + pointers.Slots = {{slot, base + 12}}; + return BuildControlFlowGraph(text, base, base, {}, pointers); + }; + + const auto next = low + 6; + const auto underflowCall = build({0xff, 0x15}, -0x20, low, next - 0x20); + std::unordered_set expectedUnderflow{low, low + 6, low + 12}; + Require(underflowCall->ReachableVaddrs() == expectedUnderflow, "Unexpected reachable instruction set for underflowing call target"); + + const auto underflowJump = build({0xff, 0x25}, -0x20, low, next - 0x20); + std::unordered_set expectedUnderflowJump{low, low + 12}; + Require(underflowJump->ReachableVaddrs() == expectedUnderflowJump, "Unexpected reachable instruction set for underflowing jump target"); + + Bytes underflowLeaText(16, 0xc3); + const Bytes leaPrefix = {0x48, 0x8d, 0x05}; + std::copy(leaPrefix.begin(), leaPrefix.end(), underflowLeaText.begin()); + const std::int32_t underflowDisp = -0x20; + std::memcpy(underflowLeaText.data() + leaPrefix.size(), &underflowDisp, sizeof(underflowDisp)); + Pointers emptyPointers; + const auto underflowLea = BuildControlFlowGraph(underflowLeaText, low, low, {}, emptyPointers); + std::unordered_set expectedUnderflowLea{low, low + 7}; + Require(underflowLea->ReachableVaddrs() == expectedUnderflowLea, "Unexpected reachable instruction set for underflowing LEA target"); + + const auto overflowCall = build({0xff, 0x15}, 0x20, high, high + 6 + 0x20); + std::unordered_set expectedOverflow{high, high + 6, high + 12}; + Require(overflowCall->ReachableVaddrs() == expectedOverflow, "Unexpected reachable instruction set for overflowing call target"); + + const auto overflowJump = build({0xff, 0x25}, 0x20, high, high + 6 + 0x20); + std::unordered_set expectedOverflowJump{high, high + 12}; + Require(overflowJump->ReachableVaddrs() == expectedOverflowJump, "Unexpected reachable instruction set for overflowing jump target"); + + Bytes overflowLeaText(16, 0xc3); + std::copy(leaPrefix.begin(), leaPrefix.end(), overflowLeaText.begin()); + const std::int32_t overflowDisp = 0x20; + std::memcpy(overflowLeaText.data() + leaPrefix.size(), &overflowDisp, sizeof(overflowDisp)); + const auto overflowLea = BuildControlFlowGraph(overflowLeaText, high, high, {}, emptyPointers); + std::unordered_set expectedOverflowLea{high, high + 7}; + Require(overflowLea->ReachableVaddrs() == expectedOverflowLea, "Unexpected reachable instruction set for overflowing LEA target"); + + const auto validZero = build({0xff, 0x15}, -static_cast(next), low, 0); + std::unordered_set expectedZero{low, low + 6, low + 12}; + Require(validZero->ReachableVaddrs() == expectedZero, "Unexpected reachable instruction set for target 0"); + + const auto validMax = build({0xff, 0x15}, 10, high, high + 16); + std::unordered_set expectedMax{high, high + 6, high + 12}; + Require(validMax->ReachableVaddrs() == expectedMax, "Unexpected reachable instruction set for target max"); + + const auto validEip = build({0x67, 0xff, 0x15}, -0x20, low, static_cast(low + 7 - 0x20)); + std::unordered_set expectedEip{low, low + 7, low + 12}; + Require(validEip->ReachableVaddrs() == expectedEip, "Unexpected reachable instruction set for 0x67 wrap"); +} + void CheckCodeAddresses() { Bytes text(17, 0x90); text[0] = 0x48; @@ -488,6 +549,49 @@ void CheckRelativeJumpTables() { Require(Relinker::FindRelativeJumpTables(cutShort, TextAddress, {{TextAddress, TextAddress + cutShort.size()}}).empty(), "Table followed by more of its function was accepted"); } +Bytes GapStubTable(std::initializer_list stub) { + constexpr std::size_t function = 16; + constexpr std::size_t tableOffset = 40; + Bytes text(64, 0x90); + for (std::size_t i = 0; i < function; ++i) text[i] = 0xcc; + std::copy(stub.begin(), stub.end(), text.begin()); + text[function] = 0x48; + text[function + 1] = 0x8d; + text[function + 2] = 0x05; + SetDisplacement(text, function + 3, function + 7, TextAddress + tableOffset); + text[function + 7] = 0xe8; + SetDisplacement(text, function + 8, function + 12, TextAddress); + text[function + 12] = 0xc3; + for (std::size_t i = function + 13; i < tableOffset; ++i) text[i] = 0xcc; + text[tableOffset] = 0x0f; + text[tableOffset + 1] = 0x78; + text[tableOffset + 2] = 0x33; + text[tableOffset + 3] = 0x1e; + return text; +} + +std::vector GapStubTables(const Bytes& text) { + return Relinker::FindRelativeJumpTables(text, TextAddress, {{TextAddress + 16, TextAddress + 29}, {TextAddress + text.size(), TextAddress + text.size() + 8}}); +} + +void CheckGapWalkBranchTargets() { + const auto returning = GapStubTables(GapStubTable({0xc3})); + Require(returning.size() == 1 && returning[0].Begin == TextAddress + 40, "A gap stub that returns hid the data table"); + + Bytes direct = GapStubTable({0xe9, 0, 0, 0, 0}); + SetDisplacement(direct, 1, 5, TextAddress + 40); + Require(GapStubTables(direct).empty(), "A jump from a called gap stub into the table was missed"); + + Bytes afterOneByte = GapStubTable({0x55, 0xe9, 0, 0, 0, 0}); + SetDisplacement(afterOneByte, 2, 6, TextAddress + 40); + Require(GapStubTables(afterOneByte).empty(), "A jump after a one-byte instruction in a gap stub was missed"); + + Bytes chained = GapStubTable({0xe9, 0, 0, 0, 0, 0xc3, 0xcc, 0xcc, 0xe9, 0, 0, 0, 0}); + SetDisplacement(chained, 1, 5, TextAddress + 8); + SetDisplacement(chained, 9, 13, TextAddress + 40); + Require(GapStubTables(chained).empty(), "A jump from a gap stub reached only through another gap stub was missed"); +} + void CheckEmbeddedDataTables() { constexpr std::size_t tableOffset = 24; Bytes text(48, 0x90); @@ -751,6 +855,7 @@ int main() { CheckRelocatedTransfers(); CheckSegmentRelativeTransfers(); CheckAddressSizeOverride(); + CheckRipDisplacementBounds(); CheckCodeAddresses(); CheckSpeculativeRoots(); CheckTrailingZeroRoots(); @@ -765,6 +870,7 @@ int main() { CheckStubBeforeUndecodableData(); CheckTableWithPaddingAndReturnBytes(); CheckLargeZeroGaps(); + CheckGapWalkBranchTargets(); 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 60a5bb4e85..0b73c77b26 100644 --- a/core/relinker/relinker/tests/ConversionTests.cpp +++ b/core/relinker/relinker/tests/ConversionTests.cpp @@ -283,7 +283,7 @@ void scanning() { Io::WriteU64(wrappingImage, 0x128, 0); reject([&] { Relinker::CodeInstructionCollector().Collect(wrappingImage, {execHeader, dynamicHeader, wrappingDataHeader}); }); const auto roots = Relinker::CodeInstructionCollector().Collect(wrappingImage, {execHeader, dynamicHeader, controlDataHeader}); - require(roots == std::set{0x2000, 0x2008}, "Valid initializer array roots were not collected"); + require(roots == std::vector{0x2000, 0x2008}, "Valid initializer array roots were not collected"); Io::WriteU64(wrappingImage, 0x3c0, 0); reject([&] { Relinker::CodeInstructionCollector().Collect(wrappingImage, {execHeader, dynamicHeader, wrappingDataHeader}); }); Io::WriteU64(wrappingImage, 0x3c0, 0x2008); @@ -310,8 +310,8 @@ void branchAcrossSignedBoundary() { header.MemorySize = header.FileSize; Io::WriteU64(image, 24, 0x8000000000000000); const auto instructions = Relinker::CodeInstructionCollector().Collect(image, {header}); - require(instructions.contains(0x8000000000000000), "Lost the entry above the signed boundary"); - require(instructions.contains(0x7ffffffffffffff0), "Lost the branch target below the signed boundary"); + require(std::binary_search(instructions.begin(), instructions.end(), 0x8000000000000000ull), "Lost the entry above the signed boundary"); + require(std::binary_search(instructions.begin(), instructions.end(), 0x7ffffffffffffff0ull), "Lost the branch target below the signed boundary"); } class FailingProgress final : public Domain::IProgress { @@ -415,21 +415,6 @@ void runtimeDirectories() { require(output.Modules.size() == 1, "Module was not converted"); require(exports(output.Modules.front().Bytes, Relinker::GuestInitializeExport) == (std::string_view(directory) == "Media/Modules"), "Wrong start of a runtime module the executable needs"); } - for (const auto* directory : {"sce_module", "sce_modules", "prx", "Media/Modules"}) { - auto source = input(); - const auto parent = source.ModuleRoot / directory; - const auto plugins = source.ModuleRoot / "Media/Plugins"; - source.Modules.push_back({plugins / "plugin.prx", fixture("needed.prx")}); - source.Modules.push_back({parent / "needed.prx", fixture()}); - source.RuntimeDirectories.push_back(plugins); - source.RuntimeDirectories.push_back(parent); - const auto output = converter.Convert(std::move(source), options); - require(output.Modules.size() == 2, "Modules were not converted"); - for (const auto& module : output.Modules) { - const bool plugin = module.SourcePath.filename() == "plugin.prx"; - require(exports(module.Bytes, Relinker::GuestInitializeExport) == (plugin || std::string_view(directory) == "Media/Modules"), "Wrong start of a runtime module another runtime module needs"); - } - } } void relocationValidation() { diff --git a/core/relinker/relinker/tests/DynamicTableBoundsTests.cpp b/core/relinker/relinker/tests/DynamicTableBoundsTests.cpp index c91f3640e0..4b7eccaad9 100644 --- a/core/relinker/relinker/tests/DynamicTableBoundsTests.cpp +++ b/core/relinker/relinker/tests/DynamicTableBoundsTests.cpp @@ -17,8 +17,12 @@ const Bytes kCode = {0xC3}; constexpr std::size_t kTagStride = 16; constexpr std::size_t kTagBase = kCodeOffset + kDynamicVaddr; +constexpr std::size_t kStrTabTag = 0; constexpr std::size_t kSymTabTag = 2; +constexpr std::size_t kRelaTag = 4; constexpr std::size_t kRelaSzTag = 5; +constexpr std::int64_t kDtStrTab = 5; +constexpr std::int64_t kDtRela = 7; constexpr std::int64_t kDtOsSymtab = 0x61000039; constexpr std::int64_t kDtRelaSz = 8; constexpr std::uint64_t kBogusOffset = 0xFFFFFFFFFFFFFFFEull; @@ -45,16 +49,41 @@ int main(const int argc, char** argv) { const auto input = directory.Path() / "input.elf"; const auto output = directory.Path() / "output.elf"; - Bytes image = MakeExecutable(kCode); - WriteRelaEntry(image, (1ull << 32) | 6ull); - WriteTag(image, kRelaSzTag, kDtRelaSz, 24); - WriteTag(image, kSymTabTag, kDtOsSymtab, kBogusOffset); - WriteFile(input, image); + { + Bytes image = MakeExecutable(kCode); + WriteRelaEntry(image, (1ull << 32) | 6ull); + WriteTag(image, kRelaSzTag, kDtRelaSz, 24); + WriteTag(image, kSymTabTag, kDtOsSymtab, kBogusOffset); + WriteFile(input, image); + + const auto run = RunRelinker(binary, {"--skip-sce-module", "--to-intel", input.string(), output.string()}, directory.Path() / "relinker.log"); + require(run.ExitCode != 0, "Relinker read a symbol table outside the file and exited 0:\n" + run.Output); + require(run.Output.find("Symbol table entry out of bounds") != std::string::npos, + "Relinker did not report the symbol table entry as out of bounds:\n" + run.Output); + } + + { + Bytes image = MakeExecutable(kCode); + WriteTag(image, kRelaTag, kDtRela, kBogusOffset); + WriteFile(input, image); + + const auto run = RunRelinker(binary, {"--skip-sce-module", "--to-intel", input.string(), output.string()}, directory.Path() / "relinker.log"); + require(run.ExitCode != 0, "Relinker read a relocation table outside the file and exited 0:\n" + run.Output); + require(run.Output.find("Virtual address not mapped by any PT_LOAD segment") != std::string::npos, + "Relinker did not report the relocation table entry as out of bounds:\n" + run.Output); + } + + { + Bytes image = MakeExecutable(kCode); + WriteTag(image, kStrTabTag, kDtStrTab, kBogusOffset); + WriteFile(input, image); + + const auto run = RunRelinker(binary, {"--skip-sce-module", "--to-intel", input.string(), output.string()}, directory.Path() / "relinker.log"); + require(run.ExitCode != 0, "Relinker read a string table outside the file and exited 0:\n" + run.Output); + require(run.Output.find("Virtual address not mapped by any PT_LOAD segment") != std::string::npos, + "Relinker did not report the string table entry as out of bounds:\n" + run.Output); + } - const auto run = RunRelinker(binary, {"--skip-sce-module", "--to-intel", input.string(), output.string()}, directory.Path() / "relinker.log"); - require(run.ExitCode != 0, "Relinker read a symbol table outside the file and exited 0:\n" + run.Output); - require(run.Output.find("Symbol table entry out of bounds") != std::string::npos, - "Relinker did not report the symbol table entry as out of bounds:\n" + run.Output); return 0; } catch (const std::exception& error) { std::cerr << "dynamic_table_bounds_tests: " << error.what() << '\n'; diff --git a/core/relinker/relinker/tests/ElfReaderBoundsTests.cpp b/core/relinker/relinker/tests/ElfReaderBoundsTests.cpp index b1122ee8a7..154a699dd9 100644 --- a/core/relinker/relinker/tests/ElfReaderBoundsTests.cpp +++ b/core/relinker/relinker/tests/ElfReaderBoundsTests.cpp @@ -93,10 +93,16 @@ void RejectAddress(Bytes bytes, const std::uint64_t address) { throw std::runtime_error("Invalid file mapping was accepted"); } -void RejectProgramHeaders(Bytes bytes) { +void RejectProgramHeaders(Bytes bytes, const std::string& expectedMessage = "", const std::uint64_t expectedOffset = std::numeric_limits::max()) { try { Relinker::ElfReader(std::move(bytes)).ReadProgramHeaders(); - } catch (const Relinker::RelinkerException&) { + } catch (const Relinker::RelinkerException& error) { + if (!expectedMessage.empty()) { + Require(std::string(error.what()).find(expectedMessage) != std::string::npos); + } + if (expectedOffset != std::numeric_limits::max()) { + Require(error.FailureOffset == expectedOffset); + } return; } throw std::runtime_error("Invalid program header was accepted"); @@ -106,6 +112,12 @@ void RejectProgramHeaders(Bytes bytes) { int main() { const std::vector>> cases = { + {"load file size exceeds memory size", [] { + auto bytes = Fixture(); + Write(bytes, 96, 64); + Write(bytes, 104, 32); + RejectProgramHeaders(std::move(bytes)); + }}, {"load file range past end of file", [] { auto bytes = Fixture(); Write(bytes, 96, 0xd0); @@ -190,10 +202,44 @@ int main() { Write(bytes, 56, 3); RejectProgramHeaders(std::move(bytes)); }}, + {"program header table out of bounds", [] { + for (const std::uint64_t offset : {std::uint64_t{0x300}, std::uint64_t{0x300} - 55, std::numeric_limits::max() - 31}) { + auto bytes = Fixture(); + Write(bytes, 32, offset); + RejectProgramHeaders(std::move(bytes), "FileByteOffset out of bounds", offset == std::uint64_t{0x300} - 55 ? offset : std::numeric_limits::max()); + } + auto truncated = Fixture(); + truncated.resize(64 + 56 - 1); + RejectProgramHeaders(std::move(truncated), "FileByteOffset out of bounds", 0x40); + auto precedence = Fixture(); + Write(precedence, 56, 20); + Write(precedence, 0x40 + 0x20, 0x200); + Write(precedence, 0x40 + 0x28, 0x100); + RejectProgramHeaders(std::move(precedence), "FileByteOffset out of bounds", 0x40); + auto zeroHeadersOobOffset = Fixture(); + Write(zeroHeadersOobOffset, 56, 0); + Write(zeroHeadersOobOffset, 32, std::numeric_limits::max()); + RejectProgramHeaders(std::move(zeroHeadersOobOffset), "FileByteOffset out of bounds", std::numeric_limits::max()); + }}, {"section entry size field", [] { const auto sections = Relinker::ElfReader(Fixture()).ReadSectionHeaders(); Require(sections.size() == 3 && sections[1].Name == ".text" && sections[1].EntrySize == 24); }}, + {"SHT_SYMTAB section entry size mismatch", [] { + for (const std::uint64_t entrySize : {0ULL, 1ULL, 16ULL, 20ULL, 23ULL, 25ULL, 32ULL, 64ULL, 0xffffffffffffffffULL}) { + auto bytes = Fixture(); + Write(bytes, 196, 2); + Write(bytes, 248, entrySize); + RejectSections(std::move(bytes)); + } + }}, + {"SHT_SYMTAB section entry size valid", [] { + auto bytes = Fixture(); + Write(bytes, 196, 2); + Write(bytes, 248, 24); + const auto sections = Relinker::ElfReader(std::move(bytes)).ReadSectionHeaders(); + Require(sections.size() == 3 && sections[1].Type == 2 && sections[1].EntrySize == 24); + }}, {"section header stride", [] { for (const std::uint16_t stride : {0, 56, 65}) { auto bytes = Fixture(); diff --git a/core/relinker/relinker/tests/ImportModuleTests.cpp b/core/relinker/relinker/tests/ImportModuleTests.cpp new file mode 100644 index 0000000000..a935c421b8 --- /dev/null +++ b/core/relinker/relinker/tests/ImportModuleTests.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include +using Bytes = std::vector; +static void Ph(Bytes& b, int i, unsigned type, unsigned flags, unsigned off, unsigned va, unsigned size) { + const auto p=64+i*56; Io::WriteU32(b,p,type); Io::WriteU32(b,p+4,flags); + Io::WriteU64(b,p+8,off);Io::WriteU64(b,p+16,va);Io::WriteU64(b,p+24,va); + Io::WriteU64(b,p+32,size);Io::WriteU64(b,p+40,size);Io::WriteU64(b,p+48,8); +} +static Bytes Fixture(bool reverse=false) { + Bytes b(0x4000);std::memcpy(b.data(),"\x7f" "ELF\x02\x01\x01",7); + Io::WriteU16(b,16,3);Io::WriteU16(b,18,62);Io::WriteU32(b,20,1); + Io::WriteU64(b,32,64);Io::WriteU16(b,52,64);Io::WriteU16(b,54,56);Io::WriteU16(b,56,3); + Ph(b,0,1,5,0x1000,0,0x1000);Ph(b,1,1,6,0x2000,0x2000,0x1000); + b[0x1020]=0xc3;b[0x1028]=0xc3; + std::string strings(1,'\0');auto str=[&](const char* s){auto o=strings.size();strings+=s;strings+='\0';return o;}; + const auto a=str(reverse?"alias#B#A":"alias#A#A"),c=str(reverse?"alias#A#A":"alias#B#A"); + const auto imp=str("imported#A#B"),host=str("libmonosgen-2.0.prx"),module=str("fixtureModule"),logical=str("libmonosgen"); + std::copy(strings.begin(),strings.end(),b.begin()+0x2200); + const std::pair tags[]={{5,0x2200},{10,strings.size()},{6,0x2400},{11,24},{4,0x2600},{7,0x2700},{8,48},{9,24},{1,host},{0x61000043,module},{0x61000045,(1ull<<48)|logical},{0,0}}; + Ph(b,2,2,6,0x2000,0x2000,sizeof(tags)); + for(unsigned i=0;i modules{{1, "libmonosgen"}}; + const std::array matching{"libmonosgen-2.0.prx"}; + const std::array other{"libmonosgen-3.0.prx"}; + const std::array ambiguous{"libmonosgen-2.0.prx", "libmonosgen-2.0.sprx"}; + if (Domain::ImportModule("nid#A#B", modules, matching) != matching[0]) return 1; + if (!Domain::ImportModule("nid#A#B", modules, other).empty()) return 2; + try { Domain::ImportModule("nid#A#B", modules, ambiguous); return 3; } + catch (const Domain::RelinkerException&) {} + const std::map versioned{{1, "libmonosgen-2.0"}}; + const std::array legacy{"libmonosgen.prx"}; + const std::array both{"libmonosgen.prx", "libmonosgen-2.0.prx"}; + if (!Domain::ImportModule("nid#A#B", versioned, legacy).empty()) return 5; + if (Domain::ImportModule("nid#A#B", modules, both) != both[0]) return 6; + const std::array normalizedMono{"libmonosgen-module.prx"}; + if (Domain::ImportModule("nid#A#B", modules, normalizedMono) != normalizedMono[0]) return 43; + std::cout << "module mapping checks passed\n"; +} diff --git a/core/relinker/relinker/tests/RelrRelocationTests.cpp b/core/relinker/relinker/tests/RelrRelocationTests.cpp index 993b71e4a1..8afb2d6f8b 100644 --- a/core/relinker/relinker/tests/RelrRelocationTests.cpp +++ b/core/relinker/relinker/tests/RelrRelocationTests.cpp @@ -105,6 +105,8 @@ int main() try { Reject([](auto& input) { input.SetWords({0x200, 0x206}); }, "Overlapping RELR targets"); Reject([](auto& input) { input.SetWords({0x3000}); }, "target exceeds load"); Reject([](auto& input) { input.SetWords({0x2ffc}); }, "target exceeds load"); + Reject([](auto& input) { input.SetWords({0x4000}); }, "target exceeds load"); + Reject([](auto& input) { input.SetWords({0x2ff8, 3}); }, "target exceeds load"); Reject([](auto& input) { input.Tags[0].Value = 0x2000; }, "table exceeds file-backed"); Reject([](auto& input) { input.Tags[0].Value = 0x1ffc; }, "table exceeds file-backed"); Reject([](auto& input) { input.Tags[1].Value = 0xfffffffffffffff8ull; }, "table exceeds file-backed"); diff --git a/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp b/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp index 209e2cc21e..a69b2b4973 100644 --- a/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp +++ b/core/relinker/relinker/tests/SdkMmanSyscallTests.cpp @@ -10,20 +10,32 @@ namespace { using Bytes = std::vector; -struct Fixture { Bytes code; std::size_t syscall; }; +struct Fixture { Bytes code; std::size_t syscall; std::size_t numberOffset = 0; }; const std::vector Fixtures = { {{0x55, 0x48, 0x89, 0xE5, 0x53, 0x50, 0x89, 0xD1, 0x49, 0x89, 0xF0, 0x48, 0x89, 0xFE, 0xF6, 0xC1, 0x04, 0x75, 0x31, 0x48, 0x63, 0xD1, 0xB8, 0x4A, 0x00, 0x00, 0x00, 0x48, 0x89, 0xF7, 0x4C, 0x89, 0xC6, 0x45, 0x31, 0xD2, 0x45, 0x31, 0xC0, 0x45, 0x31, 0xC9, 0x0F, 0x05, 0x0F, 0x93, 0xC1, 0x48, 0x89, 0xC3, 0x48, 0xF7, 0xDB, 0x84, 0xC9, - 0x48, 0x0F, 0x45, 0xD8, 0x48, 0x85, 0xDB}, 42}, + 0x48, 0x0F, 0x45, 0xD8, 0x48, 0x85, 0xDB}, 42, 23}, {{0x49, 0x63, 0xD6, 0x4C, 0x63, 0xD1, 0x4D, 0x63, 0xC0, 0xB8, 0xDD, 0x01, 0x00, 0x00, 0x48, 0x89, 0xDE, 0x0F, 0x05, 0x48, 0x89, 0xC1, 0x0F, 0x93, 0xC2, 0x48, 0xF7, 0xD8, - 0x84, 0xD2, 0x48, 0x0F, 0x45, 0xC1, 0x48, 0x85, 0xC0}, 17}, + 0x84, 0xD2, 0x48, 0x0F, 0x45, 0xC1, 0x48, 0x85, 0xC0}, 17, 10}, {{0x44, 0x89, 0xF0, 0x83, 0xE0, 0xFB, 0x48, 0x63, 0xD0, 0x4C, 0x63, 0xD1, 0x4D, 0x63, 0xC0, 0xB8, 0xDD, 0x01, 0x00, 0x00, 0x48, 0x89, 0xDE, 0x0F, 0x05, 0x0F, 0x93, 0xC1, - 0x49, 0x89, 0xC7, 0x49, 0xF7, 0xDF, 0x84, 0xC9, 0x4C, 0x0F, 0x45, 0xF8, 0x4D, 0x85, 0xFF}, 23}, + 0x49, 0x89, 0xC7, 0x49, 0xF7, 0xDF, 0x84, 0xC9, 0x4C, 0x0F, 0x45, 0xF8, 0x4D, 0x85, 0xFF}, 23, 16}, + {{0x55, 0x48, 0x89, 0xE5, 0x41, 0x56, 0x53, 0x48, 0x63, 0xD2, 0x31, 0xDB, + 0xB8, 0x15, 0x00, 0x00, 0x00, 0x49, 0x89, 0xCA, 0x45, 0x31, 0xC0, 0x45, 0x31, 0xC9, + 0x0F, 0x05, 0x0F, 0x93, 0xC1, 0x49, 0x89, 0xC6, 0x49, 0xF7, 0xDE, 0x84, 0xC9, + 0x4C, 0x0F, 0x45, 0xF0, 0x4D, 0x85, 0xF6}, 26, 13}, + {{0x55, 0x48, 0x89, 0xE5, 0x41, 0x56, 0x53, 0x89, 0xF6, 0x48, 0x63, 0xD2, 0x31, 0xDB, + 0xB8, 0x7A, 0x01, 0x00, 0x00, 0x45, 0x31, 0xD2, 0x45, 0x31, 0xC0, 0x45, 0x31, 0xC9, + 0x0F, 0x05, 0x0F, 0x93, 0xC1, 0x49, 0x89, 0xC6, 0x49, 0xF7, 0xDE, 0x84, 0xC9, + 0x4C, 0x0F, 0x45, 0xF0, 0x4D, 0x85, 0xF6}, 28, 15}, + {{0x55, 0x48, 0x89, 0xE5, 0x41, 0x56, 0x53, 0x48, 0x63, 0xF6, 0x31, 0xDB, + 0xB8, 0x16, 0x00, 0x00, 0x00, 0x31, 0xD2, 0x45, 0x31, 0xD2, 0x45, 0x31, 0xC0, 0x45, 0x31, 0xC9, + 0x0F, 0x05, 0x0F, 0x93, 0xC1, 0x49, 0x89, 0xC6, 0x49, 0xF7, 0xDE, 0x84, 0xC9, + 0x4C, 0x0F, 0x45, 0xF0, 0x4D, 0x85, 0xF6}, 28, 13}, }; void Require(bool value, const std::string& message) { @@ -56,6 +68,17 @@ void CheckFixtures() { if (Rejected(fragment, false, fragment.size())) Require(Rejected(fragment, true, fragment.size()), "SDK matching accepted a truncated prefix"); } + if (fixture.numberOffset != 0) { + std::uint32_t own = 0; + for (std::size_t i = 0; i < 4; ++i) own |= static_cast(fixture.code[fixture.numberOffset + i]) << (8 * i); + for (const std::uint32_t number : {0u, 1u, 0x15u, 0x16u, 0x17Au}) { + if (number == own) continue; + auto code = fixture.code; + for (std::size_t i = 0; i < 4; ++i) code[fixture.numberOffset + i] = static_cast(number >> (8 * i)); + Require(Rejected(code, true, code.size()), "SDK matching trapped a stub with another syscall number"); + ++rejectedMutations; + } + } for (std::size_t offset = 0; offset < fixture.code.size(); ++offset) { if (offset == fixture.syscall || offset == fixture.syscall + 1) continue; for (unsigned bit = 0; bit < 8; ++bit) { diff --git a/core/relinker/relinker/tests/StrictNidFilterTests.cpp b/core/relinker/relinker/tests/StrictNidFilterTests.cpp index bed3b24283..7de2f2db69 100644 --- a/core/relinker/relinker/tests/StrictNidFilterTests.cpp +++ b/core/relinker/relinker/tests/StrictNidFilterTests.cpp @@ -32,6 +32,18 @@ void requireFailure(const std::function& operation, const char* message) throw std::runtime_error(message); } +void requireFailureMessage(const std::function& operation, const std::string& expectedSubstring, const char* message) { + try { + operation(); + } catch (const Relinker::RelinkerException& ex) { + if (std::string(ex.what()).find(expectedSubstring) != std::string::npos) { + return; + } + throw std::runtime_error(std::string("Wrong exception message: ") + ex.what() + ", expected: " + expectedSubstring); + } + throw std::runtime_error(message); +} + template void write(std::vector& bytes, std::size_t offset, TValue value) { if (offset > bytes.size() || sizeof(value) > bytes.size() - offset) throw std::runtime_error("Test fixture write is out of bounds"); @@ -115,6 +127,124 @@ void addressSizeOverrideImport() { require(AnalyzeStrictReachability(input).ImportSlots == std::set{0x100002000ull}, "RIP-relative import lost its full 64-bit address"); } +std::vector elfFixture(const StrictReachabilityInput& input); + +void ripDisplacementOverflowUnderflowBounds() { + constexpr auto maxVaddr = std::numeric_limits::max(); + + auto input = fixture(); + input.Text.assign(32, 0); + input.TextVaddr = 0x1000; + input.Entries = {input.TextVaddr}; + emit(input, 0, {0xFF, 0x15}); + write(input.Text, 2, static_cast(-0x2000)); + emit(input, 6, {0xC3}); + const auto callUnderflow = static_cast(input.TextVaddr + 6 + static_cast(static_cast(-0x2000))); + input.ImportSlots = {callUnderflow}; + const std::vector callUnderflowRefs = {{"live", {}, 7, 0x300, callUnderflow, 0}}; + require(AnalyzeStrictReachability(input).ImportSlots == std::set{callUnderflow}, "Call RIP-relative displacement underflow did not retain its import"); + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(callUnderflowRefs, elfFixture(input), input.Text, input.TextVaddr).size() == 1, + "Strict filter removed call RIP-relative displacement underflow import"); + + input.Text.assign(32, 0); + input.TextVaddr = maxVaddr - 32; + input.Entries = {input.TextVaddr}; + emit(input, 0, {0xFF, 0x15}); + write(input.Text, 2, static_cast(0x1000)); + emit(input, 6, {0xC3}); + const auto callOverflow = static_cast(input.TextVaddr + 6 + static_cast(static_cast(0x1000))); + input.ImportSlots = {callOverflow}; + const std::vector callOverflowRefs = {{"live", {}, 7, 0x300, callOverflow, 0}}; + require(AnalyzeStrictReachability(input).ImportSlots == std::set{callOverflow}, "Call RIP-relative displacement overflow did not retain its import"); + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(callOverflowRefs, elfFixture(input), input.Text, input.TextVaddr).size() == 1, + "Strict filter removed call RIP-relative displacement overflow import"); + + input.Text.assign(32, 0); + input.TextVaddr = 0x1000; + input.Entries = {input.TextVaddr}; + emit(input, 0, {0xFF, 0x25}); + write(input.Text, 2, static_cast(-0x2000)); + emit(input, 6, {0xC3}); + const auto jmpUnderflow = static_cast(input.TextVaddr + 6 + static_cast(static_cast(-0x2000))); + input.ImportSlots = {jmpUnderflow}; + const std::vector jmpUnderflowRefs = {{"live", {}, 7, 0x300, jmpUnderflow, 0}}; + require(AnalyzeStrictReachability(input).ImportSlots == std::set{jmpUnderflow}, "Jump RIP-relative displacement underflow did not retain its import"); + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(jmpUnderflowRefs, elfFixture(input), input.Text, input.TextVaddr).size() == 1, + "Strict filter removed jump RIP-relative displacement underflow import"); + + input.Text.assign(32, 0); + input.TextVaddr = maxVaddr - 32; + input.Entries = {input.TextVaddr}; + emit(input, 0, {0xFF, 0x25}); + write(input.Text, 2, static_cast(0x1000)); + emit(input, 6, {0xC3}); + const auto jmpOverflow = static_cast(input.TextVaddr + 6 + static_cast(static_cast(0x1000))); + input.ImportSlots = {jmpOverflow}; + const std::vector jmpOverflowRefs = {{"live", {}, 7, 0x300, jmpOverflow, 0}}; + require(AnalyzeStrictReachability(input).ImportSlots == std::set{jmpOverflow}, "Jump RIP-relative displacement overflow did not retain its import"); + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(jmpOverflowRefs, elfFixture(input), input.Text, input.TextVaddr).size() == 1, + "Strict filter removed jump RIP-relative displacement overflow import"); + + input.Text.assign(32, 0); + input.TextVaddr = 0x1000; + input.Entries = {input.TextVaddr}; + emit(input, 0, {0x48, 0x8D, 0x05}); + write(input.Text, 3, static_cast(-0x2000)); + emit(input, 7, {0xC3}); + const auto leaUnderflow = static_cast(input.TextVaddr + 7 + static_cast(static_cast(-0x2000))); + input.ImportSlots = {leaUnderflow}; + const std::vector leaUnderflowRefs = {{"live", {}, 7, 0x300, leaUnderflow, 0}}; + const auto leaUnderflowResult = AnalyzeStrictReachability(input); + require(leaUnderflowResult.ImportSlots == std::set{leaUnderflow}, "LEA RIP-relative displacement underflow did not retain its import"); + require(leaUnderflowResult.AddressTakenRoots == 1, "LEA RIP-relative displacement underflow did not add an address-taken root"); + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(leaUnderflowRefs, elfFixture(input), input.Text, input.TextVaddr).size() == 1, + "Strict filter removed LEA RIP-relative displacement underflow import"); + + input.Text.assign(32, 0); + input.TextVaddr = maxVaddr - 32; + input.Entries = {input.TextVaddr}; + emit(input, 0, {0x48, 0x8D, 0x05}); + write(input.Text, 3, static_cast(0x1000)); + emit(input, 7, {0xC3}); + const auto leaOverflow = static_cast(input.TextVaddr + 7 + static_cast(static_cast(0x1000))); + input.ImportSlots = {leaOverflow}; + const std::vector leaOverflowRefs = {{"live", {}, 7, 0x300, leaOverflow, 0}}; + const auto leaOverflowResult = AnalyzeStrictReachability(input); + require(leaOverflowResult.ImportSlots == std::set{leaOverflow}, "LEA RIP-relative displacement overflow did not retain its import"); + require(leaOverflowResult.AddressTakenRoots == 1, "LEA RIP-relative displacement overflow did not add an address-taken root"); + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(leaOverflowRefs, elfFixture(input), input.Text, input.TextVaddr).size() == 1, + "Strict filter removed LEA RIP-relative displacement overflow import"); + + input.Text.assign(32, 0); + input.TextVaddr = 0x1000; + input.Entries = {input.TextVaddr}; + emit(input, 0, {0xFF, 0x15}); + write(input.Text, 2, static_cast(-0x1006)); + emit(input, 6, {0xC3}); + input.ImportSlots = {0}; + require(AnalyzeStrictReachability(input).ImportSlots == std::set{0}, "RIP-relative displacement to address 0 was not resolved"); + + constexpr auto maxTarget = std::numeric_limits::max(); + input.Text.assign(32, 0); + input.TextVaddr = maxTarget - 32; + input.Entries = {input.TextVaddr}; + emit(input, 0, {0xFF, 0x15}); + write(input.Text, 2, static_cast(26)); + emit(input, 6, {0xC3}); + input.ImportSlots = {maxTarget}; + require(AnalyzeStrictReachability(input).ImportSlots == std::set{maxTarget}, "RIP-relative displacement to maximum address was not resolved"); + + input.Text.assign(32, 0); + input.TextVaddr = 0x1000; + input.Entries = {input.TextVaddr}; + emit(input, 0, {0x67, 0xFF, 0x15}); + write(input.Text, 3, static_cast(-0x2000)); + emit(input, 7, {0xC3}); + const auto eipWrappedTarget = static_cast(input.TextVaddr + 7 + static_cast(static_cast(-0x2000))); + input.ImportSlots = {eipWrappedTarget}; + require(AnalyzeStrictReachability(input).ImportSlots == std::set{eipWrappedTarget}, "EIP-relative displacement underflow did not wrap modulo 2^32"); +} + void callbackAndRelocationRoots() { auto input = fixture(); ripOperand(input, 0, {0x48, 0x8D, 0x3D}, 0x1020); @@ -568,6 +698,154 @@ void relativeRelocationTableBounds() { write(badSize, 0x118, 25); requireFailure([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(badSize); }, "Unaligned relocation table size was accepted"); + auto validRelaEnt = bytes; + write(validRelaEnt, 0x120, 9); + write(validRelaEnt, 0x128, 24); + write(validRelaEnt, 0x130, 0); + write(validRelaEnt, 0x138, 0); + auto indexRelaEnt = Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(validRelaEnt); + require(indexRelaEnt->TargetOfSlot(0x5000) == 0x6000, "Valid DT_RELAENT = 24 was not accepted"); + + auto badRelaEnt = bytes; + write(badRelaEnt, 0x120, 9); + write(badRelaEnt, 0x128, 16); + write(badRelaEnt, 0x130, 0); + write(badRelaEnt, 0x138, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(badRelaEnt); }, "Unsupported DT_RELAENT value: 16", "Invalid DT_RELAENT was accepted"); + + auto conflictingRelaEnt = bytes; + write(conflictingRelaEnt, 0x120, 9); + write(conflictingRelaEnt, 0x128, 24); + write(conflictingRelaEnt, 0x130, 9); + write(conflictingRelaEnt, 0x138, 32); + write(conflictingRelaEnt, 0x140, 0); + write(conflictingRelaEnt, 0x148, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(conflictingRelaEnt); }, "Conflicting duplicate DT_RELAENT tags", "Conflicting DT_RELAENT was accepted"); + + auto relaEntConflictLastValid = bytes; + write(relaEntConflictLastValid, 0x100, 0x9); + write(relaEntConflictLastValid, 0x108, 32); + write(relaEntConflictLastValid, 0x110, 0x9); + write(relaEntConflictLastValid, 0x118, 24); + write(relaEntConflictLastValid, 0x120, 0x0); + write(relaEntConflictLastValid, 0x128, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(relaEntConflictLastValid); }, "Conflicting duplicate DT_RELAENT tags", "Conflicting DT_RELAENT ending in 24 was accepted"); + + auto osRelaEntConflictLastValid = bytes; + write(osRelaEntConflictLastValid, 0x100, 0x61000033); + write(osRelaEntConflictLastValid, 0x108, 32); + write(osRelaEntConflictLastValid, 0x110, 0x61000033); + write(osRelaEntConflictLastValid, 0x118, 24); + write(osRelaEntConflictLastValid, 0x120, 0x0); + write(osRelaEntConflictLastValid, 0x128, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(osRelaEntConflictLastValid); }, "Conflicting duplicate DT_OS_RELAENT tags", "Conflicting DT_OS_RELAENT ending in 24 was accepted"); + + auto bothRelaEntVariants = bytes; + write(bothRelaEntVariants, 0x120, 9); + write(bothRelaEntVariants, 0x128, 24); + write(bothRelaEntVariants, 0x130, 0x61000033); + write(bothRelaEntVariants, 0x138, 24); + write(bothRelaEntVariants, 0x140, 0); + write(bothRelaEntVariants, 0x148, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(bothRelaEntVariants); }, "Both DT_OS_ and DT_ variants present for DT_RELAENT", "Both DT_OS_ and DT_ variants for DT_RELAENT was accepted"); + + auto relaEntBothVariantsWithoutTable = bytes; + write(relaEntBothVariantsWithoutTable, 0x100, 0x9); + write(relaEntBothVariantsWithoutTable, 0x108, 24); + write(relaEntBothVariantsWithoutTable, 0x110, 0x61000033); + write(relaEntBothVariantsWithoutTable, 0x118, 24); + write(relaEntBothVariantsWithoutTable, 0x120, 0x0); + write(relaEntBothVariantsWithoutTable, 0x128, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(relaEntBothVariantsWithoutTable); }, "Both DT_OS_ and DT_ variants present for DT_RELAENT", "Both DT_RELAENT variants without a RELA table were accepted"); + + auto mismatchedRelaEnt = bytes; + write(mismatchedRelaEnt, 0x120, 0x61000033); + write(mismatchedRelaEnt, 0x128, 24); + write(mismatchedRelaEnt, 0x130, 0); + write(mismatchedRelaEnt, 0x138, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(mismatchedRelaEnt); }, "Mismatched DT_RELA and DT_RELAENT tag variants", "Mismatched DT_RELA and DT_RELAENT was accepted"); + + auto validPltRel = bytes; + write(validPltRel, 0x100, 23); + write(validPltRel, 0x108, 0x10E8); + write(validPltRel, 0x110, 2); + write(validPltRel, 0x118, 24); + write(validPltRel, 0x120, 20); + write(validPltRel, 0x128, 7); + write(validPltRel, 0x130, 0); + write(validPltRel, 0x138, 0); + auto indexPltRel = Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(validPltRel); + require(indexPltRel->TargetOfSlot(0x5000) == 0x6000, "Valid DT_PLTREL = 7 was not accepted"); + + auto badPltRel = bytes; + write(badPltRel, 0x100, 23); + write(badPltRel, 0x108, 0x10E8); + write(badPltRel, 0x110, 2); + write(badPltRel, 0x118, 24); + write(badPltRel, 0x120, 20); + write(badPltRel, 0x128, 17); + write(badPltRel, 0x130, 0); + write(badPltRel, 0x138, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(badPltRel); }, "Unsupported DT_PLTREL type: 17; expected DT_RELA (7)", "Unsupported DT_PLTREL type was accepted"); + + auto pltRelConflictLastValid = bytes; + write(pltRelConflictLastValid, 0x100, 0x14); + write(pltRelConflictLastValid, 0x108, 17); + write(pltRelConflictLastValid, 0x110, 0x14); + write(pltRelConflictLastValid, 0x118, 7); + write(pltRelConflictLastValid, 0x120, 0x0); + write(pltRelConflictLastValid, 0x128, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(pltRelConflictLastValid); }, "Conflicting duplicate DT_PLTREL tags", "Conflicting DT_PLTREL ending in DT_RELA was accepted"); + + auto osPltRelConflictLastValid = bytes; + write(osPltRelConflictLastValid, 0x100, 0x6100002B); + write(osPltRelConflictLastValid, 0x108, 17); + write(osPltRelConflictLastValid, 0x110, 0x6100002B); + write(osPltRelConflictLastValid, 0x118, 7); + write(osPltRelConflictLastValid, 0x120, 0x0); + write(osPltRelConflictLastValid, 0x128, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(osPltRelConflictLastValid); }, "Conflicting duplicate DT_OS_PLTREL tags", "Conflicting DT_OS_PLTREL ending in DT_RELA was accepted"); + + auto pltRelBothVariantsWithoutTable = bytes; + write(pltRelBothVariantsWithoutTable, 0x100, 0x14); + write(pltRelBothVariantsWithoutTable, 0x108, 7); + write(pltRelBothVariantsWithoutTable, 0x110, 0x6100002B); + write(pltRelBothVariantsWithoutTable, 0x118, 7); + write(pltRelBothVariantsWithoutTable, 0x120, 0x0); + write(pltRelBothVariantsWithoutTable, 0x128, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(pltRelBothVariantsWithoutTable); }, "Both DT_OS_ and DT_ variants present for DT_PLTREL", "Both DT_PLTREL variants without a PLT table were accepted"); + + auto jmprelWithOsPltRel = bytes; + write(jmprelWithOsPltRel, 0x100, 0x17); + write(jmprelWithOsPltRel, 0x108, 0x10E8); + write(jmprelWithOsPltRel, 0x110, 0x2); + write(jmprelWithOsPltRel, 0x118, 24); + write(jmprelWithOsPltRel, 0x120, 0x6100002B); + write(jmprelWithOsPltRel, 0x128, 7); + write(jmprelWithOsPltRel, 0x130, 0x0); + write(jmprelWithOsPltRel, 0x138, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(jmprelWithOsPltRel); }, "Mismatched DT_JMPREL and DT_PLTREL tag variants", "Mismatched DT_JMPREL and DT_PLTREL variants were accepted"); + + auto consistentEntryTagsWithoutTables = bytes; + write(consistentEntryTagsWithoutTables, 0x100, 0x9); + write(consistentEntryTagsWithoutTables, 0x108, 24); + write(consistentEntryTagsWithoutTables, 0x110, 0x9); + write(consistentEntryTagsWithoutTables, 0x118, 24); + write(consistentEntryTagsWithoutTables, 0x120, 0x14); + write(consistentEntryTagsWithoutTables, 0x128, 7); + write(consistentEntryTagsWithoutTables, 0x130, 0x14); + write(consistentEntryTagsWithoutTables, 0x138, 7); + write(consistentEntryTagsWithoutTables, 0x140, 0x0); + write(consistentEntryTagsWithoutTables, 0x148, 0); + (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(consistentEntryTagsWithoutTables); + + auto unsupportedRel = bytes; + write(unsupportedRel, 0x120, 17); + write(unsupportedRel, 0x128, 0x10E8); + write(unsupportedRel, 0x130, 0); + write(unsupportedRel, 0x138, 0); + requireFailureMessage([&] { Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(unsupportedRel); }, "DT_REL relocations are not supported", "DT_REL relocations were accepted"); + auto pltOverextended = bytes; write(pltOverextended, 0x100, 23); write(pltOverextended, 0x108, 0x10E8); @@ -688,6 +966,17 @@ void sceDynlibDataOffsets() { requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyJmprel); }, "Empty SCE JMPREL table with overflowing offset was accepted"); + auto validOsPltRel = jmprel; + write(validOsPltRel, 0x338, 0x430); + write(validOsPltRel, 208, 0x80); + write(validOsPltRel, 216, 0x80); + write(validOsPltRel, 0x350, 0x6100002B); + write(validOsPltRel, 0x358, 7); + write(validOsPltRel, 0x360, 0); + write(validOsPltRel, 0x368, 0); + require(Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(validOsPltRel)->TargetOfSlot(0x2050) == 0x1010, + "Valid DT_OS_PLTREL = 7 was not accepted"); + auto emptyRela = bytes; write(emptyRela, 0x318, 0); require(!Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(emptyRela)->TargetOfSlot(0x2040), @@ -714,6 +1003,40 @@ void sceDynlibDataOffsets() { requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, duplicate, input.Text, input.TextVaddr); }, "Strict filter accepted duplicate PT_SCE_DYNLIBDATA segments"); + auto overflowSce = bytes; + write(overflowSce, 240, ~std::uint64_t{0} - 16); + requireFailure([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(overflowSce); }, + "PT_SCE_DYNLIBDATA offset near UINT64_MAX was accepted"); + + auto sceOverextended = bytes; + write(sceOverextended, 0x318, 0); + write(sceOverextended, 264, sceOverextended.size() - 0x500 + 1); + requireFailureMessage([&] { (void)Relinker::UnusedNidFilter::BuildRelativeRelocationIndex(sceOverextended); }, + "PT_SCE_DYNLIBDATA segment out of bounds", + "PT_SCE_DYNLIBDATA extending beyond file was accepted"); + + auto strictRel = bytes; + write(strictRel, 0x320, 17); + write(strictRel, 0x328, 0); + requireFailureMessage([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, strictRel, input.Text, input.TextVaddr); }, + "Strict filter: DT_REL relocations are not supported", + "Strict filter accepted DT_REL dynamic tag"); + + auto strictBadRelaEnt = bytes; + write(strictBadRelaEnt, 0x300, 0x61000021); + write(strictBadRelaEnt, 0x310, 0x61000021); + write(strictBadRelaEnt, 0x328, 16); + requireFailureMessage([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, strictBadRelaEnt, input.Text, input.TextVaddr); }, + "Strict filter: invalid RELA entry size", + "Strict filter accepted invalid DT_OS_RELAENT value"); + + auto strictValidRelaEntNoTable = bytes; + write(strictValidRelaEntNoTable, 0x300, 0x61000021); + write(strictValidRelaEntNoTable, 0x310, 0x61000021); + write(strictValidRelaEntNoTable, 0x328, 24); + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, strictValidRelaEntNoTable, input.Text, input.TextVaddr).empty(), + "Strict filter failed to filter without relocations"); + auto sectionRelative = bytes; write(sectionRelative, 208, 0x70); write(sectionRelative, 216, 0x70); @@ -961,6 +1284,57 @@ void negativeRelativeRelocationTarget() { "Strict filter rejected a valid relative relocation target address"); } +void absoluteFunctionSymbols() { + auto input = fixture(); + emit(input, 0, {0xC3}); + importThunk(input, 16, 0x2000); + auto bytes = elfFixture(input); + bytes.resize(0xA00); + write(bytes, 56, 4); + write(bytes, 176, 2); + write(bytes, 180, 6); + write(bytes, 184, 0x300); + write(bytes, 192, 0x2000); + write(bytes, 208, 0x40); + write(bytes, 216, 0x40); + write(bytes, 224, 8); + write(bytes, 232, 0x61000000); + write(bytes, 236, 4); + write(bytes, 240, 0x500); + write(bytes, 264, 0x500); + write(bytes, 272, 0x500); + write(bytes, 280, 0x10); + write(bytes, 0x300, 0x61000039); + write(bytes, 0x308, 0x100); + write(bytes, 0x310, 0x6100003B); + write(bytes, 0x318, 24); + write(bytes, 0x320, 0x6100003F); + write(bytes, 0x328, 48); + const std::vector references = {{"dead", {}, 6, 0x900, 0x2000, 0}}; + + write(bytes, 0x61C, 0x12); + write(bytes, 0x61E, 1); + write(bytes, 0x620, 0x1010); + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, bytes, input.Text, input.TextVaddr).size() == 1, + "Strict filter dropped an import used by a regular exported function"); + + for (const std::uint8_t binding : {std::uint8_t{1}, std::uint8_t{2}}) { + for (const std::uint64_t constant : {std::uint64_t{0x123456789}, std::uint64_t{0x1010}, std::uint64_t{0}}) { + write(bytes, 0x61C, static_cast((binding << 4) | 2)); + write(bytes, 0x61E, 0xFFF1); + write(bytes, 0x620, constant); + require(Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, bytes, input.Text, input.TextVaddr).empty(), + "Strict filter treated an SHN_ABS function symbol as a code entry point"); + } + } + + write(bytes, 0x61C, 0x1A); + write(bytes, 0x61E, 0xFFF1); + write(bytes, 0x620, 0x123456789); + requireFailure([&] { (void)Relinker::MakeStrictUnusedNidFilter(testProgress)->Filter(references, bytes, input.Text, input.TextVaddr); }, + "Strict filter accepted an SHN_ABS IFUNC symbol"); +} + } int main() { @@ -970,6 +1344,7 @@ int main() { conditionalTailCall(); fallthroughAfterZeroPadding(); addressSizeOverrideImport(); + ripDisplacementOverflowUnderflowBounds(); callbackAndRelocationRoots(); registerImportCall(); mergedJumpTable(); @@ -990,6 +1365,7 @@ int main() { duplicatePointerRelocations(); constructorArrayBounds(); negativeRelativeRelocationTarget(); + absoluteFunctionSymbols(); std::cout << "Strict NID filter tests passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; diff --git a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp index fe1fdf9976..dd3ab410de 100644 --- a/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp +++ b/core/relinker/relinker/tests/UnwindRecordBoundsTests.cpp @@ -352,6 +352,41 @@ int main() { lsdaS2Rejected = std::string(error.what()).find("unwind pointer address overflowed") != std::string::npos; } Require(lsdaS2Rejected); - std::cout << "30 unwind record cases passed\n"; + Domain::ProgramHeader augLoad{}; augLoad.Type = 1; augLoad.MappedAddress = 0x1000; augLoad.FileSize = 128; + Domain::ProgramHeader augEh = augLoad; augEh.Type = 0x6474E550; augEh.FileSize = 32; + auto unterminatedAugBytes = Fixture(); + unterminatedAugBytes[73] = 'z'; + unterminatedAugBytes[74] = 'R'; + unterminatedAugBytes[75] = 'L'; + unterminatedAugBytes[76] = 'P'; + bool unterminatedAugRejected = false; + try { Relinker::UnusedNidFilter::ReadExceptionFunctions(unterminatedAugBytes, {augLoad, augEh}, {}, {}); } + catch (const Domain::RelinkerException& error) { + unterminatedAugRejected = std::string(error.what()).find("unterminated CIE augmentation") != std::string::npos; + } + Require(unterminatedAugRejected); + auto outOfBoundsAugBytes = Fixture(); + Io::WriteU32(outOfBoundsAugBytes, 64, 12); + outOfBoundsAugBytes[73] = 'z'; + outOfBoundsAugBytes[74] = 0; + outOfBoundsAugBytes[75] = 1; + outOfBoundsAugBytes[76] = 0x78; + outOfBoundsAugBytes[77] = 16; + outOfBoundsAugBytes[78] = 10; + bool outOfBoundsAugRejected = false; + try { Relinker::UnusedNidFilter::ReadExceptionFunctions(outOfBoundsAugBytes, {augLoad, augEh}, {}, {}); } + catch (const Domain::RelinkerException& error) { + outOfBoundsAugRejected = std::string(error.what()).find("CIE augmentation exceeds record") != std::string::npos; + } + Require(outOfBoundsAugRejected); + auto unterminatedRecordBytes = Fixture(); + Io::WriteU32(unterminatedRecordBytes, 64, 8); + bool unterminatedRecordRejected = false; + try { Relinker::UnusedNidFilter::ReadExceptionFunctions(unterminatedRecordBytes, {augLoad, augEh}, {}, {}); } + catch (const Domain::RelinkerException& error) { + unterminatedRecordRejected = std::string(error.what()).find("CIE fields exceed record") != std::string::npos; + } + Require(unterminatedRecordRejected); + std::cout << "33 unwind record cases passed\n"; } catch (const std::exception& error) { std::cerr << error.what() << '\n'; return 1; } } diff --git a/core/relinker/relinker/tests/WindowsExportAliasTests.cpp b/core/relinker/relinker/tests/WindowsExportAliasTests.cpp index 8c7c3cdd72..e11b78f6bf 100644 --- a/core/relinker/relinker/tests/WindowsExportAliasTests.cpp +++ b/core/relinker/relinker/tests/WindowsExportAliasTests.cpp @@ -68,7 +68,7 @@ int main() { const std::array,7> changes={{{4,0x22},{4,0x11},{5,0},{6,2},{8,0x28},{16,2},{6,Relinker::AbsoluteSection}}}; for(const auto& [field,value]:changes){auto b=Fixture();auto off=0x2400+48+field;if(field<6)b[off]=value;else if(field==6)Io::WriteU16(b,off,value);else Io::WriteU64(b,off,value);Reject([&]{Relinker::GuestImageReader().Read("generic.prx",b,true);},"Duplicate or empty export");} auto good=Relinker::GuestImageReader().Read("generic.prx",Fixture(),true); - auto symbolic=good;symbolic.Symbolic=true;Domain::GuestRuntime symbolicRuntime;Reject([&]{Elfpatcher::GuestModuleWriter().WriteWindows(symbolic,symbolicRuntime);},"DF_SYMBOLIC"); + auto symbolic=good;symbolic.Symbolic=true;Domain::GuestRuntime symbolicRuntime,ordinaryRuntime;Check(Elfpatcher::GuestModuleWriter().WriteWindows(symbolic,symbolicRuntime)==Elfpatcher::GuestModuleWriter().WriteWindows(good,ordinaryRuntime)&&symbolicRuntime.Imports.size()==ordinaryRuntime.Imports.size(),"DF_SYMBOLIC changed Windows binding"); for(unsigned kind=0;kind<10;++kind){auto g=good;auto& s=g.Symbols[2];switch(kind){case 0:s.Value+=8;break;case 1:s.Size+=1;break;case 2:s.Info=0x22;break;case 3:s.Info=0x11;break;case 4:s.Visibility=0;break;case 5:s.Section=2;break;case 6:g.Symbols[1].Info=s.Info=0x11;break;case 7:g.Symbols[1].Section=s.Section=Relinker::AbsoluteSection;break;case 8:g.Symbols[1].Info=s.Info=0x16;g.Dynamic.RelaData.clear();break;case 9:g.Symbols[1].Info=s.Info=0x10;break;}Check(!Relinker::IdenticalOrdinaryFunctionExport(g.Symbols[1],s),"nonordinary/conflicting tuple accepted");Domain::GuestRuntime r;Reject([&]{Elfpatcher::GuestModuleWriter().WriteWindows(g,r);},kind==8?"TLS":"Duplicate guest export");} Check(Domain::DeclaredImportModule("same#A#B",{{1,"one"},{2,"two"}})=="one"&&Domain::DeclaredImportModule("same#A#C",{{1,"one"},{2,"two"}})=="two","module-qualified IDs merged"); auto zeroBucket=Fixture();Io::WriteU32(zeroBucket,0x2600,0); @@ -87,6 +87,27 @@ int main() { Io::WriteU32(multiBucket,0x2600,2);Io::WriteU32(multiBucket,0x2604,4);Io::WriteU32(multiBucket,0x2608,1);Io::WriteU32(multiBucket,0x260c,2); auto goodMulti=Relinker::GuestImageReader().Read("generic.prx",multiBucket,true); Check(goodMulti.Symbols.size()==4,"valid multi-bucket SysV hash accepted"); + auto rel=Fixture(); + Io::WriteU64(rel,0x2080,17); + Io::WriteU64(rel,0x2088,0x2700); + Reject([&]{Relinker::GuestImageReader().Read("generic.prx",rel,true);},"DT_REL relocations are not supported"); + auto badRelaEnt=Fixture(); + Io::WriteU64(badRelaEnt,0x2050,0x61000021); + Io::WriteU64(badRelaEnt,0x2060,0x61000021); + Io::WriteU64(badRelaEnt,0x2078,16); + Reject([&]{Relinker::GuestImageReader().Read("generic.prx",badRelaEnt,true);},"Invalid RELA entry size"); + auto badPltRel=Fixture(); + Io::WriteU64(badPltRel,0x2080,20); + Io::WriteU64(badPltRel,0x2088,17); + Reject([&]{Relinker::GuestImageReader().Read("generic.prx",badPltRel,true);},"Unsupported PLT relocation format"); + auto validNoPlt=Fixture(); + Io::WriteU64(validNoPlt,0x2050,0x61000021); + Io::WriteU64(validNoPlt,0x2060,0x61000021); + Io::WriteU64(validNoPlt,0x2078,24); + Io::WriteU64(validNoPlt,0x2080,20); + Io::WriteU64(validNoPlt,0x2088,7); + auto goodNoPlt=Relinker::GuestImageReader().Read("generic.prx",validNoPlt,true); + Check(goodNoPlt.Symbols.size()==4,"valid RELAENT and PLTREL without tables accepted"); std::cout<<"WINDOWS_EXPORT_ALIAS_CASES_PASSED\npreserved-indices reversed-order conflicting-fields strict-nonwindows qualified-import writer-guard\n"; } catch(const std::exception& e){std::cerr<> 32 else entry for entry in rela if entry[1] & 0xffffffff] @@ -108,17 +120,23 @@ def check_output(data, system, rela, plt, initial): pe_header, = struct.unpack_from("> 32 and symbol_name(image, tags, info >> 32) == HELPER: + symbol = file_offset(image, tags[6]) + (info >> 32) * 24 + slots.append((target, info & 0xffffffff, addend, image[symbol + 4], struct.unpack_from(" +#include +#include +static void permissions(void *address) { + FILE *maps = fopen("/proc/self/maps", "r"); + if (!maps) _exit(92); + char line[512], access[5]; + unsigned long start, end; + while (fgets(line, sizeof(line), maps)) { + if (sscanf(line, "%lx-%lx %4s", &start, &end, access) == 3 + && (uintptr_t)address >= start && (uintptr_t)address < end) { + dprintf(1, "%s\n", access); + fclose(maps); + return; + } + } + _exit(93); +} +int shared(void *slot, int last) { + permissions(slot); + permissions((char *)slot + 7); + if (last) _exit(11); + return 11; +} +""" +PERMISSIONS_HOOK = r""" +#include +#include +static int (*original)(void *, int); +static int replacement(void *slot, int last) { + int result = original(slot, 0) + 66; + if (last) _exit(result); + return result; +} +void *TraceImport_nid_no_patch(void *target, const char *name, const char *library) { + if (strcmp(name, "shared") != 0 || strcmp(library, "a.prx") != 0) _exit(94); + original = target; + write(2, "hook\n", 5); + return replacement; +} +""" + + +def permissions_fixture(slots, segments): + image = executable("a.prx", extra_dependencies=("libc.prx",), + extra_symbols=("shared#A#B",) * (len(slots) - 1)) + struct.pack_into(") + if(BUILD_TESTING AND NOT TARGET spanned_uniformity_tests) + add_test_executable(spanned_uniformity_tests + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/tests/SpannedUniformityTests.cpp" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/src/SpirvSpannedUniformity.cpp") + target_include_directories(spanned_uniformity_tests PRIVATE + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/SpirvBackend/include" + ${ANYPS5_SPIRV_HEADERS_INCLUDE_DIR}) + set_target_properties(spanned_uniformity_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(spanned_uniformity_tests) + add_test(NAME spanned_uniformity COMMAND spanned_uniformity_tests) + set_tests_properties(spanned_uniformity PROPERTIES TIMEOUT 20) + if(MINGW) + target_link_libraries(spanned_uniformity_tests PRIVATE libc) + set_tests_properties(spanned_uniformity PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endif() if(BUILD_TESTING AND ANYPS5_ENABLE_SPIRV_TOOLS AND NOT ANYPS5_USE_SYSTEM_DEPS AND NOT TARGET spirv_validator_exit_tests) add_test_executable(spirv_validator_exit_tests "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/tests/SpirvValidatorExit.cpp") target_link_libraries(spirv_validator_exit_tests PRIVATE SPIRV-Tools-static) diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index d4edc51317..116703feec 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -56,6 +56,7 @@ public: append(key, request.context.vertex); append(key, request.context.floatMode); append(key, request.context.convertedSamples); + append(key, request.context.layerRowSamples); appendFallbacks(key, request); appendMesh(key, request); appendTessellation(key, request); @@ -67,6 +68,12 @@ public: return hash; } + static void SealFeatureDigest(SpirvTarget& target) { + target.featureDigest = digestFeatures(target.supportedCapabilities, target.supportedExtensions); + target.featureDigestSpans = featureSpans(target); + target.featureDigestSealed = true; + } + // A 64-bit hash of the code, two dwords per step; collisions are resolved by comparing the code. static std::uint64_t HashCode(std::span code) { std::uint64_t hash = 0x9e3779b97f4a7c15ull ^ (static_cast(code.size()) * 0x100000001b3ull); @@ -90,6 +97,7 @@ private: append(key, request.context.vertex); append(key, request.context.floatMode); append(key, request.context.convertedSamples); + append(key, request.context.layerRowSamples); appendFallbacks(key, request); appendMesh(key, request); appendTessellation(key, request); @@ -301,7 +309,11 @@ private: append(key, value.maxEvaluationOutputComponents); } - static void appendFeatureDigest(std::vector& key, std::span capabilities, std::span extensions) { + static std::array featureSpans(const SpirvTarget& target) { + return {reinterpret_cast(target.supportedCapabilities.data()), target.supportedCapabilities.size(), reinterpret_cast(target.supportedExtensions.data()), target.supportedExtensions.size()}; + } + + static std::array digestFeatures(std::span capabilities, std::span extensions) { std::uint64_t low = 0xcbf29ce484222325ull; std::uint64_t high = 0x6a09e667f3bcc909ull; const auto mix = [&](std::uint64_t value) { @@ -320,8 +332,13 @@ private: mix(chunk); } } - key.push_back(low); - key.push_back(high); + return {low, high}; + } + + static void appendFeatureDigest(std::vector& key, const SpirvTarget& target) { + const auto digest = target.featureDigestSealed && target.featureDigestSpans == featureSpans(target) ? target.featureDigest : digestFeatures(target.supportedCapabilities, target.supportedExtensions); + key.push_back(digest[0]); + key.push_back(digest[1]); } static void append(std::vector& key, const SpirvTarget& value) { @@ -329,7 +346,7 @@ private: append(key, value.spirvVersion); append(key, value.subgroupSize); append(key, value.bdaAbiVersion); - appendFeatureDigest(key, value.supportedCapabilities, value.supportedExtensions); + appendFeatureDigest(key, value); append(key, value.fragmentShaderBarycentricEnabled); append(key, value.maxWorkgroupSize); append(key, value.maxWorkgroupInvocations); @@ -338,6 +355,7 @@ private: append(key, value.tessellation); append(key, value.nonConstantImageOffsets); append(key, value.srgbDecodeFormats); + append(key, value.maxImageArrayLayers); append(key, value.narrowSubgroupClock); append(key, value.fragmentBarycentricBuiltins); append(key, value.float64DenormPreserve); @@ -359,6 +377,8 @@ private: append(key, value.deviceScopeLdsMemoryBarriers); append(key, value.ldsLockFences); append(key, value.meshWindowBuffer); + append(key, value.hostInterpolationFirst); + append(key, value.shaderImageMinLod); } }; diff --git a/core/shader/recompiler/ControlFlow/include/ControlFlow/GraphBuilder.hpp b/core/shader/recompiler/ControlFlow/include/ControlFlow/GraphBuilder.hpp index 1f333297f7..2a04e2148b 100644 --- a/core/shader/recompiler/ControlFlow/include/ControlFlow/GraphBuilder.hpp +++ b/core/shader/recompiler/ControlFlow/include/ControlFlow/GraphBuilder.hpp @@ -61,7 +61,7 @@ struct TableCallee { [[nodiscard]] std::optional UnresolvableSwappcTarget(const RdnaProgram& program, const SwappcInfo* swappc); [[nodiscard]] std::vector FindUserDataCalls(const RdnaProgram& program, const SwappcInfo& swappc); -[[nodiscard]] std::optional UserDataCalleeLength(std::span callee, std::uint32_t linkRegister); +[[nodiscard]] std::optional UserDataCalleeLength(std::span callee, std::uint32_t linkRegister, bool allowGetpc = false); [[nodiscard]] std::vector LinkUserDataCalls(const RdnaProgram& program, std::span calls, std::span> callees); [[nodiscard]] std::vector FindTableCalls(const RdnaProgram& program, const SwappcInfo& swappc); struct LinkedCalls { diff --git a/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp b/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp index 4c10e5d15c..699cefd5ea 100644 --- a/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp +++ b/core/shader/recompiler/ControlFlow/src/GraphBuilder.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -1388,6 +1389,46 @@ std::uint32_t branchWord(std::uint32_t opcode, std::size_t from, std::size_t to, return opcode | (static_cast(offset) & 0xffffu); } +std::vector expandProgramCounterReads(std::span code, std::uint64_t address) { + constexpr auto Inside = std::numeric_limits::max(); + std::vector instructions; + std::vector moved(code.size() + 1u, Inside); + std::size_t size = 0; + bool reads = false; + for (std::uint32_t word = 0; word < code.size();) { + auto instruction = DecodeRdnaInstruction(word * 4u, code, word); + moved[word] = size; + if (instruction.op == RdnaOpcode::SGetpcB64) { + if (instruction.destination.kind != RdnaOperandKind::ScalarRegister || instruction.destination.reg % 2u != 0u || instruction.destination.reg + 1u >= 106u) throw std::invalid_argument("a table callee reads its program counter into another operand than an SGPR pair at callee offset " + toHexString(instruction.programCounter)); + reads = true; + } + size += instruction.op == RdnaOpcode::SGetpcB64 ? 4u : instruction.wordCount; + word += instruction.wordCount; + instructions.push_back(std::move(instruction)); + } + if (!reads) return {code.begin(), code.end()}; + moved[code.size()] = size; + std::vector expanded; + expanded.reserve(size); + for (const auto& instruction : instructions) { + const auto word = instruction.programCounter / 4u; + if (instruction.op == RdnaOpcode::SGetpcB64) { + const auto value = address + instruction.programCounter + 4u; + const auto reg = instruction.destination.reg; + expanded.insert(expanded.end(), {0xbe8003ffu | (reg << 16u), static_cast(value), 0xbe8003ffu | ((reg + 1u) << 16u), static_cast(value >> 32u)}); + continue; + } + if (IsDirectBranchOpcode(instruction.op)) { + const auto target = instruction.branchTarget / 4u; + if (instruction.branchTarget % 4u != 0u || target > code.size() || moved[target] == Inside) throw std::invalid_argument("a table callee that reads its program counter branches outside its instructions at callee offset " + toHexString(instruction.programCounter)); + expanded.push_back(branchWord(code[word] & 0xffff0000u, moved[word], moved[target], "a table callee's branch")); + continue; + } + expanded.insert(expanded.end(), code.begin() + word, code.begin() + word + instruction.wordCount); + } + return expanded; +} + } @@ -1468,7 +1509,7 @@ std::vector LinkUserDataTail(const RdnaProgram& program, const Us return linked; } -std::optional UserDataCalleeLength(std::span callee, std::uint32_t linkRegister) { +std::optional UserDataCalleeLength(std::span callee, std::uint32_t linkRegister, bool allowGetpc) { std::uint32_t furthest = 0; for (std::uint32_t word = 0; word < callee.size();) { const auto programCounter = word * 4u; @@ -1481,7 +1522,7 @@ std::optional UserDataCalleeLength(std::span } word += instruction.wordCount; const auto at = " at callee offset " + toHexString(programCounter); - if (instruction.op == RdnaOpcode::SGetpcB64) throw std::invalid_argument("user-data callee reads its program counter" + at + ", which a linked copy does not keep"); + if (instruction.op == RdnaOpcode::SGetpcB64 && !allowGetpc) throw std::invalid_argument("user-data callee reads its program counter" + at + ", which a linked copy does not keep"); if (instruction.op == RdnaOpcode::SSwappcB64 || instruction.op == RdnaOpcode::SCallB64) throw std::invalid_argument("user-data callee makes a call" + at); if (instruction.op == RdnaOpcode::SEndpgm || instruction.op == RdnaOpcode::SCodeEnd) throw std::invalid_argument("user-data callee ends the program" + at + " before it returns"); if (IsDirectBranchOpcode(instruction.op)) { @@ -1593,6 +1634,8 @@ LinkedCalls LinkCalls(const RdnaProgram& program, std::span const auto at = "the table callees of the call at program counter " + toHexString(table.programCounter); const auto original = linked[word]; const auto count = callees.size(); + std::vector> codes; + for (const auto& callee : callees) codes.push_back(expandProgramCounterReads(callee.code, callee.target)); const auto relays = 2u * (tableCalls.size() - call - 1u); std::vector path; for (const auto& [position, block] : stations) path.push_back(position + 2u * (call - block - 1u)); @@ -1605,7 +1648,7 @@ LinkedCalls LinkCalls(const RdnaProgram& program, std::span const auto start = linked.size(); std::vector segments(count + 1u); segments[0] = start + 1u; - for (std::size_t callee = 0; callee < count; ++callee) segments[callee + 1u] = segments[callee] + 16u + callees[callee].code.size() + relays; + for (std::size_t callee = 0; callee < count; ++callee) segments[callee + 1u] = segments[callee] + 16u + codes[callee].size() + relays; const auto one = [&](std::size_t callee) { return segments[callee] + (callee < count ? 7u : 2u); }; const auto back = [&](std::size_t callee) { return callee != 0u ? segments[callee - 1u] + 15u : path.empty() ? word + 1u : path.back() + 1u; }; const auto fault = segments[count] + 3u; @@ -1628,7 +1671,7 @@ LinkedCalls LinkCalls(const RdnaProgram& program, std::span } linked.push_back(0xbb000001u | (table.linkRegister << 16u)); branch(0xbf820000u, back(callee)); - linked.insert(linked.end(), callees[callee].code.begin(), callees[callee].code.end()); + linked.insert(linked.end(), codes[callee].begin(), codes[callee].end()); station(); } linked.push_back(0xbf078080u); diff --git a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp index e6b6c9de1a..f14fda01ec 100644 --- a/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp +++ b/core/shader/recompiler/ControlFlow/src/RequestSerializer.cpp @@ -244,7 +244,7 @@ ConservativeZExport readConservativeZExport(Reader& reader) { ColorExportPacking readColorExportPacking(Reader& reader) { const auto value = reader.ReadU8(); - if (value > static_cast(ColorExportPacking::Uint)) { + if (value > static_cast(ColorExportPacking::UintClamp16)) { throw std::runtime_error("RequestSerializer: invalid ColorExportPacking value"); } return static_cast(value); @@ -898,7 +898,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const std::string buffer; Writer writer(buffer); writer.WriteU32(0x41505335u); - writer.WriteU32(51u); + writer.WriteU32(53u); writeShaderBinary(writer, request.shader); writeGuestContext(writer, request.context); writeSpirvTarget(writer, request.target); @@ -941,6 +941,8 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const writer.WriteU32(request.target.pixelFunctionStorageDwords); writer.WriteBool(request.target.fragmentBarycentricBuiltins); writer.WriteBool(request.target.tessellationEvaluationSubgroups); + writer.WriteBool(request.target.hostInterpolationFirst); + writer.WriteBool(request.target.shaderImageMinLod); return base64Encode(buffer); } @@ -949,7 +951,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const Reader reader(decoded); if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature"); const auto version = reader.ReadU32(); - if (version < 1u || version > 51u || version == 50u) throw std::runtime_error("unsupported recompile request serialization version"); + if (version < 1u || version > 53u || version == 50u) throw std::runtime_error("unsupported recompile request serialization version"); DeserializedRequest result{}; result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader); result.request.context = readGuestContext(reader, result, version); @@ -1010,6 +1012,8 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const } if (version >= 48u) result.request.target.fragmentBarycentricBuiltins = reader.ReadBool(); if (version >= 51u) result.request.target.tessellationEvaluationSubgroups = reader.ReadBool(); + if (version >= 52u) result.request.target.hostInterpolationFirst = reader.ReadBool(); + if (version >= 53u) result.request.target.shaderImageMinLod = reader.ReadBool(); if (version >= 21u && reader.Remaining() != 0u) throw std::runtime_error("RequestSerializer: unexpected trailing data"); return result; } diff --git a/core/shader/recompiler/FragmentOutputSpecialization.hpp b/core/shader/recompiler/FragmentOutputSpecialization.hpp index 515c9d635f..2122914908 100644 --- a/core/shader/recompiler/FragmentOutputSpecialization.hpp +++ b/core/shader/recompiler/FragmentOutputSpecialization.hpp @@ -32,7 +32,7 @@ inline std::vector SpecializeFragmentOutputs(std::vector= PipelineSpecialization::ExportPackingBase + 8u) continue; - if (constant.value > static_cast(ColorExportPacking::Uint)) throw std::runtime_error("invalid prepared fragment export packing"); + if (constant.value > static_cast(ColorExportPacking::UintClamp16)) throw std::runtime_error("invalid prepared fragment export packing"); if (constant.value != static_cast(ColorExportPacking::None)) packed.insert(constant.id - PipelineSpecialization::ExportPackingBase); } if (packed.empty() && !dualSource && reversed == 0u) return words; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp index 32f615dfcb..9ac3eba4e7 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ControlFlowInfo.hpp @@ -64,6 +64,7 @@ struct DescriptorSource { std::array dwords {}; std::uint32_t dwordCount = 0; std::optional tableColumn; + std::vector requiredBlocks; bool operator==(const DescriptorSource& other) const = default; }; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp index 08b2529bc0..be48288d1e 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ResourcePlan.hpp @@ -32,6 +32,28 @@ struct DescriptorValue { bool operator==(const DescriptorValue& other) const = default; }; +struct RegisteredImageView { + std::uint32_t resource = 0; + std::uint32_t record = 0; + DescriptorValue provenance; + DescriptorValue effective; + std::uint64_t generation = 0; + std::uint64_t address = 0; + std::uint64_t rangeAddress = 0; + std::uint64_t rangeBytes = 0; + std::uint64_t allocationAddress = 0; + std::uint64_t allocationBytes = 0; + std::uint64_t backingSerial = 0; + bool hasRange = false; + bool readable = false; + bool writable = false; + bool gpu = false; + + bool operator==(const RegisteredImageView& other) const { + return resource == other.resource && record == other.record && provenance == other.provenance && effective == other.effective && address == other.address && rangeAddress == other.rangeAddress && rangeBytes == other.rangeBytes && allocationAddress == other.allocationAddress && allocationBytes == other.allocationBytes && backingSerial == other.backingSerial && hasRange == other.hasRange && readable == other.readable && writable == other.writable && gpu == other.gpu; + } +}; + template class SharedValues { public: @@ -112,6 +134,14 @@ struct ImageTableColumnSnapshot { SharedValues codes; SharedValues words; SharedValues> readRanges; + struct ResourceView { + std::uint32_t resource = 0; + SharedValues words; + std::vector identities; + + bool operator==(const ResourceView& other) const = default; + }; + std::vector resourceViews; bool operator==(const ImageTableColumnSnapshot& other) const = default; }; @@ -134,6 +164,8 @@ struct ResourceSnapshot { std::vector srtPoison; std::uint32_t nullRootReads = 0; ImageTableSnapshot tables; + std::vector registeredImages; + std::uint64_t imageAllocationGeneration = 0; }; struct UniformFillPlan { @@ -227,6 +259,7 @@ struct IrResourcePlan { std::uint32_t userDataBase = 0; std::uint32_t userDataCount = 64; std::uint32_t srgbDecodeFormats = 0; + std::uint32_t maxImageArrayLayers = 0; IrValueArena valueStorage; std::vector> blockStorage; std::vector memoryInfo; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp index f371ef9407..af39e2bd1a 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/Resources.hpp @@ -102,6 +102,9 @@ struct ImageResource { bool foldTexelOffsetsOnly = false; bool convertedSamples = false; bool remappedDepth1D = false; + bool layerRowsCompatible = true; + bool layerRows = false; + std::uint32_t maxArrayLayers = 0u; bool optionalDescriptor = false; std::uint32_t byElements = 0; std::uint32_t byComponents = 0; @@ -153,6 +156,10 @@ inline bool SamplesBaseLevelIfUnnormalized(const SamplerResource& sampler) { return (sampler.uses & (SamplerUseImplicitLod | SamplerUseGradient)) != 0u && (sampler.uses & ~static_cast(SamplerUseExplicitLod | SamplerUseImplicitLod | SamplerUseGradient | SamplerUseNonZeroLod | SamplerUseOffset)) == 0u && !sampler.depthCompare; } +inline bool MirrorsIfUnnormalized(const SamplerResource& sampler) { + return sampler.table == NoTable && !sampler.depthCompare && sampler.uses != 0u && ((sampler.uses & ~static_cast(SamplerUseExplicitLod | SamplerUseOffset | SamplerUseNonZeroLod)) == 0u || SamplesBaseLevelIfUnnormalized(sampler)); +} + struct SampledResourcePair { std::uint32_t image = 0; std::uint32_t sampler = 0; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp index 76c0af830f..40f197a264 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ShaderInfo.hpp @@ -13,8 +13,8 @@ struct ShaderInfo { std::uint32_t scratchDwords = 0; std::uint32_t sharedMemoryBytes = 0; static constexpr std::uint32_t MaxImages = 64; - static constexpr std::uint32_t MaxSamplers = 32; - static constexpr std::uint32_t MaxSampledPairs = 64; + static constexpr std::uint32_t MaxSamplers = 64; + static constexpr std::uint32_t MaxSampledPairs = 256; std::vector buffers; std::vector images; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp index f9ec13648c..759194afab 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp @@ -520,6 +520,10 @@ struct PermlaneFlags { bool boundControl = false; }; +struct FloatConvertFlags { + bool nanPayloadUnused = false; +}; + struct MemoryFlags { std::uint32_t index = 0; std::uint32_t pc = 0; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrProgram.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrProgram.hpp index bb66d00d95..69abc3bb2b 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrProgram.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrProgram.hpp @@ -3,13 +3,100 @@ #include "IntermediateRepresentation/IrBlock.hpp" #include "IntermediateRepresentation/IrMetadata.hpp" +#include #include #include #include +#include +#include #include namespace ShaderRecompiler { +class IrValuePool { +public: + IrValuePool() = default; + IrValuePool(const IrValuePool&) = delete; + IrValuePool& operator=(const IrValuePool&) = delete; + IrValuePool(IrValuePool&& other) noexcept : live(std::exchange(other.live, {})), freeSlots(std::exchange(other.freeSlots, {})), chunks(std::exchange(other.chunks, {})), chunkUsed(std::exchange(other.chunkUsed, 0)), chunkCapacity(std::exchange(other.chunkCapacity, 0)) {} + IrValuePool& operator=(IrValuePool&& other) noexcept { + if (this != &other) { + release(); + live = std::exchange(other.live, {}); + freeSlots = std::exchange(other.freeSlots, {}); + chunks = std::exchange(other.chunks, {}); + chunkUsed = std::exchange(other.chunkUsed, 0); + chunkCapacity = std::exchange(other.chunkCapacity, 0); + } + return *this; + } + ~IrValuePool() { release(); } + + IrValue& Create(IrOpcode opcode, IrType type, std::uint32_t id, std::pmr::memory_resource* useArena) { + live.push_back(nullptr); + const bool reuse = !freeSlots.empty(); + if (!reuse && chunkUsed == chunkCapacity) { + try { + grow(); + } catch (...) { + live.pop_back(); + throw; + } + } + IrValue* slot = reuse ? freeSlots.back() : chunks.back() + chunkUsed; + IrValue* value = nullptr; + try { + value = new (slot) IrValue(opcode, type, id, useArena); + } catch (...) { + live.pop_back(); + throw; + } + if (reuse) freeSlots.pop_back(); + else ++chunkUsed; + live.back() = value; + return *value; + } + template + std::size_t EraseIf(TPredicate predicate) { + const auto dead = std::count_if(live.begin(), live.end(), [&](IrValue* value) { return predicate(*value); }); + if (dead == 0) return 0; + freeSlots.reserve(freeSlots.size() + static_cast(dead)); + const auto removed = std::erase_if(live, [&](IrValue* value) { + if (!predicate(*value)) return false; + value->~IrValue(); + freeSlots.push_back(value); + return true; + }); + live.shrink_to_fit(); + return removed; + } + [[nodiscard]] std::vector::const_iterator begin() const { return live.begin(); } + [[nodiscard]] std::vector::const_iterator end() const { return live.end(); } + +private: + void grow() { + const std::size_t size = chunkCapacity == 0 ? 64 : std::min(chunkCapacity * 2, 4096); + chunks.reserve(chunks.size() + 1); + chunks.push_back(static_cast(::operator new(size * sizeof(IrValue), std::align_val_t{alignof(IrValue)}))); + chunkUsed = 0; + chunkCapacity = size; + } + void release() { + for (IrValue* value : live) value->~IrValue(); + for (IrValue* chunk : chunks) ::operator delete(chunk, std::align_val_t{alignof(IrValue)}); + live.clear(); + freeSlots.clear(); + chunks.clear(); + chunkUsed = 0; + chunkCapacity = 0; + } + std::vector live; + std::vector freeSlots; + std::vector chunks; + std::size_t chunkUsed = 0; + std::size_t chunkCapacity = 0; +}; + class IrProgram { public: [[nodiscard]] std::vector>& Blocks(); @@ -41,7 +128,7 @@ private: std::vector> blocks; IrUseArena useArena; std::vector blockOrder; - std::vector> values; + IrValuePool values; IrResourcePlan resourcePlan; IrProgramMetadata metadata; IrBlock* entryBlock = nullptr; diff --git a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrValue.hpp b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrValue.hpp index 699436a0e3..c92fc9bb2a 100644 --- a/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrValue.hpp +++ b/core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrValue.hpp @@ -224,6 +224,34 @@ inline auto IrValue::Uses() const { return std::views::transform(operandUses, [](const IrUse& use) { return use.user; }); } +inline std::uint32_t IrValue::Id() const { + return id; +} + +inline const IrOperands& IrValue::Arguments() const { + return arguments; +} + +inline IrBlock* IrValue::Parent() const { + return parent; +} + +inline std::uint32_t IrValue::ImmediateU32() const { + return static_cast(immediateBits); +} + +inline bool IrValue::IsPhi() const { + return opcode == IrOpcode::Phi; +} + +inline bool IrValue::HasUses() const { + return !operandUses.empty(); +} + +inline std::size_t IrValue::UseCount() const { + return operandUses.size(); +} + inline IrOpcode IrValue::Opcode() const { return opcode; } diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrProgram.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrProgram.cpp index 34a251932b..ac91506ab8 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrProgram.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrProgram.cpp @@ -224,7 +224,7 @@ std::string valueToString(const std::map& ids, cons std::size_t IrProgram::ReclaimUnusedValues() { std::vector owned(nextValueId, nullptr); - for (const auto& value : values) owned[value->Id()] = value.get(); + for (IrValue* value : values) owned[value->Id()] = value; std::vector live(nextValueId, false); std::unordered_set liveElsewhere; std::vector pending; @@ -262,16 +262,14 @@ std::size_t IrProgram::ReclaimUnusedValues() { pending.pop_back(); for (std::size_t index = 0; index < value->ArgumentCount(); ++index) keep(value->Argument(index)); } - for (const auto& value : values) { + for (IrValue* value : values) { if (isLive(*value)) continue; for (std::size_t index = 0; index < value->ArgumentCount(); ++index) value->ReplaceArgument(index, nullptr); } - for (const auto& value : values) { + for (const IrValue* value : values) { if (!isLive(*value) && value->HasUses()) throw std::runtime_error("an unused IR value has an external user"); } - const auto removed = std::erase_if(values, [&](const auto& value) { return !isLive(*value); }); - values.shrink_to_fit(); - return removed; + return values.EraseIf([&](const IrValue& value) { return !isLive(value); }); } void IrProgram::ReleaseUseArena() { @@ -320,10 +318,8 @@ IrBlock& IrProgram::CreateBlock() { } IrValue& IrProgram::CreateValue(IrOpcode opcode, IrType type) { - auto value = std::make_unique(opcode, type, nextValueId, useArena.Resource()); + IrValue& created = values.Create(opcode, type, nextValueId, useArena.Resource()); nextValueId++; - IrValue& created = *value; - values.push_back(std::move(value)); return created; } diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp index b549aa7393..de7da9f4d5 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp @@ -9,22 +9,6 @@ IrValue::IrValue(IrOpcode opcode, IrType type, std::uint32_t id, std::pmr::memor arguments.reserve(IrOpcodeOperandCount(opcode)); } -std::uint32_t IrValue::Id() const { - return id; -} - -const IrOperands& IrValue::Arguments() const { - return arguments; -} - -IrBlock* IrValue::Parent() const { - return parent; -} - -std::uint32_t IrValue::ImmediateU32() const { - return static_cast(immediateBits); -} - void IrValue::AddArgument(IrValue* argument) { arguments.push_back(argument); if (argument == nullptr) { @@ -94,22 +78,10 @@ bool IrValue::IsEmpty() const { return opcode == IrOpcode::Void; } -bool IrValue::IsPhi() const { - return opcode == IrOpcode::Phi; -} - bool IrValue::MayHaveSideEffects() const { return IrOpcodeHasSideEffects(opcode); } -bool IrValue::HasUses() const { - return !operandUses.empty(); -} - -std::size_t IrValue::UseCount() const { - return operandUses.size(); -} - std::size_t IrValue::PhiBlockCount() const { return phiBlocks.size(); } diff --git a/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp b/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp index cc0a71caf7..0a57fb7a27 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/DescriptorBindingBuilder.hpp @@ -16,7 +16,7 @@ struct PreparedDescriptorBinding { std::vector identityImageSwizzle; std::vector samplerFilterElements; std::vector inactive; - std::vector tableSamplerMasks; + std::vector tableSamplerMasks; std::size_t writtenBuffers = 0; std::size_t readOnlyBuffers = 0; }; @@ -49,7 +49,7 @@ struct DescriptorBindingPlan { class DescriptorBindingBuilder { public: - DescriptorBindingPlan Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span exportMappings = {}, bool registrationModules = false, bool fragmentDescriptorSet = false, std::span substitutes = {}) const; + DescriptorBindingPlan Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span exportMappings = {}, bool registrationModules = false, bool fragmentDescriptorSet = false, std::span substitutes = {}, bool shaderImageMinLod = false) const; DescriptorBindingPlan Select(const DescriptorBindingPlan& plan, std::span bindings) const; void Populate(BindingAllocationResult& allocation, const CompiledBindingLayout& compiled, const DescriptorBindingPlan& plan, std::uint32_t userDataBase, const ResourceSnapshot& snapshot, const std::array& partialThreads, const ResolvedImageTables* tables = nullptr) const; void Populate(BindingAllocationResult& allocation, const IrProgram& program, const ResourceSnapshot& snapshot, const std::array& partialThreads, std::span exportMappings = {}) const; diff --git a/core/shader/recompiler/Optimization/include/Optimization/ResourceMaterializer.hpp b/core/shader/recompiler/Optimization/include/Optimization/ResourceMaterializer.hpp index ad1a0f01b5..3ff81bb440 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/ResourceMaterializer.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/ResourceMaterializer.hpp @@ -19,7 +19,7 @@ struct ImageTableElement { RdnaImageDimension dimension = RdnaImageDimension::Unknown; bool depthCompare = false; DescriptorBindingKind kind = DescriptorBindingKind::Buffers; - std::uint32_t samplers = 0; + std::uint64_t samplers = 0; bool atomic = false; bool atomic64 = false; }; @@ -66,6 +66,11 @@ public: using std::runtime_error::runtime_error; }; +class LayerRowSamplesRequired : public std::runtime_error { +public: + using std::runtime_error::runtime_error; +}; + class ResourceMaterializer { public: static std::uint32_t EmulatedCompareState(const ShaderInfo& info, const ResourceSnapshot& snapshot, std::uint32_t index); diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker.hpp index c44b2faf44..a229b26ba6 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker.hpp @@ -5,6 +5,7 @@ #include "UserDataUse.hpp" #include +#include #include #include #include @@ -66,6 +67,25 @@ inline constexpr std::size_t SrtDescriptorForceSync = std::numeric_limits::max() - 1u; using SrtDescriptorScope = std::uint64_t (*)(void* userData, std::size_t bytes); +struct SrtImageViewIdentity { + std::uint64_t generation = 0; + std::uint64_t address = 0; + std::uint64_t rangeAddress = 0; + std::uint64_t rangeBytes = 0; + std::uint64_t allocationAddress = 0; + std::uint64_t allocationBytes = 0; + std::uint64_t backingSerial = 0; + bool hasRange = false; + bool readable = false; + bool writable = false; + bool gpu = false; + + bool operator==(const SrtImageViewIdentity& other) const = default; +}; + +using SrtImageViewNormalizer = bool (*)(void* userData, std::uint32_t maxArrayLayers, std::array& descriptor, SrtImageViewIdentity& identity); +using SrtImageViewGeneration = std::uint64_t (*)(void* userData); + // The guest addresses a walk dereferenced (Detail::Evaluator::EvaluateRawRead): the leaf read of // each pure flat slot (IrResourcePlan::pureFlatSlots) as (flat offset, address), set by the // flattened loop through `leaf`/`leafSlot` around that slot's evaluation, and every other raw read @@ -92,6 +112,8 @@ struct SrtRuntime { SrtMemorySupplier supplyWords = nullptr; SrtUserDataUse* userDataUse = nullptr; SrtDescriptorScope descriptorScope = nullptr; + SrtImageViewNormalizer normalizeImageView = nullptr; + SrtImageViewGeneration imageViewGeneration = nullptr; }; struct SrtForceSyncScope { @@ -112,6 +134,16 @@ enum class RuntimeValueType { Integer }; +bool& SrtEvaluatingBranch(); + +struct SrtBranchScope { + bool saved; + SrtBranchScope() : saved(SrtEvaluatingBranch()) { SrtEvaluatingBranch() = true; } + ~SrtBranchScope() { SrtEvaluatingBranch() = saved; } + SrtBranchScope(const SrtBranchScope&) = delete; + SrtBranchScope& operator=(const SrtBranchScope&) = delete; +}; + class SrtWalker { public: void BuildPlan(IrProgram& program) const; diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDescriptorEvaluation.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDescriptorEvaluation.hpp index 2b881a4164..21887199de 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDescriptorEvaluation.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtDescriptorEvaluation.hpp @@ -15,6 +15,8 @@ namespace ShaderRecompiler::Detail { // Why the last EvaluateRuntimeSourcesImpl call on this thread returned false. const std::string& RuntimeSourceFailureReason(); +bool ApplySourceBlockGuards(const IrResourcePlan& program, std::span visited, std::span active); + bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span sources, const SrtRuntime& runtime, std::vector& results, std::vector& flat, bool evaluateFlat, std::span cleanFlatSlots, std::vector& activeSources, std::vector* poison = nullptr, std::uint32_t* nullRootReads = nullptr); } diff --git a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtEvaluator.hpp b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtEvaluator.hpp index c54e252bdf..83084fad44 100644 --- a/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtEvaluator.hpp +++ b/core/shader/recompiler/Optimization/include/Optimization/SrtWalker/SrtEvaluator.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -180,6 +181,7 @@ public: static bool MayEvaluate(const IrResourcePlan& program, IrValue* value, const IrValue* activeMask = nullptr); static bool MayDecide(const IrResourcePlan& program, IrValue* value); + static bool MayDecide(const IrResourcePlan& program, IrValue* value, std::unordered_map& decided); static EvaluatorReads Reads(const IrResourcePlan& program, const IrValue& value); bool Evaluate(TNode value, std::uint32_t& result); bool EvaluateWide(TNode value, std::uint64_t& result); diff --git a/core/shader/recompiler/Optimization/src/DenormalFlushEliminator.cpp b/core/shader/recompiler/Optimization/src/DenormalFlushEliminator.cpp index f8a013e4b7..23af2bf9ff 100644 --- a/core/shader/recompiler/Optimization/src/DenormalFlushEliminator.cpp +++ b/core/shader/recompiler/Optimization/src/DenormalFlushEliminator.cpp @@ -71,6 +71,15 @@ bool clearsSignificand(const IrValue& value) { return false; } +bool setsExponentBit(const IrValue& value) { + if (value.ArgumentCount() != 2u) return false; + for (std::size_t index = 0; index < 2u; ++index) { + const IrValue* operand = value.Argument(index)->Resolve(); + if (operand->HasImmediate() && (operand->ImmediateU32() & 0x7f800000u) != 0u) return true; + } + return false; +} + IrValue* flushedSource(const IrValue& value) { if (value.Opcode() != IrOpcode::SelectU32 || value.ArgumentCount() != 3u) return nullptr; IrValue* source = value.Argument(2)->Resolve(); @@ -105,6 +114,10 @@ private: case IrOpcode::ConvertF32F16: case IrOpcode::FPFloor32: case IrOpcode::FPCeil32: + case IrOpcode::FPTrunc32: + case IrOpcode::FPRoundEven32: + case IrOpcode::FPSqrt: + case IrOpcode::FPRecipSqrt32: return true; case IrOpcode::BitCastU32F32: case IrOpcode::BitCastF32U32: @@ -118,6 +131,8 @@ private: case IrOpcode::BitwiseAnd32: if (const IrValue* operand = maskedOperand(&value, IrOpcode::BitwiseAnd32, 0x7fffffffu)) return Contains(operand); return value.ArgumentCount() == 2u && clearsSignificand(value); + case IrOpcode::BitwiseOr32: + return setsExponentBit(value); case IrOpcode::SelectU32: case IrOpcode::SelectF32: { const IrValue* condition = value.Argument(0); diff --git a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp index 50bb4afd71..db5911ef11 100644 --- a/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp +++ b/core/shader/recompiler/Optimization/src/DescriptorBindingBuilder.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -207,15 +208,57 @@ const char* UnnormalizedUseReason(std::uint32_t uses) { } } +std::array RelativeMinLod(std::span words) { + const auto minLod = (words[1] >> 8u) & 0xfffu; + const auto baseLevel = (words[3] >> 12u) & 0xfu; + const auto maxMip = (words[5] >> 4u) & 0xfu; + const auto lastLevel = std::max(baseLevel <= maxMip ? std::min((words[3] >> 16u) & 0xfu, maxMip) : (words[3] >> 16u) & 0xfu, baseLevel); + return {minLod > baseLevel * 256u ? std::min(minLod - baseLevel * 256u, (lastLevel - baseLevel) * 256u) : 0u, lastLevel - baseLevel}; +} + +void RequireShaderMinLod(const ShaderInfo& info, const ResourceSnapshot& snapshot, std::uint32_t image, const ImageResource& mode, std::uint32_t clamp) { + const auto prefix = "image " + std::to_string(image) + " applies its T#'s minimum LOD of " + std::to_string(clamp) + "/256 in the shader"; + if ((mode.emulatedCompare & EmulatedCompare::Enabled) != 0u || mode.conversionFormat == IrBufferFormat::Format11_11_10UNorm) fail(prefix + " to an emulated depth-compare or 11:11:10 sample, which is not implemented"); + for (const auto& pair : info.sampledPairs) { + if (pair.image != image) continue; + const auto& sampler = info.samplers.at(pair.sampler); + const auto name = "S# " + std::to_string(pair.sampler); + if (sampler.table != NoTable) fail(prefix + " and is sampled through " + name + " from a table, whose LOD range is unknown, which is not implemented"); + if ((sampler.uses & SamplerUseQueryLod) != 0u) fail(prefix + " and " + name + " is used by image_get_lod, which is not implemented"); + if ((sampler.uses & SamplerUseGather) != 0u && clamp >= 256u) fail(prefix + " and " + name + " is used by a gather, which reads below that level, which is not implemented"); + const auto& words = snapshot.samplers.at(pair.sampler).dwords; + const bool unnormalized = (words[0] & ForceUnnormalizedBit) != 0u; + const bool mipmapped = !unnormalized && ((words[2] >> 26u) & 3u) != 0u; + const auto maxLod = mipmapped ? (words[1] >> 12u) & 0xfffu : 0u; + if (!unnormalized && maxLod == 0u) { + if (((words[2] >> 20u) & 3u) != ((words[2] >> 22u) & 3u)) fail(prefix + " and " + name + ", which holds the LOD at 0, has different magnification and minification filters, between which the clamp moves the choice, which is not implemented"); + for (const auto& other : info.sampledPairs) { + if (other.sampler != pair.sampler || other.image == image) continue; + const auto otherName = "image " + std::to_string(other.image); + if (info.images.at(other.image).table != NoTable) fail(prefix + " and " + name + ", which holds the LOD at 0, also samples " + otherName + " from a table, which is not implemented"); + const auto [otherClamp, otherLevels] = RelativeMinLod(snapshot.images.at(other.image).dwords); + if (otherClamp != clamp && otherLevels != 0u) fail(prefix + " and " + name + ", which holds the LOD at 0, also samples " + otherName + " with another minimum LOD, which is not implemented"); + } + continue; + } + if ((sampler.uses & (SamplerUseExplicitLod | SamplerUseImplicitLod | SamplerUseGradient)) == 0u) continue; + if (maxLod < clamp) fail(prefix + " above the maximum LOD of " + std::to_string(maxLod) + "/256 of " + name + (mipmapped ? "" : unnormalized ? " (unnormalized)" : " (no mip filter)") + ", which is not implemented"); + if ((((words[2] >> 20u) ^ (words[2] >> 22u)) & 1u) != 0u) fail(prefix + " and " + name + " has different magnification and minification filters, between which the clamp moves the choice, which is not implemented"); + const auto bias = static_cast(((words[2] & 0x3fffu) ^ 0x2000u) - 0x2000u); + if (bias != 0 && (sampler.uses & SamplerUseExplicitLod) != 0u) fail(prefix + " to an explicit LOD that " + name + " biases by " + std::to_string(bias) + "/256, which is not implemented"); + } +} + struct UnnormalizedProof { std::vector samplers; std::vector images; std::vector imagesAtBaseLevel; std::vector samplersIgnoreCoordinates; + std::vector mirroredAxes; }; UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapshot& snapshot) { - UnnormalizedProof proof{std::vector(info.samplers.size()), std::vector(info.images.size()), std::vector(info.images.size()), std::vector(info.samplers.size())}; + UnnormalizedProof proof{std::vector(info.samplers.size()), std::vector(info.images.size()), std::vector(info.images.size()), std::vector(info.samplers.size()), std::vector(info.samplers.size())}; for (std::uint32_t r = 0; r < info.samplers.size(); r++) { if (snapshot.samplers.at(r).dwordCount != 4u) fail("sampler descriptor must contain four dwords"); const auto& sampler = info.samplers[r]; @@ -271,6 +314,9 @@ UnnormalizedProof ProveUnnormalized(const ShaderInfo& info, const ResourceSnapsh } if (nullMismatch != nullptr) failUnnormalized(nullMismatch); proof.samplers[r] = true; + const auto clampX = snapshot.samplers[r].dwords[0] & 7u; + const auto clampY = (snapshot.samplers[r].dwords[0] >> 3u) & 7u; + proof.mirroredAxes[r] = (clampX == 1u || clampX == 3u ? 1u : 0u) | (clampY == 1u || clampY == 3u ? 2u : 0u); } for (std::uint32_t resource = 0; resource < info.images.size(); resource++) { bool exact = proof.images[resource]; @@ -343,7 +389,7 @@ BufferSubstitute BufferSpecializationFields(const DescriptorValue& descriptor, s return {descriptor.dwords[1] & 0xffff0000u, descriptor.dwords[3], present ? descriptor.dwords[0] & 3u : 0u, present}; } -DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span exportMappings, bool registrationModules, bool fragmentDescriptorSet, std::span substitutes) const { +DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& layout, const ShaderInfo& info, IrShaderStage stage, const ResourceSnapshot& snapshot, std::span exportMappings, bool registrationModules, bool fragmentDescriptorSet, std::span substitutes, bool shaderImageMinLod) const { if (stage == IrShaderStage::Pixel && exportMappings.size() != 8u) fail("fragment runtime export mappings are missing"); if (info.images.size() > ShaderInfo::MaxImages) fail("runtime image count exceeds the static capacity"); DescriptorBindingPlan plan; @@ -351,6 +397,7 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l for (std::uint32_t index = 0; index < info.samplers.size(); ++index) { if (FoldsTexelOffsets(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::FoldOffsetBase + index, unnormalized.samplers[index] ? 1u : 0u}); if (SamplesBaseLevelIfUnnormalized(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::UnnormalizedBaseLevelBase + index, unnormalized.samplers[index] ? 1u : 0u}); + if (MirrorsIfUnnormalized(info.samplers[index])) plan.specialization.push_back({PipelineSpecialization::MirrorUnnormalizedBase + index, unnormalized.mirroredAxes[index]}); } std::vector compareStates(info.images.size()); for (std::uint32_t index = 0; index < info.images.size(); ++index) { @@ -402,6 +449,12 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l const auto first = PipelineSpecialization::ImageBase + index * PipelineSpecialization::ImageWords; plan.specialization.push_back({first, imageModes[index]}); for (std::uint32_t component = 0; component < 4u; ++component) plan.specialization.push_back({first + 1u + component, (snapshot.images.at(index).dwords[3] >> (component * 3u)) & 7u}); + if (shaderImageMinLod) { + const auto& words = snapshot.images.at(index).dwords; + const auto clamp = RelativeMinLod(words)[0]; + if (clamp != 0u) RequireShaderMinLod(info, snapshot, index, modes.at(imageModes[index]), clamp); + plan.specialization.push_back({PipelineSpecialization::MinLodBase + index, std::bit_cast(static_cast(clamp) / 256.0f)}); + } const auto& image = info.images[index]; if (image.resourceClass != ImageResourceClass::Storage || !image.written || image.atomic) continue; const auto code = (snapshot.images.at(index).dwords[1] >> 20u) & 0x1ffu; @@ -561,12 +614,12 @@ DescriptorBindingPlan DescriptorBindingBuilder::Prepare(const IrBindingLayout& l plan.runtimeState.assign(layout.runtimeStateDwords, 0u); for (std::uint32_t target = 0; target < exportMappings.size(); ++target) plan.runtimeState.at(RuntimeAbi::StateExportMappings + target / 4u) |= static_cast(exportMappings[target]) << (target % 4u * 8u); } - std::vector samplerMasks(info.samplers.size()); + std::vector samplerMasks(info.samplers.size()); for (const auto& binding : plan.bindings) { if (binding.descriptor.role != DescriptorRole::GuestSamplers) continue; - if (binding.resources.size() > ShaderInfo::MaxSamplers) fail("prepared sampler mask capacity exceeded"); + if (binding.resources.size() > std::numeric_limits::digits) fail("prepared sampler mask capacity exceeded"); for (std::uint32_t element = 0; element < binding.resources.size(); ++element) { - if (binding.inactive.empty() || !binding.inactive[element]) samplerMasks.at(binding.resources[element]) |= 1u << element; + if (binding.inactive.empty() || !binding.inactive[element]) samplerMasks.at(binding.resources[element]) |= std::uint64_t{1} << element; } } for (auto& binding : plan.bindings) { @@ -661,9 +714,9 @@ void DescriptorBindingBuilder::Populate(BindingAllocationResult& allocation, con physical.imageWritten.push_back(ImageBindingResourceClass(element.kind) == ImageResourceClass::Storage); physical.imageAtomic.push_back(element.atomic); physical.imageAtomic64.push_back(element.atomic64); - std::uint32_t samplers = 0; + std::uint64_t samplers = 0; for (std::uint32_t sampler = 0; sampler < entry.tableSamplerMasks.size(); ++sampler) { - if ((element.samplers & (1u << sampler)) != 0u) samplers |= entry.tableSamplerMasks[sampler]; + if (((element.samplers >> sampler) & 1u) != 0u) samplers |= entry.tableSamplerMasks[sampler]; } physical.imageSamplers.push_back(samplers); } diff --git a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp index 5aad58d2f5..533f170f65 100644 --- a/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp @@ -34,6 +34,8 @@ namespace ShaderRecompiler { +static_assert(ShaderInfo::MaxSamplers <= std::numeric_limits::digits); + namespace { using ImageTableAbi::PoisonReason; @@ -59,6 +61,7 @@ struct DecodedImage { bool depthUnorm16 = false; IrBufferFormat packedFormat = IrBufferFormat::Invalid; bool srgbDecode = false; + bool layerRows = false; }; ShaderBufferResource decodeBufferDescriptor(const DescriptorValue& value) { @@ -76,13 +79,20 @@ bool nullImageDescriptor(const DescriptorValue& descriptor) { return descriptor.dwords[0] == 0u && (descriptor.dwords[1] & 0xffu) == 0u; } +bool imageFormatUnset(const DescriptorValue& descriptor) { + const auto format = (descriptor.dwords[1] >> 20u) & 0x1ffu; + return format == 0u || format == 0x1ffu; +} + ImageType rawImageType(const DescriptorValue& descriptor) { return static_cast((descriptor.dwords[3] >> 28u) & 0xfu); } bool nullImageDescriptor(const ImageResource& image, const DescriptorValue& descriptor) { if (nullImageDescriptor(descriptor)) return true; - return static_cast(rawImageType(descriptor)) <= 1u && image.table == NoTable && image.read && !image.written && !image.atomic; + const auto type = rawImageType(descriptor); + if (image.table == NoTable && image.resourceClass == ImageResourceClass::Sampled && image.read && !image.written && !image.atomic && type >= ImageType::Color1D && type <= ImageType::Color2DMsaa && imageFormatUnset(descriptor)) return true; + return static_cast(type) <= 1u && image.table == NoTable && (image.read || image.written) && !image.atomic; } IrBufferFormat rawImageFormat(const DescriptorValue& descriptor) { @@ -199,6 +209,31 @@ PoisonReason invalidImageReason(const DescriptorValue& descriptor) { return PoisonReason::Dimension; } +bool layerRowsCandidate(const ImageResource& image) { + return image.dimension == RdnaImageDimension::Dim1DArray && image.layerRowsCompatible && image.resourceClass != ImageResourceClass::Storage && image.table == NoTable && !image.depthCompare; +} + +bool registeredSampledArrayCandidate(const ImageResource& image) { + return image.dimension == RdnaImageDimension::Dim1DArray && image.layerRowsCompatible && image.resourceClass == ImageResourceClass::Sampled && !image.depthCompare && !image.atomic && !image.written; +} + +void normalizeRegisteredImageView(const ImageResource& image, const SrtRuntime& runtime, std::uint32_t maxArrayLayers, std::uint32_t resource, std::uint32_t record, DescriptorValue& descriptor, std::vector& views) { + if (runtime.normalizeImageView == nullptr || !registeredSampledArrayCandidate(image) || descriptor.dwordCount != 8u || nullImageDescriptor(image, descriptor)) return; + const auto provenance = descriptor; + auto identity = SrtImageViewIdentity{}; + auto effective = descriptor.dwords; + const bool normalized = runtime.normalizeImageView(runtime.userContext, maxArrayLayers, effective, identity); + descriptor.dwords = effective; + static_cast(normalized); + views.push_back({resource, record, provenance, descriptor, identity.generation, identity.address, identity.rangeAddress, identity.rangeBytes, identity.allocationAddress, identity.allocationBytes, identity.backingSerial, identity.hasRange, identity.readable, identity.writable, identity.gpu}); +} + +bool viewsPastLayerLimit(const DescriptorValue& descriptor, std::uint32_t limit) { + const auto lastArray = descriptor.dwords[4] & 0x1fffu; + const auto baseArray = (descriptor.dwords[4] >> 16u) & 0x1fffu; + return limit != 0u && baseArray <= lastArray && lastArray - baseArray >= limit; +} + ImageDecode inspectImageDescriptor(const DescriptorValue& descriptor, const ImageResource& base, std::uint32_t srgbDecodeFormats) { ImageDecode result; auto& decoded = result.decoded; @@ -230,6 +265,10 @@ ImageDecode inspectImageDescriptor(const DescriptorValue& descriptor, const Imag if (GcnGuestShaders() && imageCoordinates(decoded.dimension) != imageCoordinates(base.dimension)) { throw std::runtime_error("GCN image instruction on a descriptor of type " + std::to_string(static_cast(rawImageType(descriptor))) + " needs a different address count than its inferred dimension; not implemented"); } + if (decoded.dimension == RdnaImageDimension::Dim1DArray && layerRowsCandidate(base) && viewsPastLayerLimit(descriptor, base.maxArrayLayers)) { + decoded.dimension = RdnaImageDimension::Dim2D; + decoded.layerRows = true; + } decoded.cube = descriptorIsCube(descriptor); const auto format = rawImageFormat(descriptor); if (decoded.dimension == RdnaImageDimension::Dim2DMsaa && rawImageType(descriptor) == ImageType::Color2DMsaa && multisamplePlaneRead(base) && !IsFmaskTextureFormat(format) && !IsDepthBitsTexture(descriptor.dwords[1], descriptor.dwords[3])) decoded.dimension = RdnaImageDimension::Dim2D; @@ -287,8 +326,8 @@ ImageDecode inspectImageDescriptor(const DescriptorValue& descriptor, const Imag if (base.table != NoTable) return rejectImage(PoisonReason::Conversion, "samples a converted unorm image selected through an image table, which is not implemented"); if (decoded.cube || (decoded.dimension != RdnaImageDimension::Dim2D && decoded.dimension != RdnaImageDimension::Dim2DArray)) return rejectImage(PoisonReason::Conversion, "samples a converted unorm image that is not a 2D or 2D array view, which is not implemented"); if ((base.emulatedCompare & EmulatedCompare::ConvertedSampleRequiresSingleLevel) != 0u && ((descriptor.dwords[3] >> 12u) & 0xfu) != ((descriptor.dwords[3] >> 16u) & 0xfu)) return rejectImage(PoisonReason::Conversion, "samples a converted unorm image with several mip levels at a LOD that is not 0, which is not implemented"); + if (!base.depthBitsCompatible) return rejectImage(PoisonReason::Conversion, "samples a converted unorm image with 16-bit data, which is not implemented"); } - if (!base.depthBitsCompatible) return rejectImage(PoisonReason::Conversion, floating ? "reads or writes a converted float image with 16-bit data, which is not implemented" : "reads or writes a converted unorm image with 16-bit data, which is not implemented"); for (std::uint32_t component = 0; component < 4u; ++component) { if (((descriptorImageSwizzle(descriptor) >> (component * 3u)) & 7u) == 7u) return rejectImage(PoisonReason::Conversion, "selects a channel the converted image format does not have"); } @@ -1371,6 +1410,15 @@ private: out.codes = records.contents.codes; out.words = records.contents.words; out.readRanges = records.contents.readRanges; + for (std::uint32_t resource = 0; resource < plan.info.images.size(); ++resource) { + const auto& image = plan.info.images[resource]; + if (image.table != table || !registeredSampledArrayCandidate(image) || runtime.normalizeImageView == nullptr) continue; + auto effectiveWords = records.contents.words.Copy(); + std::vector identities; + identities.reserve(effectiveWords.size()); + for (std::uint32_t record = 0; record < effectiveWords.size(); ++record) normalizeRegisteredImageView(image, runtime, plan.maxImageArrayLayers, resource, record, effectiveWords[record], identities); + out.resourceViews.push_back({resource, SharedValues(std::move(effectiveWords)), std::move(identities)}); + } auto& counters = tableCounters(); if (records.fault != 0u) counters.poison[records.fault & ~ImageTableAbi::PoisonFlag].fetch_add(1, std::memory_order_relaxed); if (records.contents.unmapped != 0u) counters.poison[static_cast(PoisonReason::Unmapped)].fetch_add(records.contents.unmapped, std::memory_order_relaxed); @@ -1396,6 +1444,7 @@ private: void materializeSnapshot(const IrResourcePlan& plan, const SrtRuntime& runtime, SrtWalker& walker, ResourceSnapshot& snapshot, std::vector& activeSources) { snapshot = ResourceSnapshot{}; + snapshot.imageAllocationGeneration = runtime.imageViewGeneration != nullptr ? runtime.imageViewGeneration(runtime.userContext) : 0u; if (plan.uniformFill.fill.kind != UniformFillKind::None) { const auto words = plan.uniformFill.fill.words; if (words == 0u || words > plan.uniformFill.values.size()) { @@ -1472,6 +1521,8 @@ void materializeSnapshot(const IrResourcePlan& plan, const SrtRuntime& runtime, descriptor = DescriptorValue{}; descriptor.dwordCount = 8u; } + const auto maxArrayLayers = image.maxArrayLayers != 0u ? image.maxArrayLayers : plan.maxImageArrayLayers; + normalizeRegisteredImageView(image, runtime, maxArrayLayers, i, 0u, descriptor, snapshot.registeredImages); snapshot.images[i] = descriptor; } @@ -1664,6 +1715,11 @@ void materializeTables(const IrResourcePlan& plan, ResourceSnapshot& snapshot) { if (!image.convertedSamples && !image.srgbDecodeCompatible && convertedSampleCompatible(image) && descriptor.dwordCount == 8u && !nullImageDescriptor(image, descriptor)) { if (rawImageFormat(descriptor) == IrBufferFormat::Format11_11_10UNorm) throw ConvertedSampleRequired("samples a converted image through a shader prepared without converted-sample modes"); } + if (image.maxArrayLayers == 0u && plan.maxImageArrayLayers != 0u && layerRowsCandidate(image) && descriptor.dwordCount == 8u && !nullImageDescriptor(image, descriptor)) { + auto rows = image; + rows.maxArrayLayers = plan.maxImageArrayLayers; + if (inspectImageDescriptor(descriptor, rows, image.srgbDecodeFormats).decoded.layerRows) throw LayerRowSamplesRequired("samples a 1D array past maxImageArrayLayers through a shader prepared without layer-row modes"); + } static_cast(ResourceMaterializer::RuntimeImageMode(image, descriptor, plan.info.runtimeImageModes.at(i))); } } @@ -1776,14 +1832,17 @@ bool sameColumns(const std::vector& left, const std::v for (std::size_t index = 0; index < left.size(); index++) { const auto& a = left[index]; const auto& b = right[index]; - if (a.base != b.base || a.size != b.size || a.records != b.records || a.keys != b.keys || a.fault != b.fault || a.outside != b.outside || !a.codes.Shares(b.codes) || !a.words.Shares(b.words)) return false; + if (a.base != b.base || a.size != b.size || a.records != b.records || a.keys != b.keys || a.fault != b.fault || a.outside != b.outside || !a.codes.Shares(b.codes) || !a.words.Shares(b.words) || a.resourceViews != b.resourceViews) return false; } return true; } std::size_t resolutionFootprint(const ResolvedImageTables& resolved, const std::vector& tables) { std::size_t footprint = resolved.map.size() + (resolved.elements.size() * sizeof(ImageTableElement) + resolved.samplers.size() * sizeof(ImageTableSamplerElement) + resolved.poison.size() * sizeof(ImageTablePoisonRecord)) / sizeof(std::uint32_t); - for (const auto& table : tables) footprint += 16u + table.codes.size() + table.words.size() * 9u; + for (const auto& table : tables) { + footprint += 16u + table.codes.size() + table.words.size() * 9u; + for (const auto& view : table.resourceViews) footprint += view.words.size() * 9u + view.identities.size() * (sizeof(RegisteredImageView) / sizeof(std::uint32_t)); + } return footprint; } @@ -1831,9 +1890,9 @@ ResolvedImageTables ResourceMaterializer::ResolveImageTables(const ShaderInfo& i }; HashScratchIndex elementIndex{threadScratch()}; HashScratchIndex samplerIndex{threadScratch()}; - std::vector directSamplers(info.images.size(), 0u); + std::vector directSamplers(info.images.size(), 0u); for (const auto& pair : info.sampledPairs) { - if (info.samplers.at(pair.sampler).table == NoTable) directSamplers.at(pair.image) |= 1u << pair.sampler; + if (info.samplers.at(pair.sampler).table == NoTable) directSamplers.at(pair.image) |= std::uint64_t{1} << pair.sampler; } const auto reducingSampler = reducingSamplers(info, snapshot); const auto imageRecord = [&](std::uint32_t resource, const DescriptorValue& words) { @@ -1950,7 +2009,9 @@ ResolvedImageTables ResourceMaterializer::ResolveImageTables(const ShaderInfo& i if (const auto known = records.Find(code)) { mapped = *known; } else { - const auto& words = column.words.at(code); + const auto& rawWords = column.words.at(code); + const auto view = sampler ? column.resourceViews.end() : std::find_if(column.resourceViews.begin(), column.resourceViews.end(), [resource](const auto& candidate) { return candidate.resource == resource; }); + const auto& words = !sampler && view != column.resourceViews.end() ? view->words.at(code) : rawWords; if (sampler || (words.availableDwordMask & (1u << 3u)) == 0u || !nullImageDescriptor(info.images[resource], words)) mapped = sampler ? samplerRecord(resource, words) : imageRecord(resource, words); records.Set(code, mapped); } @@ -2107,6 +2168,12 @@ std::vector ResourceMaterializer::RuntimeImageModes(const ImageRe cube.cube = true; modes.push_back(cube); } + if (layerRowsCandidate(image) && image.maxArrayLayers != 0u && !depth && conversion == IrBufferFormat::Invalid && packed == IrBufferFormat::Invalid) { + auto rows = mode; + rows.dimension = RdnaImageDimension::Dim2D; + rows.layerRows = true; + modes.push_back(rows); + } if (image.dimension == RdnaImageDimension::Dim1DArray || image.dimension == RdnaImageDimension::Dim2DArray || image.dimension == RdnaImageDimension::Dim2DMsaaArray) { auto plain = mode; plain.dimension = image.dimension == RdnaImageDimension::Dim1DArray ? RdnaImageDimension::Dim1D : image.dimension == RdnaImageDimension::Dim2DArray ? RdnaImageDimension::Dim2D : RdnaImageDimension::Dim2DMsaa; @@ -2141,8 +2208,8 @@ std::vector ResourceMaterializer::RuntimeImageModes(const ImageRe append(IrTextureNumericClass::Uint, IrBufferFormat::Invalid, IrBufferFormat::Invalid, false, false); if (!storage) append(IrTextureNumericClass::Sint, IrBufferFormat::Invalid, IrBufferFormat::Invalid, false, false); append(IrTextureNumericClass::Uint, IrBufferFormat::Format11_11_10UInt, IrBufferFormat::Invalid, false, false); - if ((image.srgbDecodeCompatible || (image.convertedSamples && convertedSampleCompatible(image))) && image.depthBitsCompatible) append(IrTextureNumericClass::Uint, IrBufferFormat::Format11_11_10UNorm, IrBufferFormat::Invalid, false, false); - if (image.srgbDecodeCompatible && image.depthBitsCompatible) { + if (image.srgbDecodeCompatible || (image.convertedSamples && convertedSampleCompatible(image))) append(IrTextureNumericClass::Uint, IrBufferFormat::Format11_11_10UNorm, IrBufferFormat::Invalid, false, false); + if (image.srgbDecodeCompatible) { append(IrTextureNumericClass::Uint, IrBufferFormat::Format11_11_10Float, IrBufferFormat::Invalid, false, false); append(IrTextureNumericClass::Uint, IrBufferFormat::Format10_11_11Float, IrBufferFormat::Invalid, false, false); } @@ -2228,6 +2295,7 @@ bool ResourceMaterializer::NullImageDescriptor(const ImageResource& image, const const char* ResourceMaterializer::UnnormalizedSampleMismatch(const ImageResource& image) { if (image.table != NoTable) return "samples an image selected at run time"; if (image.constantSwizzle) return nullptr; + if (image.layerRows) return "samples a 1D array laid out as rows of a 2D image"; if ((image.dimension != RdnaImageDimension::Dim1D && image.dimension != RdnaImageDimension::Dim2D) || image.cube) return "samples a 1D-array, 2D-array, 3D, cube or multisampled image"; if (image.depthCompare) return "is used with depth comparison"; if (image.conversionFormat != IrBufferFormat::Invalid || image.packed) return "samples an image that needs a format conversion or packed access"; @@ -2280,7 +2348,7 @@ RuntimeImageModeMatch ResourceMaterializer::TryRuntimeImageMode(const ImageResou const auto& mode = modes[index]; if (mode.constantSwizzle) continue; if (((mode.emulatedCompare & EmulatedCompare::Enabled) != 0u) != emulated) continue; - if (mode.numericClass == decoded.numericClass && mode.dimension == decoded.dimension && mode.conversionFormat == decoded.conversionFormat && mode.packedFormat == decoded.packedFormat && mode.cube == decoded.cube && mode.depthBits == decoded.depthBits && mode.depthUnorm16 == decoded.depthUnorm16 && mode.srgbDecode == decoded.srgbDecode) { + if (mode.numericClass == decoded.numericClass && mode.dimension == decoded.dimension && mode.conversionFormat == decoded.conversionFormat && mode.packedFormat == decoded.packedFormat && mode.cube == decoded.cube && mode.depthBits == decoded.depthBits && mode.depthUnorm16 == decoded.depthUnorm16 && mode.srgbDecode == decoded.srgbDecode && mode.layerRows == decoded.layerRows) { result.mode = index; return result; } @@ -2572,6 +2640,7 @@ IrResourcePlan ResourceMaterializer::ExtractPlan(const IrProgram& program) const plan.userDataBase = source.userDataBase; plan.userDataCount = source.userDataCount; plan.srgbDecodeFormats = source.srgbDecodeFormats; + plan.maxImageArrayLayers = source.maxImageArrayLayers; plan.memoryInfo = source.memoryInfo; plan.descriptorSources = source.descriptorSources; plan.controlFlow = source.controlFlow; @@ -2583,8 +2652,9 @@ IrResourcePlan ResourceMaterializer::ExtractPlan(const IrProgram& program) const plan.info = source.info; PrepareImageModes(plan.info); plan.uniformFill = source.uniformFill; + std::unordered_map decided; for (auto& block : plan.controlFlow) { - if (block.condition != nullptr && !Detail::Evaluator::MayDecide(source, block.condition)) block.condition = nullptr; + if (block.condition != nullptr && !Detail::Evaluator::MayDecide(source, block.condition, decided)) block.condition = nullptr; } const auto addSource = [&plan](std::uint32_t index) { if (index >= plan.descriptorSources.size()) { @@ -2641,15 +2711,21 @@ void ResourceMaterializer::Materialize(const IrResourcePlan& program, const SrtR for (std::uint32_t attempt = 0; attempt < 3u; ++attempt) { const SrtForceSyncScope forceSync(runtime, attempt == 2u); const auto beforeSnapshot = runtime.descriptorScope != nullptr ? runtime.descriptorScope(runtime.userContext, 0u) : 0u; + const auto beforeImageGeneration = runtime.imageViewGeneration != nullptr ? runtime.imageViewGeneration(runtime.userContext) : 0u; activeSources.clear(); try { materializeSnapshot(plan, runtime, walker, nextSnapshot, activeSources); const auto afterSnapshot = runtime.descriptorScope != nullptr ? runtime.descriptorScope(runtime.userContext, 0u) : 0u; TableSnapshotter(plan, runtime, walker, activeSources, nextSnapshot.tables).Run(); const auto afterTables = runtime.descriptorScope != nullptr ? runtime.descriptorScope(runtime.userContext, 0u) : 0u; - if (runtime.descriptorScope != nullptr && (afterTables != afterSnapshot || (plan.uniformFill.fill.kind != UniformFillKind::None && afterSnapshot != beforeSnapshot))) { + const auto afterImageGeneration = runtime.imageViewGeneration != nullptr ? runtime.imageViewGeneration(runtime.userContext) : 0u; + if ((runtime.descriptorScope != nullptr && (afterTables != afterSnapshot || (plan.uniformFill.fill.kind != UniformFillKind::None && afterSnapshot != beforeSnapshot))) || afterImageGeneration != beforeImageGeneration) { continue; } + for (const auto& table : nextSnapshot.tables.tables) { + for (const auto& view : table.resourceViews) nextSnapshot.registeredImages.insert(nextSnapshot.registeredImages.end(), view.identities.begin(), view.identities.end()); + } + nextSnapshot.imageAllocationGeneration = nextSnapshot.registeredImages.empty() ? 0u : afterImageGeneration; const auto started = MaterializeProfiled() ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; materializeForcedDegamma(plan, nextSnapshot); materializeTables(plan, nextSnapshot); @@ -2660,8 +2736,13 @@ void ResourceMaterializer::Materialize(const IrResourcePlan& program, const SrtR } catch (const ConvertedSampleRequired&) { throw; } catch (const std::runtime_error&) { - if (runtime.descriptorScope == nullptr || attempt == 2u) throw; - if (runtime.descriptorScope(runtime.userContext, 0u) == beforeSnapshot) attempt = 1u; + if (attempt == 2u) throw; + const auto afterImageGeneration = runtime.imageViewGeneration != nullptr ? runtime.imageViewGeneration(runtime.userContext) : 0u; + if (runtime.descriptorScope == nullptr) { + if (runtime.imageViewGeneration == nullptr || afterImageGeneration == beforeImageGeneration) throw; + } else if (runtime.descriptorScope(runtime.userContext, 0u) == beforeSnapshot && (runtime.imageViewGeneration == nullptr || afterImageGeneration == beforeImageGeneration)) { + attempt = 1u; + } } } if (!completed) throw std::runtime_error("runtime resource snapshot did not stabilize"); diff --git a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp index 221bc976d0..a7e26c97d0 100644 --- a/core/shader/recompiler/Optimization/src/ResourceTracker.cpp +++ b/core/shader/recompiler/Optimization/src/ResourceTracker.cpp @@ -1,4 +1,5 @@ #include "Optimization/ResourceTracker.hpp" +#include "Optimization/DeadCodeEliminator.hpp" #include "Optimization/SrtWalker.hpp" #include "Optimization/ResourceMaterializer.hpp" #include "SpirvBackend/SpirvBufferFormat.hpp" @@ -19,6 +20,7 @@ #include #include #include +#include #include #include @@ -120,6 +122,9 @@ public: fail("SRT plan is not ready"); } SplitDescriptorPhis(); + if (m_foldedWeb) { + DeadCodeEliminator{}.Eliminate(m_program); + } PlanTableColumns(); std::unordered_map blockIndices; for (const IrBlock* block : m_program.BlockOrder()) { @@ -255,10 +260,28 @@ private: return true; } - std::uint32_t InternSource(const DescriptorSource& descriptor) { + std::uint32_t InternSource(DescriptorSource descriptor) { + if (!m_readOrigins.empty()) { + std::vector pending(descriptor.dwords.begin(), descriptor.dwords.begin() + descriptor.dwordCount); + std::unordered_set visited; + const auto& order = m_program.BlockOrder(); + while (!pending.empty()) { + const IrValue* value = pending.back()->Resolve(); + pending.pop_back(); + if (!visited.insert(value).second) continue; + if (const auto origin = m_readOrigins.find(value); origin != m_readOrigins.end()) { + const auto block = std::ranges::find(order, origin->second); + if (block == order.end()) fail("copied descriptor read has no resource control-flow block"); + descriptor.requiredBlocks.push_back(static_cast(block - order.begin())); + } + for (const IrValue* argument : value->Arguments()) pending.push_back(argument); + } + std::ranges::sort(descriptor.requiredBlocks); + descriptor.requiredBlocks.erase(std::unique(descriptor.requiredBlocks.begin(), descriptor.requiredBlocks.end()), descriptor.requiredBlocks.end()); + } for (std::uint32_t candidate = 0; candidate < m_sources.size(); candidate++) { const auto& current = m_sources[candidate]; - if (current.dwordCount != descriptor.dwordCount || current.tableColumn != descriptor.tableColumn) { + if (current.dwordCount != descriptor.dwordCount || current.tableColumn != descriptor.tableColumn || current.requiredBlocks != descriptor.requiredBlocks) { continue; } bool same = true; @@ -299,6 +322,7 @@ private: const auto& order = m_program.BlockOrder(); const auto& info = m_program.Metadata().blockInfo; if (order.empty() || order.size() != info.size()) { + if (std::ranges::any_of(m_sources, [](const DescriptorSource& source) { return !source.requiredBlocks.empty(); })) fail("copied descriptor reads require resource control flow"); return; } std::unordered_map indexOf; @@ -360,7 +384,7 @@ private: reachedFlat[slot] = 1u; } } - bool guarded = false; + bool guarded = std::ranges::any_of(m_sources, [](const DescriptorSource& source) { return !source.requiredBlocks.empty(); }); for (const auto& block : controlFlow) { for (const auto source : block.sources) { guarded = guarded || reached[source] == 0u; @@ -1133,18 +1157,75 @@ private: } IrValue* EmitCopy(const IrValue& original, std::span arguments, IrValue& position) { + if (Detail::IsRawRead(m_program.Resources(), original)) { + auto flags = original.Flags(); + auto memory = m_program.Resources().memoryInfo[flags.index]; + memory.planningOnly = true; + flags.index = static_cast(m_program.Resources().memoryInfo.size()); + m_program.Resources().memoryInfo.push_back(memory); + std::uint64_t rawFlags = 0; + std::memcpy(&rawFlags, &flags, sizeof(flags)); + IrValue* copy = EmitBefore(position, original.Opcode(), original.Type(), arguments, rawFlags); + const auto origin = m_readOrigins.find(&original); + m_readOrigins.emplace(copy, origin != m_readOrigins.end() ? origin->second : original.Parent()); + return copy; + } return EmitBefore(position, original.Opcode(), original.Type(), arguments, original.Flags()); } - IrValue* Rematerialize(IrValue* value, IrValue& position, bool emit, std::uint32_t depth) { + bool Dominates(const IrBlock* definition, const IrBlock* use) const { + const auto& order = m_program.BlockOrder(); + if (definition == nullptr || use == nullptr || order.empty() || + std::ranges::find(order, definition) == order.end() || std::ranges::find(order, use) == order.end()) return false; + if (definition == use) return true; + std::vector pending{order.front()}; + std::unordered_set visited; + while (!pending.empty()) { + const IrBlock* block = pending.back(); + pending.pop_back(); + if (!visited.insert(block).second || block == definition) continue; + if (block == use) return false; + for (const IrBlock* successor : block->Successors()) pending.push_back(successor); + } + return true; + } + + bool AvailableBefore(const IrValue& definition, const IrValue& position) const { + if (definition.Parent() == nullptr || position.Parent() == nullptr || &definition == &position) return false; + if (definition.Parent() != position.Parent()) return Dominates(definition.Parent(), position.Parent()); + for (const IrValue* instruction : definition.Parent()->Instructions()) { + if (instruction == &definition) return true; + if (instruction == &position) return false; + } + return false; + } + + bool RegisterOnlyOffset(IrValue* value, std::uint32_t depth) const { + value = value->Resolve(); + if (value->HasImmediate()) return true; + if (value->IsPhi()) { + IrValue* invariant = ResolveInvariantPhi(m_program.Resources(), value); + return invariant != nullptr && !invariant->IsPhi() && RegisterOnlyOffset(invariant, depth); + } + if (value->Opcode() == IrOpcode::GetUserData) return value->ArgumentCount() == 1u; + if (depth == 0u || !Detail::IsRuntimeUniformOp(value->Opcode())) return false; + for (std::size_t index = 0; index < value->ArgumentCount(); index++) { + if (!RegisterOnlyOffset(value->Argument(index), depth - 1u)) return false; + } + return true; + } + + IrValue* Rematerialize(IrValue* value, IrValue& position, bool emit, std::uint32_t depth, bool pointerOperand = false) { value = value->Resolve(); if (value->HasImmediate()) { return value; } if (value->IsPhi()) { IrValue* invariant = ResolveInvariantPhi(m_program.Resources(), value); - return invariant == nullptr || invariant->IsPhi() ? nullptr : Rematerialize(invariant, position, emit, depth); + if (invariant == nullptr) return pointerOperand && AvailableBefore(*value, position) ? value : nullptr; + return invariant->IsPhi() ? nullptr : Rematerialize(invariant, position, emit, depth, pointerOperand); } + if (pointerOperand && Detail::IsRawRead(m_program.Resources(), *value) && AvailableBefore(*value, position)) return value; if (depth == 0u) { return nullptr; } @@ -1152,12 +1233,16 @@ private: const bool register_ = op == IrOpcode::GetUserData; const bool argumentless = (op == IrOpcode::GetShaderBase || op == IrOpcode::GetSrtResource) && value->ArgumentCount() == 0u; const bool srtRead = op == IrOpcode::ReadConst && value->ArgumentCount() == 2u && value->Argument(0)->Resolve()->Opcode() == IrOpcode::GetSrtResource && value->Argument(1)->Resolve()->HasImmediate(); - if (!register_ && !argumentless && !srtRead && !Detail::IsRuntimeUniformOp(op)) { + std::uint32_t memoryIndex = 0u; + const bool rawRead = op == IrOpcode::LoadAddressU32 && ScalarAddressMemory(*value, memoryIndex) != nullptr && + RegisterOnlyOffset(value->Argument(1), phiSearchDepth); + const bool sourceHandle = op == IrOpcode::GetAddressResource && value->ArgumentCount() == 2u; + if (!register_ && !argumentless && !srtRead && !rawRead && !sourceHandle && !Detail::IsRuntimeUniformOp(op)) { return nullptr; } std::vector arguments; for (std::size_t index = 0; index < value->ArgumentCount(); index++) { - IrValue* argument = register_ ? value->Argument(index) : Rematerialize(value->Argument(index), position, emit, depth - 1u); + IrValue* argument = register_ ? value->Argument(index) : Rematerialize(value->Argument(index), position, emit, depth - 1u, pointerOperand || (rawRead && index == 0u)); if (argument == nullptr) { return nullptr; } @@ -1368,7 +1453,7 @@ private: inst.Parent()->RemoveInstruction(&inst); } - static bool HoldsControlValue(IrValue* value, std::uint32_t depth, std::vector& seen) { + static bool HoldsFoldableValue(IrValue* value, std::uint32_t depth, std::vector& seen) { value = value->Resolve(); if (value->HasImmediate()) { return false; @@ -1378,7 +1463,12 @@ private: case IrOpcode::GetExec: case IrOpcode::GetExecLo: case IrOpcode::GetExecHi: - case IrOpcode::Ballot: return true; + case IrOpcode::Ballot: + case IrOpcode::LaneId: + case IrOpcode::ImageRead: + case IrOpcode::ImageSampleExplicitLod: + case IrOpcode::ImageSampleImplicitLod: + case IrOpcode::ImageSampleRaw: return true; default: break; } if (depth == 0u) { @@ -1393,16 +1483,16 @@ private: return false; } for (std::size_t index = 0; index < value->ArgumentCount(); index++) { - if (HoldsControlValue(value->Argument(index), depth - 1u, seen)) { + if (HoldsFoldableValue(value->Argument(index), depth - 1u, seen)) { return true; } } return false; } - static bool HoldsControlValue(IrValue* value) { + static bool HoldsFoldableValue(IrValue* value) { std::vector seen; - return HoldsControlValue(value, phiSearchDepth, seen); + return HoldsFoldableValue(value, phiSearchDepth, seen); } struct DescriptorWeb { @@ -1410,7 +1500,7 @@ private: static constexpr std::size_t maxArms = 64u; static constexpr std::uint32_t foldedArm = 0xffffffffu; std::string failure; - bool foldedControlValue = false; + bool foldedValue = false; std::vector> arms; std::map, IrValue*> selectors; std::vector> phis; @@ -1481,17 +1571,35 @@ private: if (const auto known = web.selectors.find(tuple); known != web.selectors.end()) { return known->second; } + if (web.order.empty()) { + const auto& order = m_program.BlockOrder(); + for (std::size_t index = 0; index < order.size(); index++) { + web.order.emplace(order[index], index); + } + } const auto phi = std::ranges::find_if(tuple, [](const IrValue* value) { return value->IsPhi(); }); - const auto lane = std::ranges::find_if(tuple, [](const IrValue* value) { return value->Opcode() == IrOpcode::SelectU32 || value->Opcode() == IrOpcode::ReadFirstLane; }); - if (phi == tuple.end() && lane != tuple.end()) { - return LaneSelector(tuple, **lane, web, position); + IrValue* lane = nullptr; + std::size_t laneOrder = 0; + for (IrValue* value : tuple) { + if (value->Opcode() != IrOpcode::SelectU32 && value->Opcode() != IrOpcode::ReadFirstLane) { + continue; + } + const auto found = web.order.find(value->Parent()); + const std::size_t index = found == web.order.end() ? 0u : found->second; + if (lane == nullptr || index > laneOrder) { + lane = value; + laneOrder = index; + } + } + if (phi == tuple.end() && lane != nullptr) { + return LaneSelector(tuple, *lane, web, position); } if (phi == tuple.end()) { - if (std::ranges::any_of(tuple, [](IrValue* value) { return HoldsControlValue(value); })) { + if (std::ranges::any_of(tuple, [](IrValue* value) { return HoldsFoldableValue(value); })) { if (StrictDescriptorWebs()) { - return GiveUp(web, "a descriptor dword depends on the program counter, EXEC or a lane mask"); + return GiveUp(web, "a descriptor dword depends on the program counter, EXEC, a lane mask, the lane index or texels"); } - web.foldedControlValue = true; + web.foldedValue = true; return &m_builder.Constant(DescriptorWeb::foldedArm); } for (IrValue* value : tuple) { @@ -1511,12 +1619,6 @@ private: web.arms.push_back(std::move(tuple)); return &m_builder.Constant(static_cast(web.arms.size() - 1u)); } - if (web.order.empty()) { - const auto& order = m_program.BlockOrder(); - for (std::size_t index = 0; index < order.size(); index++) { - web.order.emplace(order[index], index); - } - } IrBlock* block = nullptr; std::size_t latest = 0; for (const IrValue* value : tuple) { @@ -1640,7 +1742,7 @@ private: } DescriptorWeb web; IrValue* selector = WebSelector(tuple, web, inst); - const bool select = selector != nullptr && !web.arms.empty() && (web.arms.size() > 1u || web.foldedControlValue); + const bool select = selector != nullptr && !web.arms.empty() && (web.arms.size() > 1u || web.foldedValue); if (select) { for (const auto& [block, phi] : web.phis) { block->InsertInstructionBefore(nullptr, phi); @@ -1671,7 +1773,7 @@ private: } } if (selector == nullptr || web.arms.empty()) { - const std::string reason = !web.failure.empty() ? web.failure : web.foldedControlValue ? std::string("every path's descriptor depends on the program counter, EXEC or a lane mask") : std::string("no descriptor was found on any path"); + const std::string reason = !web.failure.empty() ? web.failure : web.foldedValue ? std::string("every path's descriptor depends on the program counter, EXEC, a lane mask, the lane index or texels") : std::string("no descriptor was found on any path"); for (std::uint32_t slot = 0; slot < handleCount; slot++) { if (split[slot]) { m_splitFailures.try_emplace(handles[slot], reason); @@ -1680,7 +1782,8 @@ private: return; } - if (web.foldedControlValue) { + if (web.foldedValue) { + m_foldedWeb = true; const std::array compare {selector, &m_builder.Constant(DescriptorWeb::foldedArm)}; IrValue* folded = EmitBefore(inst, IrOpcode::IEqual32, IrType::Bool, compare); EmitDescriptorFault(inst, *folded, m_builder.Constant(0u), m_builder.Constant(0u)); @@ -2219,6 +2322,8 @@ private: const bool flatLineAccess = image.dimension == RdnaImageDimension::Dim2DArray ? flatLineLevelZero || flatLineExplicitLod : flatLineLoad || flatLineSample || flatLineExplicitLod; image.flatLineCompatible = image.flatLineCompatible && flatLineAccess && !memory.imagePacked && memory.imageByElements == 0u; image.flatVolumeLineCompatible = image.flatVolumeLineCompatible && (flatLineLoad || flatLineSample) && !memory.imagePacked && memory.imageByElements == 0u; + const bool layerRowsSample = op == IrOpcode::ImageSampleRaw && (memory.imageSampleFlags == RdnaImageSampleFlagLevelZero || memory.imageSampleFlags == RdnaImageSampleFlagLod); + image.layerRowsCompatible = image.layerRowsCompatible && layerRowsSample && !memory.imageR128 && !memory.imagePacked && memory.imageByElements == 0u; image.dimensionQueryOnly = image.dimensionQueryOnly && op == IrOpcode::ImageQueryDimensions; image.sampledOnly = image.sampledOnly && ImageOpcodeInfoOf(op).needsSampler; if ((memory.imageSampleFlags & RdnaImageSampleFlagCompare) != 0u) { @@ -2420,6 +2525,7 @@ private: ShaderInfo m_info; IrBuilder m_builder; std::vector m_sources; + std::unordered_map m_readOrigins; std::vector> m_blockSources; std::vector> m_blockImages; std::uint32_t m_currentBlock = std::numeric_limits::max(); @@ -2440,6 +2546,7 @@ private: }; std::vector m_edgeSelectors; std::unordered_map m_splitFailures; + bool m_foldedWeb = false; }; } diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp index 74739dfae6..c7ab511132 100644 --- a/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp @@ -104,7 +104,12 @@ bool evaluateRuntimeSources(const IrResourcePlan& program, std::span visited, std::span active) { + const auto imageBase = program.descriptorSources.size(); + if (active.size() < imageBase + program.info.images.size()) return false; + bool guarded = false; + for (std::size_t source = 0; source < imageBase; ++source) { + const auto& blocks = program.descriptorSources[source].requiredBlocks; + if (blocks.empty()) continue; + guarded = true; + for (const auto block : blocks) { + if (block >= visited.size()) return false; + if (visited[block] == 0u) active[source] = 0u; + } + } + if (guarded) { + for (std::size_t image = 0; image < program.info.images.size(); ++image) { + const auto source = program.info.images[image].source; + if (source >= imageBase) return false; + if (!program.descriptorSources[source].requiredBlocks.empty() && active[source] == 0u) active[imageBase + image] = 0u; + } + } + return true; +} + bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span sources, const SrtRuntime& runtime, std::vector& results, std::vector& flat, bool evaluateFlat, std::span cleanFlatSlots, std::vector& activeSources, std::vector* poison, std::uint32_t* nullRootReads) { const bool planInputs = evaluateFlat && sources.data() == program.materializationSources.data() && sources.size() == program.materializationSources.size() && cleanFlatSlots.data() == program.cleanFlatSlots.data() && cleanFlatSlots.size() == program.cleanFlatSlots.size(); if (planInputs) { diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp index 642ef6e452..d0361bd187 100644 --- a/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtEvaluator.cpp @@ -8,10 +8,13 @@ #include #include #include +#include #include #include +#include #include #include +#include #include #include #include @@ -399,11 +402,13 @@ bool BasicEvaluator::MayEvaluate(const IrResourcePlan& program, IrValue* template bool BasicEvaluator::MayEvaluate(const IrResourcePlan& program, IrValue* value, const IrValue* activeMask, std::unordered_set& firstLanes) { - std::unordered_map known; + std::array scratch; + std::pmr::monotonic_buffer_resource arena(scratch.data(), scratch.size()); + std::pmr::unordered_map known(&arena); const auto takesActiveArm = [activeMask](const IrValue* current) { return activeMask != nullptr && IsRuntimeSelect(current->Opcode()) && current->ArgumentCount() == 3 && ActiveMaskImplies(activeMask, current->Argument(0)); }; - std::vector required; + std::pmr::vector required(&arena); const auto requirements = [&program, &takesActiveArm, &required](IrValue* current) { required.clear(); if (takesActiveArm(current)) { @@ -443,8 +448,9 @@ bool BasicEvaluator::MayEvaluate(const IrResourcePlan& program, IrValue* required.push_back(argument != nullptr ? argument->Resolve() : nullptr); } }; - std::unordered_set expanding; - std::vector> pending{{value->Resolve(), false}}; + std::pmr::unordered_set expanding(&arena); + std::pmr::vector> pending(&arena); + pending.emplace_back(value->Resolve(), false); while (!pending.empty()) { const auto [current, expanded] = pending.back(); pending.pop_back(); @@ -487,18 +493,29 @@ bool BasicEvaluator::MayEvaluate(const IrResourcePlan& program, IrValue* template bool BasicEvaluator::MayDecide(const IrResourcePlan& program, IrValue* value) { + std::unordered_map decided; + return MayDecide(program, value, decided); +} + +template +bool BasicEvaluator::MayDecide(const IrResourcePlan& program, IrValue* value, std::unordered_map& decided) { if (value == nullptr) return false; - if (MayEvaluate(program, value)) return true; value = value->Resolve(); - const auto decides = [&program, value](std::size_t index) { return value->Argument(index) != nullptr && MayDecide(program, value->Argument(index)); }; - switch (value->Opcode()) { - case IrOpcode::LogicalNot: return value->ArgumentCount() == 1u && decides(0); - case IrOpcode::LogicalAnd: - case IrOpcode::LogicalOr: return value->ArgumentCount() == 2u && (decides(0) || decides(1)); - case IrOpcode::ULessThan32: - case IrOpcode::UGreaterThan32: return value->ArgumentCount() == 2u && (MayEvaluate(program, value->Argument(0)) || MayEvaluate(program, value->Argument(1))); - default: return false; - } + if (const auto found = decided.find(value); found != decided.end()) return found->second; + const auto decides = [&program, &decided, value](std::size_t index) { return value->Argument(index) != nullptr && MayDecide(program, value->Argument(index), decided); }; + const auto decidable = [&] { + if (MayEvaluate(program, value)) return true; + switch (value->Opcode()) { + case IrOpcode::LogicalNot: return value->ArgumentCount() == 1u && decides(0); + case IrOpcode::LogicalAnd: + case IrOpcode::LogicalOr: return value->ArgumentCount() == 2u && (decides(0) || decides(1)); + case IrOpcode::ULessThan32: + case IrOpcode::UGreaterThan32: return value->ArgumentCount() == 2u && (MayEvaluate(program, value->Argument(0)) || MayEvaluate(program, value->Argument(1))); + default: return false; + } + }(); + decided.emplace(value, decidable); + return decidable; } template diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtTape.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtTape.cpp index ddda8b1fc3..def355c32b 100644 --- a/core/shader/recompiler/Optimization/src/SrtWalker/SrtTape.cpp +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtTape.cpp @@ -1,4 +1,5 @@ #include "Optimization/SrtWalker/SrtTape.hpp" +#include "Optimization/SrtWalker/SrtDescriptorEvaluation.hpp" #include "Optimization/SrtWalker/SrtAddressArithmetic.hpp" #include "Optimization/SrtWalker/SrtInstructionPredicates.hpp" #include "IntermediateRepresentation/IrBuilder.hpp" @@ -639,7 +640,11 @@ bool SrtTape::Run(const IrResourcePlan& plan, const SrtRuntime& runtime, std::ve for (const auto image : block.images) active[imageBase + image] = 1u; for (const auto slot : block.flatSlots) activeFlat[slot] = 1u; std::uint64_t condition = 0; - const bool cleanEvaluable = block.condition != nullptr && runtime.readSpecializationMemory != nullptr && conditionRoots[index] != NoNode && evaluate(frame, conditionRoots[index], condition); + bool cleanEvaluable = false; + { + const SrtBranchScope branch; + cleanEvaluable = block.condition != nullptr && runtime.readSpecializationMemory != nullptr && conditionRoots[index] != NoNode && evaluate(frame, conditionRoots[index], condition); + } if (frame.nullRoot) return refuse(); if (cleanEvaluable) { if (block.successors.size() < 2) return refuse(); @@ -649,6 +654,7 @@ bool SrtTape::Run(const IrResourcePlan& plan, const SrtRuntime& runtime, std::ve } } } + if (!plan.controlFlow.empty() && !ApplySourceBlockGuards(plan, scratch.visited, active)) return refuse(); auto& evaluated = scratch.evaluated; evaluated.clear(); evaluated.reserve(plan.materializationSources.size()); diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtWalker.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtWalker.cpp index 8cba8936c8..ba5d00c582 100644 --- a/core/shader/recompiler/Optimization/src/SrtWalker/SrtWalker.cpp +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtWalker.cpp @@ -8,6 +8,11 @@ namespace ShaderRecompiler { +bool& SrtEvaluatingBranch() { + thread_local bool evaluating = false; + return evaluating; +} + void SrtWalker::BuildPlan(IrProgram& program) const { if (program.Resources().resourceTrackingComplete) { throw std::runtime_error("shader SRT planning failed: cannot rebuild SRT after resource tracking"); diff --git a/core/shader/recompiler/PipelineSpecialization.hpp b/core/shader/recompiler/PipelineSpecialization.hpp index 12960b63df..efbb4dfc45 100644 --- a/core/shader/recompiler/PipelineSpecialization.hpp +++ b/core/shader/recompiler/PipelineSpecialization.hpp @@ -33,6 +33,8 @@ inline constexpr std::uint32_t RasterPrimitive = 8233u; inline constexpr std::uint32_t LayerExported = 8234u; inline constexpr std::uint32_t StencilReferenceOffset = 8235u; inline constexpr std::uint32_t ReversedBlend = 8236u; +inline constexpr std::uint32_t MeshLayerBase = 8237u; +inline constexpr std::uint32_t MeshLayerCount = 8238u; inline constexpr std::uint32_t DepthRemapBase = 8240u; inline constexpr std::uint32_t DepthRemapWords = 1u; inline constexpr std::uint32_t WorkgroupOffsetBase = 8241u; @@ -46,6 +48,8 @@ inline constexpr std::uint32_t DescriptorIndexStride = 128u; inline constexpr std::uint32_t FoldOffsetBase = 57344u; inline constexpr std::uint32_t UnnormalizedBaseLevelBase = 57408u; inline constexpr std::uint32_t FmaskFormatBase = 57472u; +inline constexpr std::uint32_t MirrorUnnormalizedBase = 57536u; +inline constexpr std::uint32_t MinLodBase = 57600u; inline constexpr std::uint32_t DescriptorIndex(std::uint32_t binding, std::uint32_t element) { return DescriptorIndexBase + binding * DescriptorIndexStride + element; diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 0b83c3ae6d..8575428009 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -1100,6 +1100,7 @@ enum class RdnaOpcode : std::uint16_t { [[nodiscard]] bool IsBufferMemoryOpcode(RdnaOpcode opcode); [[nodiscard]] bool IsImageOpcode(RdnaOpcode opcode); [[nodiscard]] bool IsExportOpcode(RdnaOpcode opcode); +[[nodiscard]] bool IsSigned64BitIntegerOpcode(RdnaOpcode opcode); bool IsConditionalBranchOpcode(RdnaOpcode opcode); bool IsDirectBranchOpcode(RdnaOpcode opcode); diff --git a/core/shader/recompiler/RdnaDecoder/src/GcnInstructionDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/GcnInstructionDecoder.cpp index e5ccedb328..b80856cbc8 100644 --- a/core/shader/recompiler/RdnaDecoder/src/GcnInstructionDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/GcnInstructionDecoder.cpp @@ -46,6 +46,7 @@ struct Encoded { std::array words{}; std::uint32_t rdnaWords = 0; std::uint32_t gcnWords = 0; + std::optional compareDestination; }; std::uint32_t literalAt(std::span code, std::uint32_t index, std::uint32_t programCounter) { @@ -176,11 +177,12 @@ Encoded encodeVop3(std::span code, std::uint32_t index, std if (index + 1 >= code.size()) throw std::out_of_range("truncated GCN VOP3 instruction"); const auto word0 = code[index]; const auto opcode = (word0 >> 17u) & 0x1ffu; - if (compareWritesExec(opcode)) unsupported("VOP3 v_cmpx (writes both the scalar destination and EXEC)", opcode, programCounter); if (const auto shift = reversedShift(opcode)) return encodeVop3Words(swapAbsolute(word0), swapSources(code[index + 1]), *shift, false); const auto mapped = mapOpcode(GcnTables::Vop3, opcode); if (!mapped) unsupported("VOP3", opcode, programCounter); - return encodeVop3Words(word0, code[index + 1], *mapped, gcnVop3bCarryOut(opcode)); + auto out = encodeVop3Words(word0, code[index + 1], *mapped, gcnVop3bCarryOut(opcode)); + if (compareWritesExec(opcode)) out.compareDestination = word0 & 0xffu; + return out; } Encoded promoteToVop3(std::uint32_t gcnVop3Opcode, std::uint32_t vdst, std::uint32_t src0, std::uint32_t src1, std::uint32_t src2, bool carryOut, std::span code, std::uint32_t index, std::uint32_t programCounter) { @@ -241,7 +243,6 @@ Encoded encodeVop1(std::span code, std::uint32_t index, std Encoded encodeVopc(std::span code, std::uint32_t index, std::uint32_t programCounter) { const auto word = code[index]; const auto opcode = (word >> 17u) & 0xffu; - if (compareWritesExec(opcode)) unsupported("VOPC v_cmpx (writes both VCC and EXEC)", opcode, programCounter); const auto mapped = mapOpcode(GcnTables::Vopc, opcode); if (!mapped) unsupported("VOPC", opcode, programCounter); Encoded out; @@ -251,6 +252,7 @@ Encoded encodeVopc(std::span code, std::uint32_t index, std out.words[1] = literalAt(code, index + 1, programCounter); out.rdnaWords = out.gcnWords = 2; } + if (compareWritesExec(opcode)) out.compareDestination = VccSdst; return out; } @@ -343,6 +345,7 @@ RdnaInstruction DecodeGcnInstruction(std::uint32_t programCounter, std::span(encoded.words.data(), encoded.rdnaWords), 0); instruction.wordCount = encoded.gcnWords; + if (encoded.compareDestination) instruction.destination2 = DecodeRdnaScalarDestination(*encoded.compareDestination, programCounter); return instruction; } diff --git a/core/shader/recompiler/RdnaDecoder/src/GcnOpcodeTables.hpp b/core/shader/recompiler/RdnaDecoder/src/GcnOpcodeTables.hpp index cbe30ec329..03fdcc596a 100644 --- a/core/shader/recompiler/RdnaDecoder/src/GcnOpcodeTables.hpp +++ b/core/shader/recompiler/RdnaDecoder/src/GcnOpcodeTables.hpp @@ -94,15 +94,20 @@ inline constexpr std::array, 216> Vopc{{ {0xf0, 0xf0}, {0xf1, 0xf1}, {0xf2, 0xf2}, {0xf3, 0xf3}, {0xf4, 0xf4}, {0xf5, 0xf5}, {0xf6, 0xf6}, {0xf7, 0xf7}, }}; -inline constexpr std::array, 324> Vop3{{ +inline constexpr std::array, 360> Vop3{{ {0x0, 0x0}, {0x1, 0x1}, {0x2, 0x2}, {0x3, 0x3}, {0x4, 0x4}, {0x5, 0x5}, {0x6, 0x6}, {0x7, 0x7}, - {0x8, 0x8}, {0x9, 0x9}, {0xa, 0xa}, {0xb, 0xb}, {0xc, 0xc}, {0xd, 0xd}, {0xe, 0xe}, {0x10, 0x10}, + {0x8, 0x8}, {0x9, 0x9}, {0xa, 0xa}, {0xb, 0xb}, {0xc, 0xc}, {0xd, 0xd}, {0xe, 0xe}, {0xf, 0xf}, {0x10, 0x10}, {0x11, 0x11}, {0x12, 0x12}, {0x13, 0x13}, {0x14, 0x14}, {0x15, 0x15}, {0x16, 0x16}, {0x17, 0x17}, {0x18, 0x18}, {0x19, 0x19}, {0x1a, 0x1a}, {0x1b, 0x1b}, {0x1c, 0x1c}, {0x1d, 0x1d}, {0x1e, 0x1e}, {0x20, 0x20}, {0x21, 0x21}, {0x22, 0x22}, {0x23, 0x23}, {0x24, 0x24}, {0x25, 0x25}, {0x26, 0x26}, {0x27, 0x27}, {0x28, 0x28}, {0x29, 0x29}, - {0x2a, 0x2a}, {0x2b, 0x2b}, {0x2c, 0x2c}, {0x2d, 0x2d}, {0x2e, 0x2e}, {0x30, 0x30}, {0x31, 0x31}, {0x32, 0x32}, + {0x2a, 0x2a}, {0x2b, 0x2b}, {0x2c, 0x2c}, {0x2d, 0x2d}, {0x2e, 0x2e}, {0x2f, 0x2f}, {0x30, 0x30}, {0x31, 0x31}, {0x32, 0x32}, {0x33, 0x33}, {0x34, 0x34}, {0x35, 0x35}, {0x36, 0x36}, {0x37, 0x37}, {0x38, 0x38}, {0x39, 0x39}, {0x3a, 0x3a}, - {0x3b, 0x3b}, {0x3c, 0x3c}, {0x3d, 0x3d}, {0x3e, 0x3e}, {0x80, 0x80}, {0x81, 0x81}, {0x82, 0x82}, {0x83, 0x83}, + {0x3b, 0x3b}, {0x3c, 0x3c}, {0x3d, 0x3d}, {0x3e, 0x3e}, + {0x40, 0x0}, {0x41, 0x1}, {0x42, 0x2}, {0x43, 0x3}, {0x44, 0x4}, {0x45, 0x5}, {0x46, 0x6}, {0x47, 0x7}, + {0x48, 0x8}, {0x49, 0x9}, {0x4a, 0xa}, {0x4b, 0xb}, {0x4c, 0xc}, {0x4d, 0xd}, {0x4e, 0xe}, {0x4f, 0xf}, + {0x60, 0x20}, {0x61, 0x21}, {0x62, 0x22}, {0x63, 0x23}, {0x64, 0x24}, {0x65, 0x25}, {0x66, 0x26}, {0x67, 0x27}, + {0x68, 0x28}, {0x69, 0x29}, {0x6a, 0x2a}, {0x6b, 0x2b}, {0x6c, 0x2c}, {0x6d, 0x2d}, {0x6e, 0x2e}, {0x6f, 0x2f}, + {0x80, 0x80}, {0x81, 0x81}, {0x82, 0x82}, {0x83, 0x83}, {0x84, 0x84}, {0x85, 0x85}, {0x86, 0x86}, {0x87, 0x87}, {0x88, 0x88}, {0x89, 0x89}, {0x8a, 0x8a}, {0x8b, 0x8b}, {0x8c, 0x8c}, {0x8d, 0x8d}, {0x8e, 0x8e}, {0x90, 0x90}, {0x91, 0x91}, {0x92, 0x92}, {0x93, 0x93}, {0x94, 0x94}, {0x95, 0x95}, {0x96, 0x96}, {0x97, 0x97}, {0x98, 0x98}, {0xa0, 0xa0}, {0xa1, 0xa1}, {0xa2, 0xa2}, {0xa3, 0xa3}, @@ -117,7 +122,7 @@ inline constexpr std::array, 324> Vop3{{ {0x104, 0x104}, {0x105, 0x105}, {0x106, 0x106}, {0x107, 0x107}, {0x108, 0x108}, {0x109, 0x109}, {0x10a, 0x10a}, {0x10b, 0x10b}, {0x10c, 0x10c}, {0x10f, 0x10f}, {0x110, 0x110}, {0x111, 0x111}, {0x112, 0x112}, {0x113, 0x113}, {0x114, 0x114}, {0x116, 0x116}, {0x118, 0x118}, {0x11a, 0x11a}, {0x11b, 0x11b}, {0x11c, 0x11c}, {0x11d, 0x11d}, {0x11e, 0x363}, {0x11f, 0x11f}, {0x120, 0x120}, - {0x121, 0x121}, {0x122, 0x364}, {0x123, 0x365}, {0x124, 0x366}, {0x125, 0x30f}, {0x126, 0x310}, {0x127, 0x319}, {0x128, 0x128}, + {0x121, 0x121}, {0x122, 0x364}, {0x123, 0x365}, {0x124, 0x366}, {0x125, 0x30f}, {0x126, 0x310}, {0x127, 0x319}, {0x128, 0x128}, {0x129, 0x129}, {0x12a, 0x12a}, {0x12b, 0x362}, {0x12d, 0x368}, {0x12e, 0x369}, {0x12f, 0x12f}, {0x130, 0x36a}, {0x131, 0x36b}, {0x132, 0x132}, {0x133, 0x133}, {0x134, 0x134}, {0x135, 0x135}, {0x139, 0x139}, {0x13a, 0x13a}, {0x13b, 0x13b}, {0x140, 0x140}, {0x141, 0x141}, {0x142, 0x142}, {0x143, 0x143}, {0x144, 0x144}, {0x145, 0x145}, {0x146, 0x146}, {0x147, 0x147}, {0x148, 0x148}, {0x149, 0x149}, {0x14a, 0x14a}, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index 374315cb1a..39eda0547b 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -904,6 +904,32 @@ bool IsExportOpcode(RdnaOpcode opcode) { } } +bool IsSigned64BitIntegerOpcode(RdnaOpcode opcode) { + requireClassifiableOpcode(opcode); + switch (opcode) { + case RdnaOpcode::VCmpLtI64: + case RdnaOpcode::VCmpEqI64: + case RdnaOpcode::VCmpLeI64: + case RdnaOpcode::VCmpGtI64: + case RdnaOpcode::VCmpNeI64: + case RdnaOpcode::VCmpGeI64: + case RdnaOpcode::VCmpxLtI64: + case RdnaOpcode::VCmpxEqI64: + case RdnaOpcode::VCmpxLeI64: + case RdnaOpcode::VCmpxGtI64: + case RdnaOpcode::VCmpxNeI64: + case RdnaOpcode::VCmpxGeI64: + case RdnaOpcode::VAshrrevI64: + case RdnaOpcode::VMadI64I32: + case RdnaOpcode::SAshrI64: + case RdnaOpcode::SBfeI64: + case RdnaOpcode::SFlbitI32I64: + return true; + default: + return false; + } +} + bool IsConditionalBranchOpcode(RdnaOpcode opcode) { requireClassifiableOpcode(opcode); switch (opcode) { diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 996f1b0de3..9a9f01921d 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -25,7 +25,6 @@ enum class Vop2SdwaProfile { None, Float, Integer, - Float16, }; struct Vop2OpcodeInfo { @@ -78,19 +77,19 @@ constexpr Vop2OpcodeInfo vop2Opcodes[] = { {0x2cu, RdnaOpcode::VMadmkF32}, {0x2du, RdnaOpcode::VMadakF32}, {0x2fu, RdnaOpcode::VCvtPkrtzF16F32, Vop2SdwaProfile::Float}, - {0x32u, RdnaOpcode::VAddF16, Vop2SdwaProfile::Float16}, - {0x33u, RdnaOpcode::VSubF16, Vop2SdwaProfile::Float16}, - {0x34u, RdnaOpcode::VSubrevF16, Vop2SdwaProfile::Float16}, - {0x35u, RdnaOpcode::VMulF16, Vop2SdwaProfile::Float16}, + {0x32u, RdnaOpcode::VAddF16, Vop2SdwaProfile::Float}, + {0x33u, RdnaOpcode::VSubF16, Vop2SdwaProfile::Float}, + {0x34u, RdnaOpcode::VSubrevF16, Vop2SdwaProfile::Float}, + {0x35u, RdnaOpcode::VMulF16, Vop2SdwaProfile::Float}, {0x36u, RdnaOpcode::VFmacF16}, {0x37u, RdnaOpcode::VFmamkF16}, {0x38u, RdnaOpcode::VFmaakF16}, - {0x39u, RdnaOpcode::VMaxF16, Vop2SdwaProfile::Float16}, - {0x3au, RdnaOpcode::VMinF16, Vop2SdwaProfile::Float16}, + {0x39u, RdnaOpcode::VMaxF16, Vop2SdwaProfile::Float}, + {0x3au, RdnaOpcode::VMinF16, Vop2SdwaProfile::Float}, {0x3cu, RdnaOpcode::VPkFmacF16}, {0x06u, RdnaOpcode::VMacLegacyF32}, {0x07u, RdnaOpcode::VMulLegacyF32, Vop2SdwaProfile::Float}, - {0x3bu, RdnaOpcode::VLdexpF16, Vop2SdwaProfile::Float16}, + {0x3bu, RdnaOpcode::VLdexpF16, Vop2SdwaProfile::Float}, }; constexpr VectorOpcodeInfo vop1Opcodes[] = { @@ -1333,14 +1332,12 @@ struct Vop2SdwaRule { std::uint32_t src1Selectors = sdwaSelFull(); bool partialDst = false; bool sourceModifiers = false; - bool byteSelectorClamp = true; }; constexpr Vop2SdwaRule vop2SdwaRules[] = { {}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, true}, {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, false}, - {sdwaSelAll(), sdwaSelAll(), sdwaSelAll(), true, true, false}, }; const Vop2SdwaRule* findVop2SdwaRule(std::uint32_t encoding) { @@ -1401,10 +1398,6 @@ void validateVop2Sdwa(const RdnaInstruction& instruction, std::uint32_t opcode, if (!rule->sourceModifiers && hasSourceModifiers && !cndmaskFloatModifiers) { throw std::invalid_argument("VOP2 SDWA source modifiers are not supported"); } - const bool byteSelector = fields.src0Sel < 4u || fields.src1Sel < 4u || fields.dstSel < 4u; - if (!rule->byteSelectorClamp && fields.clamp != 0u && byteSelector) { - throw std::invalid_argument("VOP2 SDWA clamp with byte selectors is not implemented"); - } } void finalizeVop2Instruction(std::span code, std::uint32_t wordIndex, RdnaInstruction& instruction) { diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index fe989bcf5b..685b0467e7 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -256,7 +256,8 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparat translateOptions.userDataCount = static_cast(request.context.userData.size()); translateOptions.scratchDwords = request.context.compute.has_value() ? request.context.compute->scratchDwords : 0u; translateOptions.sharedMemoryBytes = DeviceMemoryLdsBytes(request); - translateOptions.fragmentShaderBarycentricEnabled = request.target.fragmentShaderBarycentricEnabled; + const bool hostInterpolationFirst = stageKind == ShaderStageKind::Pixel && request.target.fragmentShaderBarycentricEnabled && request.target.hostInterpolationFirst; + translateOptions.fragmentShaderBarycentricEnabled = request.target.fragmentShaderBarycentricEnabled && !hostInterpolationFirst; const auto supports = [&](spv::Capability capability) { return std::ranges::find(request.target.supportedCapabilities, static_cast(capability)) != request.target.supportedCapabilities.end(); }; translateOptions.nativeFloat16 = supports(spv::CapabilityFloat16) && supports(spv::CapabilityRoundingModeRTE) && supports(spv::CapabilityDenormPreserve); translateOptions.interpolationFunctions = supports(spv::CapabilityInterpolationFunction); @@ -265,6 +266,7 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparat translateOptions.float32FlushDeclared = supports(spv::CapabilityDenormFlushToZero); translateOptions.inputInfo = inputInfo; translateOptions.float32DenormPreserve = request.target.float32DenormPreserve; + translateOptions.float64 = supports(spv::CapabilityFloat64); constexpr InstructionTranslator translator; @@ -332,11 +334,11 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparat } }; simplify(); - if (stageKind == ShaderStageKind::Pixel && !translateOptions.fragmentShaderBarycentricEnabled) program.Metadata().barycentricBuiltinsUnnormalized = request.target.fragmentBarycentricBuiltins; - if (stageKind == ShaderStageKind::Pixel && !translateOptions.fragmentShaderBarycentricEnabled && !HostInterpolationChecker{}.Lower(program, *inputInfo.pixel, request.target.fragmentBarycentricBuiltins)) { + if (stageKind == ShaderStageKind::Pixel && !translateOptions.fragmentShaderBarycentricEnabled) program.Metadata().barycentricBuiltinsUnnormalized = request.target.fragmentBarycentricBuiltins && !hostInterpolationFirst; + if (stageKind == ShaderStageKind::Pixel && !translateOptions.fragmentShaderBarycentricEnabled && !HostInterpolationChecker{}.Lower(program, *inputInfo.pixel, request.target.fragmentBarycentricBuiltins && !hostInterpolationFirst)) { translateOptions.fragmentShaderBarycentricEnabled = true; program = translator.Translate(decoded, cfg, translateOptions); - program.Metadata().barycentricEmulation = true; + program.Metadata().barycentricEmulation = !hostInterpolationFirst; simplify(); } @@ -362,9 +364,13 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request, ShaderPreparat deadCodeEliminator.Eliminate(program); dumpIr("resources"); program.Resources().srgbDecodeFormats = request.target.srgbDecodeFormats; + program.Resources().maxImageArrayLayers = request.target.maxImageArrayLayers; if (request.context.convertedSamples) { for (auto& image : program.Resources().info.images) image.convertedSamples = true; } + if (request.context.layerRowSamples) { + for (auto& image : program.Resources().info.images) image.maxArrayLayers = request.target.maxImageArrayLayers; + } return program; } @@ -631,6 +637,7 @@ CompiledVariant emitVariant(const RecompileRequest& request, IrProgram& program) targetOptions.supportedCapabilities = request.target.supportedCapabilities; targetOptions.supportedExtensions = request.target.supportedExtensions; targetOptions.nonConstantImageOffsets = request.target.nonConstantImageOffsets; + targetOptions.shaderImageMinLod = request.target.shaderImageMinLod; targetOptions.narrowSubgroupClock = request.target.narrowSubgroupClock; targetOptions.float64DenormPreserve = request.target.float64DenormPreserve; targetOptions.depthRangeRemap = request.target.depthRangeRemap; @@ -703,6 +710,7 @@ CompiledVariant emitVariant(const RecompileRequest& request, IrProgram& program) } result.registrationModule = registration; if (request.shader.stage == ShaderStage::Mesh && request.graphics) result.meshBarycentrics = request.graphics->meshBarycentrics; + result.layerReplaySafe = LayerReplaySafe(program); result.stencilReplaySafe = (request.shader.stage == ShaderStage::Vertex || stencilReplay) && StencilReplaySafe(program); result.bdaWrites = program.Info().bdaWrites; if (stencilReplay) { @@ -722,6 +730,7 @@ CompiledVariant emitVariant(const RecompileRequest& request, IrProgram& program) result.fullSubgroups = emitted.fullSubgroups; result.functionLdsBuffer = emitted.functionLdsBuffer; result.meshWindowBuffer = emitted.meshWindowBuffer; + result.scratchPoolSlotDwords = emitted.scratchPoolSlotDwords; result.vertexOffsetSgpr = program.Info().vertexOffsetSgpr; result.instanceOffsetSgpr = program.Info().instanceOffsetSgpr; result.vertexOffsetShared = program.Info().vertexOffsetShared; @@ -781,6 +790,7 @@ CompiledVariant compileVariant(const RecompileRequest& request, IrProgram progra struct SpecializedModule { std::uint64_t specializationId = 0; + std::vector specialization; std::vector bindings; bool pushData = false; SpirvContentHash hash; @@ -820,6 +830,11 @@ std::vector specializeWords(const CompiledShaderArtifact& artifac } const auto found = supplied.find(words[cursor + 3u]); if (found == supplied.end()) throw std::runtime_error("missing prepared specialization value"); + if (found->first == PipelineSpecialization::MeshLayerBase || found->first == PipelineSpecialization::MeshLayerCount) { + retained.insert(words[cursor + 1u]); + cursor += count; + continue; + } auto value = found->second; if (!artifact.stencilReferences.empty()) { if (found->first == PipelineSpecialization::StencilReferenceOffset) replaySlotBytes = value / PushData::DwordCount * NativePushSlotSize; @@ -834,9 +849,7 @@ std::vector specializeWords(const CompiledShaderArtifact& artifac for (std::size_t cursor = 5; cursor < words.size();) { const auto count = words[cursor] >> 16u; const auto op = static_cast(words[cursor] & 0xffffu); - if (op == spv::OpSpecConstant && count == 4u && retained.contains(words[cursor + 2u])) { - materialized.insert(materialized.end(), words.begin() + cursor, words.begin() + cursor + count); - } else if (op == spv::OpDecorate && count == 4u && words[cursor + 2u] == spv::DecorationSpecId && retained.contains(words[cursor + 1u])) { + if ((op == spv::OpSpecConstant && count == 4u && retained.contains(words[cursor + 2u])) || (op == spv::OpDecorate && count == 4u && words[cursor + 2u] == spv::DecorationSpecId && retained.contains(words[cursor + 1u]))) { materialized.insert(materialized.end(), words.begin() + cursor, words.begin() + cursor + count); } else if (op == spv::OpSpecConstant) { const auto found = count == 4u ? values.find(words[cursor + 2u]) : values.end(); @@ -853,7 +866,7 @@ std::vector specializeWords(const CompiledShaderArtifact& artifac } materialized = SpecializeFragmentOutputs(SpecializeSpirv(materialized), constants, specializedIds); #if ANYPS5_ENABLE_SPIRV_TOOLS - materialized = ValidateAndOptimizeSpirv(materialized, target.vulkanVersion, target.spirvVersion, target.nonConstantImageOffsets, true, true); + materialized = ValidateAndOptimizeSpirv(materialized, target.vulkanVersion, target.spirvVersion, target.nonConstantImageOffsets, true, retained.empty()); #endif std::map descriptorVariables; std::set usedDescriptors; @@ -894,14 +907,22 @@ std::vector specializeWords(const CompiledShaderArtifact& artifac SpecializedSpirv buildSpecializedModule(const CompiledShaderArtifact& artifact, std::span classes, std::span constants, const SpirvTarget& target) { auto materialized = specializeWords(artifact, classes, constants, target); auto module = std::make_shared(); + std::map supplied; + for (const auto& constant : constants) supplied.emplace(constant.id, constant.value); std::map bindingNumbers; - std::unordered_set functionLdsBuffer; + std::unordered_set ldsBuffers; + bool compute = false; for (std::size_t cursor = 5; cursor < materialized.size();) { const auto count = materialized[cursor] >> 16u; const auto op = static_cast(materialized[cursor] & 0xffffu); + if (op == spv::OpEntryPoint && count >= 2u) compute = materialized[cursor + 1u] == spv::ExecutionModelGLCompute; if (op == spv::OpDecorate && count == 4u && materialized[cursor + 2u] == spv::DecorationBinding) bindingNumbers.emplace(materialized[cursor + 1u], materialized[cursor + 3u]); - if (op == spv::OpDecorate && count == 4u && materialized[cursor + 2u] == spv::DecorationDescriptorSet && (materialized[cursor + 3u] == PixelFunctionLdsDescriptorSet || materialized[cursor + 3u] == MeshWindowDescriptorSet)) functionLdsBuffer.insert(materialized[cursor + 1u]); - if (op == spv::OpVariable && count >= 4u && !functionLdsBuffer.contains(materialized[cursor + 2u])) { + if (op == spv::OpDecorate && count == 4u && materialized[cursor + 2u] == spv::DecorationDescriptorSet && (materialized[cursor + 3u] == PixelFunctionLdsDescriptorSet || materialized[cursor + 3u] == MeshWindowDescriptorSet || (compute && materialized[cursor + 3u] == WorkgroupMemoryDescriptorSet))) ldsBuffers.insert(materialized[cursor + 1u]); + if (op == spv::OpDecorate && count == 4u && materialized[cursor + 2u] == spv::DecorationSpecId) { + const auto id = materialized[cursor + 3u]; + if (!PipelineSpecialization::IsWorkgroupOffset(id)) module->specialization.push_back({id, supplied.at(id)}); + } + if (op == spv::OpVariable && count >= 4u && !ldsBuffers.contains(materialized[cursor + 2u])) { if (const auto found = bindingNumbers.find(materialized[cursor + 2u]); found != bindingNumbers.end()) module->bindings.push_back(found->second); module->pushData |= materialized[cursor + 3u] == spv::StorageClassPushConstant; } @@ -991,7 +1012,7 @@ bool bufferSubstitutesEnabled() { return enabled; } -std::shared_ptr preparedBindingPlan(const CompiledVariant& variant, const ResourceSnapshot& snapshot, std::span exports, bool fragmentDescriptorSet, std::span substitutes) { +std::shared_ptr preparedBindingPlan(const CompiledVariant& variant, const ResourceSnapshot& snapshot, std::span exports, bool fragmentDescriptorSet, std::span substitutes, bool shaderImageMinLod) { struct BindingPlanKeyStorage {}; auto& key = HostThreadLocal, BindingPlanKeyStorage>(); key.clear(); @@ -1032,7 +1053,7 @@ std::shared_ptr preparedBindingPlan(const CompiledVariant& } std::call_once(plan->ready, [&] { try { - plan->bindings = DescriptorBindingBuilder{}.Prepare(variant.bindings.layout, variant.info.info, variant.info.stage, snapshot, exports, variant.artifact.registrationModule, fragmentDescriptorSet, substitutes); + plan->bindings = DescriptorBindingBuilder{}.Prepare(variant.bindings.layout, variant.info.info, variant.info.stage, snapshot, exports, variant.artifact.registrationModule, fragmentDescriptorSet, substitutes, shaderImageMinLod); } catch (...) { plan->failure = std::current_exception(); } @@ -1147,6 +1168,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil .fullSubgroups = artifact.fullSubgroups, .functionLdsBuffer = artifact.functionLdsBuffer, .meshWindowBuffer = artifact.meshWindowBuffer, + .scratchPoolSlotDwords = artifact.scratchPoolSlotDwords, .parameterExports = artifact.parameterExports, .fragmentParameters = artifact.fragmentParameters, .barycentricEmulation = artifact.barycentricEmulation, @@ -1154,6 +1176,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil .stencilReferences = artifact.stencilReferences, .stencilReplaySafe = artifact.stencilReplaySafe, .bdaWrites = artifact.bdaWrites, + .layerReplaySafe = artifact.layerReplaySafe, .stencilReferenceDword = artifact.stencilReferenceDword, .variantId = artifact.variantId }; @@ -1161,7 +1184,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil const auto index = reg - request.context.userDataBaseRegister; result.userDataRegisterReads |= reg >= request.context.userDataBaseRegister && index < 64u ? std::uint64_t{1} << index : ~std::uint64_t{0}; } - const auto plan = preparedBindingPlan(variant, snapshot, request.context.pixel ? std::span(request.context.pixel->targetExportMapping) : std::span{}, request.target.registrationModules, substitutes); + const auto plan = preparedBindingPlan(variant, snapshot, request.context.pixel ? std::span(request.context.pixel->targetExportMapping) : std::span{}, request.target.registrationModules, substitutes, request.target.shaderImageMinLod); const DescriptorBindingPlan* bindingPlan = &plan->bindings; std::shared_ptr entry; struct LocalModuleKeyStorage {}; @@ -1217,7 +1240,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil if (artifact.registrationModule) runtimeSelectors.at(input.location / RuntimeAbi::VertexSelectorsPerDword) |= (selectors & 0xfffu) << (input.location % RuntimeAbi::VertexSelectorsPerDword * 16u); } } - if (!plan->bindings.specialization.empty() || variant.bindings.layout.UsesPushData() || !artifact.vertexInputPatches.empty() || request.context.pixel || depthRemap) { + if (!plan->bindings.specialization.empty() || variant.bindings.layout.UsesPushData() || !artifact.vertexInputPatches.empty() || request.context.pixel || depthRemap || request.shader.stage == ShaderStage::Mesh) { { std::shared_lock lock(plan->mutex); if (const auto found = plan->modules.find(moduleKey); found != plan->modules.end()) entry = found->second; @@ -1229,6 +1252,8 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil } const auto specialize = [&] { auto constants = plan->bindings.specialization; + constants.push_back({PipelineSpecialization::MeshLayerBase, 0u}); + constants.push_back({PipelineSpecialization::MeshLayerCount, 0xffffffffu}); std::size_t index = 0; if (variant.bindings.layout.UsesPushData()) constants.push_back({PipelineSpecialization::PushDataOffset, moduleKey[index++]}); if (!artifact.stencilReferences.empty()) constants.push_back({PipelineSpecialization::StencilReferenceOffset, moduleKey[index++]}); @@ -1277,6 +1302,7 @@ RecompileResult materializeResult(const CompiledVariant& variant, const Recompil if (built.empty()) built = specialize().spirv; result.specializationId = module->specializationId; result.spirv = std::move(built); + result.specialization = module->specialization; bindingPlan = &entry->bindings; } else { result.spirv = artifact.spirv; @@ -1477,19 +1503,29 @@ RecompileResult recompileOnce(const RecompileRequest& request) { return materializeResult(variant, request, snapshot); } const auto source = getSource(request); - materializer.Materialize(*acquirePlan(request, source, true), runtime, snapshot); + try { + materializer.Materialize(*acquirePlan(request, source, true), runtime, snapshot); + } catch (...) { + std::lock_guard lock(source->mutex); + source->program.reset(); + throw; + } return materializeVariant(*source, request, snapshot); } RecompileResult RecompileImpl(const RecompileRequest& request) { - try { - return recompileOnce(request); - } catch (const ConvertedSampleRequired&) { - if (request.context.convertedSamples) throw; + auto current = request; + while (true) { + try { + return recompileOnce(current); + } catch (const ConvertedSampleRequired&) { + if (current.context.convertedSamples) throw; + current.context.convertedSamples = true; + } catch (const LayerRowSamplesRequired&) { + if (current.context.layerRowSamples) throw; + current.context.layerRowSamples = true; + } } - auto converted = request; - converted.context.convertedSamples = true; - return recompileOnce(converted); } // APS5_NO_RESULT_MEMO=1: every Recompile(request, capture) materializes its own result as before. @@ -1755,6 +1791,23 @@ std::uint64_t snapshotHash(const RecompileRequest& request, const ResourceSnapsh } mixDescriptors(snapshot.images); mixDescriptors(snapshot.samplers); + mix(snapshot.registeredImages.size()); + for (const auto& view : snapshot.registeredImages) { + mix(view.resource); + mix(view.record); + mix(view.address); + mix(view.rangeAddress); + mix(view.rangeBytes); + mix(view.allocationAddress); + mix(view.allocationBytes); + mix(view.backingSerial); + mix(view.hasRange); + mix(view.readable); + mix(view.writable); + mix(view.gpu); + for (const auto word : view.provenance.dwords) mix(word); + for (const auto word : view.effective.dwords) mix(word); + } if (masked) { mix(snapshot.flattenedSrt.size()); mix(snapshot.userData.size()); diff --git a/core/shader/recompiler/Recompiler.hpp b/core/shader/recompiler/Recompiler.hpp index 80db7c0b1a..aca3fdd71b 100644 --- a/core/shader/recompiler/Recompiler.hpp +++ b/core/shader/recompiler/Recompiler.hpp @@ -129,7 +129,9 @@ enum class ColorExportPacking : std::uint8_t { Unorm10_11_11, Float11_11_10, Unorm4_4_4_4, - Uint + Uint, + UintClamp8, + UintClamp16 }; enum class RasterPrimitive : std::uint8_t { @@ -292,6 +294,7 @@ struct GuestContext { std::optional floatMode; std::span linkedCallFallbacks; bool convertedSamples = false; + bool layerRowSamples = false; }; struct MeshTargetLimits { @@ -329,8 +332,12 @@ struct SpirvTarget { std::uint32_t maxWorkgroupSharedMemoryBytes; std::optional mesh; std::optional tessellation; + std::array featureDigest{}; + std::array featureDigestSpans{}; + bool featureDigestSealed = false; bool nonConstantImageOffsets = false; std::uint32_t srgbDecodeFormats = 0; + std::uint32_t maxImageArrayLayers = 0; bool narrowSubgroupClock = false; bool fixedPushSlots = false; bool float64DenormPreserve = false; @@ -353,6 +360,8 @@ struct SpirvTarget { bool ldsLockFences = false; bool fragmentBarycentricBuiltins = false; bool meshWindowBuffer = false; + bool hostInterpolationFirst = false; + bool shaderImageMinLod = false; }; struct BindingLayout { @@ -417,6 +426,8 @@ inline constexpr std::uint32_t MeshArgumentIndexCountDword = 3; inline constexpr std::uint32_t MeshArgumentFirstIndexDword = 4; inline constexpr std::uint32_t MeshArgumentBytes = 20; inline constexpr std::uint32_t MeshIndexRestartTable = 0x100; +inline constexpr std::uint32_t MeshLayerStrideShift = 16; +inline constexpr std::uint32_t MeshLayerStrideLimit = 0xffff; inline constexpr std::uint32_t MeshRestartLengthDword = MeshArgumentBytes / 4; inline constexpr std::uint32_t MeshRestartTableDword = MeshRestartLengthDword + 1; inline constexpr std::uint32_t MeshCapturedCounterDword = MeshArgumentBytes / 4; @@ -426,6 +437,7 @@ inline constexpr std::uint32_t MeshCapturedItemAddressDword = 0; inline constexpr std::uint32_t TessellationCaptureArgumentAddressDword = 2; inline constexpr std::uint32_t MeshIndexBufferUserWord = 4; inline constexpr std::uint32_t WorkgroupMemoryDescriptorSet = 1; +inline constexpr std::uint32_t ScratchPoolDescriptorSet = 2; struct GraphicsDrawParameters { std::uint64_t indexAddress; @@ -550,7 +562,7 @@ struct DescriptorBinding { std::vector samplerCoordinatesIgnored; std::vector imageUnnormalized; std::vector imageUnnormalizedBaseLevel; - std::vector imageSamplers; + std::vector imageSamplers; std::vector bufferRead; std::vector bufferExtent; }; @@ -758,6 +770,7 @@ struct CompiledShaderArtifact { bool fullSubgroups = false; bool functionLdsBuffer = false; bool meshWindowBuffer = false; + std::uint32_t scratchPoolSlotDwords = 0; std::vector parameterExports; std::vector fragmentParameters; BarycentricEmulation barycentricEmulation; @@ -765,6 +778,7 @@ struct CompiledShaderArtifact { std::vector stencilReferences; bool stencilReplaySafe = false; bool bdaWrites = false; + bool layerReplaySafe = false; std::uint32_t stencilReferenceDword = 0xffffffffu; std::uint64_t variantId = 0; }; diff --git a/core/shader/recompiler/ShaderDiskCache.cpp b/core/shader/recompiler/ShaderDiskCache.cpp index 77fa67da41..22b9999751 100644 --- a/core/shader/recompiler/ShaderDiskCache.cpp +++ b/core/shader/recompiler/ShaderDiskCache.cpp @@ -87,7 +87,9 @@ namespace ShaderRecompiler::ShaderDiskCache { #if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__) static_assert(sizeof(CompiledShaderArtifact) == 288, "CompiledShaderArtifact changed: update the artifact encoder"); static_assert(sizeof(ShaderInvocation) == 216, "ShaderInvocation changed: update the invocation encoder"); +#if defined(__cpp_lib_atomic_shared_ptr) static_assert(sizeof(RecompileResult) == 552, "RecompileResult changed: update EncodeResult and DecodeResult"); +#endif static_assert(sizeof(DescriptorBinding) == 656, "DescriptorBinding changed: update the binding encoder"); static_assert(sizeof(VertexAttribute) == 32, "VertexAttribute changed: update the attribute encoder"); static_assert(sizeof(VertexInput) == 16, "VertexInput changed: update the vertex input encoder"); @@ -95,7 +97,7 @@ static_assert(sizeof(FragmentParameter) == 16, "FragmentParameter changed: updat static_assert(sizeof(CompiledShaderInfo) == 352, "CompiledShaderInfo changed: update the info encoder"); static_assert(sizeof(ShaderInfo) == 232, "ShaderInfo changed: update the info encoder"); static_assert(sizeof(BufferResource) == 32, "BufferResource changed: update the info encoder"); -static_assert(sizeof(ImageResource) == 96, "ImageResource changed: update the info encoder"); +static_assert(sizeof(ImageResource) == 108, "ImageResource changed: update the info encoder"); static_assert(sizeof(SamplerResource) == 24, "SamplerResource changed: update the info encoder"); static_assert(sizeof(SampledResourcePair) == 12, "SampledResourcePair changed: update the info encoder"); static_assert(sizeof(StageInput) == 56, "StageInput changed: update the info encoder"); @@ -109,7 +111,7 @@ static_assert(sizeof(IrResourcePlan) == 728, "IrResourcePlan changed: update Enc static_assert(sizeof(IrValue) == 144, "IrValue changed: update the plan value encoder"); static_assert(sizeof(GuestRegister) == 8, "GuestRegister changed: update the plan value encoder"); static_assert(sizeof(MemoryInfo) == 80, "MemoryInfo changed: update the plan encoder"); -static_assert(sizeof(DescriptorSource) == 192, "DescriptorSource changed: update the plan encoder"); +static_assert(sizeof(DescriptorSource) == 216, "DescriptorSource changed: update the plan encoder"); static_assert(sizeof(TableColumn) == 112, "TableColumn changed: update the plan encoder"); static_assert(sizeof(KeyDomain) == 16, "KeyDomain changed: update the plan encoder"); static_assert(sizeof(ResourceBlock) == 104, "ResourceBlock changed: update the plan encoder"); @@ -141,15 +143,15 @@ constexpr std::size_t fieldCount() { static_assert(fieldCount() == 24, "ShaderInfo changed: update the info encoder"); static_assert(fieldCount() == 12, "BufferResource changed: update the info encoder"); -static_assert(fieldCount() == 46, "ImageResource changed: update the info encoder"); +static_assert(fieldCount() == 49, "ImageResource changed: update the info encoder"); static_assert(fieldCount() == 8, "SamplerResource changed: update the info encoder"); static_assert(fieldCount() == 3, "SampledResourcePair changed: update the info encoder"); static_assert(fieldCount() == 5, "StageInput changed: update the info encoder"); static_assert(fieldCount() == 4, "StageOutput changed: update the info encoder"); -static_assert(fieldCount() == 24, "IrResourcePlan changed: update EncodePlan and DecodePlan"); +static_assert(fieldCount() == 25, "IrResourcePlan changed: update EncodePlan and DecodePlan"); static_assert(fieldCount() == 2, "GuestRegister changed: update the plan value encoder"); static_assert(fieldCount() == 32, "MemoryInfo changed: update the plan encoder"); -static_assert(fieldCount() == 3, "DescriptorSource changed: update the plan encoder"); +static_assert(fieldCount() == 4, "DescriptorSource changed: update the plan encoder"); static_assert(fieldCount() == 18, "TableColumn changed: update the plan encoder"); static_assert(fieldCount() == 4, "KeyDomain changed: update the plan encoder"); static_assert(fieldCount() == 5, "ResourceBlock changed: update the plan encoder"); @@ -475,7 +477,7 @@ void encodeBinding(Writer& writer, const DescriptorBinding& binding) { writer.Flags(binding.imageUnnormalizedBaseLevel); writer.Flags(binding.samplerIntegerBorder); writer.Flags(binding.samplerCoordinatesIgnored); - writer.Values(std::span(binding.imageSamplers)); + writer.Values(std::span(binding.imageSamplers)); writer.Flags(binding.bufferRead); writer.Values(std::span(binding.bufferExtent)); } @@ -542,6 +544,7 @@ void encodeArtifact(Writer& writer, const CompiledShaderArtifact& result) { writer.Value(result.fullSubgroups); writer.Value(result.functionLdsBuffer); writer.Value(result.meshWindowBuffer); + writer.Value(result.scratchPoolSlotDwords); writer.Values(std::span(result.parameterExports)); writer.List(result.fragmentParameters, [](Writer& out, const FragmentParameter& parameter) { out.Value(parameter.location); @@ -571,6 +574,7 @@ void encodeArtifact(Writer& writer, const CompiledShaderArtifact& result) { writer.Values(std::span(result.stencilReferences)); writer.Value(result.stencilReplaySafe); writer.Value(result.bdaWrites); + writer.Value(result.layerReplaySafe); writer.Value(result.stencilReferenceDword); } @@ -607,6 +611,7 @@ void decodeArtifact(Reader& reader, CompiledShaderArtifact& result) { reader.Value(result.fullSubgroups); reader.Value(result.functionLdsBuffer); reader.Value(result.meshWindowBuffer); + reader.Value(result.scratchPoolSlotDwords); reader.Values(result.parameterExports); reader.List(result.fragmentParameters, 10, [](Reader& in, FragmentParameter& parameter) { in.Value(parameter.location); @@ -640,6 +645,7 @@ void decodeArtifact(Reader& reader, CompiledShaderArtifact& result) { reader.Values(result.stencilReferences); reader.Value(result.stencilReplaySafe); reader.Value(result.bdaWrites); + reader.Value(result.layerReplaySafe); reader.Value(result.stencilReferenceDword); result.variantId = 0; } @@ -809,6 +815,9 @@ void encodeShaderInfo(Writer& writer, const ShaderInfo& info) { out.Value(image.foldTexelOffsetsOnly); out.Value(image.convertedSamples); out.Value(image.remappedDepth1D); + out.Value(image.layerRowsCompatible); + out.Value(image.layerRows); + out.Value(image.maxArrayLayers); out.Value(image.optionalDescriptor); out.Value(image.byElements); out.Value(image.byComponents); @@ -920,6 +929,9 @@ void decodeShaderInfo(Reader& reader, ShaderInfo& info) { in.Value(image.foldTexelOffsetsOnly); in.Value(image.convertedSamples); in.Value(image.remappedDepth1D); + in.Value(image.layerRowsCompatible); + in.Value(image.layerRows); + in.Value(image.maxArrayLayers); in.Value(image.optionalDescriptor); in.Value(image.byElements); in.Value(image.byComponents); @@ -1110,6 +1122,7 @@ bool encodePlan(Writer& writer, const IrResourcePlan& plan) { writer.Value(plan.userDataBase); writer.Value(plan.userDataCount); writer.Value(plan.srgbDecodeFormats); + writer.Value(plan.maxImageArrayLayers); writer.List(plan.blockStorage, [](Writer& out, const std::unique_ptr& block) { out.Value(block->Id()); }); writer.Value(plan.valueStorage.Size()); for (const auto& stored : plan.valueStorage) { @@ -1168,6 +1181,7 @@ bool encodePlan(Writer& writer, const IrResourcePlan& plan) { out.Value(column.foldedCarry); out.Value(column.selected); out.Value(column.selection); + out.Values(std::span(source.requiredBlocks)); }); writer.List(plan.controlFlow, [&](Writer& out, const ResourceBlock& block) { out.Value(valueIndex(block.condition)); @@ -1218,6 +1232,7 @@ bool decodePlan(Reader& reader, IrResourcePlan& plan) { reader.Value(plan.userDataBase); reader.Value(plan.userDataCount); reader.Value(plan.srgbDecodeFormats); + reader.Value(plan.maxImageArrayLayers); std::vector blockIds; reader.Values(blockIds); std::vector records; @@ -1299,7 +1314,7 @@ bool decodePlan(Reader& reader, IrResourcePlan& plan) { value.SetDetachedOperands(std::move(arguments), std::move(phiBlocks)); } reader.List(plan.memoryInfo, 79, decodeMemoryInfo); - reader.List(plan.descriptorSources, 72, [&](Reader& in, DescriptorSource& source) { + reader.List(plan.descriptorSources, 80, [&](Reader& in, DescriptorSource& source) { for (auto& dword : source.dwords) dword = valueAt(in.Get()); in.Value(source.dwordCount); const bool table = in.Get(); @@ -1331,6 +1346,7 @@ bool decodePlan(Reader& reader, IrResourcePlan& plan) { in.Value(column.selected); in.Value(column.selection); source.tableColumn = table ? std::optional(std::move(column)) : std::nullopt; + in.Values(source.requiredBlocks); }); reader.List(plan.controlFlow, 36, [&](Reader& in, ResourceBlock& block) { block.condition = valueAt(in.Get()); @@ -1362,6 +1378,7 @@ bool decodePlan(Reader& reader, IrResourcePlan& plan) { for (auto& value : plan.uniformFill.values) value = valueAt(reader.Get()); if (!valid || !reader.Done() || fill.words > plan.uniformFill.values.size()) return false; const auto knownSource = [&](std::uint32_t source) { return source < plan.descriptorSources.size(); }; + if (!std::ranges::all_of(plan.descriptorSources, [&](const DescriptorSource& source) { return std::ranges::all_of(source.requiredBlocks, [&](std::uint32_t block) { return block < plan.controlFlow.size(); }); })) return false; if (!std::ranges::all_of(plan.materializationSources, knownSource)) return false; if (!std::ranges::all_of(plan.descriptorSources, [&](const DescriptorSource& source) { return source.dwordCount <= source.dwords.size() && (!source.tableColumn.has_value() || (knownSource(source.tableColumn->heapSource) && (!source.tableColumn->selected || knownSource(source.tableColumn->selection)) && std::ranges::all_of(source.tableColumn->keyDomains, [&](const KeyDomain& domain) { return knownSource(domain.source); }))); })) return false; if (!std::ranges::all_of(plan.info.buffers, [&](const BufferResource& buffer) { return knownSource(buffer.source); })) return false; diff --git a/core/shader/recompiler/ShaderDiskCache.hpp b/core/shader/recompiler/ShaderDiskCache.hpp index 5f25660eb7..b937c57213 100644 --- a/core/shader/recompiler/ShaderDiskCache.hpp +++ b/core/shader/recompiler/ShaderDiskCache.hpp @@ -13,7 +13,7 @@ namespace ShaderRecompiler::ShaderDiskCache { -inline constexpr std::uint32_t FormatVersion = 50; +inline constexpr std::uint32_t FormatVersion = 52; enum class LoadStatus { Loaded, diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp index 4dc191d3d9..f534d23a51 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvAnalysis.hpp @@ -25,6 +25,8 @@ struct SpirvRequirements { bool functionScratch = false; bool pixelValidMask = false; bool bufferInt64Atomics = false; + bool bufferInt64AtomicsExact = false; + bool bufferInt64AtomicsSplit = false; bool coherentBufferPairs = false; bool bufferU64View = false; bool imageInt64Atomics = false; @@ -39,6 +41,7 @@ struct SpirvRequirements { std::vector extensions; }; +[[nodiscard]] bool IsSplittableBufferAtomic64(const IrValue* inst); [[nodiscard]] SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program); [[nodiscard]] bool IsIntegerFloat64Opcode(IrOpcode opcode); inline constexpr std::uint32_t FunctionLdsDwordLimit = 8192u; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp index 876c3eddbe..5938cf31fc 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp @@ -20,6 +20,7 @@ struct SpirvTargetOptions { std::span supportedCapabilities; std::span supportedExtensions; bool nonConstantImageOffsets = false; + bool shaderImageMinLod = false; bool narrowSubgroupClock = false; bool float64DenormPreserve = false; bool depthRangeRemap = false; @@ -51,6 +52,7 @@ struct SpirvEmitInfo { bool fullSubgroups = false; bool functionLdsBuffer = false; bool meshWindowBuffer = false; + std::uint32_t scratchPoolSlotDwords = 0; }; struct SpannedWaveUnsupported : std::runtime_error { diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp index f0f5a1c4d1..777eda1ea3 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterHelpers.hpp @@ -117,6 +117,13 @@ bool LdsInDeviceMemory(const SpirvEmitterState& state); std::uint32_t LdsMemorySemantics(const SpirvEmitterState& state); std::uint32_t LdsMemoryBarrierScope(const SpirvEmitterState& state); void EmitLdsBufferBase(SpirvEmitterState& state); +bool ScratchPoolActive(const SpirvEmitterState& state); +std::uint32_t ScratchPoolSlotDwords(const SpirvEmitterState& state); +void DefineScratchPool(SpirvEmitterState& state); +void EmitScratchPoolAcquire(SpirvEmitterState& state); +std::uint32_t EmitScratchPoolElementPointer(SpirvEmitterState& state, std::uint32_t index); +void EmitReturnReleasingScratch(SpirvEmitterState& state); +void VerifyScratchPoolReturns(const SpirvEmitterState& state, const std::vector& words); std::uint32_t EmitLdsLockPointer(SpirvEmitterState& state); std::uint32_t EmitLdsBufferU64Pointer(SpirvEmitterState& state, std::uint32_t index); MemoryResourceAccess PrepareMemoryResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem); @@ -249,6 +256,12 @@ std::uint32_t EmitValueIfElse(SpirvEmitterState& state, std::uint32_t condition, template std::uint32_t AtomicUpdateTyped(SpirvEmitterState& state, std::uint32_t pointer, ResourceKind kind, std::uint32_t type, TFunction&& function) { + if (kind == ResourceKind::Scratch && ScratchPoolActive(state)) { + const auto previous = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, type, previous, pointer); + state.module.AddFunction(spv::OpStore, pointer, function(previous)); + return previous; + } const auto scope = kind == ResourceKind::Lds ? spv::ScopeWorkgroup : spv::ScopeDevice; const auto memory = [&]() -> std::uint32_t { switch (kind) { diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp index f7db7439c4..d0c08fb226 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp @@ -172,6 +172,7 @@ std::uint32_t EmitLogicalNotBool(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFNegateValue(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitFAbsValue(SpirvEmitterState& state, std::uint32_t arg0); std::uint32_t EmitPackHalf2x16(SpirvEmitterState& state, std::uint32_t arg0); +std::uint32_t EmitPackHalf2x16Value(SpirvEmitterState& state, const IrValue& inst, std::uint32_t arg0); std::uint32_t EmitF32ToF16BitsRte(SpirvEmitterState& state, std::uint32_t value); std::uint32_t EmitFPFma32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2); std::uint32_t EmitFPMad32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp index 21cefd6303..09aec42aa3 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp @@ -138,6 +138,7 @@ struct SpirvEmitterState { std::span supportedCapabilities; std::span supportedExtensions; bool nonConstantImageOffsets = false; + bool shaderImageMinLod = false; bool tableIndexNonUniform = true; std::uint32_t storageBufferVariable = 0; std::uint32_t storageBufferU64Variable = 0; @@ -220,6 +221,14 @@ struct SpirvEmitterState { bool pixelFunctionLdsBuffer = false; std::uint32_t numWorkgroupsVariable = 0; std::array scratchVariable {}; + std::uint32_t scratchPoolVariable = 0; + std::uint32_t scratchPoolSlotVariable = 0; + std::uint32_t scratchPoolCandidateVariable = 0; + std::uint32_t scratchPoolWonVariable = 0; + std::uint32_t scratchPoolSlotCount = 0; + std::uint32_t scratchPoolSlot = 0; + std::array scratchPoolLaneBase {}; + std::uint32_t scratchPoolReturns = 0; std::array vectorWindowVariable {}; std::uint32_t vectorWindowIndexed = 0; std::array, 2> vectorWindowSlots {}; @@ -254,6 +263,8 @@ struct SpirvEmitterState { std::uint32_t sampleMaskInputVariable = 0; std::uint32_t quadSampleCoverage = 0; std::uint32_t sampleOrderWord = 0; + std::uint32_t alphaCoverageVariable = 0; + std::uint32_t exportedCoverageVariable = 0; std::uint32_t subgroupLocalInvocationIdVariable = 0; std::uint32_t subgroupIdVariable = 0; std::uint32_t perVertexVariable = 0; diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp index 18e703456f..3fbdbef112 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp @@ -15,6 +15,13 @@ bool LdsInDeviceMemory(const SpirvEmitterState& state); std::uint32_t LdsMemorySemantics(const SpirvEmitterState& state); std::uint32_t LdsMemoryBarrierScope(const SpirvEmitterState& state); void EmitLdsBufferBase(SpirvEmitterState& state); +bool ScratchPoolActive(const SpirvEmitterState& state); +std::uint32_t ScratchPoolSlotDwords(const SpirvEmitterState& state); +void DefineScratchPool(SpirvEmitterState& state); +void EmitScratchPoolAcquire(SpirvEmitterState& state); +std::uint32_t EmitScratchPoolElementPointer(SpirvEmitterState& state, std::uint32_t index); +void EmitReturnReleasingScratch(SpirvEmitterState& state); +void VerifyScratchPoolReturns(const SpirvEmitterState& state, const std::vector& words); std::uint32_t EmitLdsLockPointer(SpirvEmitterState& state); std::uint32_t EmitLdsBufferU64Pointer(SpirvEmitterState& state, std::uint32_t index); MemoryResourceAccess PrepareMemoryResourceAccess(SpirvEmitterState& state, const MemoryInfo& mem); diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvRuntimeBuffer.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvRuntimeBuffer.hpp index bd7fa1a632..a76f780378 100644 --- a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvRuntimeBuffer.hpp +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvRuntimeBuffer.hpp @@ -10,6 +10,7 @@ std::uint32_t EmitRuntimeBufferLoad(SpirvValueEmitContext& context, const IrValu void EmitRuntimeBufferStore(SpirvValueEmitContext& context, const IrValue& instruction, std::uint32_t components); std::uint32_t EmitRuntimeScalarBufferLoad(SpirvValueEmitContext& context, const IrValue& instruction); std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrValue& instruction, std::uint32_t bits, std::uint32_t active, const std::function& operation); +void EmitRuntimeBufferAtomicHalves(SpirvValueEmitContext& context, const IrValue& instruction, std::uint32_t active, const std::function& operation); } diff --git a/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvSpannedUniformity.hpp b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvSpannedUniformity.hpp new file mode 100644 index 0000000000..23d3be78d7 --- /dev/null +++ b/core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvSpannedUniformity.hpp @@ -0,0 +1,15 @@ +#ifndef CORE_SHADER_RECOMPILER_SPIRVSPANNEDUNIFORMITY_HPP +#define CORE_SHADER_RECOMPILER_SPIRVSPANNEDUNIFORMITY_HPP + +#include +#include +#include +#include + +namespace ShaderRecompiler { + +[[nodiscard]] std::optional SpannedBarrierInDivergentFlow(std::span words); + +} + +#endif diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp index 59155cf915..9a1b313b0f 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp @@ -1283,8 +1283,8 @@ std::uint32_t EmitFPLog2(SpirvEmitterState& state, std::uint32_t arg0) { const auto series = Log2SeriesNearOne(state, difference); const auto valueBits = Unary(state, spv::OpBitcast, TypeU32(state), value); const auto seriesBits = Unary(state, spv::OpBitcast, TypeU32(state), series); - const auto distance = EmitExt(state, TypeU32(state), GLSLstd450UMin, {Binary(state, spv::OpISub, TypeU32(state), valueBits, seriesBits), Binary(state, spv::OpISub, TypeU32(state), seriesBits, valueBits)}); - const auto far = Binary(state, spv::OpUGreaterThan, TypeBool(state), distance, ConstantU32(state, 4u)); + const auto biasedDifference = Binary(state, spv::OpIAdd, TypeU32(state), Binary(state, spv::OpISub, TypeU32(state), valueBits, seriesBits), ConstantU32(state, 4u)); + const auto far = Binary(state, spv::OpUGreaterThan, TypeBool(state), biasedDifference, ConstantU32(state, 8u)); return Select(state, TypeF32(state), far, series, value); }); } @@ -1310,8 +1310,8 @@ std::uint32_t CosTurnSeries(SpirvEmitterState& state, std::uint32_t turns) { std::uint32_t SeriesWhenFar(SpirvEmitterState& state, std::uint32_t value, std::uint32_t series) { const auto valueBits = Unary(state, spv::OpBitcast, TypeU32(state), value); const auto seriesBits = Unary(state, spv::OpBitcast, TypeU32(state), series); - const auto distance = EmitExt(state, TypeU32(state), GLSLstd450UMin, {Binary(state, spv::OpISub, TypeU32(state), valueBits, seriesBits), Binary(state, spv::OpISub, TypeU32(state), seriesBits, valueBits)}); - return Select(state, TypeF32(state), Binary(state, spv::OpUGreaterThan, TypeBool(state), distance, ConstantU32(state, 4u)), series, value); + const auto biasedDifference = Binary(state, spv::OpIAdd, TypeU32(state), Binary(state, spv::OpISub, TypeU32(state), valueBits, seriesBits), ConstantU32(state, 4u)); + return Select(state, TypeF32(state), Binary(state, spv::OpUGreaterThan, TypeBool(state), biasedDifference, ConstantU32(state, 8u)), series, value); } std::uint32_t TrigLinearNearZero(SpirvEmitterState& state, std::uint32_t value, std::uint32_t distance, std::uint32_t linearConstant, std::uint32_t limit) { @@ -1847,7 +1847,7 @@ static void EmitNativeF16Modes(SpirvEmitterState& state) { } } -static std::uint32_t EmitNativeF32ToF16BitsRte(SpirvEmitterState& state, std::uint32_t value) { +static std::uint32_t EmitNativeF32ToF16BitsRte(SpirvEmitterState& state, std::uint32_t value, bool nanPayloadUnused = false) { EmitNativeF16Modes(state); const auto u32 = TypeU32(state); const auto f16 = state.module.Type(spv::OpTypeFloat, 16u); @@ -1857,6 +1857,7 @@ static std::uint32_t EmitNativeF32ToF16BitsRte(SpirvEmitterState& state, std::ui const auto bits = Unary(state, spv::OpBitcast, u32, pair); const auto source = Unary(state, spv::OpBitcast, u32, value); const auto magnitude = Binary(state, spv::OpBitwiseAnd, u32, source, ConstantU32(state, 0x7fffffffu)); + if (nanPayloadUnused) return Select(state, u32, Binary(state, spv::OpUGreaterThan, TypeBool(state), magnitude, ConstantU32(state, 0x7f800000u)), ConstantU32(state, 0x7e00u), bits); const auto sign = Binary(state, spv::OpBitwiseAnd, u32, Binary(state, spv::OpShiftRightLogical, u32, source, ConstantU32(state, 16u)), ConstantU32(state, 0x8000u)); const auto payload = Binary(state, spv::OpShiftRightLogical, u32, Binary(state, spv::OpBitwiseAnd, u32, magnitude, ConstantU32(state, 0x7fffffu)), ConstantU32(state, 13u)); const auto nan = Binary(state, spv::OpBitwiseOr, u32, ConstantU32(state, 0x7e00u), payload); @@ -1913,16 +1914,24 @@ std::uint32_t EmitF32ToF16BitsRte(SpirvEmitterState& state, std::uint32_t value) return op(spv::OpBitwiseOr, sign, result); } -std::uint32_t EmitPackHalf2x16(SpirvEmitterState& state, std::uint32_t arg0) { +static std::uint32_t PackHalf2x16(SpirvEmitterState& state, std::uint32_t arg0, bool nanPayloadUnused) { const auto component = [&](std::uint32_t index) { const auto value = state.module.AllocateId(); state.module.AddFunction(spv::OpCompositeExtract, TypeF32(state), value, arg0, index); - return EmitF32ToF16BitsRte(state, value); + return nanPayloadUnused && NativeF16Rte(state) ? EmitNativeF32ToF16BitsRte(state, value, true) : EmitF32ToF16BitsRte(state, value); }; const auto high = Binary(state, spv::OpShiftLeftLogical, TypeU32(state), component(1u), ConstantU32(state, 16u)); return Binary(state, spv::OpBitwiseOr, TypeU32(state), component(0u), high); } +std::uint32_t EmitPackHalf2x16(SpirvEmitterState& state, std::uint32_t arg0) { + return PackHalf2x16(state, arg0, false); +} + +std::uint32_t EmitPackHalf2x16Value(SpirvEmitterState& state, const IrValue& inst, std::uint32_t arg0) { + return PackHalf2x16(state, arg0, inst.Flags().nanPayloadUnused); +} + std::uint32_t EmitFPFma32(SpirvEmitterState& state, std::uint32_t arg0, std::uint32_t arg1, std::uint32_t arg2) { return Exact(state, EmitGlsl(state, arg0, arg1, arg2)); } @@ -2010,7 +2019,7 @@ static bool OnlyCompared(const IrValue& value) { std::uint32_t EmitConvertF32F16(SpirvEmitterState& state, const IrValue& inst, std::uint32_t bits) { if (NarrowF16Field(*inst.Argument(0))) return EmitF16BitsToF32Decoded(state, bits); - return OnlyCompared(inst) ? EmitF16BitsToF32AnyNan(state, bits) : EmitF16BitsToF32(state, bits); + return inst.Flags().nanPayloadUnused || OnlyCompared(inst) ? EmitF16BitsToF32AnyNan(state, bits) : EmitF16BitsToF32(state, bits); } std::uint32_t EmitCompositeExtractU32x2(SpirvEmitterState& state, std::uint32_t arg0, const IrValue* arg1) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp index 1462e36096..4529d11e95 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvAnalysis.cpp @@ -334,14 +334,33 @@ std::unordered_map FunctionLdsLaneAddresses(const return addresses; } +bool IsSplittableBufferAtomic64(const IrValue* inst) { + switch (inst->Opcode()) { + case IrOpcode::BufferAtomicOr64: + case IrOpcode::BufferAtomicAnd64: + case IrOpcode::BufferAtomicXor64: + return !inst->HasUses(); + default: + return false; + } +} + SpirvRequirements AnalyzeProgramRequirements(const IrProgram& program) { bool storageWrites = false; SpirvRequirements requirements {}; + std::optional splitOpcode; for (const IrBlock* block : program.BlockOrder()) { for (const IrValue* inst : block->Instructions()) { const auto addressAccess = AddressOpcodeInfoOf(inst->Opcode()).access; if ((BufferAccessOf(inst->Opcode()) == BufferAccess::Atomic || addressAccess == AddressAccess::Atomic) && inst->Type() == IrType::U64) { requirements.bufferInt64Atomics = true; + if (!IsSplittableBufferAtomic64(inst)) { + requirements.bufferInt64AtomicsExact = true; + } else if (!splitOpcode) { + splitOpcode = inst->Opcode(); + } else if (*splitOpcode != inst->Opcode()) { + requirements.bufferInt64AtomicsExact = true; + } } if (IsImageAtomic64Opcode(inst->Opcode())) { requirements.imageInt64Atomics = true; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvBdaFault.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvBdaFault.cpp index 53f65c7c1c..cb6d2a7cda 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvBdaFault.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvBdaFault.cpp @@ -121,7 +121,7 @@ void StopBdaInvocationIf(SpirvEmitterState& state, std::uint32_t condition) { state.module.AddFunction(spv::OpEndInvocationInterlockEXT); } if (state.program.Resources().stage != IrShaderStage::Pixel) { - state.module.AddFunction(spv::OpReturn); + EmitReturnReleasingScratch(state); } else if (state.pixelStopFlag != 0u) { state.module.AddFunction(spv::OpStore, state.pixelStopFlag, ConstantBool(state, true)); state.module.AddFunction(spv::OpReturn); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp index 230ddd8bbd..591fac5fd9 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp @@ -1,5 +1,6 @@ #include "SpirvBackend/SpirvBda.hpp" #include "SpirvBackend/SpirvEmitter.hpp" +#include "SpirvBackend/SpirvSpannedUniformity.hpp" #include "SpirvBackend/SpirvEmitterHelpers.hpp" #include "SpirvBackend/SpirvEmitterState.hpp" #include "SpirvBackend/SpirvFlowEmitter.hpp" @@ -262,6 +263,7 @@ std::vector SpirvEmitter::Emit(const IrProgram& program, const Sh state.supportedCapabilities = target.supportedCapabilities; state.supportedExtensions = target.supportedExtensions; state.nonConstantImageOffsets = target.nonConstantImageOffsets; + state.shaderImageMinLod = target.shaderImageMinLod; state.narrowSubgroupClock = target.narrowSubgroupClock; state.float64DenormPreserve = target.float64DenormPreserve; state.depthRangeRemap = target.depthRangeRemap; @@ -294,6 +296,10 @@ std::vector SpirvEmitter::Emit(const IrProgram& program, const Sh state.requirements.ldsBufferInt64Atomics = true; } const bool int64Atomics = std::ranges::find(state.supportedCapabilities, static_cast(spv::CapabilityInt64Atomics)) != state.supportedCapabilities.end(); + if (!int64Atomics && state.requirements.bufferInt64Atomics && !state.requirements.bufferInt64AtomicsExact) { + state.requirements.bufferInt64Atomics = false; + state.requirements.bufferInt64AtomicsSplit = true; + } state.requirements.bufferU64View = state.requirements.bufferInt64Atomics || (state.requirements.coherentBufferPairs && int64Atomics); if (HostSubgroupSingleLane(state)) { state.requirements.subgroupBallot = false; @@ -328,12 +334,18 @@ std::vector SpirvEmitter::Emit(const IrProgram& program, const Sh EmitModuleHeader(state, bindings); EmitProgram(state); state.module.EmitEntryPoint(ExecutionModelForStage(state.program.Resources().stage), state.mainFunc, "main", state.interfaceVariables); + auto words = state.module.Finalize(); + if (state.spannedWave) { + if (auto reason = SpannedBarrierInDivergentFlow(words)) throw SpannedWaveUnsupported(*reason); + } if (info != nullptr) { info->fullSubgroups = state.spannedWave; info->functionLdsBuffer = state.pixelFunctionLdsBuffer; info->meshWindowBuffer = state.meshWindowBuffer; + info->scratchPoolSlotDwords = ScratchPoolActive(state) ? ScratchPoolSlotDwords(state) : 0u; } - return state.module.Finalize(); + VerifyScratchPoolReturns(state, words); + return words; } } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp index a48917de1f..d22f571409 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp @@ -108,7 +108,7 @@ void EmitReturnTerminator(SpirvValueEmitContext& ctx) { state.module.AddFunction(spv::OpLoad, TypeU32(state), expected, pointer); EmitKillIfBoolFalse(state, Binary(state, spv::OpIEqual, TypeBool(state), masked, expected)); } - state.module.AddFunction(spv::OpReturn); + EmitReturnReleasingScratch(state); } // The loop guard (see SpirvEmitterState::loopGuardLimit) at a branch that may leave a loop: counts the @@ -376,7 +376,7 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) { case IrOpcode::CompositeExtractU32x2: return Invoke(EmitCompositeExtractU32x2, ctx, inst); case IrOpcode::CompositeExtractU32x3: return Invoke(EmitCompositeExtractU32x3, ctx, inst); case IrOpcode::CompositeExtractU32x4: return Invoke(EmitCompositeExtractU32x4, ctx, inst); - case IrOpcode::PackHalf2x16: return Invoke(EmitPackHalf2x16, ctx, inst); + case IrOpcode::PackHalf2x16: return Invoke(EmitPackHalf2x16Value, ctx, inst); case IrOpcode::PackFloat2x16Rtz: return Invoke(EmitPackFloat2x16Rtz, ctx, inst); case IrOpcode::FPAdd16x2: return Invoke(EmitFPAdd16x2, ctx, inst); case IrOpcode::FPMul16x2: return Invoke(EmitFPMul16x2, ctx, inst); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp index 34ae6baea8..58d08e6ca9 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp @@ -204,6 +204,31 @@ std::uint32_t CoordF32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access return result; } +std::uint32_t LayerRowsCoord(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t coord) { + auto& state = ctx.state; + const auto f32 = TypeF32(state); + const auto u32 = TypeU32(state); + const auto extract = [&](std::uint32_t type, std::uint32_t composite, std::uint32_t index) { + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeExtract, type, value, composite, index); + return value; + }; + state.module.EmitCapability(spv::CapabilityImageQuery); + const auto size = state.module.AllocateId(); + state.module.AddFunction(spv::OpImageQuerySizeLod, ImageViewSizeType(state, RdnaImageDimension::Dim2D), size, LoadSampledImageDescriptor(state, access.mem.resource), ConstantU32(state, 0u)); + const auto rows = extract(u32, size, 1u); + const auto lastLayer = Binary(state, spv::OpISub, u32, Binary(state, spv::OpShiftRightLogical, u32, rows, ConstantU32(state, 1u)), ConstantU32(state, 1u)); + const auto rounded = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, f32, rounded, GlslStd450(state), GLSLstd450RoundEven, extract(f32, coord, 1u)); + const auto clamped = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, f32, clamped, GlslStd450(state), GLSLstd450NClamp, rounded, ConstantF32(state, 0u), Unary(state, spv::OpConvertUToF, f32, lastLayer)); + const auto center = Binary(state, spv::OpFAdd, f32, Binary(state, spv::OpFMul, f32, clamped, ConstantF32(state, std::bit_cast(2.0f))), ConstantF32(state, std::bit_cast(1.0f))); + const auto v = Binary(state, spv::OpFDiv, f32, center, Unary(state, spv::OpConvertUToF, f32, rows)); + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), result, extract(f32, coord, 0u), v); + return result; +} + std::uint32_t CoordU32(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto components = RdnaImageDimensionInfoFor(access.image.dimension).coordinateComponents; const auto encoded = std::min(components, AddressDimension(access).coordinateComponents); @@ -523,6 +548,35 @@ std::uint32_t FoldedOffsetCoord(SpirvValueEmitContext& ctx, const ImageEmitAcces return sum; } +bool MirrorsSampleIfUnnormalized(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, const SampleSetup& setup) { + const auto dimension = access.image.dimension; + return !setup.dref && !access.image.cube && (dimension == RdnaImageDimension::Dim1D || dimension == RdnaImageDimension::Dim2D) && MirrorsIfUnnormalized(ctx.state.program.Info().samplers.at(access.mem.sampler)); +} + +std::uint32_t MirroredUnnormalizedCoord(SpirvValueEmitContext& ctx, const SampleSetup& setup, std::uint32_t coord, std::uint32_t axes) { + auto& state = ctx.state; + const auto f32 = TypeF32(state); + const auto u32 = TypeU32(state); + const auto boolean = TypeBool(state); + const auto components = setup.dimensionInfo.spatialComponents; + std::array values{}; + for (std::uint32_t index = 0; index < components; ++index) { + auto x = coord; + if (components != 1u) { + x = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeExtract, f32, x, coord, index); + } + const auto mirrors = Binary(state, spv::OpINotEqual, boolean, Binary(state, spv::OpBitwiseAnd, u32, axes, ConstantU32(state, 1u << index)), ConstantU32(state, 0u)); + const auto negative = Binary(state, spv::OpFOrdLessThan, boolean, x, ConstantF32(state, 0u)); + const auto below = Unary(state, spv::OpBitcast, f32, Binary(state, spv::OpISub, u32, Unary(state, spv::OpBitcast, u32, Unary(state, spv::OpFNegate, f32, x)), ConstantU32(state, 1u))); + values[index] = Select(state, f32, Binary(state, spv::OpLogicalAnd, boolean, mirrors, negative), below, x); + } + if (components == 1u) return values[0]; + const auto result = state.module.AllocateId(); + state.module.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 2), result, values[0], values[1]); + return result; +} + std::uint32_t HorizontalOffsets(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { auto& state = ctx.state; const auto components = RdnaImageDimensionInfoFor(access.image.dimension).spatialComponents; @@ -558,9 +612,6 @@ SpirvBufferFormatInfo ImageConversionFormat(const ImageResource& image) { } void RequireConvertedUnormAccess(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, const SpirvBufferFormatInfo& info) { - if (access.mem.dataBits == 16u && info.byteSize != 2u) { - ctx.Fail(access.inst, info.type == SpirvFormatComponentType::Float ? "reads or writes a converted float image with 16-bit data, which is not implemented" : "reads or writes a converted unorm image with 16-bit data, which is not implemented"); - } for (std::uint32_t component = 0; component < 4u; component++) { const auto selector = (access.image.shaderSwizzle >> (component * 3u)) & 7u; if (selector >= 4u && selector - 4u >= info.componentCount && info.byteSize != 2u) { @@ -642,6 +693,19 @@ std::uint32_t UnpackImageTexel(SpirvValueEmitContext& ctx, const ImageEmitAccess return result; } +std::uint32_t LoadedTexelResult(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, std::uint32_t texel, IrTextureNumericClass numericClass) { + auto& state = ctx.state; + const auto unpacked = UnpackImageTexel(ctx, access, texel); + if (access.mem.dataBits != 16u || access.image.srgbDecode || access.image.conversionFormat == FmaskExpansion) { + return ResultVector(ctx, access, unpacked, numericClass, false, false); + } + const auto info = ImageConversionFormat(access.image); + if (info.format == IrBufferFormat::Invalid || (info.type != SpirvFormatComponentType::Unorm && info.type != SpirvFormatComponentType::Float)) { + return ResultVector(ctx, access, unpacked, numericClass, false, false); + } + return ResultVector(ctx, access, Unary(state, spv::OpBitcast, TypeF32Vector(state, 4), unpacked), IrTextureNumericClass::Float, false, false); +} + SpirvBufferFormatInfo PackedTexelFormat(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { const auto format = access.image.packedFormat; if (format == IrBufferFormat::Invalid || access.image.table != NoTable || access.image.depthBits || access.image.conversionFormat != IrBufferFormat::Invalid) { @@ -847,7 +911,7 @@ std::uint32_t StoreTexel(SpirvValueEmitContext& ctx, const ImageEmitAccess& acce if (float16 && component < storageFormat.componentCount && mem.dataBits != 16u) { values[component] = EmitFormatStoreComponent(state, storageFormat, component, values[component]); } - if (component < sint.componentCount) { + if (component < sint.componentCount && (mem.dataBits == 16u || sint.componentBits[component] < 32u)) { values[component] = mem.dataBits == 16u ? EmitD16StoreComponent(state, sint, component, values[component]) : EmitFormatStoreComponent(state, sint, component, values[component]); } } @@ -1027,7 +1091,7 @@ void EmitByReadOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { words[0] = Binary(state, spv::OpBitwiseOr, TypeU32(state), words[0], Select(state, TypeU32(state), inside, shifted, ConstantU32(state, 0))); continue; } - const auto texel = ResultVector(ctx, texelAccess, UnpackImageTexel(ctx, texelAccess, color), access.image.numericClass, false, false); + const auto texel = LoadedTexelResult(ctx, texelAccess, color, access.image.numericClass); for (std::uint32_t component = 0; component < components; ++component) { const auto value = state.module.AllocateId(); state.module.AddFunction(spv::OpCompositeExtract, TypeU32(state), value, texel, component); @@ -1071,7 +1135,7 @@ void EmitReadOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access) { if (access.mem.imagePacked) { return PackedImageTexel(ctx, access, color); } - return ResultVector(ctx, access, UnpackImageTexel(ctx, access, color), numericClass, false, false); + return LoadedTexelResult(ctx, access, color, numericClass); })); } @@ -1233,7 +1297,11 @@ SampleSetup MakeSampleSetup(SpirvValueEmitContext& ctx, const ImageEmitAccess& a if (ImageConversionFormat(access.image).type == SpirvFormatComponentType::Float) { ctx.Fail(access.inst, "samples or gathers a converted float image, which needs filtering in the shader and is not implemented"); } - const auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents, AddressDimension(access).coordinateComponents); + auto coord = CoordF32(ctx, access, layout.coord, dimensionInfo.coordinateComponents, AddressDimension(access).coordinateComponents); + if (access.image.layerRows) { + if (dref || access.inst.Opcode() != IrOpcode::ImageSampleRaw || (access.mem.imageSampleFlags != RdnaImageSampleFlagLevelZero && access.mem.imageSampleFlags != RdnaImageSampleFlagLod)) ctx.Fail(access.inst, "reads a 1D array laid out as rows of a 2D image other than by image_sample_lz or image_sample_l"); + coord = LayerRowsCoord(ctx, access, coord); + } return {dimensionInfo, layout, access.image.numericClass, dref, coord}; } @@ -1595,18 +1663,25 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con operandMask |= spv::ImageOperandsBiasMask; operands.push_back(AddressF32(ctx, access, setup.layout.bias)); } - if (setup.layout.clamp != NoImageComponent) { + const bool descriptorClamp = state.shaderImageMinLod && state.program.Info().images.at(mem.resource).table == NoTable; + if (descriptorClamp && state.registrationModules) ctx.Fail(access.inst, "applies its T#'s minimum LOD clamp in the shader, which registration modules do not implement"); + if (setup.layout.clamp != NoImageComponent || descriptorClamp) { const auto& capabilities = state.supportedCapabilities; if (std::find(capabilities.begin(), capabilities.end(), static_cast(spv::CapabilityMinLod)) == capabilities.end()) ctx.Fail(access.inst, "clamps its LOD, which needs the device's shaderResourceMinLod"); - const auto clamp = AddressF32(ctx, access, setup.layout.clamp); + const auto fmax = [&](std::uint32_t left, std::uint32_t right) { + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, TypeF32(state), value, GlslStd450(state), GLSLstd450FMax, left, right); + return value; + }; + const auto clamp = setup.layout.clamp != NoImageComponent ? AddressF32(ctx, access, setup.layout.clamp) : 0u; + const auto viewClamp = descriptorClamp ? Unary(state, spv::OpBitcast, TypeF32(state), state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::MinLodBase + mem.resource, 0u)) : 0u; if ((operandMask & spv::ImageOperandsLodMask) != 0u) { - const auto clamped = state.module.AllocateId(); - state.module.AddFunction(spv::OpExtInst, TypeF32(state), clamped, GlslStd450(state), GLSLstd450FMax, operands.back(), clamp); - operands.back() = clamped; + if (clamp != 0u) operands.back() = fmax(operands.back(), clamp); + if (viewClamp != 0u) operands.back() = Select(state, TypeF32(state), Binary(state, spv::OpFOrdGreaterThan, TypeBool(state), viewClamp, ZeroF32(state)), fmax(operands.back(), viewClamp), operands.back()); } else { state.module.EmitCapability(spv::CapabilityMinLod); operandMask |= spv::ImageOperandsMinLodMask; - operands.push_back(clamp); + operands.push_back(clamp == 0u ? viewClamp : viewClamp == 0u ? clamp : fmax(clamp, viewClamp)); } } const auto constantOffset = [&]() -> const IrValue* { @@ -1647,7 +1722,7 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con operands.insert(operands.end() - ((operandMask & spv::ImageOperandsMinLodMask) != 0u ? 1 : 0), offset); } - const auto emitSample = [&](std::uint32_t coord, std::uint32_t mask, const std::vector& arguments, std::uint32_t sampleOpcode) { + const auto emitDirectSample = [&](std::uint32_t coord, std::uint32_t mask, const std::vector& arguments, std::uint32_t sampleOpcode) { const auto sampled = MakeSampledImage(state, mem.resource, mem.sampler); const auto sample = state.module.AllocateId(); std::vector words = {sampleOpcode, resultType, sample, sampled, coord}; @@ -1661,8 +1736,20 @@ void EmitSampleOp(SpirvValueEmitContext& ctx, const ImageEmitAccess& access, con state.module.AddFunction(words); return sample; }; + const bool mirrors = MirrorsSampleIfUnnormalized(ctx, access, setup); + const auto emitSample = [&](std::uint32_t coord, std::uint32_t mask, const std::vector& arguments, std::uint32_t sampleOpcode) { + if (!mirrors) return emitDirectSample(coord, mask, arguments, sampleOpcode); + const auto axes = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::MirrorUnnormalizedBase + mem.sampler, 0u); + const auto mirrored = Binary(state, spv::OpINotEqual, TypeBool(state), axes, ConstantU32(state, 0u)); + return EmitValueIfElse(state, mirrored, resultType, [&] { + return emitDirectSample(MirroredUnnormalizedCoord(ctx, setup, coord, axes), mask, arguments, sampleOpcode); + }, [&] { + return emitDirectSample(coord, mask, arguments, sampleOpcode); + }); + }; std::uint32_t sample = 0; - const bool baseLevelIfUnnormalized = !setup.dref && SamplesBaseLevelIfUnnormalized(state.program.Info().samplers.at(mem.sampler)); + const bool alreadyBaseLevel = !hasOffset && opcode == spv::OpImageSampleExplicitLod && operandMask == spv::ImageOperandsLodMask && !HasFlag(mem, RdnaImageSampleFlagLod) && setup.layout.clamp == NoImageComponent; + const bool baseLevelIfUnnormalized = !setup.dref && !alreadyBaseLevel && SamplesBaseLevelIfUnnormalized(state.program.Info().samplers.at(mem.sampler)); if (baseLevelIfUnnormalized) { const auto selector = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::UnnormalizedBaseLevelBase + mem.sampler, 0u); const auto unnormalized = Binary(state, spv::OpINotEqual, TypeBool(state), selector, ConstantU32(state, 0u)); @@ -1981,6 +2068,47 @@ void EmitImageMode(SpirvValueEmitContext& ctx, const IrValue& inst, const ImageR } } +bool SameImageModeForInstruction(const IrValue& inst, const MemoryInfo& memory, const ImageResource& a, const ImageResource& b) { + if (a.resourceClass != b.resourceClass || a.dimension != b.dimension || a.numericClass != b.numericClass || + a.depthCompare != b.depthCompare || a.atomic != b.atomic || a.atomic64 != b.atomic64 || a.table != b.table || a.depthBits != b.depthBits) { + return false; + } + const auto opcode = inst.Opcode(); + if (ImageOpcodeInfoOf(opcode).access == ImageAccess::Atomic || opcode == IrOpcode::ImageQueryDimensions) { + return true; + } + if (opcode == IrOpcode::ImageQueryLod) { + const bool pointA = a.table != NoTable ? TablePointSampled(a) : (a.numericClass == IrTextureNumericClass::Sint || a.conversionFormat != IrBufferFormat::Invalid || a.depthBits); + const bool pointB = b.table != NoTable ? TablePointSampled(b) : (b.numericClass == IrTextureNumericClass::Sint || b.conversionFormat != IrBufferFormat::Invalid || b.depthBits); + return a.cube == b.cube && pointA == pointB && ImageConversionFormat(a).type == ImageConversionFormat(b).type; + } + if (opcode == IrOpcode::ImageRead) { + if (a.depthUnorm16 != b.depthUnorm16 || a.srgbDecode != b.srgbDecode) { + return false; + } + if (memory.imagePacked) { + return a.packedFormat == b.packedFormat && (a.conversionFormat != IrBufferFormat::Invalid) == (b.conversionFormat != IrBufferFormat::Invalid); + } + return a.conversionFormat == b.conversionFormat && (a.conversionFormat == IrBufferFormat::Invalid || a.shaderSwizzle == b.shaderSwizzle); + } + if (opcode == IrOpcode::ImageWrite) { + if (a.mipMode != b.mipMode) { + return false; + } + if (memory.imagePacked) { + return a.packedFormat == b.packedFormat && (a.conversionFormat != IrBufferFormat::Invalid) == (b.conversionFormat != IrBufferFormat::Invalid); + } + const auto formatForWrite = [&](const ImageResource& image) { + if (memory.dataBits != 16u && (image.conversionFormat == IrBufferFormat::Format32SInt || image.conversionFormat == IrBufferFormat::Format32_32SInt || image.conversionFormat == IrBufferFormat::Format32_32_32_32SInt)) { + return IrBufferFormat::Invalid; + } + return image.conversionFormat; + }; + return formatForWrite(a) == formatForWrite(b); + } + return false; +} + void EmitImage(SpirvValueEmitContext& ctx, const IrValue& inst) { auto& state = ctx.state; const auto& memory = ctx.Memory(inst); @@ -2037,10 +2165,23 @@ void EmitImage(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto invalid = tables || exits ? state.module.AllocateId() : merge; const auto undefined = returnsValue && !tables && !exits ? state.module.AllocateId() : 0u; std::vector labels(modes.size()); + std::vector emitLabel(modes.size(), false); std::vector words{spv::OpSwitch, selector, invalid}; for (std::uint32_t index = 0u; index < modes.size(); ++index) { if (base.table != NoTable && !TableModeEmitted(modes[index])) continue; - labels[index] = state.module.AllocateId(); + std::uint32_t reused = 0u; + for (std::uint32_t prev = 0u; prev < index; ++prev) { + if (emitLabel[prev] && SameImageModeForInstruction(inst, memory, modes[prev], modes[index])) { + reused = labels[prev]; + break; + } + } + if (reused != 0u) { + labels[index] = reused; + } else { + labels[index] = state.module.AllocateId(); + emitLabel[index] = true; + } words.insert(words.end(), {index, labels[index]}); } std::vector incoming; @@ -2051,7 +2192,7 @@ void EmitImage(SpirvValueEmitContext& ctx, const IrValue& inst) { state.module.AddFunction(spv::OpSelectionMerge, merge, spv::SelectionControlMaskNone); state.module.AddFunction(words); for (std::uint32_t index = 0u; index < modes.size(); ++index) { - if (labels[index] == 0u) continue; + if (!emitLabel[index]) continue; EmitLabel(state, labels[index]); emitMode(modes[index]); if (returnsValue) incoming.insert(incoming.end(), {ctx.Def(&inst), state.currentLabel}); diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvInstructionOperands.hpp b/core/shader/recompiler/SpirvBackend/src/SpirvInstructionOperands.hpp new file mode 100644 index 0000000000..0109edd90e --- /dev/null +++ b/core/shader/recompiler/SpirvBackend/src/SpirvInstructionOperands.hpp @@ -0,0 +1,231 @@ +#ifndef CORE_SHADER_RECOMPILER_SPIRVINSTRUCTIONOPERANDS_HPP +#define CORE_SHADER_RECOMPILER_SPIRVINSTRUCTIONOPERANDS_HPP + +#define SPV_ENABLE_UTILITY_CODE +#include +#undef SPV_ENABLE_UTILITY_CODE +#include +#include + +namespace ShaderRecompiler::SpirvInstructions { + +template +spv::Op Opcode(const TInstruction& instruction) { + return instruction.empty() ? spv::OpNop : static_cast(instruction[0] & 0xffffu); +} + +template +std::uint32_t Result(const TInstruction& instruction) { + if (instruction.empty()) return 0u; + bool result = false; + bool type = false; + spv::HasResultAndType(Opcode(instruction), &result, &type); + return result ? instruction.at(type ? 2u : 1u) : 0u; +} + +template +void VisitIdOperands(const TInstruction& instruction, std::uint32_t switchLiteralWords, const TVisitor& visit) { + const auto size = instruction.size(); + const auto from = [&](std::size_t first) { + for (auto index = first; index < size; ++index) visit(index); + }; + const auto only = [&](std::initializer_list indices) { + for (const auto index : indices) { + if (index < size) visit(index); + } + }; + constexpr std::uint32_t knownMemoryAccess = static_cast(spv::MemoryAccessVolatileMask) | static_cast(spv::MemoryAccessAlignedMask) | + static_cast(spv::MemoryAccessNontemporalMask) | static_cast(spv::MemoryAccessMakePointerAvailableMask) | + static_cast(spv::MemoryAccessMakePointerVisibleMask) | static_cast(spv::MemoryAccessNonPrivatePointerMask); + const auto memoryOperands = [&](std::size_t index) { + while (index < size) { + const auto mask = instruction[index++]; + if ((mask & ~knownMemoryAccess) != 0u) { + from(index); + return; + } + if ((mask & static_cast(spv::MemoryAccessAlignedMask)) != 0u) ++index; + if ((mask & static_cast(spv::MemoryAccessMakePointerAvailableMask)) != 0u && index < size) visit(index++); + if ((mask & static_cast(spv::MemoryAccessMakePointerVisibleMask)) != 0u && index < size) visit(index++); + } + }; + switch (Opcode(instruction)) { + case spv::OpNop: + case spv::OpCapability: + case spv::OpExtension: + case spv::OpExtInstImport: + case spv::OpMemoryModel: + case spv::OpSource: + case spv::OpSourceContinued: + case spv::OpSourceExtension: + case spv::OpString: + case spv::OpName: + case spv::OpMemberName: + case spv::OpModuleProcessed: + case spv::OpDecorate: + case spv::OpMemberDecorate: + case spv::OpDecorateString: + case spv::OpMemberDecorateString: + case spv::OpTypeVoid: + case spv::OpTypeBool: + case spv::OpTypeInt: + case spv::OpTypeFloat: + case spv::OpTypeSampler: + case spv::OpConstantTrue: + case spv::OpConstantFalse: + case spv::OpConstant: + case spv::OpConstantSampler: + case spv::OpConstantNull: + case spv::OpSpecConstantTrue: + case spv::OpSpecConstantFalse: + case spv::OpSpecConstant: + case spv::OpLabel: + case spv::OpNoLine: + case spv::OpReturn: + case spv::OpUnreachable: + case spv::OpKill: + case spv::OpFunctionEnd: + return; + case spv::OpTypeVector: + case spv::OpTypeMatrix: + case spv::OpTypeImage: + only({2}); + return; + case spv::OpTypePointer: + only({3}); + return; + case spv::OpTypeForwardPointer: + case spv::OpExecutionMode: + case spv::OpLine: + case spv::OpSelectionMerge: + only({1}); + return; + case spv::OpSpecConstantOp: + only({1}); + from(4); + return; + case spv::OpVariable: + case spv::OpFunction: + only({1, 4}); + return; + case spv::OpLoad: + only({1, 3}); + memoryOperands(4); + return; + case spv::OpStore: + case spv::OpCopyMemory: + only({1, 2}); + memoryOperands(3); + return; + case spv::OpCopyMemorySized: + only({1, 2, 3}); + memoryOperands(4); + return; + case spv::OpArrayLength: + case spv::OpCompositeExtract: + only({1, 3}); + return; + case spv::OpCompositeInsert: + case spv::OpVectorShuffle: + only({1, 3, 4}); + return; + case spv::OpExtInst: + only({1, 3}); + from(5); + return; + case spv::OpEntryPoint: { + only({2}); + std::size_t index = 3; + while (index < size) { + const auto word = instruction[index++]; + if ((word & 0xffu) == 0u || (word & 0xff00u) == 0u || (word & 0xff0000u) == 0u || (word & 0xff000000u) == 0u) break; + } + from(index); + return; + } + case spv::OpExecutionModeId: + case spv::OpDecorateId: + only({1}); + from(3); + return; + case spv::OpLoopMerge: + only({1, 2}); + return; + case spv::OpBranchConditional: + only({1, 2, 3}); + return; + case spv::OpSwitch: + only({1, 2}); + for (std::size_t index = 3; index + switchLiteralWords < size; index += switchLiteralWords + 1u) visit(index + switchLiteralWords); + return; + case spv::OpImageSampleImplicitLod: + case spv::OpImageSampleExplicitLod: + case spv::OpImageSampleProjImplicitLod: + case spv::OpImageSampleProjExplicitLod: + case spv::OpImageFetch: + case spv::OpImageRead: + case spv::OpImageSparseSampleImplicitLod: + case spv::OpImageSparseSampleExplicitLod: + case spv::OpImageSparseSampleProjImplicitLod: + case spv::OpImageSparseSampleProjExplicitLod: + case spv::OpImageSparseFetch: + case spv::OpImageSparseRead: + only({1, 3, 4}); + from(6); + return; + case spv::OpImageSampleDrefImplicitLod: + case spv::OpImageSampleDrefExplicitLod: + case spv::OpImageSampleProjDrefImplicitLod: + case spv::OpImageSampleProjDrefExplicitLod: + case spv::OpImageGather: + case spv::OpImageDrefGather: + case spv::OpImageSparseSampleDrefImplicitLod: + case spv::OpImageSparseSampleDrefExplicitLod: + case spv::OpImageSparseSampleProjDrefImplicitLod: + case spv::OpImageSparseSampleProjDrefExplicitLod: + case spv::OpImageSparseGather: + case spv::OpImageSparseDrefGather: + only({1, 3, 4, 5}); + from(7); + return; + case spv::OpImageWrite: + only({1, 2, 3}); + from(5); + return; + case spv::OpGroupIAdd: + case spv::OpGroupFAdd: + case spv::OpGroupFMin: + case spv::OpGroupUMin: + case spv::OpGroupSMin: + case spv::OpGroupFMax: + case spv::OpGroupUMax: + case spv::OpGroupSMax: + case spv::OpGroupNonUniformBallotBitCount: + case spv::OpGroupNonUniformIAdd: + case spv::OpGroupNonUniformFAdd: + case spv::OpGroupNonUniformIMul: + case spv::OpGroupNonUniformFMul: + case spv::OpGroupNonUniformSMin: + case spv::OpGroupNonUniformUMin: + case spv::OpGroupNonUniformFMin: + case spv::OpGroupNonUniformSMax: + case spv::OpGroupNonUniformUMax: + case spv::OpGroupNonUniformFMax: + case spv::OpGroupNonUniformBitwiseAnd: + case spv::OpGroupNonUniformBitwiseOr: + case spv::OpGroupNonUniformBitwiseXor: + case spv::OpGroupNonUniformLogicalAnd: + case spv::OpGroupNonUniformLogicalOr: + case spv::OpGroupNonUniformLogicalXor: + only({1, 3}); + from(5); + return; + default: + from(1); + return; + } +} + +} + +#endif diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp index e2f95214ee..9a2b7a33c8 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvBufferAccess.cpp @@ -199,6 +199,11 @@ MemoryResourceAccess PrepareMemoryResourceAccess(SpirvEmitterState& state, const access.length = state.gdsLength; return access; case ResourceKind::Scratch: + if (ScratchPoolActive(state)) { + access.objectPointer = state.scratchPoolVariable; + access.length = ConstantU32(state, state.program.Info().scratchDwords); + return access; + } if (state.scratchVariable.at(state.laneHalf) == 0) { FailEmit("scratch storage was not prepared before function emission"); } @@ -260,6 +265,9 @@ std::uint32_t EmitMemoryElementPointer(SpirvEmitterState& state, const MemoryRes if (access.kind == ResourceKind::Lds && LdsInDeviceMemory(state)) { return EmitStorageBufferElementPointer(state, access, EmitAddU32(state, state.ldsBufferBase, index), TypeStorageBufferElementPointer(state)); } + if (access.kind == ResourceKind::Scratch && ScratchPoolActive(state)) { + return EmitScratchPoolElementPointer(state, index); + } if (access.kind == ResourceKind::Lds || access.kind == ResourceKind::Scratch) { const auto pointer = state.module.AllocateId(); const std::uint32_t storageClass = InFunctionStorage(state, access.kind) ? spv::StorageClassFunction : spv::StorageClassWorkgroup; diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp index 27ee033600..e7cc195fa0 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvEmitterHelpers.cpp @@ -201,12 +201,12 @@ void DefineInputs(SpirvEmitterState& state) { addBuiltin(StageInputKind::WorkgroupId, 3u, "gl_WorkGroupID"); } } - if (state.requirements.ldsLock && std::none_of(state.inputs.begin(), state.inputs.end(), [](const SpirvInputBinding& input) { + if ((state.requirements.ldsLock || ScratchPoolActive(state)) && std::none_of(state.inputs.begin(), state.inputs.end(), [](const SpirvInputBinding& input) { return input.kind == StageInputKind::LocalInvocationIndex; })) { state.inputs.push_back(SpirvInputBinding {{StageInputKind::LocalInvocationIndex, 0u, 1u, "gl_LocalInvocationIndex", false}}); } - if (LdsInDeviceMemory(state)) { + if (LdsInDeviceMemory(state) || ScratchPoolActive(state)) { if (std::none_of(state.inputs.begin(), state.inputs.end(), [](const SpirvInputBinding& input) { return input.kind == StageInputKind::WorkgroupId; })) { @@ -457,7 +457,12 @@ void DefineOutputs(SpirvEmitterState& state) { BuiltIn(state.clipDistanceVariable, F32ArrayType(state, clipDistanceCount), "gl_ClipDistance", spv::BuiltInClipDistance); } if (state.program.Resources().stage == IrShaderStage::Pixel && state.inputInfo.pixel != nullptr && state.inputInfo.pixel->psAlphaToCoverage) { - if (state.inputInfo.pixel->psSampleMaskExportEnable) throw std::runtime_error("alpha-to-coverage with a sample-mask export is unsupported"); + if (state.inputInfo.pixel->psSampleMaskExportEnable) { + for (auto* variable : {&state.alphaCoverageVariable, &state.exportedCoverageVariable}) { + *variable = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassPrivate, TypeI32(state)), spv::StorageClassPrivate); + if (state.spirvVersion >= 0x00010400u) state.interfaceVariables.push_back(*variable); + } + } BuiltIn(state.sampleMaskVariable, SampleMaskArrayType(state), "gl_SampleMask", spv::BuiltInSampleMask); } if (state.program.Resources().stage == IrShaderStage::Pixel && state.inputInfo.pixel != nullptr && state.inputInfo.pixel->QuadSampleMaskPartial()) { @@ -605,6 +610,22 @@ void DefineDescriptors(SpirvEmitterState& state) { state.module.AddAnnotation(spv::OpDecorate, state.ldsBufferVariable, spv::DecorationCoherent); if (state.spirvVersion >= 0x00010400u) state.interfaceVariables.push_back(state.ldsBufferVariable); } + DefineScratchPool(state); +} + +void DefineScratchPool(SpirvEmitterState& state) { + if (!ScratchPoolActive(state)) return; + state.scratchPoolVariable = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassStorageBuffer, StorageBufferBlockType(state)), spv::StorageClassStorageBuffer); + state.module.AddName(state.scratchPoolVariable, "scratch_pool"); + state.module.AddAnnotation(spv::OpDecorate, state.scratchPoolVariable, spv::DecorationDescriptorSet, ScratchPoolDescriptorSet); + state.module.AddAnnotation(spv::OpDecorate, state.scratchPoolVariable, spv::DecorationBinding, 0u); + state.module.AddAnnotation(spv::OpDecorate, state.scratchPoolVariable, spv::DecorationCoherent); + state.scratchPoolSlotVariable = state.module.DefineGlobalVariable(TypePointer(state, spv::StorageClassWorkgroup, TypeU32(state)), spv::StorageClassWorkgroup); + state.module.AddName(state.scratchPoolSlotVariable, "scratch_pool_slot"); + if (state.spirvVersion >= 0x00010400u) { + state.interfaceVariables.push_back(state.scratchPoolVariable); + state.interfaceVariables.push_back(state.scratchPoolSlotVariable); + } } } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp index cfe05d771d..009d1cddde 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp @@ -940,6 +940,18 @@ std::uint32_t Atomic32(SpirvValueEmitContext& ctx, const IrValue& inst, const Me std::uint32_t BufferAtomic64(SpirvValueEmitContext& ctx, const IrValue& inst) { auto& state = ctx.state; static_cast(BufferMemory(ctx, inst)); + if (state.requirements.bufferInt64AtomicsSplit) { + if (!IsSplittableBufferAtomic64(&inst)) ctx.Fail(inst, "64-bit buffer atomic needs shaderBufferInt64Atomics"); + const auto data = Unary(state, spv::OpBitcast, TypeScalarU64(state), ctx.Arg(inst, inst.ArgumentCount() - 2u)); + const auto low = Unary(state, spv::OpUConvert, TypeU32(state), data); + const auto high = Unary(state, spv::OpUConvert, TypeU32(state), Binary(state, spv::OpShiftRightLogical, TypeScalarU64(state), data, BdaConstant(state, 32u))); + std::uint32_t index = 0u; + EmitRuntimeBufferAtomicHalves(ctx, inst, ActiveArgument(ctx, inst), [&](std::uint32_t pointer) { + state.module.AddFunction(SpirvAtomicOpcode(inst.Opcode()), TypeU32(state), state.module.AllocateId(), pointer, ConstantU32(state, spv::ScopeDevice), ConstantU32(state, spv::MemorySemanticsMaskNone), index++ == 0u ? low : high); + }); + EmitDeviceAtomicMemoryBarrier(state); + return ConstantU64(state, 0u); + } return EmitRuntimeBufferAtomic(ctx, inst, 64u, ActiveArgument(ctx, inst), [&](std::uint32_t pointer) { const auto value = Unary(state, spv::OpBitcast, TypeScalarU64(state), ctx.Arg(inst, inst.ArgumentCount() - 2u)); const auto old = state.module.AllocateId(); @@ -1568,7 +1580,15 @@ std::uint32_t EmitReadConst(SpirvValueEmitContext& ctx, const IrValue& inst) { EmitIfCondition(state, poisoned, [&] { const auto record = Binary(state, spv::OpIAdd, u32, ConstantU32(state, records), Binary(state, spv::OpIMul, u32, Binary(state, spv::OpISub, u32, flag, ConstantU32(state, 1u)), ConstantU32(state, 3u))); const auto stage = ConstantU32(state, static_cast(resources.stage) << BdaAbi::SrtGuardStageShift); - state.module.AddFunction(spv::OpAtomicStore, BdaWord(state, state.faultBufferVariable, ConstantU32(state, BdaAbi::SrtGuardWord)), ConstantU32(state, spv::ScopeDevice), ConstantU32(state, spv::MemorySemanticsMaskNone), Binary(state, spv::OpBitwiseOr, u32, record, stage)); + const auto word = BdaWord(state, state.faultBufferVariable, ConstantU32(state, BdaAbi::SrtGuardWord)); + const auto scope = ConstantU32(state, spv::ScopeDevice); + const auto relaxed = ConstantU32(state, spv::MemorySemanticsMaskNone); + const auto value = Binary(state, spv::OpBitwiseOr, u32, record, stage); + if (state.bdaStopsInvocations) { + state.module.AddFunction(spv::OpAtomicStore, word, scope, relaxed, value); + } else { + state.module.AddFunction(spv::OpAtomicCompareExchange, u32, state.module.AllocateId(), word, scope, relaxed, relaxed, value, ConstantU32(state, 0u)); + } }); StopBdaInvocationIf(state, poisoned); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp index fc51264bfa..6164c79c5b 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp @@ -338,7 +338,10 @@ void DefineModule(SpirvEmitterState& state) { if (state.requirements.functionLds && !state.pixelFunctionLdsBuffer) { state.ldsVariable = state.module.AllocateId(); } - if (state.requirements.functionScratch) { + if (ScratchPoolActive(state)) { + state.scratchPoolCandidateVariable = state.module.AllocateId(); + state.scratchPoolWonVariable = state.module.AllocateId(); + } else if (state.requirements.functionScratch) { for (std::uint32_t half = 0; half < state.laneCount; half++) { state.scratchVariable.at(half) = state.module.AllocateId(); } @@ -538,7 +541,10 @@ void DefineModule(SpirvEmitterState& state) { if (state.requirements.functionLds && !state.pixelFunctionLdsBuffer) { state.module.AddName(state.ldsVariable, "lds_dwords"); } - if (state.requirements.functionScratch) { + if (ScratchPoolActive(state)) { + state.module.AddName(state.scratchPoolCandidateVariable, "scratch_pool_candidate"); + state.module.AddName(state.scratchPoolWonVariable, "scratch_pool_won"); + } else if (state.requirements.functionScratch) { for (std::uint32_t half = 0; half < state.laneCount; half++) { state.module.AddName(state.scratchVariable.at(half), "scratch_dwords"); } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvScratchPool.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvScratchPool.cpp new file mode 100644 index 0000000000..cf40d62fc2 --- /dev/null +++ b/core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvScratchPool.cpp @@ -0,0 +1,164 @@ +#include "SpirvBackend/SpirvMemory/SpirvBufferAccess.hpp" +#include "SpirvBackend/SpirvAnalysis.hpp" +#include "SpirvBackend/SpirvEmitterHelpers.hpp" +#include "SpirvBackend/SpirvMemory/SpirvTypes.hpp" +#include "SpirvBackend/SpirvEmitterInstructions.hpp" +#include "SpirvBackend/SpirvMemory/SpirvConstants.hpp" +#include +#include + +namespace ShaderRecompiler { +namespace { + +constexpr std::uint32_t HeaderWordsPerSlot = 2u; + +std::uint32_t PoolWord(SpirvEmitterState& state, std::uint32_t index) { + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer, state.scratchPoolVariable, ConstantU32(state, 0u), index); + return pointer; +} + +std::uint32_t LoadU32(SpirvEmitterState& state, std::uint32_t pointer) { + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, TypeU32(state), value, pointer); + return value; +} + +std::uint32_t InvocationsPerGroup(const SpirvEmitterState& state) { + const auto size = WorkgroupLocalSize(state, *ShaderWorkgroupInput(state)); + return size[0] * size[1] * size[2]; +} + +std::uint32_t LinearWorkgroupId(SpirvEmitterState& state) { + const auto groups = [&](std::uint32_t component) { + const auto pointer = state.module.AllocateId(); + const auto value = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassInput, TypeU32(state)), pointer, state.numWorkgroupsVariable, ConstantU32(state, component)); + state.module.AddFunction(spv::OpLoad, TypeU32(state), value, pointer); + return value; + }; + const auto group = [&](std::uint32_t component) { return EmitInputComponentU32(state, StageInputKind::WorkgroupId, component); }; + const auto z = group(2u); + const auto countY = groups(1u); + const auto y = group(1u); + const auto plane = EmitAddU32(state, EmitBinaryU32(state, spv::OpIMul, z, countY), y); + const auto countX = groups(0u); + return EmitAddU32(state, EmitBinaryU32(state, spv::OpIMul, plane, countX), group(0u)); +} + +} + +bool ScratchPoolActive(const SpirvEmitterState& state) { + return state.requirements.functionScratch && state.program.Resources().stage == IrShaderStage::Compute; +} + +std::uint32_t ScratchPoolSlotDwords(const SpirvEmitterState& state) { + const auto* workgroup = ShaderWorkgroupInput(state); + if (workgroup == nullptr) FailEmit("scratch pool needs a compute workgroup size"); + const auto size = WorkgroupLocalSize(state, *workgroup); + const auto invocations = size[0] * size[1] * size[2]; + const auto lanes = state.laneCount == 2u ? (invocations - 1u) + ((invocations - 1u) & ~31u) + 33u : invocations; + return lanes * state.program.Info().scratchDwords; +} + +void EmitScratchPoolAcquire(SpirvEmitterState& state) { + if (!ScratchPoolActive(state)) return; + if (state.scratchPoolVariable == 0u || state.numWorkgroupsVariable == 0u || state.scratchPoolCandidateVariable == 0u || state.scratchPoolWonVariable == 0u) { + FailEmit("scratch pool was not declared before function emission"); + } + const auto device = ConstantU32(state, spv::ScopeDevice); + const auto acquire = ConstantU32(state, spv::MemorySemanticsAcquireMask | spv::MemorySemanticsUniformMemoryMask); + const auto length = state.module.AllocateId(); + state.module.AddFunction(spv::OpArrayLength, TypeU32(state), length, state.scratchPoolVariable, 0u); + const auto slotDwords = ScratchPoolSlotDwords(state); + const auto slots = EmitBinaryU32(state, spv::OpUDiv, length, ConstantU32(state, slotDwords + HeaderWordsPerSlot)); + state.scratchPoolSlotCount = slots; + state.laneHalf = 0u; + const auto first = state.module.AllocateId(); + state.module.AddFunction(spv::OpIEqual, TypeBool(state), first, EmitLocalInvocationIndex(state), ConstantU32(state, 0u)); + const auto linear = LinearWorkgroupId(state); + EmitIfCondition(state, first, [&] { + state.module.AddFunction(spv::OpStore, state.scratchPoolCandidateVariable, EmitBinaryU32(state, spv::OpUMod, linear, slots)); + state.module.AddFunction(spv::OpStore, state.scratchPoolWonVariable, ConstantU32(state, 0u)); + const auto header = state.module.AllocateId(); + const auto body = state.module.AllocateId(); + const auto continuation = state.module.AllocateId(); + const auto merge = state.module.AllocateId(); + state.module.AddFunction(spv::OpBranch, header); + EmitLabel(state, header); + const auto won = LoadU32(state, state.scratchPoolWonVariable); + const auto pending = state.module.AllocateId(); + state.module.AddFunction(spv::OpIEqual, TypeBool(state), pending, won, ConstantU32(state, 0u)); + state.module.AddFunction(spv::OpLoopMerge, merge, continuation, spv::LoopControlMaskNone); + state.module.AddFunction(spv::OpBranchConditional, pending, body, merge); + EmitLabel(state, body); + const auto candidate = LoadU32(state, state.scratchPoolCandidateVariable); + const auto observed = state.module.AllocateId(); + state.module.AddFunction(spv::OpAtomicLoad, TypeU32(state), observed, PoolWord(state, candidate), device, ConstantU32(state, spv::MemorySemanticsMaskNone)); + EmitIfCondition(state, EmitCompareU32Constant(state, spv::OpIEqual, observed, 0u), [&] { + const auto previous = state.module.AllocateId(); + state.module.AddFunction(spv::OpAtomicCompareExchange, TypeU32(state), previous, PoolWord(state, candidate), device, acquire, acquire, ConstantU32(state, 1u), ConstantU32(state, 0u)); + state.module.AddFunction(spv::OpStore, state.scratchPoolWonVariable, Select(state, TypeU32(state), EmitCompareU32Constant(state, spv::OpIEqual, previous, 0u), ConstantU32(state, 1u), ConstantU32(state, 0u))); + }); + const auto acquired = EmitCompareU32Constant(state, spv::OpIEqual, LoadU32(state, state.scratchPoolWonVariable), 1u); + const auto next = EmitAddU32(state, candidate, ConstantU32(state, 1u)); + const auto atEnd = state.module.AllocateId(); + state.module.AddFunction(spv::OpIEqual, TypeBool(state), atEnd, next, slots); + const auto wrapped = Select(state, TypeU32(state), atEnd, ConstantU32(state, 0u), next); + state.module.AddFunction(spv::OpStore, state.scratchPoolCandidateVariable, Select(state, TypeU32(state), acquired, candidate, wrapped)); + state.module.AddFunction(spv::OpBranch, continuation); + EmitLabel(state, continuation); + state.module.AddFunction(spv::OpBranch, header); + EmitLabel(state, merge); + const auto slot = LoadU32(state, state.scratchPoolCandidateVariable); + state.module.AddFunction(spv::OpAtomicStore, PoolWord(state, EmitAddU32(state, slots, slot)), device, ConstantU32(state, 0u), ConstantU32(state, InvocationsPerGroup(state))); + state.module.AddFunction(spv::OpStore, state.scratchPoolSlotVariable, slot); + }); + state.module.AddFunction(spv::OpControlBarrier, ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::ScopeWorkgroup), ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsUniformMemoryMask | spv::MemorySemanticsWorkgroupMemoryMask)); + state.scratchPoolSlot = LoadU32(state, state.scratchPoolSlotVariable); + const auto header = EmitBinaryU32(state, spv::OpIMul, slots, ConstantU32(state, HeaderWordsPerSlot)); + const auto slotBase = EmitAddU32(state, header, EmitBinaryU32(state, spv::OpIMul, state.scratchPoolSlot, ConstantU32(state, slotDwords))); + for (std::uint32_t half = 0; half < state.laneCount; half++) { + state.laneHalf = half; + state.scratchPoolLaneBase.at(half) = EmitAddU32(state, slotBase, EmitBinaryU32(state, spv::OpIMul, EmitLocalInvocationIndex(state), ConstantU32(state, state.program.Info().scratchDwords))); + } + state.laneHalf = 0u; +} + +std::uint32_t EmitScratchPoolElementPointer(SpirvEmitterState& state, std::uint32_t index) { + if (state.scratchPoolLaneBase.at(state.laneHalf) == 0u) FailEmit("scratch pool slot was not acquired before function emission"); + const auto pointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpAccessChain, TypeStorageBufferElementPointer(state), pointer, state.scratchPoolVariable, ConstantU32(state, 0u), EmitAddU32(state, state.scratchPoolLaneBase.at(state.laneHalf), index)); + return pointer; +} + +void EmitReturnReleasingScratch(SpirvEmitterState& state) { + if (ScratchPoolActive(state)) { + const auto device = ConstantU32(state, spv::ScopeDevice); + const auto previous = state.module.AllocateId(); + state.module.AddFunction(spv::OpAtomicIDecrement, TypeU32(state), previous, PoolWord(state, EmitAddU32(state, state.scratchPoolSlotCount, state.scratchPoolSlot)), device, ConstantU32(state, spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsUniformMemoryMask)); + EmitIfCondition(state, EmitCompareU32Constant(state, spv::OpIEqual, previous, 1u), [&] { + state.module.AddFunction(spv::OpAtomicStore, PoolWord(state, state.scratchPoolSlot), device, ConstantU32(state, spv::MemorySemanticsReleaseMask | spv::MemorySemanticsUniformMemoryMask), ConstantU32(state, 0u)); + }); + state.scratchPoolReturns++; + } + state.module.AddFunction(spv::OpReturn); +} + +void VerifyScratchPoolReturns(const SpirvEmitterState& state, const std::vector& words) { + if (!ScratchPoolActive(state)) return; + std::uint32_t returns = 0u; + bool inMain = false; + for (std::size_t cursor = 5u; cursor < words.size();) { + const auto count = words[cursor] >> 16u; + const auto op = words[cursor] & 0xffffu; + if (count == 0u) FailEmit("malformed SPIR-V while checking scratch pool releases"); + if (op == spv::OpFunction) inMain = words[cursor + 2u] == state.mainFunc; + else if (op == spv::OpFunctionEnd) inMain = false; + else if (inMain && op == spv::OpReturn) returns++; + cursor += count; + } + if (returns != state.scratchPoolReturns) FailEmit("a compute exit does not release its scratch pool slot"); +} + +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp index 44beabfca9..e0628730b3 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvModuleEmitter.cpp @@ -248,6 +248,10 @@ std::uint32_t EmitBuiltinU32(SpirvEmitterState& state, StageInputKind kind, std: const auto bits = state.module.AllocateId(); state.module.AddFunction(spv::OpLoad, TypeI32(state), value, variable); state.module.AddFunction(spv::OpBitcast, TypeU32(state), bits, value); + if (kind == StageInputKind::Layer) { + const auto base = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::MeshLayerBase, 0u); + return EmitBinaryU32(state, spv::OpIAdd, bits, base); + } return kind == StageInputKind::SampleId ? GuestSampleIndex(state, bits) : bits; } if (kind == StageInputKind::SampleMask) { @@ -453,7 +457,7 @@ std::uint32_t PackExport(SpirvEmitterState& state, IrBufferFormat format, std::u return packed; } -std::uint32_t SelectExportPacking(SpirvEmitterState& state, std::uint32_t target, std::uint32_t color) { +std::uint32_t SelectExportPacking(SpirvEmitterState& state, std::uint32_t target, std::uint32_t color, bool half) { const auto packing = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ExportPackingBase + target, static_cast(ColorExportPacking::None)); const auto unorm = EmitCompareU32Constant(state, spv::OpIEqual, packing, static_cast(ColorExportPacking::Unorm10_11_11)); const auto floating = EmitCompareU32Constant(state, spv::OpIEqual, packing, static_cast(ColorExportPacking::Float11_11_10)); @@ -461,10 +465,16 @@ std::uint32_t SelectExportPacking(SpirvEmitterState& state, std::uint32_t target const auto wide = Select(state, TypeU32(state), floating, PackExport(state, IrBufferFormat::Format10_11_11Float, color), PackExport(state, IrBufferFormat::Format11_11_10UNorm, color)); const auto word = Select(state, TypeU32(state), small, PackExport(state, IrBufferFormat::Format4_4_4_4UNorm, color), wide); const auto packed = Binary(state, spv::OpLogicalOr, TypeBool(state), Binary(state, spv::OpLogicalOr, TypeBool(state), unorm, floating), small); + const auto limit = half ? Select(state, TypeU32(state), EmitCompareU32Constant(state, spv::OpIEqual, packing, static_cast(ColorExportPacking::UintClamp8)), ConstantU32(state, 0xffu), Select(state, TypeU32(state), EmitCompareU32Constant(state, spv::OpIEqual, packing, static_cast(ColorExportPacking::UintClamp16)), ConstantU32(state, 0xffffu), ConstantU32(state, 0xffffffffu))) : 0u; std::array components{}; for (std::uint32_t component = 0; component < components.size(); ++component) { - const auto unpacked = state.module.AllocateId(); + auto unpacked = state.module.AllocateId(); state.module.AddFunction(spv::OpCompositeExtract, TypeF32(state), unpacked, color, component); + if (half) { + const auto clamped = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, TypeU32(state), clamped, GlslStd450(state), GLSLstd450UMin, Unary(state, spv::OpBitcast, TypeU32(state), unpacked), limit); + unpacked = Unary(state, spv::OpBitcast, TypeF32(state), clamped); + } components[component] = Select(state, TypeF32(state), packed, component == 0u ? Unary(state, spv::OpBitcast, TypeF32(state), word) : ConstantF32(state, 0u), unpacked); } const auto result = state.module.AllocateId(); @@ -599,6 +609,17 @@ void StoreCoverage(SpirvEmitterState& state, std::uint32_t mask) { state.module.AddFunction(spv::OpStore, pointer, mask); } +void StoreCombinedCoverage(SpirvEmitterState& state, std::uint32_t coverage, std::uint32_t own, std::uint32_t other) { + if (own == 0u) { + StoreCoverage(state, coverage); + return; + } + state.module.AddFunction(spv::OpStore, own, coverage); + const auto loaded = state.module.AllocateId(); + state.module.AddFunction(spv::OpLoad, TypeI32(state), loaded, other); + StoreCoverage(state, Binary(state, spv::OpBitwiseAnd, TypeI32(state), coverage, loaded)); +} + void StoreDitheredAlphaCoverage(SpirvEmitterState& state, std::uint32_t color) { constexpr std::array twoSampleMasks{{ 0x00000000u, 0x02000002u, 0x01000201u, 0x01020201u, 0x01020101u, 0x03020103u, 0x03010303u, 0x03030303u, @@ -640,7 +661,7 @@ void StoreDitheredAlphaCoverage(SpirvEmitterState& state, std::uint32_t color) { const auto pixel = Binary(state, spv::OpBitwiseOr, TypeU32(state), Binary(state, spv::OpShiftLeftLogical, TypeU32(state), parity[0], ConstantU32(state, 1u)), parity[1]); const auto shift = Binary(state, spv::OpShiftLeftLogical, TypeU32(state), pixel, ConstantU32(state, 3u)); const auto mask = Binary(state, spv::OpBitwiseAnd, TypeU32(state), Binary(state, spv::OpShiftRightLogical, TypeU32(state), packed, shift), ConstantU32(state, 255u)); - StoreCoverage(state, Unary(state, spv::OpBitcast, TypeI32(state), mask)); + StoreCombinedCoverage(state, Unary(state, spv::OpBitcast, TypeI32(state), mask), state.alphaCoverageVariable, state.exportedCoverageVariable); } void StoreAlphaCoverage(SpirvEmitterState& state, std::uint32_t color) { @@ -654,7 +675,7 @@ void StoreAlphaCoverage(SpirvEmitterState& state, std::uint32_t color) { state.module.AddFunction(spv::OpFUnordLessThan, TypeBool(state), below, alpha, ConstantF32(state, 0x3f000000u)); const auto mask = state.module.AllocateId(); state.module.AddFunction(spv::OpSelect, TypeI32(state), mask, below, ConstantI32(state, 0), ConstantI32(state, -1)); - StoreCoverage(state, mask); + StoreCombinedCoverage(state, mask, state.alphaCoverageVariable, state.exportedCoverageVariable); } std::uint32_t ExportRawComponent(SpirvValueEmitContext& ctx, std::uint32_t vector, std::uint32_t component) { @@ -663,11 +684,30 @@ std::uint32_t ExportRawComponent(SpirvValueEmitContext& ctx, std::uint32_t vecto return value; } +std::uint32_t WidenedHalfWord(SpirvEmitterState& state, std::uint32_t half) { + const auto u32 = TypeU32(state); + const auto op = [&](spv::Op opcode, std::uint32_t lhs, std::uint32_t rhs) { return Binary(state, opcode, u32, lhs, rhs); }; + const auto is = [&](std::uint32_t value, std::uint32_t constant) { return Binary(state, spv::OpIEqual, TypeBool(state), value, ConstantU32(state, constant)); }; + const auto sign = op(spv::OpShiftLeftLogical, op(spv::OpBitwiseAnd, half, ConstantU32(state, 0x8000u)), ConstantU32(state, 16u)); + const auto exponent = op(spv::OpBitwiseAnd, op(spv::OpShiftRightLogical, half, ConstantU32(state, 10u)), ConstantU32(state, 0x1fu)); + const auto mantissa = op(spv::OpBitwiseAnd, half, ConstantU32(state, 0x3ffu)); + const auto fraction = op(spv::OpShiftLeftLogical, mantissa, ConstantU32(state, 13u)); + const auto normal = op(spv::OpBitwiseOr, op(spv::OpShiftLeftLogical, op(spv::OpIAdd, exponent, ConstantU32(state, 127u - 15u)), ConstantU32(state, 23u)), fraction); + const auto quiet = Select(state, u32, is(mantissa, 0u), ConstantU32(state, 0u), ConstantU32(state, 0x400000u)); + const auto special = op(spv::OpBitwiseOr, op(spv::OpBitwiseOr, ConstantU32(state, 0x7f800000u), fraction), quiet); + const auto msb = state.module.AllocateId(); + state.module.AddFunction(spv::OpExtInst, u32, msb, GlslStd450(state), GLSLstd450FindUMsb, op(spv::OpBitwiseOr, mantissa, ConstantU32(state, 1u))); + const auto subnormal = op(spv::OpBitwiseOr, op(spv::OpShiftLeftLogical, op(spv::OpIAdd, msb, ConstantU32(state, 127u - 24u)), ConstantU32(state, 23u)), op(spv::OpBitwiseAnd, op(spv::OpShiftLeftLogical, mantissa, op(spv::OpISub, ConstantU32(state, 23u), msb)), ConstantU32(state, 0x7fffffu))); + const auto small = Select(state, u32, is(mantissa, 0u), ConstantU32(state, 0u), subnormal); + return op(spv::OpBitwiseOr, sign, Select(state, u32, is(exponent, 0u), small, Select(state, u32, is(exponent, 0x1fu), special, normal))); +} + std::uint32_t ExportVector(SpirvValueEmitContext& ctx, std::uint32_t data, const ExportInfo& exp, bool uintOutput) { auto& state = ctx.state; if (exp.compr && !uintOutput) { const auto mode = MrtOutputMode(state, exp); const auto unpack = mode == 5u ? GLSLstd450UnpackUnorm2x16 : mode == 6u ? GLSLstd450UnpackSnorm2x16 : GLSLstd450UnpackHalf2x16; + const auto words = mode == 4u ? EmitCompareU32Constant(state, spv::OpUGreaterThanEqual, state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::ExportPackingBase + exp.index, static_cast(ColorExportPacking::None)), static_cast(ColorExportPacking::Uint)) : 0u; std::array f32 {ConstantF32(state, 0u), ConstantF32(state, 0u), ConstantF32(state, 0u), ConstantF32(state, 0x3f800000u)}; for (std::uint32_t pair = 0; pair < 2u; pair++) { if ((exp.en & (3u << (pair * 2u))) == 0u) { @@ -682,6 +722,11 @@ std::uint32_t ExportVector(SpirvValueEmitContext& ctx, std::uint32_t data, const if (((exp.en >> component) & 1u) != 0u) { f32.at(component) = state.module.AllocateId(); state.module.AddFunction(spv::OpCompositeExtract, TypeF32(state), f32.at(component), unpacked, lane); + if (words != 0u) { + const auto half = state.module.AllocateId(); + state.module.AddFunction(spv::OpBitFieldUExtract, TypeU32(state), half, packed, ConstantU32(state, lane * 16u), ConstantU32(state, 16u)); + f32.at(component) = Select(state, TypeF32(state), words, Unary(state, spv::OpBitcast, TypeF32(state), WidenedHalfWord(state, half)), f32.at(component)); + } } } } @@ -1451,7 +1496,19 @@ void EmitMeshEntryPoint(SpirvEmitterState& state) { const auto layer = MeshLoad(state, output.meshDataVariable, spv::StorageClassWorkgroup, TypeU32(state), vertex.at(state.inputInfo.vertex->mesh.provokingVertex)); const auto pointer = state.module.AllocateId(); state.module.AddFunction(spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeU32(state)), pointer, output.variableId, index, ConstantU32(state, 0u)); - state.module.AddFunction(spv::OpStore, pointer, layer); + const auto base = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::MeshLayerBase, 0u); + const auto count = state.module.SpecializationConstant(TypeU32(state), PipelineSpecialization::MeshLayerCount, 0xffffffffu); + const auto relative = EmitBinaryU32(state, spv::OpISub, layer, base); + const auto inside = state.module.AllocateId(); + const auto outside = state.module.AllocateId(); + const auto selected = state.module.AllocateId(); + const auto discarded = state.module.AllocateId(); + state.module.AddFunction(spv::OpULessThan, TypeBool(state), inside, relative, count); + state.module.AddFunction(spv::OpLogicalNot, TypeBool(state), outside, inside); + state.module.AddFunction(spv::OpSelect, TypeU32(state), selected, inside, relative, ConstantU32(state, 0u)); + state.module.AddFunction(spv::OpLogicalOr, TypeBool(state), discarded, culled, outside); + state.module.AddFunction(spv::OpStore, MeshElement(state, state.meshCull, spv::StorageClassOutput, TypeBool(state), index), discarded); + state.module.AddFunction(spv::OpStore, pointer, selected); } if (emulating) { const auto corners = state.module.Type(spv::OpTypeArray, TypeF32Vector(state, 4u), ConstantU32(state, 3u)); @@ -1794,7 +1851,10 @@ void EmitProgram(SpirvEmitterState& state) { if (state.requirements.functionLds && !state.pixelFunctionLdsBuffer) { state.module.AddFunction(spv::OpVariable, TypeU32ArrayPointer(state, spv::StorageClassFunction, LdsStorageDwords(state)), state.ldsVariable, spv::StorageClassFunction); } - if (state.requirements.functionScratch) { + if (ScratchPoolActive(state)) { + state.module.AddFunction(spv::OpVariable, TypePointer(state, spv::StorageClassFunction, TypeU32(state)), state.scratchPoolCandidateVariable, spv::StorageClassFunction); + state.module.AddFunction(spv::OpVariable, TypePointer(state, spv::StorageClassFunction, TypeU32(state)), state.scratchPoolWonVariable, spv::StorageClassFunction); + } else if (state.requirements.functionScratch) { for (std::uint32_t half = 0; half < state.laneCount; half++) { state.module.AddFunction(spv::OpVariable, TypeU32ArrayPointer(state, spv::StorageClassFunction, state.program.Info().scratchDwords), state.scratchVariable.at(half), spv::StorageClassFunction); } @@ -1825,6 +1885,7 @@ void EmitProgram(SpirvEmitterState& state) { } } EmitLdsBufferBase(state); + EmitScratchPoolAcquire(state); if (state.requirements.ldsLock) { const auto firstInvocation = state.module.AllocateId(); state.module.AddFunction(spv::OpIEqual, TypeBool(state), firstInvocation, EmitLocalInvocationIndex(state), ConstantU32(state, 0u)); @@ -1852,7 +1913,12 @@ void EmitProgram(SpirvEmitterState& state) { for (std::uint32_t pixel = 3u; pixel-- > 0u;) selected = Select(state, TypeU32(state), EmitCompareU32Constant(state, spv::OpIEqual, quad, pixel), word(pixel), selected); state.sampleOrderWord = selected; } - if (AlphaToCoverage(state)) StoreCoverage(state, ConstantI32(state, -1)); + if (AlphaToCoverage(state)) { + StoreCoverage(state, ConstantI32(state, -1)); + for (const auto variable : {state.alphaCoverageVariable, state.exportedCoverageVariable}) { + if (variable != 0u) state.module.AddFunction(spv::OpStore, variable, ConstantI32(state, -1)); + } + } EmitBdaAnchors(state); if (state.pointSizeVariable != 0u && state.inputInfo.vertex != nullptr && state.inputInfo.vertex->pointList) { const auto& vertex = *state.inputInfo.vertex; @@ -1933,7 +1999,7 @@ void EmitProgram(SpirvEmitterState& state) { state.module.AddFunction(spv::OpKill); EmitLabel(state, mergeLabel); } - state.module.AddFunction(spv::OpReturn); + EmitReturnReleasingScratch(state); } else { StructuredFunctionState functionState; EmitControlFlow(ctx, functionState, program); @@ -2206,7 +2272,7 @@ void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) { const auto raw = ExportRawComponent(ctx, data, 2); const auto value = state.module.AllocateId(); state.module.AddFunction(spv::OpBitcast, TypeI32(state), value, raw); - StoreCoverage(state, value); + StoreCombinedCoverage(state, value, state.exportedCoverageVariable, state.alphaCoverageVariable); } return; } @@ -2241,7 +2307,7 @@ void EmitSetAttribute(SpirvValueEmitContext& ctx, const IrValue& inst) { if (uintOutput || mode == 5u || mode == 6u) FailEmit(std::string(merge->blend.enabled ? "blending into an unclamped normalized color target" : "a partial write of a packed color target") + " cannot take an integer or 16-bit normalized export"); MergeColorTarget(state, static_cast(merge - merges.begin()), value); } - if (!uintOutput && mode != 5u && mode != 6u) value = SelectExportPacking(state, exp.index, value); + if (!uintOutput && mode != 5u && mode != 6u) value = SelectExportPacking(state, exp.index, value, mode == 4u); } if (exp.kind == ExportTargetKind::Position) { if (state.inputInfo.vertex == nullptr) { diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp index 50e70196e4..cbd2743e39 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvRuntimeBuffer.cpp @@ -1021,7 +1021,7 @@ std::uint32_t EmitRuntimeScalarBufferLoad(SpirvValueEmitContext& context, const return EmitValueOrZeroIfCondition(state, inBounds, [&] { return readBuffer(context, instruction, AddBdaAddress(context, instruction, resource.base, offset, false), 32u); }); } -std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrValue& instruction, std::uint32_t bits, std::uint32_t active, const std::function& operation) { +static std::uint32_t RuntimeBufferAtomic(SpirvValueEmitContext& context, const IrValue& instruction, std::uint32_t bits, bool split, std::uint32_t active, const std::function& operation) { auto& state = context.state; if (bits != 32u && bits != 64u) context.Fail(instruction, "unsupported runtime buffer atomic width"); if (context.Memory(instruction).gpuDescriptor && (state.bdaAtomicPointerFunction == 0u || state.bdaNoteWriteFunction == 0u)) context.Fail(instruction, "BDA atomic functions are missing"); @@ -1030,18 +1030,20 @@ std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrVa if (std::ranges::find(state.supportedCapabilities, spv::CapabilityInt64) == state.supportedCapabilities.end()) context.Fail(instruction, "buffer atomic fault addresses require shaderInt64"); state.module.EmitCapability(spv::CapabilityInt64); } - const auto type = bits == 64u ? TypeU64(state) : TypeU32(state); - const auto scalar = bits == 64u ? TypeScalarU64(state) : TypeU32(state); - const auto zero = bits == 64u ? ConstantU64(state, 0u) : ConstantU32(state, 0u); + const bool wide = bits == 64u && !split; + const auto type = wide ? TypeU64(state) : TypeU32(state); + const auto scalar = wide ? TypeScalarU64(state) : TypeU32(state); + const auto zero = wide ? ConstantU64(state, 0u) : ConstantU32(state, 0u); return EmitValueOrDefaultIfCondition(state, active, type, zero, [&] { const auto resource = descriptor(context, instruction); const auto access = address(context, instruction, resource, 0u, bits / 8u); return EmitValueOrDefaultIfCondition(state, access.inBounds, type, zero, [&] { if (!context.Memory(instruction).gpuDescriptor) { - const auto storage = directAccess(context, instruction, bits == 64u); + const auto storage = directAccess(context, instruction, wide); const auto byte = EmitAddU32(state, access.guest, storage.byteOffset); - const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), byte, ConstantU32(state, bits == 64u ? 3u : 2u)); - auto valid = EmitMemoryElementInBounds(state, storage, index); + const auto index = Binary(state, spv::OpShiftRightLogical, TypeU32(state), byte, ConstantU32(state, wide ? 3u : 2u)); + const auto highIndex = split ? EmitAddU32(state, index, ConstantU32(state, 1u)) : index; + auto valid = EmitMemoryElementInBounds(state, storage, highIndex); if (bits == 64u) { const auto unaligned = nonzero(state, EmitAndConstant(state, byte, bits / 8u - 1u)); const auto guest = Binary(state, spv::OpIAdd, TypeScalarU64(state), resource.base, Unary(state, spv::OpUConvert, TypeScalarU64(state), access.guest)); @@ -1053,8 +1055,10 @@ std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrVa valid = both(state, Unary(state, spv::OpLogicalNot, TypeBool(state), unaligned), valid); } return EmitValueOrDefaultIfCondition(state, valid, type, zero, [&] { - const auto pointer = EmitStorageBufferElementPointer(state, storage, index, bits == 64u ? TypeStorageBufferU64ElementPointer(state) : TypeStorageBufferElementPointer(state)); - return operation(pointer); + const auto pointer = EmitStorageBufferElementPointer(state, storage, index, wide ? TypeStorageBufferU64ElementPointer(state) : TypeStorageBufferElementPointer(state)); + auto result = operation(pointer, 0u); + if (split) result = operation(EmitStorageBufferElementPointer(state, storage, highIndex, TypeStorageBufferElementPointer(state)), 1u); + return result; }); } const auto atomicAt = [&](std::uint32_t guest) { @@ -1064,7 +1068,14 @@ std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrVa return EmitValueOrDefaultIfCondition(state, mapped, type, zero, [&] { const auto pointer = state.module.AllocateId(); state.module.AddFunction(spv::OpConvertUToPtr, TypePointer(state, spv::StorageClassPhysicalStorageBuffer, scalar), pointer, physical); - const auto result = operation(pointer); + auto result = operation(pointer, 0u); + if (split) { + const auto high = state.module.AllocateId(); + state.module.AddFunction(spv::OpIAdd, TypeScalarU64(state), high, physical, BdaConstant(state, 4u)); + const auto highPointer = state.module.AllocateId(); + state.module.AddFunction(spv::OpConvertUToPtr, TypePointer(state, spv::StorageClassPhysicalStorageBuffer, scalar), highPointer, high); + result = operation(highPointer, 1u); + } state.module.AddFunction(spv::OpFunctionCall, state.module.Type(spv::OpTypeVoid), state.module.AllocateId(), state.bdaNoteWriteFunction, guest); return result; }); @@ -1077,4 +1088,16 @@ std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrVa }); } +std::uint32_t EmitRuntimeBufferAtomic(SpirvValueEmitContext& context, const IrValue& instruction, std::uint32_t bits, std::uint32_t active, const std::function& operation) { + return RuntimeBufferAtomic(context, instruction, bits, false, active, [&](std::uint32_t pointer, std::uint32_t) { return operation(pointer); }); +} + +void EmitRuntimeBufferAtomicHalves(SpirvValueEmitContext& context, const IrValue& instruction, std::uint32_t active, const std::function& operation) { + static_cast(RuntimeBufferAtomic(context, instruction, 64u, true, active, [&](std::uint32_t pointer, std::uint32_t) { + operation(pointer); + return ConstantU32(context.state, 0u); + })); +} + + } diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvSpannedUniformity.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvSpannedUniformity.cpp new file mode 100644 index 0000000000..1b7a231b6b --- /dev/null +++ b/core/shader/recompiler/SpirvBackend/src/SpirvSpannedUniformity.cpp @@ -0,0 +1,545 @@ +#include "SpirvInstructionOperands.hpp" +#include "SpirvBackend/SpirvSpannedUniformity.hpp" +#include +#include +#include +#include +#include + +namespace ShaderRecompiler { +namespace { + +using Instruction = std::vector; +using SpirvInstructions::Opcode; +using SpirvInstructions::Result; +using SpirvInstructions::VisitIdOperands; + +struct Block { + std::uint32_t label = 0u; + std::vector body; +}; + +struct Function { + std::uint32_t id = 0u; + std::vector parameters; + std::vector blocks; +}; + +struct Module { + std::unordered_map builtins; + std::unordered_map storageClasses; + std::unordered_map pointerClasses; + std::unordered_map constants; + std::vector functions; +}; + +Module Parse(std::span words) { + if (words.size() < 5u) throw std::runtime_error("SPIR-V module has no header"); + Module module; + Function* function = nullptr; + Block* block = nullptr; + for (std::size_t cursor = 5u; cursor < words.size();) { + const auto count = words[cursor] >> 16u; + if (count == 0u || cursor + count > words.size()) throw std::runtime_error("SPIR-V instruction runs past the module"); + Instruction instruction(words.begin() + static_cast(cursor), words.begin() + static_cast(cursor + count)); + cursor += count; + switch (Opcode(instruction)) { + case spv::OpDecorate: + if (instruction.size() >= 4u && instruction[2] == spv::DecorationBuiltIn) module.builtins[instruction[1]] = instruction[3]; + break; + case spv::OpTypePointer: + if (instruction.size() >= 4u) module.pointerClasses[instruction[1]] = instruction[2]; + break; + case spv::OpConstant: + if (instruction.size() >= 4u) module.constants[instruction[2]] = instruction[3]; + break; + case spv::OpVariable: + if (instruction.size() >= 4u) module.storageClasses[instruction[2]] = instruction[3]; + if (block != nullptr) block->body.push_back(std::move(instruction)); + break; + case spv::OpFunction: + function = &module.functions.emplace_back(); + function->id = instruction.at(2); + break; + case spv::OpFunctionParameter: + if (function != nullptr) function->parameters.push_back(instruction.at(2)); + break; + case spv::OpLabel: + if (function == nullptr) throw std::runtime_error("SPIR-V label outside a function"); + block = &function->blocks.emplace_back(); + block->label = instruction.at(1); + break; + case spv::OpFunctionEnd: + function = nullptr; + block = nullptr; + break; + default: + if (block != nullptr) block->body.push_back(std::move(instruction)); + break; + } + } + return module; +} + +bool VaryingBuiltin(std::uint32_t builtin) { + switch (builtin) { + case spv::BuiltInLocalInvocationId: + case spv::BuiltInGlobalInvocationId: + case spv::BuiltInLocalInvocationIndex: + case spv::BuiltInSubgroupId: + case spv::BuiltInSubgroupLocalInvocationId: + case spv::BuiltInSubgroupEqMask: + case spv::BuiltInSubgroupGeMask: + case spv::BuiltInSubgroupGtMask: + case spv::BuiltInSubgroupLeMask: + case spv::BuiltInSubgroupLtMask: + return true; + default: + return false; + } +} + +bool PerInvocationResult(spv::Op op) { + const auto raw = static_cast(op); + if (raw >= spv::OpAtomicExchange && raw <= spv::OpAtomicXor) return true; + if (raw >= spv::OpGroupAll && raw <= spv::OpGroupSMax) return true; + if (raw >= spv::OpGroupNonUniformElect && raw <= spv::OpGroupNonUniformQuadSwap) return true; + switch (op) { + case spv::OpAtomicFlagTestAndSet: + case spv::OpAtomicFMinEXT: + case spv::OpAtomicFMaxEXT: + case spv::OpAtomicFAddEXT: + case spv::OpSubgroupBallotKHR: + case spv::OpSubgroupFirstInvocationKHR: + case spv::OpSubgroupAllKHR: + case spv::OpSubgroupAnyKHR: + case spv::OpSubgroupAllEqualKHR: + case spv::OpSubgroupReadInvocationKHR: + case spv::OpGroupNonUniformRotateKHR: + return true; + default: + return false; + } +} + +bool PrivateStorage(std::uint32_t storageClass) { + return storageClass == spv::StorageClassFunction || storageClass == spv::StorageClassPrivate; +} + +std::uint32_t MemoryClass(std::uint32_t storageClass) { + switch (storageClass) { + case spv::StorageClassUniform: + case spv::StorageClassPhysicalStorageBuffer: + return spv::StorageClassStorageBuffer; + default: + return storageClass; + } +} + +bool AtomicWrite(spv::Op op) { + const auto raw = static_cast(op); + return (raw >= spv::OpAtomicExchange && raw <= spv::OpAtomicXor) || op == spv::OpAtomicFlagTestAndSet || op == spv::OpAtomicFMinEXT || op == spv::OpAtomicFMaxEXT || + op == spv::OpAtomicFAddEXT; +} + +bool ImageRead(spv::Op op) { + return op == spv::OpImageRead || op == spv::OpImageFetch || op == spv::OpImageSparseRead || op == spv::OpImageSparseFetch; +} + +class Analysis { +public: + explicit Analysis(const Module& module) : module(module) { + for (const auto& function : module.functions) { + for (const auto& block : function.blocks) { + for (const auto& instruction : block.body) { + const auto op = Opcode(instruction); + if (op == spv::OpControlBarrier) barriers.insert(function.id); + bool hasResult = false; + bool hasType = false; + spv::HasResultAndType(op, &hasResult, &hasType); + if (hasResult && hasType && instruction.size() > 2u) types[instruction[2]] = instruction[1]; + } + } + } + for (const auto& function : module.functions) { + for (const auto& block : function.blocks) { + for (const auto& instruction : block.body) { + const auto pointer = WrittenPointer(instruction); + if (pointer != 0u) written.insert(ClassOf(pointer)); + if (Opcode(instruction) == spv::OpImageWrite) written.insert(spv::StorageClassImage); + } + } + } + for (bool changed = true; changed;) { + changed = false; + for (const auto& function : module.functions) { + if (barriers.contains(function.id)) continue; + for (const auto& block : function.blocks) { + for (const auto& instruction : block.body) { + if (Opcode(instruction) == spv::OpFunctionCall && instruction.size() > 3u && barriers.contains(instruction[3]) && barriers.insert(function.id).second) changed = true; + } + } + } + } + } + + std::optional Run() { + if (barriers.empty()) return std::nullopt; + std::vector flows; + flows.reserve(module.functions.size()); + for (const auto& function : module.functions) flows.push_back(BuildFlow(function)); + for (;;) { + const auto before = Size(); + report.reset(); + for (std::size_t index = 0u; index < module.functions.size(); ++index) Visit(module.functions[index], flows[index]); + if (Size() == before) return report; + } + } + +private: + struct Flow { + std::vector order; + std::unordered_map index; + std::unordered_map> successors; + std::unordered_map idom; + std::unordered_map> children; + }; + + static std::uint32_t WrittenPointer(const Instruction& instruction) { + const auto op = Opcode(instruction); + if ((op == spv::OpStore || op == spv::OpAtomicStore || op == spv::OpAtomicFlagClear || op == spv::OpCopyMemory) && instruction.size() > 1u) return instruction[1]; + if (AtomicWrite(op) && instruction.size() > 3u) return instruction[3]; + return 0u; + } + + std::uint32_t ClassOf(std::uint32_t pointer) const { + if (const auto variable = module.storageClasses.find(pointer); variable != module.storageClasses.end()) return MemoryClass(variable->second); + const auto type = types.find(pointer); + if (type == types.end()) return UnknownClass; + const auto storage = module.pointerClasses.find(type->second); + return storage == module.pointerClasses.end() ? UnknownClass : MemoryClass(storage->second); + } + + bool Written(std::uint32_t storageClass) const { + return written.contains(UnknownClass) || written.contains(storageClass); + } + + bool Fence(const Instruction& instruction) const { + if (Opcode(instruction) != spv::OpControlBarrier || instruction.size() < 4u) return false; + const auto scope = module.constants.find(instruction[1]); + const auto semantics = module.constants.find(instruction[3]); + return scope != module.constants.end() && semantics != module.constants.end() && scope->second <= spv::ScopeWorkgroup && + (semantics->second & spv::MemorySemanticsWorkgroupMemoryMask) != 0u; + } + + bool Fenced(bool fenced, const Instruction& instruction) const { + if (Fence(instruction)) return true; + if (Opcode(instruction) == spv::OpFunctionCall) return false; + const auto pointer = WrittenPointer(instruction); + if (pointer == 0u) return fenced; + const auto storageClass = ClassOf(pointer); + return fenced && storageClass != spv::StorageClassWorkgroup && storageClass != UnknownClass; + } + + bool Racy(std::uint32_t pointer, bool fenced) const { + const auto storageClass = ClassOf(pointer); + if (storageClass != UnknownClass && (PrivateStorage(storageClass) || storageClass == spv::StorageClassInput)) return false; + if (!Written(storageClass)) return false; + return !fenced || storageClass != spv::StorageClassWorkgroup; + } + + std::unordered_map FencedEntries(const Function& function, const Flow& flow, const std::unordered_map& blocks) const { + std::unordered_map> predecessors; + for (const auto node : flow.order) { + for (const auto successor : flow.successors.at(node)) predecessors[successor].push_back(node); + } + std::unordered_map entries; + std::unordered_map exits; + for (const auto node : flow.order) exits[node] = true; + const auto root = function.blocks.front().label; + for (bool changed = true; changed;) { + changed = false; + for (const auto node : flow.order) { + bool fenced = node != root; + for (const auto predecessor : predecessors[node]) fenced = fenced && exits.at(predecessor); + entries[node] = fenced; + for (const auto& instruction : blocks.at(node)->body) fenced = Fenced(fenced, instruction); + if (exits[node] != fenced) { + exits[node] = fenced; + changed = true; + } + } + } + return entries; + } + + std::size_t Size() const { + return nonUniform.size() + storedNonUniform.size() + varyingFunctions.size(); + } + + bool NonUniform(std::uint32_t id) const { + return nonUniform.contains(id); + } + + static std::vector Successors(const Block& block) { + if (block.body.empty()) return {}; + const auto& terminator = block.body.back(); + switch (Opcode(terminator)) { + case spv::OpBranch: return {terminator.at(1)}; + case spv::OpBranchConditional: return {terminator.at(2), terminator.at(3)}; + case spv::OpSwitch: { + std::vector targets{terminator.at(2)}; + for (std::size_t operand = 4u; operand < terminator.size(); operand += 2u) targets.push_back(terminator[operand]); + return targets; + } + default: return {}; + } + } + + static Flow BuildFlow(const Function& function) { + Flow flow; + for (const auto& block : function.blocks) flow.successors[block.label] = Successors(block); + if (function.blocks.empty()) return flow; + std::vector postorder; + std::unordered_set seen{function.blocks.front().label}; + std::vector> stack{{function.blocks.front().label, 0u}}; + while (!stack.empty()) { + auto& [node, next] = stack.back(); + const auto& successors = flow.successors[node]; + if (next == successors.size()) { + postorder.push_back(node); + stack.pop_back(); + continue; + } + const auto successor = successors[next++]; + if (seen.insert(successor).second) stack.emplace_back(successor, 0u); + } + flow.order.assign(postorder.rbegin(), postorder.rend()); + for (std::size_t position = 0u; position < flow.order.size(); ++position) flow.index[flow.order[position]] = position; + std::unordered_map> predecessors; + for (const auto node : flow.order) { + for (const auto successor : flow.successors[node]) predecessors[successor].push_back(node); + } + const auto root = flow.order.front(); + flow.idom[root] = root; + const auto intersect = [&](std::uint32_t left, std::uint32_t right) { + while (left != right) { + while (flow.index[left] > flow.index[right]) left = flow.idom[left]; + while (flow.index[right] > flow.index[left]) right = flow.idom[right]; + } + return left; + }; + for (bool changed = true; changed;) { + changed = false; + for (std::size_t position = 1u; position < flow.order.size(); ++position) { + const auto node = flow.order[position]; + std::uint32_t dominator = 0u; + for (const auto predecessor : predecessors[node]) { + if (!flow.idom.contains(predecessor)) continue; + dominator = dominator == 0u ? predecessor : intersect(predecessor, dominator); + } + if (dominator != 0u && flow.idom[node] != dominator) { + flow.idom[node] = dominator; + changed = true; + } + } + } + for (const auto node : flow.order) { + if (node != root) flow.children[flow.idom[node]].push_back(node); + } + return flow; + } + + static bool Exits(spv::Op op) { + return op == spv::OpReturn || op == spv::OpReturnValue || op == spv::OpKill || op == spv::OpTerminateInvocation || op == spv::OpUnreachable; + } + + static std::vector Reachable(const Flow& flow, std::uint32_t from) { + std::vector blocks; + std::unordered_set seen{from}; + std::vector pending{from}; + while (!pending.empty()) { + const auto node = pending.back(); + pending.pop_back(); + blocks.push_back(node); + if (const auto found = flow.successors.find(node); found != flow.successors.end()) { + for (const auto successor : found->second) { + if (seen.insert(successor).second) pending.push_back(successor); + } + } + } + return blocks; + } + + static std::vector Construct(const Flow& flow, std::uint32_t header, std::uint32_t merge) { + std::vector blocks; + std::vector pending; + if (const auto found = flow.children.find(header); found != flow.children.end()) pending = found->second; + while (!pending.empty()) { + const auto node = pending.back(); + pending.pop_back(); + if (node == merge) continue; + blocks.push_back(node); + if (const auto found = flow.children.find(node); found != flow.children.end()) pending.insert(pending.end(), found->second.begin(), found->second.end()); + } + return blocks; + } + + std::uint32_t BaseVariable(std::uint32_t pointer, const std::unordered_map& chains) const { + for (std::size_t depth = 0u; depth < 64u; ++depth) { + const auto found = chains.find(pointer); + if (found == chains.end()) break; + pointer = found->second; + } + return pointer; + } + + void Visit(const Function& function, const Flow& flow) { + if (function.blocks.empty()) return; + std::unordered_map blocks; + std::unordered_map chains; + for (const auto& block : function.blocks) { + blocks[block.label] = █ + for (const auto& instruction : block.body) { + const auto op = Opcode(instruction); + if ((op == spv::OpAccessChain || op == spv::OpInBoundsAccessChain || op == spv::OpPtrAccessChain) && instruction.size() > 3u) chains[instruction[2]] = instruction[3]; + } + } + const auto fencedEntries = FencedEntries(function, flow, blocks); + for (const auto parameter : function.parameters) nonUniform.insert(parameter); + std::unordered_set divergent; + std::unordered_set divergentMerges; + for (auto position = flow.order.size(); position-- > 0u;) { + const auto header = flow.order[position]; + const auto& body = blocks.at(header)->body; + if (body.size() < 2u) continue; + const auto& mergeInstruction = body[body.size() - 2u]; + const auto mergeOp = Opcode(mergeInstruction); + if (mergeOp != spv::OpSelectionMerge && mergeOp != spv::OpLoopMerge) continue; + const auto merge = mergeInstruction.at(1); + const auto& terminator = body.back(); + const auto terminatorOp = Opcode(terminator); + bool split = (terminatorOp == spv::OpBranchConditional || terminatorOp == spv::OpSwitch) && NonUniform(terminator.at(1)); + const auto construct = Construct(flow, header, merge); + if (mergeOp == spv::OpLoopMerge && !split) { + for (const auto node : construct) { + const auto& successors = flow.successors.at(node); + if (std::find(successors.begin(), successors.end(), merge) == successors.end()) continue; + const auto& exit = blocks.at(node)->body.back(); + const auto exitOp = Opcode(exit); + if (divergent.contains(node) || ((exitOp == spv::OpBranchConditional || exitOp == spv::OpSwitch) && NonUniform(exit.at(1)))) { + split = true; + break; + } + } + } + if (!split) { + const auto continueTarget = mergeOp == spv::OpLoopMerge ? mergeInstruction.at(2) : 0u; + for (const auto node : construct) { + const auto& nodeBody = blocks.at(node)->body; + if (nodeBody.empty()) continue; + const auto& exit = nodeBody.back(); + const auto exitOp = Opcode(exit); + if ((exitOp != spv::OpBranchConditional && exitOp != spv::OpSwitch) || !NonUniform(exit.at(1))) continue; + if (nodeBody.size() >= 2u && (Opcode(nodeBody[nodeBody.size() - 2u]) == spv::OpSelectionMerge || Opcode(nodeBody[nodeBody.size() - 2u]) == spv::OpLoopMerge)) continue; + const auto& successors = flow.successors.at(node); + if (std::find(successors.begin(), successors.end(), merge) != successors.end() || (continueTarget != 0u && std::find(successors.begin(), successors.end(), continueTarget) != successors.end())) { + split = true; + break; + } + } + } + if (!split) continue; + divergent.insert(construct.begin(), construct.end()); + if (mergeOp == spv::OpLoopMerge) divergent.insert(header); + divergentMerges.insert(merge); + const std::unordered_set inside(construct.begin(), construct.end()); + bool exits = false; + bool escapes = false; + for (const auto node : construct) { + exits = exits || (!blocks.at(node)->body.empty() && Exits(Opcode(blocks.at(node)->body.back()))); + for (const auto successor : flow.successors.at(node)) escapes = escapes || (successor != merge && successor != header && !inside.contains(successor)); + } + if (exits) { + const auto after = Reachable(flow, merge); + divergent.insert(after.begin(), after.end()); + } else if (escapes) { + const auto after = Construct(flow, merge, 0u); + divergent.insert(merge); + divergent.insert(after.begin(), after.end()); + } + } + for (const auto label : flow.order) { + const bool inDivergentFlow = divergent.contains(label); + bool fenced = fencedEntries.at(label); + for (const auto& instruction : blocks.at(label)->body) { + const auto op = Opcode(instruction); + const bool fencedBefore = fenced; + fenced = Fenced(fenced, instruction); + if (op == spv::OpStore && instruction.size() >= 3u) { + const auto variable = BaseVariable(instruction[1], chains); + const auto storage = module.storageClasses.find(variable); + if (storage != module.storageClasses.end() && PrivateStorage(storage->second) && (inDivergentFlow || NonUniform(instruction[1]) || NonUniform(instruction[2]))) storedNonUniform.insert(variable); + continue; + } + if (inDivergentFlow && !report && (op == spv::OpControlBarrier || (op == spv::OpFunctionCall && instruction.size() > 3u && barriers.contains(instruction[3])))) { + report = std::string(op == spv::OpControlBarrier ? "an OpControlBarrier" : "a call reaching an OpControlBarrier") + " in block %" + std::to_string(label) + " of function %" + std::to_string(function.id) + " sits in control flow a per-invocation value decides"; + } + const auto result = Result(instruction); + if (result == 0u || NonUniform(result)) continue; + bool varying = false; + switch (op) { + case spv::OpLoad: { + const auto variable = BaseVariable(instruction.at(3), chains); + const auto builtin = module.builtins.find(variable); + const auto storage = module.storageClasses.find(variable); + varying = NonUniform(instruction[3]) || (builtin != module.builtins.end() && VaryingBuiltin(builtin->second)) || storedNonUniform.contains(variable) || + (storage != module.storageClasses.end() && storage->second == spv::StorageClassInput && builtin == module.builtins.end()) || Racy(instruction[3], fencedBefore); + break; + } + case spv::OpAtomicLoad: + varying = NonUniform(instruction.at(3)) || Racy(instruction[3], fencedBefore); + break; + case spv::OpPhi: + varying = inDivergentFlow || divergentMerges.contains(label); + for (std::size_t operand = 3u; !varying && operand < instruction.size(); operand += 2u) varying = NonUniform(instruction[operand]); + break; + case spv::OpFunctionCall: + varying = instruction.size() > 3u && varyingFunctions.contains(instruction[3]); + for (std::size_t operand = 4u; !varying && operand < instruction.size(); ++operand) varying = NonUniform(instruction[operand]); + break; + default: + varying = PerInvocationResult(op) || (ImageRead(op) && Written(spv::StorageClassImage)); + if (!varying) VisitIdOperands(instruction, 1u, [&](std::size_t operand) { varying = varying || NonUniform(instruction[operand]); }); + break; + } + if (!varying) continue; + nonUniform.insert(result); + if (op != spv::OpFunctionCall) varyingFunctions.insert(function.id); + } + } + } + + static constexpr std::uint32_t UnknownClass = 0xffffffffu; + + const Module& module; + std::unordered_map types; + std::unordered_set written; + std::unordered_set barriers; + std::unordered_set nonUniform; + std::unordered_set storedNonUniform; + std::unordered_set varyingFunctions; + std::optional report; +}; + +} + +std::optional SpannedBarrierInDivergentFlow(std::span words) { + bool barrier = false; + for (std::size_t cursor = 5u; !barrier && cursor < words.size(); cursor += std::max(words[cursor] >> 16u, 1u)) barrier = (words[cursor] & 0xffffu) == spv::OpControlBarrier; + if (!barrier) return std::nullopt; + const auto module = Parse(words); + return Analysis(module).Run(); +} + +} diff --git a/core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp b/core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp index 5268613c5c..00fb294562 100644 --- a/core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp +++ b/core/shader/recompiler/SpirvBackend/src/SpirvSpecialization.cpp @@ -2,6 +2,7 @@ #include #undef SPV_ENABLE_UTILITY_CODE #include "SpirvBackend/SpirvSpecialization.hpp" +#include "SpirvInstructionOperands.hpp" #include #include #include @@ -68,17 +69,9 @@ struct ArenaAllocator { using Instruction = std::vector>; -spv::Op Opcode(const Instruction& instruction) { - return instruction.empty() ? spv::OpNop : static_cast(instruction[0] & 0xffffu); -} - -std::uint32_t Result(const Instruction& instruction) { - if (instruction.empty()) return 0u; - bool result = false; - bool type = false; - spv::HasResultAndType(Opcode(instruction), &result, &type); - return result ? instruction.at(type ? 2u : 1u) : 0u; -} +using SpirvInstructions::Opcode; +using SpirvInstructions::Result; +using SpirvInstructions::VisitIdOperands; Instruction Make(spv::Op op, std::initializer_list operands) { Instruction result{(static_cast(operands.size() + 1u) << 16u) | op}; @@ -273,209 +266,6 @@ bool VisitInputs(const Instruction& instruction, const TVisitor& visit) { return true; } -template -void VisitIdOperands(const Instruction& instruction, std::uint32_t switchLiteralWords, const TVisitor& visit) { - const auto size = instruction.size(); - const auto from = [&](std::size_t first) { - for (auto index = first; index < size; ++index) visit(index); - }; - const auto only = [&](std::initializer_list indices) { - for (const auto index : indices) { - if (index < size) visit(index); - } - }; - constexpr std::uint32_t knownMemoryAccess = static_cast(spv::MemoryAccessVolatileMask) | static_cast(spv::MemoryAccessAlignedMask) | - static_cast(spv::MemoryAccessNontemporalMask) | static_cast(spv::MemoryAccessMakePointerAvailableMask) | - static_cast(spv::MemoryAccessMakePointerVisibleMask) | static_cast(spv::MemoryAccessNonPrivatePointerMask); - const auto memoryOperands = [&](std::size_t index) { - while (index < size) { - const auto mask = instruction[index++]; - if ((mask & ~knownMemoryAccess) != 0u) { - from(index); - return; - } - if ((mask & static_cast(spv::MemoryAccessAlignedMask)) != 0u) ++index; - if ((mask & static_cast(spv::MemoryAccessMakePointerAvailableMask)) != 0u && index < size) visit(index++); - if ((mask & static_cast(spv::MemoryAccessMakePointerVisibleMask)) != 0u && index < size) visit(index++); - } - }; - switch (Opcode(instruction)) { - case spv::OpNop: - case spv::OpCapability: - case spv::OpExtension: - case spv::OpExtInstImport: - case spv::OpMemoryModel: - case spv::OpSource: - case spv::OpSourceContinued: - case spv::OpSourceExtension: - case spv::OpString: - case spv::OpName: - case spv::OpMemberName: - case spv::OpModuleProcessed: - case spv::OpDecorate: - case spv::OpMemberDecorate: - case spv::OpDecorateString: - case spv::OpMemberDecorateString: - case spv::OpTypeVoid: - case spv::OpTypeBool: - case spv::OpTypeInt: - case spv::OpTypeFloat: - case spv::OpTypeSampler: - case spv::OpConstantTrue: - case spv::OpConstantFalse: - case spv::OpConstant: - case spv::OpConstantSampler: - case spv::OpConstantNull: - case spv::OpSpecConstantTrue: - case spv::OpSpecConstantFalse: - case spv::OpSpecConstant: - case spv::OpLabel: - case spv::OpNoLine: - case spv::OpReturn: - case spv::OpUnreachable: - case spv::OpKill: - case spv::OpFunctionEnd: - return; - case spv::OpTypeVector: - case spv::OpTypeMatrix: - case spv::OpTypeImage: - only({2}); - return; - case spv::OpTypePointer: - only({3}); - return; - case spv::OpTypeForwardPointer: - case spv::OpExecutionMode: - case spv::OpLine: - case spv::OpSelectionMerge: - only({1}); - return; - case spv::OpSpecConstantOp: - only({1}); - from(4); - return; - case spv::OpVariable: - case spv::OpFunction: - only({1, 4}); - return; - case spv::OpLoad: - only({1, 3}); - memoryOperands(4); - return; - case spv::OpStore: - case spv::OpCopyMemory: - only({1, 2}); - memoryOperands(3); - return; - case spv::OpCopyMemorySized: - only({1, 2, 3}); - memoryOperands(4); - return; - case spv::OpArrayLength: - case spv::OpCompositeExtract: - only({1, 3}); - return; - case spv::OpCompositeInsert: - case spv::OpVectorShuffle: - only({1, 3, 4}); - return; - case spv::OpExtInst: - only({1, 3}); - from(5); - return; - case spv::OpEntryPoint: { - only({2}); - std::size_t index = 3; - while (index < size) { - const auto word = instruction[index++]; - if ((word & 0xffu) == 0u || (word & 0xff00u) == 0u || (word & 0xff0000u) == 0u || (word & 0xff000000u) == 0u) break; - } - from(index); - return; - } - case spv::OpExecutionModeId: - case spv::OpDecorateId: - only({1}); - from(3); - return; - case spv::OpLoopMerge: - only({1, 2}); - return; - case spv::OpBranchConditional: - only({1, 2, 3}); - return; - case spv::OpSwitch: - only({1, 2}); - for (std::size_t index = 3; index + switchLiteralWords < size; index += switchLiteralWords + 1u) visit(index + switchLiteralWords); - return; - case spv::OpImageSampleImplicitLod: - case spv::OpImageSampleExplicitLod: - case spv::OpImageSampleProjImplicitLod: - case spv::OpImageSampleProjExplicitLod: - case spv::OpImageFetch: - case spv::OpImageRead: - case spv::OpImageSparseSampleImplicitLod: - case spv::OpImageSparseSampleExplicitLod: - case spv::OpImageSparseSampleProjImplicitLod: - case spv::OpImageSparseSampleProjExplicitLod: - case spv::OpImageSparseFetch: - case spv::OpImageSparseRead: - only({1, 3, 4}); - from(6); - return; - case spv::OpImageSampleDrefImplicitLod: - case spv::OpImageSampleDrefExplicitLod: - case spv::OpImageSampleProjDrefImplicitLod: - case spv::OpImageSampleProjDrefExplicitLod: - case spv::OpImageGather: - case spv::OpImageDrefGather: - case spv::OpImageSparseSampleDrefImplicitLod: - case spv::OpImageSparseSampleDrefExplicitLod: - case spv::OpImageSparseSampleProjDrefImplicitLod: - case spv::OpImageSparseSampleProjDrefExplicitLod: - case spv::OpImageSparseGather: - case spv::OpImageSparseDrefGather: - only({1, 3, 4, 5}); - from(7); - return; - case spv::OpImageWrite: - only({1, 2, 3}); - from(5); - return; - case spv::OpGroupIAdd: - case spv::OpGroupFAdd: - case spv::OpGroupFMin: - case spv::OpGroupUMin: - case spv::OpGroupSMin: - case spv::OpGroupFMax: - case spv::OpGroupUMax: - case spv::OpGroupSMax: - case spv::OpGroupNonUniformBallotBitCount: - case spv::OpGroupNonUniformIAdd: - case spv::OpGroupNonUniformFAdd: - case spv::OpGroupNonUniformIMul: - case spv::OpGroupNonUniformFMul: - case spv::OpGroupNonUniformSMin: - case spv::OpGroupNonUniformUMin: - case spv::OpGroupNonUniformFMin: - case spv::OpGroupNonUniformSMax: - case spv::OpGroupNonUniformUMax: - case spv::OpGroupNonUniformFMax: - case spv::OpGroupNonUniformBitwiseAnd: - case spv::OpGroupNonUniformBitwiseOr: - case spv::OpGroupNonUniformBitwiseXor: - case spv::OpGroupNonUniformLogicalAnd: - case spv::OpGroupNonUniformLogicalOr: - case spv::OpGroupNonUniformLogicalXor: - only({1, 3}); - from(5); - return; - default: - from(1); - return; - } -} - struct ScalarType { std::uint32_t width; bool boolean; diff --git a/core/shader/recompiler/StencilReplay.cpp b/core/shader/recompiler/StencilReplay.cpp index 65146cbcff..e735a5e2be 100644 --- a/core/shader/recompiler/StencilReplay.cpp +++ b/core/shader/recompiler/StencilReplay.cpp @@ -220,6 +220,23 @@ private: return ballot->Opcode() == IrOpcode::Ballot && ballot->ArgumentCount() == 1u ? ballot : nullptr; } + bool everyLaneWord(const IrValue* word, std::uint32_t half) const { + word = word->Resolve(); + if (const IrValue* ballot = ballotWord(word, half)) return Boolean(ballot->Argument(0), true); + if (word->Opcode() != IrOpcode::CompositeExtractU64 || word->ArgumentCount() != 2u || !Immediate(word->Argument(1), half)) return false; + const IrValue* wqm = word->Argument(0)->Resolve(); + if (wqm->Opcode() != IrOpcode::WqmU64 || wqm->ArgumentCount() != 1u) return false; + const IrValue* source = wqm->Argument(0)->Resolve(); + return source->Opcode() == IrOpcode::CompositeConstructU64 && source->ArgumentCount() == 2u && everyLaneWord(source->Argument(0), 0u) && everyLaneWord(source->Argument(1), 1u); + } + + bool ballotOfLanes(const IrValue* word, const IrValue* lanes, std::uint32_t half) const { + word = word->Resolve(); + if (const IrValue* ballot = ballotWord(word, half)) return ballot->Argument(0)->Resolve() == lanes; + if (word->Opcode() != IrOpcode::BitwiseAnd32 || word->ArgumentCount() != 2u) return false; + return (ballotOfLanes(word->Argument(0), lanes, half) && everyLaneWord(word->Argument(1), half)) || (ballotOfLanes(word->Argument(1), lanes, half) && everyLaneWord(word->Argument(0), half)); + } + bool maskStable(ReplayMask mask, unsigned depth) { if (depth > 128u) return false; mask = ReplayMask{mask.low->Resolve(), mask.high->Resolve()}; @@ -498,6 +515,19 @@ private: value = value->Resolve(); if (sameValue(value, other)) return true; if (value->Type() == IrType::Bool && other->Resolve() == predicate->Resolve() && GuardImplies(program, value, predicate)) return true; + for (std::uint32_t half = 0; half < (waveSize == 32u ? 1u : 2u); ++half) { + const IrValue* ballot = ballotWord(value, half); + if (!ballot) continue; + const IrValue* lanes = ballot->Argument(0)->Resolve(); + if (lanes->Opcode() == IrOpcode::LogicalAnd && lanes->ArgumentCount() == 2u) { + const auto inactiveOnly = [&](const IrValue* side) { side = side->Resolve(); return side->Opcode() == IrOpcode::LogicalNot && side->ArgumentCount() == 1u && side->Argument(0)->Resolve() == predicate->Resolve(); }; + if (inactiveOnly(lanes->Argument(1))) lanes = lanes->Argument(0)->Resolve(); + else if (inactiveOnly(lanes->Argument(0))) lanes = lanes->Argument(1)->Resolve(); + else continue; + } + if (const auto mask = own(lanes); mask && sameValue(half == 0u ? mask->low : mask->high, other)) return true; + if (ballotOfLanes(other, lanes, half)) return true; + } if ((value->Opcode() == IrOpcode::SelectU32 || value->Opcode() == IrOpcode::SelectF32 || value->Opcode() == IrOpcode::SelectU1) && value->ArgumentCount() == 3u && (value->Argument(0)->Resolve() == predicate->Resolve() || GuardImplies(program, value->Argument(0), predicate))) return inactiveEquals(value->Argument(2), other, predicate, region, depth + 1u); if (value->IsPhi() && region.contains(value->Parent()) && value->ArgumentCount() != 0u && value->ArgumentCount() == value->PhiBlockCount()) { return std::all_of(value->Arguments().begin(), value->Arguments().end(), [&](const IrValue* argument) { return inactiveEquals(argument, other, predicate, region, depth + 1u); }); @@ -608,6 +638,12 @@ private: return false; } + bool keepsNothing(const IrBlock* block) const { + return std::ranges::none_of(block->Instructions(), [](const IrValue* instruction) { + return instruction->Opcode() == IrOpcode::Discard || (instruction->Opcode() == IrOpcode::SetAttribute && !Boolean(instruction->Argument(1), false)); + }); + } + bool optionalBody(const IrBlock* branch, const IrBlock* body, const IrBlock* merge, const IrValue* predicate) { if (body == merge) return false; std::set region; @@ -616,7 +652,11 @@ private: const IrBlock* block = pending.back(); pending.pop_back(); if (block == merge || !region.insert(block).second) continue; - if (block == branch || region.size() > 64u || program.Metadata().blockInfo[blockIndices.at(block)].terminator.kind == TerminatorKind::Return) return false; + if (block == branch || region.size() > 64u) return false; + if (program.Metadata().blockInfo[blockIndices.at(block)].terminator.kind == TerminatorKind::Return) { + if (!keepsNothing(block)) return false; + continue; + } for (const auto* successor : block->Successors()) pending.push_back(successor); } for (const auto* block : region) { @@ -642,7 +682,7 @@ private: } for (const auto* block : region) { for (const auto* instruction : block->Instructions()) { - if (instruction->Opcode() == IrOpcode::SetAttribute || instruction->Opcode() == IrOpcode::Discard) return false; + if ((instruction->Opcode() == IrOpcode::SetAttribute && !Boolean(instruction->Argument(1), false)) || instruction->Opcode() == IrOpcode::Discard) return false; for (const auto& use : instruction->OperandUses()) { if (region.contains(use.user->Parent()) || merged.contains(use.user) || use.user->Opcode() == IrOpcode::Reference || use.user->Opcode() == IrOpcode::ReferenceU32) continue; return false; @@ -848,6 +888,61 @@ bool StencilReferenceDefinedForKeptInvocations(const IrProgram& program) { return valid; } +bool LayerReplaySafe(const IrProgram& program) { + if (program.Info().bdaWrites) return false; + for (const auto& block : program.Blocks()) { + for (const auto* instruction : block->Instructions()) { + const auto opcode = instruction->Opcode(); + if (opcode == IrOpcode::ImageBvhIntersectRay || (BufferAccessOf(opcode) == BufferAccess::Write || BufferAccessOf(opcode) == BufferAccess::Atomic) || AddressOpcodeInfoOf(opcode).access == AddressAccess::Write || AddressOpcodeInfoOf(opcode).access == AddressAccess::Atomic || ImageOpcodeInfoOf(opcode).access == ImageAccess::Write || ImageOpcodeInfoOf(opcode).access == ImageAccess::Atomic) return false; + if (opcode == IrOpcode::UndefU1 || opcode == IrOpcode::UndefU8 || opcode == IrOpcode::UndefU16 || opcode == IrOpcode::UndefU32 || opcode == IrOpcode::UndefU64) return false; + if (opcode == IrOpcode::ShaderClock || opcode == IrOpcode::RealtimeClock) return false; + if (SharedAccessOf(opcode) != SharedAccess::None) { + const auto memory = instruction->Flags().index; + if (memory >= program.Resources().memoryInfo.size() || program.Resources().memoryInfo[memory].kind != ResourceKind::Lds) return false; + continue; + } + if (!instruction->MayHaveSideEffects()) continue; + switch (opcode) { + case IrOpcode::Barrier: + case IrOpcode::MeshAllocate: + case IrOpcode::Branch: + case IrOpcode::BranchConditional: + case IrOpcode::Return: + case IrOpcode::Unreachable: + case IrOpcode::Discard: + case IrOpcode::Reference: + case IrOpcode::ReferenceU32: + case IrOpcode::SetAttribute: + case IrOpcode::InterpolateHostP2: + case IrOpcode::SetRegister: + case IrOpcode::SetThreadBitScalarRegister: + case IrOpcode::SetScalarMaskTag: + case IrOpcode::SetScalarRegister: + case IrOpcode::SetVectorRegister: + case IrOpcode::StoreVectorWindow: + case IrOpcode::SetGotoVariable: + case IrOpcode::SetScc: + case IrOpcode::SetExec: + case IrOpcode::SetExecLo: + case IrOpcode::SetExecHi: + case IrOpcode::SetVcc: + case IrOpcode::SetVccLo: + case IrOpcode::SetVccHi: + case IrOpcode::SetM0: + case IrOpcode::Waitcnt: + case IrOpcode::InstPrefetch: + break; + case IrOpcode::Sendmsg: + if (instruction->ArgumentCount() != 0u) return false; + break; + default: + return false; + } + } + } + return true; +} + bool StencilReplaySafe(const IrProgram& program) { if (program.Info().sharedMemoryBytes != 0u || program.Info().bdaWrites || (program.Info().usesFaultBuffer && program.Resources().guardedSrtSlots.empty())) return false; if (program.Info().usesFaultBuffer && !FaultControlFlowAcyclic(program)) return false; diff --git a/core/shader/recompiler/StencilReplay.hpp b/core/shader/recompiler/StencilReplay.hpp index 5c998b0056..fcd16b2c03 100644 --- a/core/shader/recompiler/StencilReplay.hpp +++ b/core/shader/recompiler/StencilReplay.hpp @@ -8,6 +8,7 @@ namespace ShaderRecompiler { [[nodiscard]] std::vector StencilReferenceValues(const IrProgram& program); [[nodiscard]] std::vector StencilReferenceValues(const IrProgram& program, std::uint32_t shift); +[[nodiscard]] bool LayerReplaySafe(const IrProgram& program); [[nodiscard]] bool StencilReplaySafe(const IrProgram& program); [[nodiscard]] bool StencilReferenceDefinedForKeptInvocations(const IrProgram& program); diff --git a/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp b/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp index b72d41b050..66d0d0cd64 100644 --- a/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp +++ b/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp @@ -38,6 +38,7 @@ struct TranslateOptions { bool float32FlushToZero = false; bool float32FlushDeclared = false; bool float32DenormPreserve = false; + bool float64 = false; bool gcnLegacyVertexInputs = false; std::optional floatMode; ShaderStageInputInfo inputInfo; diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 91f4ec7a20..8b210a30ec 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -28,6 +28,7 @@ public: void SetFloat32DenormPreserve(bool enabled) { float32DenormPreserve = enabled; } void SetFloat32FlushToZero(bool enabled) { float32FlushToZero = enabled; } void SetFloat32FlushDeclared(bool enabled) { float32FlushDeclared = enabled; } + void SetFloat64(bool enabled) { float64 = enabled; } void AddBranchCondition(const BasicBlock& source, BlockInfo& info); void TranslateCodeTableLoad(const RdnaInstruction& instruction, const ControlFlowGraph::CodeTableLoad& table); void TranslateUnlinkedCall(const RdnaInstruction& instruction); @@ -70,7 +71,7 @@ private: void rejectHalfOrDoubleOutputModifier(const RdnaOperand& operand) const; IrU32 clampF16Bits(const RdnaOperand& operand, IrU32 bits); void writeOperand(const RdnaOperand& operand, IrValue* value); - IrU32 packHalf2x16(IrF32 low, IrF32 high); + IrU32 packHalf2x16(IrF32 low, IrF32 high, bool nanPayloadUnused = false); void write16Bits(const RdnaOperand& operand, IrU32 value); void writeF16(const RdnaOperand& operand, IrF32 value, std::initializer_list sources, IrValue* invalid = nullptr); bool directedF16Rounding() const; @@ -87,7 +88,7 @@ private: IrF32 flushTinyProduct(IrValue* lhs, IrValue* rhs, IrValue* product, IrValue* addend = nullptr, bool afterRounding = true); IrU32 quietNan32(IrU32 bits); IrU32 quietNan16(IrU32 bits); - IrValue* nanResultF32(std::initializer_list sources, IrValue* result, IrValue* invalidProduct = nullptr); + IrValue* nanResultF32(std::initializer_list sources, IrValue* result, IrValue* invalidProduct = nullptr, bool multiplication = false); IrValue& invalidProductF32(IrValue* lhs, IrValue* rhs); IrValue& invalidOperationF32(std::initializer_list sources, IrValue& invalidProduct); std::array quietNan64(const std::array& bits); @@ -97,8 +98,8 @@ private: std::array flushF64Bits(const std::array& bits, std::uint32_t keepBit); IrValue& flushTinyF64Result(IrOpcode opcode, IrValue& lhs, IrValue& rhs, IrValue* addend, IrValue& result); IrU64 readF64(const RdnaOperand& operand); - IrF32 readF16LaneAsF32(const RdnaOperand& operand, bool highLane, bool packed = false); - IrF32 readF16AsF32(const RdnaOperand& operand); + IrF32 readF16LaneAsF32(const RdnaOperand& operand, bool highLane, bool packed = false, bool nanPayloadUnused = false); + IrF32 readF16AsF32(const RdnaOperand& operand, bool nanPayloadUnused = false); IrU32 readF16SourceBits(const RdnaOperand& operand); IrF32 readMixF32(const RdnaOperand& operand); IrU32 readU16LaneRaw(const RdnaOperand& operand, bool highLane); @@ -225,6 +226,7 @@ private: bool minMaxF16(const RdnaInstruction& inst, IrOpcode opcode); bool vDivScaleF32(const RdnaInstruction& inst); bool vDivFmasF32(const RdnaInstruction& inst); + IrValue& fmaScaledF32(IrValue& lhs, IrValue& rhs, IrValue& addend, IrValue& powerHigh); bool vDivFixupF32(const RdnaInstruction& inst); bool vFrexpMantF32(const RdnaInstruction& inst); bool vFmaLegacyF32(const RdnaInstruction& inst, bool accumulate); @@ -391,6 +393,7 @@ private: bool float32FlushToZero = false; bool float32FlushDeclared = false; bool float32DenormPreserve = false; + bool float64 = false; std::uint32_t currentProgramCounter = 0; std::uint32_t currentVectorLimit = 1; }; diff --git a/core/shader/recompiler/Translation/src/CompareInstructions.cpp b/core/shader/recompiler/Translation/src/CompareInstructions.cpp index a562ccc2d0..6d55ce0862 100644 --- a/core/shader/recompiler/Translation/src/CompareInstructions.cpp +++ b/core/shader/recompiler/Translation/src/CompareInstructions.cpp @@ -86,7 +86,8 @@ void TranslationContext::emitCompareResult(const RdnaInstruction& inst, IrU1 val } IrValue& masked = ir.LogicalAnd(ir.GetExec(), value.Value()); if (cmpx) { - const std::array mask = ballotMask(IrU1(masked)); + const bool writesDestination = inst.destination2.kind != RdnaOperandKind::Unknown && inst.destination2.kind != RdnaOperandKind::Null; + const std::array mask = writesDestination ? writeMask(inst.destination2, IrU1(masked)) : ballotMask(IrU1(masked)); ir.SetExec(masked); ir.SetExecLo(mask[0].Value()); ir.SetExecHi(mask[1].Value()); diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index c116783606..10c65e6179 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -21,7 +21,7 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc const IrU1 tiny(ir.ULessThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)), ir.Constant(opcode == IrOpcode::FPAdd32 ? 0x38800000u : 0x387ff000u))); return IrF32(ir.BitCastF32(ir.Select(tiny.Value(), ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), bits.Value()))); }; - const auto readLane = [&](const RdnaOperand& operand, bool high) { return flushF16InputValue(readF16LaneAsF32(operand, high, true)); }; + const auto readLane = [&](const RdnaOperand& operand, bool high) { return flushF16InputValue(readF16LaneAsF32(operand, high, true, true)); }; const auto translateLane = [&](bool high) -> IrF32 { const IrF32 lhs = readLane(sourceAt(inst, 0u), high); const IrF32 rhs = readLane(sourceAt(inst, 1u), high); @@ -65,7 +65,7 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc const IrU32 rhs = packU16Lanes(sourceLaneBits(1u, false), sourceLaneBits(1u, true)); return IrU32(ir.Emit(opcode == IrOpcode::FPAdd32 ? IrOpcode::FPAdd16x2 : IrOpcode::FPMul16x2, IrType::U32, {&lhs.Value(), &rhs.Value()})); }; - IrU32 result = quietSnan ? packU16Lanes(minMaxLane(false), minMaxLane(true)) : native ? nativeResult() : packHalf2x16(translateLane(false), translateLane(true)); + IrU32 result = quietSnan ? packU16Lanes(minMaxLane(false), minMaxLane(true)) : native ? nativeResult() : packHalf2x16(translateLane(false), translateLane(true), true); if (native || !inst.destination.clamp || !dx10Clamp() || quietSnan) { const auto isNan = [&](const IrU32& bits) { return IrU1(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u))); @@ -122,7 +122,7 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opcode) { const RdnaOperand& operand = sourceAt(inst, 0u); - const IrF32 argument = flushF16InputValue(readF16AsF32(operand)); + const IrF32 argument = flushF16InputValue(readF16AsF32(operand, true)); const auto [result, invalid] = unaryFloatSpecials(opcode, argument, IrF32(ir.Emit(opcode, IrType::F32, {&argument.Value()}))); const IrF16 rounded(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&applyF16ResultModifiers(inst.destination, clampF16Overflow(result, {&argument.Value()})).Value()})); IrU32 bits(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&rounded.Value()})})); @@ -545,6 +545,11 @@ bool TranslationContext::vDivFmasF32(const RdnaInstruction& inst) { return IrU32(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&ir.BitCastU32(value), &ir.Constant(23u), &ir.Constant(8u)})); }; const IrU1 up(ir.Emit(IrOpcode::UGreaterThanEqual32, IrType::U1, {&exponent(*addend).Value(), &ir.Constant(128u)})); + if (float64) { + IrValue& powerHigh = ir.Select(scale.Value(), ir.Select(up.Value(), ir.Constant(0x43f00000u), ir.Constant(0x3bf00000u)), ir.Constant(0x3ff00000u)); + writeOperand(inst.destination, nanResultF32({lhs, rhs, addend}, &fmaScaledF32(*lhs, *rhs, *addend, powerHigh), &invalidProductF32(lhs, rhs))); + return true; + } IrValue& power = ir.Emit(IrOpcode::SelectF32, IrType::F32, {&up.Value(), &ir.ConstantF32(18446744073709551616.0f), &ir.ConstantF32(5.42101086242752217e-20f)}); IrValue& plain = ir.Emit(IrOpcode::FPFma32, IrType::F32, {lhs, rhs, addend}); const IrU1 plainInfinite(ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(plain), ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u))); @@ -563,6 +568,46 @@ bool TranslationContext::vDivFmasF32(const RdnaInstruction& inst) { return true; } +IrValue& TranslationContext::fmaScaledF32(IrValue& lhs, IrValue& rhs, IrValue& addend, IrValue& powerHigh) { + const auto low = [&](IrValue& value) -> IrValue& { return ir.CompositeExtract(value, 0u); }; + const auto high = [&](IrValue& value) -> IrValue& { return ir.CompositeExtract(value, 1u); }; + const auto select = [&](IrValue& condition, IrValue& yes, IrValue& no) -> IrValue& { + return ir.ConstructU64(ir.Select(condition, low(yes), low(no)), ir.Select(condition, high(yes), high(no))); + }; + const auto add = [&](IrValue& a, IrValue& b) -> IrValue& { return ir.Emit(IrOpcode::FPAdd64, IrType::U64, {&a, &b}); }; + const auto multiply = [&](IrValue& a, IrValue& b) -> IrValue& { return ir.Emit(IrOpcode::FPMul64, IrType::U64, {&a, &b}); }; + const auto negate = [&](IrValue& value) -> IrValue& { return ir.ConstructU64(low(value), ir.BitwiseXor(high(value), ir.Constant(0x80000000u))); }; + const auto below = [&](IrValue& value, std::uint64_t limit) -> IrValue& { return ir.Emit(IrOpcode::ULessThan64, IrType::U1, {&value, &ir.ConstantU64(limit)}); }; + const auto widen = [&](IrValue& value) -> IrValue& { + IrValue& bits = ir.BitCastU32(value); + IrValue& magnitude = ir.BitwiseAnd(bits, ir.Constant(0x7fffffffu)); + IrValue& denormal = multiply(ir.Emit(IrOpcode::ConvertF64U32, IrType::U64, {&magnitude}), ir.ConstantU64(0x36a0000000000000ull)); + IrValue& signedDenormal = ir.ConstructU64(low(denormal), ir.BitwiseOr(high(denormal), ir.BitwiseAnd(bits, ir.Constant(0x80000000u)))); + return select(ir.ULessThan(magnitude, ir.Constant(0x00800000u)), signedDenormal, ir.Emit(IrOpcode::ConvertF64F32, IrType::U64, {&value})); + }; + IrValue& product = multiply(widen(lhs), widen(rhs)); + IrValue& wideAddend = widen(addend); + IrValue& sum = add(product, wideAddend); + IrValue& productPart = add(sum, negate(wideAddend)); + IrValue& addendPart = add(sum, negate(productPart)); + IrValue& error = add(add(product, negate(productPart)), add(wideAddend, negate(addendPart))); + IrValue& finite = ir.INotEqual(ir.BitwiseAnd(high(sum), ir.Constant(0x7ff00000u)), ir.Constant(0x7ff00000u)); + IrValue& inexact = ir.INotEqual(ir.BitwiseOr(low(error), ir.BitwiseAnd(high(error), ir.Constant(0x7fffffffu))), ir.Constant(0u)); + IrValue& even = ir.IEqual(ir.BitwiseAnd(low(sum), ir.Constant(1u)), ir.Constant(0u)); + IrValue& outward = ir.IEqual(ir.BitwiseAnd(ir.BitwiseXor(high(error), high(sum)), ir.Constant(0x80000000u)), ir.Constant(0u)); + IrValue& odd = select(outward, ir.Emit(IrOpcode::IAdd64, IrType::U64, {&sum, &ir.ConstantU64(1u)}), ir.Emit(IrOpcode::ISub64, IrType::U64, {&sum, &ir.ConstantU64(1u)})); + IrValue& rounded = select(ir.LogicalAnd(finite, ir.LogicalAnd(inexact, even)), odd, sum); + IrValue& scaled = multiply(rounded, ir.ConstructU64(ir.Constant(0u), powerHigh)); + IrValue& sign = ir.BitwiseAnd(high(scaled), ir.Constant(0x80000000u)); + IrValue& magnitude = ir.ConstructU64(low(scaled), ir.BitwiseAnd(high(scaled), ir.Constant(0x7fffffffu))); + IrValue& denormal = ir.Emit(IrOpcode::ConvertU32F64, IrType::U32, {&ir.Emit(IrOpcode::FPRoundEven64, IrType::U64, {&multiply(magnitude, ir.ConstantU64(0x4940000000000000ull))})}); + IrValue& overflow = ir.LogicalAnd(ir.LogicalNot(below(magnitude, 0x47effffff0000000ull)), below(magnitude, 0x7ff0000000000000ull)); + IrValue* bits = &ir.BitCastU32(ir.Emit(IrOpcode::ConvertF32F64, IrType::F32, {&scaled})); + bits = &ir.Select(overflow, ir.BitwiseOr(sign, ir.Constant(0x7f800000u)), *bits); + bits = &ir.Select(below(magnitude, 0x3810000000000000ull), ir.BitwiseOr(sign, denormal), *bits); + return ir.BitCastF32(*bits); +} + bool TranslationContext::vDivFixupF32(const RdnaInstruction& inst) { const IrU32 quotient = readU32(sourceAt(inst, 0u)); const IrU32 denominator = flushF32Denormal(readU32(sourceAt(inst, 1u))); @@ -610,7 +655,7 @@ bool TranslationContext::floatBinary(const RdnaInstruction& inst, IrOpcode opcod } IrValue* result = &ir.Emit(opcode, IrOpcodeType(opcode), {args[0], args[1]}); if (opcode == IrOpcode::FPMul32) result = &flushTinyProduct(args[0], args[1], result).Value(); - if (opcode == IrOpcode::FPAdd32 || opcode == IrOpcode::FPMul32) result = nanResultF32({args[0], args[1]}, result); + if (opcode == IrOpcode::FPAdd32 || opcode == IrOpcode::FPMul32) result = nanResultF32({args[0], args[1]}, result, nullptr, opcode == IrOpcode::FPMul32); if (opcode == IrOpcode::FPSub32) { IrValue* negated = &ir.BitCastF32(ir.BitwiseXor(ir.BitCastU32(*args[1]), ir.Constant(0x80000000u))); result = reverse ? nanResultF32({negated, args[0]}, result) : nanResultF32({args[0], negated}, result); diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index 4a44efe341..3bac17290d 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -269,7 +269,9 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO return entryIr.ISub(lhs, minimum(lhs, rhs)); }; IrValue& local = builtin(StageInputKind::LocalInvocationIndex); - IrValue& firstPrimitive = entryIr.IMul(builtin(StageInputKind::WorkgroupId, 0u), u32(mesh.primitivesPerGroup)); + IrValue& indexWord = draw(3u); + IrValue& group = entryIr.IAdd(builtin(StageInputKind::WorkgroupId, 0u), entryIr.IMul(builtin(StageInputKind::WorkgroupId, 2u), entryIr.ShiftRightLogical(indexWord, u32(MeshLayerStrideShift)))); + IrValue& firstPrimitive = entryIr.IMul(group, u32(mesh.primitivesPerGroup)); IrValue& step = u32(stepCount); IrValue& firstVertex = entryIr.IMul(firstPrimitive, step); IrValue& indirect = entryIr.INotEqual(entryIr.BitwiseOr(draw(MeshArgumentAddressDword), draw(MeshArgumentAddressDword + 1u)), u32(0u)); @@ -285,7 +287,6 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32((totalThreads / options.waveSize) << 28u)); entryIr.SetScalarReg(static_cast(3), entryIr.BitwiseOr(waveInfo, entryIr.BitwiseOr(entryIr.ShiftLeftLogical(primitiveCount, u32(8u)), vertexCount))); const bool strip = mesh.inputPrimitive == kTriStripPrimitiveType; - IrValue& indexWord = draw(3u); IrValue& stripPosition = entryIr.IAdd(entryIr.IAdd(firstPrimitive, local), firstIndex); IrValue& restartTable = entryIr.INotEqual(entryIr.BitwiseAnd(indexWord, u32(MeshIndexRestartTable)), u32(0u)); IrValue& stripStart = strip ? entryIr.Emit(IrOpcode::MeshRestartStart, IrOpcodeType(IrOpcode::MeshRestartStart), {&entryIr.IAdd(stripPosition, u32(2u)), &restartTable}) : u32(0u); @@ -295,9 +296,12 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO const auto unlessRestarted = [&](IrValue& offset) -> IrValue& { return strip ? entryIr.Select(entryIr.ULessThan(stripPosition, stripStart), entryIr.IMul(vertex, item), offset) : offset; }; - IrValue& first = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(1u)), item) : unlessRestarted(entryIr.IMul(entryIr.IAdd(vertex, parity), item)); - IrValue& second = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : size >= 2u ? unlessRestarted(entryIr.IMul(entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity), item)) : u32(0u); - IrValue& third = fan ? u32(0u) : size == 3u ? unlessRestarted(entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item)) : u32(0u); + const bool last = mesh.provokingVertex != 0u; + IrValue& fanSecond = entryIr.IMul(entryIr.IAdd(vertex, u32(1u)), item); + IrValue& fanThird = entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item); + IrValue& first = fan ? (last ? u32(0u) : fanSecond) : unlessRestarted(entryIr.IMul(last ? entryIr.IAdd(vertex, parity) : vertex, item)); + IrValue& second = fan ? (last ? fanSecond : fanThird) : size >= 2u ? unlessRestarted(entryIr.IMul(last ? entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity) : entryIr.IAdd(entryIr.IAdd(vertex, u32(1u)), parity), item)) : u32(0u); + IrValue& third = fan ? (last ? fanThird : u32(0u)) : size == 3u ? unlessRestarted(entryIr.IMul(last ? entryIr.IAdd(vertex, u32(2u)) : entryIr.ISub(entryIr.IAdd(vertex, u32(2u)), parity), item)) : u32(0u); setVector(static_cast(0), entryIr.BitwiseOr(entryIr.BitwiseAnd(first, u32(0xffffu)), entryIr.ShiftLeftLogical(second, u32(16u)))); setVector(static_cast(1), entryIr.BitwiseAnd(third, u32(0xffffu))); setVector(static_cast(2), entryIr.IAdd(firstPrimitive, local)); @@ -622,6 +626,7 @@ IrProgram InstructionTranslator::Translate(const RdnaProgram& decoded, const Con context.SetFloat32DenormPreserve(options.float32DenormPreserve); context.SetFloat32FlushToZero(options.float32FlushToZero); context.SetFloat32FlushDeclared(options.float32FlushDeclared); + context.SetFloat64(options.float64); context.SetEntryRounding(entryRounding.at(cfgBlock.id)); for (std::uint32_t index = cfgBlock.instructionBegin; index < cfgBlock.instructionEnd; index++) { const auto& instruction = decoded.instructions[index]; diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index 4677a173cf..42468ccf48 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -342,11 +342,17 @@ IrU32 TranslationContext::quietNan32(IrU32 bits) { return ieeeMode ? IrU32(ir.BitwiseOr(bits.Value(), ir.Constant(0x00400000u))) : bits; } -IrValue* TranslationContext::nanResultF32(std::initializer_list sources, IrValue* result, IrValue* invalidProduct) { +IrValue* TranslationContext::nanResultF32(std::initializer_list sources, IrValue* result, IrValue* invalidProduct, bool multiplication) { if (!ExactF32Arithmetic()) return result; const auto isNan = [&](IrValue& bits) -> IrValue& { return ir.UGreaterThan(ir.BitwiseAnd(bits, ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u)); }; IrValue* bits = &ir.BitCastU32(*result); - bits = &ir.Select(isNan(*bits), ir.Constant(0xffc00000u), *bits); + const bool normalFactor = multiplication && std::any_of(sources.begin(), sources.end(), [](const IrValue* source) { + const IrValue* constant = constantF32Bits(*source); + if (constant == nullptr) return false; + const std::uint32_t exponent = (constant->ImmediateU32() >> 23u) & 0xffu; + return exponent != 0u && exponent != 0xffu; + }); + if (!normalFactor) bits = &ir.Select(isNan(*bits), ir.Constant(0xffc00000u), *bits); for (auto source = sources.end(); source != sources.begin();) { --source; IrValue& sourceBits = ir.BitCastU32(**source); diff --git a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp index 3ba6021ff2..3f8d8ef4cd 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp @@ -14,7 +14,7 @@ IrU32 TranslationContext::readU32(const RdnaOperand& operand) { std::array TranslationContext::readU32Pair(const RdnaOperand& operand) { switch (operand.kind) { - case RdnaOperandKind::LiteralConstant: return {readU32(operand), IrU32(ir.Constant(0u))}; + case RdnaOperandKind::LiteralConstant: return {readU32(operand), IrU32(ir.Constant(IsSigned64BitIntegerOpcode(currentOpcode) && (operand.value & 0x80000000u) != 0u ? 0xffffffffu : 0u))}; case RdnaOperandKind::IntegerInlineConstant: return {readU32(operand), IrU32(ir.Constant(operand.signedVal < 0 ? 0xffffffffu : 0u))}; case RdnaOperandKind::FloatInlineConstant: { const auto bits = std::bit_cast(static_cast(std::bit_cast(operand.value))); @@ -67,13 +67,14 @@ void TranslationContext::writeU32Pair(const RdnaOperand& operand, const std::arr writeRawU32(offsetOperand(operand, 1u), value[1]); } -IrF32 TranslationContext::readF16LaneAsF32(const RdnaOperand& operand, bool highLane, bool packed) { +IrF32 TranslationContext::readF16LaneAsF32(const RdnaOperand& operand, bool highLane, bool packed, bool nanPayloadUnused) { const bool selectHigh = packed ? (highLane ? operand.opSelHi : operand.opSel) : highLane; const IrU32 source = readF16SourceBits(operand); const IrU32 raw(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&source.Value(), &ir.Constant(selectHigh ? 16u : 0u), &ir.Constant(16u)})); const IrU16 bits(ir.Emit(IrOpcode::ConvertU16U32, IrType::U16, {&raw.Value()})); const IrF16 half(ir.Emit(IrOpcode::BitCastF16U16, IrType::F16, {&bits.Value()})); IrF32 value(ir.Emit(IrOpcode::ConvertF32F16, IrType::F32, {&half.Value()})); + if (nanPayloadUnused) value.Value().SetFlags(FloatConvertFlags{true}); if (operand.absolute) { value = IrF32(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&value.Value()})); } @@ -104,12 +105,13 @@ IrU32 TranslationContext::readF16SourceBits(const RdnaOperand& operand) { return applyBitSourceModifiers(operand, IrU32(ir.Constant(half))); } -IrF32 TranslationContext::readF16AsF32(const RdnaOperand& operand) { +IrF32 TranslationContext::readF16AsF32(const RdnaOperand& operand, bool nanPayloadUnused) { const IrU32 source = readF16SourceBits(operand); const IrU32 raw = operand.opSel ? IrU32(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&source.Value(), &ir.Constant(16u), &ir.Constant(16u)})) : source; const IrU16 bits(ir.Emit(IrOpcode::ConvertU16U32, IrType::U16, {&raw.Value()})); const IrF16 half(ir.Emit(IrOpcode::BitCastF16U16, IrType::F16, {&bits.Value()})); IrF32 value(ir.Emit(IrOpcode::ConvertF32F16, IrType::F32, {&half.Value()})); + if (nanPayloadUnused) value.Value().SetFlags(FloatConvertFlags{true}); if (operand.absolute) { value = IrF32(ir.Emit(IrOpcode::FPAbs32, IrType::F32, {&value.Value()})); } @@ -154,9 +156,11 @@ IrU32 TranslationContext::readU16AsU32(const RdnaOperand& operand, bool signExte return IrU32(ir.Emit(IrOpcode::BitFieldSExtract, IrType::U32, {&raw.Value(), &ir.Constant(0u), &ir.Constant(16u)})); } -IrU32 TranslationContext::packHalf2x16(IrF32 low, IrF32 high) { +IrU32 TranslationContext::packHalf2x16(IrF32 low, IrF32 high, bool nanPayloadUnused) { IrValue& pair = ir.Emit(IrOpcode::CompositeConstructF32x2, IrType::F32x2, {&low.Value(), &high.Value()}); - return IrU32(ir.Emit(IrOpcode::PackHalf2x16, IrType::U32, {&pair})); + IrValue& packed = ir.Emit(IrOpcode::PackHalf2x16, IrType::U32, {&pair}); + if (nanPayloadUnused) packed.SetFlags(FloatConvertFlags{true}); + return IrU32(packed); } void TranslationContext::write16Bits(const RdnaOperand& operand, IrU32 value) { diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 291922047a..95f90d5c43 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -64,6 +64,8 @@ std::uint32_t TranslationContext::f32DenormalFlushFor(const RdnaInstruction& ins case RdnaOpcode::VMullitF32: flush = 3u; break; + case RdnaOpcode::VDivFmasF32: + return floatMode.has_value() && ((floatMode->floatMode >> 4u) & 2u) != 0u ? 0u : 2u; case RdnaOpcode::VMinF32: case RdnaOpcode::VMaxF32: case RdnaOpcode::VMin3F32: diff --git a/core/shader/recompiler/tests/RequestSerializerTests.cpp b/core/shader/recompiler/tests/RequestSerializerTests.cpp index 58f91c2f36..135e1969a1 100644 --- a/core/shader/recompiler/tests/RequestSerializerTests.cpp +++ b/core/shader/recompiler/tests/RequestSerializerTests.cpp @@ -316,7 +316,7 @@ int main() { Require(request.graphics->draw.indexAddress == 0xabcdef3400u && request.graphics->draw.indexCount == 57u && request.graphics->draw.indexElementBytes == 4u && request.graphics->draw.instanceCount == 9u); Require(request.target.float16DenormPreserve == preserve); auto upgraded = bytes; - upgraded[4] = 51u; + upgraded[4] = 53u; InsertMeshBarycentricFlag(upgraded); if (fixture.pixel) upgraded.insert(upgraded.begin() + 258u, {0u, 0u, 0u}); if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(upgraded); @@ -327,6 +327,8 @@ int main() { upgraded.insert(upgraded.end(), {0u, 0x10u, 0u, 0u}); upgraded.push_back(0u); upgraded.push_back(1u); + upgraded.push_back(0u); + upgraded.push_back(0u); Require(serializer.Serialize(request) == EncodeBase64(upgraded)); auto truncated = bytes; truncated.pop_back(); @@ -361,7 +363,7 @@ int main() { layerRejected = std::string_view(error.what()) == "RequestSerializer: truncated data"; } Require(layerRejected); - layered[4] = 51u; + layered[4] = 53u; InsertMeshBarycentricFlag(layered); layered.insert(layered.begin() + 259u, {0u, 0u}); InsertPixelDefaultsAfterFrontFace(layered); @@ -372,6 +374,8 @@ int main() { layered.insert(layered.end(), {0u, 0x10u, 0u, 0u}); layered.push_back(0u); layered.push_back(1u); + layered.push_back(0u); + layered.push_back(0u); Require(serializer.Serialize(layerReplay.request) == EncodeBase64(layered)); } } @@ -392,7 +396,7 @@ int main() { Require(replay.request.context.pixel->rasterPrimitive == ShaderRecompiler::RasterPrimitive::Rectangle); Require(replay.request.context.vertex->paClVsOutCntl == 0x00050a05u); Require(replay.request.target.float16DenormPreserve == preserve); - bytes[4] = 51u; + bytes[4] = 53u; InsertMeshBarycentricFlag(bytes); bytes.insert(bytes.begin() + 259u, {0u, 0u}); InsertPixelDefaultsAfterFrontFace(bytes); @@ -402,6 +406,8 @@ int main() { bytes.insert(bytes.end(), {0u, 0x10u, 0u, 0u}); bytes.push_back(0u); bytes.push_back(1u); + bytes.push_back(0u); + bytes.push_back(0u); Require(serializer.Serialize(replay.request) == EncodeBase64(bytes)); auto truncated23 = DecodeBase64(encoded23); @@ -527,7 +533,7 @@ int main() { if (fixture.pixel) main38.insert(main38.begin() + 259u, {0u, 0u}); if (fixture.pixel) InsertPixelDefaultsAfterFrontFace(main38, false, false); auto upgraded = main38; - upgraded[4] = 51u; + upgraded[4] = 53u; if (fixture.pixel) { const auto tail = PixelTailDefaults(true, true); upgraded.insert(upgraded.begin() + 317u, tail.begin(), tail.end()); @@ -540,6 +546,8 @@ int main() { upgraded.insert(upgraded.end(), {0u, 0x10u, 0u, 0u}); upgraded.push_back(0u); upgraded.push_back(1u); + upgraded.push_back(0u); + upgraded.push_back(0u); Require(serializer.Serialize(request) == EncodeBase64(upgraded)); const auto replay38 = serializer.Deserialize(EncodeBase64(main38)); Require(replay38.request.graphics.has_value() && replay38.request.graphics->tessellation.has_value() == fixture.tessellation); @@ -555,8 +563,14 @@ int main() { pointed.context.vertex->pointSize = 3.0f; pointed.context.vertex->pointSizeMin = 1.0f; pointed.context.vertex->pointSizeMax = 8.0f; - auto version51 = DecodeBase64(serializer.Serialize(pointed)); - Require(version51.size() > 17u && version51[4] == 51u); + auto version53 = DecodeBase64(serializer.Serialize(pointed)); + Require(version53.size() > 19u && version53[4] == 53u && version53.back() == 0u && version53[version53.size() - 2u] == 0u); + auto version52 = version53; + version52.pop_back(); + version52[4] = 52u; + auto version51 = version52; + version51.pop_back(); + version51[4] = 51u; auto version48 = version51; version48.pop_back(); version48[4] = 48u; @@ -576,7 +590,7 @@ int main() { version43.pop_back(); version43[4] = 43u; const auto replay43 = serializer.Deserialize(EncodeBase64(version43)); - Require(serializer.Serialize(replay43.request) == EncodeBase64(version51)); + Require(serializer.Serialize(replay43.request) == EncodeBase64(version53)); Require(replay43.request.graphics->tessellation->mergedHull == merged && replay43.request.context.vertex->pointList && replay43.request.context.vertex->pointSize == 3.0f && replay43.request.context.vertex->pointSizeMin == 1.0f && replay43.request.context.vertex->pointSizeMax == 8.0f); auto version42 = version43; version42.resize(version42.size() - 13u); @@ -603,7 +617,7 @@ int main() { const auto replay25 = serializer.Deserialize(EncodeBase64(main25)); Require(!fixture.pixel || (replay25.request.context.pixel->layerExported == layer && replay25.request.context.pixel->ditheredAlphaToCoverage == dither && replay25.request.context.pixel->reversedBlend == 0u)); Require(replay25.request.target.float16DenormPreserve == preserve && (!fixture.tessellation || replay25.request.graphics->tessellation->primitiveGroupSize == 37u)); - main25[4] = 51u; + main25[4] = 53u; InsertMeshBarycentricFlag(main25); if (fixture.pixel) { main25.insert(main25.begin() + 260u, 0u); @@ -616,6 +630,8 @@ int main() { main25.insert(main25.end(), {0u, 0x10u, 0u, 0u}); main25.push_back(0u); main25.push_back(1u); + main25.push_back(0u); + main25.push_back(0u); Require(serializer.Serialize(replay25.request) == EncodeBase64(main25)); } } @@ -633,8 +649,10 @@ int main() { const auto replay = serializer.Deserialize(encoded); Require(replay.request.target.float16DenormPreserve == preserve && replay.request.target.float32FlushToZero == !preserve && replay.request.target.float32DenormPreserve == preserve && replay.request.target.largePixelFunctionLdsBuffer == !preserve && replay.request.target.pixelFunctionStorageDwords == request.target.pixelFunctionStorageDwords); auto bytes = DecodeBase64(encoded); - Require(bytes.size() >= 8u && bytes[4] == 51u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u); + Require(bytes.size() >= 8u && bytes[4] == 53u && bytes[5] == 0u && bytes[6] == 0u && bytes[7] == 0u); auto version40 = bytes; + version40.pop_back(); + version40.pop_back(); version40.resize(version40.size() - sizeof(std::uint32_t)); version40.pop_back(); version40.pop_back(); @@ -645,7 +663,7 @@ int main() { const auto replay40 = serializer.Deserialize(EncodeBase64(version40)); Require(!replay40.request.target.float32DenormPreserve && replay40.request.target.float32FlushToZero == !preserve); auto future = bytes; - future[4] = 52u; + future[4] = 54u; bool futureRejected = false; try { static_cast(serializer.Deserialize(EncodeBase64(future))); @@ -691,6 +709,8 @@ int main() { const auto orderOffset = std::search(version42.begin(), version42.end(), pixel.sampleLocations.begin(), pixel.sampleLocations.end()) - version42.begin() + static_cast(pixel.sampleLocations.size() + 1u); Require(version42.end() - version42.begin() > orderOffset + static_cast(sizeof(pixel.sampleOrder)) && version42[static_cast(orderOffset)] == 0x10u); EraseDefaultTargetMerge(version42, orderOffset + static_cast(sizeof(pixel.sampleOrder))); + version42.pop_back(); + version42.pop_back(); version42.resize(version42.size() - 4u); version42.pop_back(); version42.pop_back(); @@ -714,6 +734,8 @@ int main() { Require(serializer.Deserialize(serializer.Serialize(request)).request.target.spannedWaves == spanned); request.target.float32FlushToZero = true; auto version35 = DecodeBase64(serializer.Serialize(request)); + version35.pop_back(); + version35.pop_back(); version35.resize(version35.size() - sizeof(std::uint32_t)); version35.pop_back(); version35.pop_back(); @@ -765,7 +787,7 @@ int main() { for (const std::uint8_t version : {32u, 33u, 34u, 35u, 36u, 37u, 38u}) { pointRequest.target.spannedWaves = spanned; auto legacy = DecodeBase64(serializer.Serialize(pointRequest)); - legacy.resize(legacy.size() - (version >= 36u ? 22u : version >= 35u ? 23u : 28u)); + legacy.resize(legacy.size() - (version >= 36u ? 24u : version >= 35u ? 25u : 30u)); legacy[4] = version; const auto replayLegacy = serializer.Deserialize(EncodeBase64(legacy)); Require(replayLegacy.request.target.spannedWaves == spanned); @@ -777,7 +799,7 @@ int main() { Require(!replayLegacy.request.context.vertex->pointList && replayLegacy.request.context.vertex->pointSize == 1.0f); Require(replayLegacy.request.context.vertex->pointSizeMin == 0.0f && replayLegacy.request.context.vertex->pointSizeMax == 0.0f); const auto upgradedLegacy = DecodeBase64(serializer.Serialize(replayLegacy.request)); - Require(upgradedLegacy[4] == 51u && upgradedLegacy.size() == legacy.size() + (version >= 36u ? 22u : version >= 35u ? 23u : 28u)); + Require(upgradedLegacy[4] == 53u && upgradedLegacy.size() == legacy.size() + (version >= 36u ? 24u : version >= 35u ? 25u : 30u)); } } @@ -788,6 +810,8 @@ int main() { auto legacy43 = DecodeBase64(serializer.Serialize(pointRequest)); const auto mergeOffset = std::search(legacy43.begin(), legacy43.end(), pixel.sampleLocations.begin(), pixel.sampleLocations.end()) - legacy43.begin() + static_cast(pixel.sampleLocations.size() + 1u + sizeof(pixel.sampleOrder)); EraseDefaultTargetMerge(legacy43, mergeOffset); + legacy43.pop_back(); + legacy43.pop_back(); legacy43.resize(legacy43.size() - 4u); legacy43.pop_back(); legacy43.pop_back(); @@ -805,7 +829,7 @@ int main() { pixel.sampleOrder = {}; pointRequest.context.pixel = pixel; auto legacy34 = DecodeBase64(serializer.Serialize(pointRequest)); - legacy34.resize(legacy34.size() - 28u); + legacy34.resize(legacy34.size() - 30u); const auto stencil34 = std::search(legacy34.begin(), legacy34.end(), pixel.sampleLocations.begin(), pixel.sampleLocations.begin() + 4) + pixel.sampleLocations.size(); const auto legacyPixelTail = static_cast(1u + sizeof(pixel.sampleOrder)); Require(legacy34.end() - stencil34 > legacyPixelTail && std::all_of(stencil34, stencil34 + legacyPixelTail, [](std::uint8_t value) { return value == 0u; })); @@ -820,7 +844,9 @@ int main() { request.target.fusedExactArithmetic = fused; request.target.largePixelFunctionLdsBuffer = false; auto bytes = DecodeBase64(serializer.Serialize(request)); - Require(serializer.Deserialize(EncodeBase64(bytes)).request.target.fusedExactArithmetic == fused && bytes[bytes.size() - 8u] == (fused ? 1u : 0u) && bytes[bytes.size() - 7u] == 0u); + Require(serializer.Deserialize(EncodeBase64(bytes)).request.target.fusedExactArithmetic == fused && bytes[bytes.size() - 10u] == (fused ? 1u : 0u) && bytes[bytes.size() - 9u] == 0u); + bytes.pop_back(); + bytes.pop_back(); bytes.pop_back(); bytes.pop_back(); const auto fusedMerge = std::search(bytes.begin(), bytes.end(), pixel.sampleLocations.begin(), pixel.sampleLocations.end()) - bytes.begin() + static_cast(pixel.sampleLocations.size() + 1u + sizeof(pixel.sampleOrder)); @@ -864,6 +890,8 @@ int main() { auto version46Dither = twoSample; version46Dither.pop_back(); version46Dither.pop_back(); + version46Dither.pop_back(); + version46Dither.pop_back(); version46Dither[4] = 46u; Require(rejectsDither(version46Dither)); version46Dither[ditherByte] = 1u; @@ -878,9 +906,15 @@ int main() { domain.target.tessellationEvaluationSubgroups = subgroups; const auto encoded = serializer.Serialize(domain); auto bytes = DecodeBase64(encoded); - Require(bytes.size() > 8u && bytes[4] == 51u && bytes.back() == (subgroups ? 1u : 0u)); + Require(bytes.size() > 9u && bytes[4] == 53u && bytes.back() == 0u && bytes[bytes.size() - 2u] == 0u && bytes[bytes.size() - 3u] == (subgroups ? 1u : 0u)); Require(serializer.Deserialize(encoded).request.target.tessellationEvaluationSubgroups == subgroups); bytes.pop_back(); + bytes[4] = 52u; + Require(serializer.Deserialize(EncodeBase64(bytes)).request.target.tessellationEvaluationSubgroups == subgroups); + bytes.pop_back(); + bytes[4] = 51u; + Require(serializer.Deserialize(EncodeBase64(bytes)).request.target.tessellationEvaluationSubgroups == subgroups); + bytes.pop_back(); bool truncatedRejected = false; try { static_cast(serializer.Deserialize(EncodeBase64(bytes))); @@ -892,8 +926,10 @@ int main() { const auto replay49 = serializer.Deserialize(EncodeBase64(bytes)); Require(replay49.request.shader.stage == ShaderRecompiler::ShaderStage::TessellationEvaluation && replay49.request.target.tessellationEvaluationSubgroups); auto upgraded = bytes; - upgraded[4] = 51u; + upgraded[4] = 53u; upgraded.push_back(1u); + upgraded.push_back(0u); + upgraded.push_back(0u); Require(serializer.Serialize(replay49.request) == EncodeBase64(upgraded)); for (auto skipped : {bytes, upgraded}) { skipped[4] = 50u; @@ -906,6 +942,32 @@ int main() { Require(skippedRejected); } } + std::string hostFirstEncoded[2]; + for (const bool hostFirst : {false, true}) { + request.target.hostInterpolationFirst = hostFirst; + hostFirstEncoded[hostFirst] = serializer.Serialize(request); + const auto replay = serializer.Deserialize(hostFirstEncoded[hostFirst]); + Require(replay.request.target.hostInterpolationFirst == hostFirst && serializer.Serialize(replay.request) == hostFirstEncoded[hostFirst]); + auto bytes = DecodeBase64(hostFirstEncoded[hostFirst]); + Require(bytes[4] == 53u && bytes.back() == 0u && bytes[bytes.size() - 2u] == (hostFirst ? 1u : 0u)); + bytes.pop_back(); + bytes[4] = 52u; + Require(serializer.Deserialize(EncodeBase64(bytes)).request.target.hostInterpolationFirst == hostFirst); + bytes.pop_back(); + bytes[4] = 51u; + Require(!serializer.Deserialize(EncodeBase64(bytes)).request.target.hostInterpolationFirst); + } + Require(hostFirstEncoded[0] != hostFirstEncoded[1]); + request.target.hostInterpolationFirst = false; + for (const bool minLod : {false, true}) { + request.target.shaderImageMinLod = minLod; + auto bytes = DecodeBase64(serializer.Serialize(request)); + Require(serializer.Deserialize(EncodeBase64(bytes)).request.target.shaderImageMinLod == minLod && bytes[4] == 53u && bytes.back() == (minLod ? 1u : 0u)); + bytes.pop_back(); + bytes[4] = 52u; + Require(!serializer.Deserialize(EncodeBase64(bytes)).request.target.shaderImageMinLod); + } + request.target.shaderImageMinLod = false; return 0; } diff --git a/core/shader/recompiler/tests/SpannedUniformityTests.cpp b/core/shader/recompiler/tests/SpannedUniformityTests.cpp new file mode 100644 index 0000000000..7e1915714b --- /dev/null +++ b/core/shader/recompiler/tests/SpannedUniformityTests.cpp @@ -0,0 +1,314 @@ +#include "SpirvBackend/SpirvSpannedUniformity.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +enum Id : std::uint32_t { + Void = 1u, + Bool, + Uint, + FunctionType, + InputPointer, + Index, + Two, + Three, + Semantics, + WorkgroupPointer, + WorkgroupArrayType, + WorkgroupArrayPointer, + Exchange, + FunctionPointer, + True, + Main, + Helper, + StoragePointer, + Storage, + PhysicalPointer, + First = 100u, +}; + +class Module { +public: + Module() { + words = {spv::MagicNumber, 0x00010300u, 0u, 1000u, 0u}; + emit(spv::OpCapability, {spv::CapabilityShader}); + emit(spv::OpMemoryModel, {spv::AddressingModelLogical, spv::MemoryModelGLSL450}); + emit(spv::OpEntryPoint, {spv::ExecutionModelGLCompute, Main, 0x6e69616du, 0u, Index}); + emit(spv::OpExecutionMode, {Main, spv::ExecutionModeLocalSize, 64u, 1u, 1u}); + emit(spv::OpDecorate, {Index, spv::DecorationBuiltIn, spv::BuiltInLocalInvocationIndex}); + emit(spv::OpTypeVoid, {Void}); + emit(spv::OpTypeBool, {Bool}); + emit(spv::OpTypeInt, {Uint, 32u, 0u}); + emit(spv::OpTypeFunction, {FunctionType, Void}); + emit(spv::OpTypePointer, {InputPointer, spv::StorageClassInput, Uint}); + emit(spv::OpVariable, {InputPointer, Index, spv::StorageClassInput}); + emit(spv::OpConstant, {Uint, Two, 2u}); + emit(spv::OpConstant, {Uint, Three, 3u}); + emit(spv::OpConstant, {Uint, Semantics, 264u}); + emit(spv::OpConstantTrue, {Bool, True}); + emit(spv::OpTypePointer, {WorkgroupPointer, spv::StorageClassWorkgroup, Uint}); + emit(spv::OpTypeArray, {WorkgroupArrayType, Uint, Three}); + emit(spv::OpTypePointer, {WorkgroupArrayPointer, spv::StorageClassWorkgroup, WorkgroupArrayType}); + emit(spv::OpVariable, {WorkgroupArrayPointer, Exchange, spv::StorageClassWorkgroup}); + emit(spv::OpTypePointer, {FunctionPointer, spv::StorageClassFunction, Uint}); + emit(spv::OpTypePointer, {StoragePointer, spv::StorageClassStorageBuffer, Uint}); + emit(spv::OpVariable, {StoragePointer, Storage, spv::StorageClassStorageBuffer}); + emit(spv::OpTypePointer, {PhysicalPointer, spv::StorageClassPhysicalStorageBuffer, Uint}); + } + + void emit(spv::Op op, std::initializer_list operands) { + words.push_back((static_cast(operands.size() + 1u) << 16u) | op); + words.insert(words.end(), operands); + } + + void function(std::uint32_t id) { + emit(spv::OpFunction, {Void, id, spv::FunctionControlMaskNone, FunctionType}); + } + + void barrier() { + emit(spv::OpControlBarrier, {Two, Two, Semantics}); + } + + void lane(std::uint32_t result) { + emit(spv::OpLoad, {Uint, result, Index}); + } + + void end() { + emit(spv::OpReturn, {}); + emit(spv::OpFunctionEnd, {}); + } + + std::vector words; +}; + +bool failed = false; + +void expect(const char* name, const Module& module, bool rejected) { + const auto reason = ShaderRecompiler::SpannedBarrierInDivergentFlow(module.words); + if (reason.has_value() == rejected) return; + std::fprintf(stderr, "%s: expected %s, got %s\n", name, rejected ? "a rejection" : "no rejection", reason ? reason->c_str() : "none"); + failed = true; +} + +Module selection(bool perLane) { + Module module; + module.function(Main); + module.emit(spv::OpLabel, {First}); + module.lane(First + 1u); + module.emit(spv::OpULessThan, {Bool, First + 2u, perLane ? First + 1u : Two, Three}); + module.emit(spv::OpSelectionMerge, {First + 4u, spv::SelectionControlMaskNone}); + module.emit(spv::OpBranchConditional, {First + 2u, First + 3u, First + 4u}); + module.emit(spv::OpLabel, {First + 3u}); + module.barrier(); + module.emit(spv::OpBranch, {First + 4u}); + module.emit(spv::OpLabel, {First + 4u}); + module.barrier(); + module.end(); + return module; +} + +Module loop(bool laneExit, bool barrierInSelection) { + Module module; + module.function(Main); + module.emit(spv::OpLabel, {First}); + module.lane(First + 1u); + module.emit(spv::OpULessThan, {Bool, First + 2u, First + 1u, Three}); + module.emit(spv::OpBranch, {First + 3u}); + module.emit(spv::OpLabel, {First + 3u}); + module.emit(spv::OpLoopMerge, {First + 6u, First + 5u, spv::LoopControlMaskNone}); + module.emit(spv::OpBranch, {First + 4u}); + module.emit(spv::OpLabel, {First + 4u}); + if (barrierInSelection) { + module.emit(spv::OpSelectionMerge, {First + 8u, spv::SelectionControlMaskNone}); + module.emit(spv::OpBranchConditional, {First + 2u, First + 7u, First + 8u}); + module.emit(spv::OpLabel, {First + 7u}); + module.emit(spv::OpBranch, {First + 8u}); + module.emit(spv::OpLabel, {First + 8u}); + } + module.barrier(); + module.emit(spv::OpBranch, {First + 5u}); + module.emit(spv::OpLabel, {First + 5u}); + module.emit(spv::OpBranchConditional, {laneExit ? First + 2u : True, First + 3u, First + 6u}); + module.emit(spv::OpLabel, {First + 6u}); + module.end(); + return module; +} + +Module call(bool perLane) { + Module module; + module.function(Helper); + module.emit(spv::OpLabel, {First + 10u}); + module.barrier(); + module.end(); + module.function(Main); + module.emit(spv::OpLabel, {First}); + module.lane(First + 1u); + module.emit(spv::OpULessThan, {Bool, First + 2u, perLane ? First + 1u : Two, Three}); + module.emit(spv::OpSelectionMerge, {First + 4u, spv::SelectionControlMaskNone}); + module.emit(spv::OpBranchConditional, {First + 2u, First + 3u, First + 4u}); + module.emit(spv::OpLabel, {First + 3u}); + module.emit(spv::OpFunctionCall, {Void, First + 5u, Helper}); + module.emit(spv::OpBranch, {First + 4u}); + module.emit(spv::OpLabel, {First + 4u}); + module.end(); + return module; +} + +Module early(bool perLane, spv::Op exit) { + Module module; + module.function(Main); + module.emit(spv::OpLabel, {First}); + module.lane(First + 1u); + module.emit(spv::OpULessThan, {Bool, First + 2u, perLane ? First + 1u : Two, Three}); + module.emit(spv::OpBranch, {First + 10u}); + module.emit(spv::OpLabel, {First + 10u}); + module.emit(spv::OpLoopMerge, {First + 13u, First + 12u, spv::LoopControlMaskNone}); + module.emit(spv::OpBranch, {First + 11u}); + module.emit(spv::OpLabel, {First + 11u}); + module.emit(spv::OpSelectionMerge, {First + 4u, spv::SelectionControlMaskNone}); + module.emit(spv::OpBranchConditional, {First + 2u, First + 3u, First + 4u}); + module.emit(spv::OpLabel, {First + 3u}); + if (exit == spv::OpReturn) module.emit(spv::OpReturn, {}); + else module.emit(spv::OpBranch, {First + 12u}); + module.emit(spv::OpLabel, {First + 4u}); + module.barrier(); + module.emit(spv::OpBranch, {First + 12u}); + module.emit(spv::OpLabel, {First + 12u}); + module.emit(spv::OpBranchConditional, {True, First + 10u, First + 13u}); + module.emit(spv::OpLabel, {First + 13u}); + module.end(); + return module; +} + +Module unmergedContinue(bool perLane) { + Module module; + module.function(Main); + module.emit(spv::OpLabel, {First}); + module.lane(First + 1u); + module.emit(spv::OpULessThan, {Bool, First + 2u, perLane ? First + 1u : Two, Three}); + module.emit(spv::OpBranch, {First + 10u}); + module.emit(spv::OpLabel, {First + 10u}); + module.emit(spv::OpLoopMerge, {First + 13u, First + 12u, spv::LoopControlMaskNone}); + module.emit(spv::OpBranch, {First + 11u}); + module.emit(spv::OpLabel, {First + 11u}); + module.emit(spv::OpBranchConditional, {First + 2u, First + 12u, First + 14u}); + module.emit(spv::OpLabel, {First + 14u}); + module.barrier(); + module.emit(spv::OpBranch, {First + 12u}); + module.emit(spv::OpLabel, {First + 12u}); + module.emit(spv::OpBranchConditional, {True, First + 10u, First + 13u}); + module.emit(spv::OpLabel, {First + 13u}); + module.end(); + return module; +} + +Module joined(const std::function& condition) { + Module module; + module.function(Main); + module.emit(spv::OpLabel, {First}); + module.emit(spv::OpVariable, {FunctionPointer, First + 20u, spv::StorageClassFunction}); + module.lane(First + 1u); + condition(module); + module.emit(spv::OpSelectionMerge, {First + 9u, spv::SelectionControlMaskNone}); + module.emit(spv::OpBranchConditional, {First + 8u, First + 7u, First + 9u}); + module.emit(spv::OpLabel, {First + 7u}); + module.barrier(); + module.emit(spv::OpBranch, {First + 9u}); + module.emit(spv::OpLabel, {First + 9u}); + module.end(); + return module; +} + +void laneSplit(Module& module, std::uint32_t merge) { + module.emit(spv::OpULessThan, {Bool, First + 2u, First + 1u, Three}); + module.emit(spv::OpSelectionMerge, {merge, spv::SelectionControlMaskNone}); + module.emit(spv::OpBranchConditional, {First + 2u, First + 3u, merge}); + module.emit(spv::OpLabel, {First + 3u}); +} + +} + +int main() { + expect("a barrier under a lane comparison", selection(true), true); + expect("a barrier under a constant comparison", selection(false), false); + expect("a barrier in a loop a lane value leaves", loop(true, false), true); + expect("a barrier in a uniform loop after a per-lane selection", loop(false, true), false); + expect("a call reaching a barrier under a lane comparison", call(true), true); + expect("a barrier after a per-lane return", early(true, spv::OpReturn), true); + expect("a barrier after a per-lane continue", early(true, spv::OpBranch), true); + expect("a barrier after a uniform return", early(false, spv::OpReturn), false); + expect("a barrier after a per-lane continue without a selection merge", unmergedContinue(true), true); + expect("a barrier after a uniform continue without a selection merge", unmergedContinue(false), false); + expect("a call reaching a barrier under a constant comparison", call(false), false); + expect("a barrier under a phi joining a per-lane selection", joined([](Module& module) { + laneSplit(module, First + 4u); + module.emit(spv::OpBranch, {First + 4u}); + module.emit(spv::OpLabel, {First + 4u}); + module.emit(spv::OpPhi, {Bool, First + 8u, True, First + 3u, True, First}); + }), true); + expect("a barrier under a variable stored on one side of a per-lane selection", joined([](Module& module) { + module.emit(spv::OpStore, {First + 20u, Two}); + laneSplit(module, First + 4u); + module.emit(spv::OpStore, {First + 20u, Three}); + module.emit(spv::OpBranch, {First + 4u}); + module.emit(spv::OpLabel, {First + 4u}); + module.emit(spv::OpLoad, {Uint, First + 5u, First + 20u}); + module.emit(spv::OpIEqual, {Bool, First + 8u, First + 5u, Two}); + }), true); + expect("a barrier under an atomic result", joined([](Module& module) { + module.emit(spv::OpAccessChain, {WorkgroupPointer, First + 5u, Exchange, Two}); + module.emit(spv::OpAtomicIAdd, {Uint, First + 6u, First + 5u, Two, Semantics, Three}); + module.emit(spv::OpIEqual, {Bool, First + 8u, First + 6u, Two}); + }), true); + expect("a barrier under a workgroup word read at a uniform index", joined([](Module& module) { + module.emit(spv::OpAccessChain, {WorkgroupPointer, First + 5u, Exchange, Two}); + module.emit(spv::OpLoad, {Uint, First + 6u, First + 5u}); + module.emit(spv::OpIEqual, {Bool, First + 8u, First + 6u, Two}); + }), false); + expect("a barrier under a workgroup word read at a uniform index after a store", joined([](Module& module) { + module.emit(spv::OpAccessChain, {WorkgroupPointer, First + 5u, Exchange, Two}); + module.emit(spv::OpStore, {First + 5u, Three}); + module.emit(spv::OpLoad, {Uint, First + 6u, First + 5u}); + module.emit(spv::OpIEqual, {Bool, First + 8u, First + 6u, Two}); + }), true); + expect("a barrier under a workgroup word read at a uniform index after a store and a barrier", joined([](Module& module) { + module.emit(spv::OpAccessChain, {WorkgroupPointer, First + 5u, Exchange, Two}); + module.emit(spv::OpStore, {First + 5u, Three}); + module.barrier(); + module.emit(spv::OpLoad, {Uint, First + 6u, First + 5u}); + module.emit(spv::OpIEqual, {Bool, First + 8u, First + 6u, Two}); + }), false); + expect("a barrier under a workgroup word read at a uniform index after a store on one path past the barrier", joined([](Module& module) { + module.emit(spv::OpAccessChain, {WorkgroupPointer, First + 5u, Exchange, Two}); + module.barrier(); + module.emit(spv::OpSelectionMerge, {First + 4u, spv::SelectionControlMaskNone}); + module.emit(spv::OpBranchConditional, {True, First + 3u, First + 4u}); + module.emit(spv::OpLabel, {First + 3u}); + module.emit(spv::OpStore, {First + 5u, Three}); + module.emit(spv::OpBranch, {First + 4u}); + module.emit(spv::OpLabel, {First + 4u}); + module.emit(spv::OpLoad, {Uint, First + 6u, First + 5u}); + module.emit(spv::OpIEqual, {Bool, First + 8u, First + 6u, Two}); + }), true); + expect("a barrier under a buffer word read at a uniform index", joined([](Module& module) { + module.emit(spv::OpLoad, {Uint, First + 6u, Storage}); + module.emit(spv::OpIEqual, {Bool, First + 8u, First + 6u, Two}); + }), false); + expect("a barrier under a buffer word read at a uniform index after a store through a device address", joined([](Module& module) { + module.emit(spv::OpConvertUToPtr, {PhysicalPointer, First + 10u, Two}); + module.emit(spv::OpStore, {First + 10u, Three}); + module.emit(spv::OpLoad, {Uint, First + 6u, Storage}); + module.emit(spv::OpIEqual, {Bool, First + 8u, First + 6u, Two}); + }), true); + expect("a barrier under a workgroup word read at a lane index", joined([](Module& module) { + module.emit(spv::OpAccessChain, {WorkgroupPointer, First + 5u, Exchange, First + 1u}); + module.emit(spv::OpLoad, {Uint, First + 6u, First + 5u}); + module.emit(spv::OpIEqual, {Bool, First + 8u, First + 6u, Two}); + }), true); + return failed ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/demo/web/tests/archive-root.test.mjs b/demo/web/tests/archive-root.test.mjs index b7467885f0..e598bd8a13 100644 --- a/demo/web/tests/archive-root.test.mjs +++ b/demo/web/tests/archive-root.test.mjs @@ -219,7 +219,7 @@ for (const directories of [[], parents, [...parents].reverse()]) { const { downloadLibraries } = await import(pathToFileURL(path.join(process.argv[2], "worker.mjs"))); const bytes = Uint8Array.from({ length: 32 }, (_, index) => index); const sha256 = [...new Uint8Array(await crypto.subtle.digest("SHA-256", bytes))].map(byte => byte.toString(16).padStart(2, "0")).join(""); - const asset = { path: "libraries/linux-1234.zip", size: bytes.length, expandedBytes: 100, sha256 }; + const asset = { path: `libraries/linux-${sha256}.zip`, size: bytes.length, expandedBytes: 100, sha256 }; let cancelled = false; let pulled = 0; const configureDownload = (chunks, size = bytes.length) => { diff --git a/demo/web/tests/archive.test.mjs b/demo/web/tests/archive.test.mjs index 9b798f7798..4448c6f651 100644 --- a/demo/web/tests/archive.test.mjs +++ b/demo/web/tests/archive.test.mjs @@ -210,10 +210,10 @@ globalThis.self = { postMessage: message => workerMessages.push(message) }; const worker = await import(site("worker.mjs")); const libraryArchive = await zip([["libs/system.prx", new Uint8Array([1, 2, 3])]]); const libraryBytes = new Uint8Array(await libraryArchive.arrayBuffer()); -const libraryPath = "libraries/windows-acde1234.zip"; const librarySha256 = [...new Uint8Array(await crypto.subtle.digest("SHA-256", libraryBytes))] .map(byte => byte.toString(16).padStart(2, "0")) .join(""); +const libraryPath = `libraries/windows-${librarySha256}.zip`; const release = { tag: "v1", platforms: { diff --git a/demo/web/tests/worker-manifest-fixture.mjs b/demo/web/tests/worker-manifest-fixture.mjs new file mode 100644 index 0000000000..d516cf32ab --- /dev/null +++ b/demo/web/tests/worker-manifest-fixture.mjs @@ -0,0 +1,61 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import vm from "node:vm"; +import { webcrypto } from "node:crypto"; + +const bytes = new Uint8Array([1, 2, 3]); +const digest = Buffer.from(await webcrypto.subtle.digest("SHA-256", bytes)).toString("hex"); +const valid = target => ({ tag: "v1.2.3", platforms: { [target]: { size: 3, expandedBytes: 100, sha256: digest, path: `libraries/${target}-${digest}.zip` } } }); + +async function load(release, options = {}) { + const requests = []; + let cancelled = false; + const context = vm.createContext({ console, Uint8Array, Blob, ReadableStream, WritableStream, performance, crypto: webcrypto, + self: { postMessage() {} }, fetch: async url => { + requests.push(url); + if (url === "./release.json") return { ok: true, json: async () => release }; + if (url.startsWith("https:")) return options.rateLimit ? { ok: false, status: 403, headers: new Map([["x-ratelimit-remaining", "0"]]) } : { ok: true, json: async () => ({ tag_name: release.tag }) }; + const contents = options.contents ?? bytes; + return { ok: true, body: new ReadableStream({ start(controller) { controller.enqueue(contents); if (!options.keepOpen) controller.close(); }, cancel() { cancelled = true; } }) }; + } }); + const module = new vm.SourceTextModule(readFileSync(new URL("../worker.mjs", import.meta.url), "utf8"), { context }); + await module.link(async name => { + const exports = name === "./batch.mjs" ? { batchMemoryEstimate() {}, batchOutputDirectory() {}, MEMORY_LIMIT: 0 } : { Archive: class {}, CONVERSION_LIMIT: 0, createPackageWriter() {}, packageArchive() {} }; + return new vm.SyntheticModule(Object.keys(exports), function() { for (const [key, value] of Object.entries(exports)) this.setExport(key, value); }, { context }); + }); + await module.evaluate(); + return { api: module.namespace, requests, cancelled: () => cancelled }; +} + +const invalid = [null, [], {}, { tag: "v1", platforms: null }, { ...valid("linux"), tag: "" }, { ...valid("linux"), tag: "v1." }]; +for (const [key, value] of [["path", `libraries/windows-${digest}.zip`], ["path", "libraries/linux-a.zip"], ["sha256", null], ["sha256", "a".repeat(63)], ["sha256", 123], ["size", 0], ["size", -1], ["size", 1.5], ["size", "3"], ["size", 512 * 1024 * 1024 + 1], ["expandedBytes", -1], ["expandedBytes", 1.5], ["expandedBytes", null], ["expandedBytes", 512 * 1024 * 1024 + 1]]) { + const release = valid("linux"); + release.platforms.linux[key] = value; + invalid.push(release); +} +for (const release of invalid) { + const { api, requests } = await load(release); + await assert.rejects(api.downloadLibraries("linux"), /Invalid release manifest/); + assert.deepEqual(requests, ["./release.json"]); +} +const unsupported = await load(valid("linux")); +await assert.rejects(unsupported.api.downloadLibraries("macos"), /Invalid library target/); +assert.deepEqual(unsupported.requests, []); +let validCount = 0; +for (const target of ["linux", "windows"]) { + for (const rateLimit of [false, true]) { + const release = valid(target); + const { api, requests } = await load(release, { rateLimit }); + const downloaded = await api.downloadLibraries(target); + assert.equal(downloaded.expandedBytes, 100); + assert.deepEqual(new Uint8Array(await downloaded.file.arrayBuffer()), bytes); + assert.equal(requests.at(-1), release.platforms[target].path); + validCount++; + } +} +const corrupt = await load(valid("linux"), { contents: new Uint8Array([3, 2, 1]) }); +await assert.rejects(corrupt.api.downloadLibraries("linux"), /checksum mismatch/); +const oversized = await load(valid("linux"), { contents: new Uint8Array([1, 2, 3, 4]), keepOpen: true }); +await assert.rejects(oversized.api.downloadLibraries("linux"), /download is incomplete/); +assert.equal(oversized.cancelled(), true); +console.log(JSON.stringify({ invalid: invalid.length + 1, valid: validCount })); diff --git a/demo/web/tests/worker-manifest.test.mjs b/demo/web/tests/worker-manifest.test.mjs new file mode 100644 index 0000000000..3a445c247d --- /dev/null +++ b/demo/web/tests/worker-manifest.test.mjs @@ -0,0 +1,12 @@ +import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import { promisify } from "node:util"; +import { fileURLToPath } from "node:url"; +import { test } from "node:test"; + +test("library manifest validation runs before downloading and preserves valid downloads", async () => { + const { stdout } = await promisify(execFile)(process.execPath, ["--experimental-vm-modules", fileURLToPath(new URL("./worker-manifest-fixture.mjs", import.meta.url))], { timeout: 15000 }); + const result = JSON.parse(stdout); + assert.ok(result.invalid >= 15); + assert.equal(result.valid, 4); +}); diff --git a/demo/web/worker.mjs b/demo/web/worker.mjs index be35844090..4adc71b8ee 100644 --- a/demo/web/worker.mjs +++ b/demo/web/worker.mjs @@ -31,12 +31,17 @@ function reportFor(item, index) { } export async function downloadLibraries(target) { + if (target !== "linux" && target !== "windows") throw new Error("Invalid library target"); report({ phase: "Downloading system libraries" }); const response = await fetch("./release.json", { cache: "no-cache" }); if (!response.ok) throw new Error(`Cannot load release manifest (${response.status})`); const release = await response.json(); - const asset = release.platforms[target]; - if (!asset || !Number.isSafeInteger(asset.size) || asset.size < 0 || asset.size > 512 * 1024 * 1024 || !/^libraries\/(windows|linux)-[a-f0-9]+\.zip$/u.test(asset.path) || !/^[a-f0-9]{64}$/u.test(asset.sha256)) throw new Error("Invalid release manifest"); + const asset = release?.platforms?.[target]; + if (!release || typeof release.tag !== "string" || !/^v[0-9A-Za-z][0-9A-Za-z._-]*$/u.test(release.tag) || release.tag.endsWith(".") + || !asset || typeof asset.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(asset.sha256) + || asset.path !== `libraries/${target}-${asset.sha256}.zip` + || !Number.isSafeInteger(asset.size) || asset.size <= 0 || asset.size > 512 * 1024 * 1024 + || !Number.isSafeInteger(asset.expandedBytes) || asset.expandedBytes < 0 || asset.expandedBytes > 512 * 1024 * 1024) throw new Error("Invalid release manifest"); const latestResponse = await fetch("https://api.github.com/repos/boykopovar/AnyPS5/releases/latest", { headers: { Accept: "application/vnd.github+json" } }); if (!latestResponse.ok) { const rateLimited = latestResponse.status === 429 || (latestResponse.status === 403 && latestResponse.headers.get("x-ratelimit-remaining") === "0"); diff --git a/docs/dev/BUILD.md b/docs/dev/BUILD.md index 5545fb58f8..b2f12324e4 100644 --- a/docs/dev/BUILD.md +++ b/docs/dev/BUILD.md @@ -2,7 +2,7 @@ ## Relinker only -The relinker can be built without initializing submodules or configuring SDL, Vulkan, FFmpeg, FreeType or the shader recompiler. It requires CMake, a C++20 compiler and a build tool. Python 3.9 or newer enables the Python regression tests. +The relinker can be built without initializing submodules or configuring SDL, Vulkan, FFmpeg, FreeType or the shader recompiler. It requires CMake, a C++20 compiler and a build tool. Python 3.9 or newer enables the Python regression tests; Python 3.11 or newer also registers `validate_patch_catalog`, which needs `tomllib`. ```sh cmake -S . -B build-relinker -G Ninja -DCMAKE_BUILD_TYPE=Release -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON @@ -130,6 +130,7 @@ Project switches accept `ON` or `OFF`: | `-DANYPS5_ENABLE_ASAN=ON` | `OFF` | Build with AddressSanitizer on Linux and move the guest arena out of ASan's shadow gap. | | `-DANYPS5_ENABLE_TSAN=ON` | `OFF` | Build with ThreadSanitizer on Linux and link executables dynamically. Cannot be combined with `-DANYPS5_ENABLE_ASAN=ON`. | | `-DANYPS5_USE_SYSTEM_DEPS=ON` | `OFF` | Resolve SDL2, freetype, Vulkan-Headers, SPIRV-Headers, glslang and SPIRV-Tools from the system instead of the submodules. | +| `-DANYPS5_RELINKER_CLI=OFF` | `ON` | Skip the native relinker command line (`ANYPS5_BUILD_WASM` turns it off). | | `-DAPS5_ENABLE_TIMING_LOG=ON` | `OFF` | Compile frame timing logging. | | `-DAPS5_ENABLE_TRACY=ON` | `OFF` | Build the [Tracy](#tracy) profiler client into the agc driver. | | `-DAGC_BUILD_VISUAL_TEST=ON` | `OFF` | Build the standalone AGC SPIR-V visual test. | @@ -150,11 +151,13 @@ Build configuration parameters: | `-DCMAKE_C_COMPILER_LAUNCHER=ccache` | Optional C compiler cache; requires `ccache`. | | `-DCMAKE_CXX_COMPILER_LAUNCHER=ccache` | Optional C++ compiler cache; requires `ccache`. | | `-DFFMPEG_PREBUILT_DIR=` | Unpacked FFmpeg package for the target platform; empty by default. | +| `-DANYPS5_NODE=` | Node.js 26 or newer that runs the memory64 Emscripten tests `guest_long_double` and `emscripten_module_unwind`; `node` by default, Emscripten builds only. | +| `-DANYPS5_VIDEO_OUT_TEST_DRIVER=` | SDL video driver for the VideoOut flip tests; `windows` on Windows and `offscreen` elsewhere. | | `-DANYPS5_AGC_EXECUTION_TESTS=` | Semicolon-separated file names, without `.cpp`, of the `core/libs/prx/libSceAgcDriver/tests/execution` tests to build. Every other execution test is built from a stub that prints `skipped, this build leaves the test out with ANYPS5_AGC_EXECUTION_TESTS` and exits 77. A name with no source there fails the configure. Empty by default, which builds them all. | 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`, `LibAtrac9`, `ogg` and `vorbis` 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`, `vorbis`, `opus`, `libusb-cmake`, `mbedtls`, `vgmstream`, `zstd`, `ZArchive` and, on Windows, `mimalloc` always stay bundled and still need `git submodule update --init`. ## HTTP locale tests @@ -258,7 +261,7 @@ 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. +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, on Windows only, `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, on Windows only, `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 @@ -296,7 +299,7 @@ The summary's scope durations are wall time aggregated across threads. Nested sc The shader preparation threads are traced as well, `shader prepare`, with a buffer for 32,768 events each. A wait for the GPU mutex on a traced thread is a `GpuMutex.wait.` slice (`dispatch`, `draw`, `present`, `flush` and so on, as on the `[lock]` line). -The `gpu` track holds one `batch` slice per recorder batch, from the batch's timestamp pair. With `APS5_PROFILE_GPU=1` it also holds a slice per command class range, under the class names of the `[gputime]` line (`draw`, `copy`, `fill`, `present-blit`, ...); program ranges are left out. The track keeps up to 262,144 ranges; more set `trace_dropped`. The GPU clock is mapped onto the CPU clock with calibrated timestamps (`VK_KHR_calibrated_timestamps` or the EXT version, enabled only when a trace switch is set) taken when the window opens and when it closes, so the two clocks are matched at both ends of the window. A batch is added when the driver reads its timestamps, after its fence signals, so the batches of the last frames of the window, which complete after it closes, are missing. Without calibrated timestamps the trace has no `gpu` track, and the `[trace]` line says so. +The `gpu` track holds one `batch` slice per recorder batch, from the batch's timestamp pair. With `APS5_PROFILE_GPU=1` it also holds a slice per command class range, under the class names of the `[gputime]` line (`draw`, `copy`, `fill`, `present-blit`, ...); program ranges are left out. The track keeps up to 262,144 ranges; more set `trace_dropped`. The GPU clock is mapped onto the CPU clock with calibrated timestamps (`VK_KHR_calibrated_timestamps` or the EXT version, enabled only when a trace switch is set) against the performance counter on Windows and `CLOCK_MONOTONIC` elsewhere, or `CLOCK_MONOTONIC_RAW` on a device that calibrates against no `CLOCK_MONOTONIC` (MoltenVK), taken when the window opens and when it closes, so the two clocks are matched at both ends of the window. A batch is added when the driver reads its timestamps, after its fence signals, so the batches of the last frames of the window, which complete after it closes, are missing. Without calibrated timestamps the trace has no `gpu` track, and the `[trace]` line says so. With any of the three switches set, the recorder writes a timestamp pair per batch for the whole run, as with `APS5_PROFILE_DRAW`. Without them, a `PerformanceTimer`, a GPU mutex acquisition and a read of batch timestamps each cost one more predictable branch. @@ -368,7 +371,7 @@ With `APS5_PROFILE_DRAW` set (any value), the driver prints a profile line on st ## Driver thread priority -Set `APS5_THREAD_PRIORITY=high` to raise the AGC driver's queue workers and the video output presenter above the title's threads. On Linux the threads get nice `APS5_THREAD_NICE` (default -10) through rtkit; on Windows they get `THREAD_PRIORITY_HIGHEST`. `APS5_THREAD_PRIORITY=rt` asks rtkit for real-time priority `APS5_THREAD_RT` (default 10, at most rtkit's `MaxRealtimePriority`) under rtkit's policy, `SCHED_RR` by default; on Windows it sets `THREAD_PRIORITY_TIME_CRITICAL`. rtkit only grants it when the process's hard `RLIMIT_RTTIME` is at most its `RTTimeUSecMax` (200000 us by default), so a hard limit above `APS5_THREAD_RTTIME_US` (default 200000, capped at `RTTimeUSecMax`) is lowered to it and the soft limit to at most half of it. The hard limit cannot be raised again and stays lowered for the whole process even if rtkit then refuses; see [technical debt](TechnicalDebt.md#functional). There is no fallback between the modes: when rtkit or `SetThreadPriority` refuses, a queue worker reports the refusal as a driver failure, which the next submission, wait or shutdown rethrows, and the presenter's start throws it. Each raised thread logs its priority once as `[thread-priority]`. The setting is `off` by default; a value other than `off`, `high` or `rt` is reported the same way as a refusal. +Set `APS5_THREAD_PRIORITY=high` to raise the AGC driver's queue workers and the video output presenter above the title's threads. On Linux the threads get nice `APS5_THREAD_NICE` (default -10) through rtkit; on Windows they get `THREAD_PRIORITY_HIGHEST`. `APS5_THREAD_PRIORITY=rt` asks rtkit for real-time priority `APS5_THREAD_RT` (default 10, at most rtkit's `MaxRealtimePriority`) under rtkit's policy, `SCHED_RR` by default; on Windows it sets `THREAD_PRIORITY_TIME_CRITICAL`. rtkit only grants it when the process's hard `RLIMIT_RTTIME` is at most its `RTTimeUSecMax` (200000 us by default), so a hard limit above `APS5_THREAD_RTTIME_US` (default 200000, capped at `RTTimeUSecMax`) is lowered to it and the soft limit to at most half of it. The hard limit cannot be raised again and stays lowered for the whole process even if rtkit then refuses; see [technical debt](debt/libSceAgcDriver.md#functional). There is no fallback between the modes: when rtkit or `SetThreadPriority` refuses, a queue worker reports the refusal as a driver failure, which the next submission, wait or shutdown rethrows, and the presenter's start throws it. Each raised thread logs its priority once as `[thread-priority]`. The setting is `off` by default; a value other than `off`, `high` or `rt` is reported the same way as a refusal. ## Shader recompiler diff --git a/docs/dev/ENVIRONMENT.md b/docs/dev/ENVIRONMENT.md index 1b03605cae..786b43b533 100644 --- a/docs/dev/ENVIRONMENT.md +++ b/docs/dev/ENVIRONMENT.md @@ -22,6 +22,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | Name | Kind | Source | Default | Description | |------|------|--------|---------|-------------| +| `ANYPS5_AUTORUN_NO_PAUSE` | string | `core/relinker/cli/src/Autorun.cpp` | | exactly `1` skips the exit prompt and stdin read after autorun; unset, empty and other values keep the prompt | | `ANYPS5_GUEST_MODULE_ARGS` | string | `core/relinker/elfpatcher/tests/GuestModuleArgsHost.cpp` | | path of the file the module-argument fixture appends its events to | ### core/shader @@ -62,13 +63,14 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_SAVEEXEC_WRITE_FIRST` | presence | `core/shader/recompiler/Translation/src/ControlFlowInstructions.cpp` | | writes the saved exec to the destination before reading the source in s_*_saveexec | | `APS5_SINGLE_LANE` | string | `core/shader/recompiler/Recompiler.cpp` | | hex code addresses (comma separated, or all) of wave64 programs kept at one guest lane per invocation | | `APS5_SRT_DEBUG` | presence | `core/shader/recompiler/Optimization/src/RequestMemoryView.cpp` | | prints extra SRT descriptor-walk debug output, such as guest reads outside the request memory | -| `APS5_STRICT_DESCRIPTOR_WEBS` | string | `core/shader/recompiler/Optimization/src/ResourceTracker.cpp` | | makes a T# or S# read in a descriptor web throw when a path's descriptor dwords depend on the program counter, EXEC or a lane mask, instead of selecting the first descriptor and recording an `InvalidDescriptor` fault when a lane takes that path; 0 leaves it off | +| `APS5_STRICT_DESCRIPTOR_WEBS` | string | `core/shader/recompiler/Optimization/src/ResourceTracker.cpp` | | makes a T# or S# read in a descriptor web throw when a path's descriptor dwords depend on the program counter, EXEC, a lane mask, the lane index or an image read or sample result (not a gather or an image atomic), instead of selecting the first descriptor and recording an `InvalidDescriptor` fault when a lane takes that path; 0 leaves it off | | `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_WAIT_PENDING_BRANCHES` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/ShaderMemory.cpp` | | a resource capture that evaluates a scalar branch condition over a word with a pending GPU write waits for the write to prune the branch, instead of leaving the branch unpruned (both successors' resources captured) | | `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 | ### libSceAgc @@ -92,12 +94,13 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `ANYPS5_REQUIRE_ROBUST2` | presence | `core/libs/prx/libSceAgcDriver/tests/execution/LdsMixedWidthAtomics.cpp` | | fails the LDS lock-budget test when the device does not enable robustBufferAccess2, instead of only printing it | | `ANYPS5_REQUIRE_VULKAN` | presence | `core/libs/prx/libSceAgcDriver/tests/BdaDevice.cpp` | | fails a test instead of skipping when no usable Vulkan device is present | | `ANYPS5_SCREENSHOTS` | string | `core/libs/prx/libSceAgcDriver/Execution/src/Screenshot.cpp` | | directory the screenshot key saves its PNG files to, defaulting to anyps5-screenshots beside the executable | +| `ANYPS5_TEST_HOST_IMPORT_ALIGNMENT` | integer | `core/libs/prx/libSceAgcDriver/tests/Recorder.cpp` | | stands in for the device's minimum host import alignment in every recorder test when the device has `VK_EXT_external_memory_host`: `0` makes host imports unavailable to them, and a nonzero value must be a multiple of the device's own | | `APS5_AB` | string | `core/libs/prx/libSceAgcDriver/Graphics/src/RuntimeSwitches.cpp` | | `:[+...]`: alternates phase A (switches as the environment sets them) and phase B (the listed runtime switches toggled) at flip packets, each phase at least `` long; prints `[ab]` lines; a name that is not a runtime switch fails the first flip | | `APS5_AB_SETTLE` | string | `core/libs/prx/libSceAgcDriver/Graphics/src/RuntimeSwitches.cpp` | 2 | seconds into a phase after which APS5_AB prints the phase's `settled` line; shorter than the phase | | `APS5_AB_START` | string | `core/libs/prx/libSceAgcDriver/Graphics/src/RuntimeSwitches.cpp` | 0 | flip packet count (from 1) at which APS5_AB starts, or `s` for seconds after the first flip | | `APS5_ADDRESS_COPY_MAX_MIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 64 | upper bound in MiB on the guest memory one address-based build copies; exceeding it aborts | | `APS5_ALL_BUFFERS_WRITTEN` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | notes every shader buffer element as written, as before bufferWritten existed | -| `APS5_ASSERT_GPU_LOCK` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | aborts when a guarded path is reached without holding GuestMemory::GpuMutex | +| `APS5_ASSERT_GPU_LOCK` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | aborts when a guarded path is reached without holding GuestMemory::GpuMutex, or when the unwatch hook would take it while the thread holds the host imports mutex without it; builds without NDEBUG always check the second | | `APS5_ATOMIC_STAGE_MAX_KIB` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | 1024 | size cap in KiB on an atomic shader element covered by device-local staging | | `APS5_BACKGROUND_COMPILE_KIB` | integer | `core/libs/prx/libSceAgcDriver/Execution/include/CompileBudget.hpp` | 8192 | KiB of SPIR-V that compute pipelines built ahead of their dispatch (dispatch prebuilds and the pipeline journal's replay) may be compiling at once; a build that does not fit waits, one larger than the limit builds alone, and 0 builds them one at a time. Dispatches that need a pipeline now are not limited | | `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 | @@ -115,6 +118,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `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_CHANGED_COPY_ALWAYS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | makes the changed-byte copy-back store every byte of the range, not only those the shader changed and the import still holds at their copy-in value | | `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 | @@ -156,6 +160,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_DUMP_DRAW_SLOT1` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/Draw.cpp` | 0 | hex slot-1 address whose draw's shaders and register state are dumped | | `APS5_DUMP_FRAMES` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 0 | saves the first n presented frames as frame_.bmp | | `APS5_DUMP_FRAMES_EVERY` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 1 | dumps every n-th presented frame | +| `APS5_DUMP_PREPARE_INPUTS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderDiagnostics.cpp` | off | saves inputs before preparation as `shader_
.input---.prepare.req`, respecting `APS5_DUMP_SHADERS` selection; logs begin after saving and end after successful preparation | | `APS5_DUMP_QUEUE` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/PacketExecution.cpp` | | dumps the first submissions of the queue with this hex id | | `APS5_DUMP_REJECTED` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Packets/CommandValidation.cpp` | | writes a rejected submission to rejected_submission_.bin | | `APS5_DUMP_SCALE` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 4 | keeps every n-th pixel in each direction of a dumped frame (1 to 16384; any other value fails) | @@ -173,6 +178,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_FILL_SYNC` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | waits for every recorded store over the fill's range | | `APS5_FLIP_INFLIGHT` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 1 | number of presentations that may trail on the GPU (0, 1 or 2) | | `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_FLUSH_WAIT_WHOLE_RANGE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | waits on FlushPending for an unfinished writer anywhere in the flushed range instead of only over the pending layers it stores and the shadowed units it publishes | | `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/shader/recompiler/ControlFlow/src/Structurizer.cpp` | | makes the structurizer use the reach-flag fallback for every graph that supports it instead of trying cloning first; the control-flow tests then expect that fallback | | `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` | @@ -231,6 +237,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `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 | | `APS5_NO_CACHED_PRECOLLECT` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | leaves the write-watch walks of a cached object's Revalidate under the GPU mutex | +| `APS5_NO_CHANGED_COPY_BACK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | copies a GPU-copied buffer's whole range back into its import instead of only the bytes the shader changed | | `APS5_NO_CLEAR_HAND_OVER` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | writes other images' pending results back at a clear-key storage refresh instead of handing them over to the refreshed image | | `APS5_NO_CLEARED_VIEW` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | keeps fast-cleared surfaces as CPU snapshots instead of viewing the cleared storage image | | `APS5_NO_COLLECT_MEMO` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | disables the per-thread memo of write-watch collects | @@ -250,6 +257,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_DISPATCH_KEY_HYGIENE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/Dispatch.cpp` | | mixes every shader register into the dispatch key instead of only the registers that affect the dispatch | | `APS5_NO_DISPATCH_LRU` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Dispatch/DispatchInsert.cpp` | | clears the whole dispatch cache on overflow instead of evicting the least recently used entry | | `APS5_NO_DISPATCH_MOVE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | leaves a dispatch-cache hit from moving its read-only buffers into the template, so it prepares the dispatch again; also off when APS5_NO_TEMPLATE_DATA_REFRESH is set | +| `APS5_NO_DISPATCH_OVERLAP` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | records every dispatch of a compute ring with its own leading and trailing barrier, also those the title left unordered after the ring's previous dispatch, as before | | `APS5_NO_DISPATCH_RECIPE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | builds no dispatch recipes, so dispatch-cache hits repeat the recorded work | | `APS5_NO_DRAW_DATA_HITS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawCache.cpp` | | disables draw-cache hits that differ only in ShaderData words | | `APS5_NO_DRAW_INPUTS_IN_PLACE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | copies draw vertex and index ranges instead of binding them in place from host imports, on any device; wins over `APS5_DRAW_INPUTS_IN_PLACE` | @@ -282,9 +290,12 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_HOOK_COMPLETION_GUARD` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | lets the flush hook record the accessing worker's queued labels even from inside a completion action | | `APS5_NO_HOOK_SNAPSHOT` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | takes the GpuMutex on every access instead of using the pending-write snapshot | | `APS5_NO_HOST_IMPORT` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | disables importing guest memory, so address-based shaders copy instead of using it in place | +| `APS5_NO_HOST_INTERPOLATION_FIRST` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | on RADV, reads pixel shader inputs through VK_KHR_fragment_shader_barycentric again instead of trying host interpolation first | | `APS5_NO_HW_F32_FLUSH` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | skips the startup f32 denormal flush probe and keeps the emulated flush in every shader | | `APS5_NO_HW_F32_PRESERVE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | stops requesting DenormPreserve for 32-bit floats, so the device keeps its default f32 denormal handling | +| `APS5_NO_IDLE_STORE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp` | | stops queue 0's worker from storing pending storage-image results after the queues stay idle for 50 ms, so they stay pending until a reader, label or flush stores them; queue 0 still waits for work while they are pending | | `APS5_NO_IMAGE_PAGE_CACHE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | leaves the main guest image's page-protection table uncached | +| `APS5_NO_IMAGE_VIEW_MIN_LOD` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | leaves VK_EXT_image_view_min_lod disabled, so a T#'s minimum LOD is applied in the shader where shaderResourceMinLod allows it and graphics pipeline libraries are off | | `APS5_NO_INDIRECT_DRAW` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp` | | rejects indirect draw packets at submission validation | | `APS5_NO_JOIN_WAW_CHECK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp` | | lets a contiguous store join the pending one without the write-after-write check | | `APS5_NO_KEEP_NEW_STORAGE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | does not keep a newly created storage image alive with the open batch; such an image evicted before the batch completes can hand its memory to another image through the buffer pool while the batch still writes it | @@ -296,6 +307,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `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, which runs only when batched label submission is off (`APS5_NO_LABEL_BATCH_SUBMIT` or `APS5_NO_LABEL_BATCHING`) | | `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_READ_TRAP` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | on macOS, stores pending linear and small storage images before a label instead of trapping reads of their pages until something stores them | | `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 | @@ -327,6 +339,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_NO_PROC_TABLE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | resolves the device's Vulkan entry points per call instead of filling the function table once | | `APS5_NO_QUEUED_DRAW_READ_ORDER` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawPipeline.cpp` | | stops queued draws and DMA copies from holding back write-backs over the guest memory they read at commit | | `APS5_NO_QUEUED_LABELS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Synchronization/DeferredLabels.cpp` | | disables the queued-label table | +| `APS5_NO_READ_ONLY_LEASE_SKIP` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | counts the lease of an address-based build whose shaders never store through BDA as a pending writer of its leased range instead of only a reader | | `APS5_NO_RECENT_IMPORTS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | looks every host import up in the import map instead of first checking the last four found while the map is unchanged | | `APS5_NO_RECORDED_CLEAR` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | waits for a storage image's fast clear in a batch of its own instead of recording it | | `APS5_NO_RECORDER_SYNC_DRAWS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | gives the draws the recorder waits for their own batch instead of recording them with the wait | @@ -341,11 +354,14 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `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 | | `APS5_NO_SHAPE_MEMO` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp` | | looks up every draw's resident targets and pipeline anew instead of repeating the ones an earlier draw of the same register shape used | +| `APS5_NO_SHARED_FAULT_SLOTS` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/BdaResources.cpp` | | gives every BDA resource object its own full-size fault buffer for its whole life instead of a slot from the device's shared fault chunks that an idle cached object gives back (a 32-byte slot when its programs can write only the fault report) | +| `APS5_NO_SHARED_GDS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp` | | keeps the GDS in a private guest mapping, which the GPU cannot import, instead of anonymous shared memory on an integrated GPU with host imports; DMA_DATA into the GDS behind GPU writes to it then drains, and DMA_DATA out of it is read on the CPU | | `APS5_NO_SINGLE_PASS_COLLECT` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | walks the written pages once without clearing them and, when any were written, a second time clearing them, instead of one cleared pass | | `APS5_NO_SITE_SAMPLING` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp` | | disables per-site sampling of write evidence | | `APS5_NO_SNAPSHOT_CHECK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | skips the live compare that proves a captured snapshot still equals the guest bytes | | `APS5_NO_SORTED_SNAPSHOT_LOOKUP` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | scans the snapshot's regions in AddSnapshot instead of searching them | | `APS5_NO_SPACE_STAMP_ONCE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | stamps every direct import of a cached address space on each hit instead of once per space | +| `APS5_NO_SPANNED_WAVE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | | keeps single-wave wave64 compute programs at two guest lanes per invocation instead of spanning two full 32-lane subgroups | | `APS5_NO_SPARSE_HEAP_MIRROR` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | disables sparse heap mirrors, mirroring only whole heap ranges | | `APS5_NO_STAGE_A_IMAGES` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | leaves every image element to the stage-B full lookup instead of a stage-A record | | `APS5_NO_STORAGE_DEDUPE` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp` | | looks each identical consecutive storage descriptor up, and refreshes its image, separately | @@ -380,7 +396,8 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `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` | 4096 | number of journal entries loaded at most; a longer journal keeps its last entries, the ones recorded last | +| `APS5_PIPELINE_JOURNAL_COMPUTE_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 1024 | number of compute pipelines a replay builds at most, the last recorded, counted apart from the graphics ones | +| `APS5_PIPELINE_JOURNAL_ENTRIES` | integer | `core/libs/prx/libSceAgcDriver/Graphics/src/PipelineJournal.cpp` | 4096 | number of graphics pipelines a replay builds at most, the last recorded; a log line names how many of each kind were left out | | `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 | @@ -405,6 +422,8 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_SAMPLE_THREADS` | string | `core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp` | | samples every thread of the process every 2 ms and writes each busy thread's hottest frames to a file (Windows only) | | `APS5_SAMPLE_WORKER` | string | `core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp` | | samples the calling queue worker every millisecond and writes its stacks to a file (Windows only) | | `APS5_SAMPLE_WORKER_QUEUE` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp` | 0 | selects the queue worker sampled by `APS5_SAMPLE_WORKER`, accepting decimal or hexadecimal IDs such as `0x50`; process and committer sampling remain independent | +| `APS5_SCRATCH_POOL_MIB` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 64 | cap in MiB on the device buffer that backs the scratch of one compute program (header plus workgroup slots, never below one slot), also held under `APS5_SCRATCH_POOL_TOTAL_MIB` and 2 GiB; a nonzero value only | +| `APS5_SCRATCH_POOL_TOTAL_MIB` | integer | `core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp` | 256 | budget in MiB for the scratch pool buffers of all compute programs together: a new or enlarged pool first drops the least recently used pools until it fits (one pool is also capped at it); a nonzero value only | | `APS5_SHADOW_VERIFY` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp` | | verifies that the shadow-published bytes equal the guest bytes | | `APS5_SHARED_COLLECT_MEMO` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | uses the tracker's shared collect-memo ring under its mutex instead of the per-thread ring | | `APS5_SHARED_MEMORY_COUNTERS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp` | | accumulates the memory counters into one shared set with atomic RMWs | @@ -414,7 +433,6 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `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/shader/recompiler/SpirvBackend/src/SpirvOptimizer.cpp` | | selects the SPIR-V optimization pipeline: `full` restores the performance pipeline, `none` skips optimization | | `APS5_SRGB_ATTACHMENT_PROXY` | string | `core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp` | | forces the sRGB color-attachment proxy format; `1` forces it | | `APS5_SRGB_SHADER_DECODE` | string | `core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp` | | `1` forces shader sRGB decode for the sRGB 8-bit formats; `copy` does the same, except that 8 and 8_8 sRGB sample an RGBA8 sRGB copy when the device can blit to RGBA8; any other value has no effect | @@ -446,6 +464,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `APS5_TRACE_BIGBUF` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | lists every copied guest region of at least 1 MiB | | `APS5_TRACE_CAPSYNC` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/WriteEvidence.cpp` | | prints every unlocked hook wait | | `APS5_TRACE_CONTEXT_STATE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp` | | logs context-state operations and the context registers written | +| `APS5_TRACE_COPY_BACK` | presence | `core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp` | | logs, per copied-back range, the dwords in which the shader and the CPU changed the same bytes, and whether the CPU's bytes were kept or, with APS5_CHANGED_COPY_ALWAYS, the shader's stored | | `APS5_TRACE_COPY_HLE` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/Driver/Memory/BufferCopy.cpp` | | traces the CPU copy HLE's refusals | | `APS5_TRACE_DCC_KEYS` | presence | `core/libs/prx/libSceAgcDriver/Execution/src/DisplayBuffer.cpp` | | traces stores of uncompressed DCC keys | | `APS5_TRACE_DIR` | string | `core/libs/prx/libSceAgcDriver/Execution/src/PerformanceTrace.cpp` | . | directory for the trace and summary files | @@ -564,7 +583,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | Name | Kind | Source | Default | Description | |------|------|--------|---------|-------------| -| `APS5_PLAYGO_UNLISTED_CHUNKS` | string | `core/libs/prx/libScePlayGo/Export.cpp` | | answer for a chunk ID the playgo manifest does not list: installed or absent; unset throws instead of answering | +| `APS5_PLAYGO_UNLISTED_CHUNKS` | string | `core/libs/prx/libScePlayGo/Export.cpp` | | answer for a chunk ID the playgo manifest does not list: installed, absent, or a comma-separated list of installed chunk ids; unset throws instead of answering | ### libSceSaveData.native @@ -577,7 +596,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | Name | Kind | Source | Default | Description | |------|------|--------|---------|-------------| -| `ANYPS5_LANGUAGE` | integer | `core/libs/prx/libSceSystemService/SystemService.hpp` | 1 | console language number from 0 to 30 returned by sceSystemServiceParamGetInt, defaulting to English US; any other value throws | +| `ANYPS5_LANGUAGE` | integer | `core/libs/prx/libSceSystemService/SystemService.hpp` | 1 | console language number from 0 to 30 returned by sceSystemServiceParamGetInt, defaulting to English US; `auto` (any letter case) maps the host's locale to a console language once per process and falls back to English US with one stderr line; any other value throws | ### libSceUsbStorage @@ -621,7 +640,7 @@ The code reads environment names through `getenv`, `IsEnvironmentSet`, `kibSetti | `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` | string | `core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp` | | comma-separated libraries (or all) whose guest calls are traced; an empty import-library label uses the defining host module filename when it can be found | | `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 | diff --git a/docs/dev/GAME_TESTING.md b/docs/dev/GAME_TESTING.md index a750e37dc2..ce3a6874f7 100644 --- a/docs/dev/GAME_TESTING.md +++ b/docs/dev/GAME_TESTING.md @@ -89,13 +89,13 @@ cmd /c '.\app.exe > run.log 2>&1' - Use the title's default settings, and say so. A changed graphics mode makes a number incomparable with everyone else's. - 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_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, except `perf(tools)`, `perf(ci)` and `perf(build)`. - `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 -- **PlayGo:** dumps without PlayGo metadata stop at start unless `APS5_PLAYGO_UNLISTED_CHUNKS` is set; report the value used. +- **PlayGo:** dumps without PlayGo metadata stop at start unless `APS5_PLAYGO_UNLISTED_CHUNKS` is set (installed, absent, or a comma-separated list of installed chunk ids); report the value used. - **UE4 titles:** keep the startup movies in place. Renaming them to skip the intro has made a level render without its 3D view, which looked like an emulator bug and wasn't. ## Input diff --git a/docs/dev/HW_ORACLE.md b/docs/dev/HW_ORACLE.md index 01583cdc19..f4e4c14c1a 100644 --- a/docs/dev/HW_ORACLE.md +++ b/docs/dev/HW_ORACLE.md @@ -20,7 +20,7 @@ the tool, but differs from gfx1013 in more places. Say which GPU you measured on `PATH`, they are taken from `$ROCM_PATH/llvm/bin`. The first run builds `oracle.c` into `$XDG_CACHE_HOME/anyps5-hw-oracle/`, or `~/.cache/anyps5-hw-oracle/` when -`XDG_CACHE_HOME` is unset or empty (`HW_ORACLE_CACHE` overrides both). The target is read from the GPU (`HW_ORACLE_TARGET` +`XDG_CACHE_HOME` is unset, empty or relative (`HW_ORACLE_CACHE` overrides both). The target is read from the GPU (`HW_ORACLE_TARGET` overrides it). ### Windows @@ -31,7 +31,7 @@ overrides it). 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 +elsewhere; `vulkan` is never the default, see [Recompiler comparison](#recompiler-comparison)). `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 @@ -89,6 +89,41 @@ results = run(body, rows, wave64=True, ieee=0, dx10_clamp=1, Usual workflow: run the instruction on rows that separate the candidate models (edge values, NaN payloads, rounding ties), write a model that matches every row, implement it, and put measured rows in the execution test. +## Recompiler comparison + +The `vulkan` runtime runs the same assembled kernel through the shader recompiler on the Vulkan device instead of on AMD +hardware, so one body and one set of rows can be measured on both and the results compared. Build the runner: + +```sh +cmake --build build --target agc_oracle_runner +``` + +| Variable | Meaning | +| --- | --- | +| `HW_ORACLE_RUNTIME=vulkan` | selects the runtime | +| `HW_ORACLE_VULKAN_RUNNER` | path of the built `agc_oracle_runner` executable | +| `ANYPS5_GPU` | case-insensitive substring of the Vulkan device name, as for every AnyPS5 tool | + +On Windows the runner needs `build/core/libs/libs/unpatched` in `PATH` ([BUILD](BUILD.md)). Run the hardware with the default +runtime and the recompiler with `vulkan`, with the same body, rows and settings, each with `--json`, then compare: + +```sh +SETTINGS="--ieee 0 --dx10-clamp 1 --denorm32 0 --denorm16 3 --round32 0 --round16 0 --fp16-overflow 0" +python3 tools/hw-oracle/hw_oracle.py body.s rows.txt --json $SETTINGS > hardware.json +HW_ORACLE_RUNTIME=vulkan HW_ORACLE_VULKAN_RUNNER= \ + python3 tools/hw-oracle/hw_oracle.py body.s rows.txt --json $SETTINGS > recompiler.json +python3 tools/hw-oracle/compare_results.py hardware.json recompiler.json +``` + +The report's `runtime` field says which side produced it. + +- Compute only. +- Whole waves: Python pads the rows with zeros to the wave size, so cross-lane instructions see the padded lanes. +- At most 1024 lanes per dispatch. +- No `coarse`. +- The assembly target comes from the AMD driver on Windows or from `HW_ORACLE_TARGET`; the recompiler has no target of its + own, and the encoding assembled is the RDNA2 one. + ## Limits Only compute kernels. Pixel-shader ops that read their inputs from LDS also work: `v_interp_*` reads the attribute diff --git a/docs/dev/PROGRESS.md b/docs/dev/PROGRESS.md index 03e5813ceb..1214069432 100644 --- a/docs/dev/PROGRESS.md +++ b/docs/dev/PROGRESS.md @@ -16,6 +16,27 @@ A library with no scanned definitions and no shared sources, such as one whose ` [`tools/progress_history.py`](../../tools/progress_history.py) measures the first-parent history of `main` with the same rules as `progress.py` and writes `history.json`, `history.svg` and `history.html` (the chart with a tooltip per day plus a table, linked from the README and the end of the progress page). The chart plots counts, not percentages, so it shows how much is added over time: one panel for library functions and one for shader instructions, each on its own scale with the implemented count as a solid line and the declared total as a dashed one (for shaders the declared total is the instruction list in `tools/rdna_isa.txt`); the current percentage sits next to each panel's implemented label. It takes one point per UTC day, at the last commit of the day, and skips commits whose tree lacks the scanned paths. The chart compacts as days accumulate: up to 14 days it labels every day, up to 150 days one label per Monday, beyond that one per month; points are drawn up to 45 days, and every day keeps a hover tooltip, while the table and `history.json` keep every day. Old trees are measured with today's counting rules and instruction list, so the series is comparable but not what the page showed on that day. The workflow checks out the full history for it. +## Shader recompiler numbers + +The shaders badge and the "Shader recompiler: counted from the code" section of the progress page come from [`tools/shader_scan.py`](../../tools/shader_scan.py). They are counted from the shader recompiler's code, not from playing games, so they say nothing about whether a game runs. The older "GPU shader instructions" figure counted instruction names that have an `RdnaOpcode` enumerator and reached 100%; the badge, the progress map panel ("Shader forms decoded") and the page now show the forms instead. That figure stays in `progress.json` under `shaders` for comparisons and history, and is shown only when the shader source scan cannot run (a local run without `--require-shader-source`). + +The scan takes every encoded form of every instruction in AMD's RDNA 2 instruction set from [`tools/rdna2_isa_forms.txt`](../../tools/rdna2_isa_forms.txt). A form is the base encoding, the VOP3 encoding of a VOP1, VOP2 or VOPC instruction, or a DPP16 or DPP8 word on a VOP1 or VOP2 instruction. SDWA forms are not counted, because whether the decoder accepts one depends on selector bits that a source scan cannot tell apart. + +- **Decoded:** the decoder's opcode tables under `core/shader/recompiler/RdnaDecoder/src` map the form to an `RdnaOpcode`, applying the same VOP3 opcode offsets and the VOP2 and DPP refusals as the decoder. The badge shows decoded forms out of all base and VOP3 forms. +- **Translated:** that `RdnaOpcode` has a `case` label in `TranslationContext::emitScalar`, `emitVector`, `emitMemory` or `emitInterpolation`. A case label shows the translator handles the opcode at all, not that the translation is correct. +- **DPP forms** are reported separately. +- **Unsupported throws:** `throw` statements under `core/shader/recompiler` (tests excluded) whose message or exception type says unsupported or not implemented. + +The "Still needed" panel under the progress map, and the matching section of the progress page, list what is left, all read from the same scan: + +- the forms the decoder does not decode (base and VOP3 forms, then DPP forms separately) and the decoded opcodes the translator has no `case` for, by name, grouped by encoding and form; +- the unsupported-throw sites as bars per recompiler area (the first directory under `core/shader/recompiler`; top-level files are "Recompiler"). `progress.json` carries the per-site file, line and message under `shader_source.throw_sites`; +- image formats as bars: sampled and typed-buffer formats from the recompiler's format tables (`IrBufferFormat`, `DefinedImageFormats`, `kFormatInfoTable`, `GetFormatInfo`), and formats the driver maps to a Vulkan format in `libSceAgcDriver/Graphics/src/TextureFormat.cpp` (`kFormatLookup` and `remapGuestFormat`). + +When the recompiler is restructured so a table or function cannot be found, the scan stops with an error naming it. The Pages workflows pass `--require-shader-source`, so nothing is published from a failed scan; without the flag `progress.py` prints a warning and leaves the shader source numbers out. + +`tools/rdna2_isa_forms.txt` is derived from AMD's machine-readable ISA XML (`amdgpu_isa_rdna2.xml`, published under the MIT licence; the file keeps AMD's copyright and permission notice). To refresh it for a newer release, run `python3 tools/progress.py --regenerate-isa-forms amdgpu_isa_rdna2.xml`. + ## Comparisons Comparisons identify functions by library and name. When a head group becomes a shared-source wrapper, its previous definitions are compared under the source library's identity. Removing a duplicate implementation is reported as sharing sources, without reporting the retained functions as removed. diff --git a/docs/dev/debt/libAacs.md b/docs/dev/debt/libAacs.md new file mode 100644 index 0000000000..4fcada1564 --- /dev/null +++ b/docs/dev/debt/libAacs.md @@ -0,0 +1,287 @@ +# libAacs technical debt + +### Callable exports + +- [Callable exports](../../../core/libs/prx/libAacs/Export.cpp) - 264 entries are import-resolution declarations only. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Data exports require separate layout and ownership analysis. Candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +| Name | NID | +| --- | --- | +| `Unknown` | `+0gHTNd4xs4` | +| `Unknown` | `+3dyyKZMTyw` | +| `Unknown` | `+7ZoZWbIhlE` | +| `Unknown` | `+CiBKXuK8c8` | +| `Unknown` | `+RinEsZfOmw` | +| `Unknown` | `+XVwfwtkAi4` | +| `Unknown` | `-2QNA1pCQqU` | +| `Unknown` | `-kAJeMNy-0E` | +| `Unknown` | `-lknLNU2NAU` | +| `Unknown` | `-xuQAxlUvmk` | +| `Unknown` | `0bhkzdWU6Y4` | +| `Unknown` | `0oisD9Dr3FI` | +| `Unknown` | `12CP5PHvlgs` | +| `Unknown` | `15QPrQC1usM` | +| `Unknown` | `1CNq2v6hoig` | +| `Unknown` | `1aMnfYcnn1I` | +| `Unknown` | `1bzdduTshZ4` | +| `Unknown` | `1eNkqOSGM+A` | +| `Unknown` | `1hyWLxCZ-X8` | +| `Unknown` | `1pvpAK9Rg1k` | +| `Unknown` | `23BaEjHuFUA` | +| `Unknown` | `2HaRuPcSWtE` | +| `Unknown` | `2KQWcFTN8ZE` | +| `Unknown` | `2YklE8dG4v8` | +| `Unknown` | `38ZtQUT4lMs` | +| `Unknown` | `3LFB-Ey4DUk` | +| `Unknown` | `3OYBQUy9yVI` | +| `Unknown` | `3ZqZuNL3qrM` | +| `Unknown` | `3odIlZa6t2I` | +| `Unknown` | `4IQhgxl103g` | +| `Unknown` | `4r3seYGdIK0` | +| `AacsPermissionActivate` | `4zU8rDPa4r4` | +| `Unknown` | `54hkTYBIxqs` | +| `AacsBusDecrypt` | `6KcHbwBESLA` | +| `AacsModuleStop` | `6T773MR5QPI` | +| `Unknown` | `6ToH2wFRWf4` | +| `Unknown` | `6XcWJGADJeU` | +| `Unknown` | `753FlqNkJlM` | +| `Unknown` | `7ETkLi9auE0` | +| `Unknown` | `7KCIejRxaB0` | +| `AacsDecrypt` | `7TaiaYkvLFo` | +| `Unknown` | `7eM7N1Anox4` | +| `Unknown` | `7iLrHfVzuvs` | +| `Unknown` | `7qIZfzr0Dgk` | +| `Unknown` | `85kBXEP1moE` | +| `Unknown` | `8Gj9M3o-rrY` | +| `Unknown` | `8TJlScPxUa8` | +| `Unknown` | `8YaNJTuYU+g` | +| `Unknown` | `9S1fVMePqpI` | +| `Unknown` | `9UJqpmNn494` | +| `Unknown` | `9eTs6-zAOJs` | +| `Unknown` | `9rQgwWQB9NE` | +| `Unknown` | `AVRC3P8reF0` | +| `Unknown` | `Apl9mjDSCE0` | +| `Unknown` | `B10mYlgLwMM` | +| `Unknown` | `BFdZY3ocsJo` | +| `Unknown` | `BO+NBS2KrZc` | +| `Unknown` | `BaMuhhCt-dQ` | +| `AacsPermissionCheck` | `CJOLmTNwg6g` | +| `Unknown` | `DWJK8GjbNHo` | +| `AacsModuleInit` | `DdD8hWNsUSc` | +| `Unknown` | `EBGsSrhNRnM` | +| `Unknown` | `EFpFjkHGewY` | +| `Unknown` | `EIEQ4mWz5sU` | +| `Unknown` | `ELsVqt8cS3Q` | +| `Unknown` | `EQWDDRWfuOU` | +| `Unknown` | `ESDneLy+ExU` | +| `Unknown` | `ETDNd-CDbkc` | +| `Unknown` | `EX5Y3UepfU4` | +| `Unknown` | `EgaEREMnudw` | +| `Unknown` | `FA1OyesdYiA` | +| `Unknown` | `FEgD32GcLdM` | +| `Unknown` | `FEi5mHUWB0M` | +| `Unknown` | `FHWdAzU-rRI` | +| `Unknown` | `Fl0RrZ7B27g` | +| `Unknown` | `G4zfSFYC1f8` | +| `Unknown` | `GD6inRSAHfo` | +| `Unknown` | `GWA91aXeU1Y` | +| `Unknown` | `GXLnqJJ85dM` | +| `Unknown` | `GpsjAdWq5hk` | +| `Unknown` | `HXafooMuNTY` | +| `Unknown` | `HhIFaBlod8A` | +| `Unknown` | `HmdBrUh2Xz4` | +| `Unknown` | `HuTXuHqMrVE` | +| `Unknown` | `ILwfo94p-ng` | +| `Unknown` | `Iofb1lKwTNA` | +| `Unknown` | `IxvgFI5JtxE` | +| `Unknown` | `JXvlGrRcNlg` | +| `Unknown` | `Jmviii0cDro` | +| `Unknown` | `JolZPea7pio` | +| `Unknown` | `JqeAwcly1Lc` | +| `Unknown` | `JqrjeblDpqo` | +| `Unknown` | `KG5C7s3p+mM` | +| `Unknown` | `KLxEGrIPbbo` | +| `Unknown` | `L6tIkKT5Vdg` | +| `Unknown` | `LFpG7wYNZ+I` | +| `Unknown` | `LT-aek5dBRQ` | +| `Unknown` | `LTIva3bGCXI` | +| `Unknown` | `MCPkB4DYvAM` | +| `Unknown` | `MILG1Bi10BQ` | +| `Unknown` | `MLHm3u-UfPk` | +| `Unknown` | `MRMYIqUmmcQ` | +| `Unknown` | `MVoCh7vg9Bk` | +| `Unknown` | `NK6Ke-y45cI` | +| `Unknown` | `NL6gDScOFl8` | +| `Unknown` | `NOWYNS5AnRg` | +| `Unknown` | `NRv4EFuYq9I` | +| `Unknown` | `NSph9LkyLmI` | +| `Unknown` | `OI1om0rmEYk` | +| `Unknown` | `OOrYw04s+VU` | +| `Unknown` | `ObJaLtAn3TM` | +| `AacsModuleStart` | `OiQkT8AUD5k` | +| `Unknown` | `Ozlqilsf3Tw` | +| `Unknown` | `PHgHus4a3M0` | +| `Unknown` | `PUDoXp1l-Po` | +| `Unknown` | `Q2eGSNdsRq0` | +| `Unknown` | `Q3vOGHtSk9Y` | +| `Unknown` | `Q74CET98b04` | +| `Unknown` | `QCq8qc+kt7g` | +| `Unknown` | `QDc8xyN5AQw` | +| `Unknown` | `QabaLwU3b9o` | +| `Unknown` | `RI7fQrbtZko` | +| `Unknown` | `RZFK7f3Oi24` | +| `Unknown` | `RkkCme3dsyo` | +| `Unknown` | `SJ9GmthhZDk` | +| `Unknown` | `SjEjF5V5XHc` | +| `Unknown` | `SjewOoJlr9c` | +| `Unknown` | `TK5prfVU4b0` | +| `Unknown` | `TQCLQjHkkW0` | +| `Unknown` | `TuTNDTTOXHo` | +| `Unknown` | `U2zlEU4AhRk` | +| `Unknown` | `U6RAXYIdJJE` | +| `Unknown` | `UArM6xy6B3g` | +| `Unknown` | `UMZJ2F8Lr4o` | +| `Unknown` | `UqqcPoUuAqE` | +| `AacsPermissionGetNonce` | `V1THGaJc23o` | +| `Unknown` | `VnixdJqGnjU` | +| `Unknown` | `WOxxWZjZMfQ` | +| `Unknown` | `WaOsDN+9lZg` | +| `Unknown` | `WknurdpKTqo` | +| `Unknown` | `WrEjdzfOo2I` | +| `Unknown` | `XQuTyX2WprY` | +| `Unknown` | `XUL7r8v2Q3Y` | +| `Unknown` | `XbP3p94ujkg` | +| `Unknown` | `XhMh4CEdKGU` | +| `Unknown` | `Xokc3inTVbM` | +| `Unknown` | `XrkLs6PFhac` | +| `Unknown` | `Xy748BBvB2w` | +| `Unknown` | `YDl9jLvW+SM` | +| `Unknown` | `YOlHXj4rrzQ` | +| `Unknown` | `Yqt0NzhE-eU` | +| `Unknown` | `Z9yhz8+DS3k` | +| `Unknown` | `ZCRVIAxld6o` | +| `Unknown` | `ZEdbi75pkX0` | +| `Unknown` | `ZUEIg4QZJ+k` | +| `Unknown` | `ZnJgSGpLzGs` | +| `Unknown` | `a3D2-Eh-DW8` | +| `Unknown` | `aOX5VPrNfqo` | +| `Unknown` | `aXeMNVrwOTw` | +| `Unknown` | `aZmsxlYbNes` | +| `Unknown` | `aacj8q7PTFI` | +| `Unknown` | `b0ZC6N9oQlU` | +| `Unknown` | `bHqBriHrgXA` | +| `Unknown` | `bQl9nHS7Jgg` | +| `Unknown` | `bQmoqxNo0rE` | +| `Unknown` | `c04ksdAcncM` | +| `AacsSelectKeyFrom` | `c5xyR+kt8mo` | +| `Unknown` | `c9tUe3nNpxo` | +| `Unknown` | `cI0YmNGV22E` | +| `Unknown` | `cnGJD38STwQ` | +| `AacsModuleTerm` | `dSZ7mcs3MNk` | +| `Unknown` | `e-SciZxh1HY` | +| `Unknown` | `e-yS+nlBtlE` | +| `Unknown` | `e0UQuzSUkI8` | +| `Unknown` | `e5qwpKPf1aI` | +| `Unknown` | `eRXHQokealY` | +| `Unknown` | `egjNL-sA-s0` | +| `Unknown` | `eoQNtjg5QyQ` | +| `Unknown` | `f5FWQj--MwM` | +| `Unknown` | `fWmfE-A0k90` | +| `Unknown` | `fkra1GmOMmQ` | +| `Unknown` | `flzhCbf4xnw` | +| `Unknown` | `gAV79zVGfzI` | +| `Unknown` | `gPhz2sY+lHM` | +| `Unknown` | `gTXUpBIgmcg` | +| `Unknown` | `gmOvd0UR6rk` | +| `Unknown` | `h6JW5ZErMHE` | +| `Unknown` | `hDCeckXsgIw` | +| `Unknown` | `hFXdc25NmT4` | +| `Unknown` | `hWhpklSoZ7g` | +| `Unknown` | `hct40EnKHyM` | +| `Unknown` | `i0ZMaUuKzkE` | +| `Unknown` | `iIgtd05IlbA` | +| `Unknown` | `iJ3zPE02P1w` | +| `Unknown` | `iOLQbPwqsJU` | +| `Unknown` | `igIuBDqdlZo` | +| `Unknown` | `j0quzCtmBr4` | +| `Unknown` | `jXZJmZu3ihs` | +| `Unknown` | `jdd4KuD7bOg` | +| `Unknown` | `jpKit8Qv76g` | +| `AacsPermissionSet` | `kDxzgCcMty0` | +| `Unknown` | `kX1hCR9FDo0` | +| `Unknown` | `kYQis7lBy+U` | +| `Unknown` | `kaxfKxeOvbU` | +| `Unknown` | `keNMJVznBe0` | +| `Unknown` | `kk8ELO4Y2Fo` | +| `Unknown` | `l18OOzQPPKo` | +| `Unknown` | `l8uscgUMuWE` | +| `Unknown` | `ldkS5zx7P6s` | +| `Unknown` | `llme91x0I-k` | +| `Unknown` | `lq8QnjYyTGQ` | +| `Unknown` | `mExJcLz6QWE` | +| `Unknown` | `mR3leJVBYbw` | +| `Unknown` | `mbKaZoh30DM` | +| `Unknown` | `mlZY17mBv9M` | +| `Unknown` | `mnf5OTi-urc` | +| `Unknown` | `moU18PvmqEU` | +| `AacsPermissionInit` | `n8SlmGnoGtI` | +| `Unknown` | `o33b+5keZic` | +| `Unknown` | `oJobSSDssQw` | +| `Unknown` | `oU59phxP5U4` | +| `Unknown` | `oij+E5v8qjM` | +| `Unknown` | `p8Lv5g5o9pY` | +| `Unknown` | `p9sZ84FK-qw` | +| `Unknown` | `ptrqNcGG0eM` | +| `Unknown` | `qFchDSE1J3o` | +| `Unknown` | `qGMyKAQhniM` | +| `Unknown` | `qKmr8FI7Too` | +| `Unknown` | `qPi2nzCUYiQ` | +| `Unknown` | `qpebtLhTJBc` | +| `Unknown` | `r960kCkFF3I` | +| `Unknown` | `rMR6nG8LdyQ` | +| `Unknown` | `rOM7gjFX3nM` | +| `Unknown` | `raaoezfuSqY` | +| `Unknown` | `ruidkq8amJE` | +| `Unknown` | `s+BvUCe+t+I` | +| `Unknown` | `s6CgM5B+8zc` | +| `Unknown` | `sjNkMwi80Os` | +| `Unknown` | `sv8IiSJQ0Mo` | +| `Unknown` | `tPYLJSOXQ8k` | +| `Unknown` | `tWHiNKLyUn4` | +| `Unknown` | `uG7mSoxfjL0` | +| `Unknown` | `ujgxfJSpLrc` | +| `Unknown` | `ulInPmGONKo` | +| `Unknown` | `uvJDUwXFtw0` | +| `Unknown` | `uvLxjEqwYKk` | +| `Unknown` | `v5IGfww3qSA` | +| `Unknown` | `vM2spx6P6RU` | +| `Unknown` | `vcFZSn+m4-o` | +| `Unknown` | `w4Go9bJ-JGQ` | +| `Unknown` | `x34TWztyXy8` | +| `Unknown` | `x82CfygyUPA` | +| `Unknown` | `xQoCQNT3skQ` | +| `Unknown` | `y56l-E5S0Qs` | +| `Unknown` | `y5OsRUqNoX4` | +| `Unknown` | `yFNWXXeNxF0` | +| `AacsUpdateKeyArea` | `yUEiOjyQK2w` | +| `Unknown` | `yZjYlfcfEFE` | +| `Unknown` | `yaNpz6jnOzE` | +| `Unknown` | `yrqEHk366AM` | +| `Unknown` | `z0b7NKDfWpo` | +| `Unknown` | `zgZWaxsJGyM` | +| `Unknown` | `zreU+KU3nLw` | +| `Unknown` | `zt4Xv-4fjx0` | + +### Unresolved data exports + +These older data exports remain undeclared. Their NIDs and sizes also appear in the later inventory, but their types, initialization and lifetime have not been established. Relocation counts refer to entries within each object in the older image; relocation-free does not establish safe semantics or ownership. + +| NID | Size (bytes) | Relocations | +| --- | ---: | ---: | +| `Ig+KGFjQHCg` | 384 | 46 | +| `0hCijDZ52LQ` | 384 | 46 | +| `NTbhN7TXzI0` | 736 | 90 | +| `50ti-+HJsoY` | 8192 | 0 | +| `UVmPYK3n8Ac` | 736 | 90 | +| `kvmcOEpZYhU` | 4 | 0 | +| `adiny53QVzk` | 384 | 46 | +| `H8V9nrBQMJc` | 736 | 90 | diff --git a/docs/dev/debt/libCss.md b/docs/dev/debt/libCss.md new file mode 100644 index 0000000000..559fc1192e --- /dev/null +++ b/docs/dev/debt/libCss.md @@ -0,0 +1,89 @@ +# libCss technical debt + +### Callable exports + +- [Callable exports](../../../core/libs/prx/libCss/Export.cpp) - 65 entries are import-resolution declarations only. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Data exports require separate layout and ownership analysis. Candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +| Name | NID | +| --- | --- | +| `Unknown` | `+zpMhCilHTQ` | +| `Unknown` | `-WR4-J79nn0` | +| `Unknown` | `45oyUIl85dg` | +| `Unknown` | `4qq5OHYaFU8` | +| `Unknown` | `6BDNwI1e59c` | +| `Unknown` | `6QhXfVttAyU` | +| `Unknown` | `74insBAQhg8` | +| `Unknown` | `7YdC9p2cTSQ` | +| `Unknown` | `81obgoH2X2g` | +| `Unknown` | `9T-RYQ7HwqI` | +| `Unknown` | `9wgr5dQqxyg` | +| `Unknown` | `AfC4Omb8svg` | +| `Unknown` | `B314wB+fU6g` | +| `Unknown` | `BpqeQbNU0qo` | +| `Unknown` | `CnQiuTNrJcM` | +| `Unknown` | `E96QCyLgJ3o` | +| `dvdAuthInitialize` | `Ej2QCEJXBec` | +| `DvdMac` | `EvvQwl+bIv4` | +| `Unknown` | `FTFSIzVj7gE` | +| `Unknown` | `FsJO2R6pcFY` | +| `Unknown` | `HaAxhqb2xoM` | +| `Unknown` | `Hm2XEUlTjSk` | +| `Unknown` | `IcknOdMyfxQ` | +| `Unknown` | `J6BYqrHA5UM` | +| `Unknown` | `KUvq4G5VBPo` | +| `Unknown` | `LAcpQCmLWwI` | +| `Unknown` | `MRbX1Lg7JPo` | +| `Unknown` | `OM3ptXltjzo` | +| `Unknown` | `Q4g0Ji0W5GY` | +| `Unknown` | `SRPTG7u8o98` | +| `Unknown` | `TJdtvWbNKhs` | +| `Unknown` | `UCT-EFL9GlY` | +| `Unknown` | `UlBK7McbIBI` | +| `Unknown` | `VLaRWTMO9U8` | +| `Unknown` | `VMBVlZevTLw` | +| `Unknown` | `Wdf6DHvrt4g` | +| `Unknown` | `X9dl5pCTGiE` | +| `Unknown` | `XWqQyI3K47s` | +| `dvdAuthFinalize` | `ZzaGM24XNFI` | +| `Unknown` | `bQpZTilHSL0` | +| `Unknown` | `cN12O1uQ6Fo` | +| `Unknown` | `cTnMCeNpGqc` | +| `Unknown` | `czKT3U30FFE` | +| `Unknown` | `daklpMlbo00` | +| `Unknown` | `f7iq9LsamYo` | +| `Unknown` | `gbja3RUMWJ4` | +| `Unknown` | `giIpEkyo9Hg` | +| `Unknown` | `hD3HhdNG+xw` | +| `Unknown` | `hbTTMmwE3lo` | +| `Unknown` | `hbvafh9uQqY` | +| `Unknown` | `jYGAlLsijgw` | +| `Unknown` | `k1+Z1AtnLYI` | +| `Unknown` | `k5AW5Xs4aqg` | +| `Unknown` | `kc5B1ELiUv4` | +| `Unknown` | `nhK3hP3XL3Y` | +| `Unknown` | `ohRg1gu5G1w` | +| `Unknown` | `pTycImStaM4` | +| `Unknown` | `rylZqUytLj8` | +| `Unknown` | `szOnkd+7b3U` | +| `Unknown` | `utwkEaQy6D4` | +| `Unknown` | `wDe5E-Nfd7E` | +| `Unknown` | `xQX0HWKQKDA` | +| `Unknown` | `z4KQSeB3+6U` | +| `CpsInitKey` | `z7D3a8pXEeE` | +| `Unknown` | `zxv6uaL0lAs` | + +### Unresolved data exports + +These nine older data exports remain undeclared. Their NIDs and sizes also appear in the later inventory. Three contain relocation entries in the older image, and the other six occupy non-file-backed storage. Their types, initialization and ownership remain unverified; no object contents or behavior are inferred from matching sizes. + +| NID | Size (bytes) | Relocations | +| --- | ---: | ---: | +| `Ea79aJutCLc` | 40 | 3 | +| `+ItBPjmv86M` | 40 | 3 | +| `r6olfOgegxU` | 32 | 2 | +| `0wlN5i59xrM` | 1 | 0 | +| `dqpZ10KTssY` | 10 | 0 | +| `CjPeOAnJSIs` | 10 | 0 | +| `KZbHx6nNRQw` | 5 | 0 | +| `ZDKe0O0ynIs` | 5 | 0 | +| `mkFxjg4B3Dg` | 1 | 0 | diff --git a/docs/dev/debt/libMozaSDK.md b/docs/dev/debt/libMozaSDK.md new file mode 100644 index 0000000000..00dd80f10b --- /dev/null +++ b/docs/dev/debt/libMozaSDK.md @@ -0,0 +1,5 @@ +# libMozaSDK technical debt + +### Functional + +- [Exports](../../../core/libs/prx/libMozaSDK/Export.cpp): the library's exports, signatures and behavior are unknown, so it exports only a throwing import-resolution placeholder. The library is absent from the PS5 3.20 export inventory that the other placeholder libraries come from, and its firmware source and version are not verified. PPSA01317 (01.710.000) lists `libMozaSDK.prx` as a needed module and library without shipping it and binds no import to it, so on Linux the loader stops before the title runs without a file of that name; Windows skips the missing dependency. diff --git a/docs/dev/debt/libReactNative.Modules.Vsh.WebAppLauncher.md b/docs/dev/debt/libReactNative.Modules.Vsh.WebAppLauncher.md new file mode 100644 index 0000000000..3b65836960 --- /dev/null +++ b/docs/dev/debt/libReactNative.Modules.Vsh.WebAppLauncher.md @@ -0,0 +1,72 @@ +# libReactNative.Modules.Vsh.WebAppLauncher technical debt + +### Callable exports + +- [Callable exports](../../../core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/Export.cpp) - 45 callable NIDs match the [public 3.20 catalogue](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libReactNative.Modules.Vsh.WebAppLauncher.c) and the [selected export metadata](https://gist.github.com/kyon-eth/b36a766ab0a33d469711188e09f19e2a/cc181f10ffa02358b56e235c69f5af19cfaf6d89) from two version-labelled images (SHA-256 `14fb80674fe2054933885dc3fa8a8abaad406191cdae915eb1a91b5580cae0a8`). The labels are not authenticated. Prototypes and behavior are unverified; the declarations throw through `NotImplemented_nid_no_patch` and implement no runtime functionality. Three other functions shared with the React Native Vsh library need separate provider-resolution analysis. + +| Name | NID | +| --- | --- | +| `CheckSystemEvents` | `VC8PCwSx3g0` | +| `CommerceDialogBrowseCategory` | `yNFPPeNhIwk` | +| `CommerceDialogBrowseProduct` | `YpeJJ7DL1Zo` | +| `CommerceDialogInit` | `o3tYKx8oBBA` | +| `CommerceDialogRedeemPromotionCode` | `ZLINx2XbQQ8` | +| `CommerceHidePsStoreIcon` | `k7y31RwRcGg` | +| `CommerceSetPsStoreIconLayout` | `3rXpEBCRi1o` | +| `CommerceShowPsStoreIcon` | `9-iVwdotSr4` | +| `DisableSystemMedia` | `jlIfgOEzZ2A` | +| `ErrorDialogClose` | `PUnaOnE5qv4` | +| `ErrorDialogInit` | `WeZCXuG60OQ` | +| `ErrorDialogOpen` | `ds9BJbeJ0ZQ` | +| `GetAccountAgeA` | `mypq-tQJOlo` | +| `GetAccountCountryA` | `+UmoBB-Kur0` | +| `GetAccountIdA` | `W17CX8bEsI4` | +| `GetAccountLanguage2` | `AitA5hf0b4o` | +| `GetAuthorizationCode` | `zn1GN6GVKwY` | +| `GetClosedCaptionSettings` | `xVlRy5NAUsw` | +| `GetOnlineId` | `2uw51wxVsiI` | +| `GetProcessTime` | `FQz66HusRxA` | +| `GetVideoOutputStatus` | `19LEprfkGS0` | +| `GetVideoOutputStatusInt` | `ndc59UeFwpE` | +| `Hide` | `6dhVu-SHjHI` | +| `IsDebuggingSupported` | `6eoxFFSEuoY` | +| `NpWebAbortRequest` | `eNQ0FStvU1c` | +| `NpWebDeleteRequest` | `yjjDwtgu4jE` | +| `NpWebInitialize` | `0zuAEore8pY` | +| `NpWebReadData` | `9gfFGr6B1Fc` | +| `NpWebTerminate` | `sXn8uN-ijvw` | +| `OpenClosedCaptionAdvancedSettings` | `fqERqkvThgQ` | +| `OpenClosedCaptionSettings` | `kroxOU831V4` | +| `SoundInitialize` | `82UnAk1CC98` | +| `SoundLoad` | `7x711wfwKEM` | +| `SoundPlay` | `k77hI5zSW1c` | +| `SoundTerminate` | `pO9cpXuuPwU` | +| `SpeechCancel` | `rwnTeqYe0bY` | +| `SpeechClose` | `7SmqB-eXB+4` | +| `SpeechInitialize` | `9ozXeR1-w4c` | +| `SpeechSpeak` | `DceITXrEqMI` | +| `SpeechStatus` | `cJ1aLzpbyow` | +| `SpeechTerminate` | `W5pdIiQSrmY` | +| `SystemDialogInit` | `zmUqZyLpEhs` | +| `SystemDialogOpen` | `AVyuvygnd0w` | +| `SystemServiceParamGetInt` | `Ewd+kXqU2Bc` | +| `SystemServiceParamGetString` | `vqMvoe76W6k` | + +### Additional unidentified exports + +- Twelve [callable exports](../../../core/libs/prx/libReactNative.Modules.Vsh.WebAppLauncher/Export.cpp) are throwing placeholders. Names, signatures and behavior are unverified. Matching module and library identities occur in two version-labelled images; those labels are not authenticated. + +| Name | NID | +| --- | --- | +| `Unknown` | `+ucrAt9fI7c` | +| `Unknown` | `06N-dAyE+HY` | +| `Unknown` | `41blU2JCqLQ` | +| `Unknown` | `BGgSEXB1Now` | +| `Unknown` | `CAK-S2JVdsk` | +| `Unknown` | `QxCdpkEsOgo` | +| `Unknown` | `Tjj++S2qvOU` | +| `Unknown` | `f5NHqUziG+4` | +| `Unknown` | `mI-ltMuVN+Q` | +| `Unknown` | `vDup+0mWZf8` | +| `Unknown` | `wR1ucKGDM9Q` | +| `Unknown` | `xPA-tXzPGpc` | diff --git a/docs/dev/debt/libReactNative.Modules.Vsh.md b/docs/dev/debt/libReactNative.Modules.Vsh.md new file mode 100644 index 0000000000..dfc85d4513 --- /dev/null +++ b/docs/dev/debt/libReactNative.Modules.Vsh.md @@ -0,0 +1,98 @@ +# libReactNative.Modules.Vsh technical debt + +### Callable exports + +- [Callable exports](../../../core/libs/prx/libReactNative.Modules.Vsh/Export.cpp) - 65 callable NIDs match the [public 3.20 catalogue](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libReactNative.Modules.Vsh.c) and the [selected export metadata](https://gist.github.com/kyon-eth/c09d6fdb71ceddd6d272f23ccc02ea4d/c875018fc77e54dcf76c639ceb7a58b0a85cf16b) from two version-labelled images (SHA-256 `ee343a3ad0c3badb1ed2ebff777bcc24cc00fbfedf7797ae37ed51b4d46dcd9e`). The labels are not authenticated. Prototypes and behavior are unverified; the declarations throw through `NotImplemented_nid_no_patch` and implement no runtime functionality. Data exports are not included. Three other functions shared with the WebAppLauncher library need separate provider-resolution analysis. + +| Name | NID | +| --- | --- | +| `CreateWebApiLibCtx` | `MjYzx34cnmE` | +| `CryptographyDecryptAES128CBC` | `x4fETeuAcVU` | +| `CryptographyDecryptAES128CBC_HS256` | `1HSkObnLOC0` | +| `CryptographyDecryptAES128GCM` | `MaAqY5ufzmk` | +| `CryptographyEncryptAES128CBC` | `WM4KfSxNA3I` | +| `CryptographyEncryptAES128GCM` | `SYc9wxeZia8` | +| `CryptographyGenerateHmacSha256` | `0L0rPrqMqqE` | +| `DestroyWebApiLibCtx` | `RJ9NntjPcEs` | +| `GetLibHttpCtxId` | `cCMDYO9tVWc` | +| `GetPs4AppCategoryForTitleId` | `bmzRityxR44` | +| `InetStatGetResultNative` | `nNDGebQ9xr0` | +| `InetStatGetStateNative` | `katLYKl5Xhw` | +| `InetStatStartTestNative` | `iTT38xYbOqI` | +| `InitializeNetLib` | `YzB8-Aa5UV4` | +| `LoginMgrCreateUser` | `jpb8rbo4MwU` | +| `LoginMgrDestroyUser` | `-N9SbnOwNto` | +| `LoginMgrInitialize` | `lvsjPQ8LMH0` | +| `LoginMgrSetLoginFlag` | `xXkwDDcyfBw` | +| `LoginMgrSetUserStatus` | `cxRN3ES1Hmk` | +| `LoginMgrTerminate` | `CBgGpKwrHSA` | +| `NpStateManagerRegisterCallback` | `D3CRTfQdhbA` | +| `NpStateManagerRtcSetTick` | `P6fVk0LPubc` | +| `NpStateManagerStart` | `eXL2pNf3JSA` | +| `NpStateManagerStop` | `5gsmp2Ox0Vs` | +| `NpStateManagerUnregisterCallback` | `f5Zum3vudFM` | +| `PSNowGetStatus` | `BFyDpKjORBg` | +| `PSNowGetStreamingStatus` | `ln8IwqIWE4I` | +| `PSNowInitialize` | `9Gsw6lvdddE` | +| `PSNowReset` | `gm2mcS8itac` | +| `PSNowSendCommand` | `mjcI9VABYqw` | +| `PSNowSetAuthCodes` | `PMAYiRpjoMY` | +| `PSNowSetSettings` | `H0yW7HkbWzI` | +| `PSNowTerminate` | `FbdEMaWO06g` | +| `RemotePlayClearConnectHistory` | `1nU-9usW+G0` | +| `RemotePlayConfirmDeviceRegist` | `Sh2l-tqTK68` | +| `RemotePlayDisconnect` | `Tjr5ksLUG1o` | +| `RemotePlayGeneratePinCode` | `Ktw+4DDd-60` | +| `RemotePlayGetConnectHistory` | `Pp9o-vFsZxc` | +| `RemotePlayGetConnectUserId` | `sB3dPDza3bk` | +| `RemotePlayGetConnectionStatus` | `PIt3ROlWOic` | +| `RemotePlayGetRemoteplayStatus` | `Mio-My1B0Ek` | +| `RemotePlayGetRpMode` | `4U7XQYIZAI0` | +| `RemotePlayIsRemotePlaying` | `RXRWD6iVzvI` | +| `RemotePlayNotifyCanWakeup` | `p1zwikCkFho` | +| `RemotePlayNotifyPinCodeError` | `xiJubl4cUak` | +| `RemotePlayNotifyUserDelete` | `GyKvp1lgCFw` | +| `RemotePlayProhibitStreaming` | `JSbNmt85AcQ` | +| `RemotePlaySetProhibition` | `BOtuzpAvdiU` | +| `RemotePlaySetProhibitionForVsh` | `4T9kRhpb-V0` | +| `RemotePlaySetRpMode` | `2YfXXadue0s` | +| `RtcGetCurrentAdNetworkTickNative` | `BMqsRTJXTRk` | +| `RtcGetCurrentDebugNetworkTickNative` | `8WzUhuCvVx4` | +| `RtcGetCurrentNetworkTickNative` | `m7od1ikoZpQ` | +| `TerminateNetLib` | `n38wJyalO6c` | +| `WebBrowserGetEnvelopeData` | `Z5HNtKvubuo` | +| `sceRemotePlayClientAddEventListener` | `gezFjY7iHbk` | +| `sceRemotePlayClientAttachVirtualMicrophone` | `nQ9t43vahAE` | +| `sceRemotePlayClientDetachVirtualMicrophone` | `nePk3oFOMXA` | +| `sceRemotePlayClientGetClientStatus` | `ux9VESJUgh4` | +| `sceRemotePlayClientInitialize` | `R0TfhbZMHm4` | +| `sceRemotePlayClientRemoveController` | `tDjU6UN7meM` | +| `sceRemotePlayClientShowHomeScreen` | `T589M5rT9iw` | +| `sceRemotePlayClientStartMicCapture` | `r9MlfKcu6N4` | +| `sceRemotePlayClientStopMicCapture` | `jLpO843jV50` | +| `sceRemotePlayClientTerminateStreaming` | `N1dFmppU9j0` | + +### Additional unidentified exports + +- Eighteen [callable exports](../../../core/libs/prx/libReactNative.Modules.Vsh/Export.cpp) are throwing placeholders. Names, signatures and behavior are unverified. Matching module and library identities occur in two version-labelled images; those labels are not authenticated. + +| Name | NID | +| --- | --- | +| `Unknown` | `+I5sPs9FLXw` | +| `Unknown` | `-9hYGDAsb4E` | +| `Unknown` | `BzJsgzD97LU` | +| `Unknown` | `IQNf2nnftNI` | +| `Unknown` | `N3mcm-qDFR0` | +| `Unknown` | `OME2Be+FlwA` | +| `Unknown` | `PLEe-toB-KE` | +| `Unknown` | `QGGsQrtLW9Y` | +| `Unknown` | `Rm+rgnBoPIE` | +| `Unknown` | `SJU+qK+8yrM` | +| `Unknown` | `T7dL-hEySvU` | +| `Unknown` | `b+MJDz3HB6s` | +| `Unknown` | `bbsP8-e8GZA` | +| `Unknown` | `fxEQAPDRhLw` | +| `Unknown` | `j917Pp4k-CI` | +| `Unknown` | `lZ-JDEqB34k` | +| `Unknown` | `tKy-Qdg+FRc` | +| `Unknown` | `uQfQgwm36L4` | diff --git a/docs/dev/debt/libSceAbstractStorage.md b/docs/dev/debt/libSceAbstractStorage.md index 133265d1eb..7890899db0 100644 --- a/docs/dev/debt/libSceAbstractStorage.md +++ b/docs/dev/debt/libSceAbstractStorage.md @@ -2,7 +2,7 @@ ### Known export names; ABI prototypes unverified -- 110 pending exports in [Export.cpp](../../../core/libs/prx/libSceAbstractStorage/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. +- 110 pending exports in [Export.cpp](../../../core/libs/prx/libSceAbstractStorage/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Each is named after the mangled symbol the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) gives for its NID, and the NID of that name matches. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. ### Additional callable exports diff --git a/docs/dev/debt/libSceAbstractTcs.md b/docs/dev/debt/libSceAbstractTcs.md index 1235886bd6..4dbce661f4 100644 --- a/docs/dev/debt/libSceAbstractTcs.md +++ b/docs/dev/debt/libSceAbstractTcs.md @@ -1,5 +1,5 @@ # libSceAbstractTcs technical debt -### Functional +### Unknown function info - [Older callable NIDs](../../../core/libs/prx/libSceAbstractTcs/Export.cpp): 40 unidentified exports have unverified names, signatures and behavior. These are throwing import-resolution placeholders; no functionality is implemented. No matching NIDs were found in the later module inventory. diff --git a/docs/dev/debt/libSceAbstractYoutube.md b/docs/dev/debt/libSceAbstractYoutube.md index 9b35c970a8..7e05322fa8 100644 --- a/docs/dev/debt/libSceAbstractYoutube.md +++ b/docs/dev/debt/libSceAbstractYoutube.md @@ -3,3 +3,51 @@ ### Known export names; ABI prototypes unverified - 17 pending exports in [Export.cpp](../../../core/libs/prx/libSceAbstractYoutube/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. + +### Additional callable exports + +- [Additional callable exports](../../../core/libs/prx/libSceAbstractYoutube/Export.cpp) - 39 callable NIDs match the [public 3.20 catalogue](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAbstractYoutube.c) and the [selected export metadata](https://gist.github.com/kyon-eth/d35bcd082a172a28ada2052647b0ff20/72a4f16fcb238e61dc8c6424dc26eee106e6c8ce) from two version-labelled images (SHA-256 `3d83607a2ed81110a37d677e5ba1936a01cb78c5847a2e9b582a4a981d313050`). The labels are not authenticated. Prototypes and behavior are unverified; the declarations throw through `NotImplemented_nid_no_patch` and implement no runtime functionality. + +| Name | NID | +| --- | --- | +| `_ZN15AbstractStorage13YoutubeFolder6RemoveEb` | `sFDmBXjzJKs` | +| `_ZN15AbstractStorage13YoutubeFolderD0Ev` | `oFx2mmWanvg` | +| `_ZN15AbstractStorage13YoutubeFolderD1Ev` | `lkGvBL1ly-Y` | +| `_ZN15AbstractStorage13YoutubeFolderD2Ev` | `KdoemZ8qogY` | +| `_ZN15AbstractStorage14YoutubeContent11SetMetadataEPN23sceMetadataReaderWriter8MetadataE` | `835p-ywJWEY` | +| `_ZN15AbstractStorage14YoutubeContent13ParseMetadataEPN23sceMetadataReaderWriter8MetadataES3_` | `iYDibzjdBf8` | +| `_ZN15AbstractStorage14YoutubeContent4OpenEit` | `gID4V0+Uk+k` | +| `_ZN15AbstractStorage14YoutubeContent4ReadEPvlPl` | `MJP2vvOKllQ` | +| `_ZN15AbstractStorage14YoutubeContent5CloseEv` | `BbkdawQaHyg` | +| `_ZN15AbstractStorage14YoutubeContent5FlushEv` | `Tz3OEt87piQ` | +| `_ZN15AbstractStorage14YoutubeContent5WriteEPKvlPl` | `95+sJrPf30A` | +| `_ZN15AbstractStorage14YoutubeContent6RemoveEv` | `XSYZ7Ac3HBQ` | +| `_ZN15AbstractStorage14YoutubeContent8SeekByteEliPl` | `d5LwAYdjVqk` | +| `_ZN15AbstractStorage14YoutubeContent8SeekTimeElPl` | `uHAsYIPFXPA` | +| `_ZN15AbstractStorage14YoutubeContentD0Ev` | `3XwULRPa25E` | +| `_ZN15AbstractStorage14YoutubeContentD1Ev` | `TFyVtXqnZrA` | +| `_ZN15AbstractStorage14YoutubeContentD2Ev` | `F09B23u5GSU` | +| `_ZN15AbstractStorage14YoutubeService13SearchStorageEv` | `6OnmSnvjzPI` | +| `_ZN15AbstractStorage14YoutubeService4StopEv` | `7JwKchX2P8o` | +| `_ZN15AbstractStorage14YoutubeService5StartEv` | `DGbb+VBzV6c` | +| `_ZN15AbstractStorage14YoutubeServiceC1Ev` | `UyVVO5GxZl4` | +| `_ZN15AbstractStorage14YoutubeServiceC2Ev` | `sYyou0hGbP8` | +| `_ZN15AbstractStorage14YoutubeServiceD0Ev` | `J526sLKBCc8` | +| `_ZN15AbstractStorage14YoutubeServiceD1Ev` | `jGcnzPEHoZ8` | +| `_ZN15AbstractStorage14YoutubeServiceD2Ev` | `dhk0U1yIuJo` | +| `_ZN15AbstractStorage14YoutubeStorage14GetStorageSizeEPlS1_S1_` | `u6FSGzt9Ay0` | +| `_ZN15AbstractStorage14YoutubeStorageD0Ev` | `VkzubmxnY7s` | +| `_ZN15AbstractStorage14YoutubeStorageD1Ev` | `D5iaQceaLcI` | +| `_ZN15AbstractStorage14YoutubeStorageD2Ev` | `hfifwAJDCvU` | +| `_ZNK15AbstractStorage13YoutubeFolder10GetServiceEv` | `cyheZerH-jM` | +| `_ZNK15AbstractStorage13YoutubeFolder13GetCapabilityEv` | `2jgqoWLtP4o` | +| `_ZNK15AbstractStorage13YoutubeFolder7GetStatEPNS_4StatE` | `Frm1N1BC9XE` | +| `_ZNK15AbstractStorage14YoutubeContent10GetServiceEv` | `zIMH5hIuWk4` | +| `_ZNK15AbstractStorage14YoutubeContent13GetCapabilityEv` | `EDw-LQKe3fE` | +| `_ZNK15AbstractStorage14YoutubeContent7GetStatEPNS_4StatE` | `+SMBrTixsiA` | +| `_ZNK15AbstractStorage14YoutubeService14GetServiceTypeEv` | `UTsKij05zBY` | +| `_ZNK15AbstractStorage14YoutubeStorage10GetServiceEv` | `uVLETIxb0yA` | +| `_ZNK15AbstractStorage14YoutubeStorage13GetCapabilityEv` | `jgO+nc-VCgU` | +| `_ZNK15AbstractStorage14YoutubeStorage7GetStatEPNS_4StatE` | `GGthfnlokVw` | + +- [Older callable NIDs](../../../core/libs/prx/libSceAbstractYoutube/Export.cpp): 20 unidentified exports have unverified names, signatures and behavior. These are throwing import-resolution placeholders; no functionality is implemented. No matching NIDs were found in the later module inventory. diff --git a/docs/dev/debt/libSceAgc.md b/docs/dev/debt/libSceAgc.md index 454547f8a6..81f8fce239 100644 --- a/docs/dev/debt/libSceAgc.md +++ b/docs/dev/debt/libSceAgc.md @@ -19,7 +19,7 @@ - [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 +- [sceAgcUpdateInterpolantMapping_0100](../../../core/libs/prx/libSceAgc/Shader/src/InterpolantMapping.cpp) (libSceAgc) - null pointers, missing semantic tables and input counts above 32 throw as in the sibling host exports; the console error behavior for these inputs has not been measured - [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. - [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 @@ -34,7 +34,7 @@ - [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) - [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. In PPSA12544 the Acb function is called from 2 sites (0x11d9538, 0x11d95a4) and the Dcb function from 4 (0x11d76d4, 0x11d7740, 0x16e5eac, 0x16f32a6), each with three arguments and no read of the result; which of the last two arguments is the handle and which the index is not shown. - [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. +- [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. PPSA25286 01.000.568, PPSA34502 01.010.000 and PPSA36591 01.002.000 each link one wrapper per function (`SetIndexIndirectArgs`/`GetSize` at 0x405AC0/0x406030, 0xAA1C10/0xAA2180 and 0x898300/0x898870): the first passes index 0 with the buffer and address it was given, the second passes its arguments unchanged. Run-time use is untraced; PPSA01494, PPSA02225, PPSA03984, PPSA18194, PPSA30839 and PPSA31162 import neither. - [sceAgcSuspendPointAndCheckStatus](../../../core/libs/prx/libSceAgc/Export.cpp) - this named callable export is listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAgc.c) and was cross-checked against the same module in PS5 firmware 13.60. Its signature and behaviour are unverified; the declaration uses the no-parameter unknown-signature convention and throws through `NotImplemented_nid_no_patch`. diff --git a/docs/dev/debt/libSceAgcDriver.md b/docs/dev/debt/libSceAgcDriver.md index f3e14005e4..ec905ee835 100644 --- a/docs/dev/debt/libSceAgcDriver.md +++ b/docs/dev/debt/libSceAgcDriver.md @@ -4,7 +4,9 @@ - [sceAgcDriverInitResourceRegistration, sceAgcDriverQueryResourceRegistrationUserMemoryRequirements](../../../core/libs/prx/libSceAgcDriver/Resource/src/Lifecycle.cpp) (libSceAgcDriver) - the init call returns 0 without setting anything up. The query writes 16 KiB (0x4000) as the user memory registration needs and returns 0, and a null output returns `AGC_EINVAL` (0x80020065) without writing. I found no source for that size. Registration stays unavailable (see Resource registration), so the size is never used. Crimson Desert (PPSA25286), Call of Duty: Black Ops II (PPSA34502), Steins;Gate Re:Boot (PPSA36591) and Metal Gear Solid 4 (PPSA18194) reach both through one-instruction wrappers. - [sceAgcDriverRegisterDefaultOwner](../../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp) (libSceAgcDriver) - takes one uint32 owner by value (OpenAGC `include/agcdriver.h`, SharpEmu `AgcExports.DriverResources.cs`) and returns 0 without recording it. PPSA21480 treats the code 0x8A6C9018 it returned before as fatal. Console behaviour is unverified: OpenAGC describes the function as a firmware 5.50 stub that returns 0x8A6C9018, SharpEmu returns 0. The one call in PPSA21480 (0x7f41f) is followed by a test of the sign of the result only; a negative result ends in a coredump user string write and `abort()` (0x131d0-0x13214). +- [sceAgcDriverIsCaptureInProgress, sceAgcDriverIsTraceInProgress, sceAgcDriverIsSubmitValidationEnabled](../../../core/libs/prx/libSceAgcDriver/State/src/Status.cpp) (libSceAgcDriver) - return false in all cases. The console value was not measured, and what titles do with the result is unknown: the title code only turns it into a bool and returns it. - [sceAgcDriverSetResourceRegistrationInfo](../../../core/libs/prx/libSceAgcDriver/Resource/src/Registration.cpp) (libSceAgcDriver) - returns 0 without storing the info, as the retail `libSceAgcDriver.sprx` body does; registration is unavailable, so nothing reads it back. +- [Global atomics on buffer device addresses](../../../core/libs/prx/libSceAgcDriver/Execution/include/BdaAtomics.hpp) (libSceAgcDriver) - a flat atomic reaches its target through a Vulkan buffer device address (`OpConvertUToPtr` over a `PhysicalStorageBuffer` pointer), so the read-modify-write is atomic only where the driver guarantees atomics on such an address. MoltenVK does not: Metal has no atomic on a device pointer, so the update is a load and a store and concurrent updates to one address are silently lost. The driver does not refuse the shader; `BdaAtomicsUsable` reports the limitation and only `agc_driver_pixel_interlock` consults it. `agc_driver_global_atomics` cannot cover the case: every lane has its own address, so it checks an uncontended read-modify-write. A title that counts or locks through a flat atomic on macOS counts wrong, and no title has been measured. ### Additional callable exports @@ -31,13 +33,17 @@ ### Unknown function info +- [libSceAgcDriver pending exports](../../../core/libs/prx/libSceAgcDriver/Export.cpp) - 24 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAgcDriver.c). Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. + - [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. Compute queues are still accepted only for 0x20 to 0x57; queue 0x58 and above throw. Console acceptance of 0x58 on these entrypoints was not measured +- [sceAgcDriverAddEqEvent, sceAgcDriverDeleteEqEvent](../../../core/libs/prx/libSceAgcDriver/Eq/src/Event.cpp) (libSceAgcDriver) - validates the event queue descriptor and non-negative event IDs for graphics and compute end-of-pipe event registration. Deletion unregisters from the kernel event queue before removing the registration from driver state, ensuring failed deletions preserve registered events. Event delivery and filter semantics for alternate event filters remain unverified on console hardware. + - [sceAgcDriverSubmitAcb](../../../core/libs/prx/libSceAgcDriver/Submit/src/Acb.cpp) (libSceAgcDriver) accepts compute queues 0x20 to 0x58 and throws for any other. On a PS5 (firmware 13.40, measured 2026-10-19 from inside a retail title) it returned 0 and ran a label-only buffer on queues 0x20, 0x30, 0x40, 0x50, 0x57 and 0x58; queue 0x1F faulted inside the console's driver (SIGSEGV in libkernel on the calling thread), which throws here too. Queues above 0x58 were not measured. Four titles (PPSA15246 at 0x71dc9b, PPSA13356 at 0xd0c7b, PPSA21480 at 0xa920b, PPSA35169 at 0x6d08bb) disassembled for this entry call it once each from the same small wrapper, with queue 0x20 + 8 * the low byte of its argument | the next byte, and return the result unchanged; which values occur is not shown. Closes when: the console's highest accepted compute queue is measured - [sceAgcDriverSubmitMultiAcbs](../../../core/libs/prx/libSceAgcDriver/Submit/src/Acb.cpp) (libSceAgcDriver) still accepts only compute queues 0x20 to 0x57; queue 0x58 and above throw. Console acceptance of 0x58 on this entrypoint was not 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`. -- [sceAgcDriverSetFlip, sceAgcDriverSetWorkloadsActive, sceAgcDriverSetWorkloadComplete, sceAgcDriverUserDataWritePacket, sceAgcDriverUserDataWritePushMarker, sceAgcDriverUserDataWritePopMarker, sceAgcDriverUserDataWriteSetMarker](../../../core/libs/prx/libSceAgcDriver/Export.cpp) (libSceAgcDriver) - declared without parameters; they throw through `NotImplemented_nid_no_patch`. The tree answers their packet sizes (`sceAgcDriverGetSetFlipPacketSizeInDwords`, `sceAgcDriverGetSetWorkloadsActivePacketSize`, `sceAgcDriverGetSetWorkloadCompletePacketSize`, `sceAgcDriverUserDataGetPacketSize` in `Submit/src/Dcb.cpp`), and libSceAgc writes its own flip, workload and marker packets through a `CommandBuffer*` (`libSceAgc/DcbState/src/Display.cpp`, `Workload.cpp`, `Marker.cpp`), but it has no driver-level prototype, so whether these calls take the same arguments and write the same words is not known +- [sceAgcDriverSetFlip, sceAgcDriverSetWorkloadsActive, sceAgcDriverSetWorkloadComplete](../../../core/libs/prx/libSceAgcDriver/Export.cpp) (libSceAgcDriver) - declared without parameters; they throw through `NotImplemented_nid_no_patch`. The tree answers their packet sizes (`sceAgcDriverGetSetFlipPacketSizeInDwords`, `sceAgcDriverGetSetWorkloadsActivePacketSize`, `sceAgcDriverGetSetWorkloadCompletePacketSize` in `Submit/src/Dcb.cpp`), and libSceAgc writes its own flip and workload packets through a `CommandBuffer*` (`libSceAgc/DcbState/src/Display.cpp`, `Workload.cpp`), but it has no driver-level prototype, so whether these calls take the same arguments and write the same words is not known - [sceAgcDriverCreateQueue, sceAgcDriverDestroyQueue, sceAgcDriverSetupAsyncGraphics, sceAgcDriverSuspendPointSubmit, sceAgcDriverIDHSSubmit, sceAgcDriverTmpInitIdhs, sceAgcDriverSubmitToHDRScopesACQ, sceAgcDriverCwsrSuspendAcq, sceAgcDriverCwsrResumeAcq](../../../core/libs/prx/libSceAgcDriver/Export.cpp) (libSceAgcDriver) - declared without parameters; they throw through `NotImplemented_nid_no_patch`. The tree submits only through the calls in `Submit/src` and knows a queue context only as `sceAgcDriverSubmitCommandBuffer` reads it; it has no arguments or results for queue creation, suspend-point or IDHS submission, or the HDR-scopes and CWSR queue calls - [sceAgcDriverGetRegShadowInfo, sceAgcDriverGetRegShadowInfoAgr, sceAgcDriverSetupRegisterShadow, sceAgcDriverPatchClearState, sceAgcDriverNotifyDefaultStates, sceAgcDriverPassInfoDownward, sceAgcDriverGetReservedDmemForAgc, sceAgcDriverGetWorkloadStreamInfo](../../../core/libs/prx/libSceAgcDriver/Export.cpp) (libSceAgcDriver) - declared without parameters; they throw through `NotImplemented_nid_no_patch`. The executor keeps no register-shadow memory and runs `CLEAR_STATE` itself (`Execution/src/Pm4.cpp`) and workload streams are kept only as registered ids (`Resource/src/Registration.cpp`); the arguments and results of these calls are not in the tree - [sceAgcDriverDebugHardwareStatus, sceAgcDriverSysEnableSubmitDone45Exception, sceAgcDriverSysGetClientNumber, sceAgcDriverSysIsGameClosed](../../../core/libs/prx/libSceAgcDriver/Export.cpp) (libSceAgcDriver) - declared without parameters; they throw through `NotImplemented_nid_no_patch`. They are not among the retail constant results in `Unimplemented.cpp`, and the tree has no prototype or console answer for them @@ -59,16 +65,19 @@ - [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. 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). +- [Native multisampled depth sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) accepts numeric D16 or D32, and D32 with an 8-bit stencil plane whose state is in HTILE or, with a depth-only HTILE, in the plane, two/four/eight samples, one Z_X slice and mip, and pipe-aligned HTILE, or a depth-only HTILE that is not pipe-aligned whose words are all one clear word, without 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, with the stencil in HTILE, SMem 0 (the stencil holds `DB_STENCIL_CLEAR`), or with a depth-only HTILE every stencil plane byte must equal it; and the complete sample-aware depth, stencil 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 fill of the whole HTILE, or a one-byte fill of the whole stencil plane (a stencil clear), is proven at the next sample like an HTILE mask clear: complete tracked stores since the fill, or a fill still queued that is the one write recorded over the range since it was noted (a fill noted again once that write is recorded counts once), and every word proving the depth and, with the stencil in HTILE, the stencil clear, or every plane byte the fill byte; partial fills, an HTILE mask clear of a target with stencil, and an HTILE fill noted while an HTILE mask clear dispatch is pending (from its notification until a sample takes it) are refused. A queued fill of more than 64 KiB is stored through a host import, so where host imports are compared rather than write-watched (always on Windows, and on Linux where the import probe finds that imports resolve write protection) its HTILE is no longer write-watched after the store and the fill is refused. A stencil plane whose exact footprint a resident multisampled R8 colour target of the same sample count covers takes its value from that target's newest uniform content (a fill it absorbed, a CMASK fast clear, or a recorded clear with no draw since), and a plane fill is proven the same way, without reading guest memory; any colour target over the depth or HTILE, or a colour draw over a plane taken this way, refuses sampling, and other colour content over the plane leaves it unproven as listed below. A descriptor without metadata whose XOR and ITERATE_256 match is sampled from the decompressed host image only after an observed in-place expand, a draw over the target with `DB_RENDER_CONTROL.DEPTH_COMPRESS_DISABLE` and, with a stencil plane, `STENCIL_COMPRESS_DISABLE`, and until an HTILE clear or fill, a plane fill or a clear draw; a later depth write that recompresses tiles is not observed. Other metadata is refused. 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, raw-bit sampling, arrays and storage access remain unimplemented; stencil plane sampling is listed below. 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 D32S8 depth without HTILE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) is imported only when its depth plane repeats one float in [0, 1], its separate stencil plane repeats one byte and no multisampled colour target covers either plane; the host image starts at those values and the sampled descriptor must name no metadata, whose pipe-aligned and ITERATE_256 bits are then not compared. The depth plane is sampled as a 32-bit float and the stencil plane through the R8_UINT descriptor of the stencil plane sampling entry below. That every sample then holds them is inferred from the uncompressed layout, not measured on the console. Planes holding more than one value, and D16 or D32 without HTILE, still throw. +- [Native multisample stencil plane drawn as colour](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a native D32S8 target whose separate stencil plane a multisampled colour target covers with anything but a known R8 value of the same sample count is imported with the plane unproven: its host stencil is not taken from it, and a stencil-tested draw or a sample of the plane refuses (`native multisample stencil plane aliases a multisampled colour target whose samples are only on the host`) until a whole-plane fill gives its byte, which a colour target still over the plane must have absorbed as the same R8 byte at the same sample count. Taking a drawn plane without a fill needs a GPU copy of the colour target's R8 samples, in the same sample order, into the depth image's stencil aspect, which is not implemented. Whether the console's stencil reads of a plane written through the colour path match that copy is not measured. +- [Native multisample stencil plane sampling](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - the separate stencil plane of a native multisampled D32S8 target with a depth-only HTILE is sampled through an R8_UINT 2D MSAA descriptor at the plane with the target's extent, samples, tile mode and XOR and no metadata, as a stencil-aspect view of the host depth image or of its sample shadow, which then copies the stencil with the depth. A stencil kept in HTILE, another format, metadata or layout is refused, and a plane left unproven by a colour target refuses as listed above. Whether the console's reads of the plane through such a descriptor match the host stencil is not measured. - [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. +- [GS_FAST_LAUNCH geometry](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - `VGT_SHADER_STAGES_EN.GS_FAST_LAUNCH = 1` with `GE_CNTL` groups of one vertex and one primitive decodes as one point per subgroup whatever `VGT_PRIMITIVE_TYPE` holds, 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`, a point list whose vertex id gives the workgroup index), not measured on the console; for a line or triangle type (PPSA34547 uses a triangle strip) one subgroup per vertex is assumed. Fast launch 2, other group sizes 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 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) - `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. Two-sample `DB_ALPHA_TO_MASK = 0xe701` (Bugsnax, PPSA01503) uses an eight-level table measured on gfx1032 with the locations (4, 4), (-4, -4) and (-4, 4), (4, -4), not on gfx1013; other sample counts, locations and exports throw, and `MASK_EXPORT_NUM_SAMPLES` 0 is accepted though measured at 1 +- [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`, 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. Two-sample `DB_ALPHA_TO_MASK = 0xe701` (Bugsnax, PPSA01503) uses an eight-level table measured on gfx1032 with the locations (4, 4), (-4, -4) and (-4, 4), (4, -4), not on gfx1013; other sample counts, locations and exports throw, and `MASK_EXPORT_NUM_SAMPLES` 0 is accepted though measured at 1 - [Single-sample dithered alpha-to-coverage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (AGC driver) - single-sample targets with dithered `DB_ALPHA_TO_MASK` (0xe701 or 0x18701) throw `requires two matched raster/DB_EQAA samples and one pixel iteration` (for 0xe701) or `requires eight matched raster/DB_EQAA samples and one pixel iteration` (for 0x18701) in `DecodeState` and `DrawRejection`. An initial Vulkan `alphaToCoverageEnable` readback on RADV (RX 6650 XT) for a 4x4 target exhibited quad-granular coverage rather than a per-pixel Bayer pattern inside each 2x2 quad, but the spatial repetition period and PS5 console silicon behaviour remain unmeasured across larger targets. Bugsnax (PPSA01503) reaches 0xe701 with one sample at the Flavor Falls bridge cutscene transition. Stays unsupported until measured on hardware. Closes when: spatial repetition period is measured on AMD RADV or PS5 hardware - [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. @@ -78,12 +87,13 @@ - [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 +- [sceAgcDriverUserDataWritePacket, sceAgcDriverUserDataWritePushMarker, sceAgcDriverUserDataWriteSetMarker, sceAgcDriverUserDataWritePopMarker](../../../core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp) (libSceAgcDriver) - the packets follow firmware 13.42's libSceAgcDriver, read from disassembly, not measured on a console: a SET_UCONFIG_REG of register 0x342 (and 0x343 with up to four data bytes), then a WRITE_DATA of the remaining bytes to register 0xC343. The second argument is named `asyncCompute` because firmware libSceAgc passes 1 from its `sceAgcAcb*` marker calls and 0 from `sceAgcDcbPopMarker`; 0 sets header bit 2 in both packets. The marker kinds (0xC0000000 set, 0xC1000000 push, 0xC2000000 pop) and the colour packing are copied without names - [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) - 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. +- [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. A V# within its offset adjustment of the limit is bound by the limit rounded down to a DWORD, so its last bytes are past the bound range. 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. - [Guest buffer region over 4 GiB copied whole](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) - `GuestBufferMemory::copyRegion` copies a guest buffer region over 4 GiB whole into one HOST_VISIBLE|HOST_COHERENT buffer of the region's size (0x102000000, 0x142000000 and 0x102000000 bytes in three runs of PPSA15246 on an AMD RX 6400), and `vkAllocateMemory` refuses it with -13 although the host heap (31961 MiB) had at most 5933 MiB in use; only a sparse region is cut to its backed and viewed extent. An RTX 4080 SUPER stops at the descriptor range check in `GuestBufferMemory::Descriptor` for the same shader instead; its `maxStorageBufferRange` was not measured. Which part of the region the shader reads is unknown. - [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) - 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. @@ -96,7 +106,7 @@ - [NGG vertex workgroups](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - type-2 PRIMGEN programs using DS instructions or `s_barrier` are prepared with the draw's routing and primitive registers on the existing mesh path. The workgroup requirement follows [Mesa's NGG lowering](https://github.com/mihawk-99/PS5_Mesa/blob/b3588f78dd7fd46fbadc7ecd487e5f14d4213e4b/src/amd/common/nir/ac_nir_lower_ngg.c#L1135); the mesh path supplies LDS and two guest lanes per host invocation for wave64 on a 32-lane host subgroup. Console routing has not been measured. Passthrough and tessellation keep their existing paths; unsupported mesh assembly and subgroup budgets throw. - [Shader header copies](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (libSceAgcDriver) - a header bound for a draw or dispatch at another address than the registered one is accepted when its fixed fields match the registered header, and every array it points at still holds the registered bytes; the registered header's metadata is used. `user_data` is not compared (PPSA21567 binds such a copy of a vertex shader header with `user_data` set to null; whether the console reads the copy's `user_data` is unknown). A copy whose registers, context registers, specials or semantics differ, or whose arrays are unreadable, throws `... refers to a replaced shader header`; what the console does with a header a title patches while relocating it is not measured. PPSA01325 (#2914) resolves the ABI with such a copy - [ClearStateContextRegisters](../../../core/libs/prx/libSceAgcDriver/Execution/include/QueueState.hpp) (libSceAgcDriver) - `CLEAR_STATE` and a push-clear `CONTEXT_STATE` (operation 3) reset `SPI_PS_IN_CONTROL` (0x1b6) to NUM_INTERP 2, the value of Linux's GFX10 clear-state table (`clearstate_gfx10.h`, `gfx10_SECT_CONTEXT_def_2`) that radeonsi tracks after `CLEAR_STATE`. The value on a PS5 console is not measured. The CP queue reset (PM4 operation 0x09) and the starting context keep 0, because registration-time decoding prepares a pixel program only while NUM_INTERP is 0. The other registers radeonsi assumes after a clear (`CB_TARGET_MASK`, `CB_SHADER_MASK`, `PA_SU_SC_MODE_CNTL`, `PA_SU_VTX_CNTL`) still reset to the starting table. -- [HtileFillCovers](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a fill is taken to cover a depth surface's whole HTILE when it spans 32 KiB per 1024x512 pixel block from DB_HTILE_DATA_BASE, the pipe-aligned SW_64KB_Z_X metadata extent AMD PAL's gfx10 addrlib gives for GB_ADDR_CONFIG 0x00100044 (1-8 samples, D16/D32); other metadata layouts are not decoded. A partial fill is ignored (the surface keeps its contents); not measured on the console. +- [HtileFillCovers](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a fill is taken to cover a depth surface's whole HTILE when it spans 32 KiB per 1024x512 pixel block from DB_HTILE_DATA_BASE, the pipe-aligned SW_64KB_Z_X metadata extent AMD PAL's gfx10 addrlib gives for GB_ADDR_CONFIG 0x00100044 (1-8 samples, D16/D32). A PS4 depth target's HTILE spans 16 KiB per 512x512 pixel block, the macro tile addrlib's CI `ComputeTileDataWidthAndHeight` gives for 32-bit HTILE words, 16384 cache bits and the 8 pipes of P8_32x32_16x16, and the size a PS4 title's clears fill for 512x512, 2048x1152 and 4096x4096 targets; other metadata layouts are not decoded. A partial fill is ignored (the surface keeps its contents); not measured on the console. - [Depth fast clear through HTILE](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a surface cleared by an HTILE fill is cleared to DB_DEPTH_CLEAR, also when it is next sampled. For a depth-only HTILE the clear word also carries a value (radv writes `minz = maxz = round(depth * 0x3fff)`, `HTILE_Z_CLEAR_REG` in Mesa's `src/amd/common/ac_descriptors.h`, and allows TC-compatible fast clears only to 0.0 or 1.0); which of the two the texture unit returns for a cleared tile was not measured. - [Depth-only and stencil HTILE fill](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - in a whole-HTILE fill, ZMask (bits 0-3) 0 clears the depth and SMem (bits 8-9) 0 clears the stencil, while SMem 3 leaves the stencil alone (`HtileFillClears`); other SMem values are taken to leave it alone too. The bit meanings follow the gfx10 HTILE layout as the code reads it, not a measurement; `agc_driver_depth_fast_clear_submit` pins them for the paths the Driver takes. - [Depth and stencil plane fills](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - a fill that covers a known depth surface's whole stencil plane (`DepthSliceBytes` of its extent at one byte per texel) with four equal bytes clears the host stencil to that byte the next time the surface is bound, sampled or stored to, and a fill that covers the whole depth plane (two or four bytes per texel) clears the depth when the dword is a float in 0.0-1.0, or two equal 16-bit halves read as UNORM for D16 (`NoteDepthMetadataFill`). A partial fill, a dword whose bytes differ, a depth value outside 0.0-1.0 (0xff000000 is seen), a multisampled surface, or a fill made before the surface exists leave the host image as it was. An HTILE fill or a recognized HTILE clear kernel noted after a plane fill replaces its value with DB_DEPTH_CLEAR/DB_STENCIL_CLEAR. On the console the fill writes the plane the depth block reads; the surfaces this was seen on keep the stencil out of HTILE (PPSA26525 Heretic + Hexen and PPSA21444 DOOM + DOOM II reset the Cohtml UI stencil plane to 1 with a 0x01010101 fill every frame, then draw with stencil Equal 1 and 2), and what the console returns for a plane fill under a compressed HTILE was not measured. `agc_driver_depth_plane_fill` pins the rules. @@ -111,23 +121,29 @@ ### Functional +- [Native multisampled depth import](../../../core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp) (libSceAgcDriver) - refuses a depth HTILE that is not pipe-aligned (`0x2af` bit 18 is zero). Sonic Superstars (PPSA06888) uses a layout with iterate-256 also off (`DB_Z_INFO` bit 20 is zero) on its 2x depth targets, and the refusal stops presentation before its title screen. Not verified: whether the clear-proof is correct for a non-pipe-aligned layout, since the import reads the HTILE words as a flat array. - [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. +- [FS table calls on macOS](../../../core/libs/prx/libSceAgcDriver/Execution/include/Driver/DriverMemory.hpp) (libSceAgcDriver) - macOS keeps the console's 0xfe0000000 for itself, so the driver block, and the FS table at 0xfe0040000 inside it, moves to 0xee0000000 there; `sceAgc*StaticBuffer` and `AgcDriverStaticTable_nid_postfix` use the moved address. A shader that builds the table address from two `s_mov_b32` literals still loads from 0xfe0040000: `ReadTableCallees` (`Driver/Shaders/ShaderRegistry.cpp`) reads that address unchanged, and the shader also loads from it at run time, so those dispatches cannot link on macOS. #6160 lists titles that build this address. `ComputeFsTableCalls` is skipped on macOS as it is under `ANYPS5_ENABLE_ASAN`. Closes when: the driver rewrites that pointer in the shader code for both the callee lookup and the code the GPU runs, or the console address can be mapped on macOS. +- [Capture trace output](../../../core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp) has no timeout for permanently blocked file I/O, so normal shutdown can wait for its writer. The pending queue holds up to 16 MiB, and the batch being written can hold another 16 MiB plus loss markers. Queue overflow drops new records until the next drain; markers and a shutdown total identify the incomplete trace. A failed write or flush disables tracing and reports an error to stderr, but the failed output may not receive its final markers. - [Frame capture](../../../core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp) (libSceAgcDriver) throws on macOS. Section views, aliases of one direct memory block and file views replay as separate private memory, so a store through one alias is not seen through another. Only write-watched memory records CPU stores during the frames: private memory on Windows, where a store through one alias is recorded only at that alias; registered ranges on Linux, including direct memory unless mapped more than once (libkernel leaves those views unwatched, so no store through any of them is recorded). Title image pages are recorded only when changed and holding a shader header or its arrays. Linux omits no-access pages (Windows records them unreadable) and records file mapping pages past the file's end as unreadable. A capture starts only at a frame boundary where the driver idles within 10 s (five tries), and 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. - [Host-only multisample memory in frame capture](../../../core/libs/prx/libSceAgcDriver/Execution/src/FrameRecorder.cpp) (libSceAgcDriver) - memory under multisampled color targets, their FMASK and native multisample depth whose samples are only on the host is zero in the snapshot and in deltas (`host_only_sample_bytes`, with a warning), so replay starts those targets without the contents earlier frames left in them. +- [Oracle runner](../../../core/libs/prx/libSceAgcDriver/tools/OracleRunner.cpp) (libSceAgcDriver) accepts only the hardware oracle template's ABI (kernarg pointer in `s[0:1]`, no scratch, 16-dword output) and runs one workgroup of whole waves. NaN results are compared bit for bit although the recompiler canonicalizes NaNs on hosts that do, so a NaN payload difference from the hardware is a finding about the recompiler, not the runner. - [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. -- [UINT color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - what the console's CB does with a raw 32-bit export into an 8- or 16-bit UINT channel (clamp or truncate) and whether it blends into integer targets at all is not measured. Here a raw 32-bit export (`32_R`, `32_GR`, `32_AR`, `32_ABGR`) into a UINT target is passed as the exported bits through the `Uint` export packing and the target's blend is dropped; the execution test draws `32_R` into `R32_UINT`, `32_GR` into `R32G32_UINT` and `32_AR` and `32_ABGR` into `R32G32B32A32_UINT` (tiled and linear) and reads every written channel back bit for bit, and draws `32_R` values from 0 to 255 into `R8_UINT`; a value an 8- or 16-bit channel cannot hold is left to the Vulkan attachment's conversion, which Vulkan leaves undefined, and is not tested. On lavapipe (Mesa, LLVM 20) a constant export of exactly 1.0 in the first channel and -0.0 in the second stores +0.0 in the second although the module declares `SignedZeroInfNanPreserve`, so the test leaves that pair out. The blend is dropped since Vulkan's framebuffer chapter says blending is only defined for floating-point, UNORM, SNORM and sRGB formats and Metal refuses both the float output and the blend on integer attachments (#9783). `FP16_ABGR`, `UNORM16_ABGR`, `SNORM16_ABGR` and `SINT16_ABGR` exports into UINT targets throw. +- [UINT color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - what the console's CB does with a raw 32-bit export into an 8- or 16-bit UINT channel (clamp or truncate) and whether it blends into integer targets at all is not measured. Here a raw 32-bit export (`32_R`, `32_GR`, `32_AR`, `32_ABGR`) into a UINT target is passed as the exported bits through the `Uint` export packing and the target's blend is dropped; the execution test draws `32_R` into `R32_UINT`, `32_GR` into `R32G32_UINT` and `32_AR` and `32_ABGR` into `R32G32B32A32_UINT` (tiled and linear) and reads every written channel back bit for bit, and draws `32_R` values from 0 to 255 into `R8_UINT`; a value an 8- or 16-bit channel cannot hold is left to the Vulkan attachment's conversion, which Vulkan leaves undefined, and is not tested. On lavapipe (Mesa, LLVM 20) a constant export of exactly 1.0 in the first channel and -0.0 in the second stores +0.0 in the second although the module declares `SignedZeroInfNanPreserve`, so the test leaves that pair out. The blend is dropped since Vulkan's framebuffer chapter says blending is only defined for floating-point, UNORM, SNORM and sRGB formats and Metal refuses both the float output and the blend on integer attachments (#9783). An `FP16_ABGR` export into a UINT target stores each half widened to a 32-bit float (a NaN with its quiet bit set) and read as an unsigned integer, clamped to the channel, so an 8- or 16-bit channel holds 0 for +0.0 and its maximum for any other half; two Navi21 GPUs (RDNA2) store this with RADV forced to `FP16_ABGR` (#11513), the console is not measured. The shader widens the half bits with integer operations and the `UintClamp8` and `UintClamp16` packings clamp them; the execution test draws halves read at run time into 32-, 16- and 8-bit UINT targets, and through each clamping packing into `R32_UINT`. An uncompressed `FP16_ABGR` export into a UINT target keeps its 32-bit words without a conversion to half, which is not measured. `UNORM16_ABGR`, `SNORM16_ABGR` and `SINT16_ABGR` exports into UINT targets throw. - [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 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. +- [Staging shadow headroom](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Resources.cpp) (libSceAgcDriver) - a device-local staging shadow (`stagingBuffer` in `GuestBufferMemory.cpp`) that needs a new allocation is refused when that allocation would end within the last eighth of the budget `VK_EXT_memory_budget` reports for the heap of the memory type it would be allocated from; the region then takes its unstaged path. A shadow the buffer pool already retains is reused without the check. The eighth comes from one title on one GPU (PPSA02530 on a 16 GiB RX 9070 XT, RADV), not from a measured rule. Budget readings are the driver's estimates, and the check reserves nothing: allocations made concurrently by other threads or by the caches can still take the heap past the eighth. Devices without `VK_EXT_memory_budget`, or whose heap reports no budget, make every shadow as before. - [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) (libSceAgcDriver) take only uniform 0000/0001/1110/1111 keys, or a whole-range DCC fill of one, into a single-layer, level-zero SW_64KB_R_X image of 2, 4 or 8 fragments. Other initial DCC, register CMASK clears, compressed payloads, color transforms, lossy modes, partial fills, a CMASK changed without a whole fill, a range compared only after its import unless a whole fill follows, guest publication and writes over a live owner throw. Color and metadata must stay write-watched or lie in a host import the Linux probe compares, where a same-value CPU store is not seen; compared Windows imports throw. The metadata extent follows [AMD Addrlib](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp#L347-L454) for GFX10.1 with 16 pipes; console geometry is unverified. +- [Native multisampled DCC fast clears](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) (libSceAgcDriver) take uniform 0000/0001/1110/1111 keys and the register-clear key 0x20, or a whole-range DCC fill of one, into a single-layer, level-zero SW_64KB_R_X image of 2, 4 or 8 fragments. The register-clear key uses `CB_COLOR_CLEAR_WORD0/1` through the existing exact texel-to-host clear conversion; formats over 64 bits or without an exact conversion throw. Register-clear semantics follow [Mesa ac_descriptors.h](https://chromium.googlesource.com/external/gitlab.freedesktop.org/mesa/mesa/+/418c4cfa6708a0e0b1175e72fb8fd27d3ca1615a/src/amd/common/ac_descriptors.h) (`GFX8_DCC_CLEAR_REG`); the console result has not been measured. Other initial DCC, register CMASK clears, compressed payloads, color transforms, lossy modes, partial fills, a CMASK changed without a whole fill, a range compared only after its import unless a whole fill follows, guest publication and writes over a live owner throw. Color and metadata must stay write-watched or lie in a host import the Linux probe compares, where a same-value CPU store is not seen; compared Windows imports throw. The metadata extent follows [AMD Addrlib](https://github.com/GPUOpen-Drivers/pal/blob/c5e800072a32f68b6ccc4422936d96167c6e0728/src/core/imported/addrlib/src/gfx10/gfx10addrlib.cpp#L347-L454) for GFX10.1 with 16 pipes; console geometry is 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. - [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. +- [Written type-0 and type-1 texture descriptors](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp) (`TYPE` = 0 or 1 in dword 3) in [static slots](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) with no image table are bound as null storage images, as read slots are; atomic slots throw. Stores are measured on gfx1030 (RDNA2) in #9749: an `image_store` through the PPSA01474 words with TYPE 0 or 1 changed no memory around the base and didn't fault, and `nullDescriptor` discards a null storage image's stores. Neither type was measured as a store on the console's gfx1013. - [Multisampled color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp) keep their samples on the host, and guest access to live samples throws. A guarded access retires a target instead when its samples are not only on the host (a texture sampled before any draw, until a draw, storage write or absorbed fill; it is rebuilt from guest memory at its next use), or when its memory was reused since that last use: a CPU write over the accessed bytes in watched memory, or a bind over any of its guarded ranges of a depth or stencil plane, HTILE or color target other than itself or its sRGB twin. A one-fragment target's FMASK guard is never claimed and still refuses. A bind is taken as reuse, so a target only sampled after one loses its samples. An unmap or allocation removal of a whole guarded range also retires it; partial unmaps keep the guard. Retired cache owners go at the next lookup; recorded work and sampled views keep their images until released. - [Shader buffer access to native multisample targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp) - Shader buffer descriptors retain the existing native color/depth guest-access refusal during preparation, completion and binding, including cached bindings and moved draw ranges. The check covers logical descriptor ranges, not larger import windows; dynamically addressed BDA heaps and vertex/index input snapshots remain outside this check. Native MSAA buffer publication and console equivalence remain unimplemented or unmeasured. - [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. @@ -162,28 +178,33 @@ - [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): 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. +- [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) graphics 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. +- [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`. The fast link kept the ordering on an RTX 4090 (NVIDIA 617.42, 240 of 240 loaded runs), an RTX 5060 Ti (621.01) and a Radeon 860M on AMD's Windows driver (24.30.58.08), 120 of 120 each; Intel and MoltenVK are unmeasured. - [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 the prebuild thread pool, 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 - 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), 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. +- [The active recorder](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp) (libSceAgcDriver) is one per process, and `Recorder::Activate` switches it without the GPU mutex. With two live devices (tests that open their own device next to the driver's), a device's idle, label and flip stores take only its own pending storage images, and a storage-image write-back records into the active recorder only when that recorder is its device's, otherwise in a submission of its own. Other users of `Recorder::Active()` don't check the device, so they can still record one device's work on the other's command buffer. +- [Label read traps](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver, macOS): at a label, a pending linear or small storage image that shares no page with another image a label stores or one being stored, and is not direct memory mapped twice, gets a [read trap](../../../core/libs/prx/libc/src/GuestWriteWatch.cpp) instead of its store. The first access to its pages stores it whole through the host-read hook; a store of another image over it stores it too, and an eviction or explicit write-back stores it first. Other stores release the trap once their GPU copy is recorded, so a read before that copy lands sees the old bytes; a CPU store releases the traps on its pages first, leaving those images untrapped until the next label. A thread holding `GpuMutex` touching a trapped image waits forever, a system call that reads one without the hook fails with `EFAULT`, and other double mappings go undetected. `APS5_NO_LABEL_READ_TRAP=1` stores at the label. - [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. +- [Tiled images read after a label](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver): a larger tiled image, a write after its one store with no gap, and an image rewritten every 8 flips or fewer can leave stale bytes behind the label. Before a label, a tiled image over 64 KiB is stored only if it is at most 1 MiB and its first write or a write more than 8 flips after the one before armed it, and that takes one store until the next such write. Writes are compared by flip serial, so writes within a flip count as one. The 1 MiB and 8 flip limits are a heuristic from PPSA06866, not measured console behaviour. - [Linear color targets](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) with no host import are drawn synchronously (copied in, drawn, waited for, copied back), where the console renders into memory in stream order, unless the draw can make an import that keeps them write-watched. It makes one for a target in registered, writable memory only when the whole import run is write-watched and the import stays watched: a separate alias of shared memory, a direct import of 1 MiB or more of private memory that settles on Windows (when the import probe keeps the watch), or one the probe keeps watched elsewhere. Smaller private ranges on Windows, runs with memory outside the write watch and devices whose imports are compared stay synchronous: a compared import makes every refresh of the resident image compare the whole target. A target over an existing import is resident, compared or not. `APS5_NO_RESIDENT_LINEAR=1` keeps every linear target synchronous. - [`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 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. +- [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. +- [Units without a baseline outside the write watch](../../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) (libSceAgcDriver) - results that land outside the watch in a unit with a write generation but no saved bytes throw when they are stored, unless the unwatch hook hashed the unit while its range was still watched and its bytes still match that hash. The hook hashes nothing where the CPU writes cannot be proven: a failed collect, and a failed page walk of an import that stays watched, before or after the import. Some ranges leave the watch without the hook: on Linux, `GuestWriteWatchUnregister` called from libkernel when a mapping is aliased, unmapped or replaced. Closes when: every range leaves the watch through the hook with its CPU writes proven. - [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. +- [Driver-store pieces of a 64 KiB block](../../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) (libSceAgcDriver) forget a block's partial driver stores past four per ring: a store or possible write that finds its ring's four pieces taken evicts the oldest, and the block then counts as stored over in every range for any generation older than the evicted piece: `StoredOver` answers true, and for an evicted exact piece `StoredAllSince` answers false as well. A storage image's write-back then leaves a block it shares with other data to the CPU unless the bytes there still equal a copy the image kept (none above 1 MiB over a watched host import), losing the GPU's results in it; an exact eviction in an HTILE block also makes a native multisample depth clear refuse with "no complete tracked HTILE store" (DepthSurface.cpp). Only stores that contain none of the kept pieces fill a ring; keeping the evicted ranges' extent instead of one generation per ring is not implemented. - 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 watch and a settle queue exists: those of 1 MiB or more stay watched, smaller ones return to the watch after their settle unless a storage image overlaps them. Shared memory imported through a separate writable alias keeps protection-based tracking on its guest views. [GuestMemory](../../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) excludes the 4 KiB pages an import overlaps, each with the generation of its latest exclusion; a fresh private mapping restores the complete pages it covers, protection changes and shared remappings do not, nor does retiring an import (unimplemented). Blocks with excluded pages keep their write-watch marks as a hint only (`UntrackedHint`): byte comparisons keep CPU edits, but snapshot reuse lasts one collect epoch and unit shadows are off. - [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 block's other pages keep their state. 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 are read the same way; that read or one on demand returns 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 +- [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. A run list above 64 MiB is bound from the shared 64 MiB chunk imports in one sparse buffer, as a single run above 64 MiB is; one at or below 64 MiB, or one whose runs are not on the sparse block size, is one udmabuf of the list, which the kernel refuses above `size_limit_mb` (64 MiB by default) - [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. AMD's Windows driver (Radeon 860M) has one too: a Quake II port's trace had 0.3 ms presents (max 3.5), no `GpuMutex.wait.*` behind them. A device with a free queue in that family presents on it under its own mutex, after the GPU mutex is released. Closes when: traces on RADV, lavapipe and Intel's Windows driver also show 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, 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. +- An [unnormalized S#](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) follows RDNA2 (gfx1036) [hardware oracle](../HW_ORACLE.md) measurements, not gfx1013: clamp mode 0 (repeat) samples like clamp-to-last-texel, and modes 1 (mirror) and 3 (mirror-once) mirror texel index i < 0 to -i - 1 after the texel offset, then clamp. Modes 0, 1 and 3 bind clamp-to-edge; for 1 and 3 the shader first maps a negative coordinate c to the float just below -c, so an exact negative integer -k reads texel k - 1, not k. Unequal minification and magnification filters sample with the magnification filter (unnormalized coordinates sample at LOD 0). Anisotropy, MC_COORD_TRUNC and clamp modes 4, 5 and 7 throw. Secondary LOD bias (LOD_BIAS_SEC, bits [19:14]) is accepted and ignored, matching RDNA2 measurements across explicit and implicit samples. High on Life (PPSA15388, #4736) uses secondary LOD bias and clamp mode 0 with differing min/mag filters. - [TRUNC_COORD](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestSamplerResource.cpp) (libSceAgcDriver) is not applied, with normalized or unnormalized coordinates: 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. - [Background link-time-optimized relink of library pipelines](../../../core/libs/prx/libSceAgcDriver/Execution/include/PipelineLinkOptimization.hpp) (libSceAgcDriver) is skipped on every GPU under NVIDIA's proprietary Vulkan driver (`VK_DRIVER_ID_NVIDIA_PROPRIETARY`), so those pipelines keep their fast-linked form. The optimized link loses the device when bound on an RTX 3080 (Ampere, #2866) and an RTX 4060 (Ada, #3434); an RTX 5060 and an RTX 5080 (Blackwell) on the same driver do not, so the scope is wider than the evidence. No standard Vulkan query tells the architecture apart, and the driver version that fixes it is unknown. `APS5_NO_PIPELINE_LTO` disables the relink on every driver. - [8_SRGB and 8_8_SRGB textures](../../../core/libs/prx/libSceAgcDriver/Graphics/src/TextureFormat.cpp) (libSceAgcDriver) that the device samples as R8_SRGB or R8G8_SRGB use its own decode, which need not match gfx1035: NVIDIA's R8_SRGB is one bf16 step off in 29 of 256 codes. A device that cannot sample them gets an RGBA8 sRGB copy instead (a blit of the R8 or RG8 texels, refreshed with the storage image's version), whose decode is likewise the device's; 3D textures and storage images that are not single-depth and format-matched are copied from their snapshot instead. @@ -197,18 +218,19 @@ - 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). +- A [label](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp) stored on the GPU is ordered behind the batch's earlier writes, for a host that polls it, only by driver behavior: Vulkan makes device writes visible to the host through a fence or event wait. The store run recorded at `Submit` takes every earlier write (`MEMORY_WRITE`) as its barrier's source access. With only shader, transfer and color attachment writes, Intel's Windows driver (Arc Graphics, 101.9150) let a polling host see the label before an earlier copy's bytes (#10199). A run forced in place (`closeStoreRun(false)`) keeps the narrower `LabelLeadingBarrier`, or the pass end it merges into. On that driver, such a label can still become visible before the writes recorded ahead of it. - [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, 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). `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. +- 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, unless the fill lands before the draw or dispatch reading it is recorded: the worker compares the memory a draw or dispatch captured with guest memory before recording or queuing it, and prepares it again once from current memory when they differ. `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 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. - [11_11_10 float CB resolve](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a CB resolve whose source is a multisampled 11_11_10 float color target is refused. The console's averaging and rounding for that resolve are not measured and no known source describes them. PPSA12361 reaches it: a local diagnostic print showed a two-sample R32_UINT source (packing 11_11_10 float) of 512x512 resolving into one sample, and the title stops there (issue #11362). -- [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). +- [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 selects Vulkan's last-vertex mode per opengnm's `flatshade_first`; rect-list flat inputs come from vertex 0, or 2 in last mode; a geometry program gets strip and fan triangles in an RX 6700 XT's order per mode, and in last mode its primitives take flat inputs and layer from their last index, keeping per-vertex order; a flat input without pass-through read only via `v_interp_mov_f32` P10 (P20) is the provoking vertex's (#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, explicit per-vertex flat inputs, and triangle strips whose pixel shader reads a vertex slot (`v_interp_mov_f32`, or 16-bit interpolation until checked), as odd triangle order varies by device. - [Triangle strips through the barycentric tessellation stages](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) (libSceAgcDriver) - without `geometryShader`, a triangle strip 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. A non-indexed strip ignores the primitive restart bit, which has no index to match; indexed strips with primitive restart and 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). @@ -227,12 +249,13 @@ - [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) of two to five slices are only sampled: the [materializer](../../../core/shader/recompiler/Optimization/src/ResourceMaterializer.cpp) rejects loads, stores and dimension queries through them, which on RDNA2 stop at `DEPTH` while the view holds six faces. Out-of-range cube-array sampling is unimplemented; PS5 equivalence is unmeasured. A range of fewer than six slices samples six faces from `BASE_ARRAY` (measured with `tools/hw-oracle` on gfx1036 for two to five slices, and on RX 6600, gfx1032, for equal `BASE_ARRAY` and `DEPTH`); one of six or more keeps every slice up to `DEPTH`. 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 sampled through such a sampler throws. - [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. +- [Linear 2D texture pitch](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestTextureResource.cpp) (libSceAgcDriver) - the PS5's gfx1013 is not measured, and a `DEPTH` pitch on a block-compressed, mipmapped or base-array linear 2D T# throws, as does any pitch other than the default from a PS4 guest. A single-level uncompressed linear 2D T# uses `DEPTH` + 1 as its row pitch in elements, raised to the width and rounded up to 256 bytes, as `image_load` read it on RDNA2 (gfx1036, AMD Windows driver 32.0.21030.31, [hardware oracle](../HW_ORACLE.md)) in 135 kernels over 8_UINT, 16_UINT, 32_UINT and 32_32_UINT, widths 16 to 300 and pitches of 1 to 2048 elements. - [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. - [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 planes, each watched or in a host import the Linux write-watch probe compares (kept as a copy and compared at most once per collect epoch); 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, except a same-value CPU store to a compared plane. - [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, 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. +- [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; other RenderTarget and Z64KBX contents are uploaded sample by sample through the native color initialization. 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. Non-uniform Standard4KB, Standard64KB and D64KBX memory, other tile modes, 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. @@ -243,7 +266,7 @@ - [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. +- [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 told apart from no store only in a whole 64 KiB block of the image: a CPU stamp newer than the write generation of the layer's last GPU results keeps the CPU's bytes there. In an edge block, whose stamp can come from bytes beside the image, such a store is not told apart from no store and the GPU result wins. A block a write-back kept for the CPU stays the CPU's until its unit is uploaded again: results written into the unit before that are dropped in that block, also after the memory left the write watch (the unit's guest bytes are saved when it is written again 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. - [Queue-idle storage write-back](../../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/QueueWorker.cpp) (libSceAgcDriver) - storage-image results that nothing flushes, waits for or overwrites reach guest memory only after every queue has been idle for 50 ms, where the console's GPU writes them to memory as it runs. The idle store goes 4 MiB at a time and stops as soon as queue 0 has queued work or another queue is running, leaving the rest for the next idle period. A title whose frame gaps pass 50 ms still starts an idle store between two frames, and its next frame waits for the piece in progress: a title's movies at about 25 fps (frame gaps up to 65 ms) show frames of up to about 100 ms there. - [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. - [BT.2100 PQ display buffers](../../../core/libs/prx/libSceAgcDriver/Execution/include/PqToSdr.hpp) (libSceAgcDriver, libSceVideoOut) - pixel formats `0x8100070422000000` and `0x8100070400000000` are taken as 10:10:10:2 texels with BT.2020 primaries and the SMPTE ST 2084 transfer, red or blue in the low bits, as [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/VideoOut/VideoOutTypes.cs) names them (`Rgb10A2Bt2100Pq`, `Bgr10A2Bt2100Pq`) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/59183dbfa81ed7efdff6069177fed50de0552725/src/graphics/host_gpu/renderer/image/imageInfo.h) maps the first; PPSA01317 registers display buffers of the first when its output reports HDR. With HDR output they are presented unchanged through an HDR10 swapchain (`VK_COLOR_SPACE_HDR10_ST2084_EXT`, 10-bit, with `VK_EXT_hdr_metadata`); otherwise they are converted to 8-bit sRGB: ST 2084 EOTF, BT.2020 to BT.709 primaries with negative components clipped, a luminance tone map and the sRGB encoding. The tone map is ours: min(203 nits, white level) is SDR white (the BT.2408 HDR reference white), luminance passes unchanged up to 0.75 of it and an extended Reinhard shoulder above reaches 1.0 at the white level, the peak sceSystemServiceGetHdrToneMapLuminance last reported to the title (or the current one when it never asked), since titles read it once. How a console shows such a buffer on an SDR display is unknown. Alpha is opaque. @@ -255,7 +278,7 @@ - [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. +- [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; shadPS4 (PS4) and KytyPS5 (PS5, naming this value 5_5_5_1) swap red and blue too. Tests cover the format, mapping and write mask; 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 swap is accepted. Constant blend colors use the `0xc6` swap handling, untested for this format. PPSA06699 renders into this format. - [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 partial writes with another merged target](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a partial write of a 4_4_4_4 UNORM target throws when another color target of the draw also merges in the pixel shader (a second partial write or an unclamped normalized blend), and so does any draw whose merged color targets share memory. - [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. @@ -300,9 +323,11 @@ - [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 current or the previous frame (a frame ends at a flip packet or a CPU flip), so a set used in every frame keeps its imports and what does not fit is copied, as below the budget; until two frames have ended imports age by lookups, an import being evictable 256 lookups after its last use; after that, without flips (a loading stretch, compute-only work) no frame ends and nothing more becomes evictable, so imports that do not fit are copied instead of evicting by recency; 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`; that eviction also takes imports the recency rule protects whose single range is releasable and write-watched, so a heap mirror can serve it, as long as the build has not taken them yet. 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, unless the build can import its range first, evicting for it as the copy cap does, with the imports of earlier builds no longer held; 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: no frame has ended (or, before two frames have ended, 256 lookups have not passed), and no build holding an import two frames old has raised its floor. 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 two-frame protection, the 2 GiB reserve, the eighth, the 64 MiB steps and the three failures are choices, not measured. +- [Heap mirror reference storage](../../../core/libs/prx/libSceAgcDriver/Graphics/src/ReferenceStorage.cpp) (libSceAgcDriver): the 8-buffer/2 GiB idle cap and 2 s reuse window are unmeasured choices. Idle release is polled by a take, an eligible return, a heap mirror sweep or a present; without another poll, expired buffers remain until process shutdown. A title taking references less often than every 2 s reallocates them. The cap covers idle storage only, not buffers still held by active references. +- [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 half that heap, kept between 2 and 4 GiB (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, and on a 32 GiB machine a reserve of half its 16 GiB heap left imports 8 GiB, less than the 10466 MiB one title holds at once). 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 current or the previous frame (a frame ends at a flip packet or a CPU flip), so a set used in every frame keeps its imports and what does not fit is copied, as below the budget; until two frames have ended imports age by lookups, an import being evictable 256 lookups after its last use; after that, without flips (a loading stretch, compute-only work) no frame ends and nothing more becomes evictable, so imports that do not fit are copied instead of evicting by recency; 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`; that eviction also takes imports the recency rule protects whose single range is releasable and write-watched, so a heap mirror can serve it, as long as the build has not taken them yet. 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, unless the build can import its range first, evicting for it as the copy cap does, with the imports of earlier builds no longer held; 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: no frame has ended (or, before two frames have ended, 256 lookups have not passed), and no build holding an import two frames old has raised its floor. 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 two-frame 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. +- [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 after it. The copy-back stores only the bytes the shader changed and the import still holds at their copy-in value, so a CPU store in between survives; where both changed the same byte, the CPU's byte is kept, whichever the console would store last. The whole range is still copied back, rolling CPU stores back, at the unaligned ends of a range, for an element or import offset that is not 4-byte aligned, without a shadow copy or buffer device addresses, with `APS5_NO_CHANGED_COPY_BACK`, and when a synchronous draw stores a GPU-copied region from the CPU (no copy-back recorded, no copy-in snapshot). A CPU store made while the copy-back runs can still be overwritten by it. +- [Write-back conflicts with CPU stores](../../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) (libSceAgcDriver) - a buffer region the CPU copies for a dispatch is stored back with `WriteChangedKeepingGuest`: a byte takes the shader's value only where the shader changed it and guest memory still holds the uploaded value, so a CPU store made after the upload wins where both changed the same byte. A CPU store that restores the uploaded value is indistinguishable from no store and takes the shader's byte. Writable image mirrors, heap mirrors, storage image write-backs and tiled colour targets use `WriteChanged`, which stores every byte the GPU changed: a CPU store into the same byte between the upload and the write-back is overwritten there, as before; a CPU store into a byte the GPU left alone survives on every path. Which side the console would store last is not measured. - [Committed reads of released pages](../../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) (libSceAgcDriver) - `ReadCommitted` and `EqualsCommitted` find the readable pages of a range first and read them afterwards. On Windows they read through `ReadProcessMemory`, so a page released in between reads as zero and is left out of the comparison instead of faulting: Demon's Souls (PPSA01342) samples a texture at 0x38400 of 352 MiB while it loads the tutorial level, a range that holds no guest allocation and on Windows covers host heap pages other threads release, which stopped it with an access violation. A texture outside every registered guest allocation still takes the host bytes found there, as it did before; what the console returns for it is not measured. On Linux the pages are copied directly, so a page released between the check and the copy still faults. - [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. @@ -320,18 +345,26 @@ - [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 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. +- [1D arrays past maxImageArrayLayers](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) - a 1D array view of more than `maxImageArrayLayers` slices, sampled only by `image_sample_lz` or `image_sample_l` without depth comparison or a 128-bit T#, is a 2D image holding each viewed slice in two adjacent rows; a view of exactly the limit stays a 1D array. A shader is prepared with this mode only once such a view reaches it. The layer is rounded half to even and clamped to the view, as [Vulkan's array layer selection](https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#textures-layer-selection) does; RDNA's rounding of a halfway layer is unmeasured; a NaN layer reads slice 0. Views of other mip levels, block-compressed or RGBA8-expanded formats, native depth surfaces, other uses of the view and views needing more than `maxImageDimension2D` rows throw, as do `ShaderReplay` requests (no limit recorded). PPSA08329 samples 5,120 slices. - [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. +- [dma-buf imports](../../../core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp) (libSceAgcDriver) - the udmabuf of a guest range lists the runs of the direct memory backing file behind it (`SharedBackingRuns`, one per physical allocation the range was mapped from, up to udmabuf's 1024), where it took the first run's offset for the whole range. The start-up probe also stores to the import from the GPU after CPU stores to the same pages and counts pages the CPU still reads stale: i915 maps a dma-buf without snooping the CPU caches (4 of 4 pages stale on Meteor Lake with Mesa 26.2.3 and main), so such a device does not fall back to dma-buf imports and copies instead. The memory type the import reports is host-coherent for the driver's own mappings; the guest's mapping of the same pages is what goes stale. - [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 - [Depth view mip on a single-level surface](../../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - a `DB_DEPTH_VIEW.MIPID` above zero on a surface whose `DB_Z_INFO.MAXMIP` is zero selects the first level. In Flashback (PPSA08546) the depth view follows a mipmapped colour target from mip 1 to 6 over six slices of a 128x128 Z_X depth array; the depth allocation is six 64 KiB slices (the next allocation starts 0x61000 bytes later, too small for a mip tail) and every view uses the same base and extent, so the field has no other level to select. The console was not measured. A depth copy from such a view still throws - [Long Windows pipeline-cache paths](../../../core/libs/prx/libSceAgcDriver/Graphics/src/PipelineCache.cpp) (libSceAgcDriver) - whether retail console filesystem APIs normalize non-canonical or relative cache paths beyond MAX_PATH without truncation remains unmeasured against console diagnostics. On Windows, filesystem journal normalization and shared content readers do not cover non-canonical or relative pipeline cache paths beyond MAX_PATH. -- [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 +- [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 (`vk_rasterizer.cpp`, `StencilValid`) and KytyPS5 (PS5, `depthRenderTarget.cpp`, `has_stencil`) drop it the same way; fpPS4 sets the clear from the bit alone, but its host depth format then has no stencil aspect to clear. 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. - [Adaptive staging](../../../core/libs/prx/libSceAgcDriver/Graphics/include/AdaptiveStaging.hpp) (libSceAgcDriver): whether a staged element pays for its two copies is decided by probing the frame GPU time, not from the element's accesses. The probe is measured on one title (PPSA03912, NVIDIA on Linux). It judges by the whole frame's GPU time, so a scene whose GPU load moves by itself can keep the slower mode until the next probe, 20 to 160 s later, and each probe runs a few frames in the slower mode. - [WriteChanged](../../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) (AGC driver) - a partly changed 16-byte write-back chunk is stored with one byte store per changed byte when at most `ChangedByteStoreLimit()` bytes changed, and with `MASKMOVDQU` otherwise. The limit is 16 (byte stores at every density) on a CPU whose vendor string is `AuthenticAMD` and 4 on any other. The cost of `MASKMOVDQU` is measured only on a Ryzen 9 7950X, where byte stores win up to about 10 changed bytes in a microbenchmark but PPSA16064 lost its gain when `MASKMOVDQU` was kept from 10 changed bytes on, and on a Core i9-14900KF, where byte stores win only at 3 or 4 changed bytes and are up to 2.6 times slower for dense chunks (#8930). Other AMD and Intel generations and other vendors are not measured. - [Host report](../../../core/libs/prx/libSceAgcDriver/Execution/src/HostReportVulkan.cpp) (libSceAgcDriver, `ANYPS5_HOST_REPORT=1`): run on Linux with a software Vulkan device only. Its Windows branches (`RtlGetVersion`, `GlobalMemoryStatusEx`, `USERNAME`, `COMPUTERNAME`, `USERPROFILE`) and its macOS branches are not run, and no hardware GPU's output is checked. The list of required Vulkan items in `HostReportVulkan.cpp` mirrors the checks of `VulkanDevice.cpp` by hand: a new hard requirement added there must be added to the list, or the start fails with the older single-item message. Closes when: the report is read on Windows, macOS and a hardware GPU. +- [Compute scratch pool size](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) - each compute program that uses scratch gets one device buffer of per-workgroup slots, capped at `APS5_SCRATCH_POOL_MIB` (default 64 MiB, never below one slot) and sized from the group count of the first dispatch that needs it (an indirect dispatch asks for the cap); a later dispatch with more groups replaces it with a larger one, up to the cap. The cap is not derived from the device's resident workgroups, so workgroups beyond the slots spin on the slot locks and serialize. One pool is also held under 2 GiB: on an RTX 4090 (driver 617.42) a pool of 2049 MiB returned wrong scratch values while 2047 MiB was correct, though `maxStorageBufferRange` allows more; whether other drivers share that limit is unmeasured. A single slot over that limit or `maxStorageBufferRange` throws at the first dispatch. +- [Compute scratch pool budget](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) - all scratch pools together are held under `APS5_SCRATCH_POOL_TOTAL_MIB` (default 256 MiB): a new or enlarged pool drops the least recently used ones first, and a dropped pool lives until the batches that use it complete. The default is a guess, not derived from the device's memory or from a title's working set. A title whose scratch programs need more than the budget at once recreates a pool, and zeroes its header with a queue round trip, each time one comes back. +- [Compute scratch pool header clear](../../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) - a new scratch pool's slot-lock header is zeroed by its own one-off command batch, submitted and waited on inside the dispatch record path before the dispatch's batch opens (it also submits any batch recorded so far). Every new or enlarged pool therefore stalls the dispatching thread for one queue round trip; it was measured only in the focused tests, not on the game traces. A pool buffer is never zeroed again, so a shader that leaves a slot lock set (a device loss mid-dispatch) is not repaired. +- [Pixel interlock on a device without atomic buffer device addresses](../../../core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp) (libSceAgcDriver) - `agc_driver_pixel_interlock` skips where `BdaAtomicsUsable` is false, because it is the only test that counts overlapping updates to one address and that count cannot be right without the guarantee. On MoltenVK it also fails before it reaches the count, at `vkCreateGraphicsPipelines` with `Vulkan result -3`: MoltenVK's own SPIRV-Cross refuses `raster_order_group` in an argument buffer, so `RasterOrderGroupUsable` covers that failure first and the skip names it. With the argument buffers off (`MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=0`) the pipeline compiles and the plain draws pass, and only then does the missing atomic show as lost updates. Neither is fixed here: a descriptor-bound atomic would change the BDA table ABI, whose ranges carry a flat device address + +- [Draw](../../../core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp) partitions layered mesh color draws into attachment windows within the host `maxMeshOutputLayers` limit, remaps output layers and reconstructs the guest layer for fragment ancillary input. Replay requires shaders without guest memory writes, clocks, GDS access or unproven side effects; those draws still throw until mesh output can be captured once and rasterized separately. Layered depth rendering remains unsupported. Vulkan layer-limit semantics follow [Layer](https://docs.vulkan.org/refpages/latest/refpages/source/Layer.html); PS5 behavior outside Vulkan’s defined layer range is unmeasured. Layered 2D array views remain unsupported by the existing color-view decoder. ### Opt-in draw paths diff --git a/docs/dev/debt/libSceAjmi.md b/docs/dev/debt/libSceAjmi.md index 3d21dfd34d..4d7d648aa4 100644 --- a/docs/dev/debt/libSceAjmi.md +++ b/docs/dev/debt/libSceAjmi.md @@ -1,5 +1,34 @@ # libSceAjmi technical debt +### Additional callable exports + +- [Additional callable exports](../../../core/libs/prx/libSceAjmi/Export.cpp) - 22 entries present in both version-labelled images are import-resolution declarations only. None is listed in the pinned public 3.20 catalogue for this library. Neither label independently authenticates retail firmware provenance. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Data exports require separate layout and ownership analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +| Name | NID | +| --- | --- | +| `sceAjmiBatchCancel` | `6lWAGxD8NLs` | +| `sceAjmiBatchWait` | `JJ9g04D344Q` | +| `Unknown` | `4MsUK9kAnKg` | +| `Unknown` | `6fY-ci3au2U` | +| `Unknown` | `9zA4YMlkR9M` | +| `Unknown` | `BD002VBfhuE` | +| `Unknown` | `EYIibEpcHuw` | +| `Unknown` | `J3UwQ5e6sNU` | +| `Unknown` | `LESEGgm133w` | +| `Unknown` | `MTdd8ApwO50` | +| `Unknown` | `QhA7rTjJbro` | +| `Unknown` | `RbLGSlJB6dc` | +| `Unknown` | `Uqtj4RSP-JQ` | +| `Unknown` | `X8sT-xi-dVE` | +| `Unknown` | `YtubBgSeNH0` | +| `Unknown` | `cWZnCK7na5Y` | +| `Unknown` | `dTsNpEt1AqQ` | +| `Unknown` | `dmwcwCeItmE` | +| `Unknown` | `m0xisYC6U2w` | +| `Unknown` | `ofRX8Vq0ss0` | +| `Unknown` | `pfPwiigOE3A` | +| `Unknown` | `r7NMFYcJi08` | + ### Unknown function info - [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, as `sceAjmBatchJobInlineBuffer_nid_disambig1` and `sceAjmBatchJobRunBufferRa_nid_disambig1`, which the NID patcher exports under the names' NIDs, because libSceAjm implements the same names for PS4 titles. Both are firmware 13.42's routines, which write the same bytes as libSceAjm's in an emulator ([sceAjmBatchJobInlineBuffer](https://github.com/boykopovar/AnyPS5/pull/11238#issuecomment-6418132093), [sceAjmBatchJobRunBufferRa](https://github.com/boykopovar/AnyPS5/pull/11238#issuecomment-6442306780)); neither was run on a console. 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 0c9bbb353d..c47843542d 100644 --- a/docs/dev/debt/libSceAmpr.md +++ b/docs/dev/debt/libSceAmpr.md @@ -8,6 +8,7 @@ - [sceAmprAmmSetPageTablePoolOccupancyNotificationThreshold](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - throws as unimplemented. The `(int threshold)` signature follows [drakmor/ampr_emu](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/src/sceampr_exports.cpp#L336-L337), where the export passes the threshold to `sceKernelMapperSetPageTablePoolOccupancyNotificationThreshold(int)`; not measured on a console. The thresholds the kernel rejects, their error codes and how the notification is delivered are unknown, and no title is known to call it (Planet Zoo, PPSA18026, imports it). - [AMPR command buffer null arguments](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the result for a null command buffer is unknown for `sceAmprCommandBufferConstructor`, `sceAmprAprCommandBufferConstructor`, `sceAmprCommandBufferReset`, `sceAmprCommandBufferSetBuffer` and `sceAmprCommandBufferGetSize`; the first four return `SCE_KERNEL_ERROR_EINVAL` here, and `sceAmprCommandBufferGetSize` chooses 0, but none of the null behaviour has been confirmed on a PS5. +- [sceAmprMeasureCommandSizeReadFile](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - takes no argument and returns the size of the host command record every time, so it never fails; the commit that added it names no measurement. KytyPS5 (PS5, `src/libs/libAmpr.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99), the only one of the three emulators with libSceAmpr (shadPS4 and fpPS4 only list the names), takes four arguments and returns `KERNEL_ERROR_EINVAL` when the destination range or the file offset is out of range, and otherwise 0x14, or 0x18 when the offset needs more than 32 bits. `sceAmprMeasureCommandSizeReadFileGather`, `sceAmprMeasureCommandSizeReadFileScatter` and `sceAmprMeasureCommandSizeReadFileGatherScatter` take arguments here and refuse invalid ones. Which signature is right is not measured. - [AMPR WriteAddressFromCounterPairOnCompletion](../../../core/libs/prx/libSceAmpr/Export.cpp) (libSceAmpr) - the pair of an even counter n is assumed to give counter n in the low 32 bits and n + 1 in the high 32 bits. Wait compare values, wait flush and counter index ranges follow the [ampr_emu](https://github.com/drakmor/ampr_emu) reimplementation; the flush is accepted and has no effect, as commands run in order - [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) - non-`Nop` commands use host encoding with an 8-byte header, exceeding console sizes. On overflow, the buffer expands up to a 32 MiB cap on the guest heap, relocating `base` and `size`; below this cap a full buffer avoids `EBUSY`, placing commands in host memory not allocated by the guest, and AMM submits resolve original pointers to expanded memory. Rebinding the same memory via `SetBuffer` frees prior expansions and resets that buffer's offset and commands to 0. Beyond the cap, appends return `EBUSY`. `Nop(n)` is n dwords and `NopWithData(n)` (n + 1) dwords with header `0x5452000F | payloadDwords << 8 | (type & 0xF) << 12`, and `ConstructNop` writes its word before payload and zeroes padding (FW 13.42, #10936); null `base` reports before count or range. `NopWithData` and `ConstructNop` stop at the record without trailing zeros. The at-start flag of `_04_00` writes is ignored; commands run in order. diff --git a/docs/dev/debt/libSceAppContent.md b/docs/dev/debt/libSceAppContent.md index 9ea6e297ba..18f5fc7272 100644 --- a/docs/dev/debt/libSceAppContent.md +++ b/docs/dev/debt/libSceAppContent.md @@ -11,7 +11,8 @@ - [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 - +- [sceAppContentDownload2Expand, sceAppContentDownload2Shrink](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - callable exports imported by Call of Duty: Modern Warfare 3 (PPSA24263), declared in [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/sceappcontent_exports.cpp#L42-L43) as taking one `const void* downloadHandle`. Their signatures and behaviour on hardware are unverified; all calls throw through `NotImplemented_nid_no_patch`. +- [sceAppContentUnknown_SWVxsi_ZBlw](../../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent, NID SWVxsi-ZBlw) - master data identifier query (UnknownMdid in [dlc_emu](https://github.com/drakmor/dlc_emu/blob/fcf69b16d46a9850578bce19dc2c1b4dc867cbb5/src/dlc_modules/dlc_content.cpp#L2258-L2262)). Reconstructed signature and return values on hardware are unverified; all calls throw through `NotImplemented_nid_no_patch`. - [libSceAppContent pending exports](../../../core/libs/prx/libSceAppContent/Export.cpp) - 8 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 diff --git a/docs/dev/debt/libSceAudioIn.md b/docs/dev/debt/libSceAudioIn.md index 1dd766532f..4e2432f8ba 100644 --- a/docs/dev/debt/libSceAudioIn.md +++ b/docs/dev/debt/libSceAudioIn.md @@ -3,6 +3,7 @@ ### Silent stubs - [sceAudioInInit](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - returns 0 without library-wide setup, as `sceAudioOutInit` does; each `sceAudioInOpen` opens its own SDL capture device +- [sceAudioInSetCompressorPreGain, sceAudioInSetNoiseGateThreshold](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signatures; the PS5 13.60 module returns 0 without reading any argument, and so do these stubs ### Unknown function info @@ -20,8 +21,6 @@ - [sceAudioInChangeAppModuleState](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature; the export throws through `NotImplemented_nid_no_patch`, and this parameterless declaration is for import resolution only. The closest signed sibling, `sceMbusChangeAppModuleState` (`std::uint32_t deviceClass`, `std::int32_t moduleId`, `std::uint32_t state`), takes three arguments, so this shape is probably not the real one. What the console notifies and whether a title calls it is unmeasured - [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; the PS5 13.60 module returns 0 without reading any argument, and so does this one -- [sceAudioInSetNoiseGateThreshold](../../../core/libs/prx/libSceAudioIn/Export.cpp) (libSceAudioIn) - unknown signature; the PS5 13.60 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 diff --git a/docs/dev/debt/libSceAudioOut.md b/docs/dev/debt/libSceAudioOut.md index 1761fe08e4..4b0b0fe741 100644 --- a/docs/dev/debt/libSceAudioOut.md +++ b/docs/dev/debt/libSceAudioOut.md @@ -78,15 +78,17 @@ - [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 13.60 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. 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) - 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 +- [sceAudioOutSetMixLevelPadSpk](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - no emulator applies the level: [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0614ce324be092791b758d79f93d9f1ca5b10a81/src/core/libraries/audio/audioout.cpp#L769-L808) (PS4) checks and stores it, [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/audio/ps4_libsceaudioout.pas#L474-L488) (PS4) ignores it and KytyPS5 (PS5) has no such function anywhere in its repository; 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. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0614ce324be092791b758d79f93d9f1ca5b10a81/src/core/libraries/audio/audioout.cpp#L183-L187) (PS4) stamps it after its output thread hands the block on and [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/audio/ps4_libsceaudioout.pas#L695-L804) (PS4) after its blocking device write; AnyPS5 once it has queued the block to a host device or paced a port without one, which can be earlier than both; KytyPS5 (PS5) has no such function -- [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 +- [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. [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/5e837a9beac82c031868eaad9348d6a48245fb99/src/libs/audio.cpp#L961-L971) (PS5), shadPS4 and AnyPS5 zero the whole state; [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/audio/ps4_libsceaudioout.pas#L432-L439) (PS4) writes only the loudness (1) and checks the pointer before initialization - [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. One title (PPSA03355, in its libfmod plugin) disassembled for this entry has three call sites (0xe5faf, 0xe64a3, 0xe6a3b): type 0 with format 0x800 at 48000 Hz and flags 0; type 0x100 with format 0x100 at 48000 Hz and flags 0, after which it compares the result with 0x80268002; and a third that was not read. The first format is within the MAIN ranges above; for type 0x100 the validation is unverified, and what the type means is not shown. - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - 12 channels are assumed to be L R C LFE Ls Rs Lb Rb Ltf Rtf Ltb Rtb as in KytyPS5's 12-to-8 fold, and 6 and 8 channels to be its first 6 and 8; no title or SDK header confirms the order - [sceAudioOut2PortCreate](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - the stereo fold is assumed, not the console's downmix: C at -3 dB into both sides, Ls/Rs, Lb/Rb and Ltf/Rtf at -3 dB and Ltb/Rtb at -6 dB into their own side, LFE dropped - [sceAudioOut2PortSetAttributes](../../../core/libs/prx/libSceAudioOut/src/AudioOut2Port.cpp) (libSceAudioOut) - the console behaviour for several buffer sets (attribute 0) per push and for a push without a new buffer is not documented; this is inferred from titles. Buffers set between pushes are queued and played one per grain, at most 16. A grain without a new buffer repeats the last one only for a port given one buffer once since it was created or cleared with a null buffer (rewritten in place); otherwise it plays silence: PPSA12544 moves through a ring, and PPSA02433 gives each Wwise object port the same buffer every grain but none for ~350 ms while loading, when that memory is reused. Each set also copies the grain; once another port of the context is given the same buffer, the queued grains and the repeat play that copy (PPSA08587 hands one buffer to three ports in turn). Other buffers are read when the grain is pushed - [sceAudioOutOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - format error `0x80260007` (`SCE_AUDIO_OUT_ERROR_INVALID_FORMAT`) follows PS4 comparator [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/0614ce324be092791b758d79f93d9f1ca5b10a81/src/core/libraries/audio/audioout.cpp#L283-L287) (`audioout_error.h`), which rejects formats outside `S16Mono` through `Float_8CH_Std`; console behaviour for a format low byte above 7 and for higher bits is unmeasured. Output buffer checks validate total byte count against `INT32_MAX` on virtual ports, and an oversized vibration pad buffer throws. +- [sceAudioOutOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - a length of 0 returns `SCE_AUDIO_OUT_ERROR_INVALID_SIZE` (0x80260006) and a frequency of 0 returns `SCE_AUDIO_OUT_ERROR_INVALID_SAMPLE_FREQ` (0x80260008); every other length and frequency is accepted, which is not measured on hardware. shadPS4 (PS4, `src/core/libraries/audio/audioout.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) and fpPS4 (PS4, `src/audio/ps4_libsceaudioout.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) return those two errors for any length outside 256 to 2048 in steps of 256 and any frequency but 48000. KytyPS5 (PS5, `src/libs/audio.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) rejects a frequency of 0, as this library does, and checks no length, where this library rejects 0. +- [sceAudioOutOpen](../../../core/libs/prx/libSceAudioOut/src/AudioOut.cpp) (libSceAudioOut) - an `index` (the third argument) other than 0 throws, after the port type, length and frequency checks; the console's answer for such an index is not measured. fpPS4 (PS4, `src/audio/ps4_libsceaudioout.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) documents the index as unused with 0 to be specified, and stores it; shadPS4 (PS4, `src/core/libraries/audio/audioout.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) logs an error for it and goes on opening the port; KytyPS5 (PS5, `src/libs/audio.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) stops, as this library does. Every direct call in the titles checked passes the constant 0 for it, except one that is not shown to be a constant; no call through a tail jump was found. - [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) - 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. diff --git a/docs/dev/debt/libSceAudioSystem.md b/docs/dev/debt/libSceAudioSystem.md index b572e26258..3b704b2db0 100644 --- a/docs/dev/debt/libSceAudioSystem.md +++ b/docs/dev/debt/libSceAudioSystem.md @@ -4,3 +4,42 @@ - [sceAudioSystemInit, sceAudioSystemProcessAdd, sceAudioSystemProcessRemove, sceAudioSystemProcessResume, sceAudioSystemProcessSuspend, sceAudioSystemTerminate](../../../core/libs/prx/libSceAudioSystem/Export.cpp) have unknown signatures and behaviour. The parameterless `int` declarations are import-resolution placeholders, not confirmed ABI signatures, and throw through `NotImplemented_nid_no_patch`. Only the names and NIDs are established by the [13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libsceaudiosystem-bfe74a18cc.jsonl). - [sceAudioSystemSystemResume and sceAudioSystemSystemSuspend](../../../core/libs/prx/libSceAudioSystem/Export.cpp) (libSceAudioSystem) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAudioSystem.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 + +### Additional callable exports + +- [Additional callable exports](../../../core/libs/prx/libSceAudioSystem/Export.cpp) - 30 entries present in both version-labelled images are import-resolution declarations only. None is listed in the pinned public 3.20 catalogue for this library. Neither label independently authenticates retail firmware provenance. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +[Version-pinned selected ELF export records](https://gist.github.com/kyon-eth/3fbb1a4a6f95a936b4cd58e9c3d8c50c/5e3af709224ed8235af11337309af0725e5685cb) list every NID below with its image label, decoded export module and library, raw qualified symbol, ELF function type, value and size for each inspected version-labelled image. The extract SHA-256 is `02ab27f12bcd0cc9fa03fa33f55fc201110f0d54a86bbbaa093d55d0a8cf262d`; its full-module sorted-function-NID digests and raw module-parameter words permit independent comparison. This is an analyst-produced metadata record, not independent authentication of the original images or proof of prototypes or behavior. + +| Name | NID | +| --- | --- | +| `sceAudioOutServerBusGetIoParam` | `jhrJaWSScXs` | +| `sceAudioOutServerGetOption` | `D95bo7YrrGM` | +| `Unknown` | `-e5aJEqL34Y` | +| `Unknown` | `17nOsxccH5o` | +| `Unknown` | `2bKJUeywpeY` | +| `Unknown` | `3HOelmA2vcw` | +| `Unknown` | `5SM0uQwcgTQ` | +| `Unknown` | `5oOYYvwIb0k` | +| `Unknown` | `6PGbkl6OnvE` | +| `Unknown` | `9QaKJdd7jKk` | +| `Unknown` | `B-JyTVr90kc` | +| `Unknown` | `Fd9u7-OKW+Q` | +| `Unknown` | `FkFcs7NTFDo` | +| `Unknown` | `GUtNdBeMrcM` | +| `Unknown` | `HxLyu3KUv5U` | +| `Unknown` | `IiO+DdVz-4E` | +| `Unknown` | `J2Fx9JLKcqU` | +| `Unknown` | `Met2nIMg+Ng` | +| `Unknown` | `WRDd2feDYzg` | +| `Unknown` | `c4m3OSO0lj8` | +| `Unknown` | `ds20PmJfSSk` | +| `Unknown` | `hjlhfF8Voz0` | +| `Unknown` | `jLjHQbmK9+A` | +| `Unknown` | `jZPcy9MG4w0` | +| `Unknown` | `lSatNsWSmps` | +| `Unknown` | `ltR0PMUM270` | +| `Unknown` | `mS9gpe47Eio` | +| `Unknown` | `tHodLaUv04w` | +| `Unknown` | `uoPBJTVWhrg` | +| `Unknown` | `xG7GUeOKtBM` | diff --git a/docs/dev/debt/libSceAudiodec.md b/docs/dev/debt/libSceAudiodec.md index b5f8b83ee0..b166062918 100644 --- a/docs/dev/debt/libSceAudiodec.md +++ b/docs/dev/debt/libSceAudiodec.md @@ -2,11 +2,12 @@ ### 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, named by their range errors (`0x807F0305` `SCE_AUDIODEC_ERROR_M4AAC_INVALID_ENABLE_NONDELAY_OUTPUT`, `0x807F0306` `SCE_AUDIODEC_ERROR_M4AAC_INVALID_SURROUND_CHANNEL_INTERLEAVE_ORDER`, in fpPS4's `ps4_libsceaudiodec.pas`). Non-delay output 1 decodes every access unit to its own PCM from the first one on, as shadPS4's AJM AAC decoder does with `EnableNondelayOutput`; without that flag shadPS4 holds back the first 2 frames. Non-delay output 0 still decodes the same way, with no frame held back: no measurement shows whether the console delays it. Surround channel interleave orders 1 and 2 throw: shadPS4 names two AJM orders (`ExtlExtrLsRs`, `LsRsExtlExtr`) but uses neither, and no source says which value selects which order or which order 0 is. +- [M4AAC extended parameters](../../../core/libs/prx/libSceAudiodec/Export.cpp): 28-byte and 32-byte parameter variants add fields at offsets 24 and 28, named by their range errors (`0x807F0305` `SCE_AUDIODEC_ERROR_M4AAC_INVALID_ENABLE_NONDELAY_OUTPUT`, `0x807F0306` `SCE_AUDIODEC_ERROR_M4AAC_INVALID_SURROUND_CHANNEL_INTERLEAVE_ORDER`, in fpPS4's `ps4_libsceaudiodec.pas`). Non-delay output 1 decodes every access unit to its own PCM from the first one on, as shadPS4's AJM AAC decoder does with `EnableNondelayOutput`; without that flag shadPS4 holds back the first 2 frames. Non-delay output 0 still decodes the same way, with no frame held back: no measurement shows whether the console delays it. Interleave orders 1 and 2 leave mono and stereo output unchanged, having no surround channels to place (PPSA21607 decodes stereo with order 2); with more channels they throw: no source says which value selects shadPS4's `ExtlExtrLsRs` or `LsRsExtlExtr` AJM order, or which order 0 is. - [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) +- [sceAudiodecDecode](../../../core/libs/prx/libSceAudiodec/src/Codecs.cpp) (libSceAudiodec) - the console's answer to an M4AAC layout its decoder cannot use is not measured. An ADTS header whose layout FFmpeg cannot open (for example the reserved sampling index 13) is answered with `M4AAC_RESULT_DECODE_ERROR` (-4) and `ERROR_API_FAIL` with sizes 0, as an access unit FFmpeg cannot decode is. The decoder then has no FFmpeg context and answers the same for every access unit until a header names a layout that opens; the raw layout search skips a layout FFmpeg refuses ### Functional diff --git a/docs/dev/debt/libSceAudiodecCpu.md b/docs/dev/debt/libSceAudiodecCpu.md index d249001e8d..3369a41ff2 100644 --- a/docs/dev/debt/libSceAudiodecCpu.md +++ b/docs/dev/debt/libSceAudiodecCpu.md @@ -2,4 +2,4 @@ ### Unknown function info -- [libSceAudiodecCpu pending exports](../../../core/libs/prx/libSceAudiodecCpu/Export.cpp) - 8 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAudiodecCpu.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`. +- [libSceAudiodecCpu codecs](../../../core/libs/prx/libSceAudiodecCpu/Export.cpp) - Argument checks and LPCM decode follow firmware 13.60 disassembly, unverified on console. Only 16- and 24-bit Blu-ray stereo LPCM decodes. These throw through `NotImplemented_nid_no_patch`: non-LPCM codecs (separate modules), DVD LPCM, non-stereo layouts (channel tables not recovered), frames larger than the 0x43800-byte input buffer, frames whose decoded PCM exceeds 0x43800 bytes (firmware 13.42 wraps output at 276,480 bytes, repeating earlier samples), an AU shorter than its frame or a PCM buffer smaller than the output (the module copies without checking), and `InitDecoder` without AU or PCM. Header byte 7 (frame count) and 33-bit timestamp continuation in the param are inferred. The type mask when `sceKernelIsCronos` returns non-zero is not modelled. diff --git a/docs/dev/debt/libSceAvPlayer.md b/docs/dev/debt/libSceAvPlayer.md index 312d8c076f..8efa46c8b9 100644 --- a/docs/dev/debt/libSceAvPlayer.md +++ b/docs/dev/debt/libSceAvPlayer.md @@ -16,8 +16,13 @@ - [Paused initial video](../../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) keeps the first picture available when audio has already established the playback epoch before the caller pauses. Further pictures of that epoch stay blocked, as after a paused jump. The console's exact initial preparation ordering has not been measured. +- [AvPlayer frame aspect ratio](../../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) - the `aspect_ratio` of the stream info is the stream's sample aspect ratio, and that of a video frame is the picture's sample aspect ratio, falling back to the stream's; both are 0.0 when no ratio is given. This follows [BunnyFl's reading of firmware 13.42](https://github.com/boykopovar/AnyPS5/pull/9937#issuecomment-6396994223) (emulator execution and code reading, H.264 only, no console run). AnyPS5 reads the ratio through FFmpeg, which does not tell an `aspect_ratio_idc` of 0 (1.0 on the console) from missing aspect information, nor apply the older mapping for titles built with an SDK below 6.00; HEVC and VP9 are unchecked. A title that sizes its video quad from the frame value drew it (9/16)^2 too small when it was width / height. + ### Functional +- [HTTP file sources](../../../core/libs/prx/libSceAvPlayer/src/HttpStream.cpp) remain unverified on PS5, and HLS remains unimplemented. Remote MP4/WebM inputs follow [SharpProspero's source types](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Media/AvPlayer.cs#L187-L205); host HTTP byte ranges and redirects follow [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-range), framing [RFC 9112](https://www.rfc-editor.org/rfc/rfc9112.html#name-chunked-transfer-coding). Unsupported URL forms, TLS, encodings, proxy, cookie, authentication and console HTTP-context integration remain gaps in the [shared libSceHttp host client](libSceHttp.md#functional). +- [HTTP source lifecycle](../../../core/libs/prx/libSceAvPlayer/src/HttpStream.cpp) still has no synchronous source-open deadline and cannot interrupt host DNS. Source opening waits for the shared client's initial response and known size, including full buffering of chunked, connection-close or ignored-range 200 responses. Stop cancels socket polling; failed reads use FFmpeg's existing demux-error path. Console timeout and network-error mapping remain unverified; transport validator and buffering limitations are listed in [libSceHttp](libSceHttp.md#functional). + - [sceAvPlayerGetStreamInfo / sceAvPlayerGetStreamInfoEx](../../../core/libs/prx/libSceAvPlayer/src/Player.cpp) (libSceAvPlayer) - every player reports audio streams as type 2 (video 1), whether it was made by `sceAvPlayerInit` or by `sceAvPlayerInitEx` of any `this_size`. No module, SDK header or console measurement was used; the rule is inferred from titles. PPSA02433 makes its logo players with `sceAvPlayerInit` and its READY callback (app.elf 0x1509d50) enables type 2 as audio and type 1 as video and ignores other types, so type 0 left the logos silent; the PPSA02508 and PPSA03706 players made with the 0x230-byte `sceAvPlayerInitEx` layout enable type 2 as well. Unverified: whether the console library reports a different value for some init path or SDK version, and the values of other stream types (timed text). No title expecting audio type 0 is known - [sceAvPlayerGetVideoData / sceAvPlayerGetVideoDataEx](../../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) - delivers a video frame up to 0.75 × frame duration ahead of its timestamp in default sync mode to accommodate presentation loops that poll slightly before the refresh boundary; the console's exact presentation tolerance is unverified. - [sceAvPlayerGetVideoData](../../../core/libs/prx/libSceAvPlayer/src/Source.cpp) (libSceAvPlayer) in sync mode `SCE_AVPLAYER_AV_SYNC_MODE_NONE` skips queued frames that the audio clock has already passed while the title pulls audio, so a title that requests video slower than the film's frame rate keeps the picture with the sound. Whether the console hands out every frame in this mode is unknown. PPSA12544 requests 7-19 frames per second for a 30 fps cinematic diff --git a/docs/dev/debt/libSceAvSetting.md b/docs/dev/debt/libSceAvSetting.md index 1171471326..2b200a5c7d 100644 --- a/docs/dev/debt/libSceAvSetting.md +++ b/docs/dev/debt/libSceAvSetting.md @@ -1,12 +1,22 @@ # libSceAvSetting technical debt +### Recovered behavior + +- [`sceAvControlIsColorimetryHdr`](../../../core/libs/prx/libSceAvSetting/Export.cpp) (NID `3jnRsjnzIjI`) maps colorimetry values 9 through 20 to a 0/1 table; value 18 and values outside that range return false. +- [`sceAvControlIsDeepColorSupportedByMonitorInfo`](../../../core/libs/prx/libSceAvSetting/Export.cpp) (NID `fnqFpsRrsg4`) checks masks `0x0a` at offsets `0x74` and `0x75`, then mask `0x03` at offset `0x352`. +- [`sceAvControlIsHdrSupportedByMonitorInfo`](../../../core/libs/prx/libSceAvSetting/Export.cpp) (NID `5wP7bivaX7c`) requires mask `0x04` at offset `0x358` and mask `0x30` at offset `0x78`. +- [`sceAvControlIsModeGen1Vr`](../../../core/libs/prx/libSceAvSetting/Export.cpp) (NID `C81K5tny9CE`) returns bit 1 of the 32-bit field at offset `0x30`. +- [`sceAvControlIsModeSystemDefault`](../../../core/libs/prx/libSceAvSetting/Export.cpp) (NID `rVK3QqurCkg`) returns true when bit 0 of the 32-bit field at offset `0x30` is clear. + +These behaviors are recovered from static disassembly of the firmware 13.60 `libSceAvSetting.sprx` using Ghidra 12.1.4. The opaque monitor-info and video-mode layouts are represented by byte offsets because no project structure definition was found. These implementations have not been verified on a console. + ### Unknown function info - [sceAvControlChangeOutputLayout, sceAvControlChangeOutputMode, sceAvControlChangeOutputModeForDiag, sceAvControlChangeProcessAttribute, sceAvControlChangeScanInMode](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. - [sceAvControlGetCurrentDeviceId, sceAvControlGetCurrentOutputMode, sceAvControlGetCurrentOutputModeWithSync, sceAvControlGetDetailHdcpStatus, sceAvControlGetDeviceInfo](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. - [sceAvControlGetHdcpStatus, sceAvControlGetMonitorInfo, sceAvControlGetNativeMonitorInfo, sceAvControlGetPuStatus, sceAvControlGetPuType](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. -- [sceAvControlInit, sceAvControlIsAudioOutModeSupportedByMonitorInfo, sceAvControlIsColorimetryHdr, sceAvControlIsDeepColorSupportedByMonitorInfo, sceAvControlIsDeviceConnected](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. -- [sceAvControlIsHdrSupportedByMonitorInfo, sceAvControlIsModeGen1Vr, sceAvControlIsModeSystemDefault, sceAvControlIsSuspendedProcessOutputModeAvailable, sceAvControlIsVideoOutModeSupportedByMonitorInfo](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. +- [sceAvControlInit, sceAvControlIsAudioOutModeSupportedByMonitorInfo, sceAvControlIsDeviceConnected](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. +- [sceAvControlIsSuspendedProcessOutputModeAvailable, sceAvControlIsVideoOutModeSupportedByMonitorInfo](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. - [sceAvControlSet2dVrMode, sceAvControlSetAudioMuteForShutdown, sceAvControlSetAvOutputMode, sceAvControlSetColorEffect, sceAvControlSetDefaultAudioOutMode](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. - [sceAvControlSetDefaultVideoOutMode, sceAvControlSetDispclk, sceAvControlSetGamutMetadata, sceAvControlSetHdcpEncryption, sceAvControlSetHdcpStatus](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. - [sceAvControlSetHdmiGamutMetadata, sceAvControlSetHdrMetadata, sceAvControlSetVideoOutSource, sceAvControlSimulateProcessOutputModeArbitration, sceAvSettingAddCallbacks](../../../core/libs/prx/libSceAvSetting/Export.cpp) - names and NIDs match the [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceAvSetting.c); signatures and behavior are unverified. No-parameter declarations resolve imports and throw through `NotImplemented_nid_no_patch` when called. diff --git a/docs/dev/debt/libSceAvcap2.md b/docs/dev/debt/libSceAvcap2.md index 5b45837431..a73fec5a9e 100644 --- a/docs/dev/debt/libSceAvcap2.md +++ b/docs/dev/debt/libSceAvcap2.md @@ -19,3 +19,24 @@ The 7 original exports listed below in [Export.cpp](../../../core/libs/prx/libSc - [sceAvcap2Start](../../../core/libs/prx/libSceAvcap2/Export.cpp) (libSceAvcap2) - unknown signature (NID 8CGNCwBsItI) - [sceAvcap2Stop](../../../core/libs/prx/libSceAvcap2/Export.cpp) (libSceAvcap2) - unknown signature (NID Z5dKK0xnQ8g) - [sceAvcap2ReadAudio](../../../core/libs/prx/libSceAvcap2/Export.cpp) (libSceAvcap2) - unknown signature (NID WhsHggqdwcg) + +### Additional callable exports + +- [Additional callable exports](../../../core/libs/prx/libSceAvcap2/Export.cpp) - 12 entries present in both version-labelled images are import-resolution declarations only. None is listed in the pinned public 3.20 catalogue for this library. Neither label independently authenticates retail firmware provenance. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +[Version-pinned selected ELF export records](https://gist.github.com/kyon-eth/3fbb1a4a6f95a936b4cd58e9c3d8c50c/5e3af709224ed8235af11337309af0725e5685cb) list every NID below with its image label, decoded export module and library, raw qualified symbol, ELF function type, value and size for each inspected version-labelled image. The extract SHA-256 is `02ab27f12bcd0cc9fa03fa33f55fc201110f0d54a86bbbaa093d55d0a8cf262d`; its full-module sorted-function-NID digests and raw module-parameter words permit independent comparison. This is an analyst-produced metadata record, not independent authentication of the original images or proof of prototypes or behavior. + +| Name | NID | +| --- | --- | +| `Unknown` | `+OspdnZ-8mc` | +| `Unknown` | `1Xp85wUaVTE` | +| `Unknown` | `32gujAUoIMc` | +| `Unknown` | `4hkuDvkGu7U` | +| `Unknown` | `7KBaem5GyBQ` | +| `Unknown` | `88YEF+XHfRY` | +| `Unknown` | `HAeczEM-m7c` | +| `Unknown` | `HrGLkn9o4MY` | +| `Unknown` | `Tzk7-W5mp0A` | +| `Unknown` | `VNRuuZQx7gE` | +| `Unknown` | `iy6ZcsdeoMk` | +| `Unknown` | `k7gz608olKM` | diff --git a/docs/dev/debt/libSceCamera.md b/docs/dev/debt/libSceCamera.md index 12df0e604c..2ed63b0b71 100644 --- a/docs/dev/debt/libSceCamera.md +++ b/docs/dev/debt/libSceCamera.md @@ -22,6 +22,7 @@ - [sceCameraGetConfig, sceCameraSetConfig, sceCameraSetConfigInternal, sceCameraGetDeviceConfig and sceCameraGetDeviceConfigWithoutHandle](../../../core/libs/prx/libSceCamera/src/Config.cpp) (libSceCamera) - the type 1 default configuration and the cleared device configurations follow shadPS4's camera_config.cpp and [camera_device.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/camera/camera_device.cpp); a new configuration is refused with `NOT_CONNECTED` before its type is read, as shadPS4 does without a camera. Not measured on a console. Closes when: a host camera is exposed to guests. - [sceCameraGetDeviceInfo, sceCameraGetProductInfo, sceCameraGetRegistryInfo, sceCameraGetMmapConnectedCount, sceCameraGetCalibDataFromDevice, sceCameraGetCalibrationData and sceCameraAudioOpen](../../../core/libs/prx/libSceCamera/src/Device.cpp) (libSceCamera) - report a missing device with the errors from shadPS4's camera_device.cpp, camera_config.cpp, [camera_calibration.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/camera/camera_calibration.cpp) and [camera_audio.cpp](https://github.com/shadps4-emu/shadPS4/blob/396c2a09a38d22eaa02aab32c1967263dceab95f/src/core/libraries/camera/camera_audio.cpp). `sceCameraGetCalibDataFromDevice` and `sceCameraAudioOpen` are declared without parameters, as in shadPS4. Not measured on a console. Closes when: a host camera is exposed to guests. - [sceCameraAudioClose, sceCameraAudioGetData, sceCameraAudioGetData2, sceCameraAudioReset](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera) - follow [shadPS4's camera_audio.cpp](https://github.com/shadps4-emu/shadPS4/blob/0614ce324be092791b758d79f93d9f1ca5b10a81/src/core/libraries/camera/camera_audio.cpp#L9-L43) with no camera attached, as the rest of the library does: they answer 0, and the data calls refuse a null buffer with `SCE_CAMERA_ERROR_PARAM` and write nothing, where shadPS4 notes the console answers 0 while failing internally. Not measured on a console +- [sceCameraDeviceOpen, sceCameraSetAttributeInternal](../../../core/libs/prx/libSceCamera/Export.cpp) (libSceCamera) - answer 0 and do nothing, before or after `sceCameraOpen`: shadPS4 marks both as stubbed on the console itself ([device open](https://github.com/shadps4-emu/shadPS4/blob/0614ce324be092791b758d79f93d9f1ca5b10a81/src/core/libraries/camera/camera_device.cpp#L9-L12), [internal attribute](https://github.com/shadps4-emu/shadPS4/blob/0614ce324be092791b758d79f93d9f1ca5b10a81/src/core/libraries/camera/camera_config.cpp#L324-L327)), where its unfinished calls log `(STUBBED)` instead. That is a PS4 source: the PS5 module was not read, and the parameter lists stay unverified because the bodies read no arguments. Not measured on a console ### Additional PS5 3.20 imports @@ -39,7 +40,6 @@ - [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` -- [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` - [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` - [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` @@ -76,7 +76,6 @@ - [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` - [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` -- [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` - [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` diff --git a/docs/dev/debt/libSceCdlgPlayerReview.md b/docs/dev/debt/libSceCdlgPlayerReview.md index 0e97a3d72d..45cde68133 100644 --- a/docs/dev/debt/libSceCdlgPlayerReview.md +++ b/docs/dev/debt/libSceCdlgPlayerReview.md @@ -6,4 +6,4 @@ ### Functional -- [Player review dialog](../../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) shows nothing: `scePlayerReviewDialogOpen` finishes at once and the result reports that the user canceled, as `sceWebBrowserDialogOpen` does +- [Player review dialog](../../../core/libs/prx/libSceCdlgPlayerReview/Export.cpp) (libSceCdlgPlayerReview) shows nothing: `scePlayerReviewDialogOpen` finishes at once and the result reports that the user canceled, as `sceWebBrowserDialogOpen` does; returning `COMMON_DIALOG_ERROR_INVALID_ARGUMENT` for an unwritable result is a host-safety fallback as console behavior for unwritable memory has not been measured diff --git a/docs/dev/debt/libSceCesCs.md b/docs/dev/debt/libSceCesCs.md index 1022e41e4f..e538d11f5e 100644 --- a/docs/dev/debt/libSceCesCs.md +++ b/docs/dev/debt/libSceCesCs.md @@ -22,8 +22,9 @@ - [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 - [EUC-JIS-2004 and Shift_JIS-2004 profiles](../../../core/libs/prx/libSceCesCs/JisX0213.hpp) (libSceCesCs) - the errors follow the other JIS profiles, unmeasured; the tables were measured on a PS5 (`sceCesMbcToUtf32` over every one-, two- and `0x8F`-led three-byte sequence, `sceCesUtf32ToMbc` over U+0000-U+110001). Both planes follow Project X0213's JIS X 0213:2004 table with the 90 cells it notes as having a fullwidth form mapped to that form, and also cells 1-17, 1-79 and 2-15 to 2-18 (U+FFE3, U+FFE5, U+FF07, U+FF02, U+FF0D, U+FF5E); U+203E and U+00A5 still encode into 1-17 and 1-79. EUC-JIS-2004's SS3 reads JIS X 0212 in the rows plane 2 leaves empty, both ways, with plane 2 preferred for a code point in both. The 25 cells the standard maps to two code points are unassigned both ways on the console (`A4F7` gives `0x805C0020`; U+304B U+309A encodes U+304B, then fails). Bytes below `0x80` are ASCII. The `sceCesEucJp*` and `sceCesSJis*` scalar helpers keep refusing these two profiles. - [ISO-2022-JP profiles and string conversions](../../../core/libs/prx/libSceCesCs/Export.cpp) (libSceCesCs) - the signatures follow the MBCS string family by analogy (none in the type database). The UTF-16 and UCS-2 forms share the converters and were not measured separately. `sceCesIso2022UcsContextInit` with a null profile zeroes 256 context bytes and answers `INVALID_PARAMETER`, from a PC run of the firmware 13.42 module, not a console. `sceCesIso2022JpUcsProfileSetSbcs` still throws. Measured on a PS5 through the UTF-32 and UTF-8 forms (every BMP scalar, every designation then every byte and 94x94 pair, 46 strings per profile): RFC 1468 starts in JIS X 0201 Roman (`ESC ( B` and `ESC ( J` both select it) and uses the strict JIS X 0208 table; the code pages start in ASCII and use CP932's table plus the JIS forms. No profile has JIS X 0212. A trailing designation is not counted; one at the buffer end answers `SRC_BUFFER_END`. The encoder returns to the initial set only if it left it. -- [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 +- [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/). Their unassigned bytes are in the next entry. The remaining profiles, including CP864 and the private-use Windows profiles, still throw +- [Cp1254 and Cp1258 unassigned bytes, and surrogates in sceCesUtf32ToSbc](../../../core/libs/prx/libSceCesCs/SingleByte.hpp) (libSceCesCs) - bytes `81 8D 8E 8F 90 9D 9E` of 1254 and `81 8A 8D 8E 8F 90 9A 9D 9E` of 1258 answer `0x805C0020` both ways, as [rooklz](https://github.com/boykopovar/AnyPS5/pull/10183#issuecomment-6389941634) measured on a PS5 through `sceCesSbcToUtf32` and `sceCesUtf32ToSbc`; BunnyFl's [13.42 run](https://github.com/boykopovar/AnyPS5/pull/9912#issuecomment-6403897082) gives the same bytes. The MBCS context conversions were not measured with these profiles. The same sweep found `sceCesUtf32ToSbc` answering `0x805C0015` (`ILLEGAL_CODE`) for a surrogate with every single-byte profile, as it does here; the other UTF-32 and UCS-2 forms were not measured and keep `INVALID_ENCODE`. - [sceCesRefersUcsProfileCp858](../../../core/libs/prx/libSceCesCs/SingleByte.hpp) (libSceCesCs) - the console's table is not verified. The profile is the Cp850 one with byte 0xD5 mapped to U+20AC instead of U+0131, which is the whole high half of ICU's [ibm-858_P100-1997.ucm](https://github.com/unicode-org/icu-data/blob/main/charset/data/ucm/ibm-858_P100-1997.ucm); bytes 0x00-0x7F stay unchanged as in the other single-byte profiles, not swapped as in IBM's control mapping -- [libSceCesCs firmware 3.20 pending exports](../../../core/libs/prx/libSceCesCs/PendingExports.cpp) - 26 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`. +- [libSceCesCs firmware 3.20 pending exports](../../../core/libs/prx/libSceCesCs/PendingExports.cpp) - 18 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 - [ISO 646 variants and JIS X 0201 profiles](../../../core/libs/prx/libSceCesCs/SingleByte.hpp) (libSceCesCs) - the tables and errors were measured on a PS5 with `sceCesSbcToUtf32` over every byte and `sceCesUtf32ToSbc` over every BMP scalar; the other single-byte forms share them and were not measured separately. The 7-bit sets (ASCII, BS 4730, GB 1988-80, JIS X 0201 Roman and Katakana, KS X 1003 and their `Tilde0x7e` variants) answer `INVALID_ENCODE` for bytes `0x80`-`0xFF`. BS 4730 changes only `0x23` (£), GB 1988-80 `0x24` (¥), JIS X 0201 Roman `0x5C` (¥) and KS X 1003 `0x5C` (₩); the base GB, JIS and KS profiles also map `0x7E` to overline and the `Tilde0x7e` variants keep a tilde. The katakana-only profile maps `0x21`-`0x5F` to U+FF61-U+FF9F and leaves `0x60`-`0x7E` unassigned; the 8-bit katakana profiles use `0xA1`-`0xDF`. diff --git a/docs/dev/debt/libSceCloudMessaging.md b/docs/dev/debt/libSceCloudMessaging.md new file mode 100644 index 0000000000..0a38a1baa1 --- /dev/null +++ b/docs/dev/debt/libSceCloudMessaging.md @@ -0,0 +1,5 @@ +# libSceCloudMessaging technical debt + +### Unknown function info + +- [libSceCloudMessaging inventory exports](../../../core/libs/prx/libSceCloudMessaging/Export.cpp) - all six names and NIDs are present in the complete [PS5 13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libscecloudmessaging-1d0153bc23.jsonl), and the pinned [module map](https://github.com/boykopovar/ps5-nid-map/blob/cc1d586fddab576a520f226da6b2632b031e6d09/data/Modules/libSceCloudMessaging.inc) assigns them to the physical `libSceCloudMessaging.sprx` provider. The mangled names establish export identities only; their encoded types are not treated as verified signatures. Parameter lists, return types and behavior are unverified; the parameterless `int` declarations are import-resolution placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. No cloud-messaging behavior is implemented. diff --git a/docs/dev/debt/libSceCompanionUtil.md b/docs/dev/debt/libSceCompanionUtil.md index be4a09862a..32c2127db2 100644 --- a/docs/dev/debt/libSceCompanionUtil.md +++ b/docs/dev/debt/libSceCompanionUtil.md @@ -2,9 +2,8 @@ ### Silent stubs -- [sceCompanionUtilGetRemoteOskEvent, sceCompanionUtilInitialize, sceCompanionUtilOptParamInitialize and sceCompanionUtilTerminate](../../../core/libs/prx/libSceCompanionUtil/Export.cpp) return success without managing companion service state or events. +- [sceCompanionUtilGetRemoteOskEvent, sceCompanionUtilInitialize, sceCompanionUtilOptParamInitialize and sceCompanionUtilTerminate](../../../core/libs/prx/libSceCompanionUtil/Export.cpp) (libSceCompanionUtil) - return success without managing companion service state or events. 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; parameterless declarations are for import resolution only. ### Unknown function info - [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 aa5a59ec25..d30c4bb904 100644 --- a/docs/dev/debt/libSceComposite.md +++ b/docs/dev/debt/libSceComposite.md @@ -6,10 +6,47 @@ ### Unknown function info -- [sceCompositorGetNullCanvasHandle, sceCompositorIsVirtualCanvasHandle, sceCompositorSetMainPortPixelFormat, sceCompositorGetVirtualCanvasHandle, sceCompositorSetCanvasIdToCanvasHandle, sceCompositorSetMetaIdToCanvasHandle, sceCompositorSetMainPort and sceCompositorSetVirtualCanvasRenderTargetCommand](../../../core/libs/prx/libSceComposite/Export.cpp) - signatures and behavior remain unknown. The parameterless `int` declarations only resolve imports, make no ABI claim and every call throws through `NotImplemented_nid_no_patch`. Names and NIDs are recorded in the [13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libscecomposite-23841193cf.jsonl); not measured on a console. +- [sceCompositorGetNullCanvasHandle, sceCompositorSetMainPortPixelFormat, sceCompositorGetVirtualCanvasHandle, sceCompositorSetCanvasIdToCanvasHandle, sceCompositorSetMetaIdToCanvasHandle, sceCompositorSetMainPort and sceCompositorSetVirtualCanvasRenderTargetCommand](../../../core/libs/prx/libSceComposite/Export.cpp) - signatures and behavior remain unknown. The parameterless `int` declarations only resolve imports, make no ABI claim and every call throws through `NotImplemented_nid_no_patch`. Names and NIDs are recorded in the [13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libscecomposite-23841193cf.jsonl); not measured on a console. +- [sceCompositorIsVirtualCanvasHandle](../../../core/libs/prx/libSceComposite/Export.cpp) - what a virtual canvas handle is and which calls make one are unknown. It follows firmware 13.42's libSceComposite.sprx (the copy in libSceCompositeExt.sprx has the same code), read from its disassembly and run natively on x86-64 (not a console): it takes a 32-bit handle and returns true exactly when bits 8 to 15 are all set, without reading any library state - [sceCompositorMemoryPoolCommit, sceCompositorGetSecondaryMemorySize, sceCompositorGetMemoryPoolAddress, sceCompositorGetSecondaryMemoryAddress, sceCompositorSecondaryMemoryCreate, sceCompositorGetMemoryPoolSize, sceCompositorPermanentMemoryCreate and sceCompositorMemoryPoolCreate](../../../core/libs/prx/libSceComposite/Export.cpp) - signatures and behavior remain unknown. The parameterless `int` declarations only resolve imports, make no ABI claim and every call throws through `NotImplemented_nid_no_patch`. Names and NIDs are recorded in the [13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libscecomposite-23841193cf.jsonl); not measured on a console. - [sceCompositorGetTFRing, sceCompositorStopSeeThroughBuffer, sceCompositorSetNativeVrVideoOutMode, sceCompositorWaitEndOfRenderingIfGpuWorking, sceCompositorSetFlipCommand3, sceCompositorSetTFRing and sceCompositorStartSeeThroughBuffer](../../../core/libs/prx/libSceComposite/Export.cpp) - signatures and behavior remain unknown. The parameterless `int` declarations only resolve imports, make no ABI claim and every call throws through `NotImplemented_nid_no_patch`. Names and NIDs are recorded in the [13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libscecomposite-23841193cf.jsonl); not measured on a console. - [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 + +### Additional callable exports + +- [Additional callable exports](../../../core/libs/prx/libSceComposite/Export.cpp) - 29 entries present in both version-labelled images are import-resolution declarations only. None is listed in the pinned public 3.20 catalogue for this library. Neither label independently authenticates retail firmware provenance. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Data exports require separate layout and ownership analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +| Name | NID | +| --- | --- | +| `sceCompositorSecondaryMemoryRelease` | `gEME-XiMfm0` | +| `Unknown` | `+A5zYfomrKo` | +| `Unknown` | `+DMKcAbpcrQ` | +| `Unknown` | `0hVj7ycuNjY` | +| `Unknown` | `3znAWbdvzNo` | +| `Unknown` | `4iL4YvcEKdU` | +| `Unknown` | `5vAisOgay3M` | +| `Unknown` | `8l316W539Zw` | +| `Unknown` | `FDHuP+z8KnM` | +| `Unknown` | `HXxq9giIX-I` | +| `Unknown` | `KpuS3CIcckw` | +| `Unknown` | `NMGq0MJ2iGo` | +| `Unknown` | `RPdjyM2WKcw` | +| `Unknown` | `SpfMcnnpfDA` | +| `Unknown` | `UEW01n5qkdA` | +| `Unknown` | `XZzyQX4T5ts` | +| `Unknown` | `Yck3L7ExyVc` | +| `Unknown` | `Z25ikyBUYy0` | +| `Unknown` | `aczOzib8mzI` | +| `Unknown` | `asXqPHp3O5g` | +| `Unknown` | `bwXAoMVv94M` | +| `Unknown` | `dTPMTtYqPX4` | +| `Unknown` | `gLAJaS1VbsA` | +| `Unknown` | `inplO6BVf5k` | +| `Unknown` | `j2CEWhcK0gc` | +| `Unknown` | `lvaCIMhYNh0` | +| `Unknown` | `mzPjqJ+unSw` | +| `Unknown` | `va+6TiqnE1Y` | +| `Unknown` | `yZcGEBwnDAM` | diff --git a/docs/dev/debt/libSceContentExport.md b/docs/dev/debt/libSceContentExport.md index 0e70333be7..ba4d78b3fa 100644 --- a/docs/dev/debt/libSceContentExport.md +++ b/docs/dev/debt/libSceContentExport.md @@ -2,9 +2,9 @@ ### Functional -- [Content export sessions](../../../core/libs/prx/libSceContentExport/Export.cpp): the host has no media library, so `sceContentExportFromDataWithThumbnail` goes through `NotImplemented_nid_no_patch` for a valid session without invoking data callbacks. Session allocation does not reproduce guest allocator calls, worker creation or their failure paths. `sceContentExportGetProgress` goes through `NotImplemented_nid_no_patch` 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. +- [Content export sessions](../../../core/libs/prx/libSceContentExport/Export.cpp): the host has no media library, so `sceContentExportFromData`, `sceContentExportFromFile`, `sceContentExportFromFileWithThumbnail` and `sceContentExportFromDataWithThumbnail` validate the export session before calling `NotImplemented_nid_no_patch` without invoking data callbacks. Session allocation does not reproduce guest allocator calls, worker creation or failure paths. `sceContentExportGetProgress` calls `NotImplemented_nid_no_patch` because an idle session returns either zero or invalid-state according to caller SDK compatibility mode. Initialization and session lookup throw for some invalid inputs instead of reproducing console error precedence and legacy SDK behavior. Session IDs use the lowest free slot in 0–9; exhaustion, termination with live sessions and cancellation before export return confirmed errors. -- [sceContentExportFromData](../../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - throws as unimplemented. Two titles disassembled for this entry call it once each with 7 arguments: PPSA01746 (0xba0e14) between `sceContentExportStart` and `sceContentExportFinish`, treating any result other than 0 as failure, and the ContentManagement plugin of PPSA06465 (0x11f9), treating a negative result as failure and any other as success. What the arguments and the callback mean is not shown. +- [sceContentExportFromData](../../../core/libs/prx/libSceContentExport/Export.cpp) (libSceContentExport) - validates the export session and throws as unimplemented. Two titles disassembled for this entry call it once each with 7 arguments: PPSA01746 (0xba0e14) between `sceContentExportStart` and `sceContentExportFinish`, treating any result other than 0 as failure, and the ContentManagement plugin of PPSA06465 (0x11f9), treating a negative result as failure and any other as success. What the arguments and the callback mean is not shown. ### Unknown function info diff --git a/docs/dev/debt/libSceConvertKeycode.md b/docs/dev/debt/libSceConvertKeycode.md index 22a13f06a2..6b2929fc28 100644 --- a/docs/dev/debt/libSceConvertKeycode.md +++ b/docs/dev/debt/libSceConvertKeycode.md @@ -2,6 +2,6 @@ ### Functional -- [sceConvertKeycodeGetVirtualKeycode](../../../core/libs/prx/libSceConvertKeycode/Export.cpp), `sceConvertKeycodeGetCharacter` and `sceConvertKeycodeGetCharacterFromKeyboardData`: 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. Three titles disassembled for this entry call `sceConvertKeycodeGetVirtualKeycode` at four sites (PPSA24779 0xa9199a, PPSA29866 0x2d8bdd, PPSA30601 0x15e4624 and 0x15e47da): the keyboard type is the constant 4 at 0xa9199a and 0x15e47da and a run-time value at the other two, and no value read is 2, 3, 12 or in 31 to 39. -- [sceConvertKeycodeGetCharacterFromKeyboardData](../../../core/libs/prx/libSceConvertKeycode/Export.cpp): an index outside `keyData->length` or `KEYBOARD_MAX_KEYCODES` throws; no reference gives the real result or validation order. +- [sceConvertKeycodeGetVirtualKeycode](../../../core/libs/prx/libSceConvertKeycode/Export.cpp), `sceConvertKeycodeGetCharacter` and `sceConvertKeycodeGetCharacterFromKeyboardData`: layouts 31 and up need a PS5 title or a PS4 SDK of 0x05000000, 38 and 39 a PS5 SDK of 0x06000000 or a PS4 SDK of 0x10000000, and 39 a PS5 title with SDK 0x08000000; other titles get `0x80BC001D`. `GetVirtualKeycode` swaps the Y and Z keys of layouts 2, 3 and 12 below PS5 SDK 0x04000000 or PS4 SDK 0x09000000. These rules come from firmware 13.42's module, run natively, not from a console; the SDK version is the one in the process parameters. Three titles disassembled for this entry call `sceConvertKeycodeGetVirtualKeycode` at four sites (PPSA24779 0xa9199a, PPSA29866 0x2d8bdd, PPSA30601 0x15e4624 and 0x15e47da): the keyboard type is the constant 4 at 0xa9199a and 0x15e47da and a run-time value at the other two, and no value read is 2, 3, 12 or in 31 to 39. +- [sceConvertKeycodeGetCharacterFromKeyboardData](../../../core/libs/prx/libSceConvertKeycode/Export.cpp): returns `0x80BC0030` for a `length` above 16 (or negative), an `led` of 8 or more, a `modifier_key` of 0x100 or more, a `state` bit outside 0x1C000080, or an index outside the key data, before the layout checks, and otherwise converts `key_code[index]` with `state | modifier_key << 8 | led << 16`. From firmware 13.42's module, run natively, not from a console. - [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/libSceCustomMusicPlayReady4.md b/docs/dev/debt/libSceCustomMusicPlayReady4.md index 23ea10e415..099d04406f 100644 --- a/docs/dev/debt/libSceCustomMusicPlayReady4.md +++ b/docs/dev/debt/libSceCustomMusicPlayReady4.md @@ -1,5 +1,5 @@ # libSceCustomMusicPlayReady4 technical debt -### Functional +### Unknown function info - [Callable exports](../../../core/libs/prx/libSceCustomMusicPlayReady4/Export.cpp): 82 unidentified exports have unverified names, signatures and behavior. These are throwing import-resolution placeholders; no playback functionality is implemented. No matching NIDs were found in the later module inventory. Data exports require separate layout and ownership analysis. diff --git a/docs/dev/debt/libSceCustomMusicSysCallWrapper.md b/docs/dev/debt/libSceCustomMusicSysCallWrapper.md index f7379ba3fc..525a5deb5f 100644 --- a/docs/dev/debt/libSceCustomMusicSysCallWrapper.md +++ b/docs/dev/debt/libSceCustomMusicSysCallWrapper.md @@ -1,5 +1,121 @@ # libSceCustomMusicSysCallWrapper technical debt +### Additional callable exports + +- [Additional callable exports](../../../core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp) - 101 entries present in both version-labelled images are import-resolution declarations only. None is listed in the pinned public 3.20 catalogue for this library. Neither label independently authenticates retail firmware provenance. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +[Version-pinned selected ELF export records](https://gist.github.com/kyon-eth/3fbb1a4a6f95a936b4cd58e9c3d8c50c/5e3af709224ed8235af11337309af0725e5685cb) list every NID below with its image label, decoded export module and library, raw qualified symbol, ELF function type, value and size for each inspected version-labelled image. The extract SHA-256 is `02ab27f12bcd0cc9fa03fa33f55fc201110f0d54a86bbbaa093d55d0a8cf262d`; its full-module sorted-function-NID digests and raw module-parameter words permit independent comparison. This is an analyst-produced metadata record, not independent authentication of the original images or proof of prototypes or behavior. + +| Name | NID | +| --- | --- | +| `sceCustomMusicAudiodecClearContext` | `WsJoyOz47OE` | +| `sceCustomMusicAudiodecCpuDecode` | `mz62R6Qjmf0` | +| `sceCustomMusicAudiodecCreateDecoder` | `QCuN-9Dd1bg` | +| `sceCustomMusicAudiodecDecode` | `qqFUG6MJKcU` | +| `sceCustomMusicAudiodecDeleteDecoder` | `4rOCTBP87dQ` | +| `sceCustomMusicFileClose` | `E0KbSONVDcU` | +| `sceCustomMusicFileLseek` | `go-ecEub9P0` | +| `sceCustomMusicFileOpenRead` | `V4A7LcYAdgA` | +| `sceCustomMusicFileRead` | `xrrLysMOFr4` | +| `sceCustomMusicKernelGettimeofday` | `GlUEBpsSb2E` | +| `sceCustomMusicKernelNanosleep` | `SsMe-3HXEHE` | +| `sceCustomMusicNetAccept` | `DELu3JawLrk` | +| `sceCustomMusicNetBind` | `EOO+r1P-9Nc` | +| `sceCustomMusicNetConnect` | `wvD8sZ7XghA` | +| `sceCustomMusicNetCtlInit` | `jORbkQLFfRM` | +| `sceCustomMusicNetCtlTerm` | `ZqJXAMKrqus` | +| `sceCustomMusicNetEpollAbort` | `Injn5VOe4Gg` | +| `sceCustomMusicNetEpollControl` | `H466Vy6VBLk` | +| `sceCustomMusicNetEpollCreate` | `5TsLV1cUWTc` | +| `sceCustomMusicNetEpollDestroy` | `XcF5UiPx-pc` | +| `sceCustomMusicNetEpollWait` | `FQ3hKAacgR8` | +| `sceCustomMusicNetErrnoLoc` | `E62C8Og0N5E` | +| `sceCustomMusicNetInetPton` | `DRqtzRNDa7s` | +| `sceCustomMusicNetListen` | `m2UfRq+Z1NU` | +| `sceCustomMusicNetPoolCreate` | `D62ZUAXrLTM` | +| `sceCustomMusicNetPoolDestroy` | `4cT5gPSCILs` | +| `sceCustomMusicNetRecv` | `Plv2m81jN84` | +| `sceCustomMusicNetResolverAbort` | `nvJMgVPsXUo` | +| `sceCustomMusicNetResolverCreate` | `lWQRcz0JroQ` | +| `sceCustomMusicNetResolverDestroy` | `NwCCxIIlm6I` | +| `sceCustomMusicNetSend` | `3+ODHrZgJRc` | +| `sceCustomMusicNetSetsockopt` | `2rJROSjbRZw` | +| `sceCustomMusicNetSocketClose` | `uAM3M24ikfU` | +| `sceCustomMusicNetSocket` | `bl5G0iE1jE4` | +| `sceCustomMusicPthreadAttrDestroy` | `UKFhulDRkuo` | +| `sceCustomMusicPthreadAttrInit` | `rKSYoakH9zg` | +| `sceCustomMusicPthreadCondDestroy` | `364AWhMvihw` | +| `sceCustomMusicPthreadCondInit` | `u0ACyDAN+gk` | +| `sceCustomMusicPthreadCondWait` | `34KbODwYX1s` | +| `sceCustomMusicPthreadCreate` | `BPtJo-zdOJI` | +| `sceCustomMusicPthreadDetach` | `iZNSjGzrgBc` | +| `sceCustomMusicPthreadExit` | `0cnReUY0C94` | +| `sceCustomMusicPthreadJoin` | `EtoqDoZoLFQ` | +| `sceCustomMusicPthreadMutexDestroy` | `gFxwjwKPvzc` | +| `sceCustomMusicPthreadMutexInit` | `7cqf8yfv218` | +| `sceCustomMusicPthreadMutexLock` | `HCNWPzFo2ak` | +| `sceCustomMusicPthreadSetprio` | `L7x6JPdSMtQ` | +| `sceCustomMusicPthreadYield` | `4LwHgkQnNSY` | +| `Unknown` | `+LdekyVYugM` | +| `Unknown` | `-f8PcMdF8L0` | +| `Unknown` | `0WBrHbI33Ec` | +| `Unknown` | `0d4Aw8u3xzE` | +| `Unknown` | `1auNQHxva3I` | +| `Unknown` | `24ZHuc5b0RA` | +| `Unknown` | `2yE7PehS6iI` | +| `Unknown` | `3DyCYcJudI4` | +| `Unknown` | `5I2IqwB0Hes` | +| `Unknown` | `5p7ZpbyUKuo` | +| `Unknown` | `6PNhaGAw6Ho` | +| `Unknown` | `70NNIr-nfFo` | +| `Unknown` | `7uOVo0lL97g` | +| `Unknown` | `8t8UK2XkJ1Y` | +| `Unknown` | `CQv51NIK3i4` | +| `Unknown` | `DABJz+GrAWg` | +| `Unknown` | `EaC0JWOBvy4` | +| `Unknown` | `HNx-bHhat18` | +| `Unknown` | `J4+14VzlTHM` | +| `Unknown` | `JSZXadyt-nk` | +| `Unknown` | `Kxq7i4lQjGg` | +| `Unknown` | `L0MmfM0N-y8` | +| `Unknown` | `Mk6mbbOvaBM` | +| `Unknown` | `NbAJfB19zUk` | +| `Unknown` | `QEVVJH35Fis` | +| `Unknown` | `ROnMWfJ56t8` | +| `Unknown` | `SneVIJUdrLw` | +| `Unknown` | `Tr-kghWgcoI` | +| `Unknown` | `VDAd516qud8` | +| `Unknown` | `VTS+zlRk-wo` | +| `Unknown` | `Xkm5tRxA0UQ` | +| `Unknown` | `Y3l3L6A4OYE` | +| `Unknown` | `ZiO9aSV1ZKc` | +| `Unknown` | `bzddXxOnr30` | +| `Unknown` | `eTHTXNt0B2k` | +| `Unknown` | `fsQhNY+RJf8` | +| `Unknown` | `gxew9cxe+xk` | +| `Unknown` | `hd0MG4ZvfRA` | +| `Unknown` | `jETJjJLQtas` | +| `Unknown` | `jVD2jFJAQ28` | +| `Unknown` | `kso0cI0kq1U` | +| `Unknown` | `lgRL6Oblmyg` | +| `Unknown` | `mgSEL99WSwA` | +| `Unknown` | `n0aVoomYuOM` | +| `Unknown` | `n4jZ2HjzXUo` | +| `Unknown` | `oaaW7CuQLV0` | +| `Unknown` | `qqNVevi1LlY` | +| `Unknown` | `sWVPeXELNss` | +| `Unknown` | `sak+IpFmBkk` | +| `Unknown` | `y2Fu+vFwEXo` | +| `Unknown` | `yM-etuyf-2M` | +| `Unknown` | `zRDEHOFDvzQ` | +| `Unknown` | `zUgaEYLIysQ` | + ### Unknown function info -- [ and sceCustomMusicSelect](../../../core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp) (libSceCustomMusicSysCallWrapper) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCustomMusicSysCallWrapper.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 +- [sceCustomMusicSelect](../../../core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp) (libSceCustomMusicSysCallWrapper) - the name and NID are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceCustomMusicSysCallWrapper.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 + +- [sceCustomMusicNetInetNtop](../../../core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp) - signature and behavior are unknown; the parameterless int declaration is an import-resolution placeholder and throws. +- [sceCustomMusicNetShutdown](../../../core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp) - signature and behavior are unknown; the parameterless int declaration is an import-resolution placeholder and throws. +- [sceCustomMusicNetSocketAbort](../../../core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp) - signature and behavior are unknown; the parameterless int declaration is an import-resolution placeholder and throws. +- [sceCustomMusicPthreadAttrGet](../../../core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp) - signature and behavior are unknown; the parameterless int declaration is an import-resolution placeholder and throws. +- [sceCustomMusicPthreadSelf](../../../core/libs/prx/libSceCustomMusicSysCallWrapper/Export.cpp) - signature and behavior are unknown; the parameterless int declaration is an import-resolution placeholder and throws. diff --git a/docs/dev/debt/libSceDbg.md b/docs/dev/debt/libSceDbg.md new file mode 100644 index 0000000000..a210cc5bae --- /dev/null +++ b/docs/dev/debt/libSceDbg.md @@ -0,0 +1,35 @@ +# libSceDbg technical debt + +### Unknown function info + +The 29 exports in [Export.cpp](../../../core/libs/prx/libSceDbg/Export.cpp) come from the `libSceDbg` list in [claimore22/ps5rs](https://github.com/claimore22/ps5rs/blob/5f252a658ff470bcb2b6cd15205fd8ca64ae4477/data/stubs/by_library/libSceDbg_nosubmission.txt) (commit 5f252a658ff470bcb2b6cd15205fd8ca64ae4477), a catalogue of PS5 symbols per library whose provenance the project does not document, and no PS5 export inventory of this module is available to confirm the list is complete or exact. Every name was kept only because its hash equals the listed NID. Prototypes and behaviour are unverified: every function takes no parameters in the import-resolution convention and throws through `NotImplemented_nid_no_patch`. + +- [sceDbgUserChannelPrint](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID 1Qy1jn47QWA) +- [sceDbgAmprAprGetNumberOfErrors](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID 4F7uYHxulZ4) +- [sceDbgMapperVirtualQuery](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID 4OqKr5iYCqI) +- [sceDbgUserChannelPrintf](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID 5huYgqy73xo) +- [sceDbgAssertionHandler](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID 5sBISBR0ooA) +- [sceDbgGetSystemSwVersion](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID B4c4qfm3m2c) +- [sceDbgSetBreakOnWarningState](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID BxQv1DSUaeY) +- [sceDbgSetBreakOnErrorState](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID CXoCR22n86Q) +- [sceDbgDeleteExternalTriggerEvent](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID Fhc+DypKzcU) +- [sceDbgAmprAmmGetNumberOfErrors](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID FvI50-3hoSo) +- [sceDbgGetModuleInfo](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID G4c6av2TDtc) +- [sceDbgBacktraceSelf](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID GDX5XhnWKrw) +- [sceDbgAmprAmmGetErrors](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID HNRCEumA2V8) +- [sceDbgGetDebugSuspendCount](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID He3luzgOL-w) +- [sceDbgUserChannelVPrintf](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID MltFJdH0xRM) +- [sceDbgSetMinimumLogLevel](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID OaQI1HqFAtk) +- [sceDbgGetModuleList](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID PvIP303tQx4) +- [sceDbgClearHardwareBreakPoint](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID Q-m9mL4Yznw) +- [sceDbgAddExternalTriggerEvent](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID SxSEAtl-vow) +- [sceDbgLoggingHandler](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID To9mmGL+3G8) +- [sceDbgEnableExtraHeapDebugInfo](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID WBQJDbBxH3E) +- [sceDbgAmprAprGetErrors](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID ddYgzamMD2U) +- [sceDbgVLoggingHandler](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID ie2v2DfofTs) +- [sceDbgGetReleaseCheckMode](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID jaYypweDbyA) +- [sceDbgSetHardwareBreakPoint](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID kwELJE1qN0o) +- [sceDbgGetExecutablePath](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID ta-oGegHdTQ) +- [sceDbgGetCpuGpuFrequencySetting](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID uhJ+bSomPoE) +- [sceDbgRequireSystemSwVersion](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID v6OElL4DxCo) +- [sceDbgIsDebuggerAttached](../../../core/libs/prx/libSceDbg/Export.cpp) (libSceDbg) - unknown signature (NID v9ERBDafV60) diff --git a/docs/dev/debt/libSceDeci5.md b/docs/dev/debt/libSceDeci5.md new file mode 100644 index 0000000000..72dc8f378e --- /dev/null +++ b/docs/dev/debt/libSceDeci5.md @@ -0,0 +1,13 @@ +# libSceDeci5 technical debt + +### Unknown function info + +The 7 exports in [Export.cpp](../../../core/libs/prx/libSceDeci5/Export.cpp) come from the `libSceDeci5` list in [claimore22/ps5rs](https://github.com/claimore22/ps5rs/blob/5f252a658ff470bcb2b6cd15205fd8ca64ae4477/data/stubs/by_library/libSceDeci5_nosubmission.txt) (commit 5f252a658ff470bcb2b6cd15205fd8ca64ae4477), a catalogue of PS5 symbols per library whose provenance the project does not document, and no PS5 export inventory of this module is available to confirm the list is complete or exact. Every name was kept only because its hash equals the listed NID. Prototypes and behaviour are unverified: every function takes no parameters in the import-resolution convention and throws through `NotImplemented_nid_no_patch`. + +- [sceDeci5Write](../../../core/libs/prx/libSceDeci5/Export.cpp) (libSceDeci5) - unknown signature (NID 5IVqeThOi2s) +- [sceDeci5Read](../../../core/libs/prx/libSceDeci5/Export.cpp) (libSceDeci5) - unknown signature (NID 8T8VUmTIKE0) +- [sceDeci5CreateHostProcessAndWait](../../../core/libs/prx/libSceDeci5/Export.cpp) (libSceDeci5) - unknown signature (NID Ez2iQHniESc) +- [sceDeci5Close](../../../core/libs/prx/libSceDeci5/Export.cpp) (libSceDeci5) - unknown signature (NID gYg5m+zII9w) +- [sceDeci5CreateHostProcess](../../../core/libs/prx/libSceDeci5/Export.cpp) (libSceDeci5) - unknown signature (NID nS4i1N-HEws) +- [sceDeci5Open](../../../core/libs/prx/libSceDeci5/Export.cpp) (libSceDeci5) - unknown signature (NID sLfxYROuNZU) +- [sceDeci5GetStatus](../../../core/libs/prx/libSceDeci5/Export.cpp) (libSceDeci5) - unknown signature (NID v-VB0EguQWg) diff --git a/docs/dev/debt/libSceEsvm.md b/docs/dev/debt/libSceEsvm.md index ff5986e112..ed4ba70116 100644 --- a/docs/dev/debt/libSceEsvm.md +++ b/docs/dev/debt/libSceEsvm.md @@ -53,3 +53,16 @@ | `sceEsvmVideoOutSetContentType` | `oUGLWJNfVwM` | - [sceEsvmAppAudioSyncCreateContext, sceEsvmAppAudioSyncFlush and sceEsvmSyncVideo](../../../core/libs/prx/libSceEsvm/Export.cpp) (libSceEsvm) - signatures and behavior are unverified; these parameterless declarations only resolve imports and throw through `NotImplemented_nid_no_patch`. + +### Additional callable exports + +- [Additional callable exports](../../../core/libs/prx/libSceEsvm/Export.cpp) - 4 entries present in both version-labelled images are import-resolution declarations only. None is listed in the pinned public 3.20 catalogue for this library. Neither label independently authenticates retail firmware provenance. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +[Version-pinned selected ELF export records](https://gist.github.com/kyon-eth/3fbb1a4a6f95a936b4cd58e9c3d8c50c/5e3af709224ed8235af11337309af0725e5685cb) list every NID below with its image label, decoded export module and library, raw qualified symbol, ELF function type, value and size for each inspected version-labelled image. The extract SHA-256 is `02ab27f12bcd0cc9fa03fa33f55fc201110f0d54a86bbbaa093d55d0a8cf262d`; its full-module sorted-function-NID digests and raw module-parameter words permit independent comparison. This is an analyst-produced metadata record, not independent authentication of the original images or proof of prototypes or behavior. + +| Name | NID | +| --- | --- | +| `sceEsvmInitializePlayReady` | `9FyuJdx5q9s` | +| `sceEsvmInitializeWidevine` | `9+Iejj-syHc` | +| `sceEsvmRequestMediaKeySystemAccessPlayReady` | `UOTAc1GJCLI` | +| `sceEsvmRequestMediaKeySystemAccessWidevine` | `bYyMqndqumU` | diff --git a/docs/dev/debt/libSceFios2.md b/docs/dev/debt/libSceFios2.md index 7b43eb0959..f52986776b 100644 --- a/docs/dev/debt/libSceFios2.md +++ b/docs/dev/debt/libSceFios2.md @@ -1,7 +1,23 @@ # libSceFios2 technical debt +### Functional + +- [libSceFios2 prototypes](../../../core/libs/prx/libSceFios2/include/Api.hpp) (libSceFios2) - the implemented exports take the prototypes of RPCS3's Vita FIOS2 module (`Emu/ARMv7/Modules/sceFios.h` and `sceFios.cpp`, jacquesjk/RPCS-DX12 at `9b8b7c9eea446095f24af19182032013837e6ca5`, 64-bit types substituted), checked against the PS4 headers and call sites of kalaposfos13/fios2 (`src/fios2.h`, `ae6ca1d78e7a822c2528f7660959167b9b3d8b63`), sonny-tel/libSceFios2 (`sceFios2.h`, `5d44f9e7a80ae00557af80fad335e4c74ef35d22`), Inori/GPCS4 (`SceFios2/sce_fios2.cpp`) and SonicMastr/SceLibcPosix (`src/internal/fios2.h`, Vita, `c2cd5e880a8770bdbfb58de761de11f396c1a2e7`). +- [Layouts](../../../core/libs/prx/libSceFios2/include/Api.hpp) (libSceFios2) - The `SceFiosOpAttr`, `SceFiosOpenParams`, `SceFiosStat` and `SceFiosDirEntry` layouts and the `SCE_FIOS_ERROR_*` values (`0x80820000` plus the enumeration order) come from the same sources; vascon-es, a PC port of a PS4 title (CUSA01176 and CUSA00935 version 01.24), at `57c3c202975ea48e574df2d1e4a620b4e18b7701`, confirms the `SceFiosStat` and `SceFiosDirEntry` offsets and the `fileSize`, `statFlags` bit 0 (directory) and `offsetToName` reads from the call sites of a PS4 title. None of it was measured on a PS5. The `SceFiosOpenParams` buffer sits at offset 8 in kalaposfos13 and sonny-tel and at offset 0x10 in vascon-es; only `openFlags` is read +- [Operations](../../../core/libs/prx/libSceFios2/src/Runtime.cpp) (libSceFios2) - there are no I/O or decompressor threads. An operation runs on the calling thread and is complete when the export returns, and its callback fires there with `SCE_FIOS_OPEVENT_COMPLETE`; while `sceFiosSuspend` is in effect new operations queue and run on the thread whose `sceFiosResume` ends the suspension. `deadline`, `priority`, `opflags`, `userTag`, `userPtr` and the scheduler fields are stored and returned but have no effect, and `SCE_FIOS_OPEVENT_START` is never sent. The `*Sync` exports are the asynchronous export followed by `sceFiosOpSyncWait`, so they also fire the completion and `SCE_FIOS_OPEVENT_DELETE` callbacks. +- [Completion values and parameters](../../../core/libs/prx/libSceFios2/src/Runtime.cpp) (libSceFios2) - `sceFiosOpSyncWait` returns the error or the low 32 bits of the result (1 or 0 for the existence queries, as in kalaposfos13/fios2), `sceFiosOpGetActualCount` returns the result (the size for `sceFiosFileGetSize`) also for a failed operation, and `sceFiosOpGetError` returns 1 while an operation is queued; none of these is measured. Handles of operations, files and directories share one number space that starts at 1. Nothing requires `sceFiosInitialize` first, and `SceFiosParams` is stored as `paramsSize` bytes (the 15 bits at bit 1 of the first word) without interpretation, so the storage buffers, `pathMax` (1024 is used), thread and scheduler settings are ignored +- [sceFiosExists, sceFiosFileExists, sceFiosDirectoryExists, sceFiosFileGetSize, sceFiosStat and their Sync forms](../../../core/libs/prx/libSceFios2/src/PathQueries.cpp) (libSceFios2) - paths go to `sceKernelStat`, so archive and alias mapping is the kernel's. A missing path is not an error for the existence queries. `SceFiosStat` carries only the sizes, the nanosecond dates as seconds since 1970 times 10^9 plus nanoseconds (the epoch and unit are not measured), `statFlags` bit 0 for a directory, and the kernel `st_uid`, `st_gid`, `st_dev`, `st_ino` and `st_mode`; other `statFlags` bits are 0. `sceFiosFileExists` and `sceFiosDirectoryExists` take the `pOutExists` argument of RPCS3's prototype, which kalaposfos13/fios2 omits, and `sceFiosFileGetSize` has no output pointer as in kalaposfos13/fios2 and sonny-tel (RPCS3 declares one) +- [sceFiosDelete, sceFiosFileDelete, sceFiosDirectoryCreate, sceFiosDirectoryDelete, sceFiosRename, sceFiosFileRead, sceFiosFileWrite, sceFiosFileTruncate and their Sync forms](../../../core/libs/prx/libSceFios2/src/PathOperations.cpp) (libSceFios2) - behaviour is inferred from the export names; none of it is measured. They run on `sceKernelStat`, `sceKernelOpen`, `sceKernelPread`, `sceKernelPwrite`, `sceKernelFtruncate`, `sceKernelMkdir`, `sceKernelRmdir`, `sceKernelUnlink` and `sceKernelRename`, so guest path mapping and the read-only application directory are the kernel's. `sceFiosFileWrite` opens the file write-only with creation and no truncation, `sceFiosFileTruncate` does not create it, directories are created with mode 0777 and files with 0666, `sceFiosDelete` removes a file or an empty directory, and a negative length returns `SCE_FIOS_ERROR_BAD_SIZE`, a negative offset `SCE_FIOS_ERROR_BAD_OFFSET`. +- [Error mapping](../../../core/libs/prx/libSceFios2/src/PathOperations.cpp) (libSceFios2) - A read stops at the end of the file and returns the bytes read. The mapping of kernel errors to `SCE_FIOS_ERROR_*` is chosen here: `ENOENT` and `ENOTDIR` to `BAD_PATH`, `EPERM`, `EACCES` and `ENOTEMPTY` to `ACCESS`, `EROFS` to `READ_ONLY`, `EEXIST` to `ALREADY_EXISTS`, `EISDIR` to `NOT_A_FILE`, `EINVAL` to `BAD_OFFSET`, `ENFILE` and `EMFILE` to `CANT_ALLOCATE_FH`, `ENAMETOOLONG` to `PATH_TOO_LONG`, `EFAULT` to `BAD_PTR`, and anything else to `UNKNOWN`. The prototypes follow RPCS3 (`sceFios.cpp` `sceFiosFileWrite`, `sceFiosFileTruncate`, `sceFiosRename`, `sceFiosDelete`) and kalaposfos13/fios2 (`sceFiosFileRead`) +- [sceFiosFHOpen, sceFiosFHOpenWithMode, sceFiosFHClose, sceFiosFHStat, sceFiosFHTruncate, sceFiosFHSync, sceFiosFHRead, sceFiosFHWrite, sceFiosFHPread, sceFiosFHPwrite, the vectored forms with their Sync forms, sceFiosFHSeek, sceFiosFHTell, sceFiosFHGetSize, sceFiosFHGetPath and sceFiosFHGetOpenParams](../../../core/libs/prx/libSceFios2/src/FileHandles.cpp) (libSceFios2) - a file handle owns one kernel descriptor, and `sceFiosFHRead`, `sceFiosFHWrite`, `sceFiosFHSeek` and `sceFiosFHTell` use its kernel position. +- [File handle errors and lifetime](../../../core/libs/prx/libSceFios2/src/FileHandles.cpp) (libSceFios2) - `openFlags` map as `SCE_FIOS_O_READ` 1, `SCE_FIOS_O_WRITE` 2, `SCE_FIOS_O_APPEND` 4, `SCE_FIOS_O_CREAT` 8 and `SCE_FIOS_O_TRUNC` 16 (sonny-tel/libSceFios2, SonicMastr/SceLibcPosix); `SCE_FIOS_O_DIRECT` (1 << 12) is ignored, no flags or null parameters open read-only, and the handle returns `SCE_FIOS_ERROR_WRITE_ONLY` or `SCE_FIOS_ERROR_READ_ONLY` for a transfer its flags exclude (the console result is not measured). The creation mode is 0777 unless `sceFiosFHOpenWithMode` is given one (`-1` means the default, as in kalaposfos13/fios2). +- [Open flags and offsets](../../../core/libs/prx/libSceFios2/src/FileHandles.cpp) (libSceFios2) - Opening a directory returns `SCE_FIOS_ERROR_NOT_A_FILE`. An invalid `whence` or a negative resulting position returns `SCE_FIOS_ERROR_BAD_OFFSET`, a vector count outside 1 to 1024 `SCE_FIOS_ERROR_BAD_IOVCNT`. The Readv and Writev exports transfer the vectors in order and stop at the first short one. `sceFiosFHGetPath` and `sceFiosFHGetOpenParams` return pointers that stay valid until the handle is closed; the stored `SceFiosOpenParams` has no buffer. The handle prototypes follow RPCS3's `sceFios.cpp` and kalaposfos13/fios2; `sceFiosFHToFileno` and `sceFiosFilenoToFH` have no source and keep throwing +- [sceFiosDHOpen, sceFiosDHRead, sceFiosDHClose, their Sync forms and sceFiosDHGetPath](../../../core/libs/prx/libSceFios2/src/DirectoryHandles.cpp) (libSceFios2) - the prototypes follow RPCS3 and kalaposfos13/fios2, with the `SceFiosBuffer` passed by value in two registers as in vascon-es. Opening reads the whole listing through `sceKernelGetdirentries` into host memory; the caller's buffer is never written. A title that opens with a null or too small buffer gets `SCE_FIOS_ERROR_BAD_SIZE` and the required size from `sceFiosOpGetActualCount`, the probe that vascon-es observes at the call sites of a PS4 title. +- [Required directory buffer size](../../../core/libs/prx/libSceFios2/src/DirectoryHandles.cpp) (libSceFios2) - The required size, `16` plus the name lengths plus one each, is chosen here and is not what the console reports, so a title that sizes its buffer from another source can get a false `BAD_SIZE`. `.` and `..` are not listed (unverified), entries come in kernel order, the end of the listing returns `SCE_FIOS_ERROR_EOF` and `sceFiosDHRead` fills `fileSize`, `statFlags` bit 0, `nameLength`, `fullPathLength`, `offsetToName` and `fullPath` as vascon-es reads them, with `fullPath` the opened path, a slash and the name +- [sceFiosDateGetCurrent, sceFiosDateFromComponents, sceFiosDateToComponents, sceFiosTimeIntervalFromNanoseconds and sceFiosTimeIntervalToNanoseconds](../../../core/libs/prx/libSceFios2/src/Time.cpp) (libSceFios2) - a `SceFiosDate` is taken as nanoseconds since 1970-01-01 UTC and a `SceFiosTimeInterval` as nanoseconds (identity conversions), as in kalaposfos13/fios2 (`src/fios2.cpp`, `time(nullptr) * 1000000000`) and RPCS3 (`sceFios.cpp`, 64-bit unsigned date); neither is measured, and both may descend from one Vita SDK. kalaposfos13/fios2 converts components through local time, here they are UTC (`sceFiosDateFromComponents` normalizes out-of-range fields like `timegm` and returns 0 before 1970). Only the nine leading `int` fields of `struct tm` are read and written, so `tm_gmtoff` and `tm_zone` are left untouched. `sceFiosTimeGetCurrent` has no source for its clock and keeps throwing +- [Operation threading](../../../core/libs/prx/libSceFios2/src/Runtime.cpp) (libSceFios2) - an operation and its `SCE_FIOS_OPEVENT_COMPLETE` callback run inside the call that issues the operation, whereas on the console the callback runs on a FIOS I/O thread. A title that issues an operation while holding a lock its own callback takes deadlocks on its own thread, and a title that expects the output pointers (`pOutFH`, `pOutExists`, `pOutStatus`) to be written only after `sceFiosOpWait` is not tested. A worker thread was not added: guest callbacks need a guest thread (`scePthreadCreate`, as libSceJobManager uses) with its TLS and stack, the `*Sync` forms must stay synchronous on the caller, and moving completion to another thread changes every output-pointer, callback and teardown ordering in the library and its tests, which could not be validated here against a title or a console +- [Guest inputs](../../../core/libs/prx/libSceFios2/src/Runtime.cpp) (libSceFios2) - `SceFiosOpAttr`, `SceFiosOpenParams`, `SceFiosParams`, the vector arrays and `struct tm` are copied with `GuestCopyIn_nid_no_patch`; a failed copy returns `SCE_FIOS_ERROR_BAD_PTR` (the operation handle value itself for an unreadable `SceFiosOpAttr`), or is ignored by the `void` exports. That the console returns `SCE_FIOS_ERROR_BAD_PTR` for an unreadable pointer is not measured + ### Unknown function info -- [sceFiosArchiveGetDecompressorThreadCount, sceFiosArchiveGetMountBufferSize, sceFiosArchiveGetMountBufferSizeSync, sceFiosArchiveMount, sceFiosArchiveMountSync, sceFiosArchiveMountWithOrder, sceFiosArchiveMountWithOrderSync, sceFiosArchiveSetDecompressorThreadCount, sceFiosArchiveUnmount, sceFiosArchiveUnmountSync, sceFiosCacheContainsFileRangeSync, sceFiosCacheContainsFileSync, sceFiosCacheFlushFileRangeSync, sceFiosCacheFlushFileSync, sceFiosCacheFlushSync, sceFiosCachePrefetchFH, sceFiosCachePrefetchFHRange, sceFiosCachePrefetchFHRangeSync, sceFiosCachePrefetchFHSync, sceFiosCachePrefetchFile, sceFiosCachePrefetchFileRange, sceFiosCachePrefetchFileRangeSync, sceFiosCachePrefetchFileSync, sceFiosCancelAllOps, sceFiosClearTimeStamps, sceFiosCloseAllFiles, sceFiosDHClose, sceFiosDHCloseSync, sceFiosDHGetPath, sceFiosDHOpen, sceFiosDHOpenSync, sceFiosDHRead, sceFiosDHReadSync, sceFiosDLLInitialize, sceFiosDLLTerminate, sceFiosDeallocatePassthruFH, sceFiosDebugDumpDH, sceFiosDebugDumpDate, sceFiosDebugDumpError, sceFiosDebugDumpFH, sceFiosDebugDumpOp, sceFiosDelete, sceFiosDeleteSync, sceFiosDirectoryCreate, sceFiosDirectoryCreateSync, sceFiosDirectoryCreateWithMode, sceFiosDirectoryCreateWithModeSync, sceFiosDirectoryDelete, sceFiosDirectoryDeleteSync, sceFiosDirectoryExists, sceFiosDirectoryExistsSync, sceFiosExists, sceFiosExistsSync, sceFiosFHClose, sceFiosFHCloseSync, sceFiosFHGetOpenParams, sceFiosFHGetPath, sceFiosFHGetSize, sceFiosFHOpen, sceFiosFHOpenSync, sceFiosFHOpenWithMode, sceFiosFHOpenWithModeSync, sceFiosFHPread, sceFiosFHPreadSync, sceFiosFHPreadv, sceFiosFHPreadvSync, sceFiosFHPwrite, sceFiosFHPwriteSync, sceFiosFHPwritev, sceFiosFHPwritevSync, sceFiosFHRead, sceFiosFHReadSync, sceFiosFHReadv, sceFiosFHReadvSync, sceFiosFHSeek, sceFiosFHStat, sceFiosFHStatSync, sceFiosFHSync, sceFiosFHSyncSync, sceFiosFHTell, sceFiosFHToFileno, sceFiosFHTruncate, sceFiosFHTruncateSync, sceFiosFHWrite, sceFiosFHWriteSync, sceFiosFHWritev, sceFiosFHWritevSync, sceFiosFileDelete, sceFiosFileDeleteSync, sceFiosFileExists, sceFiosFileExistsSync, sceFiosFileGetSize, sceFiosFileGetSizeSync, sceFiosFileRead, sceFiosFileReadSync, sceFiosFileTruncate, sceFiosFileTruncateSync, sceFiosFileWrite, sceFiosFileWriteSync, sceFiosFilenoToFH, sceFiosGetAllDHs, sceFiosGetAllFHs, sceFiosGetAllOps, sceFiosGetDefaultOpAttr, sceFiosGetGlobalDefaultOpAttr, sceFiosGetSuspendCount, sceFiosGetThreadDefaultOpAttr, sceFiosIOFilterAdd, sceFiosIOFilterCache, sceFiosIOFilterGetInfo, sceFiosIOFilterPsarcDearchiver, sceFiosIOFilterRemove, sceFiosInitialize, sceFiosIsIdle, sceFiosIsInitialized, sceFiosIsSuspended, sceFiosOpCancel, sceFiosOpDelete, sceFiosOpGetActualCount, sceFiosOpGetAttr, sceFiosOpGetBuffer, sceFiosOpGetError, sceFiosOpGetOffset, sceFiosOpGetPath, sceFiosOpGetRequestCount, sceFiosOpIsCancelled, sceFiosOpIsDone, sceFiosOpReschedule, sceFiosOpRescheduleWithPriority, sceFiosOpSetBuffer, sceFiosOpSyncWait, sceFiosOpSyncWaitForIO, sceFiosOpWait, sceFiosOpWaitUntil, sceFiosOverlayAdd, sceFiosOverlayGetInfo, sceFiosOverlayGetList, sceFiosOverlayModify, sceFiosOverlayRemove, sceFiosOverlayResolveSync, sceFiosPathNormalize, sceFiosPathcmp, sceFiosPathncmp, sceFiosPrintTimeStamps, sceFiosPrintf, sceFiosRename, sceFiosRenameSync, sceFiosResolve, sceFiosResolveSync, sceFiosResume, sceFiosSaveTimeStamp, sceFiosSetGlobalDefaultOpAttr, sceFiosSetThreadDefaultOpAttr, sceFiosShutdownAndCancelOps, sceFiosStat, sceFiosStatSync, sceFiosSuspend, sceFiosTerminate, sceFiosTimeGetCurrent, sceFiosTimeIntervalFromNanoseconds, sceFiosTimeIntervalToNanoseconds, sceFiosTraceTimestamp, sceFiosUpdateParameters and sceFiosVprintf](../../../core/libs/prx/libSceFios2/Export.cpp) (libSceFios2) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFios2.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 - -- [sceFiosDateFromComponents, sceFiosDateGetCurrent, sceFiosDateToComponents and sceFiosIsValidHandle](../../../core/libs/prx/libSceFios2/Export.cpp) (libSceFios2) - the date packing and the handle validity rule are unverified and the declarations in Export.cpp still carry no parameters. kalaposfos13/fios2 (`src/fios2.h`, PS4) and RPCS3 (`Emu/PSP2/Modules/sceFios.cpp`) independently declare `sceFiosDateFromComponents(tm*)` and `sceFiosDateGetCurrent()` returning a 64-bit date, `sceFiosDateToComponents(int64_t, tm*)` returning the `tm` pointer and `sceFiosIsValidHandle(int)` returning a bool, but both may descend from one leaked Vita SDK and kalaposfos13's bodies are guesses (`mktime` times 1e9). sonny-tel/libSceFios2 (a header read from a Fortnite build) also declares `sceFiosIsValidHandle(int)` and gives `SceFiosParams`, `SceFiosOpAttr`, `SceFiosOpenParams` and `SceFiosStat` layouts, unverified against firmware +- [sceFiosArchiveGetDecompressorThreadCount, sceFiosArchiveGetMountBufferSize, sceFiosArchiveGetMountBufferSizeSync, sceFiosArchiveMount, sceFiosArchiveMountSync, sceFiosArchiveMountWithOrder, sceFiosArchiveMountWithOrderSync, sceFiosArchiveSetDecompressorThreadCount, sceFiosArchiveUnmount, sceFiosArchiveUnmountSync, sceFiosCacheContainsFileRangeSync, sceFiosCacheContainsFileSync, sceFiosCacheFlushFileRangeSync, sceFiosCacheFlushFileSync, sceFiosCacheFlushSync, sceFiosCachePrefetchFH, sceFiosCachePrefetchFHRange, sceFiosCachePrefetchFHRangeSync, sceFiosCachePrefetchFHSync, sceFiosCachePrefetchFile, sceFiosCachePrefetchFileRange, sceFiosCachePrefetchFileRangeSync, sceFiosCachePrefetchFileSync, sceFiosClearTimeStamps, sceFiosDLLInitialize, sceFiosDLLTerminate, sceFiosDeallocatePassthruFH, sceFiosDebugDumpDH, sceFiosDebugDumpDate, sceFiosDebugDumpError, sceFiosDebugDumpFH, sceFiosDebugDumpOp, sceFiosDirectoryCreateWithMode, sceFiosDirectoryCreateWithModeSync, sceFiosFHToFileno, sceFiosFilenoToFH, sceFiosIOFilterAdd, sceFiosIOFilterCache, sceFiosIOFilterGetInfo, sceFiosIOFilterPsarcDearchiver, sceFiosIOFilterRemove, sceFiosOpReschedule, sceFiosOpRescheduleWithPriority, sceFiosOpSetBuffer, sceFiosOpWaitUntil, sceFiosOverlayAdd, sceFiosOverlayGetInfo, sceFiosOverlayGetList, sceFiosOverlayModify, sceFiosOverlayRemove, sceFiosOverlayResolveSync, sceFiosPathNormalize, sceFiosPathcmp, sceFiosPathncmp, sceFiosPrintTimeStamps, sceFiosPrintf, sceFiosResolve, sceFiosResolveSync, sceFiosSaveTimeStamp, sceFiosTimeGetCurrent, sceFiosTraceTimestamp and sceFiosVprintf](../../../core/libs/prx/libSceFios2/Export.cpp) (libSceFios2) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFios2.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/libSceFont.md b/docs/dev/debt/libSceFont.md index eb223e4d97..4fb91133ae 100644 --- a/docs/dev/debt/libSceFont.md +++ b/docs/dev/debt/libSceFont.md @@ -1,9 +1,17 @@ # libSceFont technical debt +### Silent stubs + +- [sceFontSetFontsOpenMode](../../../core/libs/prx/libSceFont/src/Library.cpp) (libSceFont) - stores nothing: the mode does not change how fonts are opened. It rejects a null or non-library handle and a mode above 2, as the [shadPS4 port](https://github.com/shadps4-emu/shadPS4/blob/b6f0c58dba9ee75b93477703032ae4f1e0de8f1c/src/core/libraries/font/font.cpp#L5796-L5821) does; what each mode does is not measured. Closes when: measured on a PS5 + ### Unknown function info +- [Render surface rows](../../../core/libs/prx/libSceFont/src/Layout.cpp): negative row strides return `SCE_FONT_ERROR_NO_SUPPORT_SURFACE` instead of writing before the buffer as the firmware 13.42 modules did in [BunnyFl's native x86-64 harness](https://github.com/boykopovar/AnyPS5/pull/8250#issuecomment-6417381846), not on console hardware. Nonnegative strides address each row at `row * stride`, including overlapping rows and stride 0; `(height - 1) * stride + width * pixelSize` bounds the addressable range of a nonempty surface, but the API supplies no allocation length to verify the caller's storage. +- [Four-byte render pixels](../../../core/libs/prx/libSceFont/src/Layout.cpp): the host repeats coverage into four bytes per pixel. The same firmware 13.42 report retained four-byte surface/output metadata but wrote one-byte coverage rows; the general raster format and its selection remain unrecovered. -- [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. + + +- [sceFontRenderCharGlyphImage, sceFontRenderCharGlyphImageHorizontal, sceFontRenderCharGlyphImageVertical](../../../core/libs/prx/libSceFont/src/Layout.cpp) (libSceFont) accept a surface whose width or height is 0, with a nonnegative row stride, and return the glyph metrics and an empty update rectangle without writing to the surface. A negative width, height or stride, a null buffer and a pixel size other than 1 or 4 bytes still return `SCE_FONT_ERROR_NO_SUPPORT_SURFACE`; the null-buffer, negative-dimension and unsupported-pixel-size errors 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. The 40 additional exports below have unknown signatures and behavior. Their parameterless `int` declarations are import-resolution placeholders, not confirmed ABI signatures; every call throws through `NotImplemented_nid_no_patch`. Only names and NIDs are established by the [13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/bfec40deb4c47a17d7603ee2bc79aa6862c0c56b/data/firmware-13.60/libscefont-63ba420c2e.jsonl). They have not been measured on a console. @@ -27,10 +35,12 @@ The 40 additional exports below have unknown signatures and behavior. Their para - [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. +- [sceFontWritingGetRenderMetrics, sceFontWritingSetMaskInvisible](../../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - not measured on a console, and what masking format characters changes when a writing is rendered is unknown: the metrics zeroed for a null writing, the accepted masks 0 and 1 and `SCE_FONT_ERROR_INVALID_PARAMETER` for other masks follow the shadPS4 port ([metrics](https://github.com/shadps4-emu/shadPS4/blob/b6f0c58dba9ee75b93477703032ae4f1e0de8f1c/src/core/libraries/font/font.cpp#L6943-L6973), [mask](https://github.com/shadps4-emu/shadPS4/blob/b6f0c58dba9ee75b93477703032ae4f1e0de8f1c/src/core/libraries/font/font.cpp#L7157-L7182)). Closes when: measured on a PS5 - [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 +- [sceFontCharacterGetTextFontCode, sceFontCharacterGetTextOrder](../../../core/libs/prx/libSceFont/src/Text.cpp) (libSceFont) - not measured on a console: the null-argument errors, the outputs cleared for a null character and the returned font, code and order follow the [shadPS4 port](https://github.com/shadps4-emu/shadPS4/blob/b6f0c58dba9ee75b93477703032ae4f1e0de8f1c/src/core/libraries/font/font.cpp#L793-L828). Closes when: measured on a PS5 - [sceFontGraphicsDrawupFillTextureImageObject, sceFontGraphicsDrawupFillTexturePatternObject, sceFontGraphicsDrawupFillFlatColor, sceFontGraphicsDrawupFillLinearGradient, sceFontGraphicsDrawupFillRadialGradient](../../../core/libs/prx/libSceFont/src/Graphics.cpp) (libSceFont) - disassembled from PS5 4.03 `libSceFont.sprx` (SHA-256 `3c67846f50ed3bf844efdc274f83c7f0043581b607154f3946499b72a3bfa086`): bodies `0x1f940` (image), `0x20f50` (pattern), `0x20cb0` (flat), gradient producers `0x1fc10`/`0x20080`, allocator `0x1bcc0`, pool init `0x1bc40` (sole `scan` write, `=nodes`), texture callers in PPSA20397 `libSceFontGsm.prx`; not console-measured. - Disassembled: magic `0xf53`, errors `0x80460084`/`0x804600a9`/`0x80460002`/`0x80460001`/`0x8046005c`/`0x804600a2`, error paths storing at frame `+4` and invoking the registered callback (sealed included), pool/bitmap/queue mechanics, record layouts and tables, `mode` as `memcpy` size, `dimension-1` bytes, `option1` flags `{0x1000, 0x3000, 0x4000, 0x6000, 0x2000}` (jump table `0x37fcc`), allocator marking slots `start..start+count` but returning `memory + completingNodeIndex*slotSize`, the fills' packed-color and float fields. @@ -42,6 +52,7 @@ The 40 additional exports below have unknown signatures and behavior. Their para - [sceFontDefineAttribute, sceFontGetAttribute](../../../core/libs/prx/libSceFont/src/Style.cpp), [sceFontGlyphDefineAttribute, sceFontGlyphGetAttribute](../../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - the signatures `(handle, int attribute, int* oldAttribute)` and `(handle, int attribute, int* nowAttribute)`, and the same shape for a glyph, follow the [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Font/SceFont.Engine.cs) bindings, matching the IDA type database of [Orbital](https://github.com/AlexAltea/orbital/blob/master/tools/ida/db_types.json) and [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1249-L1264), not confirmed on a title. Every handle keeps one attribute, an opaque int whose meaning and valid values are unknown: define stores the new value and returns the previous one, and get returns the stored value with its `attribute` argument unused, as in KytyPS5. An attribute never set is 0. A glyph keeps one bit instead: its define accepts only 0x10 and 0x11, returning `SCE_FONT_ERROR_INVALID_PARAMETER` for any other value, stores the value and returns the previous 0x10 or 0x11, which is 0x10 when never set; its get returns 0x10 or 0x11 and ignores its `attribute` argument. The null checks and the invalid-object errors follow the other libSceFont functions and diverge from [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/d2413fc2ebd91d1b7234197c2dd5bd48e1cb6a51/src/libs/libFont.cpp#L1251-L1265), which returns `OK` for a null handle or glyph after writing 0 to the old-attribute pointer: every other handle and glyph entry point in this library reports the invalid object instead, and a title that passes one is already failing. shadPS4 leaves both exports unimplemented, so there is no second implementation to compare. PPSA01325 calls the glyph define with 0x11 on a glyph from `sceFontGenerateCharGlyph` that it deletes unused and draws with `sceFontRenderCharGlyphImageHorizontal`; the three bindings all read the third argument as an out pointer, not a value, so this call sets the glyph's attribute to 0x11. The attribute changes neither shaping nor rendering. - [sceFontGlyphRenderImage, sceFontGlyphRenderImageHorizontal, sceFontGlyphRenderImageVertical](../../../core/libs/prx/libSceFont/src/Render.cpp) (libSceFont) - follow firmware 13.42's module as BunnyFl ran it natively ([comment](https://github.com/boykopovar/AnyPS5/pull/10892#issuecomment-6415399922); one font, 'A', one pixel format, not a console): the glyph's character is drawn as `sceFontRenderCharGlyphImage` and its directional forms draw it, at the glyph's generation scale; the surface's style frame wins over the argument, and a frame that sets no scale keeps that scale. A valid `sceFontGlyphRenderImageVertical` call, and `sceFontGlyphRenderImage` on a vertical font, throw: the module reports advance and stride 40/40 there where the char call gives 90/90 at 40 px, and one case doesn't name the rule. A pointer that isn't a renderer, and `SCE_FONT_ERROR_INVALID_FONT_HANDLE` for a closed owner font, are not measured - [sceFontRebindRenderer](../../../core/libs/prx/libSceFont/src/Open.cpp): follows the shadPS4 port and its return codes are unmeasured: `SCE_FONT_ERROR_INVALID_FONT_HANDLE` for a null handle or a block with a zero handle magic, `SCE_FONT_ERROR_NOT_BOUND_RENDERER` for a font without a renderer, `SCE_FONT_ERROR_FATAL` for a renderer with a wrong magic, and `SCE_FONT_OK` on a bound font, repeated calls included. The `guest_font_rebind_renderer` test pins the invalid handle, unbound and bound cases; the wrong-magic case is not covered. +- [sceFontGetLibrary](../../../core/libs/prx/libSceFont/src/Open.cpp) (libSceFont) - the null-output error and the returned owning library follow the [shadPS4 port](https://github.com/shadps4-emu/shadPS4/blob/b6f0c58dba9ee75b93477703032ae4f1e0de8f1c/src/core/libraries/font/font.cpp#L2039-L2046). The invalid-handle error and the output cleared on it are not in shadPS4 and not measured. Closes when: measured on a PS5 ### Functional diff --git a/docs/dev/debt/libSceFontGs.native.md b/docs/dev/debt/libSceFontGs.native.md index 8fafc9ce1c..08e92e0bda 100644 --- a/docs/dev/debt/libSceFontGs.native.md +++ b/docs/dev/debt/libSceFontGs.native.md @@ -2,4 +2,13 @@ ### Unknown function info -- [6 exports](../../../core/libs/prx/libSceFontGs.native/Export.cpp) (libSceFontGs.native) - every function export of `libSceFontGs.native.sprx` from the firmware 13.60 export table, names from aerolib.csv only where `tools/nid_names.py` `compute_nid` reproduces the NID. Signatures, parameter layouts and behaviour are unknown; every export throws through `NotImplemented_nid_no_patch` +- [sceFontGraphicsAgcSurfaceInit](../../../core/libs/prx/libSceFontGs.native/Export.cpp) (libSceFontGs.native) - still throws through `NotImplemented_nid_no_patch`. The libSceFontGsm.prx body (PPSA13197 unwrapped ELF, VA 0x39e0, size 0x324) validates surface format/attributes, writes a large AGC surface descriptor through a deep helper tree and returns 0x8a6cXXXX family errors; the full object layout it produces is not yet mapped, so no general implementation exists. +- [sceFontSelectGraphicsAgc](../../../core/libs/prx/libSceFontGs.native/Export.cpp) (libSceFontGs.native) - throws for index 0 and returns `nullptr` for other indices. Verified bodies disagree on index 0: the 4.03 `libSceFontGs.native.sprx` body is `xor eax,eax; ret` (VA 0x250; SHA-256 `6c72c3ff5785d6bc68984de885d19a812f98626d45d2909794c31f489926101c`), while the newer `libSceFontGsm.prx` body returns a backend descriptor pointer for index 0 (`3890: 31 c0 / xor eax,eax; 85 ff / test edi,edi; 48 8d 0d 85 07 03 00 / lea rcx,[rip->0x34020]; 48 0f 44 c1 / cmove rax,rcx; c3 / ret`). The repository targets the 13.60 export table, and no 13.60 native body proving `nullptr` is available, so index 0 stays unimplemented until the descriptor table is mapped. The `nullptr` for index != 0 is verified by both bodies. +- [FontAgcSurface producer](../../../core/libs/prx/libSceFontGs.native/Export.cpp) (libSceFontGs.native) - the object consumed by `sceFontGraphicsAgcDrawupFillTextureImage`/`sceFontGraphicsAgcDrawupFillTexturePattern` (tag byte 0x58 at +0x0d, mode at +0x0f, busy flag at +0x1c, fill object at +0x28, initialized flag at +0x38) is produced by an export outside the six bridge functions (the FontGsm `sceFontGraphicsAgcSurfaceInit` path does not set +0x38), so its producer is not yet identified; the layout is firmware-verified from the libSceFontGsm.prx bridge bodies. +- [FontAgcSequence producer](../../../core/libs/prx/libSceFontGs.native/Export.cpp) (libSceFontGs.native) - the sequence object consumed by `sceFontGraphicsSequenceGetAgcCommandsForJump`/`...ForSubmit` (tag 0xf80 at +0x00, lock bit at +0x1b, commands descriptor at +0x28) is produced by `sceFontGraphicsStructureCanvasSequence` in libSceFont, which is itself still a stub; the descriptor layout (+0x28 first stream, +0x30 first size, +0x34 second count, +0x38 second stream) is firmware-verified. + +### Notes + +- The four drawup/sequence [bridge exports](../../../core/libs/prx/libSceFontGs.native/Export.cpp) are transliterated from the `libSceFontGsm.prx` bodies (PPSA13197 `sce_module/`, SHA-256 `0c7ebf7f36aba76df09cba3509bf91e4a716c58963ce3f70198eeadec3527cd9`): `XKfh4naaANE` @0x3890 (select), `PwZEP8ZBK2Y` @0x38a0 (image), `z-m061OoEqQ` @0x3950 (pattern), `qEGeDXvL3fc` @0x39e0 (surface-init), `6zxuBlsGF0I` @0x3d10 (submit), `CeSI7C4ihjo` @0x3dc0 (jump). The image bridge head — `38a0: mov eax,0x80460002; test rdx,rdx; je; movq [rdx],0`, then `cmpb [rsi+0xd],0x58`, `cmpq [rsi],0`, `cmpl [rsi+0x38],1` — matches the implemented tag/head/initialized ordering. +- The drawup [bridges](../../../core/libs/prx/libSceFontGs.native/Export.cpp) forward to `sceFontGraphicsDrawupFillTextureImageObject`/`...PatternObject` in `core/libs/prx/libSceFont/src/Graphics.cpp` through the same export boundary firmware uses (FontGsm PLT imports `CfkpBe2CqBQ`/`FL0unhGcFvI` from `libSceFont-module`). +- Nothing in PPSA13197 imports the six [bridge](../../../core/libs/prx/libSceFontGs.native/Export.cpp) NIDs: they appear only in the bundled `libSceFontGsm.prx`'s own export table (module tag `#F` = `libSceFontGsm` itself; its import table lists only `libSceLibcInternal`, `libSceFont-module`, `libSceAgcDriver`, `libSceAgc`). No guest module binds them against `libSceFontGs.native` in this title; the exports are implemented here because `libSceFontGs.native` is the canonical provider module for these NIDs in the 13.60 inventory. diff --git a/docs/dev/debt/libSceFreeType.md b/docs/dev/debt/libSceFreeType.md index 9836e0ec21..33eeb2f08f 100644 --- a/docs/dev/debt/libSceFreeType.md +++ b/docs/dev/debt/libSceFreeType.md @@ -1,5 +1,12 @@ # libSceFreeType technical debt +- [FT_Face_SetUnpatentedHinting](../../../core/libs/prx/libSceFreeType/src/FaceCompat.cpp) (libSceFreeType) - console signature is unverified; the adapter forwards to bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftpatent.c), where the call is deprecated, ignores the value and always returns false +- [FT_Face_CheckTrueTypePatents](../../../core/libs/prx/libSceFreeType/src/FaceCompat.cpp) (libSceFreeType) - console signature is unverified; the adapter forwards to bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftpatent.c), where the call is deprecated and always returns false +- [FT_Get_Font_Format](../../../core/libs/prx/libSceFreeType/src/FaceCompat.cpp) (libSceFreeType) - console signature is unverified; the adapter forwards to bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c). The returned string is static native storage, valid for the life of the process +### Build + +- [FT_Library_Version and FT_Get_TrueType_Engine_Type](../../../core/libs/prx/libSceFreeType/src/BuildInfo.cpp) have no console ABI or build-option verification. They query the native library backing the guest handle, following bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftobjs.c); the version therefore describes that backend, not an identified console release. A null library yields version 0.0.0 and engine type NONE. + ### Unknown function info - [autofitter glyph-to-script-map](../../../core/libs/prx/libSceFreeType/src/ModuleProperties.cpp) assumes the FreeType LP64 face/map carrier and translates the guest face to its host face. The returned 16-bit map borrows host face storage and is valid only while that face is alive; the setter returns the bundled FreeType error. This is an exception to the general unsupported-property note below. Console ABI, glyph classification and firmware behavior are unverified. @@ -27,10 +34,14 @@ - [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_New_Memory and FT_Done_Memory](../../../core/libs/prx/libSceFreeType/src/MemoryRecord.cpp) (libSceFreeType) - console behaviour is unverified. The argument-less creation, callback wiring and record destruction follow [FreeType 2.4.12](https://github.com/freetype/freetype/blob/cecf93ef90c660a8b0b45e5adbbfb5ea443fb6b9/src/base/ftsystem.c#L286-L317) and bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftsystem.c), which agree on this interface; the callbacks wrap the host allocator instead of the console's system layer. Records not created by this adapter are destroyed the same way the bundled source destroys them and their lifetime remains the caller's contract. The matching inventory helper in the distinct physical `libSceFreeTypeOt` provider still throws. + - [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, contour endpoints at or beyond `n_points` or signed 64-bit overflow in scaling, translation or midpoint sums throw; native behavior there is undefined. Callers must supply valid point/tag/contour arrays and the callbacks their contours use. +- [FT_Get_Glyph, FT_Glyph_Copy and FT_Done_Glyph](../../../core/libs/prx/libSceFreeType/src/Glyph.cpp) - console ABI/version, build options and errors are unverified. LP64 layout and ownership follow [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftglyph.c); names/NIDs match [PS5 3.20](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceFreeTypeOt.c). Bitmap copy/transfer follows the host FreeType version; console ownership is unverified. Class pointers stay native and opaque. Outline counts outside native field ranges throw. SVG/custom classes, foreign glyphs, changed library/class pointers and foreign bitmap slots or replaced slot buffers throw. Destruction with replaced owned arrays/buffers or cleared outline ownership throws; restore original storage first. Free glyphs before their library. + - [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_Get_SubGlyph_Info](../../../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). Composite components loaded with `FT_LOAD_NO_RECURSE` are queried through the native slot without exposing its private `subglyphs` pointer; the returned matrix uses signed 64-bit guest fields. Output pointers follow the native API's preconditions. @@ -44,6 +55,8 @@ - [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_New](../../../core/libs/prx/libSceFreeType/src/OutlineStorage.cpp) (libSceFreeType) - console signature, errors and allocator are unverified; the adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) with the guest LP64 outline layout. The arrays are zero-initialised from the host C heap, not from a guest or FreeType allocator, because the guest points are 64 bits wide +- [FT_Outline_Done](../../../core/libs/prx/libSceFreeType/src/OutlineStorage.cpp) (libSceFreeType) - console signature, errors and allocator are unverified; the adapter follows bundled [FreeType 2.13.3](https://github.com/freetype/freetype/blob/42608f77f20749dd6ddc9e0536788eaad70ea4b5/src/base/ftoutln.c) with the guest LP64 outline layout. The point, tag and contour arrays of an outline with the owner flag are released with the host C heap, so a title that fills an owning outline with arrays from another allocator is not supported - [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 diff --git a/docs/dev/debt/libSceFreeTypeOt.md b/docs/dev/debt/libSceFreeTypeOt.md index 8de27f976e..c8deef6008 100644 --- a/docs/dev/debt/libSceFreeTypeOt.md +++ b/docs/dev/debt/libSceFreeTypeOt.md @@ -1,7 +1,7 @@ # libSceFreeTypeOt technical debt -- The FT_Outline_Get_Orientation, FT_Request_Size, FT_Sin, FT_Tan, FT_Vector_Unit, FT_New_Size, FT_Outline_Copy, FT_Outline_Decompose, FT_Stroker_New, FT_Stroker_Done, FT_Stroker_GetCounts, FT_Stroker_BeginSubPath, FT_Stroker_LineTo, FT_Stroker_EndSubPath, FT_Stroker_Set, FT_Stroker_Rewind, FT_Stroker_GetBorderCounts, FT_Stroker_ConicTo and FT_Stroker_CubicTo 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. -- The 73 `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_Glyph_Copy, FT_Outline_Get_Orientation, FT_Request_Size, FT_Sin, FT_Tan, FT_Vector_Unit, FT_New_Size, FT_Outline_Copy, FT_Outline_Decompose, FT_Stroker_New, FT_Stroker_Done, FT_Stroker_GetCounts, FT_Stroker_BeginSubPath, FT_Stroker_LineTo, FT_Stroker_EndSubPath, FT_Stroker_Set, FT_Stroker_Rewind, FT_Stroker_GetBorderCounts, FT_Stroker_ConicTo and FT_Stroker_CubicTo 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. +- The 68 `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. ### 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. diff --git a/docs/dev/debt/libSceFsInternalForVsh.md b/docs/dev/debt/libSceFsInternalForVsh.md index 5ef46ad555..5d42f8c25e 100644 --- a/docs/dev/debt/libSceFsInternalForVsh.md +++ b/docs/dev/debt/libSceFsInternalForVsh.md @@ -7,3 +7,173 @@ ### 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, 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 + +### Additional callable exports + +- [Additional callable exports](../../../core/libs/prx/libSceFsInternalForVsh/Export.cpp) - 163 entries present in both version-labelled images are import-resolution declarations only. None is listed in the pinned public 3.20 catalogue for this library. Neither label independently authenticates retail firmware provenance. Parameter lists, return types and behavior are unverified; parameterless `int` declarations are placeholders, not verified ABI contracts. Every entry throws through `NotImplemented_nid_no_patch`. Unidentified names use literal NID aliases. No runtime functionality is implemented. Data exports require separate layout and ownership analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). + +| Name | NID | +| --- | --- | +| `sceFsBfsAllocateAdditionalContents` | `gyqYJnZGUeg` | +| `sceFsBfsAllocateGameImage` | `Hwr6oTQrHYA` | +| `sceFsBfsAllocatePatchImage` | `dRB3R2dK7lM` | +| `sceFsBfsAllocateSaveData` | `eLrSG2GpWfU` | +| `sceFsBfsAllocateTrophyData` | `pG3cfObys9w` | +| `sceFsBfsApplyPatch` | `qtWlhnEzWqs` | +| `sceFsBfsCancelPatch` | `VOFWv+ZZgpc` | +| `sceFsBfsCommitPatch` | `OCV7fw1WFHY` | +| `sceFsBfsContainerBind` | `U0Kb1beVNHk` | +| `sceFsBfsContainerCreate` | `Zr6b1pGvea0` | +| `sceFsBfsContainerQuery` | `zR4vUK1nJxM` | +| `sceFsBfsContainerUnbind` | `pGk-HIsJgPc` | +| `sceFsBfsContainerUpdate` | `7WUxqTxbHH8` | +| `sceFsBfsExtendFile` | `eVZX55IE8Qw` | +| `sceFsBfsFormat` | `NdmkyL4E+6A` | +| `sceFsBfsFsck` | `YaWCv3JUFdM` | +| `sceFsBfsGcGetStatus` | `j8aGZfdbhc8` | +| `sceFsBfsGcMove` | `hLLe9yb4CI8` | +| `sceFsBfsGetAvailableSize` | `MSQ1n+tzSEA` | +| `sceFsBfsGetBindStatus` | `MqHkPiPliiQ` | +| `sceFsBfsGetContainers` | `mw0lr-KcAVM` | +| `sceFsBfsGetFileSize` | `07w8uSyyDgo` | +| `sceFsBfsGetMapping` | `fOZZcF1L-4I` | +| `sceFsBfsGetSCFStatus` | `EV39PBp42ss` | +| `sceFsBfsGetStatus` | `A--9Ydn8BOU` | +| `sceFsBfsM2Format` | `rPXZMK+AG0I` | +| `sceFsBfsMount` | `geq37pht+aU` | +| `sceFsBfsTrim` | `B2Waw07963I` | +| `sceFsBfsUnprotect` | `oNxcT+bbrd8` | +| `sceFsCreatePprPfsSaveDataImage` | `koGUBftreVg` | +| `sceFsCreateWorkspace` | `vjXtNys5x6M` | +| `sceFsDeleteWorkspace` | `Yola1NbNquE` | +| `sceFsDevpfsAllocateFile` | `A8RiikwWk-U` | +| `sceFsDevpfsAllocateSparseFile` | `OD0v8qAhYWo` | +| `sceFsDevpfsMountForVSH` | `nLkSa8Hcik0` | +| `sceFsDevpfsSystemPowerEvent` | `d+s2iU87ZzI` | +| `sceFsDevpfsTermFetchMetaEntryCtx` | `73RHx-09jRA` | +| `sceFsDevpfsUmountForFSMP` | `8x7XaKzCy-A` | +| `sceFsDevpfsUmountForVSH` | `bMPogEwPp+g` | +| `sceFsDevpfsWriteChunk` | `sCqqQQYUiK8` | +| `sceFsDrfsDestroyContext` | `UUgb-PXhk2Q` | +| `sceFsDrfsDisconnect` | `KNujEf-sWXY` | +| `sceFsDrfsInitContext` | `ajihfYkZJ6I` | +| `sceFsDrfsMount` | `igEsGj+Ys5o` | +| `sceFsDrfsSuspendSocket` | `G+qmQeVtF9Q` | +| `sceFsDrfsUmount` | `1UNNe8ym0-A` | +| `sceFsDrfsUnregisterSocket` | `f-AL1VFNUAo` | +| `sceFsDrfsWaitEvent` | `9yYkQ0ZT4+s` | +| `sceFsFetchWorkspaceFileMetadata` | `pf-lA0kywn4` | +| `sceFsInitCreatePprPfsSaveDataOpt` | `ns-+qa8Pd5c` | +| `sceFsInitCreateWorkspaceOpt` | `G+lXWFmyIrc` | +| `sceFsInitDeleteWorkspaceOpt` | `tAnh6QH6lR0` | +| `sceFsInitMountWorkspaceOpt` | `vjXmWuIa46I` | +| `sceFsInitUmountWorkspaceOpt` | `HSLfRvvCFJ8` | +| `sceFsIsActiveBankSystemEx` | `ZIXa1o3EEao` | +| `sceFsIsActiveBankSystem` | `d3BVR4cnRxo` | +| `sceFsM2StorageCheckCleanFlag` | `nnQ-729CENw` | +| `sceFsM2StorageDeconfigure` | `vYKMA1Eyvks` | +| `sceFsM2StorageFsckWithMM` | `Q3Dx5zpjZds` | +| `sceFsM2StorageGetM2UniqueId` | `e6RvxzRHg0g` | +| `sceFsM2StorageInitialize` | `afZqNcRuBfg` | +| `sceFsM2StorageMount` | `3gF9wsB0078` | +| `sceFsM2StorageUnmount` | `5QhQmY1NL-Y` | +| `sceFsM2StorageVerifyMetadata` | `V6i6yMSwqo4` | +| `sceFsMountPprSaveData` | `rFjyPw4dCiw` | +| `sceFsMountWorkspace` | `WB69f40fP8s` | +| `sceFsNewfsDefault` | `Ggp-Q5xJa20` | +| `sceFsNsfsMount` | `Qjoa0WfIKfs` | +| `sceFsPartitionSwitchBank` | `90NDXvGAt2E` | +| `sceFsPathMappingResolve` | `LRhP2sMzWKA` | +| `sceFsPlaygoDisable` | `WvQjGuJgbDQ` | +| `sceFsPlaygoEnable` | `ELZxyxIOpuM` | +| `sceFsPlaygoInitializeParams` | `5WGkFjeeTy0` | +| `sceFsPlaygoSetParams` | `JB6h0wNjfXE` | +| `sceFsTransactionCommit` | `sSqVfkgfjFs` | +| `sceFsTransactionPprNoCowSaveDataMount` | `aOFarTYiGKg` | +| `sceFsTransactionPprSaveDataMount` | `hdK6CFu85Yk` | +| `sceFsTransactionSaveDataReadParam` | `3ebHTSYhPZY` | +| `sceFsTransactionSaveDataUmount` | `8X8J-jiHTn0` | +| `sceFsTransactionSaveDataWriteParam` | `CBUzOsP0eS8` | +| `sceFsUfsCoerceCryptMode` | `6HcKx4a0alU` | +| `sceFsUfsMkfsProsperoApplicationTemporaryData` | `pMwPp537VNk` | +| `sceFsUfsMkfsProsperoDownloadData` | `VFD9HXJT5PY` | +| `sceFsUmountPprPkg` | `S5+bpaH0TWk` | +| `sceFsUpdatePackageMetadataApp0` | `IEepZ-CjqQQ` | +| `sceFsWorkspaceCoreDump` | `XLW5WuNYVfo` | +| `sceFsWsWriteFile` | `DnuQ4XKoF+s` | +| `sceFsWsWriteFileExtdata` | `JTQE83+A0rM` | +| `Unknown` | `+gVdjPejLwc` | +| `Unknown` | `-HW6neCER+A` | +| `Unknown` | `0JhonchgIa8` | +| `Unknown` | `0XlyVxRNeIw` | +| `Unknown` | `1lLh9mukbz0` | +| `Unknown` | `2UgTmJt2dsc` | +| `Unknown` | `3TFUof4RLWM` | +| `Unknown` | `41PqBeun5cU` | +| `Unknown` | `4uqQOvVpISA` | +| `Unknown` | `68F1C7m2pO4` | +| `Unknown` | `6Bku+Im1Pew` | +| `Unknown` | `89zs8+EWQYQ` | +| `Unknown` | `9X3ydcDhbhw` | +| `Unknown` | `AnMWzsLhsYk` | +| `Unknown` | `CFlYeKz+HYs` | +| `Unknown` | `DEHwNbsX2BA` | +| `Unknown` | `DNjr3qYr-lY` | +| `Unknown` | `DSmBrz8enws` | +| `Unknown` | `Elqj7jkJd5s` | +| `Unknown` | `GNeCtmqY-58` | +| `Unknown` | `GsHxp1IXkfA` | +| `Unknown` | `KesAmkrIb9Y` | +| `Unknown` | `KrZEB+TsZ5k` | +| `Unknown` | `LGPQXeAAFdc` | +| `Unknown` | `LrpsS5hGlyY` | +| `Unknown` | `M9342PuS+y0` | +| `Unknown` | `MQs7RB-KfYM` | +| `Unknown` | `N7SnQKgSGA4` | +| `Unknown` | `NHH7572Mh30` | +| `Unknown` | `NUfgeY29b9E` | +| `Unknown` | `NdL2fJF8e78` | +| `Unknown` | `NrSqjlYMetU` | +| `Unknown` | `O6akvoAE6k0` | +| `Unknown` | `P8F9U9uTPqA` | +| `Unknown` | `Rwk7z377rco` | +| `Unknown` | `SyNglOzKCV8` | +| `Unknown` | `T3LiDkpiS7s` | +| `Unknown` | `UD35isX9168` | +| `Unknown` | `W8CmA+wZ9+w` | +| `Unknown` | `WwEWgYYuAmo` | +| `Unknown` | `XwPXO+lxKy0` | +| `Unknown` | `Y-AXyBKGn8I` | +| `Unknown` | `Z9o0nKENoCQ` | +| `Unknown` | `ZcrLUvsmmrU` | +| `Unknown` | `a5ex0HGaufY` | +| `Unknown` | `b5kZJiwZPT8` | +| `Unknown` | `bGBEeJ2B6XE` | +| `Unknown` | `cDZDkhZeK4o` | +| `Unknown` | `cUyUz68FKBc` | +| `Unknown` | `czWRa7s1ogE` | +| `Unknown` | `dKQBYtuMOWA` | +| `Unknown` | `eV9ctY+OiOo` | +| `Unknown` | `g3Lf8TiVEBM` | +| `Unknown` | `gcOITai35ls` | +| `Unknown` | `gerE9e47NoU` | +| `Unknown` | `gtzIEP+e2jo` | +| `Unknown` | `hfb5Y906dYA` | +| `Unknown` | `hhPrQak7oFg` | +| `Unknown` | `iJQ7aolqS1A` | +| `Unknown` | `juY2ugdopus` | +| `Unknown` | `k-WaWvM74jI` | +| `Unknown` | `lZ+oSeAyI8Q` | +| `Unknown` | `lxyJjALNmp4` | +| `Unknown` | `mvNIgoNVeWA` | +| `Unknown` | `nIMoeyl3SRY` | +| `Unknown` | `noVGZAYgjI8` | +| `Unknown` | `oP09BdVEs7o` | +| `Unknown` | `pt+umHHWgvM` | +| `Unknown` | `rOoqs69ugy8` | +| `Unknown` | `rakMRjDRGS4` | +| `Unknown` | `s7X-JxrF650` | +| `Unknown` | `v3jcGxw1mR8` | +| `Unknown` | `v9uqOhwlhjs` | +| `Unknown` | `yadgQseaDKA` | +| `Unknown` | `ydRWehw26Kk` | diff --git a/docs/dev/debt/libSceGLSlimVSH.md b/docs/dev/debt/libSceGLSlimVSH.md index bbfcbe020d..404b16aaba 100644 --- a/docs/dev/debt/libSceGLSlimVSH.md +++ b/docs/dev/debt/libSceGLSlimVSH.md @@ -2,7 +2,32 @@ ### Unknown function info -- The 22 known-name imports in [Export.cpp](../../../core/libs/prx/libSceGLSlimVSH/Export.cpp) are listed in the PS5 3.20 [export inventory](https://github.com/DNNDHH/PS5-3.20_Libs). Their exact prototypes and behavior are unverified; each uses a no-parameter throwing import-resolution placeholder. +- The 22 Sony-specific exports in [Export.cpp](../../../core/libs/prx/libSceGLSlimVSH/Export.cpp) have names recovered by `tools/nid_names.py` from its pinned [NID database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv). Each name hashes to the original explicit NID. Their prototypes and behavior remain unverified; all retain no-parameter throwing placeholders. No recovered name is a standard EGL or GLES2 function. + +| Name | NID | +| --- | --- | +| `glOrbisMapTextureResourceSCE` | `iIsF4NzYgD4` | +| `glOrbisTexImageCanvas2DSCE` | `hA8TWZkwPRU` | +| `glOrbisTexImageResourceSCE` | `LuP1plTAR7g` | +| `glOrbisUnmapTextureResourceSCE` | `J5IG3lL56FI` | +| `scePigletAllocateSystemMemoryEx` | `oXkQEVitkCs` | +| `scePigletAllocateVideoMemoryEx` | `sDotUt1uEhA` | +| `scePigletGetConfigurationVSH` | `3gtNzvkq-XY` | +| `scePigletReleaseSystemMemoryEx` | `T-+xooU3VvQ` | +| `scePigletReleaseVideoMemoryEx` | `AcruVcmKz78` | +| `sceSlimglClientStartIPC` | `QvXbiLqpn6c` | +| `sceSlimglCompositorAllocateIndex` | `mgKTS0PIvd8` | +| `sceSlimglCompositorCreateIndirectRenderTarget` | `RP5cXUjm48U` | +| `sceSlimglCompositorDeleteIndirectRenderTarget` | `srJHx3p48v0` | +| `sceSlimglCompositorFlush` | `6ZQXcSKKjyo` | +| `sceSlimglCompositorFlushWithRepeat` | `wdRBDMIxI7I` | +| `sceSlimglCompositorGetCanvasHandle` | `CzmA-OALrH8` | +| `sceSlimglCompositorGetError` | `aRqvgeH+oe0` | +| `sceSlimglCompositorReleaseIndex` | `gI+XEd5mWPU` | +| `sceSlimglCompositorSetEndOfFrameCommand` | `1TFdFqQBGqY` | +| `sceSlimglCompositorSetIndirectCanvasCommand` | `7Mp6qE235Qc` | +| `sceSlimglCompositorSetIndirectCompositionCommand` | `BrNgn7z6A40` | +| `sceSlimglCompositorSetIndirectRenderTargetConfigCommand` | `iyTnezpw1jE` | ### Additional callable exports @@ -18,6 +43,8 @@ | `sceSlimglIPCEnd` | `Fa0atOcWPaI` | | `sceSlimglIPCSetup` | `F4pQ5tsDo74` | +- [`scePigletSetConfigurationVSH`](../../../core/libs/prx/libSceGLSlimVSH/Export.cpp) (`M9RtXpjSYtE`) is listed in the same 3.20 catalogue and is declared as a no-parameter throwing placeholder; its prototype and behavior are unverified. + ### Additional unidentified exports - Seven [callable exports](../../../core/libs/prx/libSceGLSlimVSH/Export.cpp) are declared as throwing placeholders. Their names, signatures and behavior are unverified. The same module and library identities occur in two version-labelled images; those labels are not authenticated. @@ -31,3 +58,47 @@ | `Unknown` | `Yf0LATiXPsg` | | `Unknown` | `ZLvm5korFRA` | | `Unknown` | `qSuVNKHENqs` | + +### Functional + +- The [host implementation](../../../core/libs/prx/libSceGLSlimVSH/src/Egl.cpp) is not a conformant GLES2 renderer. The single pbuffer configuration advertises `EGL_NON_CONFORMANT_CONFIG` and no `EGL_CONFORMANT` bits. Native window/pixmap integration, OpenVG client-buffer import, GLSL ES compilation, program linking/reflection and rasterization are absent. The 35 standard exports below still call `NotImplemented_nid_no_patch`; no shader or program can acquire a successful compile/link status. Uniform locations and storage require linked shader reflection and remain unavailable. + + - `eglCreatePbufferFromClientBuffer` + - `glCompileShader` + - `glDrawElements` + - `glGetShaderPrecisionFormat` + - `glGetUniformfv` + - `glGetUniformiv` + - `glUniform1fv` + - `glUniform1iv` + - `glUniform2f` + - `glUniform2fv` + - `glUniform2i` + - `glUniform2iv` + - `glUniform3f` + - `glUniform3fv` + - `glUniform3i` + - `glUniform3iv` + - `glUniform4f` + - `glUniform4fv` + - `glUniform4i` + - `glUniform4iv` + - `glUniformMatrix2fv` + - `glUniformMatrix3fv` + - `glValidateProgram` + - `glDrawArrays` + - `glLinkProgram` + - `glShaderBinary` + - `glGetString` + - `glGetAttribLocation` + - `glGetUniformLocation` + - `glGetActiveAttrib` + - `glGetActiveUniform` + - `glUniform1f` + - `glUniform1i` + - `glUniformMatrix4fv` + - `eglCreateWindowSurface` + +- [State queries](../../../core/libs/prx/libSceGLSlimVSH/src/GlState.cpp): `glGetBooleanv`, `glGetFloatv` and `glGetIntegerv` throw for `GL_SHADER_COMPILER`; compiler availability cannot be advertised until a compiler is integrated. Other implemented state queries use host limits, not measured console limits. Calls requiring a GL context throw when none is current; GLES2 does not specify that case. +- [Console-specific ABI](../../../core/libs/prx/libSceGLSlimVSH/include/Api.hpp) behavior and implementation-dependent limits are unmeasured. The host contracts come from [EGL 1.4](https://registry.khronos.org/EGL/specs/eglspec.1.4.pdf) sections 3.1-3.8 and [OpenGL ES 2.0.25](https://registry.khronos.org/OpenGL/specs/es/2.0/es_full_spec_2.0.pdf) sections 2.7-2.10, 3.7, 4 and 6. The backend has one default display, ES2 contexts, a single RGBA8/depth16/stencil8 pbuffer configuration, 2048-pixel image limits and eight vertex attributes and texture units. It supports no compressed texture formats, shader binary formats, EGL texture-binding configurations or extensions. Native-display handles other than the default do not resolve. +- [Mipmap filtering](../../../core/libs/prx/libSceGLSlimVSH/src/Images.cpp) uses a CPU box filter; implementation-dependent rounding and dithering have not been compared with console output. The state/storage implementation does not establish shader execution precision or rendering capabilities; `glGetString` and `glGetShaderPrecisionFormat` remain throwing exports. diff --git a/docs/dev/debt/libSceGnmDriverForNeoMode.md b/docs/dev/debt/libSceGnmDriverForNeoMode.md index d38b5f9ac6..ad90e7dffa 100644 --- a/docs/dev/debt/libSceGnmDriverForNeoMode.md +++ b/docs/dev/debt/libSceGnmDriverForNeoMode.md @@ -2,14 +2,16 @@ ### Unknown function info +- [sceGnmIsDebugResetAvailable (`xMMot887QXE`)](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) - the PS5 firmware 13.60 export table and public NID map identify the export, but its signature and behaviour are unknown; its placeholder uses the no-parameter unknown-signature convention and throws through `NotImplemented_nid_no_patch`. - [sceGnmDingDongForWorkload, sceGnmRegisterOwner](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) - the first throws through `NotImplemented_nid_no_patch` and `sceGnmRegisterOwner` returns `Failure` (0x8EEE00FF) without registering anything; 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. -- [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) - 33 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`. ### 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 - [sceGnmDrawInitToDefaultContextStateInternalCommand](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) (libSceGnmDriverForNeoMode) - answers as `sceGnmDrawInitToDefaultContextState` when the compiled SDK version in the process parameters is below 0x04000000 and as `sceGnmDrawInitToDefaultContextState400` from it, as firmware 13.42's module does, run natively with its imports stubbed, not on a console. The module reads the version at init through `sceKernelGetCompiledSdkVersion`. From 0x04000000 its result and output equal `...State400` for every size tried ([run](https://github.com/boykopovar/AnyPS5/pull/10701#issuecomment-6403613914)). Below it, size 0x20 writes 19 words byte-identical to the module's own `sceGnmDrawInitToDefaultContextState`, from a copy of that sequence, and other sizes write nothing ([@BunnyFl's comparison](https://github.com/boykopovar/AnyPS5/pull/13712#issuecomment-6543609746)). The version is read at each call here, not cached at init +- [sceGnmDrawInitToDefaultContextStateInternalSize](../../../core/libs/prx/libSceGnmDriverForNeoMode/Export.cpp) (libSceGnmDriverForNeoMode) - not called in the native firmware 13.42 runs and not measured on a console; returning `0x20` below compiled SDK `0x04000000` and `0x100` from `0x04000000` follows [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/2183807e592b36d4440384878165b43d930c1dee/src/core/libraries/gnmdriver/gnmdriver.cpp#L2986-L2992) and matches the command sizes of `sceGnmDrawInitToDefaultContextStateInternalCommand`, reading the compiled SDK version on each call rather than caching at init - [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 diff --git a/docs/dev/debt/libSceGpuTrace.md b/docs/dev/debt/libSceGpuTrace.md new file mode 100644 index 0000000000..fa25c339f5 --- /dev/null +++ b/docs/dev/debt/libSceGpuTrace.md @@ -0,0 +1,14 @@ +# libSceGpuTrace technical debt + +### Unknown function info + +The 8 exports in [Export.cpp](../../../core/libs/prx/libSceGpuTrace/Export.cpp) come from the `libSceGpuTrace` list in [claimore22/ps5rs](https://github.com/claimore22/ps5rs/blob/5f252a658ff470bcb2b6cd15205fd8ca64ae4477/data/stubs/by_library/libSceGpuTrace_nosubmission.txt) (commit 5f252a658ff470bcb2b6cd15205fd8ca64ae4477), a catalogue of PS5 symbols per library whose provenance the project does not document, and no PS5 export inventory of this module is available to confirm the list is complete or exact. Every name was kept only because its hash equals the listed NID. Prototypes and behaviour are unverified: every function takes no parameters in the import-resolution convention and throws through `NotImplemented_nid_no_patch`. + +- [sceGpuTraceParametersSetDuration](../../../core/libs/prx/libSceGpuTrace/Export.cpp) (libSceGpuTrace) - unknown signature (NID GLFNgsekcJU) +- [sceGpuTraceParametersInit](../../../core/libs/prx/libSceGpuTrace/Export.cpp) (libSceGpuTrace) - unknown signature (NID IQl1M8Gynsw) +- [sceGpuTraceParametersSetMemorySize](../../../core/libs/prx/libSceGpuTrace/Export.cpp) (libSceGpuTrace) - unknown signature (NID QDMpdglDPoo) +- [sceGpuTraceParametersSetGroup](../../../core/libs/prx/libSceGpuTrace/Export.cpp) (libSceGpuTrace) - unknown signature (NID TehfTHUiaag) +- [sceGpuTraceStart](../../../core/libs/prx/libSceGpuTrace/Export.cpp) (libSceGpuTrace) - unknown signature (NID WpNcYUoJe74) +- [sceGpuTraceStop](../../../core/libs/prx/libSceGpuTrace/Export.cpp) (libSceGpuTrace) - unknown signature (NID rxXvAxEbq8I) +- [sceGpuTraceMonitorTrigger](../../../core/libs/prx/libSceGpuTrace/Export.cpp) (libSceGpuTrace) - unknown signature (NID t+2V-Erb8Bg) +- [sceGpuTraceCancel](../../../core/libs/prx/libSceGpuTrace/Export.cpp) (libSceGpuTrace) - unknown signature (NID ubB+tVwd4-U) diff --git a/docs/dev/debt/libSceHmd2.md b/docs/dev/debt/libSceHmd2.md index c453ebbed2..48662531da 100644 --- a/docs/dev/debt/libSceHmd2.md +++ b/docs/dev/debt/libSceHmd2.md @@ -20,13 +20,17 @@ ### Silent stubs -- [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. +- [libSceHmd2](../../../core/libs/prx/libSceHmd2/Export.cpp) has no PS VR2 runtime: Initialize returns unsupported-feature, so normal calls never reach initialized device behavior. The reprojection session exports below answer the measured no-session behaviour; the remaining reprojection and gaze operations retain their throwing bodies. All five size/alignment queries answer measured constants. Device setup, headset tracking and rendering are not implemented. +- the [libSceHmd2 reprojection session](../../../core/libs/prx/libSceHmd2/Export.cpp) follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/libHmd2.cpp)'s state machine with the layouts its static_asserts pin: `sceHmd2ReprojectionQueryBufferSizeAlign` answers the 0x100828-byte, 0x100-aligned requirement a retail PS5's firmware 13.60 reports for every argument while the process's positional setting is unset (measured in this pull request's discussion; KytyPS5's 16-byte state is not what the console answers) and, after `sceHmd2ReprojectionSetAllowPositionalReprojection` records a first argument of n = 1 to 5, the 0x1103000 + n * 0x1000000-byte, 0x1000-aligned requirement a retail PS5's firmware 13.40 answers instead, `sceHmd2ReprojectionSetAllowPositionalReprojection` answering 0 for a first argument of 0 to 5, recording it process-wide without an initialization and answering `PARAMETER_INVALID` (0x8A720009) for 6, 7, 8, 0xFF and 0xFFFFFFFF with the previous setting kept, the console ignoring the setting call's further parameters and the query's own arguments (the restored ABI carries the single mode argument) (measured on a retail PS5's firmware 13.40 in this pull request's discussion, where PPSA23479 and PPSA05666 set 2 before their query and PPSA01317 leaves the setting unset; every measurement was taken without a reprojection session, so what the setting call returns inside a session and whether the query still follows the setting there is not measured - PPSA01317's in-session queries only exercised the unset setting), and the 16-byte, 8-aligned state (output mode, timing in microseconds) the session keeps occupies the start of the 0x100828 buffer, `sceHmd2ReprojectionInitialize` first reads the process's app info on the firmware and answers 0x8A72001F before its arguments when `sceKernelGetAppInfo` fails or the info's nonzero attribute field lacks bit 0x400 (the answer MLB The Show 25 gets on #8645; module read, firmware 13.40); the emulator's app info does not model the attribute fields, so every caller passes the gate. It answers `PARAMETER_NULL` (0x8A720008) for a null param or buffer, throws for an auxiliary-memory buffer (the host has no camera), writes the state into the guest's work buffer with the timing the param carries or the 3000 us default and answers 0, and a second initialization answers `REPROJECTION_ALREADY_INITIALIZED` (0x8A72000C). There is no reprojection stage on the host, so the session is bookkeeping only. `sceHmd2ReprojectionSetTiming` answers reprojection `NOT_INITIALIZED` (0x8A72000B) without a session, `PARAMETER_INVALID` (0x8A720009) below 2000 us and above 7000 us (measured, firmware 13.40 module read; the previous release threw there) and otherwise records the value rounded up to the next hundred microseconds in the work buffer, answering 0 at once when the rounded value equals the current timing; the module forwards `sceVideoOutSetDisplayEventPosition`'s result when the timing changes, which is not modelled until VideoOut grows that export. `sceHmd2ReprojectionEnableVrMode` answers reprojection `NOT_INITIALIZED` without a session, `VIDEO_OUT_ERROR_INVALID_VALUE` (0x80290001) for an output mode other than KytyPS5's 89/91 Hz (0x2000C) and 119/88 Hz (0x2000F), and otherwise `VIDEO_OUT_ERROR_NO_DEVICE` (0x80290018, [KytyPS5 errno.h](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/errno.h)): the console reports no VR runtime, so the state's output mode stays 0. `sceHmd2ReprojectionTerminate` ends the session: it clears the record under the reprojection mutex and answers 0, so afterwards the gated exports answer `NOT_INITIALIZED` again and a new initialization is accepted; this teardown follows the session model, it is not a console measurement, and the positional setting survives it, as it survives a second initialization. The record is a borrow of the guest's work buffer: the guest keeps the buffer for the session's lifetime and frees it after terminating, every read and write of the record happens inside an export call under the mutex, and no export frees guest memory. A guest that frees the buffer while the session is live breaks that contract, and the writes still land in the guest address space at the address the guest gave, where the guest's own allocator owns the memory again, so the guest corrupts its own heap exactly as it would on hardware ### Unknown function info - [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. One title (PPSA29866) disassembled for this entry calls it once (0x2d1e2e) with a user ID from a variable of the title whose origin was not read, type 0, index 0 and a null parameter, stores the result in a field of its own object and tests it for a negative value. Which user it opens for is not shown. - [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 - [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. +- 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) - inside a session on firmware 13.40 (PPSA01317) it answered 0 and wrote 0x68 bytes (zeros to 0x28, four advancing timestamps at 0x28/0x30/0x38/0x40, then zeros) and 0x8A720008 for a null output +- 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 the session does not cover, `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 - [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 @@ -51,14 +55,14 @@ ### 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. One title (PPSA29866) disassembled for this entry calls it once (0x2d2f6c) with the address of a zeroed 16-byte local block and hands the result straight to another function; a branch on the result was not read. -- [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. +- [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 the process's positional setting; its native pair return type is restored, and this branch implements the measured table the reprojection-session entry under Silent stubs records. - [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 - [Hmd2BeforeInitializeParameterNull](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720008` to these 16 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `MNtiJTDOPAU`, `RRy9WmqZfgU` (sceHmd2InternalDecryptData), `uobR4m9lgwc` (sceHmd2InternalGetMirroringMode), `-XPk5KWBYdo` (sceHmd2InternalRelocalizerSetResultData), `J44idh9izZM`, `Ps0wAvYaa1E`, `kLnMR8oQ1MY`, `kMz4rbBxvjo`, `DIU1sFRRaiE` (sceHmd2GazeGetCalibrationData), `ZQvU4vDzZGQ` (sceHmd2GazeSetUserCalibrationData), `TJpyGGP9YEY`, `Dih+Odk+RC0`, `oaqdSJw+HVY`, `m35gtHuanXU`, `CTM7QPLJF9Q` (sceHmd2InternalGetFlipInfo), `+rExHeC9lP0` (sceHmd2GazeObjectSelectionInitialize). 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 - [Hmd2BeforeInitializeParameterInvalid](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a720009` to these 9 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `bBh5bCp9coo` (sceHmd2InternalSetPositionResetIcon), `G3PNDE2VjfY` (sceHmd2InternalSetPowerOff), `nScCT0OPU0o`, `6Q0Ng7OTN9o`, `-fJKh4SnFR0` (sceHmd2InternalSetBudgetMode), `jUA2s-l0npY` (sceHmd2InternalSetForceIcon), `+9XhyAIJVj4`, `eBGK-o6E4RQ` (sceHmd2InternalBindDeviceWithUserId), `MTuFeL9184E`. 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 - [Hmd2BeforeInitializeReprojectionNotInitialized](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a72000b` to these 10 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `d3SEIYTP6wc`, `EVECbTvBezo`, `ciusPdJKjSU`, `DIwqTkcZiJo` (sceHmd2ReprojectionSetUserEventStart), `64ERZWVFBTE` (sceHmd2ReprojectionSetUserEventEnd), `z0+0gXUJWs8` (sceHmd2ReprojectionClearUserEventStart), `rjAd4nCY4DM` (sceHmd2ReprojectionClearUserEventEnd), `Acyg4vTdGm0` (sceHmd2ReprojectionGetCurrentOutput), `aGOWaEeg04I`, `9up+EPhw1No`. 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 -- [sceHmd2ReprojectionEnableMirroring, sceHmd2ReprojectionGetPredictedDisplayTime and sceHmd2ReprojectionSetRenderConfig](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - the answers inside a reprojection session are not implemented (for the last two [measured](https://github.com/boykopovar/AnyPS5/pull/7658#issuecomment-6408246816) in a PPSA01317 session on firmware 13.40), so a session has to gate these calls once `sceHmd2ReprojectionInitialize` can create one. Without a session each returns 0x8a72000b for every argument and writes nothing: the firmware 13.40 module tests the session before its arguments, and a retail PS5 answered so from a payload before `sceHmd2Initialize` with null arguments and with zeroed buffers ([firmware 13.60](https://github.com/boykopovar/AnyPS5/pull/7798#issuecomment-6262489180)) and in a PPSA23479 game process whose arguments were not recorded ([firmware 13.40](https://github.com/boykopovar/AnyPS5/pull/8645#issuecomment-6307024384)) +- [sceHmd2ReprojectionEnableMirroring, sceHmd2ReprojectionGetPredictedDisplayTime and sceHmd2ReprojectionSetRenderConfig](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - inside a reprojection session they throw, except the answers [measured](https://github.com/boykopovar/AnyPS5/pull/7658#issuecomment-6408246816) in a PPSA01317 session on firmware 13.40: GetPredictedDisplayTime with a null output 0x8a720008, SetRenderConfig with a bit outside bits 0 and 32 or a nonzero second or third argument 0x8a720009. The 0x8a720019 GetPredictedDisplayTime answered there without a headset is not returned, because the answer with one is unknown. Without a session each returns 0x8a72000b for every argument and writes nothing, as a retail PS5 answered from a payload before `sceHmd2Initialize` ([firmware 13.60](https://github.com/boykopovar/AnyPS5/pull/7798#issuecomment-6262489180)) - [Hmd2BeforeInitializeError002C](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a72002c` to these 1 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `V1kLQmP-17U` (sceHmd2GazeObjectSelectionTerminate). 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 - [Hmd2BeforeInitializeError1002](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a721002` to these 5 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `2LVD1EC5SYE`, `Bf87AL6zWxU`, `XKt7w1MODFY`, `Y5s22M-qUDA`, `JJKY-BtQJLg`. 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 - [Hmd2BeforeInitializeError1003](../../../core/libs/prx/libSceHmd2/Export.cpp) (libSceHmd2) - before `sceHmd2Initialize` the console answers `0x8a721003` to these 1 exports both with every argument null and with every argument pointing at a zeroed 16 KiB buffer, writing nothing: `9kj9vw8UShQ`. 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 @@ -101,8 +105,8 @@ - [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. +- [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. In a session on firmware 13.40 (PPSA01317) GetPredictedDisplayTime answered 0x8A720019 for frame ids 0, 1 and 0x123456789 and 0x8A720008 for a null output. 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. SetParam and SetParamWithBuffer remain unimplemented. +- [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, and in a session on firmware 13.40 (PPSA01317), (0, 1, 1) and (2, 0, 0) answered 0x8A720009. SetParam answered 0x8029000A for (16, 0, buffer, 0) and 0x80290002 for (0, 0, NULL, 0) in the same session. SetParam and SetParamWithBuffer remain unimplemented. - [Older callable NIDs](../../../core/libs/prx/libSceHmd2/Export.cpp): 7 unidentified exports have unverified names, signatures and behavior. These are throwing import-resolution placeholders; no functionality is implemented. No matching NIDs were found in the later module inventory. diff --git a/docs/dev/debt/libSceHttp.md b/docs/dev/debt/libSceHttp.md index c24ee6b1e0..177bd95d28 100644 --- a/docs/dev/debt/libSceHttp.md +++ b/docs/dev/debt/libSceHttp.md @@ -1,5 +1,7 @@ # libSceHttp technical debt +Sony guest HTTP requests still do not reach the network. The separate `Aps5HttpClient_*_nid_postfix` host client does not implement the Sony request, context, callback, authentication or cookie APIs described below. + ### Silent stubs - [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 @@ -13,6 +15,7 @@ - [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. A value above 1 returns the same error as `sceHttpSetInflateGZIPEnabled` before the init check; otherwise either value is stored in the template, connection or request options, an unknown id returns `ERROR_INVALID_ID`, and `sceHttpGetCookieEnabled` reads the value back. No cookie is kept or sent, as no request reaches the network. The default (disabled) and the order of the checks are unverified on a PS5 - [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. PPSA01494 01.100.002 (at 0x34F6BA6) and PPSA34502 01.010.000 (0x71BB8E, 0x71D463), disassembled for this entry, pass the arguments in that order, the two outputs on the stack. PPSA01494 treats only a negative result as a failure; PPSA34502 treats 0 as one too, so for it an empty value is a missing field, and it compares the value length with its own buffer size before copying +- [sceHttpAbortRequest](../../../core/libs/prx/libSceHttp/Export.cpp) (libSceHttp) - returns 0 for every request ID, before `sceHttpInit` and for an unknown ID too; no request reaches the network, so nothing is aborted. `sceHttpAbortRequestForce` calls it. [GuestHttpOptions.cpp](../../../core/libs/tests/GuestHttpOptions.cpp) tests only a live request, and the commit that added it names no measurement. shadPS4 (PS4, `src/core/libraries/network/http.cpp`, commit 0614ce324be092791b758d79f93d9f1ca5b10a81) returns `BEFORE_INIT` without a context and `INVALID_ID` for an unknown request, and marks a request that is created or sending as aborted; KytyPS5 (PS5, `src/libs/network.cpp`, commit 5e837a9beac82c031868eaad9348d6a48245fb99) returns `INVALID_ID` for an unknown request and otherwise OK without aborting; fpPS4 (PS4, `src/ps4_libscehttp.pas`, commit 04cefd43e6fddd1ab033e7980cd356d14c964905) always returns 0. - [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. @@ -34,6 +37,8 @@ ### Functional +- [Shared host HTTP client](../../../core/libs/prx/libSceHttp/src/Client.cpp) - HTTPS, URL user information, non-ASCII URLs, encoded representations and transfer codings other than chunked remain unsupported and throw. Host DNS is blocking and cannot be cancelled; synchronous opening has no deadline. A successful open eagerly consumes the initial response to establish its size, including chunked or connection-close 200 responses. Every 200 response is buffered in full, including servers that ignore ranges; memory use is not bounded independently of the representation size. Strong ETags or Last-Modified dates at least 60 seconds older than Date are used for If-Range, but absent or later omitted validators cannot establish representation identity across requests. Console timeout and network-error mapping remain unverified. This host helper does not supply proxy, cookie, authentication or Sony HTTP-context integration. + - [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 40867c9d7f..9b00f5c685 100644 --- a/docs/dev/debt/libSceHttp2.md +++ b/docs/dev/debt/libSceHttp2.md @@ -28,7 +28,7 @@ ### Functional - [Default TLS trust](../../../core/libs/prx/libSceHttp2/Transport.cpp) (libSceHttp2) - HTTPS connections also trust the system CA list, `//common/cert/CA_LIST.cer`, served by libkernel from the host trust store, after the title's own libSceSsl certificates. RetroArch (PPSA99169, [updater](https://github.com/mihawk-99/PS5_RetroArch/blob/7268510b12fe7461342e0cc4497c87a0910c1055/src/webui_update.cpp#L302-L312)), PS5LM (PPSA99575) and Porpoise (PPSA99764) send version-3 requests to public `https://` servers with no loaded certificate, verify callback or verification option, which pass the known-CA check only if the console trusts public roots by default. That the console's roots are exactly this file is not measured -- [libSceHttp2 transport](../../../core/libs/prx/libSceHttp2/Transport.cpp) - only synchronous HTTPS requests with version 3 and ALPN `h2` are supported. HTTP/1.x, cleartext HTTP/2, negotiation fallback, connection reuse, multiplexing across requests, CONNECT, response trailers, automatic redirects and authentication challenges throw. Version values, method strings, header overwrite/append modes, status/read/content-length signatures and timeout units follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http2.cs); wire framing and negotiation follow [RFC 9113 sections 3, 5, 6 and 8](https://www.rfc-editor.org/rfc/rfc9113.html). [prosper](https://github.com/mattias800/prosper/blob/b1bec57186a0/prosper/src/hle/net/hle_http2.cpp) instead records the method argument as an integer; the string signature follows SharpProspero. These are public compatibility contracts, not console measurements. +- [libSceHttp2 transport](../../../core/libs/prx/libSceHttp2/Transport.cpp) - only synchronous HTTPS requests with version 3 and ALPN `h2` are supported. HTTP/1.x, cleartext HTTP/2, negotiation fallback, connection reuse, multiplexing across requests, CONNECT, response trailers and authentication challenges throw. Version values, method strings, header overwrite/append modes, status/read/content-length signatures and timeout units follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http2.cs); wire framing and negotiation follow [RFC 9113 sections 3, 5, 6 and 8](https://www.rfc-editor.org/rfc/rfc9113.html). [prosper](https://github.com/mattias800/prosper/blob/b1bec57186a0/prosper/src/hle/net/hle_http2.cpp) instead records the method argument as an integer; the string signature follows SharpProspero. These are public compatibility contracts, not console measurements. - [libSceHttp2 error paths](../../../core/libs/prx/libSceHttp2/Transport.cpp) - cancellation, deadlines, DNS/TLS/protocol failures, malformed inputs, concurrent calls on one request and exhausted connection slots throw because their console error mappings are unknown. Socket connect errors propagate the libSceNet result, following the module-analysis report in [prosper #2894](https://github.com/mattias800/prosper/issues/2894); the report does not establish every exported error path. A request uses one connection; a context's concurrency cap counts connections until EOF, failure or deletion. Native pooling, scheduling, validation order and teardown semantics remain unverified. DNS uses two host workers and a queue of eight; cancellation/deadlines abandon the caller's wait, not libc's lookup. Library unload can wait for a host lookup to return. A resolve retry resubmits a failed lookup with a new timeout; a timed-out lookup isn't retried. The console's retry semantics are unverified. - [libSceHttp2 TLS settings](../../../core/libs/prx/libSceHttp2/Export.cpp) - minimum SSL versions outside 2 to 5, nonnull SSL callbacks and flags outside `0x3d` throw. Supported certificate-check bits follow SharpProspero and use libSceSsl's TLS 1.2 backend and CA context; native defaults and error translation are unverified. Settings on object kinds other than templates/requests throw, except for the request options below and context SSL flags; native object-kind restrictions are otherwise unverified. Settings become immutable when transport is created. Zero timeout values, negative resolve retries and connection-slot wait timeouts throw; a failed lookup is retried up to the set count. Host HTTP allocations are not charged to the context pool, so `sceHttp2GetMemoryPoolStats` reports a live context's pool size with zero peak and current use. PPSA01494, PPSA30839 and PPSA31162 read the three sizes for memory logging without checking the result, and stopped at startup while it threw. - [sceHttp2SetMinSslVersion](../../../core/libs/prx/libSceHttp2/Export.cpp) - libSceHttp2's own range and validation order are not measured: versions 2 (SSL 3.0) to 5 (TLS 1.2) are accepted, the range `sceSslSetMinSslVersion` takes on a 13.40 console ([libSceSsl](libSceSsl.md)). The floor is not kept: libSceSsl negotiates TLS 1.2 only, which meets every accepted floor, so a server limited to versions older than TLS 1.2 fails the handshake whatever the floor; what a lower floor does with such a server on a console is not measured. @@ -37,12 +37,14 @@ - [libSceHttp2 gzip responses](../../../core/libs/prx/libSceHttp2/Transport.cpp) - `sceHttp2SetInflateGZIPEnabled` stores the flag. When it is set and the response carries `content-encoding: gzip`, the transport inflates the body with the vendored zlib before `sceHttp2ReadData` and `sceHttp2GetResponseContentLength` see it, and reports the inflated length; both calls wait for END_STREAM while the enabled response is gzip. The transport neither adds nor inspects `Accept-Encoding`, so the guest supplies that request header and an unsolicited gzip response is inflated too. Whether a console rewrites the header block (Content-Encoding and Content-Length) is unverified, so the serialized headers keep the fields the server sent; other encodings are not inflated, a malformed or truncated stream, a bad checksum or size and concatenated members throw, and the optional gzip header CRC is not verified. - [sceHttp2GetAllResponseHeaders](../../../core/libs/prx/libSceHttp2/Export.cpp) throws: the serialization, size and lifetime of a decoded HTTP/2 header block have not been established. Status and Content-Length fields are decoded; absent Content-Length reports `NotFound`, following SharpProspero. - [libSceHttp2 asynchronous requests](../../../core/libs/prx/libSceHttp2/Export.cpp) - send/read submission and `sceHttp2WaitAsync` throw. The previous offline implementation queued a fabricated network failure and used event type zero; public sources do not establish the completion event types, wait timeout updates, cancellation results or ownership of async buffers. PPSA17221's kqueue layout and PPSA15508's 24-byte wait result establish caller ABI only. No completion or equeue event is manufactured. +- [Host request header](../../../core/libs/prx/libSceHttp2/Transport.cpp) (libSceHttp2) - a `Host` header set with `sceHttp2AddRequestHeader` becomes the request's `:authority` pseudo-header and is not sent as a `Host` field, following the HTTP/1.1-to-HTTP/2 translation in [RFC 9113 section 8.3.1](https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1). The last value set wins. The console's handling of a `Host` that differs from the URL authority and of whitespace in the value is not measured; here whitespace throws. - [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. - [sceHttp2Init](../../../core/libs/prx/libSceHttp2/Export.cpp) - a negative maximum concurrent request count throws, and the pool size is not checked. In a payload process on a PS5 (firmware 12.70), with a 64 KiB pool, a count of 0 returned `0x817B11FE` (invalid value) and a count of 1 returned `0x817B121D` (pool too small). A count of -1 with that pool, and a count of 1 with a 1 MiB pool in a second run, produced no result before the runner's timeout, so their return values and the smallest accepted pool are not measured. Every call with a constant count in the titles checked passes 1 to 32, and every call with a constant pool size passes 256 KiB to 2.75 MiB, the range above the measured 64 KiB pool; the value of the other calls is not known. - [libSceHttp2 automatic proxy configuration](../../../core/libs/prx/libSceHttp2/Export.cpp) - flags 0 and 1 both use direct connections, matching [libSceNetCtl](../../../core/libs/prx/libSceNetCtl/Export.cpp), which reports proxy configuration off, an empty server and port zero. Other flag values throw. This is the host configuration policy, not a console measurement of proxy discovery; PAC/WPAD and configured proxies remain unsupported. The boolean argument and acceptance of 1 by callers are evidenced by [OliSe Player](https://github.com/Marice/OliSePlayer/blob/fb06d7ac7467a50ba1f2ee299dac20e43213d674/src/netxm.c#L73) and [RetroArch](https://github.com/mihawk-99/PS5_RetroArch/blob/7268510b12fe7461342e0cc4497c87a0910c1055/src/webui_update.cpp#L305); call sites do not establish library behavior. -- [libSceHttp2 default trust](../../../core/libs/prx/libSceHttp2/Transport.cpp) - HTTPS currently uses only authorities explicitly loaded into the caller's libSceSsl context; a fresh context cannot validate public servers. Verification remains enabled. Loading host roots is left to the shared per-connection SSL work in [#11134](https://github.com/boykopovar/AnyPS5/pull/11134) and the stacked HTTP2 follow-up [#11145](https://github.com/boykopovar/AnyPS5/pull/11145), rather than adding a second trust-store implementation here. The exact console root set, trust updates and pool accounting remain unverified. This transport alone does not enable stock HTTPS callers that load no roots. + +- [libSceHttp2 redirects](../../../core/libs/prx/libSceHttp2/Transport.cpp) - native caching, callback scheduling, target-reference delivery and method/body rewriting remain unverified. The typed callback, zero-follow/nonzero-refuse behavior and six-redirect cap follow [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Net/Http2.cs#L327-L334), not console measurements. Refusal exposes the received status/body. Method selection follows [RFC 9110 section 15.4](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4); HTTPS reference resolution follows [RFC 3986 section 5.2](https://www.rfc-editor.org/rfc/rfc3986.html#section-5.2). Downgrade to HTTP, duplicate Location fields, invalid callback methods, redirect-limit exhaustion and changing active-request options throw; native error mappings are unknown. ### Additional PS5 3.20 imports (xdCloudy #9485) diff --git a/docs/dev/debt/libSceIcu.md b/docs/dev/debt/libSceIcu.md index 8b1b49669f..8ee68e53ff 100644 --- a/docs/dev/debt/libSceIcu.md +++ b/docs/dev/debt/libSceIcu.md @@ -2,6 +2,8 @@ ### Unknown function info +- The 134 [recovered inventory exports](../../../core/libs/prx/libSceIcu/RecoveredInventory.cpp) have unverified parameters, return types, callback ABIs and behavior. Every entry immediately throws through `NotImplemented_nid_no_patch`; the parameterless `int` declarations are import-resolution placeholders. The [13.60 inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/226383a7fa4b1275d6cebf61eb1c1e7ca7b58bcb/data/firmware-13.60/libsceicu-9790ee85e3.jsonl) establishes only name/NID bindings. The [module map](https://github.com/boykopovar/ps5-nid-map/blob/cc1d586fddab576a520f226da6b2632b031e6d09/data/Modules/libSceIcu.inc) assigns all 134 NIDs to logical `libSceIcu` in `libSceIcu.sprx`. Recovered `ucfpos`, `uset` and `uiter` groups are excluded. Console ABI, behavior and title compatibility remain unverified. + - [ICU export availability](../../../core/libs/prx/libSceIcu/Unescape.cpp) across firmware versions remains unproven. The [PS5 3.20 inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceIcu.c) includes the implemented escape, invariant-character and version helpers. [BunnyFl’s firmware 13.42 report](https://github.com/boykopovar/AnyPS5/pull/10961#issuecomment-6400071863) records `u_getVersion` returning `78.1.0.0` under an emulator, but neither escape function is in the checked native `system_ex/common_ex/lib/libSceIcu.sprx` export table. Only 104 of the 661 inventory NIDs appear among that module’s 299 exports. The exports for compatibility with older firmware are retained; availability and routing across firmware versions remain unproven. ### Older dispatch exports @@ -35,6 +37,14 @@ - [Default-converter string copy helpers](../../../core/libs/prx/libSceIcu/Invariant.cpp) `u_austrcpy` and `u_austrncpy` are unverified on a console and throw `NotImplemented_nid_no_patch` on code units above `0x7f`. Their names and NIDs come from the PS5 3.20 inventory; signatures and ASCII (`0x01`–`0x7f`) behavior follow ICU 78.1's [ustr_cnv.cpp](https://github.com/unicode-org/icu/blob/release-78.1/icu4c/source/common/ustr_cnv.cpp), unchanged since 65.1, without assuming the console's default converter for non-ASCII units. `u_austrncpy` writes a single NUL when the source ends before the count without zero-padding the remainder. Null buffers with `u_austrcpy` or a positive count throw an invalid-argument exception, whereas ICU dereferences in `u_austrcpy` and returns the destination unchanged in `u_austrncpy`. Closes when: compared with the console, including the default converter for non-ASCII units. +- [UTF-16 set searches and tokenization](../../../core/libs/prx/libSceIcu/Tokenize.cpp) have no console verification; ICU versions on other firmware are unverified. Signatures and behavior follow ICU 78.1's [ustring.h](https://github.com/unicode-org/icu/blob/049e0d6a420629ac7db77256987d083a563287b5/icu4c/source/common/unicode/ustring.h) and [ustring.cpp](https://github.com/unicode-org/icu/blob/049e0d6a420629ac7db77256987d083a563287b5/icu4c/source/common/ustring.cpp). `u_strtok_r` preserves that source's one-code-unit delimiter overwrite, including the trailing surrogate left after splitting at a supplementary delimiter. Invalid null inputs and rejected guest-write scopes throw; an initial continuation assignment is retained if a later delimiter write is rejected. Set-search offsets beyond `INT32_MAX` throw. + +- [Numeric platform helpers](../../../core/libs/prx/libSceIcu/Numeric.cpp) are unverified on a console. The 24 `uprv_` IEEE 754, rounding, extreme, overflow and pointer-limit exports follow ICU 78.1 [putil.cpp](https://github.com/unicode-org/icu/blob/release-78.1/icu4c/source/common/putil.cpp) and [umath.cpp](https://github.com/unicode-org/icu/blob/release-78.1/icu4c/source/common/umath.cpp), unchanged since 65.1, assuming the default `IEEE_754` and `U_MAX_PTR` configuration. The 3.20 table has them; firmware 13.42 exports none ([#11013](https://github.com/boykopovar/AnyPS5/pull/11013#issuecomment-6401310351)). The libm wrappers call the guest libc, so their precision is libc's. ICU's quirks are kept: `uprv_trunc` of negative infinity is positive infinity, NaN results are the canonical quiet NaN, `uprv_fmax(0.0, -0.0)` is `-0.0`, and `uprv_round` floors `x + 0.5`. Null `uprv_modf` and overflow result pointers throw; ICU dereferences them. Closes when: compared with the console. + +- [u_getVersion and u_errorName](../../../core/libs/prx/libSceIcu/ErrorName.cpp) follow ICU 78.1, the version firmware 13.42's module reports and exports both for ([#11013](https://github.com/boykopovar/AnyPS5/pull/11013#issuecomment-6401310351)); only `u_getVersion`'s 78.1.0.0 was measured there. `u_errorName` returns the names in 78.1's [utypes.cpp](https://github.com/unicode-org/icu/blob/release-78.1/icu4c/source/common/utypes.cpp), including its `U_BRK_VARIABLE_REDFINITION` spelling, and `[BOGUS UErrorCode]` for every other code; the names were not compared with the console. Both report 78.1 whatever firmware is emulated, although the 3.20 module was ICU 65 to 68 and has no names for the codes added since. Closes when: compared with the console. + +- [Constrained field positions](../../../core/libs/prx/libSceIcu/ConstrainedFieldPosition.cpp) are unverified on a console. The seven `ucfpos_` exports firmware 13.42 has ([#11013](https://github.com/boykopovar/AnyPS5/pull/11013#issuecomment-6401310351)) follow ICU 78.1 [formattedvalue.cpp](https://github.com/unicode-org/icu/blob/release-78.1/icu4c/source/i18n/formattedvalue.cpp) and [capi_helper.h](https://github.com/unicode-org/icu/blob/release-78.1/icu4c/source/common/capi_helper.h). A handle is a host object whose first four bytes hold ICU's magic `0x55434600`; the rest of its layout differs from ICU's. A set error changes nothing, a null handle sets `U_ILLEGAL_ARGUMENT_ERROR` and other memory `U_INVALID_FORMAT_ERROR`; `ucfpos_open` ignores the error unless allocation fails. No export fills a position yet, so the indexes stay 0. Null error pointers and index outputs throw where ICU dereferences them. Closes when: compared with the console. + ### Known export names; ABI prototypes unverified - The 93 `sceIcuUnknown_*_nid_no_patch` and named `*_nid_postfix` stubs in [Export.cpp](../../../core/libs/prx/libSceIcu/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. diff --git a/docs/dev/debt/libSceIme.md b/docs/dev/debt/libSceIme.md index d05f886ca9..53e8e0b915 100644 --- a/docs/dev/debt/libSceIme.md +++ b/docs/dev/debt/libSceIme.md @@ -36,3 +36,5 @@ - [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. +- [sceImeOpen](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) tracks a host composition (SDL_TEXTEDITING, forwarded by [ImeTextInput](../../../core/libs/prx/libSceVideoOut/src/ImeTextInput.cpp)) and never delivers it to the title: the preedit is not in the text, the buffers, the text filter or the events, and the console's delivery of a preedit to a title (a text area of mode Preedit) is not measured. While a composition is active Enter and Escape only discard it, and Backspace, Delete, the arrow keys, Home, End and paste are ignored, assuming an input method takes those keys itself; no real input method was tried on any platform. Closes when: measured with a CJK input method on Linux, Windows and macOS +- Pasting (Ctrl+V, Shift+Insert, Command+V on macOS) into [sceImeOpen](../../../core/libs/prx/libSceIme/Export.cpp) (libSceIme) and [sceImeDialogInit](../../../core/libs/prx/libSceImeDialog/Export.cpp) is a host feature with no console counterpart measured: the clipboard is read only on the key press, converted to UTF-16 with invalid bytes as U+FFFD, controls stripped, line breaks dropped (kept as LF for a multi-line `sceImeOpen` session) and cut to 64 KiB after SDL has already copied the whole text. The panel type and the password option are not modelled by the host, so a numeric or password field accepts what a typed key would; only the title's text filter shapes a paste. Copy and cut are not implemented, as there is no selection diff --git a/docs/dev/debt/libSceImeDialog.md b/docs/dev/debt/libSceImeDialog.md index 1a5297436d..0c2709c190 100644 --- a/docs/dev/debt/libSceImeDialog.md +++ b/docs/dev/debt/libSceImeDialog.md @@ -9,7 +9,7 @@ - [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 when the text is accepted, 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 it is accepted +- [sceImeDialogInit, sceImeDialogGetStatus, sceImeDialogGetResult, sceImeDialogAbort, sceImeDialogTerm](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - statuses (0-2), end statuses (0-2) and `sceImeDialogInit` errors (`0x80bc0001`, `0x80bc0031`, `0x80bc0016`, `0x80bc0017`) follow [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/b2d7ec7d31f9ad9426db93afe079db8679b3e1e0/src/core/libraries/ime/ime_dialog.cpp); unmeasured on PS5. In `sceImeDialogGetResult`, returning `0x80bc0002` for null differs from shadPS4 (`0x80bc0031`), and for non-null unmapped pointer is an unmeasured host-safety fallback. Abort or term on a non-finished dialog returns `0x80bc0002` (shadPS4: `0x80bc0105`-`0x80bc0107`), and `sceImeDialogTerm` closes running dialogs. Accepted text writes to buffer once - [Pad capture](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - while the dialog's keyboard holds the controller, every pad read of every port ([PadState](../../../core/libs/prx/libScePad/src/PadState.cpp)) returns a neutral sample: buttons `0x80000000` and nothing else, sticks centred, triggers released, no touch and no motion, as [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/30f4170896ce974353a6f6486d0ecec179aec54b/src/core/libraries/pad/pad.cpp#L376-L389) (PS4) reports its `Intercepted` bit. Neither the bit nor the rest of the sample is measured on a PS5. Buttons still held when the keyboard closes stay hidden from the title until each is released. Closes when: measured on a PS5 - [sceImeDialogGetResult, sceImeDialogTerm](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - while the keyboard is open, `sceImeDialogGetResult` returns `0x80bc0002` and `sceImeDialogTerm` closes the keyboard and returns 0. [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/30f4170896ce974353a6f6486d0ecec179aec54b/src/core/libraries/ime/ime_dialog.cpp#L933-L939) (PS4) returns `DIALOG_NOT_FINISHED` (`0x80bc0106`) from both and keeps its keyboard open ([Term](https://github.com/shadps4-emu/shadPS4/blob/30f4170896ce974353a6f6486d0ecec179aec54b/src/core/libraries/ime/ime_dialog.cpp#L1331-L1334)). Neither is measured on a PS5. Closes when: measured on a PS5 @@ -21,3 +21,4 @@ - [Keyboard redraw](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) - the keyboard is drawn into the frames the title flips, from the dialog's state at each flip ([VideoOutDriver](../../../core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp)). A title that stops flipping while the dialog runs shows the last keyboard it flipped, and typing changes nothing on screen until it flips again. Closes when: a title is seen to wait on the dialog without flipping - [Held-button repeat](../../../core/libs/prx/libSceImeDialog/src/PadKeyboard.cpp) (libSceImeDialog) - held Cross (on a character, Space, Backspace or arrow key), D-pad, Square, L1 and R1 repeat after 0.275 s and then every 0.050 s, Dear ImGui's `ImGuiIO` defaults `KeyRepeatDelay` and `KeyRepeatRate`, which [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/30f4170896ce974353a6f6486d0ecec179aec54b/src/core/libraries/ime/ime_ui_shared.cpp#L756-L757) (PS4) reads. The left stick repeats on shadPS4's own tilt-scaled timings. None of these is measured on a PS5. Closes when: measured on a PS5 - [Keyboard extras](../../../core/libs/prx/libSceImeDialog/src/PadKeyboard.cpp) (libSceImeDialog) - the language switch, `...` menu and R3 settings keys of [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/30f4170896ce974353a6f6486d0ecec179aec54b/src/core/libraries/ime/ime_kb_layout.cpp#L490-L492) (PS4) are left as empty cells, and there is no R3 control mode, no right-stick panel drag, no prediction pane, no top close panel ([kTopPanelDefaultElements](https://github.com/shadps4-emu/shadPS4/blob/30f4170896ce974353a6f6486d0ecec179aec54b/src/core/libraries/ime/ime_kb_layout.cpp#L966-L969)) and no copy or paste. Glyphs outside the pages, such as CJK text, are drawn as a box +- [sceImeDialogInit](../../../core/libs/prx/libSceImeDialog/Export.cpp) (libSceImeDialog) shows a host composition at the caret in the keyboard text field, underlined with the selected part underlined twice, and keeps it out of the buffer the title reads. The overlay font has no CJK glyphs, so a composition and committed CJK text draw as the replacement box, which shows the length but not the characters. Closes when: the overlay gets a font with CJK coverage diff --git a/docs/dev/debt/libSceIpmi.md b/docs/dev/debt/libSceIpmi.md index e60923e470..fa4b20dfcd 100644 --- a/docs/dev/debt/libSceIpmi.md +++ b/docs/dev/debt/libSceIpmi.md @@ -8,7 +8,7 @@ ### Known export names; ABI prototypes unverified -- 40 pending exports in [Export.cpp](../../../core/libs/prx/libSceIpmi/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. +- 40 pending exports in [Export.cpp](../../../core/libs/prx/libSceIpmi/Export.cpp) are present in the [PS5-3.20_Libs](https://github.com/DNNDHH/PS5-3.20_Libs) inventory. Each is named after the mangled symbol the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) gives for its NID, and the NID of that name matches. Their prototypes and behavior are unverified. They use the no-parameter import-resolution convention and throw through `NotImplemented_nid_no_patch`; these declarations make no ABI or behavior claim. ### Additional callable exports diff --git a/docs/dev/debt/libSceJpegDec.md b/docs/dev/debt/libSceJpegDec.md index af33d224c8..88110645be 100644 --- a/docs/dev/debt/libSceJpegDec.md +++ b/docs/dev/debt/libSceJpegDec.md @@ -1,10 +1,7 @@ # libSceJpegDec technical debt -### 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 - ### Functional - [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. One title (PPSA29866) disassembled for this entry has one call site (0x2c111d); it passes a null `imageInfo` and a parameter structure with decode mode 0, down-scale 1 and pixel format 0, and hands the result to another function whose handling of a negative result was not read. In the parameter structure built around this call site, mode 1 and formats 9, 10 and 11 do not show up; whether a down-scale of 1 means no scaling was not compared. +- [sceJpegDecDecodeWithInputControl](../../../core/libs/prx/libSceJpegDec/Export.cpp) (libSceJpegDec): decode mode 1, pixel formats 9 to 11, unlisted parse down scale, decoding without parse and decoding at another down scale throw. A stream that ends before the frame header, or cannot form one, returns 0x80650012 (measured on PS5 firmware 13.42 libSceJpegDec.sprx; sceJpegDecParseHeader on the same bytes returns 0x80650010 or 0x80650002); callback call counts differ because the firmware (13.60 disassembly) refills its 2 KiB buffer when under 0x3F0 bytes remain, whereas this implementation pulls in 2048-byte blocks. Streamed bytes reside in host memory; freeing work memory without `sceJpegDecDelete` leaves them allocated. A call reaching a stream another call on the handle is still reading (a decode from its own input callback) throws; the console's answer is unknown. A delete or new parse during a call leaves it reading its own stream. diff --git a/docs/dev/debt/libSceJson2.md b/docs/dev/debt/libSceJson2.md index bb2d163782..e365122622 100644 --- a/docs/dev/debt/libSceJson2.md +++ b/docs/dev/debt/libSceJson2.md @@ -1,84 +1,129 @@ # libSceJson2 technical debt +SDK 5/6 below refer to the application compatibility SDK queried by `sceKernelGetCompiledSdkVersion` (process-parameter offset `0x10`). The PS5 SDK field at `0x14` and the console firmware version are distinct. + ### Unknown function info -- [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, 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. +- [JSON real-number conversion](../../../core/libs/prx/libSceJson2/Export.cpp) - floating-point environment behavior and malformed numeric-token grammar remain unverified. Valid decimal conversion follows the instructions at `0x7750`: separate multiply/add accumulation, per-digit fractional division and repeated exponent scaling. Reference-backed exact-bit tests cover signed underflow, subnormals, normal boundaries, large finite values and overflow across special-float modes. These results intentionally retain the reference rounding, which differs from standard correctly rounded conversion. Formatting still uses locale-independent `to_chars` with 17 significant digits for modern settings; broader formatting equivalence remains unverified. +- [sce::Json::Array::pop_front](../../../core/libs/prx/libSceJson2/Export.cpp) - the console result for an empty array remains unverified; the existing host behavior throws `std::out_of_range`. Missing storage throws unsupported. Nonempty removal releases the allocator-owned node without allocating and preserves surviving element/iterator identity. +- [sce::Json::Parser::parse](../../../core/libs/prx/libSceJson2/Export.cpp) - null text with nonzero size throws; decimal overflow follows the configured real-conversion mode. Numeric-token validation, more complex parser allocation failures and cross-thread operation behavior remain incomplete. Parsing mutates the supplied destination, following `0x6940`, `0x6840` and `0x5d30`: failed arrays/objects retain admitted entries, strings append their decoded prefix, and incomplete scalar literals preserve the previous value. Existing collections retain their entries across later parses. Local tests cover nested parent links, duplicate-member failure, type transitions and actual allocator cleanup; the same contract passes the reference module. These checks do not establish allocator-failure equivalence or Linux/Windows guest execution. +- [sce::Json::InitParameter2, InitParameterRtti and InitParameterRtti2](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - initialization owns its allocator-backed context, default resources, module fallback and kernel synchronization handles. Allocation admission, rollback and retry follow the observed default-resource protocol; file-buffer size is snapshotted and used by file parsing. Remaining kernel-status and callback/concurrent lifecycle paths, plus full guest execution, remain unverified. The active-byte and enum validation rules follow the inspected initializer at `0x920` and `0x12b0`–`0x13f7`, with local allocation-failure coverage. The module firmware version is unverified; these are library observations, not console measurements. +- [sce::Json::Value auxiliary ownership](../../../core/libs/prx/libSceJson2/Export.cpp) - values use the inspected 32-byte inline payload and parent layout, and allocator-backed auxiliary ownership. Outer value handles, string headers and characters, array and object storage and their iterator states use the supplied allocator; module defaults are allocated during initialization, including admitted partial default resources. Request sizes, failure admission and SDK-dependent ownership below SDK 6 remain incomplete. Modern callbacks and SDK 5 mismatch-map copying have reference-backed contracts below; legacy special-float/element registration and full guest execution remain incomplete. +- [InitParameter2 special-float configuration](../../../core/libs/prx/libSceJson2/Export.cpp) follows assignment helpers at `0x40a0` and `0x41a0` and formatting at `0x26a0`: mode 0 converts non-finite numbers to strings, mode 1 to null, and mode 2 retains real values and emits lowercase non-JSON tokens. Parser acceptance of those tokens, floating-point exception flags, signaling-NaN behavior and concurrent reconfiguration remain unverified. Legacy initialization does not enable the conversion; its internal formatter is now reproduced through the inspected float32 conversion, six truncating fractional digits, range saturation and NaN integer result. The regression covers signed zero, precision boundaries, large values, infinities and NaNs; floating-point rounding modes and exception flags remain unverified. `Value::set(double)` and `toString(String&)` expose no consistent return value and are declared `void`. +- [sce::Json::Object::Pair, sce::Json::Array::iterator and sce::Json::Object::iterator members](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - Pair allocator-callback reentry and inherited older-SDK failure scopes still require audit. Both Pair constructors preserve the eight bytes between key and value and inactive Value bytes, following `0xc230` and `0xc280`. Reference tests cover both constructor/destructor aliases, all value tags, independent copies and allocator cleanup. Array and object postfix increment use `this` and the dummy integer argument and advance in place, following `0xb270` and `0xc4a0`; they do not take a hidden result pointer. This ABI is verified against the inspected module, not every firmware version. +- [sce::Json::Value handler registration](../../../core/libs/prx/libSceJson2/Export.cpp) - legacy special-float/element registration and libc-heap failure handling remain incomplete. Modern auxiliary records, fallback values and callback records use the supplied allocator. The inspected setters at `0x39c0`, `0x3b00`, `0x3e90` and `0x3fa0` reject owned children with `0x80848104`. The value null-access setter is a three-argument member returning status, rather than a static callback setter. Legacy mismatch-map storage is covered separately below; other SDK-dependent legacy combinations remain unverified. +- [sce::Json::Value type-mismatch dispatch](../../../core/libs/prx/libSceJson2/Export.cpp) - libc-heap failures and untested legacy callback combinations remain incomplete. Global registration follows `0x1480`; root-local mismatch precedes global null recovery, followed by global mismatch. Current root lookup and borrowed-result dispatch follow `0x22b0` and `0x2930`, including requested type, source, immediate parent, callback precedence and a shared reentry guard. Additional legacy compatibility remains unverified. +- [sce::Json::Value element-access failure dispatch](../../../core/libs/prx/libSceJson2/Export.cpp) - SDK-dependent dispatch remains incomplete. Global registration and fallback selection follow `0x1670` and `0x2c70`. Root-owned fallback identity, reentry suppression and errors `0x80848115` through `0x80848118` follow `0x2c70`, `0x4e80` and `0x5070`. Nested fallback payload failures and compatibility below SDK 6 remain incomplete. +- [sce::Json::Value special-float recovery](../../../core/libs/prx/libSceJson2/Export.cpp) - allocator-backed value failures remain incomplete. Global registration follows `0x14f0`, including legacy-initializer rejection and duplicate registration; root handlers precede global handlers. Root recovery follows `0x2b90`, `0x2d90` and `0x41a0`, including the hidden result object, temporary cloning and teardown, function types 0/1, optional error flag and reentry suppression. SDK-dependent registration below SDK 6, signaling NaNs and floating-point exception flags remain unverified. +- [sce::Json::MemAllocator::notifyError](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the base class's virtual does nothing; whether the console's base implementation logs is unknown. String, array, object, callback metadata and file-buffer allocation failures dispatch the supplied allocator's notifier. +- [sce::Json::String size, capacity, resize, append(const char*, size_t), operator+=](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - headers and characters use the supplied allocator, with the inspected 24-byte header and 32-bit capacity. Constructors, clear, reserve, resize, copy, assignment and append failure contracts follow `0xa100`–`0xb0a0`, `0xde50` and the instructions at `0xdf20`. Overlapping assignment, exact substring temporary allocations, allocator failure propagation through every value/parser operation and native assertion behavior for sizes above 32 bits remain incomplete. SDK-dependent growth reads process parameters; missing metadata uses the reference query-failure rule. Failed host metadata lookup is remembered to avoid repeated allocating exceptions; successful SDK queries remain live. Appending null text with a nonzero length throws instead of dereferencing it. +- [sce::Json::Array::iterator::advance and Object::iterator members](../../../core/libs/prx/libSceJson2/Export.cpp) (libSceJson2) - the source-level return type of `advance` remains unverified; array advancement is declared `void` and tested for its side effects. Object iterator advancement past the end throws instead of asserting. Array iterators expose the inspected node payload address, including the sentinel; missing-state Array dereference/arrow share the initialized 32-byte allocator-owned fallback with keyed lookup. Iterator access allocates nothing and preserves its bytes under allocation failure. +- [sce::Json::Value::operator bool](../../../core/libs/prx/libSceJson2/Export.cpp) - untested legacy callback combinations remain incomplete. Modern root-local mismatch precedes global mismatch; ordinary boolean values bypass both. Root-local mismatch dispatch precedes ordinary conversion, following `0x2100`; the returned value is read through its boolean getter under the shared callback guard. + +- [sce::Json::Parser file input](../../../core/libs/prx/libSceJson2/Export.cpp) - allocator failures outside the file buffer and callback-read failure combinations remain incomplete. Buffer ownership and the read adapter follow `0x69c0` and `0x6b20`; local contract tests substitute the kernel file boundary and do not prove guest filesystem integration on Linux or Windows. +- [sce::Json::Array storage and iterators](../../../core/libs/prx/libSceJson2/Export.cpp) - nested value-copy allocator failures, shared concurrent allocation-operation accounting and legacy parent binding remain incomplete. Header, sentinel, node and iterator allocation follow `0xb2d0`, `0xb410`, `0xb540`, `0xb6f0` and `0xb130`; local contract tests cover scalar/nested copies and partial ownership. Failed node allocation in `push_back` removes the existing last entry, following `0xb880`; this behavior is verified against the inspected module only. Circular traversal follows `0xb250` and `0xb290`; large advances reduce complete cycles without changing the resulting node. `front` on a partial header with no sentinel throws instead of dereferencing null. Full Linux/Windows execution is unverified. + +- [sce::Json::Object storage and assignment](../../../core/libs/prx/libSceJson2/Export.cpp) - legacy ownership, callback-bearing copies and concurrent operation accounting remain incomplete. Allocator-backed tree nodes can retain incomplete entries or a source count exceeding their live nodes after copy failure (`0xca40`, `0xe000`). Modern assignment (`0xd400`) preserves the original on initial allocation failure but can replace it with partial entries after later failures; self-assignment rebuilds the tree. Legacy insertion copies the pair directly. SDK 5 boundary tests do not establish cross-firmware compatibility. Erasure allocates nothing (`0xd350`). Missing-state iterator dereference throws if its temporary default iterator cannot be allocated; the reference can recurse. Full Linux/Windows execution remains unverified. +- [sce::Json::Parser embedded zero characters](../../../core/libs/prx/libSceJson2/Export.cpp) - decoded Unicode zero is discarded through the inspected character-append behavior. Binary string values serialize using their stored length, while object keys serialize their C-string prefix. Broader malformed Unicode handling remains incomplete. + +- [sce::Json::Value handle admission and type changes](../../../core/libs/prx/libSceJson2/Export.cpp) - collection copy/assignment failure ordering, nested recovery failures and older SDK compatibility remain incomplete. The supplied allocator owns each eight-byte complex-value handle. Typed construction and type changes follow `0x580`, `0x10d0`, `0x1170` and `0x1210`: the old payload is destroyed before replacement admission; string/header failure leaves null, an array can retain a header without its sentinel, and object construction reclaims a failed sentinel. Scalar changes preserve inactive payload bytes. Special-float string conversion follows the temporary string at `0x40a0`; failure of its header preserves the prior value, while later failures can leave null or an empty string. Local tests cover SDK 6 process metadata, not every firmware version. + +- [sce::Json::Value auxiliary admission and retry](../../../core/libs/prx/libSceJson2/Export.cpp) - legacy special-float and element registration remain unsupported and throw. The helper at `0x3280` allocates 24 bytes below application SDK 6 (`0x34d1`), while the setters at `0x3e90` and `0x3fa0` read/write handler slots at offsets `0x20` and `0x28` without an SDK check. Those accesses exceed the legacy allocation, so this implementation throws. Modern admission follows `0x3280`: a failed fallback allocation retains the 72-byte auxiliary record; later registration reuses it without recreating the missing fallback. Local tests cover all four callback setters, their allocation failures, replacement and null callbacks, child rejection, parent preservation and teardown through the original allocator. Legacy auxiliary records use 24 bytes; mismatch callbacks use a separate map whose header uses the supplied allocator and whose 56-byte nodes use libc new/delete. + +- [sce::Json::Value scalar and string copying](../../../core/libs/prx/libSceJson2/Export.cpp) - nested collection assignment failures remain incomplete. Scalar and string dispatch follow `0x1e40`: null and boolean copies preserve inactive destination bytes, numeric values retain their exact payloads, and string assignment changes type before copying characters into the existing header. A character-allocation failure can empty an existing string; it does not restore its old contents. Copy construction initializes ownership metadata directly, following `0x1e20`. Local allocator tests cover null, boolean, signed, unsigned, finite real and string sources across all destination types and failure budgets. + +- [sce::Json::Array and Value collection assignment](../../../core/libs/prx/libSceJson2/Export.cpp) - nested allocator failures, legacy callback propagation and concurrent operation accounting remain incomplete. Modern array assignment follows `0xbc80`: initial header/sentinel failure preserves the original, node failures retain a partial replacement and iteration continues, self-assignment replaces the header, and entries bind to the destination owner. Legacy assignment uses replacement list storage inside the existing header, following `0xb540`. Value collection dispatch follows `0x1e40`, changing type before invoking Array/Object assignment rather than cloning through their copy constructors. Local tests cover scalar array assignment, self-assignment and empty collection Value admission across destination types. + +- [sce::Json::Value constructors and setters taking Array/Object](../../../core/libs/prx/libSceJson2/Export.cpp) - nested allocation failures and older SDK compatibility remain incomplete. These paths follow `0x1d00`, `0x1d50`, `0x1d90` and `0x1de0`: a missing source header is ignored, while admitted inputs change the destination type before collection assignment. Missing-header construction initializes null ownership metadata without changing inactive payload bytes. Self-setting retains the Value handle and callback metadata while replacing collection storage. The setters expose no consistent status result and are declared `void`; local tests verify side effects rather than a fabricated success return. + +- [sce::Json::Value nested array allocation regressions](../../../core/libs/prx/libSceJson2/Export.cpp) - local same-source tests cover every allocation point in copying and assigning an array containing a string, integer and inner array containing another string. Persistent and single-allocation failures preserve the observed null entries, empty strings, missing sentinels, parent links, error counts and actual owned cleanup. These checks do not complete nested object failure coverage, callback-return cloning failures, legacy SDK behavior or full guest execution. + +- [sce::Json::Initializer resource admission](../../../core/libs/prx/libSceJson2/Export.cpp) - returned kernel setup/locking errors, recovery-callback termination and active-operation allocator-callback termination remain explicitly unsupported; concurrent/reentrant lifecycle and Linux/Windows integration remain unverified. Reference tests cover SDK 5/6 allocation failures, partial admission, null frees and retry; SDK 6 tests also reject duplicate initialization and inactive termination from allocate/notify/free callbacks while the context remains published. Seventeen host-boundary schedules verify mutex-construction errors/exceptions, balanced guest/attribute/mutex ownership and retry. Setup exceptions reclaim local resources before global rollback. The reference skips creation after attribute errors and ignores mutex-init/destroy results, potentially retaining unwritten handles; safely reproducing those states remains unverified. + +- [sce::Json::Value legacy mismatch map](../../../core/libs/prx/libSceJson2/Export.cpp) - legacy special-float/element registration and libc allocation exceptions remain incomplete. Map registration, lookup, copying and destruction follow `0x3b00`, `0x7230`, `0x22b0`, `0x1e40` and `0x1810`: a parsed root must already own auxiliary metadata; a shared 24-byte map header uses the supplied allocator, while its sentinel and entries use libc new/delete. Copies propagate callback records independently of auxiliary ownership. Empty map storage survives until termination. Local SDK 5 tests cover replacement, null handlers, parent dispatch, null-recovery ordering, swap temporary cleanup, tree balance, erasure and both allocator domains. Sentinel/entry allocation exceptions and retry follow the reference contracts below; broader exception paths remain unverified. + +- [sce::Json::Object nested allocation regressions](../../../core/libs/prx/libSceJson2/Export.cpp) - local tests cover 322 copy-construction and assignment cases for an object containing a string, inner object/string and array. Persistent and single-allocation failure budgets exercise every allocation position through successful completion (50 allocations for construction, 109 for assignment). Reference comparisons cover retained partial contents, collection counts, parent/owner links, request counts, notifications, unchanged source state and allocator cleanup. Construction may retain a source count larger than its admitted nodes and leave copied root entries unbound; tests preserve those observed semantics. Callback-bearing copy failures, alternate SDK behavior and concurrent failure accounting remain unverified. + +- [sce::Json::Parser value admission](../../../core/libs/prx/libSceJson2/Export.cpp) - recognized values change type before root metadata admission, following `0x5d30` and `0x3280`; incomplete scalar tokens allocate no auxiliary record. Twelve reference-backed string cases cover persistent and one-shot failure at every allocation position, retained type, error status, request/notification counts, character retries and cleanup. A failed character allocation can discard the first character while later characters are admitted. More complex parser low-memory paths and callback-driven allocation failures remain incomplete; reference execution terminated during one missing-sentinel array case, so that case does not establish a verified recovery contract. + +- [sce::Json::Parser array allocation failures](../../../core/libs/prx/libSceJson2/Export.cpp) - numeric token storage uses the supplied allocator; array parsing uses the public append implementation. Parser and append operations share the depth/error flag observed at `0x6840`, `0x310`, `0x340` and `0xb880`, retaining inherited errors until the outer operation finishes. Twenty-four reference-backed cases cover persistent and one-shot failures through successful parsing, partial counts, numeric-zero fallback after token-allocation failure, string suffixes, parent links, notifications and cleanup. Missing-sentinel parsing throws unsupported because reference execution crashes; callback-bearing collections and concurrent behavior remain incomplete. + +- [sce::Json::Parser object allocation failures](../../../core/libs/prx/libSceJson2/Export.cpp) - object keys use one allocator-backed string reused between members, following `0x6250`; lookup closes its shared operation scope before assigning the exposed key and owner, following `0xda40`. Sixty-six reference-backed cases cover persistent and one-shot failure at each returning allocation position through a 33-allocation successful parse of an object containing a string and array. Checks include partial keys/members, numeric and string fallback values, parent links, error status, request/notification counts and complete allocator cleanup. The nested array sentinel failure is excluded because reference execution crashes; more complex nested objects, callback-driven allocation failures and concurrency remain unverified. + +- [sce::Json::Collection inherited allocation errors](../../../core/libs/prx/libSceJson2/Export.cpp) - array assignment, object assignment/insertion and legacy mismatch-map updates now use the shared depth/error protocol instead of counter snapshots, following `0xbc80`, `0xd400`, `0xcd30` and `0x3b00`. Six reference-backed public parser/read-callback cases invoke array assignment, object assignment or insertion after a one-shot character allocation failure, plus healthy controls. Checks cover placeholder retention, skipped payload binding, unchanged source, outer error persistence, recovery on the next independent parse and complete cleanup. Baseline and targeted assignment/insertion mutations fail these checks. Callback-return cloning and cross-thread failure propagation remain unverified; legacy map updates under inherited failure are covered separately below. ### Unknown 3.20 import declarations -- [_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. +- [_ZN3sce4Json12MemAllocatorD0Ev](../../../core/libs/prx/libSceJson2/InventoryExports.cpp) (libSceJson2) - NID `ypazoTrKYhE`; the deleting base allocator destructor traps in the reference. This invalid destruction path remains explicitly unsupported. + +- [Counted string searches](../../../core/libs/prx/libSceJson2/Export.cpp) with an admitted positive count and a null needle remain explicitly unsupported; the reference dereferences the invalid pointer. Oversized and zero counts retain their verified results. + +- [sce::Json::Array::erase](../../../core/libs/prx/libSceJson2/Export.cpp) - invalid, missing or sentinel positions in an allocated array remain explicitly unsupported. Reference invalid-iterator paths can corrupt storage or fault; valid erasure retains its mutation when returned-iterator allocation fails. + +- [Array insertion and payload transfer](../../../core/libs/prx/libSceJson2/Export.cpp) - nested copy-stage failures, missing-sentinel paths and older-SDK callback combinations still need direct regression coverage. String-copy failures and successful nested-array transfer follow the reference protocol. Forty-eight additional reference-backed cases reject every temporary iterator allocation during transfer of three-element nested arrays and objects, using persistent and one-shot failure schedules. Insertion retains the new node and rebinds only the admitted child prefix; prepending removes the new entry on failure. Checks cover exact requests/notifications, surviving iterator identity, existing destination order, unchanged source bytes and allocation generations, result identity, ordered object keys, nested links/counts and complete cleanup. Invalid insertion iterators are explicitly unsupported. + +- [Value keyed lookup](../../../core/libs/prx/libSceJson2/Export.cpp) - valid string-key overloads and C-string key-construction failures follow the observed iterator allocation and fallback protocol. Missing object handles, corrupted tree storage, lifecycle reentry during lookup and remaining older-SDK callback combinations remain unverified; invalid reference arguments are explicitly unsupported. + +- [Value callback serialization](../../../core/libs/prx/libSceJson2/Export.cpp) - nested iterator/metadata failures, non-finite recovery failures, lifecycle reentry, other legacy callback combinations and floating-point environment behavior still require audit. Null callback/output references are explicitly unsupported. All serialization shares the collecting/traversal protocol. Reference tests cover modern chunk boundaries, callback results, output mutation, scalar allocation failures and opening-callback array mutations. SDK 5 tests cover root admission/reset, child metadata release, numeric edge cases and persistent/one-shot root admission failures. + +- [Parser parent and auxiliary transitions](../../../core/libs/prx/libSceJson2/Export.cpp) - allocator-driven lifecycle changes, more complex nested metadata failures and alternate SDK combinations still require audit. Six notifier-reentry cases cover failure publication after notification, nested failures, callback-return copying and retry. Reader-driven allocation failures are checked before starting a value, following `0x5d30`; an already-started literal still completes, while outermost completion reports and clears the shared failure. Reference tests cover direct parsing into an owned child across eight value forms, six root-admission failure/retry cases, incomplete-literal preservation, modern root callback retention and nested release of three handler records, fallback and auxiliary in native order. Allocation generations distinguish release from later address reuse. + +- [Special-float callback-return allocation](../../../core/libs/prx/libSceJson2/Export.cpp) - more complex object shapes, allocator-driven lifecycle changes and alternate SDK combinations remain unverified. Twelve reader/notifier-reentry cases verify delayed failure publication, inherited nested failures, string/object/nested-array callback returns, literal completion, request-order fingerprints and retry. Reference tests cover 834 persistent/one-shot cases through successful string, one-entry object and nested array/object recovery for assignment and serialization. Checks include partial contents, internal-key lookup, allocation-order fingerprints, notifications, callback ABI, destination metadata generations and complete cleanup. Object assignment copies the internal tree key separately from the public pair key, following `0xd400`; these can diverge after allocation failure. + +- [Object Pair allocation and teardown](../../../core/libs/prx/libSceJson2/Export.cpp) - inherited failure scopes, allocator-driven reentry and alternate SDK behavior remain unverified. Reference tests cover 1,320 modern persistent/one-shot cases with and without four registered source handlers through every allocation position for default, null, string, nested-object and nested-array copies across both constructor/destructor aliases. Checks preserve inactive bytes, partial contents, collection counts, child parents, storage owners, allocation generations, request/notification order and construction/destruction release identities. Source auxiliary bytes, generations and handlers survive cleanup. Pair teardown releases the value before the key, following `0xc2b0`. Failed temporary header allocation in modern collection assignment still calls the allocator free function with null, following `0xbc80` and `0xd400`; failed sentinel allocation reclaims its admitted header. + +- [Modern callback-bearing copies](../../../core/libs/prx/libSceJson2/Export.cpp) - allocation-failure combinations, nested owners, lifecycle changes from callbacks and alternate SDK behavior remain unverified. Fifteen reference-backed cases exercise null, real and object sources with all four local handlers through Value construction, assignment with/without destination handlers, Pair construction and standalone Array insertion. Modern copies discard source-local handlers; assignment retains destination handlers. Tests invoke null/mismatch/element/float recovery and verify complete auxiliary bytes, fallback ownership, all callback-record generations, borrowed replacement identity and source-handler usability after copy destruction. + +- [Callback-time termination](../../../core/libs/prx/libSceJson2/Export.cpp) - composite-destruction reinitialization, teardown exceptions and unmeasured publication variants, concurrent lifecycle, older SDKs and guest-kernel error responses remain unverified. Recovery-callback owner termination and allocate/notify/free callback termination inside an active allocation operation are explicitly unsupported before teardown. Reference recovery/notifier probes reused freed/null mutex slots after termination and faulted at the substituted kernel boundary; direct allocate/free probes also faulted after termination returned success. These are not console measurements. Reference-backed controls preserve inactive-initializer termination, standalone Malloc-notifier termination and standalone Free-callback termination/reinitialization. Production tests verify ownership-preserving refusal, repeatable recovery, successful parse continuation, OOM/retry and fresh module generations. + +- [Termination reentry](../../../core/libs/prx/libSceJson2/Export.cpp) - callback exceptions during partially completed teardown and older-SDK combinations remain unverified. Nested owner termination while cleanup is already active is explicitly unsupported before further ownership changes. The reference allocator observed a duplicate free request when a teardown free callback recursively terminated the module; a standalone Free callback retained its target until its original release completed. Twelve production cases exercise reentry at every normal module release position, require one refusal and exactly twelve releases, and verify that the outer call completes and the next module generation initializes and terminates normally. + +- [Initialization callback publication](../../../core/libs/prx/libSceJson2/Export.cpp) - concurrent publication remains unverified. One hundred forty-four direct-allocator reference cases span SDK 5/6, both initializer overloads, shared/separate owners and receiver/context domains. They verify saved file-buffer settings, nested handoff, completed nested lifecycles and retry. Double success orphans the first epoch in its original domain: eleven resources/one mutex for SDK 5, twelve/two for SDK 6. Outer termination reclaims only its own epoch and nested termination reports not-initialized. Tests assert ownership and stale-domain avoidance, then separately reclaim the observed orphaned epoch through the caller-owned fixture. SDK 5 teardown also forwards its observed null free; fixture reclamation is not library cleanup. + +- [RTTI initialization publication](../../../core/libs/prx/libSceJson2/Export.cpp) - concurrent reconfiguration and thrown allocator callbacks remain unverified. One hundred ninety-two additional SDK 5/6 reference cases cover legacy construction/newer RTTI setters, shared/independent contexts and configuration before initialization or inside its first allocation callback. All parameter objects share one adapter. An already-entered first allocation uses the original callback once; subsequent allocations and releases use the latest table while preserving each context and file-buffer setting. Tests cover handoff, lifecycle completion, orphaned epochs, SDK-specific mutex ownership and retry, distinguishing actual RTTI releases, SDK 5 null-free callbacks and later fixture reclamation. + +- [Legacy Pair callback copies](../../../core/libs/prx/libSceJson2/Export.cpp) - inherited failure scopes, allocator-driven reentry and libc allocation exceptions remain unverified. Thirty-six SDK 5 reference-backed cases cover numeric and string sources with a registered mismatch handler, both constructor/destructor aliases and every guest allocation failure position through success using persistent and one-shot schedules. Even a null partial copy retains its independent callback map record. Checks cover exact request/notification order, partial key/text admission, inactive Pair bytes, borrowed callback result identity, independent auxiliary ownership, source allocation bytes/generations and source callback usability after copy cleanup. Every case restores the original live generations in both the guest allocator and libc heap. + +- [Legacy nested Pair copies](../../../core/libs/prx/libSceJson2/Export.cpp) - inherited failure scopes, allocator-driven reentry and libc allocation exceptions remain unverified. One hundred fifty-two SDK 5 reference-backed cases cover an array containing an object/string and an object containing an array/string, both Pair constructor/destructor aliases and persistent/one-shot failure at every allocation position through eighteen-allocation success. Checks cover partial counts, missing sentinels, independent internal/public object keys, exact request/notification fingerprints, inactive bytes, source bytes/generations and both allocator domains. Copies retain the root mismatch-map callback independently of auxiliary metadata; unbound descendants use typed defaults without inheriting that callback. Root and source recovery remain callable after copy cleanup. + +- [Legacy callback map inherited failures](../../../core/libs/prx/libSceJson2/Export.cpp) - allocator-driven lifecycle changes, concurrent propagation and thrown callbacks remain unverified. Fifty-four SDK 5 reference cases cover scalar and nested Array/Object copies through Value and both Pair constructor aliases, new/existing records, healthy readers, published errors and notifier-time registration. Published failure preserves existing handlers or retains empty records; payload copying still completes. Notifier-time copies retain callbacks before error publication. Checks cover exact requests/notices, Pair padding and independent keys, nested contents and parent links, borrowed callback results, source bytes/generations, record-reusing retry and cleanup in both allocator domains. A mutation that skips collection copying after inherited failure is detected. + + +- [Composite-destruction termination](../../../core/libs/prx/libSceJson2/Export.cpp) - additional legacy callback combinations and thrown callbacks remain unverified. One hundred ten SDK 6 reference cases cover both Value destructor aliases, four payload/metadata shapes and every release position. Following `0x17e0`, destruction requires the published recovery mutex after clearing the payload; callback termination during payload release therefore preserves the auxiliary pointer and bytes. Checks cover exact release-generation prefixes, allocator receiver/context, module/source ownership, repeated destruction and fresh initialization. Early termination orphans the remaining caller allocations; final-release termination completes cleanup. The caller-owned fixture separately reclaims the observed orphaned epoch. The pre-fix destructor fails the auxiliary-retention regression. + +- [Legacy auxiliary-cleanup termination](../../../core/libs/prx/libSceJson2/Export.cpp) - a missing recovery mutex after auxiliary-release callbacks is explicitly unsupported at legacy map cleanup, before kernel use. Eight reference probes across both Value destructor aliases returned termination success, then faulted through a null mutex slot at the substituted kernel boundary; these are not console measurements. Ninety-two termination-only production cases cover 84 returning reference cases and eight explicit errors. Checks preserve eleven module resources plus the observed null free, release prefixes, auxiliary state, allocator identities and retry. Termination itself remains allowed so its callback can reinitialize before cleanup resumes. Thrown callbacks remain unverified. + +- [Composite callback reinitialization](../../../core/libs/prx/libSceJson2/Export.cpp) - repeated nested reinitialization and thrown callbacks remain unverified. Five hundred sixty-four SDK 5/6 reference cases cover both destructor aliases, every release position and shared, changed-context or changed-receiver/context domains. The already-entered free keeps its original receiver/context; subsequent releases use the newly published allocator even for old allocations. Reinitialization admits a complete fresh module epoch and lets auxiliary/map cleanup finish. Tests verify release identities/order, domain routing, complete payload cleanup, repeated destruction and later module retry. The pooled backend records cross-domain dispatch; it does not establish safe transfer between independent allocator pools. + +- [Initialization allocator exceptions](../../../core/libs/prx/libSceJson2/Export.cpp) - rollback free exceptions and guest exception integration remain unverified. Fifty isolated SDK 5/6 reference-backed cases cover allocator/notifier throws at each resource position and success. Throws propagate with their original identity and no frees. A first context allocation returning null reports OOM without notifying; a first allocator throw propagates. Both permit retry. Later throws retain admitted resources, leave owners inactive and reject termination/retry. Checks cover resource sizes, receiver/context, exact notifier error/size and both owners. The fixture separately reclaims orphaned storage and any host mutex. SDK 5 normal teardown forwards its observed null free. Null-return rollback and kernel setup error cleanup remain covered. Pre-fix rollback and swallowed-notifier mutations fail the regressions. + +- [Termination free exceptions](../../../core/libs/prx/libSceJson2/Export.cpp) - nested cleanup, callbacks freeing then throwing at other stages and guest exception integration remain incomplete. Fifty SDK 5/6 reference cases cover throws around twelve frees and healthy controls, preserving exception identity, owner activity, release prefixes and initialization refusal. Sixteen returning retries cover null frees, retained generations and fresh owners. Completed defaults forward null frees during retry; the old `ready` gate fails the regression. Eighteen production cases reject destroyed mutex slots or poisoned default inner headers before retry effects. Retirement survives callback exceptions and clears only after the corresponding release returns. Outer-handle retries remain available. Separate probes faulted at substituted kernel boundaries or requested released resources; these are not returning contracts or console measurements. Fixture reclamation is separate. + +- [Legacy map heap exceptions](../../../core/libs/prx/libSceJson2/Export.cpp) - nested copy/erase exceptions and concurrent lock ownership remain unverified. Three isolated SDK 5 reference cases cover sentinel allocation failure, entry allocation failure and success. Sentinel failure propagates `std::bad_alloc` and orphans an unpublished 24-byte header with only its count initialized; retry allocates a replacement header. Entry failure retains the published sentinel and retry reuses it. Checks cover guest/libc domains, absent guest notifications, source bytes/generations, callback identity, termination and fresh initialization. The fixture reclaims the unpublished orphan only after asserting that library cleanup left it intact. The pre-fix implementation replaces the exception with an unsupported error and fails the regression. + +- [Legacy copy heap exceptions](../../../core/libs/prx/libSceJson2/Export.cpp) - deeper entry-allocation exceptions during collection insertion and concurrent lock ownership remain unverified. Twelve SDK 5 reference cases cover Value and both Pair constructor aliases with scalar, string and nested Array/Object sources. Failure to allocate the destination callback-map entry propagates `std::bad_alloc` after payload copying. Pair keys remain independently owned; constructor unwinding does not release them. Checks cover exact guest requests, complete payloads, padding, absent copy callbacks, preserved source callbacks/bytes/generations, shared-map invariants, assignment retry and cleanup in both allocator domains. The pre-fix Pair catch frees the key and fails the retained-allocation regression. Fresh initialization succeeds after explicit destruction and teardown. + +- [Array child callback heap exceptions](../../../core/libs/prx/libSceJson2/Export.cpp) - concurrent publication and additional thrown-callback combinations remain unverified. Twelve SDK 5 reference cases cover both direct Array and Value-wrapped constructor aliases, failure at either child map entry and success. Construction publishes the initial empty destination before copying a temporary list; Value wrappers also publish their type and independent handle. Entry exceptions orphan the temporary sentinel/admitted nodes while completed callback records survive until module teardown. Retry replaces active storage without reclaiming orphaned links. Checks cover requests, generations, source bytes, map invariants, recovery, teardown and fresh initialization. Fixture orphan reclamation is separate. Pre-fix rollback and a mutation clearing the wrapper type both fail regressions. + +- [Object child callback heap exceptions](../../../core/libs/prx/libSceJson2/Export.cpp) - deeper tree shapes, guest allocator throws during cloning and concurrent ownership remain unverified. Twelve SDK 5 reference cases cover both direct Object and Value-wrapped constructor aliases, either child entry exception and success. The initial empty tree remains published while unfinished replacement nodes, keys and sentinel become orphaned; completed callback records remain until module teardown. Retry publishes a complete tree without reclaiming the orphaned replacement. Checks cover exact requests, source bytes/generations, tree contents, key storage, callback identity, published wrappers, retry, cleanup and fresh initialization. The fixture separately reclaims orphaned caller storage. The pre-fix recursive rollback and delayed destination publication fail the regression. + +- [Partial element-recovery fallback](../../../core/libs/prx/libSceJson2/Export.cpp) - nested callback-created fallback payload failures and older SDK dispatch remain unverified. Eight SDK 6 reference cases cover failure of auxiliary/fallback/handler admission and success, with and without callback reentry. Registration retry reuses admitted metadata without repairing a missing fallback. Parsing preserves that missing state; root/child failures invoke the handler and return the module fallback instead. Checks cover request order, error/index/source/parent ABI, suppression of recursive dispatch, visible scalar mutation, zero-allocation dispatch under failure, stable auxiliary/fallback/record generations and complete teardown. A mutation recreating the missing fallback fails the retry regression. + +- [Element fallback String allocation failures](../../../core/libs/prx/libSceJson2/Export.cpp) - nested collection payloads and thrown allocator callbacks during fallback mutation remain unverified. Twenty-four SDK 6 reference cases cover root/module fallbacks, root/child dispatch, all C-string setter allocation positions and success. The setter first allocates a temporary String; failed temporary admission preserves the destination. Later failures retain null or empty payloads and exact notices, including two notices after temporary character failure. Retry reuses admitted handles/headers while recreating temporary storage. Checks cover callback reentry, request order, visible text, metadata generations, source bytes, repeated recovery and complete teardown. The previous host-string temporary skipped guest requests and fails the regression. The setter at `0x1c00` leaves guard residue in the return register; it is declared `void` and tests check state rather than fabricated status. + +- [Value setter return ABI](../../../core/libs/prx/libSceJson2/Export.cpp) - String references invalidated by destination type changes still require runtime audits. Type, Boolean, signed/unsigned integer, String and Value setters expose no defined status result. The type helper at `0x580` ends with guard verification; scalar wrappers at `0x1a90`, `0x1ae0` and `0x1b30` do not establish another result. The String overload at `0x1cc0` can return without setting a result; Value setting at `0x1e40` also leaves guard residue. Definitions and test declarations use `void`; tests retain content, layout, ownership and callback assertions instead of testing fabricated zero returns. The reference scalar-layout contract passes with the corrected declaration. Full guest integration remains unverified. + +- [Value String-argument setter](../../../core/libs/prx/libSceJson2/Export.cpp) - sources nested inside a destination being replaced, binary aliases and thrown callbacks remain unverified. Eighteen SDK 6 reference cases cover missing headers on numeric/String destinations, direct/shared-header aliases, optional root callback metadata and allocation disabled/enabled. Those paths preserve all Value/allocation bytes and perform no allocator effects. Independent-source controls copy text through the existing handle/header, clear old characters before failed character admission and retry successfully. The old host-string snapshot changed missing-header destinations and reallocated self-aliases; it fails the regression. Checks retain source text/ownership and complete cleanup. The implementation uses the String header and underlying text alias check directly. + +- [Value self-setting and assignment](../../../core/libs/prx/libSceJson2/Export.cpp) - nested older-SDK self-copies, non-finite recovery and interrupted modern nonempty self-copies remain unverified. Fifty-two SDK 6 reference cases cover all eight types through both entry points, empty collection admission failures and healthy nonempty Array/Object copies. Assignment at `0x20e0` unconditionally calls the setter and returns its receiver. Both operations rebuild collections while retaining the Value handle and callback metadata. Scalars and Strings naturally preserve their storage. Checks cover replacement/null-free sequences, rollback, inactive bytes, unaffected allocations, internal/public keys, nested contents and the observed nested-header owner rebinding. The old self-guards suppress replacement and fail the regression. All original/replacement storage is reclaimed on destruction. + +- [Legacy collection self-use](../../../core/libs/prx/libSceJson2/Export.cpp) - nested/child-callback self-copies and concurrent ownership remain unverified. Twelve SDK 5 reference cases cover empty Array/Object setters and assignment, sentinel allocation failure and success. Both entry points retain the 24-byte collection header and Value/auxiliary ownership while replacing the sentinel. Failure still releases the old sentinel and leaves a destructible missing-sentinel header. Same-Value retry repairs it without forwarding a null free. Checks cover exact requests/releases, live generations, unchanged callback/fallback/module bytes, partial destruction and fresh module generations. The previous Object replacement helper forwards an extra null free and fails the retry regression. + +- [Nonempty legacy self-use failures](../../../core/libs/prx/libSceJson2/Export.cpp) - nested/child-callback payloads, thrown callbacks and concurrent accounting remain unverified. Fifty-six SDK 5 reference cases cover two-element Arrays/Objects through setters and assignment, persistent/one-shot failure at every sentinel/node/key admission position and success. Exact request and released-generation fingerprints accompany checks of partial values, parent links, missing sentinels, independent internal/public keys and counts exceeding live nodes. Retry clones the actual partial state without restoring lost entries or inventing key/count repairs. Checks preserve module, handle, auxiliary and fallback bytes/generations and verify complete teardown. A mutation clamping a partial tree count to its live nodes fails the regression. + +- [Element fallback collection allocation failures](../../../core/libs/prx/libSceJson2/Export.cpp) - nested payloads, thrown callbacks and concurrent lifecycle changes remain unverified. One hundred fifty-two SDK 6 reference cases cover two-element Array/Object assignment into root-owned and module fallbacks, persistent/one-shot failure at every allocation position and success. Checks include reentry suppression, exact request/notice fingerprints, missing sentinels, partial values/keys/counts, parent links, header ownership and unchanged source/module/callback allocation generations and bytes. Independent retry completes the replacement; subsequent recovery retains its handle. Teardown reclaims all source, partial and replacement payloads. All production probe results match the reference. A mutation dropping the fallback collection owner fails the regression. + +- [Initializer allocator-info callback](../../../core/libs/prx/libSceJson2/Export.cpp) - the inspected 19-byte function at `0x16e0` ignores the receiver, callback and context. It returns `0x80848110` when the allocation context is absent and zero when present, without storing or invoking the callback. The implementation intentionally follows that behavior; it is not evidence of allocation telemetry support on other library versions. + +- [Descendant assignment and overlapping swap](../../../core/libs/prx/libSceJson2/Export.cpp) - assigning a descendant or an owned object key snapshots its payload before releasing its owner. Cross-type Array/String ownership regressions exercise public setters and assignment. Swapping ancestor and descendant values throws before mutation: the first replacement invalidates the second receiver, and the reference contract remains unverified. Ordinary independent swaps retain their existing behavior. +- [Legacy callback concurrency](../../../core/libs/prx/libSceJson2/Export.cpp) - allocation failure/depth state uses the host state mutex without nesting the shared legacy recovery mutex. A coordinated mismatch callback and parser test reproduces the former inverse lock order and verifies both operations complete. It substitutes the application-heap entry points and kernel mutex primitives; it exercises the real Json2 state locks, callbacks and parser. Concurrent initialization/termination, callback null-access cycles and partial object trees after failed key/subtree allocations remain unverified; partial copies can retain admitted nodes with incomplete keys or counts. diff --git a/docs/dev/debt/libSceLibcInternal.md b/docs/dev/debt/libSceLibcInternal.md index 82d71328ac..a8130c2a23 100644 --- a/docs/dev/debt/libSceLibcInternal.md +++ b/docs/dev/debt/libSceLibcInternal.md @@ -6,7 +6,7 @@ The named entries below use provider-specific NID aliases to avoid interposing h - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/6bd58c3d7a0fa933dd4b524754c43265/e4e5dbc43890a24e35dc1c656135000210e28390) records module, library, symbol type and NIDs for [InventoryExports.cpp](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp); it is a metadata summary rather than independent verification. One entry belongs to the logical library `libSceLibcInternalExt` within this module. -- [Additional callable exports](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp) - 297 entries observed in a 3.20-labelled image and a later 13.60-labelled image; 291 match the public 3.20 catalogue and 6 do not. These are declarations only; neither image label authenticates a retail firmware version. Signatures and behavior are unverified; parameterless `int` declarations are placeholders. Every entry throws through `NotImplemented_nid_no_patch`. All declarations use literal NID aliases; the table records candidate names where known. No runtime functionality is implemented; data exports need separate analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). +- [Additional callable exports](../../../core/libs/prx/libSceLibcInternal/InventoryExports.cpp) - 295 entries observed in a 3.20-labelled image and a later 13.60-labelled image; 289 match the public 3.20 catalogue and 6 do not. These are declarations only; neither image label authenticates a retail firmware version. Signatures and behavior are unverified; parameterless `int` declarations are placeholders. Every entry throws through `NotImplemented_nid_no_patch`. All declarations use literal NID aliases; the table records candidate names where known. No runtime functionality is implemented; data exports need separate analysis. Identified candidate names match their NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). | Name | NID | | --- | --- | @@ -28,7 +28,6 @@ The named entries below use provider-specific NID aliases to avoid interposing h | `_ZNSt10moneypunctIwLb1EED1Ev` | `OM0FnA7Tldk` | | `_ZNSt10moneypunctIwLb1EED2Ev` | `uVOxy7dQTFc` | | `_ZNSt12future_errorD0Ev` | `rNYLEsL7M0k` | -| `_ZNSt12future_errorD2Ev` | `XFh0C66aEms` | | `_ZNSt12system_errorC2ESt10error_codePKc` | `oly3wSwEJ2A` | | `_ZNSt13bad_exceptionD0Ev` | `lX9M5u0e48k` | | `_ZNSt13bad_exceptionD1Ev` | `t6egllDqQ2M` | @@ -54,14 +53,12 @@ The named entries below use provider-specific NID aliases to avoid interposing h | `_ZNSt13basic_ostreamIwSt11char_traitsIwEE6sentryD2Ev` | `MB1VCygerRU` | | `_ZNSt13basic_ostreamIwSt11char_traitsIwEED0Ev` | `7VRfkz22vPk` | | `_ZNSt13basic_ostreamIwSt11char_traitsIwEED1Ev` | `EYZJsnX58DE` | -| `_ZNSt14overflow_errorD2Ev` | `OypvNf3Uq3c` | | `_ZNSt15basic_streambufIcSt11char_traitsIcEE6xsgetnEPci` | `VpwymQiS4ck` | | `_ZNSt15basic_streambufIcSt11char_traitsIcEE6xsputnEPKci` | `sXaxl1QGorg` | | `_ZNSt15basic_streambufIcSt11char_traitsIcEE9showmanycEv` | `IAEl1Ta7yVU` | | `_ZNSt15basic_streambufIwSt11char_traitsIwEE6xsgetnEPwi` | `lZVehk7yFok` | | `_ZNSt15basic_streambufIwSt11char_traitsIwEE6xsputnEPKwi` | `041c37QfoUc` | | `_ZNSt15basic_streambufIwSt11char_traitsIwEE9showmanycEv` | `olsoiZsezkk` | -| `_ZNSt15underflow_errorD2Ev` | `xiqd+QkuYXc` | | `_ZNSt16nested_exceptionD1Ev` | `+qKS53qzWdA` | | `_ZNSt16nested_exceptionD2Ev` | `8R00hgzXQDY` | | `_ZNSt22_Future_error_categoryD0Ev` | `WDKzMM-uuLE` | @@ -298,8 +295,6 @@ The named entries below use provider-specific NID aliases to avoid interposing h | `_seekdir` | `Do3zPpsXj1o` | | `backtrace_game` | `jFSmlgyyEUg` | | `clock_1700` | `n8onIBR4Qdk` | -| `devname_r` | `BIALMFTZ75I` | -| `devname` | `fqLrWSWcGHw` | | `malloc_initialize` | `owT6zLJxrTs` | | `Unknown` | `GG6441JdYkA` | | `Unknown` | `QZ9YgTk+yrE` | @@ -329,6 +324,8 @@ The named entries below use provider-specific NID aliases to avoid interposing h - [_sceLibcInternalThreadAtexit, _sceLibcInternalThreadDtors](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - FreeBSD `__cxa_thread_atexit` semantics assumed; a null or non-image destructor and an allocation failure throw - [_sceLibcInternalForceTlsDestructor](../../../core/libs/prx/libSceLibcInternal/Export.cpp) (libSceLibcInternal) - behaviour follows `sceLibcForceTlsDestructor(int)` in a title's `libc.prx` (disassembly): the calling thread's destructors whose dso belongs to the module run newest first, only if the dso's module is loaded and the dso points to itself, in up to 4 passes. The return type is unknown (that function returns nothing), 0 is returned. The module of each dso is looked up when the destructors are forced, not when they are registered as `libc.prx` does +- [Recovered inventory exports](../../../core/libs/prx/libSceLibcInternal/RecoveredInventory.cpp) (libSceLibcInternal) - all 40 parameterless `int` declarations are import-resolution placeholders with unverified parameter lists, return types and behavior; every call throws through `NotImplemented_nid_no_patch`. The names and NIDs are listed in the [PS5 13.60 inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/226383a7fa4b1275d6cebf61eb1c1e7ca7b58bcb/data/firmware-13.60/libscelibcinternal-f9a6d6abff.jsonl), and the [physical-module map](https://github.com/boykopovar/ps5-nid-map/blob/cc1d586fddab576a520f226da6b2632b031e6d09/data/Modules/libSceLibcInternal.inc) assigns all 40 NIDs to libSceLibcInternal. Neither source establishes the ABI or behavior. + ### Functional - [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 @@ -337,14 +334,169 @@ The named entries below use provider-specific NID aliases to avoid interposing h - [__opendir2](../../../core/libs/prx/libSceLibcInternal/Directory.cpp) (libSceLibcInternal, NID `KOy7MeQ7OAU`) - the device's answer to any flag is not measured; the flags follow FreeBSD 11.4's [`__opendir2`](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libc/gen/opendir.c#L76-L95) - [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 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` +- [posix_spawn and posix_spawnp](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) (libSceLibcInternal) have unverified console sandbox and error equivalence. POSIX errors missing from the shared translator fall back to `EIO`. Only converted programs under `/app0/` run (`/app0/eboot.bin` uses the running image). Windows open/duplication actions redirect only descriptors 0 to 2; nonzero attribute flags throw. POSIX hosts implement only `POSIX_SPAWN_RESETIDS` and `POSIX_SPAWN_SETPGROUP`; other flags throw. Guest IDs are host IDs, a null `argv` returns `EFAULT`, and null or uninitialized objects throw where FreeBSD dereferences them. A null `envp` inherits the parent environment. `posix_spawnp` follows FreeBSD 11 `_execvpe` path search; PPSA99305 reaches it while SDL searches 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 +- [_ZNSt4_PadD1Ev](../../../core/libs/prx/libSceLibcInternal/Pad.cpp) (libSceLibcInternal, NID `XyJPhPqpzMw`) is the complete-object destructor of `_Pad`, which has no virtual bases, so the [Itanium C++ ABI](https://itanium-cxx-abi.github.io/cxx-abi/abi.html#definitions) makes it do the same work as the base-object `D2` destructor; it runs the `libc` export of that `D2` name, which unlocks and destroys the pad's mutex and condition and throws from a thread that does not hold the mutex. The console's `libSceLibcInternal.sprx` was not read and no title was seen calling it - [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 +- [Optional PID outputs in posix_spawn and posix_spawnp](../../../core/libs/prx/libSceLibcInternal/Spawn.cpp) (libSceLibcInternal) are unverified on the console; null PID outputs follow [FreeBSD 11.4](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/gen/posix_spawn.c#L279-L287), creating a child that can still be reaped with `waitpid` - [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)) + +### Additional PS5 3.20 import declarations + +- [_ZNKSt7_MpunctIcE16do_negative_signEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Rqu9OmkKY+M` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIcE16do_positive_signEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ARZszYKvDWg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIcE16do_thousands_sepEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `6aFwTNpqTP8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIwE11do_groupingEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ckD5sIxo+Co` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIwE13do_neg_formatEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `UzmR8lOfyqU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIwE13do_pos_formatEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `zF2GfKzBgqU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIwE14do_curr_symbolEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ypq5jFNRIJA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIwE14do_frac_digitsEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ij-yZhH9YjY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIwE16do_decimal_pointEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `v8P1X84ytFY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIwE16do_negative_signEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `zkUC74aJxpE` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIwE16do_positive_signEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `PWFePkVdv9w` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7_MpunctIwE16do_thousands_sepEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `XX+xiPXAN8I` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDic9_MbstatetE10do_unshiftERS0_PcS3_RS3_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `c6Lyc6xOp4o` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDic9_MbstatetE11do_encodingEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `DDnr3lDwW8I` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDic9_MbstatetE13do_max_lengthEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `E5NdzqEmWuY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDic9_MbstatetE16do_always_noconvEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `NQ81EZ7CL6w` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDic9_MbstatetE5do_inERS0_PKcS4_RS4_PDiS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `PJ2UDX9Tvwg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDic9_MbstatetE6do_outERS0_PKDiS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `eoW60zcLT8Q` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDic9_MbstatetE9do_lengthERS0_PKcS4_m](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `m6rjfL4aMcA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDsc9_MbstatetE10do_unshiftERS0_PcS3_RS3_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `RYTHR81Y-Mc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDsc9_MbstatetE11do_encodingEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Mo6K-pUyNhI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDsc9_MbstatetE13do_max_lengthEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `BvRS0cGTd6I` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDsc9_MbstatetE16do_always_noconvEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `9Vyfb-I-9xw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDsc9_MbstatetE5do_inERS0_PKcS4_RS4_PDsS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `+uPwCGfmJHs` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDsc9_MbstatetE6do_outERS0_PKDsS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `0FKwlv9iH1c` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIDsc9_MbstatetE9do_lengthERS0_PKcS4_m](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `lynApfiP6Lw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIcc9_MbstatetE10do_unshiftERS0_PcS3_RS3_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `iCWgjeqMHvg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIcc9_MbstatetE16do_always_noconvEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `7tIwDZyyKHo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIcc9_MbstatetE2inERS0_PKcS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `TNexGlwiVEQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIcc9_MbstatetE3outERS0_PKcS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `14xKj+SV72o` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIcc9_MbstatetE5do_inERS0_PKcS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `P+q1OLiErP0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIcc9_MbstatetE6do_outERS0_PKcS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Uc+-Sx0UZ3U` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIcc9_MbstatetE6lengthERS0_PKcS4_m](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ikBt0lqkxPc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIcc9_MbstatetE7unshiftERS0_PcS3_RS3_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `N7z+Dnk1cS0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIcc9_MbstatetE9do_lengthERS0_PKcS4_m](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Xk7IZcfHDD8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIwc9_MbstatetE10do_unshiftERS0_PcS3_RS3_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `oDtGxrzLXMU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIwc9_MbstatetE11do_encodingEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `4fPIrH+z+E4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIwc9_MbstatetE13do_max_lengthEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `5BQIjX7Y5YU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIwc9_MbstatetE16do_always_noconvEv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `KheIhkaSrlA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIwc9_MbstatetE5do_inERS0_PKcS4_RS4_PwS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `WAPkmrXx2o8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIwc9_MbstatetE6do_outERS0_PKwS4_RS4_PcS6_RS6_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ABFE75lbcDc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7codecvtIwc9_MbstatetE9do_lengthERS0_PKcS4_m](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `G1zcPUEvY7U` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIcE10do_compareEPKcS2_S2_S2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `1eEXfeW6wrI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIcE12do_transformEPKcS2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `gYlF567r3-A` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIcE4hashEPKcS2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `6vYXzFD-mrk` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIcE7compareEPKcS2_S2_S2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Q-8lQCGMj4g` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIcE7do_hashEPKcS2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `GSAXi4F1SlM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIwE10do_compareEPKwS2_S2_S2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `roztnFEs5Es` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIwE12do_transformEPKwS2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Zxe-nQMgxHM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIwE4hashEPKwS2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `entYnoIu+fc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIwE7compareEPKwS2_S2_S2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `n-3HFZvDwBw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIwE7do_hashEPKwS2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `cWaCDW+Dc9U` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7collateIwE9transformEPKwS2_](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `81uX7PzrtG8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERPv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `my9ujasm6-0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERb](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `OWO5cpNw3NA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERd](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `mAwXCpkWaYc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERe](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `wUCRGap1j0U` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERf](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `6RGkooTERsE` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERj](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `N1VqUWz2OEI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERl](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `I2UzwkwwEPs` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `2bfL3yIBi5k` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERt](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `gozsp4urvq8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERx](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `4hiQK82QuLc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERy](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `eZfFLyWCkvg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERPv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `VKdXFE7ualw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERb](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `SmtBNDda5qU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERd](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `bNQpG-eKogg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERe](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `uukWbYS6Bn4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERf](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `IntAnFb+tw0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERj](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ywJpNe675zo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERl](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ALEXgLx9fqU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Pq4PkG0x1fk` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERt](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `dRu2RLn4SKM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERx](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `F+AmVDFUyqM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERy](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `TtYifKtVkYA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE8_GetffldEPcRS3_S6_RSt8ios_basePi](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `4+y8-2NsDw0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE8_GetifldEPcRS3_S6_NSt5_IosbIiE9_FmtflagsERKSt6locale](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `G9LB1YD5-xc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE9_GetffldxEPcRS3_S6_RSt8ios_basePi](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `J-0I2PtiZc4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERPv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `zomvAQ5RFdA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERb](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `vW-nnV62ea4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERd](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `+hjXHfvy1Mg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERe](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `xLZr4GJRMLo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERf](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `2mb8FYgER+E` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERj](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Y3hBU5FYmhM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERl](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `-m2YPwVCwJQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `94ZLp2+AOq0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERt](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `bZ+lKHGvOr8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERx](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `cG5hQhjFGog` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERy](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `banNSumaAZ0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERPv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `BNZq-mRvDS8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERb](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `wEU8oFtBXT8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERd](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `t39dKpPEuVA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERe](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `MCtJ9D7B5Cs` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERf](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Gy2iRxp3LGk` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERj](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `2bUUbbcqHUo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERl](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `QossXdwWltI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ig6SRr1GCU4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERt](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `kU7PvJJKUng` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERx](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Ou7GV51-ng4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE6do_getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateERy](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `rYLrGFoqfi4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE8_GetffldEPcRS3_S6_RSt8ios_basePi](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `W5VYncHdreo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE8_GetifldEPcRS3_S6_NSt5_IosbIiE9_FmtflagsERKSt6locale](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `GGqIV4cjzzI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_getIwSt19istreambuf_iteratorIwSt11char_traitsIwEEE9_GetffldxEPcRS3_S6_RSt8ios_basePi](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `bZ0oEGQUKO8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basecPKv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `8NVUcufbklM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basecb](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `nftirmo6hBg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basecd](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `w9NzCYAjEpQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basece](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `VPcTGA-LwSo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basecl](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ffnhh0HcxJ4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basecm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `uODuM76vS4U` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basecx](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `NJtKruu9qOs` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE3putES3_RSt8ios_basecy](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `dep6W2Ix35s` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE4_PutES3_PKcm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `k8zgjeBmpVY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE4_RepES3_cm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `tCihLs4UJxQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE5_FfmtEPccNSt5_IosbIiE9_FmtflagsE](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `w11G58-u4p8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE5_FputES3_RSt8ios_basecPKcm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ll99KkqO6ig` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE5_FputES3_RSt8ios_basecPKcmmmm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `mNk6FfI8T7I` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE5_IfmtEPcPKcNSt5_IosbIiE9_FmtflagsE](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `xlgA01CQtBo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE5_IputES3_RSt8ios_basecPcm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `jykT-VWQVBY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basecPKv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `0xgFRKf0Lc4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basecb](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ke36E2bqNmI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basecd](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `F+cp2B3cWNU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basece](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `rLiFc4+HyHw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basecl](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `I3+xmBWGPGo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basecm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `nlAk46weq1w` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basecx](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `H2KGT3vA7yQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE6do_putES3_RSt8ios_basecy](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `Vbeoft607aI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE3putES3_RSt8ios_basewPKv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `xfOSCbCiY44` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE3putES3_RSt8ios_basewb](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `mY9FWooxqJY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE3putES3_RSt8ios_basewd](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `V7aIsVIsIIA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE3putES3_RSt8ios_basewe](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `vCIFGeI6adI` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE3putES3_RSt8ios_basewl](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `USLhWp7sZoU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE3putES3_RSt8ios_basewm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `qtpzdwMMCPc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE3putES3_RSt8ios_basewx](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `ryykbHJ04Cw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE3putES3_RSt8ios_basewy](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `lmb3oBpMNPU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE4_PutES3_PKwm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `kRGVhisjgMg` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE4_RepES3_wm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `-b+Avqa2v9k` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE5_FfmtEPccNSt5_IosbIiE9_FmtflagsE](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `T07KcAOlIeU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE5_FputES3_RSt8ios_basewPKcm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `IdV-tXejEGQ` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE5_FputES3_RSt8ios_basewPKcmmmm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `B6JXVOMDdlw` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE5_IfmtEPcPKcNSt5_IosbIiE9_FmtflagsE](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `WheFSRlZ9JA` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE5_IputES3_RSt8ios_basewPcm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `4pQ3B1BTMgo` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE6do_putES3_RSt8ios_basewPKv](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `BDteGj1gqBY` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE6do_putES3_RSt8ios_basewb](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `1C2-2WB9NN4` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE6do_putES3_RSt8ios_basewd](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `sX3o6Zmihw0` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE6do_putES3_RSt8ios_basewe](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `6OYWLisfrB8` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE6do_putES3_RSt8ios_basewl](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `VpwhOe58wsM` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE6do_putES3_RSt8ios_basewm](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `jHo78LGEtmU` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE6do_putES3_RSt8ios_basewx](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `9SSHrlIamto` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. +- [_ZNKSt7num_putIwSt19ostreambuf_iteratorIwSt11char_traitsIwEEE6do_putES3_RSt8ios_basewy](../../../core/libs/prx/libSceLibcInternal/Err.cpp) (libSceLibcInternal) - NID `uX0nKsUo8gc` from the PS5 3.20 export inventory; prototype and behavior are unverified. Declaration is for import resolution only. - [_ZnamSt11align_val_tRKSt9nothrow_t and _ZdaPvSt11align_val_t](../../../core/libs/prx/libSceLibcInternal/AlignedNew.cpp) (libSceLibcInternal, NIDs `s2eGsgUF9vk` and `v09ZcAhZzSc`) take the standard C++17 signatures their mangled names give, the aligned `nothrow` array `operator new` and the aligned array `operator delete`, and run the same code as the `libc` exports of those names, on the same application heap and new handler. The console's `libSceLibcInternal.sprx` was not read and no title was seen calling them, so whether it allocates from the same heap as `libc.prx` is unverified +- [_ZNSt12future_errorD2Ev, _ZNSt14overflow_errorD2Ev and _ZNSt15underflow_errorD2Ev](../../../core/libs/prx/libSceLibcInternal/ExceptionDestructors.cpp) (libSceLibcInternal, NIDs `XFh0C66aEms`, `OypvNf3Uq3c` and `xiqd+QkuYXc`) are the base-object destructors of classes without virtual bases, which the [Itanium C++ ABI](https://itanium-cxx-abi.github.io/cxx-abi/abi.html#definitions) makes do the same work as the complete-object `D1` destructors, so they run the `libc` exports of those `D1` names. The console's `libSceLibcInternal.sprx` was not read and no title was seen calling them, so whether it frees the message from the same heap as `libc.prx` is unverified - [_Mtxinit, _Mtxlock, _Mtxunlock, _Mtxdst](../../../core/libs/prx/libSceLibcInternal/Mutex.cpp) (libSceLibcInternal) follow the PS4 code of [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/860d73b7e99559106a3ffa3d3d69f0f9237e115e/src/core/libraries/libc_internal/libc_internal_threads.cpp#L10-L62) and [fpPS4](https://github.com/red-prig/fpPS4/blob/04cefd43e6fddd1ab033e7980cd356d14c964905/src/libcinternal/ps4_mtx_internal.pas#L47-L99): the argument points to a pointer-sized libkernel mutex handle, `_Mtxinit` creates it recursive, a zeroed handle becomes an error-checking mutex on its first lock as with the POSIX calls, and each returns 0 on success and 1 on any failure. A null handle pointer throws in libkernel. The `SceLibcI_` name goes to `scePthreadMutexInit`, which drops it. For PS5 only the names are known (NID hashes, ps5sdk stubs); layout, signatures, mutex type and results are not verified on a PS5 ### Additional PS5 3.20 imports (xdCloudy #9498) @@ -446,7 +598,6 @@ The named entries below use provider-specific NID aliases to avoid interposing h - [_ZSt11setiosflagsNSt5_IosbIiE9_FmtflagsE](../../../core/libs/prx/libSceLibcInternal/InventoryExports6.cpp) (libSceLibcInternal, NID `L7Vnk06zC2c`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` - [__atomic_load_n](../../../core/libs/prx/libSceLibcInternal/InventoryExports6.cpp) (libSceLibcInternal, NID `Q6oqEnefZQ8`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` - [_ZNSt8ios_base4InitC2Ev](../../../core/libs/prx/libSceLibcInternal/InventoryExports6.cpp) (libSceLibcInternal, NID `bTQcNwRc8hE`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` -- [_ZNSt4_PadD1Ev](../../../core/libs/prx/libSceLibcInternal/InventoryExports6.cpp) (libSceLibcInternal, NID `XyJPhPqpzMw`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` - [_malloc_thread_cleanup](../../../core/libs/prx/libSceLibcInternal/InventoryExports6.cpp) (libSceLibcInternal, NID `Sopthb9ztZI`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` - [_ZNSt8time_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEED1Ev](../../../core/libs/prx/libSceLibcInternal/InventoryExports6.cpp) (libSceLibcInternal, NID `h+c9OSfCAEg`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.c); signature and behavior are unverified, so this parameterless declaration is for import resolution only and throws through `NotImplemented_nid_no_patch` - [_ZNSt8ios_baseD1Ev](../../../core/libs/prx/libSceLibcInternal/InventoryExports6.cpp) (libSceLibcInternal, NID `X9D8WWSG3As`) - listed in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceLibcInternal.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/libSceLibreSsl.md b/docs/dev/debt/libSceLibreSsl.md index 271bc7c326..296026380b 100644 --- a/docs/dev/debt/libSceLibreSsl.md +++ b/docs/dev/debt/libSceLibreSsl.md @@ -7,16 +7,17 @@ ### Unknown function info -- [libSceLibreSsl pending exports](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - 292 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) - 231 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. SHA-384 and SHA-512 initialization retains the block buffer, following [LibreSSL 2.5.4 `sha512.c`](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/sha/sha512.c) - [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. - [CRYPTO_num_locks, CRYPTO_set_locking_callback, CRYPTO_set_add_lock_callback, CRYPTO_THREADID_set_callback, CRYPTO_THREADID_set_numeric](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - the locking, add-lock and thread id callbacks are stored but never called: LibreSSL calls them from `CRYPTO_lock`, `CRYPTO_add_lock` and `CRYPTO_THREADID_current`, which the module does not export, around its own shared state. Nothing is checked on the console. Results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/cryptlib.c), the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214): 41 locks, the first non-null thread id callback is kept and later ones return 0, and `CRYPTO_THREADID_set_numeric` clears the 16-byte `{ptr, val}` structure; a null structure throws, as LibreSSL writes through it unchecked - [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 +- [ERR_get_error, ERR_peek_error, ERR_clear_error, ERR_remove_thread_state](../../../core/libs/prx/libSceLibreSsl/src/Error.cpp) have no console execution verification. Memory BIO and CMAC failures publish to this queue. The 16-slot calling-thread queue retains 15 codes following LibreSSL err.c and err.h. [Firmware 13.42 module comparisons on a PC](https://github.com/boykopovar/AnyPS5/pull/9535#issuecomment-6407060914) matched libSceLibreSsl3 queue ordering and overflow for 0-20 publications; whether the two firmware libraries share a queue was not tested. Explicit thread IDs throw because guest thread identity mapping is unverified; null clears the calling thread. File/line metadata is stored by the native producer helper but no guest export exposes it. - [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_get_ex_new_index, SSL_CTX_get_ex_new_index, SSL_get_ex_data_X509_STORE_CTX_idx and CRYPTO_cleanup_all_ex_data](../../../core/libs/prx/libSceLibreSsl/src/ExData.cpp) (libSceLibreSsl) - unverified on a PS5. The stored `argl`, `argp` and callbacks are never called, because no export creates an `SSL` or `SSL_CTX` and `SSL_get_ex_data`/`SSL_set_ex_data` still throw. Index numbering follows LibreSSL 2.5.4's [ex_data.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ex_data.c#L310-L406), [ssl_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_lib.c#L2828-L2854) and [ssl_cert.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_cert.c#L133-L160): per class from 0, the verify-callback index allocated once and kept across `CRYPTO_cleanup_all_ex_data`, which restarts every class at 0. +- [EVP digest and HMAC functions](../../../core/libs/prx/libSceLibreSsl/src/Evp.cpp) (libSceLibreSsl) - follow LibreSSL 2.5.4's [digest.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp/digest.c) and [hmac.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/hmac/hmac.c). They and the `EVP_MD`, `EVP_MD_CTX` and `HMAC_CTX` layouts behave the same up to 3.4.1, so this holds for 2.5.4 (the firmware 13.40 measurements) and for 2.7 or later (its `SSL_get0_param` export). Nothing is checked on the console. `sign` and `verify` in the `EVP_MD` tables are null as `RSA_sign` and `RSA_verify` still throw, failures add nothing to the error queue, and a failed allocation leaves the context without a digest, where LibreSSL keeps it. An `ENGINE`, an `EVP_PKEY_CTX`, a null context, `EVP_DigestInit_ex` without a type on a context that has a digest, and an update or final without a digest throw - [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. - [sk_num, sk_value, sk_pop, sk_pop_free](../../../core/libs/prx/libSceLibreSsl/src/Stack.cpp) (libSceLibreSsl) - the 32-byte `_STACK` layout (`num`, `data`, `sorted`, `num_alloc`, `comp`) and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/stack) `stack.h` and `stack.c`, the version identified in [published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); nothing is checked on the console. `sk_pop_free` releases the element array and the stack with the guest `free`, as the module would through libc. No export of this module creates a stack yet, so only stacks the caller builds reach them. A null element array that LibreSSL would read through, and a null free function with a non-null element, throw where LibreSSL faults @@ -26,9 +27,22 @@ - [UI_set_result](../../../core/libs/prx/libSceLibreSsl/src/Ui.cpp) (libSceLibreSsl) - unverified on a PS5, and no export of this module creates a `UI` or `UI_STRING` yet. The "You must type in %d to %d characters" text LibreSSL 2.5.4 attaches to the size errors is not kept, because the error queue holds codes only. A null `UI` or result, or null boolean characters that must be compared, throws; LibreSSL dereferences them. The rest follows its [ui_lib.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_lib.c#L816-L879) over the 40-byte `UI` and 64-byte `UI_STRING` of [ui_locl.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/ui/ui_locl.h#L104-L148): a result outside the bounds sets `UI_FLAG_REDOABLE` and queues `UI_R_RESULT_TOO_SMALL` or `UI_R_RESULT_TOO_LARGE`. - [PKCS12_PBE_add, SSL_load_error_strings](../../../core/libs/prx/libSceLibreSsl/Export.cpp) (libSceLibreSsl) - unverified on a PS5; each returns without an observable effect and queues no error. `SSL_load_error_strings` builds no string table, because `ERR_error_string` and `ERR_error_string_n` throw ([ssl_err.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libssl/ssl_err.c#L483-L490)); the tables are needed once a string lookup exists. `PKCS12_PBE_add` is empty in LibreSSL 2.5.4 ([p12_crpt.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/pkcs12/p12_crpt.c#L67-L70)). - [EVP_PKEY_type](../../../core/libs/prx/libSceLibreSsl/src/Pkey.cpp) (libSceLibreSsl) - follows the method table of [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1/ameth_lib.c#L83-L111) `ameth_lib.c` built without GOST and with no engine (every `ENGINE_` export throws): RSA (6) and its alias 19 give 6, the DSA types 66, 67, 70, 113 and 116 give 116, DH (28), EC (408), HMAC (855) and CMAC (894) give themselves, and every other type, the GOST types 811, 815, 946 and 947 included, gives 0. These results match [firmware 13.42's module run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/10398#issuecomment-6406072913) for every type from -1000 to 5000 and the `int` extremes; nothing is checked on the console +- [EVP_CIPHER_CTX_init, EVP_CIPHER_CTX_new, EVP_CIPHER_CTX_set_padding](../../../core/libs/prx/libSceLibreSsl/src/EvpCipherCtx.cpp) (libSceLibreSsl) - nothing is checked on the console. The 168-byte `EVP_CIPHER_CTX` layout and the results follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/evp) `evp.h` and `evp_enc.c`, the version in the [firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214), which also measured `EVP_CIPHER_CTX_init` clearing 168 bytes. `EVP_CIPHER_CTX_new` allocates through the guest `calloc` as the module would through libc. A null context, which LibreSSL dereferences, throws ### Functional +- [Error publication](../../../core/libs/prx/libSceLibreSsl/ErrorQueue.hpp) is integrated with memory BIO and CMAC cipher errors. The digest, low-level AES, allocator/RAND, DES, BIGNUM and EVP/HMAC implementations require separate error-publication integration. `LibreSslPutError_nid_no_patch(int library, int function, int reason, const char* file, int line)` has C linkage and the native host calling convention, accepts upstream numeric fields and publishes to the calling thread's queue. Three-argument source calls default file/line to null/zero. It stores file/line without taking ownership; producers must supply a filename with static lifetime. Guest ERR entry points use `APS5_VABI`. Unsupported producer exports continue throwing; the local tests publish through this helper. + +- [CMAC and AES-CBC descriptors](../../../core/libs/prx/libSceLibreSsl/src/Cmac.cpp) have no PS5 equivalence verification. Signatures, the 88-byte cipher method and 168-byte cipher context follow [LibreSSL 3.4.1](https://github.com/libressl/openbsd/blob/078b3d1df8ca2011c1605cd40418c58fa0f5b143/src/lib/libcrypto/cmac/cmac.c); the console version remains unknown. AES cipher data uses Mbed TLS storage instead of LibreSSL's private key schedule. Guest-defined cipher methods run through guest-ABI callbacks. Other built-in cipher selectors and the general EVP cipher exports still throw. ENGINE-backed methods throw. Null contexts, null nonempty update buffers, null initialized-final length pointers, negative cipher dimensions and missing required callbacks throw where upstream dereferences unchecked pointers. Error metadata names host source locations. + +- [BN_new, BN_free, BN_CTX_new, BN_CTX_free, BN_CTX_start, BN_CTX_end, BN_bin2bn, BN_bn2bin, BN_hex2bn, BN_num_bits, BN_set_word, BN_add_word, BN_sub_word, BN_mul and BN_mod_inverse](../../../core/libs/prx/libSceLibreSsl/src/Bignum.cpp) (libSceLibreSsl) - the `BIGNUM` layout (24 bytes, 64-bit limbs, `top`, `dmax`, `neg`, `flags`) and the documented semantics follow the public LibreSSL 2.x API; the module's LibreSSL version is unknown and nothing is checked on the console. `BN_CTX` is opaque and only counts `BN_CTX_start` and `BN_CTX_end`, since `BN_CTX_get` is not implemented, and `BN_mul` and `BN_mod_inverse` ignore it. `BN_mod_inverse` treats a negative modulus by its magnitude and returns 0 for a modulus of 1, which is not compared with LibreSSL. `BN_hex2bn` rejects a string without a hex digit, such as `-`, which older LibreSSL versions accept. Null `BIGNUM` pointers, which LibreSSL does not check, throw. `BN_print` still throws because it needs a `BIO`. + +- [Memory BIOs](../../../core/libs/prx/libSceLibreSsl/src/Bio.cpp) have no console execution verification. [Firmware 13.42 module comparisons on a PC](https://github.com/boykopovar/AnyPS5/pull/9536#issuecomment-6407192928) cover 1,048 sequences with stand-in allocators, including null/zero writes, callbacks and growth wiping. They support dispatching nonnegative writes to the method even with null source or zero length; LibreSSL 3.4.1 instead guards those calls. Negative public lengths retain that source's early return because the module report did not measure them. The 104-byte BIO, 80-byte method and 24-byte buffer use LP64 layouts. Allocator padding is zeroed rather than retaining the module allocator's fill. Other firmware and console imports remain unverified. CRYPTO ex-data, file, socket and SSL BIOs remain unsupported. + +- [Memory BIO unchecked inputs](../../../core/libs/prx/libSceLibreSsl/src/Bio.cpp) throw for null method or raw line-buffer pointers, negative raw method write sizes, an overflowing raw line-buffer size, inconsistent read-only buffer lengths, and memory write lengths that overflow size_t. Upstream does not validate these inputs. An unrepresentable string length throws instead of narrowing into an unchecked write. + +- [ERR string formatting](../../../core/libs/prx/libSceLibreSsl/ErrorQueue.cpp) is not console-verified. [Firmware 13.42 modules run natively on a PC](https://github.com/boykopovar/AnyPS5/pull/9535#issuecomment-6407060914) produced numeric-only strings before and after their loaders. Formatting, truncation and caller/static buffers matched with a standard-C snprintf stand-in; other firmware, console imports and concurrent static-buffer use remain unverified. Loading and freeing do not enable named strings or alter queued errors. Historical tables are generated only as regression-test inputs. + - [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. They follow [LibreSSL 2.5.4](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/asn1), reported for firmware 13.40 in [discussion #8981](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214). These throw where 2.5.4 has undefined behaviour: a null structure passed to `ASN1_STRING_data`, `ASN1_STRING_length` or `ASN1_STRING_type`, a null conversion output, missing nonempty data, oversized converted output and unrepresentable signed integers. As in 2.5.4, `ASN1_INTEGER_get(NULL)` returns 0 and `ASN1_STRING_to_UTF8` returns -1 for a null string, leaving the output alone; malformed input and allocation failure also return -1 and queue the ASN.1 error (129, 133, 134, 124, or 65, twice for a UTF8String copy). Conversion buffers come from the guest libc allocator. @@ -46,3 +60,5 @@ - [X509_LOOKUP_file and X509_STORE_add_lookup](../../../core/libs/prx/libSceLibreSsl/src/X509Store.cpp) are unverified on PS5. The 80-byte method, the 32-byte lookup, the stack growth and the results follow LibreSSL 2.5.4 [`by_file.c`, `x509_lu.c`](https://github.com/libressl/openbsd/tree/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509) and `stack.c`, unchanged in 2.7. The file method's `ctrl`, which loads PEM certificates and CRLs, throws: there is no certificate model. Callbacks of a guest-supplied method (`new_item`, `shutdown`, `free`) throw instead of being called, and a null store, method or listed lookup throws where LibreSSL faults - [X509_VERIFY_PARAM_set1_host](../../../core/libs/prx/libSceLibreSsl/src/X509Store.cpp) is unverified on PS5. It follows `int_x509_param_set_hosts` of [LibreSSL 2.7](https://github.com/libressl/openbsd/blob/OPENBSD_6_3/src/lib/libcrypto/x509/x509_vpm.c), the first release with this export, and copies the name with the guest `strndup`. LibreSSL 2.8 also marks the parameters as poisoned when the call fails, through a field the 2.5.4 to 2.7 layout lacks, so a failed call leaves no mark. Null verify parameters or identity throw where LibreSSL faults + +- [X509_EXTENSION_get_critical, X509_EXTENSION_get_data and X509_EXTENSION_get_object](../../../core/libs/prx/libSceLibreSsl/Export.cpp) - signatures, null-input results, borrowed pointers and the signed `critical > 0` rule follow [LibreSSL 2.5.4 x509_v3.c](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_v3.c). The 24-byte LP64 extension layout (object at 0, 32-bit critical at 8, data at 16) follows [x509.h](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509.h#L196-L201). [Published firmware 13.40 measurements](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214) identify libSceLibreSsl as 2.5.4, but these getters and this layout have not been independently checked on a console. The getters do not validate or decode the referenced ASN.1 objects. diff --git a/docs/dev/debt/libSceLibreSsl3.md b/docs/dev/debt/libSceLibreSsl3.md index f50c9ec2cd..67eef01d34 100644 --- a/docs/dev/debt/libSceLibreSsl3.md +++ b/docs/dev/debt/libSceLibreSsl3.md @@ -1,8 +1,14 @@ # libSceLibreSsl3 technical debt +### Functional + +- [OpenSSL_version](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - other firmware revisions are unverified. Selectors 0 through 5 follow the public firmware 13.40 measurements in [discussion #8981](https://github.com/boykopovar/AnyPS5/discussions/8981#discussioncomment-19111214); unknown selectors follow LibreSSL 4.0.0 and return `not available`. No independent console test was run. + +- [ERR_load_crypto_strings and ERR_peek_last_error](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) have no console execution verification. [Firmware 13.42 modules run on a PC](https://github.com/boykopovar/AnyPS5/pull/9535#issuecomment-6407060914) retained numeric-only output after loaders and matched queue order/overflow; whether the two firmware modules share a queue was not tested. This implementation reads libSceLibreSsl's calling-thread queue. `ERR_print_errors_cb` remains throwing because guest thread identifiers and diagnostic metadata are unverified. + ### Unknown function info -- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 77 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA` and `EVP_PKEY_get0_DH`, 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`. +- [libSceLibreSsl3 pending exports](../../../core/libs/prx/libSceLibreSsl3/Export.cpp) - 76 named callable exports, excluding `CRYPTO_memcmp`, `MD5`, `SHA1`, `d2i_ASN1_INTEGER`, `EVP_PKEY_up_ref`, `EC_KEY_up_ref`, `RSA_up_ref`, `EVP_PKEY_get0_RSA`, `EVP_PKEY_get0_DSA`, `EVP_PKEY_get0_DH` and `OpenSSL_version`, 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 @@ -18,9 +24,11 @@ - [X509_VERIFY_PARAM_set_flags](../../../core/libs/prx/libSceLibreSsl3/src/X509VerifyParam.cpp) (libSceLibreSsl3) - nothing is checked on the console. It ORs the flags into `flags` and returns 1, as both [LibreSSL 2.5.4](https://github.com/libressl/openbsd/blob/2e2c612066aa993717528a0249c15c51128d61cb/src/lib/libcrypto/x509/x509_vpm.c) and [4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/x509/x509_vpm.c) `x509_vpm.c` do. They differ when `EXPLICIT_POLICY`, `INHIBIT_ANY` or `INHIBIT_MAP` is set while `POLICY_CHECK` is neither set nor requested: 2.5.4 also adds `POLICY_CHECK` and 4.0.0 does not. The console's choice is unknown, so that case throws through `NotImplemented_nid_no_patch` and leaves the parameters unchanged. It writes libSceLibreSsl's 2.5.4 `LibreSslX509VerifyParam`, whose `flags` is at offset 24 as in 4.0.0. Null parameters throw. Closes when: the console's policy behaviour is measured. - [sk_new, sk_push and sk_free](../../../core/libs/prx/libSceLibreSsl3/src/Stack.cpp) (libSceLibreSsl3) - follow [LibreSSL 4.0.0](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/stack/stack.c) `stack.c` with libSceLibreSsl's 32-byte `_STACK` layout, which `stack_local.h` keeps there. Stacks and element arrays come from the guest heap, so the forwarded `sk_pop_free` can release them. Nothing is checked on the console. Where LibreSSL faults or overflows, they throw: `sk_push` on a null stack, on a negative element count, on a null element array it does not regrow, or when the element array cannot double without overflowing `int` +- [ASN1_TIME_diff](../../../core/libs/prx/libSceLibreSsl3/src/Time.cpp) has no console ABI or behavior verification. Its signature and date rules follow LibreSSL 4.0.0 [a_time.c](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1/a_time.c), [a_time_tm.c](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1/a_time_tm.c) and [a_time_posix.c](https://github.com/libressl/openbsd/blob/ce488b78e0bd994780569df718596b9776ff681a/src/lib/libcrypto/asn1/a_time_posix.c), using the existing 24-byte string layout. Null inputs use the host wall clock; the console clock relationship is unverified. Null output pointers throw after valid input parsing, where the reference dereferences them. Closes when: measured on a PS5. + ### Additional callable export inventory -- [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp) declare 176 callable exports: 50 named and 126 known only by NID. Every entry throws. Parameterless `int` declarations are unknown-signature placeholders, not verified ABI contracts. Names/NIDs match the 13.60 export table. No functional implementation or title unblock is claimed. +- [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp) declare 175 callable exports: 165 named and 10 known only by NID. Every entry throws. Parameterless `int` declarations are unknown-signature placeholders, not verified ABI contracts. Names/NIDs match the 13.60 export table. No functional implementation or title unblock is claimed. | Name | NID | | --- | --- | @@ -191,7 +199,6 @@ | `tls_config_insecure_noverifyname` | `wNQT6Zn4UG4` | | `X509_EXTENSION_free` | `wbJDMMsje6s` | | `ASN1_TYPE_free` | `whL1IRBxFZA` | -| `ASN1_TIME_diff` | `xBkcXV1OqWQ` | | `ECDSA_SIG_set0` | `xVv3qlSRldc` | | `OBJ_obj2txt` | `xZ1BME5qDv0` | | `SSL_set_quic_transport_params` | `xf6anoZpWRM` | @@ -201,4 +208,4 @@ | `ECDSA_SIG_get0` | `z8fsXF8JuL0` | | `EVP_MD_block_size` | `zCa75KLWWn4` | -- 116 of the 126 exports the 13.60 table lists only by NID carry, in the table above and in [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp), the LibreSSL names whose `tools/nid_names.py` hash equals the NID; the names were taken from the identifiers of the LibreSSL 3.8.4 and 4.0.0 `include`, `crypto`, `ssl` and `tls` sources. A name match does not show that the console function has LibreSSL's prototype or behavior; each keeps the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. `AES_wrap_key`, `ECDSA_SIG_free`, `ECDSA_do_verify`, `ERR_load_ERR_strings`, `ERR_print_errors_fp` and `i2d_PUBKEY` also hash to the NIDs of six `unknown` rows, which keep their NID names; `PBYz0kpXwg8` hashes to no identifier of those sources. +- 115 of the 126 exports the 13.60 table lists only by NID carry, in the table above and in [Additional exports](../../../core/libs/prx/libSceLibreSsl3/AdditionalExports.cpp), the LibreSSL names whose `tools/nid_names.py` hash equals the NID; the names were taken from the identifiers of the LibreSSL 3.8.4 and 4.0.0 `include`, `crypto`, `ssl` and `tls` sources. A name match does not show that the console function has LibreSSL's prototype or behavior; each keeps the no-parameter import-resolution convention and throws through `NotImplemented_nid_no_patch`. `AES_wrap_key`, `ECDSA_SIG_free`, `ECDSA_do_verify`, `ERR_load_ERR_strings`, `ERR_print_errors_fp` and `i2d_PUBKEY` also hash to the NIDs of six `unknown` rows, which keep their NID names; `PBYz0kpXwg8` hashes to no identifier of those sources. diff --git a/docs/dev/debt/libSceLoginService.md b/docs/dev/debt/libSceLoginService.md index db043fd9c7..3a3bfa14ce 100644 --- a/docs/dev/debt/libSceLoginService.md +++ b/docs/dev/debt/libSceLoginService.md @@ -2,8 +2,8 @@ ### Silent stubs -- [sceLoginServiceInitialize, sceLoginServiceTerminate](../../../core/libs/prx/libSceLoginService/Export.cpp) (libSceLoginService) - return success and keep no login-service state: AnyPS5 has no PSN login service. EA SPORTS FC 27 (PPSA34015) loads the module with `sceSysmoduleLoadModule(0xe3)` and calls `sceLoginServiceInitialize` with no arguments; a missing `libSceLoginService.prx` stops Windows start-up with `Failed to load module` / `GetLastError: 126` +- [sceLoginServiceInitialize, sceLoginServiceTerminate](../../../core/libs/prx/libSceLoginService/Export.cpp) (libSceLoginService) - return success and track initialization state: AnyPS5 has no PSN login service. EA SPORTS FC 27 (PPSA34015) loads the module with `sceSysmoduleLoadModule(0xe3)` and calls `sceLoginServiceInitialize` with no arguments; a missing `libSceLoginService.prx` stops Windows start-up with `Failed to load module` / `GetLastError: 126` ### Unknown function info -- [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. +- [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. diff --git a/docs/dev/debt/libSceMat.md b/docs/dev/debt/libSceMat.md new file mode 100644 index 0000000000..ca692fc1d8 --- /dev/null +++ b/docs/dev/debt/libSceMat.md @@ -0,0 +1,58 @@ +# libSceMat technical debt + +### Unknown function info + +The 52 exports in [Export.cpp](../../../core/libs/prx/libSceMat/Export.cpp) come from the `libSceMat` list in [claimore22/ps5rs](https://github.com/claimore22/ps5rs/blob/5f252a658ff470bcb2b6cd15205fd8ca64ae4477/data/stubs/by_library/libSceMat_nosubmission.txt) (commit 5f252a658ff470bcb2b6cd15205fd8ca64ae4477), a catalogue of PS5 symbols per library whose provenance the project does not document, and no PS5 export inventory of this module is available to confirm the list is complete or exact. Every name was kept only because its hash equals the listed NID. Prototypes and behaviour are unverified: every function takes no parameters in the import-resolution convention and throws through `NotImplemented_nid_no_patch`. + +- [sceMatMemoryPoolReserve](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID +v5r0-k24j0) +- [sceMatUninitialize](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 0kgbiCpmb1M) +- [sceMatTagVirtualMemory](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 0v6+W4ulXJ0) +- [sceMatMspaceCreate](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 1HWnwXRCrdI) +- [sceMatUnregisterResource](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 1TVH3eVt1Mg) +- [sceMatMprotect](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 3AI9N7clwCs) +- [sceMatMapDirectMemory](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 3yTx5JA+cgg) +- [sceMatInitialize](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 5Zlr3joORIE) +- [sceMatNewFrame](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 6gOfuHe419U) +- [sceMatRegisterResourceType](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 71P0F8eJfPk) +- [sceMatFree](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 9W19mAu8Bzc) +- [sceMatSetUserCallstackCapture](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID 9m6mmioPTOo) +- [sceMatMemoryPoolMove](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID C++Y3xup+O0) +- [sceMatRegisterGroup](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID CEcz6AdSUpw) +- [sceMatMemoryPoolExpand](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID Cnn3HtdAFOU) +- [sceMatMemoryPoolBatch](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID HE8VNl2+1Fw) +- [sceMatAllocPoolMemory](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID I8sUe4XPSvU) +- [sceMatAgcUnregisterOwnerAndResources](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID JyFq+FZ6ujw) +- [sceMatReleasePhysicalMemory](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID LIVclwLLnyM) +- [sceMatMemoryPoolDecommit](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID N9YwXumgOfo) +- [sceMatSetPrtAperture](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID NDbQMSkpoXg) +- [sceMatFreePoolMemory](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID NHjHSABXkLo) +- [sceMatUnmapMemory](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID O19gIiIute4) +- [sceMatMspaceDestroy](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID PXtTCOohxj0) +- [sceMatAlloc](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID QrzsrnqGy9g) +- [sceMatReallocBegin](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID RWNATmy2cug) +- [sceMatReallocEnd](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID XC2CuQMB9js) +- [sceMatAgcRegisterResource](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID YIFDiAInu7Y) +- [sceMatWriteBookmark](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID Z5-0uTMnWnE) +- [sceMatBatchMap](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID b-BtawOVCmQ) +- [sceMatTagPool](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID cY6ZpCec3Oo) +- [sceMatAgcRegisterOwner](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID dl1xO-QP3C8) +- [sceMatUnregisterAllResourcesForOwner](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID gEBIkRrqDBA) +- [sceMatMtypeprotect](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID hVvUitUwrFs) +- [sceMatSetThreadInfo](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID k6QfiQvAEJc) +- [sceMatPushMarker](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID kd0VbEN5bIY) +- [sceMatUnregisterOwnerAndResources](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID lqHsrpuW4MI) +- [sceMatAgcUnregisterResource](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID n-zwIVhJMTY) +- [sceMatRegisterOwner](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID ptoJYu3CkyQ) +- [sceMatAgcUnregisterAllResourcesForOwner](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID qdLo7hti5rc) +- [sceMatPopMarker](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID quIxw751PLs) +- [sceMatPushMarkerStatic](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID rDtFuWpxAgM) +- [sceMatUpdateModuleList](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID spov0bD6PpM) +- [sceMatResizeCommsBuffer](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID srnAu7m5VKk) +- [sceMatReallocPoolMemory](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID tH+nf8PVkP8) +- [sceMatMemoryPoolCommit](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID tIBrrdbjJBA) +- [sceMatRegisterResource](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID uAxS1aVr6OY) +- [sceMatAllocPhysicalMemory](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID uNPUU9tAUw4) +- [sceMatReserveVirtualRange](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID vYS-YC+Cdkg) +- [sceMatTagAllocation](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID xDzWWofyEtA) +- [sceMatFreeRange](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID yO6vE0Y9eiY) +- [sceMatCalloc](../../../core/libs/prx/libSceMat/Export.cpp) (libSceMat) - unknown signature (NID z9e-MCsVZZQ) diff --git a/docs/dev/debt/libSceMbus.md b/docs/dev/debt/libSceMbus.md index 89c4ff5e82..6a51009a7e 100644 --- a/docs/dev/debt/libSceMbus.md +++ b/docs/dev/debt/libSceMbus.md @@ -22,7 +22,7 @@ - [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 has no defined return value and its recovered behavior is to clear memory. The second argument is treated here as a byte count, but the source analysis in [merged PR #9137](https://github.com/boykopovar/AnyPS5/pull/9137) does not record firmware, function offset or instructions establishing whether the native routine clamps it to the 16-byte callback table; this must be verified before claiming full parity. A null callback pointer with a nonzero size is not validated and is not measured on a console. 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 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 has no defined return value and its recovered behavior is to clear memory. The second argument is treated here as a byte count, but the source analysis in [merged PR #9137](https://github.com/boykopovar/AnyPS5/pull/9137) does not record firmware, function offset or instructions establishing whether the native routine clamps it to the 16-byte callback table; this must be verified before claiming full parity. The 11.00 wrapper ignores a null callback pointer before clearing, and the host now does the same; that case is not measured on a console and the 13.42 behaviour remains unverified. 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. diff --git a/docs/dev/debt/libSceMouse.md b/docs/dev/debt/libSceMouse.md index df77103a23..3dbb311dfc 100644 --- a/docs/dev/debt/libSceMouse.md +++ b/docs/dev/debt/libSceMouse.md @@ -21,8 +21,9 @@ - [sceMouseDisconnectAll, sceMouseVirtualDeviceAddDevice, sceMouseVirtualDeviceDeleteDevice and sceMouseVirtualDeviceGetCursor](../../../core/libs/prx/libSceMouse/InventoryExports.cpp) - the names and NIDs are recorded in the [13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/e495d3cf24aea17cefda59decdfda9e3e4e3e5ff/data/firmware-13.60/libscemouse-acdd5b5e23.jsonl). Signatures and behavior remain unknown; the parameterless `int` declarations only resolve imports and every call throws. Not measured on a console. - [sceMouseConnectPort, sceMouseDebugGetDeviceId, sceMouseDeviceOpen, sceMouseDisconnectDevice, sceMouseDisconnectPort, sceMouseGetDeviceInfo, sceMouseMbusInit, sceMouseSetHandType, sceMouseSetPointerSpeed and sceMouseSetProcessPrivilege](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceMouse.c); the no-argument prototypes follow [shadPS4's PS4 stubs](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/mouse/mouse.cpp#L34-L146), not measured on a console. These declarations have no parameters solely to resolve known imports and make no ABI claim. -- [sceMouseReadAbsolutePosition, sceMouseSetAbsoluteHighPrecisionMode and sceMouseSetAbsoluteVirtualScreenSize](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - the names match unresolved NIDs reported by a title, but their signatures and behavior are not verified. These no-argument declarations only resolve imports; the exports throw if called. ### Functional -- [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 +- [Publish](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - consecutive motion-only reports with the same buttons are summed into the newest unread entry, so a title that reads a few entries per frame gets current motion instead of a full queue of old reports. Reports with no motion and no button, wheel, tilt or connection change are dropped. Per-report granularity and the console's own report rate are not reproduced + +- [sceMouseResetAbsolutePosition, sceMouseSetAbsoluteVirtualScreenSize, sceMouseSetAbsoluteHighPrecisionMode, sceMouseReadAbsolutePosition](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - follow firmware 13.42 [run natively](https://github.com/boykopovar/AnyPS5/issues/11477#issuecomment-6502712241) with the driver stubbed: checks and order, screen sizes and rescale, the mode byte, the curve with its fraction, the 40-byte record. Host choices, after [XatiX's title-tested version](https://github.com/boykopovar/AnyPS5/issues/11477#issuecomment-6503782633): each host event is one sample with unscaled counts, in its own queue, so `sceMouseRead`'s motion merging does not feed the curve. A read takes all pending samples; past `num`, same-button neighbours share a record, each still moving the pointer, and the last `num` records are kept. Speed is taken as 4, the hand swap and flagged bit never apply, and a disconnected mouse stands in for a zero driver status diff --git a/docs/dev/debt/libSceMsgDialog.native.md b/docs/dev/debt/libSceMsgDialog.native.md index 48eb0c188b..e52604f7c0 100644 --- a/docs/dev/debt/libSceMsgDialog.native.md +++ b/docs/dev/debt/libSceMsgDialog.native.md @@ -6,4 +6,4 @@ ### Unknown function info -- [sceMsgDialogGetResult](../../../core/libs/prx/libSceMsgDialog.native/Export.cpp) (libSceMsgDialog.native) returns the button id the player picked in a shown dialog, and 1 for every other dialog and for one the title closed; the result code is always 0. Which button ids and result codes a PS5 reports after a close or a cancel is not measured. `sceMsgDialogOpen` closes a dialog that is still waiting and shows the new one, instead of failing as libSceMsgDialog does with busy. Closes when: measured on a PS5 +- [sceMsgDialogGetResult](../../../core/libs/prx/libSceMsgDialog.native/Export.cpp) (libSceMsgDialog.native) returns the button id the player picked in a shown dialog, and 1 for every other dialog and for one the title closed; the result code is always 0. Which button ids and result codes a PS5 reports after a close or a cancel is not measured. `sceMsgDialogOpen` closes a dialog that is still waiting and shows the new one, instead of failing as libSceMsgDialog does with busy. Returning `0x80b8000d` (`COMMON_DIALOG_ERROR_ARG_NULL`) for a non-null unwritable result pointer is a host-safety fallback; on a console an unmapped pointer faults and its answer is not measured. Closes when: measured on a PS5 diff --git a/docs/dev/debt/libSceNKWebKit.md b/docs/dev/debt/libSceNKWebKit.md index 5be53cc5d4..e181103836 100644 --- a/docs/dev/debt/libSceNKWebKit.md +++ b/docs/dev/debt/libSceNKWebKit.md @@ -1,21 +1,28 @@ # libSceNKWebKit technical debt +Inventory prose retains historical counts; use the [generated progress report](https://boykopovar.github.io/AnyPS5/progress.html) for current totals. + ### Unknown function info - [WebKit API objects](../../../core/libs/prx/libSceNKWebKit/src/Object.hpp) (libSceNKWebKit) - `WK*` objects are host objects numbered with `API::Object::Type` as [APIObject.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebKit/Shared/API/APIObject.h) builds for PlayStation, with ApplicationManifest, InspectorExtension, WebExtension and WebAuthn types off and PopupMenuItem and AccessibilityObject on per [OptionsPlayStation.cmake](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/cmake/OptionsPlayStation.cmake); the console module version, its type numbers and its object layouts are unverified, so these objects cannot be passed to a console WebKit module. A null or wrong-type reference throws where WebKit dereferences it. - [WTF and WebCore value layouts](../../../core/libs/prx/libSceNKWebKit/src/WtfStringAbi.hpp) (libSceNKWebKit) - `WTF::String`, `StringImpl`, `StringView`, `CString`, `URL` and the WebCore geometry, transform and `Color` layouts follow the release 64-bit headers at the [pinned source](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/wtf/text/StringImpl.h) and are pinned with `static_assert`; they are tested on Windows, not against console binaries. `StringImpl::destroy`, atom and symbol strings and external buffers still throw, so a string whose last reference guest code drops terminates. - [Media preferences](../../../core/libs/prx/libSceNKWebKit/src/Preferences.cpp) (libSceNKWebKit) - unset `MediaSourceEnabled`, `EncryptedMediaAPIEnabled` and `SourceBufferChangeTypeEnabled` getters throw, and feature-key setters for `EncryptedMediaAPIEnabled`, `SourceBufferChangeTypeEnabled` and `WebMParserEnabled` throw, because the PlayStation media player configuration behind `ENABLE_MEDIA_SOURCE` and `ENABLE_ENCRYPTED_MEDIA` is not in the pinned source. Other defaults evaluate [UnifiedWebPreferences.yaml](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml) for PlayStation; downstream console overrides are unverified. +- [FastBitVector exports](../../../core/libs/prx/libSceNKWebKit/src/WtfFastBitVector.cpp) have no console ABI or behavior verification. The 16-byte owner (word pointer at 0, 64-bit bit count at 8), range clearing, growth and copying follow the paired header and [PS5 11.x WebKit source](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WTF/wtf/FastBitVector.cpp). `resizeSlow` preserves the stored bit count and unused bits of existing words; new words are zeroed. Allocation uses the module's `fastMalloc` and `fastFree`. Invalid ranges, null accessed storage, shrinking the word count and overflowing the upstream round-up throw instead of reproducing assertion failures or invalid memory access. Closes when: compared with a console module. + ### Functional +- [Constant-return exports](../../../core/libs/prx/libSceNKWebKit/src/ConstantExports.cpp) (libSceNKWebKit) - 137 exports answer with a fixed value in the PS5 11.00 module: 111 execute `xor eax,eax; ret`, 13 `mov eax,imm32; ret` and 13 `mov al,1; ret` (addresses and bodies from the 11.00 `libSceNKWebKit.sprx`, SHA-256 `3af992253665387feeb5df90ff27dff683e3aa270cc4d92a0535b46ab18fa20d`; NIDs recomputed from the declared names). The 124 EAX-writing bodies measure the 32-bit return; the 13 `mov al,1` bodies write only the low byte, so for them only AL is measured and the upper bits are unspecified. The declared prototypes stay the no-parameter `int` placeholders, so no other width, pointer or floating-point return is claimed. Only 11.00 was measured. + - [JavaScriptCore C API](../../../core/libs/prx/libSceNKWebKit/src/JscContext.cpp) (libSceNKWebKit) - `JS*` runs on QuickJS-NG (`3rdparty/quickjs`), not JavaScriptCore: there is no JIT and no conservative stack scan, and error messages, stack traces and property order come from QuickJS. An unprotected `JSObjectRef` held only in a native variable or heap structure across `JSGarbageCollect` is collected, where JavaScriptCore keeps it through its stack scan; `JSValueProtect`, a marking constraint, a JavaScript reference or an active callback frame keep it alive. `JSStringRef` values are `libSceJsc` strings. - [Dictionary key order](../../../core/libs/prx/libSceNKWebKit/src/Dictionary.cpp) (libSceNKWebKit) - `WKDictionaryCopyKeys` returns keys sorted by UTF-16 code unit, not in the `WTF::HashMap` iteration order of [WKDictionary.cpp](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebKit/Shared/API/c/WKDictionary.cpp). - [Out-of-line colors](../../../core/libs/prx/libSceNKWebKit/src/WebCoreColor.cpp) (libSceNKWebKit) - `WebCore::Color` operations on an out-of-line value (extended color spaces in [Color.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/graphics/Color.h)) throw; only inline 8-bit sRGBA values are handled. - [WTF host services](../../../core/libs/prx/libSceNKWebKit/src/WtfSystem.cpp) (libSceNKWebKit) - `fastMalloc`, `bmalloc` and Gigacage allocations come from the host allocator, and `numberOfProcessorCores`, `ramSize`, the clocks and random values come from the host, not the console; they throw when the host cannot answer. `WTFCrash` and failed assertions abort the process. +- [WebCore::ResourceResponseBase](../../../core/libs/prx/libSceNKWebKit/src/ResourceResponse.cpp) (libSceNKWebKit) - the object layout in [ResourceResponseAbi.hpp](../../../core/libs/prx/libSceNKWebKit/src/ResourceResponseAbi.hpp) comes from [ResourceResponseBase.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/network/ResourceResponseBase.h) and is not checked against the PS5 module: `URL` at 0, four `AtomString` and the 64-bit expected length up to offset 80, then a 32-byte `HTTPHeaderMap` (two vectors of inline capacity 0 as in [HTTPHeaderMap.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/network/HTTPHeaderMap.h)). Only these fields are modelled because `std::optional` follows the `Box` and the size of the PS5 `CertificateInfo` is unknown, so the offsets of `m_httpStatusCode`, `m_isNull`, `m_initLevel`, `m_type`, `m_source` and the flags are not established. The getters (`url`, `mimeType`, `expectedContentLength`, `textEncodingName`, `httpStatusText`, `httpVersion`, `httpHeaderFields`, `isInHTTPFamily`) skip `lazyInit` because the curl [ResourceResponse.h](https://github.com/neonmodder123/PS5-WebKit-11.x/blob/80f77e94c63ee7202eec3d4b5b1a021dd89011ba/Source/WebCore/platform/network/curl/ResourceResponse.h) has no `platformLazyInit`; the PS5 `ResourceResponse` is not known to match. `isInHTTPFamily` reads the valid bit of `URL::bits` and the scheme text, not the cached `m_protocolIsInHTTPFamily` bit, whose position is unknown. The 30 other exports (`ResourceResponseBase` constructors, `setURL`, `setType`, `setExpectedContentLength`, `setHTTPStatusCode`, `httpStatusCode`, `source`, `isSuccessful`, `isRedirection`, `isHTTP09`, `isAttachment*`, `suggestedFilename`, `sanitize*`, `syntheticRedirectResponse`, `cacheControlContains*`, `hasCacheValidatorFields`, `lazyInit`, `httpHeaderField*`, `addHTTPHeaderField*`, `setHTTPHeaderField*`) still throw `NotImplemented_nid_no_patch`: they write `m_isNull`, read fields after `CertificateInfo`, or need `HTTPHeaderMap`, `CertificateInfo`, `Vector` or `ResourceResponse` layouts. ### Known export names; ABI prototypes unverified -- The 130 remaining WebCore, WebKit C API, JavaScriptCore, Inspector and WTF 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). Each is named after the symbol the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) gives for its NID, and the NID of that name matches. 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 127 remaining WebCore, WebKit C API, JavaScriptCore, Inspector and WTF 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). Each is named after the symbol the [verified NID name database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) gives for its NID, and the NID of that name matches. 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. ### Older callable exports @@ -25,7 +32,7 @@ - The [revision-pinned identity inventory](https://gist.github.com/kyon-eth/d44995f181e1818977bd38f0e6fc0f34/8afcb14b0707a09b6274ff7f7bcf33ef46cfe2e7) records the module, library, symbol type and NIDs for the declarations in [Export.cpp](../../../core/libs/prx/libSceNKWebKit/Export.cpp); it is a metadata summary rather than independent verification. For the 255 absent from the public catalogue, [per-image ELF records](https://gist.github.com/kyon-eth/147be8dd556e9cced2577ccbed548972/1a659ecfe819e724675991a574a1aebb5fbb48e5) include the raw qualified names, module and library identities, symbol type, values and sizes; the record file SHA-256 is `44bdc4d6073192a8f41b7be359b75364b3d41dbbd11ce649924b8cebdd4ffe51`. Image labels are not authenticated. -- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7573 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. +- [Additional callable exports](../../../core/libs/prx/libSceNKWebKit/Export.cpp) - 7437 of the 8703 inventory declarations listed below throw through `NotImplemented_nid_no_patch`; signatures and behavior are unverified. The later 13.60-labelled image contains all 8703; 8448 match the public 3.20 catalogue, while 255 are absent and their presence in retail 3.20 is unverified. Per-image records show the latter in a separate 3.20-labelled image, but neither image label is authenticated. Identified names match NIDs in the [public symbol database](https://github.com/zecoxao/sce_symbols/blob/2883963a0a514ba6e77407a08ef4730a65e88254/aerolib.csv) or [supplemental catalog](https://github.com/claimore22/ps5rs/blob/2304be5131221bae0dac24b9767cabffda33f747/data/nids.csv). Data exports need separate analysis. | Name | NID | | --- | --- | @@ -45,7 +52,6 @@ | `WKAXObjectFrame` | `huPnixU5dNc` | | `WKAXObjectPage` | `KrVXrd3WyfM` | | `WKAXObjectRole` | `pt0Eyg26hqw` | -| `WKAccessibilityCanUseSecondaryAXThread` | `7bL9hbEC1w0` | | `WKAccessibilityEnableEnhancedAccessibility` | `SFLp7Zjq3n8` | | `WKAccessibilityEnhancedAccessibilityEnabled` | `b5bQfU9tUKo` | | `WKAccessibilityFocusedObject` | `WqnB6ITORdg` | @@ -78,17 +84,6 @@ | `WKBackForwardListItemCopyURL` | `LcWGak3Mp3c` | | `WKBundleAddOriginAccessAllowListEntry` | `Ec6dqG56Kog` | | `WKBundleBackForwardListClear` | `uDz2mwaGQxc` | -| `WKBundleBackForwardListCopyItemAtIndex` | `w7TgSPl+20I` | -| `WKBundleBackForwardListGetBackListCount` | `jbkqtarv--A` | -| `WKBundleBackForwardListItemCopyChildren` | `FbPgeyqyKxw` | -| `WKBundleBackForwardListItemCopyOriginalURL` | `A3HuqQOmdmA` | -| `WKBundleBackForwardListItemCopyTarget` | `eNlDM--QehI` | -| `WKBundleBackForwardListItemCopyTitle` | `oA7TAoIYksg` | -| `WKBundleBackForwardListItemCopyURL` | `lW7Qtc4Q4bI` | -| `WKBundleBackForwardListItemHasCachedPageExpired` | `AMLRfnzY1CU` | -| `WKBundleBackForwardListItemIsInBackForwardCache` | `LKMVeWFdRNk` | -| `WKBundleBackForwardListItemIsSame` | `Q7y-M7olJkc` | -| `WKBundleBackForwardListItemIsTargetItem` | `6yQTKzljpcM` | | `WKBundleClearHistoryForTesting` | `4XbhuyqB6pA` | | `WKBundleClearResourceLoadStatistics` | `zm7AblpGSLk` | | `WKBundleCreateWKDataFromUInt8Array` | `IOaozfGUAFI` | @@ -96,7 +91,6 @@ | `WKBundleDOMWindowExtensionGetFrame` | `VN5TX+cUFf4` | | `WKBundleDOMWindowExtensionGetScriptWorld` | `CFtAcs7A434` | | `WKBundleExtendClassesForParameterCoder` | `11DvPWLfwtU` | -| `WKBundleFileHandleCreateWithPath` | `wWExpSMGya0` | | `WKBundleFrameAllowsFollowingLink` | `pmZi4yM8pxM` | | `WKBundleFrameCallShouldCloseOnWebView` | `N+icA-jvTHs` | | `WKBundleFrameClearOpener` | `5XkPh73uOas` | @@ -112,8 +106,6 @@ | `WKBundleFrameCopySecurityOrigin` | `-s98d979pPQ` | | `WKBundleFrameCopySuggestedFilenameForResourceWithURL` | `kGAitTwtXq0` | | `WKBundleFrameCopyURL` | `xoaQMJQ9JFg` | -| `WKBundleFrameCopyWebArchiveFilteringSubframes` | `gkYgrCQKKrk` | -| `WKBundleFrameCopyWebArchive` | `kWLVY4iAwME` | | `WKBundleFrameCreateFrameHandle` | `URUBvWtDFRU` | | `WKBundleFrameCreateHitTestResult` | `OuVGXFusQvs` | | `WKBundleFrameFocus` | `kgGUjo9h2ao` | @@ -123,7 +115,6 @@ | `WKBundleFrameGetFrameLoadState` | `jB0PrHhQOZ8` | | `WKBundleFrameGetJavaScriptContextForWorld` | `dmRRBt+qPcs` | | `WKBundleFrameGetJavaScriptContext` | `eI2iByoOtYU` | -| `WKBundleFrameGetJavaScriptWrapperForFileForWorld` | `ERgb4lPId44` | | `WKBundleFrameGetJavaScriptWrapperForNodeForWorld` | `uvtLlI3dT8o` | | `WKBundleFrameGetJavaScriptWrapperForRangeForWorld` | `b5yYFj7O3kk` | | `WKBundleFrameGetPage` | `P8y9OgqOGts` | @@ -177,19 +168,12 @@ | `WKBundleNavigationActionGetShouldTryAppLinks` | `x2Bw3o33jBY` | | `WKBundleNodeHandleCopyDocumentFrame` | `o1BJoFZNBYo` | | `WKBundleNodeHandleCopyDocument` | `H7cknGH2DVo` | -| `WKBundleNodeHandleCopyHTMLFrameElementContentFrame` | `aHUVAX-1UOA` | | `WKBundleNodeHandleCopyHTMLIFrameElementContentFrame` | `tS9cMOapHWU` | -| `WKBundleNodeHandleCopyHTMLTableCellElementCellAbove` | `kFXtge4WNeg` | | `WKBundleNodeHandleCopySnapshotWithOptions` | `splXjoRt+jY` | -| `WKBundleNodeHandleCopyVisibleRange` | `WisAKbUAQO0` | | `WKBundleNodeHandleCreate` | `QJxnRATldPc` | | `WKBundleNodeHandleGetElementBounds` | `eQyyR4yPFKQ` | -| `WKBundleNodeHandleGetHTMLInputElementAutoFillAvailable` | `wgIKemgsVmc` | | `WKBundleNodeHandleGetHTMLInputElementAutoFillButtonBounds` | `hjt6ZGKuEfo` | -| `WKBundleNodeHandleGetHTMLInputElementAutoFillButtonEnabled` | `kuwpvpIwSXU` | | `WKBundleNodeHandleGetHTMLInputElementAutoFillButtonType` | `O9k0o6Iqejs` | -| `WKBundleNodeHandleGetHTMLInputElementAutoFilled` | `2atcqaHtnO0` | -| `WKBundleNodeHandleGetHTMLInputElementAutofilled` | `Q13L4R2-JYU` | | `WKBundleNodeHandleGetHTMLInputElementLastAutoFillButtonType` | `6Z7V+FlXSE8` | | `WKBundleNodeHandleGetHTMLInputElementLastChangeWasUserEdit` | `p3uXvmETGok` | | `WKBundleNodeHandleGetHTMLTextAreaElementLastChangeWasUserEdit` | `R+2FUI+qUB4` | @@ -216,8 +200,6 @@ | `WKBundlePageClose` | `Y9OWNvuSVJg` | | `WKBundlePageConfirmComposition` | `T4vOLGegdvQ` | | `WKBundlePageConfirmCompositionWithText` | `4eheJycBxI0` | -| `WKBundlePageCopyContextMenuAtPointInWindow` | `MgyEz5SwXAI` | -| `WKBundlePageCopyContextMenuItems` | `IKfdBNxQ0Nw` | | `WKBundlePageCopyGroupIdentifier` | `R9X0jHsH64E` | | `WKBundlePageCopyOriginsWithApplicationCache` | `cN8iJ612c0k` | | `WKBundlePageCopyRenderLayerTree` | `dCVd7TqyhYA` | @@ -239,7 +221,6 @@ | `WKBundlePageFlushPendingEditorStateUpdate` | `iG3Y7R3AFOM` | | `WKBundlePageForceRepaint` | `jaQ3h3J4zd4` | | `WKBundlePageGetAppCacheUsageForOrigin` | `qjrn0E95Tak` | -| `WKBundlePageGetBackForwardList` | `myZvY137tks` | | `WKBundlePageGetBackingScaleFactor` | `PS5n3xxFDXQ` | | `WKBundlePageGetInspector` | `Gl9UFzi54FQ` | | `WKBundlePageGetMainFrame` | `78YSUMVcvLY` | @@ -337,12 +318,10 @@ | `WKContextClearCurrentModifierStateForTesting` | `Yk2qJg3VRBY` | | `WKContextClearSupportedPlugins` | `XIckVUXyJsE` | | `WKContextClearVisitedLinks` | `HiehcAiEDCc` | -| `WKContextCopyPlugInAutoStartOriginHashes` | `G4+RWlGLEt0` | | `WKContextCreate` | `EnYKPlIQ0DA` | | `WKContextCreateWithConfiguration` | `XgehYXFKMDs` | | `WKContextCreateWithInjectedBundlePath` | `2lRXRLy4Pfc` | | `WKContextDisableProcessTermination` | `2jwy6+fFilk` | -| `WKContextDownloadURLRequest` | `DDW8PFTS+1g` | | `WKContextEnableProcessTermination` | `FJB0fZbTUWY` | | `WKContextGarbageCollectJavaScriptObjects` | `MBIrQeS3ILw` | | `WKContextGetAccessibilityEnabled` | `YREfLD1wwT0` | @@ -350,9 +329,7 @@ | `WKContextGetCacheModel` | `vdM7J5Xjh2A` | | `WKContextGetGeolocationManager` | `GJsL9usTdig` | | `WKContextGetGlobalStatistics` | `cLN+-CVotuU` | -| `WKContextGetIconDatabase` | `YQEg-76vfzE` | | `WKContextGetKeyValueStorageManager` | `XlTwFUI6oSI` | -| `WKContextGetMaximumNumberOfProcesses` | `5sd-RroOg-w` | | `WKContextGetNotificationManager` | `ZGEEHCN2FvE` | | `WKContextGetResourceCacheManager` | `N3bGYLHZl48` | | `WKContextGetStatisticsWithOptions` | `lihFGjz1c-c` | @@ -360,17 +337,6 @@ | `WKContextGetUsesSingleWebProcess` | `qZDRsY7kQ0s` | | `WKContextGetWebsiteDataStore` | `dDc6-iayAyg` | | `WKContextJavaScriptConfigurationFileEnabled` | `efD5N1zaabU` | -| `WKContextMenuCopySubmenuItems` | `PyySvnK-JGM` | -| `WKContextMenuItemCopyTitle` | `r0bRjKqIBxE` | -| `WKContextMenuItemCreateAsAction` | `FL+PbMcEVGI` | -| `WKContextMenuItemCreateAsCheckableAction` | `k0495LPSWPU` | -| `WKContextMenuItemCreateAsSubmenu` | `VpslPflOWdM` | -| `WKContextMenuItemGetChecked` | `42HcmdjlDmc` | -| `WKContextMenuItemGetEnabled` | `gEV0JIqdYdg` | -| `WKContextMenuItemGetTag` | `I849n0yCszo` | -| `WKContextMenuItemGetType` | `bIdRTPcRP7w` | -| `WKContextMenuItemGetUserData` | `oRR5HoDuB7Y` | -| `WKContextMenuItemSeparatorItem` | `yOyUp3CncVw` | | `WKContextMenuItemSetUserData` | `nsNNFzmax7s` | | `WKContextPostMessageToInjectedBundle` | `N93Nb5-YJsw` | | `WKContextPreconnectToServer` | `MQI+nL6CJkU` | @@ -380,7 +346,6 @@ | `WKContextRegisterURLSchemeAsCanDisplayOnlyIfCanRequest` | `rAU9SRsnG3E` | | `WKContextRegisterURLSchemeAsEmptyDocument` | `ad1-t6xKVtA` | | `WKContextRegisterURLSchemeAsSecure` | `6P04fQkPVec` | -| `WKContextResumeDownload` | `bIU19RyXVUg` | | `WKContextSetAccessibilityEnabled` | `05HZtE0FPg0` | | `WKContextSetAdditionalPluginsDirectory` | `BFnY5ZzK7-Q` | | `WKContextSetAlwaysUsesComplexTextCodePath` | `Y3Z2695WJ8E` | @@ -438,7 +403,6 @@ | `WKFrameCopyURL` | `3PiMrWNwmgw` | | `WKFrameCopyUnreachableURL` | `U4g7RC9mWd8` | | `WKFrameCreateFrameHandle` | `3MUp7-9-xkg` | -| `WKFrameCreateFrameInfo` | `iUg3lPaKd4E` | | `WKFrameGetCertificateInfo` | `L1HtA3ROv5k` | | `WKFrameGetFrameLoadState` | `82n4LwLnE3s` | | `WKFrameGetMainResourceData` | `wd3NsMeiyj4` | @@ -449,7 +413,6 @@ | `WKFrameInfoCreateFrameHandleRef` | `lQUbnRQ699M` | | `WKFrameIsDisplayingMarkupDocument` | `vwFaWsNoiQw` | | `WKFrameIsDisplayingStandaloneImageDocument` | `3HDn1r7lsGM` | -| `WKFrameIsFrameSet` | `BTOv1017JlU` | | `WKFrameIsMainFrame` | `fo3MaNfUAUE` | | `WKFramePolicyListenerDownload` | `IT1HfKWFBQ0` | | `WKFramePolicyListenerIgnore` | `0ppk-ExuWJA` | @@ -478,8 +441,6 @@ | `WKHitTestResultIsContentEditable` | `iioP3xFfzJ8` | | `WKIconDatabaseCheckIntegrityBeforeOpening` | `Q6e57L2Vat8` | | `WKIconDatabaseClose` | `D6zZXKyK2XI` | -| `WKIconDatabaseCopyIconDataForPageURL` | `ErWkFcqcBYE` | -| `WKIconDatabaseCopyIconURLForPageURL` | `PEWusNk7cIM` | | `WKIconDatabaseEnableDatabaseCleanup` | `Fg2BRITDYDg` | | `WKIconDatabaseReleaseIconForURL` | `pKUEWyAWkeI` | | `WKIconDatabaseRemoveAllIcons` | `LMXftlH0v2Y` | @@ -514,10 +475,7 @@ | `WKInspectorTogglePageProfiling` | `rTTWU3xHaCE` | | `WKKeyValueStorageManagerDeleteAllEntries` | `l+EnasCRj9M` | | `WKKeyValueStorageManagerDeleteEntriesForOrigin` | `uUq1q3l2pD0` | -| `WKKeyValueStorageManagerGetCreationTimeKey` | `peSbz8jaqpo` | | `WKKeyValueStorageManagerGetKeyValueStorageOrigins` | `oGlYuEguJWk` | -| `WKKeyValueStorageManagerGetModificationTimeKey` | `luMlXp-022c` | -| `WKKeyValueStorageManagerGetOriginKey` | `Bk3H5AlkwIM` | | `WKKeyValueStorageManagerGetStorageDetailsByOrigin` | `D2O5KU7KlMo` | | `WKKeyboardEventMake` | `UJXHm7AkPK8` | | `WKMessageListenerSendReply` | `9Rvb-lG-IYI` | @@ -544,11 +502,9 @@ | `WKOpenPanelParametersCopyAcceptedFileExtensions` | `13zq0KIVJJg` | | `WKOpenPanelParametersCopyAcceptedMIMETypes` | `FajrozYzGvw` | | `WKOpenPanelParametersCopyAllowedMIMETypes` | `YacE5rS1b3k` | -| `WKOpenPanelParametersCopyCapture` | `9HzWfg82K2w` | | `WKOpenPanelParametersCopySelectedFileNames` | `Oa8DwUsT8yg` | | `WKOpenPanelParametersGetAllowsDirectories` | `yZ8MpxQizWM` | | `WKOpenPanelParametersGetAllowsMultipleFiles` | `6JUqQUan7cM` | -| `WKOpenPanelParametersGetMediaCaptureType` | `UYD2jkWiCr0` | | `WKOpenPanelResultListenerCancel` | `O0YiYsUaleQ` | | `WKOpenPanelResultListenerChooseFiles` | `2ChSmFDC91I` | | `WKPageAreScrollbarAnimationsSuppressed` | `OQYQvowtC6I` | @@ -597,7 +553,6 @@ | `WKPageGetContext` | `fLfh+wZ12vQ` | | `WKPageGetEstimatedProgress` | `EEXgOrxJ1go` | | `WKPageGetFocusedFrame` | `a0SVtiDOBgM` | -| `WKPageGetFrameSetLargestFrame` | `dKIIZYGCGhQ` | | `WKPageGetGapBetweenPages` | `F-vtAS2uuns` | | `WKPageGetImageForFindMatch` | `KPPJLasPz+o` | | `WKPageGetInspector` | `QUMw9Z2Ia1I` | @@ -636,7 +591,6 @@ | `WKPageHorizontalRubberBandingIsEnabled` | `2S-hLekYP7g` | | `WKPageIsClosed` | `ErgHdDWCpVc` | | `WKPageIsContentEditable` | `Jub+F7uGS58` | -| `WKPageIsMockRealtimeMediaSourceCenterEnabled` | `AfexwR3oSR8` | | `WKPageIsPinnedToBottomSide` | `0Snx5Y8fnAk` | | `WKPageIsPinnedToLeftSide` | `KwQN78iZcJU` | | `WKPageIsPinnedToRightSide` | `NbMwXYcno7g` | @@ -739,27 +693,10 @@ | `WKPageValidateCommand` | `K82wly5zJoc` | | `WKPageVerticalRubberBandingIsEnabled` | `p-jMGxCpEF4` | | `WKPageWillHandleHorizontalScrollEvents` | `pYHt5lL67G8` | -| `WKPlugInInformationReplacementObscuredKey` | `O2dvtjs5sWU` | -| `WKPluginInformationBundleIdentifierKey` | `nBYJ4942-WQ` | -| `WKPluginInformationBundleShortVersionKey` | `C-blDyZOIa8` | -| `WKPluginInformationBundleVersionKey` | `w8rgJiyY10M` | -| `WKPluginInformationDefaultLoadPolicyKey` | `gx130ttraj0` | -| `WKPluginInformationDisplayNameKey` | `m2Sd6gEcz9o` | -| `WKPluginInformationFrameURLKey` | `nSm+f63zez0` | -| `WKPluginInformationHasSandboxProfileKey` | `h+E3nihI2mM` | -| `WKPluginInformationMIMETypeKey` | `zN3TYAlQEsk` | -| `WKPluginInformationPageURLKey` | `-+9ekha4fN0` | -| `WKPluginInformationPathKey` | `xP6Sta4xjvs` | -| `WKPluginInformationPluginURLKey` | `BgnV9ciabNs` | -| `WKPluginInformationPluginspageAttributeURLKey` | `WKUanh-oVyc` | -| `WKPluginInformationUpdatePastLastBlockedVersionIsKnownAvailableKey` | `w5yd8587Xyo` | | `WKPopupMenuItemCopyText` | `xmQq6hB1vqE` | | `WKPopupMenuItemIsEnabled` | `DUF5lHKpf6o` | | `WKPopupMenuItemIsItem` | `w6uemFn00hc` | | `WKPopupMenuItemIsSeparator` | `cIEUZ-pzXZo` | -| `WKPreferencesGetAccelerated2DCanvasEnabled` | `DrEP3qaThDE` | -| `WKPreferencesGetQTKitEnabled` | `8N-4nYLwXak` | -| `WKPreferencesGetTypeID` | `dyqLbVYkq3o` | | `WKPreferencesSetAccelerated2DCanvasEnabled` | `UEBk9iGqhPo` | | `WKPreferencesSetQTKitEnabled` | `6jkxaKVf01A` | | `WKProtectionSpaceCopyCertificateInfo` | `FdhooB4MqkE` | @@ -837,7 +774,6 @@ | `WKURLResponseHTTPStatusCode` | `V8sTDUESRVE` | | `WKURLResponseIsAttachment` | `73gjSG1YiTU` | | `WKUserContentExtensionStoreCompile` | `6g8aRptom6Y` | -| `WKUserContentExtensionStoreCreate` | `qlWLlgf0DyY` | | `WKUserContentExtensionStoreLookup` | `l8gsEeVfp+s` | | `WKUserContentURLPatternMatchesURL` | `JfpZWg77-Zw` | | `WKUserMediaPermissionCheckSetUserMediaAccessInfo` | `MBwkZncofuk` | @@ -1060,7 +996,6 @@ | `_ZN3JSC12SymbolObjectC1ERNS_2VMEPNS_9StructureE` | `potS1HOUEe4` | | `_ZN3JSC12SymbolObjectC2ERNS_2VMEPNS_9StructureE` | `tvnxawGcaOU` | | `_ZN3JSC12addErrorInfoEPNS_14JSGlobalObjectEPNS_8JSObjectEb` | `IGFTSBRJiHk` | -| `_ZN3JSC12isFromJSCodeEPv` | `Tb2NxhCO51o` | | `_ZN3JSC12iteratorStepEPNS_14JSGlobalObjectENS_15IterationRecordE` | `6LvU2UX265I` | | `_ZN3JSC12profiledCallEPNS_14JSGlobalObjectENS_15ProfilingReasonENS_7JSValueERKNS_8CallDataES3_RKNS_7ArgListERN3WTF8NakedPtrINS_9ExceptionEEE` | `Y2W62LZvRHA` | | `_ZN3JSC12profiledCallEPNS_14JSGlobalObjectENS_15ProfilingReasonENS_7JSValueERKNS_8CallDataES3_RKNS_7ArgListE` | `tmHQakReRvI` | @@ -1316,8 +1251,6 @@ | `_ZN3JSC22getInt32ArrayClassInfoEv` | `siYafTIfQdU` | | `_ZN3JSC22getUint8ArrayClassInfoEv` | `RMDMvfUBN9I` | | `_ZN3JSC22globalMemoryStatisticsEv` | `6+mBcQlHzNg` | -| `_ZN3JSC22optimizeNextInvocationENS_7JSValueE` | `QtEl0Z5ogAE` | -| `_ZN3JSC22optimizeNextInvocationEPNS_14JSGlobalObjectEPNS_9CallFrameE` | `9WSnQEt0mxM` | | `_ZN3JSC23AbstractSamplingCounter4dumpEv` | `NO8LGTKyCTk` | | `_ZN3JSC23JSModuleNamespaceObjectC1ERNS_2VMEPNS_9StructureE` | `fRcAtZp5MOQ` | | `_ZN3JSC23JSModuleNamespaceObjectC2ERNS_2VMEPNS_9StructureE` | `whnNR7vJo40` | @@ -1378,7 +1311,6 @@ | `_ZN3JSC32throwDOMAttributeGetterTypeErrorEPNS_14JSGlobalObjectERNS_10ThrowScopeEPKNS_9ClassInfoENS_12PropertyNameE` | `hcr91gUh9Mw` | | `_ZN3JSC33getHashMapBucketKeyValueClassInfoEv` | `FMhcXpjqzIc` | | `_ZN3JSC35getSomeBaselineCodeBlockForFunctionENS_7JSValueE` | `CYkcE1YQNeg` | -| `_ZN3JSC3DFG18getNumCompilationsEv` | `36QNLZCsGZ8` | | `_ZN3JSC41constructFunctionSkippingEvalEnabledCheckEPNS_14JSGlobalObjectERKNS_7ArgListERKNS_10IdentifierERKNS_12SourceOriginERKN3WTF6StringERKNSB_12TextPositionEiNS_24FunctionConstructionModeENS_7JSValueE` | `1usDFC2WXJ4` | | `_ZN3JSC41constructObjectFromPropertyDescriptorSlowEPNS_14JSGlobalObjectERKNS_18PropertyDescriptorE` | `RoiTvhwOekc` | | `_ZN3JSC4Heap10collectNowENS_15SynchronousnessENS_9GCRequestE` | `IbuWiz2xOr0` | @@ -1672,7 +1604,6 @@ | `_ZN3WTF12CountingLock10unlockSlowEv` | `y9CL7tutRKQ` | | `_ZN3WTF12CountingLock8lockSlowEv` | `QSRjO+sqoFU` | | `_ZN3WTF12isMainThreadEv` | `MKhFWAdCyCw` | -| `_ZN3WTF13FastBitVector10clearRangeEmm` | `q1+R1z2aE0E` | | `_ZN3WTF13MetaAllocator17addFreshFreeSpaceEPvm` | `RPLKRR7vgYE` | | `_ZN3WTF13MetaAllocator17currentStatisticsERKNS_6LockerINS_4LockEEE` | `3SW2Rs+BCYE` | | `_ZN3WTF13MetaAllocator17freeFreeSpaceNodeEPNS0_13FreeSpaceNodeE` | `sIQ2j-9PjgU` | @@ -1750,7 +1681,6 @@ | `_ZN3WTF14FileSystemImpl24fileSystemRepresentationERKNS_6StringE` | `sxavOcVMOFA` | | `_ZN3WTF14FileSystemImpl25createTemporaryZipArchiveERKNS_6StringE` | `9FdGIgYrVF4` | | `_ZN3WTF14FileSystemImpl25pathByAppendingComponentsENS_10StringViewERKNS_6VectorIS1_Lm0ENS_15CrashOnOverflowELm16ENS_10FastMallocEEE` | `k73D+1jaJZ0` | -| `_ZN3WTF14FileSystemImpl27isSafeToUseMemoryMapForPathERKNS_6StringE` | `7++HyggGH44` | | `_ZN3WTF14FileSystemImpl29makeSafeToUseMemoryMapForPathERKNS_6StringE` | `GzaGT-nX+vk` | | `_ZN3WTF14FileSystemImpl30appendFileContentsToFileHandleERKNS_6StringERi` | `bFLwUoQqNHM` | | `_ZN3WTF14FileSystemImpl34stringFromFileSystemRepresentationEPKc` | `Q5IWykchiD8` | @@ -1769,10 +1699,8 @@ | `_ZN3WTF14dataLogFStringEPKc` | `xQQ4VSubx-k` | | `_ZN3WTF14dupCloseOnExecEi` | `wQ9-tXakN90` | | `_ZN3WTF14isVersion4UUIDENS_10StringViewE` | `XbR6JVGM+0U` | -| `_ZN3WTF14setCloseOnExecEi` | `YebyCbJ7X7k` | | `_ZN3WTF15AtomStringTableD1Ev` | `YHjoCx7QaOs` | | `_ZN3WTF15AtomStringTableD2Ev` | `JxkGFAxlPa8` | -| `_ZN3WTF15AutomaticThread11shouldSleepERKNS_14AbstractLockerE` | `IRiggzlySno` | | `_ZN3WTF15AutomaticThread14threadDidStartEv` | `WgCZ0HyfQ3I` | | `_ZN3WTF15AutomaticThread16threadIsStoppingERKNS_14AbstractLockerE` | `s-PEdREq5oE` | | `_ZN3WTF15AutomaticThread4joinEv` | `SJux-9UF6Qo` | @@ -1836,7 +1764,6 @@ | `_ZN3WTF19ParallelEnvironmentC1EPFvPvEmi` | `Efn-a1tGPqw` | | `_ZN3WTF19ParallelEnvironmentC2EPFvPvEmi` | `ciXM3j2URKk` | | `_ZN3WTF19isCompilationThreadEv` | `qoO6VaMuvG0` | -| `_ZN3WTF19mimeTypeFromDataURLENS_10StringViewE` | `kie+nv-1HCM` | | `_ZN3WTF19numGraphemeClustersENS_10StringViewE` | `K7qAbz7P3rs` | | `_ZN3WTF20ConcurrentPtrHashSet15deleteOldTablesEv` | `K59xDVxbCyM` | | `_ZN3WTF20ConcurrentPtrHashSet5clearEv` | `sDuWnr1d9ko` | @@ -1880,8 +1807,6 @@ | `_ZN3WTF22CrossThreadTaskHandlerD0Ev` | `aMwGXRZ6024` | | `_ZN3WTF22CrossThreadTaskHandlerD1Ev` | `RQGUZHWVCLY` | | `_ZN3WTF22CrossThreadTaskHandlerD2Ev` | `F9bDxOEGeA8` | -| `_ZN3WTF22FastBitVectorWordOwner10resizeSlowEm` | `NetEd5QBLRw` | -| `_ZN3WTF22FastBitVectorWordOwner13setEqualsSlowERKS0_` | `qY895ndHoiI` | | `_ZN3WTF22TextBreakIteratorCache9singletonEv` | `m9nEKMAp80k` | | `_ZN3WTF22isMainThreadOrGCThreadEv` | `UHbf2fZIrxk` | | `_ZN3WTF23callOnMainThreadAndWaitEONS_8FunctionIFvvEEE` | `95FhkfvoGQM` | @@ -2121,9 +2046,7 @@ | `_ZN7WebCore10Pasteboard11writeStringERKN3WTF6StringES4_` | `6zGNzBYG3-8` | | `_ZN7WebCore10Pasteboard14readAllStringsERKN3WTF6StringE` | `28y+c5-yi2c` | | `_ZN7WebCore10Pasteboard14writePlainTextERKN3WTF6StringENS0_18SmartReplaceOptionE` | `QH664gYg5n8` | -| `_ZN7WebCore10Pasteboard15canSmartReplaceEv` | `VW8s48m0Ieo` | | `_ZN7WebCore10Pasteboard15writeCustomDataERKN3WTF6VectorINS_20PasteboardCustomDataELm0ENS1_15CrashOnOverflowELm16ENS1_10FastMallocEEE` | `DDmxBXZkpHo` | -| `_ZN7WebCore10Pasteboard16fileContentStateEv` | `fx-H-TjSi0Y` | | `_ZN7WebCore10Pasteboard20typesSafeForBindingsERKN3WTF6StringE` | `LyRrwIE5yMA` | | `_ZN7WebCore10Pasteboard22readStringInCustomDataERKN3WTF6StringE` | `gygzdpUOeyI` | | `_ZN7WebCore10Pasteboard28typesForLegacyUnsafeBindingsEv` | `fEC20q42qRw` | @@ -2427,7 +2350,6 @@ | `_ZN7WebCore11prefetchDNSERKN3WTF6StringE` | `3h1io5JBDcA` | | `_ZN7WebCore11startOfLineERKNS_15VisiblePositionE` | `lVxn1IsZyGE` | | `_ZN7WebCore11startOfWordERKNS_15VisiblePositionENS_9EWordSideE` | `jwIZMWyLmuY` | -| `_ZN7WebCore12BlobRegistry16blobRegistryImplEv` | `1sWmNDp+0To` | | `_ZN7WebCore12BlobRegistryC2ERKS0_` | `Dys3YgSazRQ` | | `_ZN7WebCore12BlobRegistryC2Ev` | `pmZ0xgBMeqo` | | `_ZN7WebCore12BlobRegistryD0Ev` | `GKDQvZF6sRk` | @@ -2450,7 +2372,6 @@ | `_ZN7WebCore12EventHandler14scrollOverflowENS_15ScrollDirectionENS_17ScrollGranularityEPNS_4NodeE` | `f0GV9zbaSgc` | | `_ZN7WebCore12EventHandler15handleAccessKeyERKNS_21PlatformKeyboardEventE` | `1uCdVAqt+YA` | | `_ZN7WebCore12EventHandler17scrollRecursivelyENS_15ScrollDirectionENS_17ScrollGranularityEPNS_4NodeE` | `GyVmPPMYRA4` | -| `_ZN7WebCore12EventHandler18accessKeyModifiersEv` | `BZEcdytvUYM` | | `_ZN7WebCore12EventHandler20sendContextMenuEventERKNS_18PlatformMouseEventE` | `yv8JYywpwz8` | | `_ZN7WebCore12EventHandler21handleMousePressEventERKNS_18PlatformMouseEventE` | `P0mseHxZBwA` | | `_ZN7WebCore12EventHandler21handleMousePressEventERKNS_28MouseEventWithHitTestResultsE` | `Y0eI-b+mHIw` | @@ -2636,9 +2557,7 @@ | `_ZN7WebCore13MIMETypeCache13canDecodeTypeERKN3WTF6StringE` | `x-za71ceNzM` | | `_ZN7WebCore13MIMETypeCache14supportedTypesEv` | `ywU-pWrM-Vw` | | `_ZN7WebCore13MIMETypeCache17addSupportedTypesERKN3WTF6VectorINS1_6StringELm0ENS1_15CrashOnOverflowELm16ENS1_10FastMallocEEE` | `xA+rJXMHbqo` | -| `_ZN7WebCore13MIMETypeCache21canDecodeExtendedTypeERKNS_11ContentTypeE` | `JqqHwIrj160` | | `_ZN7WebCore13MIMETypeCache21supportsContainerTypeERKN3WTF6StringE` | `TNpVhxCBHCA` | -| `_ZN7WebCore13MIMETypeCache26isUnsupportedContainerTypeERKN3WTF6StringE` | `Ua86vx5IaDU` | | `_ZN7WebCore13MIMETypeCacheC1ERKS0_` | `d5vSl0NN8vg` | | `_ZN7WebCore13MIMETypeCacheC1Ev` | `XQ79w+11C78` | | `_ZN7WebCore13MIMETypeCacheC2ERKS0_` | `toO3YXHsAdg` | @@ -2686,7 +2605,6 @@ | `_ZN7WebCore13endOfDocumentEPKNS_4NodeE` | `N+vcSXzdOJY` | | `_ZN7WebCore13endOfDocumentERKNS_15VisiblePositionE` | `-rlG+vYrV58` | | `_ZN7WebCore13endOfSentenceERKNS_15VisiblePositionE` | `kvemsBD8L0I` | -| `_ZN7WebCore13getLogChannelERKN3WTF6StringE` | `gRNu4fkK4tw` | | `_ZN7WebCore13internalErrorERKN3WTF3URLE` | `n+bQ6u1wxSk` | | `_ZN7WebCore13isStartOfLineERKNS_15VisiblePositionE` | `pu8CWYnACfE` | | `_ZN7WebCore13pointerCursorEv` | `clQC+AAy6pw` | @@ -3138,7 +3056,6 @@ | `_ZN7WebCore16HTMLVideoElement24webkitSupportsFullscreenEv` | `CNVHR5xfEqI` | | `_ZN7WebCore16HTMLVideoElement26webkitDisplayingFullscreenEv` | `SwVRb5Q7GCQ` | | `_ZN7WebCore16HTMLVideoElement6createERNS_8DocumentE` | `GLRtuC2tjQY` | -| `_ZN7WebCore16ISOSchemeTypeBox11boxTypeNameEv` | `pH77OiapXx0` | | `_ZN7WebCore16ISOSchemeTypeBox5parseERN3JSC8DataViewERj` | `F963GLVx2ek` | | `_ZN7WebCore16ISOSchemeTypeBoxC1ERKS0_` | `sDW6WO4WMd4` | | `_ZN7WebCore16ISOSchemeTypeBoxC1Ev` | `E4-gy9tn-e4` | @@ -3315,7 +3232,6 @@ | `_ZN7WebCore17JSDOMGlobalObject14finishCreationERN3JSC2VME` | `ToTgULkheEM` | | `_ZN7WebCore17JSDOMGlobalObject15createStructureERN3JSC2VMENS1_7JSValueE` | `mFkAY973pGc` | | `_ZN7WebCore17JSDOMGlobalObject17addBuiltinGlobalsERN3JSC2VME` | `2Z89SocEqmo` | -| `_ZN7WebCore17JSDOMGlobalObject21offsetOfWorldIsNormalEv` | `2ApsCsIuALw` | | `_ZN7WebCore17JSDOMGlobalObject24builtinInternalFunctionsEv` | `8of6l1u8E4U` | | `_ZN7WebCore17JSDOMGlobalObject4infoEv` | `Ry2Bjw-gItY` | | `_ZN7WebCore17JSDOMGlobalObject5worldEv` | `19bXLmSr5Pg` | @@ -3471,8 +3387,6 @@ | `_ZN7WebCore18PerformanceLogging21memoryUsageStatisticsENS_34ShouldIncludeExpensiveComputationsE` | `Twv2J+xbqTg` | | `_ZN7WebCore18PerformanceLogging22javaScriptObjectCountsEv` | `K0sTKrYVnmk` | | `_ZN7WebCore18PlatformPasteboard5writeERKN3WTF6StringES4_` | `smwyQcAh3tk` | -| `_ZN7WebCore18PlatformPasteboard5writeERKN3WTF6VectorINS_20PasteboardCustomDataELm0ENS1_15CrashOnOverflowELm16ENS1_10FastMallocEEE` | `C6lWET8ErOA` | -| `_ZN7WebCore18PlatformPasteboard5writeERKNS_20PasteboardCustomDataE` | `x6LF3hMLGPA` | | `_ZN7WebCore18PlatformPasteboard5writeERKNS_20PasteboardWebContentE` | `aMH6eRoQoDA` | | `_ZN7WebCore18PlatformPasteboardC1ERKN3WTF6StringE` | `Khws8dt04Sk` | | `_ZN7WebCore18PlatformPasteboardC1Ev` | `knX2cGAmdW8` | @@ -3715,7 +3629,6 @@ | `_ZN7WebCore20HTMLTableCellElement10setColSpanEj` | `1eejHQ3QcE8` | | `_ZN7WebCore20HTMLTableCellElement21setRowSpanForBindingsEj` | `sFjcaILv9NQ` | | `_ZN7WebCore20HTMLTableCellElement8setScopeERKN3WTF10AtomStringE` | `2JXVTjOe-qM` | -| `_ZN7WebCore20ISOOriginalFormatBox11boxTypeNameEv` | `ZI2IUkQLm0w` | | `_ZN7WebCore20ISOOriginalFormatBox5parseERN3JSC8DataViewERj` | `kGwtyyyhjyE` | | `_ZN7WebCore20ISOOriginalFormatBoxC1ERKS0_` | `IP1evjq7z2E` | | `_ZN7WebCore20ISOOriginalFormatBoxC1Ev` | `5xnIXBGQEV8` | @@ -3921,7 +3834,6 @@ | `_ZN7WebCore21HTMLOptionsCollection16setSelectedIndexEi` | `a2VWRSroXlY` | | `_ZN7WebCore21HTMLOptionsCollection6removeEi` | `1zovCBzwnbc` | | `_ZN7WebCore21HTMLOptionsCollection9setLengthEj` | `kR09Tm7VQbw` | -| `_ZN7WebCore21ISOTrackEncryptionBox11boxTypeNameEv` | `QByiQ+otd5o` | | `_ZN7WebCore21ISOTrackEncryptionBox5parseERN3JSC8DataViewERj` | `eCjonvf5rso` | | `_ZN7WebCore21ISOTrackEncryptionBoxC1ERKS0_` | `gBcHNw5c3Ng` | | `_ZN7WebCore21ISOTrackEncryptionBoxC2ERKS0_` | `ILkZXXuyNWc` | @@ -3997,7 +3909,6 @@ | `_ZN7WebCore21PageOverlayController32copyAccessibilityAttributesNamesEb` | `eioBaGW46xA` | | `_ZN7WebCore21PageOverlayController43copyAccessibilityAttributeBoolValueForPointEN3WTF6StringENS_10FloatPointERb` | `2ZkjWav84Zs` | | `_ZN7WebCore21PageOverlayController45copyAccessibilityAttributeStringValueForPointEN3WTF6StringENS_10FloatPointERS2_` | `-Ih6ikmQLbE` | -| `_ZN7WebCore21PlatformKeyboardEvent20currentCapsLockStateEv` | `4ufGsV2Qxvo` | | `_ZN7WebCore21PlatformKeyboardEvent23getCurrentModifierStateERbS1_S1_S1_` | `MqLM8CBC9FM` | | `_ZN7WebCore21RemoteCommandListener6createERNS_27RemoteCommandListenerClientE` | `x1AP-G3h75A` | | `_ZN7WebCore21SQLiteDatabaseTracker24hasTransactionInProgressEv` | `caM6qseCSfI` | @@ -4050,7 +3961,6 @@ | `_ZN7WebCore22EmptyFrameLoaderClient16restoreViewStateEv` | `tx5pKeUBkgE` | | `_ZN7WebCore22EmptyFrameLoaderClient17cancelPolicyCheckEv` | `4ySoP73IpDQ` | | `_ZN7WebCore22EmptyFrameLoaderClient17dispatchWillCloseEv` | `e8P9xcYmFp4` | -| `_ZN7WebCore22EmptyFrameLoaderClient17objectContentTypeERKN3WTF3URLERKNS1_6StringE` | `Symql6+eiQk` | | `_ZN7WebCore22EmptyFrameLoaderClient17setCopiesOnScrollEv` | `SYuiZEPIJOg` | | `_ZN7WebCore22EmptyFrameLoaderClient18frameLoadCompletedEv` | `xaZIwYQG0rw` | | `_ZN7WebCore22EmptyFrameLoaderClient18makeRepresentationEPNS_14DocumentLoaderE` | `xCC2+A1QU2M` | @@ -4077,7 +3987,6 @@ | `_ZN7WebCore22EmptyFrameLoaderClient27willReplaceMultipartContentEv` | `cIcJ41juvWI` | | `_ZN7WebCore22EmptyFrameLoaderClient29dispatchDidFinishDocumentLoadEv` | `XlIysvPfuNQ` | | `_ZN7WebCore22EmptyFrameLoaderClient29dispatchDidPopStateWithinPageEv` | `5aXKyDH-FQs` | -| `_ZN7WebCore22EmptyFrameLoaderClient29hasFrameSpecificStorageAccessEv` | `MrJUmRrcu5A` | | `_ZN7WebCore22EmptyFrameLoaderClient29savePlatformDataToCachedFrameEPNS_11CachedFrameE` | `DF2bDNLQbUM` | | `_ZN7WebCore22EmptyFrameLoaderClient30didRestoreFromBackForwardCacheEv` | `r4o4j3iaN64` | | `_ZN7WebCore22EmptyFrameLoaderClient30dispatchDidPushStateWithinPageEv` | `mYuf10s9APM` | @@ -4094,7 +4003,6 @@ | `_ZN7WebCore22EmptyFrameLoaderClient33dispatchWillPerformClientRedirectERKN3WTF3URLEdNS1_8WallTimeENS_19LockBackForwardListE` | `6q1eUV0OZoE` | | `_ZN7WebCore22EmptyFrameLoaderClient36transitionToCommittedFromCachedFrameEPNS_11CachedFrameE` | `pHmVAreojKs` | | `_ZN7WebCore22EmptyFrameLoaderClient37dispatchDidReachVisuallyNonEmptyStateEv` | `8RJJo61fpeg` | -| `_ZN7WebCore22EmptyFrameLoaderClient38dispatchDidLoadResourceFromMemoryCacheEPNS_14DocumentLoaderERKNS_15ResourceRequestERKNS_16ResourceResponseEi` | `3ip78Y0ydtU` | | `_ZN7WebCore22EmptyFrameLoaderClient50dispatchDidReceiveServerRedirectForProvisionalLoadEv` | `Oy92GWEKFIQ` | | `_ZN7WebCore22EmptyFrameLoaderClient8setTitleERKNS_19StringWithDirectionERKN3WTF3URLE` | `2oNey8Gkn4M` | | `_ZN7WebCore22EmptyFrameLoaderClientC1ERKS0_` | `E2pRXDTKB68` | @@ -4139,7 +4047,6 @@ | `_ZN7WebCore23HTMLTableSectionElement4rowsEv` | `vj1wM+GCC1g` | | `_ZN7WebCore23HTMLTableSectionElement9deleteRowEi` | `dAYfKRKR2QQ` | | `_ZN7WebCore23HTMLTableSectionElement9insertRowEi` | `7bTJr1njnSc` | -| `_ZN7WebCore23ISOSchemeInformationBox11boxTypeNameEv` | `5++VaMgcCng` | | `_ZN7WebCore23ISOSchemeInformationBox5parseERN3JSC8DataViewERj` | `48WiSdjl-uQ` | | `_ZN7WebCore23ISOSchemeInformationBoxC1Ev` | `CFvmCVk6TfI` | | `_ZN7WebCore23ISOSchemeInformationBoxC2Ev` | `RfDI9yBTxRM` | @@ -4303,7 +4210,6 @@ | `_ZN7WebCore26HTMLTextFormControlElement21setCanShowPlaceholderEb` | `iSolxX558O0` | | `_ZN7WebCore26HTMLTextFormControlElement21setSelectionDirectionERKN3WTF6StringE` | `5lqFaOk9rQE` | | `_ZN7WebCore26HTMLTextFormControlElement6selectENS_19SelectionRevealModeERKNS_23AXTextStateChangeIntentE` | `2P789Qo27CQ` | -| `_ZN7WebCore26ISOProtectionSchemeInfoBox11boxTypeNameEv` | `BgqMT+9JnBk` | | `_ZN7WebCore26ISOProtectionSchemeInfoBox5parseERN3JSC8DataViewERj` | `dpnSZ6bCdKs` | | `_ZN7WebCore26ISOProtectionSchemeInfoBoxC1Ev` | `4gLqVhLngro` | | `_ZN7WebCore26ISOProtectionSchemeInfoBoxC2Ev` | `O5XvFuIJg4Y` | @@ -4367,7 +4273,6 @@ | `_ZN7WebCore27PlatformMediaSessionManager32setIsPlayingToAutomotiveHeadUnitEb` | `kYgNNhsYnJA` | | `_ZN7WebCore27parseCacheControlDirectivesERKNS_13HTTPHeaderMapE` | `kB7iRn6G2NQ` | | `_ZN7WebCore27parseHTMLNonNegativeIntegerEN3WTF10StringViewE` | `V4T4zWxST6E` | -| `_ZN7WebCore27screenSupportsExtendedColorEPNS_6WidgetE` | `V0+T-rQTbe0` | | `_ZN7WebCore27withinTextUnitOfGranularityERKNS_15VisiblePositionENS_15TextGranularityENS_18SelectionDirectionE` | `3M4Zu3TOlcI` | | `_ZN7WebCore28BackingStoreBackendCairoImplC1ERKNS_7IntSizeEf` | `h3up3fILBXA` | | `_ZN7WebCore28BackingStoreBackendCairoImplC2ERKNS_7IntSizeEf` | `SW7SgFwOmu4` | @@ -4480,7 +4385,6 @@ | `_ZN7WebCore35serializePreservingVisualAppearanceERKNS_11SimpleRangeEPN3WTF6VectorIPNS_4NodeELm0ENS3_15CrashOnOverflowELm16ENS3_10FastMallocEEENS_22AnnotateForInterchangeENS_22ConvertBlocksToInlinesENS_11ResolveURLsE` | `9rGzyu0ZL0w` | | `_ZN7WebCore36ContentSecurityPolicyResponseHeadersC1ERKNS_16ResourceResponseE` | `01beKkjIUv8` | | `_ZN7WebCore36ContentSecurityPolicyResponseHeadersC2ERKNS_16ResourceResponseE` | `2gPLknvqjzk` | -| `_ZN7WebCore36ISOProtectionSystemSpecificHeaderBox11boxTypeNameEv` | `OvTLUSlWifE` | | `_ZN7WebCore36ISOProtectionSystemSpecificHeaderBox5parseERN3JSC8DataViewERj` | `PkKivtUQ5Mc` | | `_ZN7WebCore36ISOProtectionSystemSpecificHeaderBoxC1ERKS0_` | `NqUcrCZ7+f4` | | `_ZN7WebCore36ISOProtectionSystemSpecificHeaderBoxC1Ev` | `Wt4UzkFSXI8` | @@ -5369,8 +5273,6 @@ | `_ZN9Inspector18InjectedScriptBaseD2Ev` | `AMhoOLgG9e8` | | `_ZN9Inspector18InjectedScriptBaseaSERKS0_` | `5pMktPrQ1pg` | | `_ZN9Inspector18InjectedScriptHost16clearAllWrappersEv` | `c+YCXKe0E34` | -| `_ZN9Inspector18InjectedScriptHost19isHTMLAllCollectionERN3JSC2VMENS1_7JSValueE` | `x32zUERnINY` | -| `_ZN9Inspector18InjectedScriptHost21getInternalPropertiesERN3JSC2VMEPNS1_14JSGlobalObjectENS1_7JSValueE` | `xKiOo15Kcn0` | | `_ZN9Inspector18InjectedScriptHost6createEv` | `2SMzgmzR-6Q` | | `_ZN9Inspector18InjectedScriptHost7wrapperEPN3JSC14JSGlobalObjectE` | `Cw74jJ7Cvzs` | | `_ZN9Inspector18InjectedScriptHostC1Ev` | `QFj6QQ4cxXU` | @@ -6128,7 +6030,6 @@ | `_ZNK3JSC9CallFrame10codeOriginEv` | `8W5b+PNZYwI` | | `_ZNK3JSC9CallFrame11callerFrameERPNS_10EntryFrameE` | `FQf7z9EVOYQ` | | `_ZNK3JSC9ClassInfo4dumpERN3WTF11PrintStreamE` | `L9Hmp6AYcQU` | -| `_ZNK3JSC9CodeBlock26reoptimizationRetryCounterEv` | `8qygzLD+JkI` | | `_ZNK3JSC9CodeBlock4dumpERN3WTF11PrintStreamE` | `4f4k1BywICY` | | `_ZNK3JSC9JSPromise6resultERNS_2VME` | `Ctz7dKz3Da0` | | `_ZNK3JSC9JSPromise6statusERNS_2VME` | `u9H28omaVEc` | @@ -6154,7 +6055,6 @@ | `_ZNK3WTF3URL19protocolHostAndPortEv` | `q8fEMeIqd5w` | | `_ZNK3WTF3URL21truncatedForUseAsBaseEv` | `uekVJTVqBGc` | | `_ZNK3WTF3URL24strippedForUseAsReferrerEv` | `CyN41AniueA` | -| `_ZNK3WTF3URL30stringCenterEllipsizedToLengthEj` | `JRPrmfTHt08` | | `_ZNK3WTF3URL4dumpERNS_11PrintStreamE` | `ITwB6pPwmRc` | | `_ZNK3WTF3URL4userEv` | `ETN4mn+-5l0` | | `_ZNK3WTF3URL8passwordEv` | `aCtpeXWclTE` | @@ -6486,7 +6386,6 @@ | `_ZNK7WebCore13JSHTMLElement7wrappedEv` | `gRyLWstYGXM` | | `_ZNK7WebCore13KeyboardEvent7keyCodeEv` | `bElrc3VqGXk` | | `_ZNK7WebCore13KeyboardEvent8charCodeEv` | `O4PQrbyLS7k` | -| `_ZNK7WebCore13MIMETypeCache11isAvailableEv` | `XPpjBuVLPVA` | | `_ZNK7WebCore13MIMETypeCache7isEmptyEv` | `l+OspgT++TA` | | `_ZNK7WebCore13RenderElement16imageOrientationEv` | `ttg3VJv7+Wg` | | `_ZNK7WebCore13ScriptElement13scriptContentEv` | `rlTwz32Y4kY` | @@ -6515,10 +6414,7 @@ | `_ZNK7WebCore14HTMLCollection13namedItemSlowERKN3WTF10AtomStringE` | `XKZPrb4Pfqg` | | `_ZNK7WebCore14JSVoidCallback22scriptExecutionContextEv` | `hHkNFU67uNM` | | `_ZNK7WebCore14JSWebAnimation7wrappedEv` | `rlOCpNWBbhQ` | -| `_ZNK7WebCore14LoaderStrategy11usePingLoadEv` | `FhapblPGTsQ` | | `_ZNK7WebCore14LoaderStrategy12ongoingLoadsEv` | `m8rqXLGqxH4` | -| `_ZNK7WebCore14LoaderStrategy27havePerformedSecurityChecksERKNS_16ResourceResponseE` | `goURkCMz2mY` | -| `_ZNK7WebCore14LoaderStrategy27shouldPerformSecurityChecksEv` | `768mKI7DmSk` | | `_ZNK7WebCore14LoggedInStatus10hasExpiredEv` | `mgNAFnYj41Q` | | `_ZNK7WebCore14LoggedInStatus6expiryEv` | `0uCtvJ7t7X8` | | `_ZNK7WebCore14ResourceHandle6clientEv` | `Cgh0i6SAWZo` | @@ -6560,7 +6456,6 @@ | `_ZNK7WebCore15ActiveDOMObject16isContextStoppedEv` | `x9nBgCbadYs` | | `_ZNK7WebCore15ActiveDOMObject18hasPendingActivityEv` | `H-gEkxHy3s8` | | `_ZNK7WebCore15ActiveDOMObject20isAllowedToRunScriptEv` | `6ot8v-aJp4w` | -| `_ZNK7WebCore15ActiveDOMObject25virtualHasPendingActivityEv` | `iNX6kO6iSaM` | | `_ZNK7WebCore15ActiveDOMObject30assertSuspendIfNeededWasCalledEv` | `wRA9-Ih1MkU` | | `_ZNK7WebCore15AffineTransform9mapRegionERKNS_6RegionE` | `D-MYWruwiUQ` | | `_ZNK7WebCore15CSSGroupingRule8cssRulesEv` | `FfRPM8I9Wvw` | @@ -6680,7 +6575,6 @@ | `_ZNK7WebCore16JSStringCallback22scriptExecutionContextEv` | `CtqqsxZ-lpo` | | `_ZNK7WebCore16JSXMLHttpRequest7wrappedEv` | `kQEacF2vg-k` | | `_ZNK7WebCore16JSXMLHttpRequest8responseERN3JSC14JSGlobalObjectE` | `8oQrlcWx0fg` | -| `_ZNK7WebCore16TrackPrivateBase10trackIndexEv` | `m99zKY4J6BQ` | | `_ZNK7WebCore16TrackPrivateBase17startTimeVarianceEv` | `AYaZ5CHXzNA` | | `_ZNK7WebCore16TrackPrivateBase2idEv` | `DaYfJlkP-0k` | | `_ZNK7WebCore16TrackPrivateBase5labelEv` | `W+KOtirSb7U` | @@ -6780,9 +6674,7 @@ | `_ZNK7WebCore18TextureMapperLayer9layerRectEv` | `og6IDmhKqO4` | | `_ZNK7WebCore18TextureMapperLayer9rootLayerEv` | `KUMJF3eI5qg` | | `_ZNK7WebCore18TextureMapperLayer9transformEv` | `gj02Pc41dZk` | -| `_ZNK7WebCore19BlurFilterOperation11movesPixelsEv` | `i1KnWrxf1c0` | | `_ZNK7WebCore19BlurFilterOperation12stdDeviationEv` | `uqTupLakPfA` | -| `_ZNK7WebCore19BlurFilterOperation14affectsOpacityEv` | `JcGel5E1+KU` | | `_ZNK7WebCore19BlurFilterOperation5cloneEv` | `UFwDzfdUExo` | | `_ZNK7WebCore19BlurFilterOperationeqERKNS_15FilterOperationE` | `H9RRA3+Ow28` | | `_ZNK7WebCore19DragCaretController23isContentRichlyEditableEv` | `8zj2LYparBk` | @@ -6835,34 +6727,24 @@ | `_ZNK7WebCore20HTMLTableCellElement9cellAboveEv` | `o+X-i3Y20j4` | | `_ZNK7WebCore20HTMLTableCellElement9cellIndexEv` | `yLtMsTWo9zs` | | `_ZNK7WebCore20ISOOriginalFormatBox10dataFormatEv` | `OzsnvV0TdHA` | -| `_ZNK7WebCore20LowPowerModeNotifier21isLowPowerModeEnabledEv` | `mkMdmnmWONA` | | `_ZNK7WebCore20PasteboardCustomData18createSharedBufferEv` | `oNgcYv7sg2E` | | `_ZNK7WebCore20PasteboardWriterData7isEmptyEv` | `pCjXnlN6R4c` | | `_ZNK7WebCore20RenderBoxModelObject18inlineContinuationEv` | `nV9al8rbOKg` | -| `_ZNK7WebCore20ResourceResponseBase11httpVersionEv` | `qJpr45shtzs` | | `_ZNK7WebCore20ResourceResponseBase12isAttachmentEv` | `C+morSMfP84` | | `_ZNK7WebCore20ResourceResponseBase12isSuccessfulEv` | `3a6B43jFjX8` | | `_ZNK7WebCore20ResourceResponseBase13isRedirectionEv` | `BrSesfxNNvs` | | `_ZNK7WebCore20ResourceResponseBase14httpStatusCodeEv` | `bFhYDNofRSE` | -| `_ZNK7WebCore20ResourceResponseBase14httpStatusTextEv` | `MYd7F5R6ryw` | -| `_ZNK7WebCore20ResourceResponseBase14isInHTTPFamilyEv` | `wW+Pyy9LiGA` | | `_ZNK7WebCore20ResourceResponseBase15httpHeaderFieldENS_14HTTPHeaderNameE` | `JkEYUFSjoXw` | -| `_ZNK7WebCore20ResourceResponseBase16httpHeaderFieldsEv` | `cots5mYtZpo` | -| `_ZNK7WebCore20ResourceResponseBase16textEncodingNameEv` | `wTRdXzKM3u4` | | `_ZNK7WebCore20ResourceResponseBase17suggestedFilenameEv` | `fBVtigUQ-qk` | -| `_ZNK7WebCore20ResourceResponseBase21expectedContentLengthEv` | `i4zxQPHkqJs` | | `_ZNK7WebCore20ResourceResponseBase23hasCacheValidatorFieldsEv` | `qAv1-ceZ9iQ` | | `_ZNK7WebCore20ResourceResponseBase24isAttachmentWithFilenameEv` | `ClZ8vxvE28U` | | `_ZNK7WebCore20ResourceResponseBase27cacheControlContainsNoCacheEv` | `bTMmuXgrk-4` | | `_ZNK7WebCore20ResourceResponseBase27cacheControlContainsNoStoreEv` | `CchgKVep+h8` | | `_ZNK7WebCore20ResourceResponseBase29cacheControlContainsImmutableEv` | `OKrc5AyAqtM` | | `_ZNK7WebCore20ResourceResponseBase34cacheControlContainsMustRevalidateEv` | `8956F1voabk` | -| `_ZNK7WebCore20ResourceResponseBase3urlEv` | `VljXeIAPJoA` | | `_ZNK7WebCore20ResourceResponseBase6sourceEv` | `Nc4OHQvmIOM` | | `_ZNK7WebCore20ResourceResponseBase8isHTTP09Ev` | `JjPx6bo9fbs` | | `_ZNK7WebCore20ResourceResponseBase8lazyInitENS0_9InitLevelE` | `o+TiqWzvS8s` | -| `_ZNK7WebCore20ResourceResponseBase8mimeTypeEv` | `HDR9zkTEoJ4` | -| `_ZNK7WebCore20ScrollingCoordinator25scrollableContainerNodeIDERKNS_12RenderObjectE` | `DVeoAh0xG6w` | | `_ZNK7WebCore20ScrollingCoordinator36coordinatesScrollingForOverflowLayerERKNS_11RenderLayerE` | `1nxJkpm8wLA` | | `_ZNK7WebCore21BackForwardController12forwardCountEv` | `wmkk8v-vEI0` | | `_ZNK7WebCore21BackForwardController18canGoBackOrForwardEi` | `No2407W5Jes` | @@ -6894,20 +6776,12 @@ | `_ZNK7WebCore22DefaultFilterOperation15representedTypeEv` | `ksB78CcT0mg` | | `_ZNK7WebCore22DefaultFilterOperation5cloneEv` | `mx1YlEp90mU` | | `_ZNK7WebCore22DefaultFilterOperationeqERKNS_15FilterOperationE` | `EWVFpBhoEGg` | -| `_ZNK7WebCore22EmptyFrameLoaderClient10hasWebViewEv` | `gv8bC38U34E` | | `_ZNK7WebCore22EmptyFrameLoaderClient12blockedErrorERKNS_15ResourceRequestE` | `X1bFhjwBxXU` | -| `_ZNK7WebCore22EmptyFrameLoaderClient12canCachePageEv` | `1YFXTueqrU8` | | `_ZNK7WebCore22EmptyFrameLoaderClient14cancelledErrorERKNS_15ResourceRequestE` | `BywqS3Q8szU` | -| `_ZNK7WebCore22EmptyFrameLoaderClient14shouldFallBackERKNS_13ResourceErrorE` | `v0GvL1SMt8M` | -| `_ZNK7WebCore22EmptyFrameLoaderClient15canShowMIMETypeERKN3WTF6StringE` | `JZQPxcMK+ZA` | -| `_ZNK7WebCore22EmptyFrameLoaderClient16canHandleRequestERKNS_15ResourceRequestE` | `glKx1Zdz50w` | | `_ZNK7WebCore22EmptyFrameLoaderClient17overrideMediaTypeEv` | `bh4bKfeivgQ` | | `_ZNK7WebCore22EmptyFrameLoaderClient18cannotShowURLErrorERKNS_15ResourceRequestE` | `Hn+oQHUrE1M` | -| `_ZNK7WebCore22EmptyFrameLoaderClient21canShowMIMETypeAsHTMLERKN3WTF6StringE` | `75fdGWpRw34` | | `_ZNK7WebCore22EmptyFrameLoaderClient21fileDoesNotExistErrorERKNS_16ResourceResponseE` | `fajGVxixYQg` | -| `_ZNK7WebCore22EmptyFrameLoaderClient21shouldGoToHistoryItemERNS_11HistoryItemE` | `s+Gety9ZDs4` | | `_ZNK7WebCore22EmptyFrameLoaderClient23cannotShowMIMETypeErrorERKNS_16ResourceResponseE` | `ab+5Dimx0Kw` | -| `_ZNK7WebCore22EmptyFrameLoaderClient24isEmptyFrameLoaderClientEv` | `VVzGt0RzJZc` | | `_ZNK7WebCore22EmptyFrameLoaderClient25pluginWillHandleLoadErrorERKNS_16ResourceResponseE` | `mGriYqpliqc` | | `_ZNK7WebCore22EmptyFrameLoaderClient28blockedByContentBlockerErrorERKNS_15ResourceRequestE` | `0d9jr6TzI-o` | | `_ZNK7WebCore22EmptyFrameLoaderClient31interruptedForPolicyChangeErrorERKNS_15ResourceRequestE` | `tRdCmMXTagg` | @@ -6935,13 +6809,10 @@ | `_ZNK7WebCore24CoordinatedGraphicsLayer17usesContentsLayerEv` | `pf2fwfamoG0` | | `_ZNK7WebCore24CoordinatedGraphicsLayer22filtersCanBeCompositedERKNS_16FilterOperationsE` | `deY8cQlIt9U` | | `_ZNK7WebCore24CoordinatedGraphicsLayer22shouldHaveBackingStoreEv` | `osZckBsVgRg` | -| `_ZNK7WebCore24CoordinatedGraphicsLayer26isCoordinatedGraphicsLayerEv` | `Ex6z08qINEI` | | `_ZNK7WebCore24CoordinatedGraphicsLayer28shouldDirectlyCompositeImageEPNS_5ImageE` | `RUB6zNhQUJQ` | | `_ZNK7WebCore24CoordinatedGraphicsLayer2idEv` | `NQvgMXcGmjs` | | `_ZNK7WebCore24CoordinatedGraphicsLayer41selfOrAncestorHasActiveTransformAnimationEv` | `8reqGK4Jslw` | -| `_ZNK7WebCore25DropShadowFilterOperation11movesPixelsEv` | `oDR18NZmOgg` | | `_ZNK7WebCore25DropShadowFilterOperation12stdDeviationEv` | `Zwz6pQRbIaI` | -| `_ZNK7WebCore25DropShadowFilterOperation14affectsOpacityEv` | `y7uxQXIITME` | | `_ZNK7WebCore25DropShadowFilterOperation1xEv` | `ACFqSfIdK+4` | | `_ZNK7WebCore25DropShadowFilterOperation1yEv` | `dSXGljvWG7Q` | | `_ZNK7WebCore25DropShadowFilterOperation5cloneEv` | `lrKNEr+8Ilk` | @@ -7016,7 +6887,6 @@ | `_ZNK7WebCore4Page15forEachDocumentERKN3WTF8FunctionIFvRNS_8DocumentEEEE` | `7t3lPIaxzws` | | `_ZNK7WebCore4Page15visibilityStateEv` | `9QZluZ4dNDY` | | `_ZNK7WebCore4Page16hasSeenAnyPluginEv` | `5HR+YnAQc14` | -| `_ZNK7WebCore4Page17useDarkAppearanceEv` | `K3n-MX0qBKg` | | `_ZNK7WebCore4Page17viewportArgumentsEv` | `5Q9eXxWwvAg` | | `_ZNK7WebCore4Page19disabledAdaptationsEv` | `ilM1cSlkdrg` | | `_ZNK7WebCore4Page20renderingUpdateCountEv` | `MjKAjGkB7Mk` | @@ -7074,13 +6944,8 @@ | `_ZNK7WebCore6Length3refEv` | `8OHZsn1lLv0` | | `_ZNK7WebCore6Length5derefEv` | `CKQm3LRNxFQ` | | `_ZNK7WebCore6Path2D4pathEv` | `Z-n9eCkcABY` | -| `_ZNK7WebCore6Quirks25needsYouTubeMouseOutQuirkEv` | `SLSp+Z+-W9Y` | -| `_ZNK7WebCore6Quirks29shouldAvoidUsingIOS13ForGmailEv` | `86JgtVBApTw` | -| `_ZNK7WebCore6Quirks32isNeverRichlyEditableForTouchBarEv` | `ABPW-f1l6DA` | | `_ZNK7WebCore6Quirks44shouldAvoidResizingWhenInputViewBoundsChangeEv` | `zPWH7YflRh4` | | `_ZNK7WebCore6Quirks47shouldIgnoreContentObservationForSyntheticClickEb` | `7MeYabjRcQo` | -| `_ZNK7WebCore6Quirks49isTouchBarUpdateSupressedForHiddenContentEditableEv` | `YFqTNZQwPwM` | -| `_ZNK7WebCore6Quirks50shouldIgnoreAriaForFastPathContentObservationCheckEv` | `sJVPrwAjvos` | | `_ZNK7WebCore6Quirks56shouldDispatchSyntheticMouseEventsWhenModifyingSelectionEv` | `G3FORkp2ePs` | | `_ZNK7WebCore6Quirks61shouldUseLegacySelectPopoverDismissalBehaviorInDataActivationEv` | `M97BIVb8Vs8` | | `_ZNK7WebCore6Quirks62shouldLayOutAtMinimumWindowWidthWhenIgnoringScalingConstraintsEv` | `9tvPjSs-M-o` | @@ -7140,7 +7005,6 @@ | `_ZNK7WebCore8Document20isRunningUserScriptsEv` | `Z0bnBaoXk7w` | | `_ZNK7WebCore8Document20linkColorForBindingsEv` | `StXnL46JBH8` | | `_ZNK7WebCore8Document21haveStylesheetsLoadedEv` | `u39XzpncQzA` | -| `_ZNK7WebCore8Document22touchEventHandlerCountEv` | `Amlk9ZNFJag` | | `_ZNK7WebCore8Document22wheelEventHandlerCountEv` | `csW7BKwFn60` | | `_ZNK7WebCore8Document28characterSetWithUTF8FallbackEv` | `NrKtvW0gC-Q` | | `_ZNK7WebCore8Document29originIdentifierForPasteboardEv` | `6QWMcGENlPE` | @@ -7214,7 +7078,6 @@ | `_ZNK7bmalloc11IsoTLSEntry4sizeEv` | `zkhfTgB1ABM` | | `_ZNK7bmalloc11IsoTLSEntry6extentEv` | `1UchwEvKOCY` | | `_ZNK7bmalloc11IsoTLSEntry6offsetEv` | `lRFFPidOgTg` | -| `_ZNK7bmalloc11IsoTLSEntry9alignmentEv` | `75nTISHl6cY` | | `_ZNK9Inspector14ConsoleMessage12globalObjectEv` | `ijTy57nFYLw` | | `_ZNK9Inspector14ConsoleMessage13argumentCountEv` | `DR5-oCJNvBU` | | `_ZNK9Inspector14ConsoleMessage3urlEv` | `In5ugGO8zCE` | @@ -7241,7 +7104,6 @@ | `_ZNK9Inspector15AsyncStackTrace20buildInspectorObjectEv` | `XS8CSiOlDVI` | | `_ZNK9Inspector15AsyncStackTrace8isLockedEv` | `zXiD0GC9Vh8` | | `_ZNK9Inspector15AsyncStackTrace9isPendingEv` | `YPyYVEu-17g` | -| `_ZNK9Inspector15InspectorTarget13isProvisionalEv` | `ZcQzzUTFsJU` | | `_ZNK9Inspector15InspectorTarget8isPausedEv` | `KrfpsXXdW-w` | | `_ZNK9Inspector15RemoteInspector16listingForTargetERKNS_24RemoteControllableTargetE` | `w+T7oawE1mg` | | `_ZNK9Inspector15RemoteInspector21hasActiveDebugSessionEv` | `P0V8cbW+Z-E` | @@ -7289,10 +7151,8 @@ | `_ZNK9Inspector22InspectorDebuggerAgent27pauseOnNextStatementEnabledEv` | `25lgQJ93JO4` | | `_ZNK9Inspector22InspectorDebuggerAgent7enabledEv` | `1DD+9cjt3hU` | | `_ZNK9Inspector22InspectorDebuggerAgent8isPausedEv` | `Jgg+mJLsHHs` | -| `_ZNK9Inspector22RemoteAutomationTarget4typeEv` | `E9lS19wjNII` | | `_ZNK9Inspector22RemoteAutomationTarget8isPairedEv` | `ew8ebxoQMLo` | | `_ZNK9Inspector22RemoteInspectionTarget20remoteControlAllowedEv` | `QGapaf1-Pp0` | -| `_ZNK9Inspector22RemoteInspectionTarget26automaticInspectionAllowedEv` | `OVQXcjKPf8M` | | `_ZNK9Inspector22RemoteInspectionTarget3urlEv` | `r20Y87D+ibY` | | `_ZNK9Inspector22RemoteInspectionTarget4nameEv` | `hep5VUibI2A` | | `_ZNK9Inspector24RemoteControllableTarget16targetIdentifierEv` | `untnO4d6OQs` | diff --git a/docs/dev/debt/libSceNet.md b/docs/dev/debt/libSceNet.md index fae68ec19a..d9c9e689a7 100644 --- a/docs/dev/debt/libSceNet.md +++ b/docs/dev/debt/libSceNet.md @@ -46,14 +46,21 @@ - [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. +- [sceNetSocketInternal, sceNetGetsockoptEx and sceNetSetsockoptEx](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - forward to sceNetSocket, sceNetGetsockopt and sceNetSetsockopt. Their restored argument lists match the public ones, and nothing in the tree records how the console tells these entry points apart from the public ones (for example by the calling process). +- [sceNetSysctl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - calls libkernel's sysctl with the FreeBSD sysctl(3) argument list, so it answers exactly the MIBs libkernel models, and reports a failure as the library's other calls do (`0x80410100 | errno`, with the errno set). How the console's routine reports errors is not measured. +- [sceNetInetNtopWithScopeId and sceNetInetPtonWithScopeId](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - console behaviour from the module; slot-to-host-index mapping is ours. A zone name maps to a host index (`lo0` the loopback, `eth0` the first up interface with an address, the others a host interface of that name); a name with no host interface does not convert, and ntop prints the first name, in sceNetGetIfName's order, with the scope's index. A null destination throws `std::invalid_argument`, where the module faults. Not measured: ntop for a scope that matches no interface (throws), exact name comparison, the 0 written to the scope for a plain address, and the order of the family and null checks. Module: rooklz's [reading](https://github.com/boykopovar/AnyPS5/pull/9578#issuecomment-6549946559) of `libSceNet.sprx`, FUN_00006be0 (FUN_000065e0) and FUN_000072d0 (FUN_00006ee0), names through FUN_00005d40, FUN_00005ea0 and FUN_00005f20. +- [sceNetConfigGetDefaultRoute](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the route is the host's: the IPv4 gateway of the interface sceNetCtlGetInfo describes (`DEFAULT_ROUTE`), in network byte order, stands for the kernel record's dword at 0x84. A null output with a route throws `std::invalid_argument`, where the module faults. Not measured: that the dword is the gateway in that order, whether `0x8041130C` sets errno (unchanged here), and the getter's other negative results, which have no host source. Module: rooklz's [reading](https://github.com/boykopovar/AnyPS5/pull/9578#issuecomment-6565097123) of `libSceNet.sprx` (NID `lFJb+BlPK1c`, wrapper 0x153f0, route-table getter 0xc70): `0x804101C8` with errno 200 before initialization, `0x8041130C` with the output untouched without a route, and 1 after storing the dword; no null check. ### Functional +- [sceNetGetMacAddress](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the console's result for a non-null unwritable output is unmeasured. AnyPS5 returns `EFAULT` through a checked guest write instead of crashing the host; null output and invalid flags retain `EINVAL`. The synthetic address remains independent of the host adapter. + - [IPV6_V6ONLY in sceNetSetsockopt and sceNetGetsockopt](../../../core/libs/prx/libSceNet/Export.cpp) is unverified on PS5. The IPv6 level (41), option (27), exact integer input size, nonzero enable behavior, boolean readback and rejection after binding follow FreeBSD 11.4's [`ip6_ctloutput`](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/netinet6/ip6_output.c). The shared libkernel option implementation applies the host's IPv6-only mode; disabling it permits IPv4-mapped traffic on a socket bound to the IPv6 wildcard address. Host network configuration controls IPv6 availability. Null buffers retain this library's `EINVAL` behavior instead of FreeBSD's fault handling. Crimson Desert (PPSA25286) disables it, passing a 4-byte 0 at both of its two calls; of the other nine titles checked, only Borderlands 4 (PPSA01494) passes an IPv6-level option as a constant, one `IPV6_JOIN_GROUP` (12). - [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; on macOS a non-stream socket uses `SO_NREAD` (next datagram), as macOS's `FIONREAD` counts more. A PS4 Pro counts payload plus 16 bytes per datagram (#9932). Libkernel's own sockets go to its `ioctl`; other descriptors get `EBADF`. +- [sceNetResolverGetError](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the console's error for a non-null unwritable status output is unmeasured. AnyPS5 returns `EFAULT` through a checked guest write instead of crashing the host; null output retains `EINVAL` and an unknown resolver retains `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 read after the host accept returns; changes between connection creation and accept are not modelled. Not measured on a console. @@ -62,17 +69,22 @@ - [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 - [SO_OOBINLINE](../../../core/libs/prx/libSceNet/Export.cpp) uses native TCP urgent-data handling; its urgent-pointer and signalling behavior has not been compared with PS5. The flag bit (0x100), non-stream flag storage and truncated readback follow FreeBSD 11.4 [sosetopt/sogetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c). Null pointers retain the existing libSceNet `EINVAL` result. - [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 +- [sceNetSetsockopt socket-level options](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - a `SOL_SOCKET` option it does not handle returns `EOPNOTSUPP`. Titles checked set four of them, named as in [shadPS4's `net_types.h`](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/net/net_types.h): `SO_POLICY` (`0x1201`) once each in PPSA01494, PPSA21564, PPSA30839 and PPSA31162; `SO_USECRYPTO` (`0x20000`) twice in PPSA02225; `SO_LINGER` (`0x80`) once in PPSA25286, with linger off; and `SO_REUSEPORT` (`0x200`) once each in PPSA18194 and PPSA21564, which sets it instead of `SO_REUSEADDR` when its own socket-type field is 1 or 3. `SO_LINGER` and `SO_REUSEPORT` are FreeBSD 11.4 socket options; what the console does with the other two is unknown - [SO_REUSEADDR and SO_BROADCAST readback](../../../core/libs/prx/libSceNet/Export.cpp) follows FreeBSD 11.4's [sogetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c#L2674-L2688): enabled options return their flag bit, and output is truncated to the supplied length. PS5 results are unverified. Values reflect the native socket; host restrictions on setting the options are unchanged. Null pointers retain libSceNet's existing `EINVAL` result. Windows passes `SO_REUSEADDR` directly to Winsock, which permits a second bind to an occupied address and port under its [reuse rules](https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse), unlike FreeBSD's separate `SO_REUSEPORT` requirement for duplicate unicast binds. Guest `SO_REUSEPORT` (`0x200`) still fails with `EOPNOTSUPP`. - [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. One title (PPSA02225) disassembled for this entry has one call site (0x1056d34), with level 0xffff, option 0x1007 (FreeBSD's `SOL_SOCKET` and `SO_ERROR`) and a length variable set to 4 beforehand, reached only after a helper called with the value 2 returned true. For a negative result it takes the thread's error code as the value; for a result of 0 or more with a value of 0 it reports the connection as made, and otherwise it turns the value into an error code of its own. The call does not show how often the title reads it or whether it keeps using the socket after an error. - [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. - [SO_TYPE and SO_ACCEPTCONN](../../../core/libs/prx/libSceNet/Export.cpp) read the guest socket type and listening flag, with bit-valued listening results and truncated output following FreeBSD 11.4's [sogetopt](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/kern/uipc_socket.c#L2667-L2693), without PS5 verification. Null pointers retain the existing libSceNet `EINVAL` result instead of FreeBSD's pointer handling. -- [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. +- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) reports 0 for the name, pid, policy, priority, send queue length, 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. +- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - the receive queue length is the host's `FIONREAD` count: the bytes waiting on a stream socket. A PS4 Pro (firmware 10.71) reports 0 for an empty queue and counts each queued datagram's payload plus 16 bytes, as FreeBSD 11.4's receive buffer does (10 then 30 bytes queued: 26, then 72, and 46 after one receive). No host matches that on a datagram socket: Linux counts the first datagram (0 for an empty one, which then blocks the queue for a title that reads only above 0), Windows every queued payload byte, and macOS adds 16 bytes for the first datagram and 32 for each later one. On that console `n` 0 with `s` 0 or above returns 0 where AnyPS5 fails with `EINVAL`, a bound UDP socket's record carries pid 0x87, priority 0x10 and state 2, and a socket made with `socket()` is listed too. - [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`. +- [sceNetGetSockInfo](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - every direct call to it in the local titles whose executables import it passes the constant 0 as `flags` (disassembly of each direct call), so none of them reaches the `EINVAL` that other bits than 0x40 get here, or passes 0x40; what a console returns for other `flags` bits is not measured. - [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. - [sceNetEpollControl and sceNetEpollWait](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) take sockets made with libkernel's `socket()`. PPSA02429 registers such sockets and retries the failed `ADD` and `DEL` about 1.6 million times in its first seconds. This follows the position taken for PPSA30839 in `sceNetSocketClose` and `sceNetSetsockopt`, that both libraries work on the same kernel sockets; epoll on them is not measured on a console. libkernel's `close()` and `sceKernelClose` retire their registrations at once, so a socket that reuses the number starts unregistered. +- [sceNetEpollCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - a null name or non-zero `flags` fail with `EINVAL`, as [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L443-L449) gives. Every direct call in the titles checked passes the constant 0 as flags, so none reaches the check +- [sceNetEpollControl](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - an `op` other than 1 (add), 2 (modify) and 3 (delete) fails with `EINVAL`. In [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L471-L491) the module checks `op` and the event before the epoll id, and an add or modify whose event has an events mask of 0 also fails with `EINVAL`; here an unknown epoll id fails with `EBADF` before `op` is checked, and a mask of 0 is accepted. Every direct call in the titles checked whose `op` is a constant passes 1, 2 or 3; the `op` of the calls where it is not a constant is not known - [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. - [MSG_NOSIGNAL](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - `sceNetSend`, `sceNetSendto` and `sceNetSendmsg` accept `0x20000`, FreeBSD's `MSG_NOSIGNAL` (`sys/sys/socket.h`), which ProsperoTV (PPSA99003) passes on every phone-remote reply. Firmware 13.42's `libSceNet.sprx` adds `0x40020000` to the flags of every send and refuses no flag itself ([measured in an emulator](https://github.com/boykopovar/AnyPS5/pull/11037#issuecomment-6400759240)), so passing the flag changes nothing on the console. What `0x40000000` means and which flags the kernel refuses are unknown; other flags still fail with `EOPNOTSUPP` here. Host sends outside Windows already pass `MSG_NOSIGNAL` and Windows raises no `SIGPIPE`, so a send on a stream whose write side is shut down fails with `EPIPE`. 35 of 53 local titles call `sceNetSend`/`sceNetSendto` directly from the executable (title libraries, function pointers, 2 tail jumps not read) with flags 0 at 86 sites, 0x80 at 10, non-constant at 2. - [Stream sends on macOS](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - a `MSG_DONTWAIT` transfer also passes XNU's `MSG_NBIO` (`0x20000`, absent from the SDK headers), because XNU's `MSG_DONTWAIT` does not keep a stream send from waiting for buffer space. A blocking stream send goes to the host in sends of 64 KiB, so an error after some progress, such as an abort, reports the bytes already queued, which one host send would drop; the bytes that the failing 64 KiB send had queued are not counted @@ -83,7 +95,8 @@ - [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) - 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. In the executables of the 53 titles run here, 36 call `sceNetInetPton` at 107 call sites and 22 call `sceNetInetNtop` at 67; every one of those call sites loads the family 2 (`AF_INET`) into the first argument, none loads 28 (`AF_INET6`) and none leaves it undetermined, so the IPv6 text conversion that is unverified on a console is not reached by a direct call in those executables (read from the instructions, not measured; calls from the titles' library modules and through function pointers are not seen). -- [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. A timeout from 1 to 999999 also fails with `EINVAL` before the resolver is looked up, and 0 counts as 1000000, as @BunnyFl's [13.42 run of the module](https://github.com/boykopovar/AnyPS5/pull/9727#issuecomment-6362795252) shows. +- [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. A timeout from 1 to 999999 also fails with `EINVAL` before the resolver is looked up, and 0 counts as 1000000, as @BunnyFl's [13.42 run of the module](https://github.com/boykopovar/AnyPS5/pull/9727#issuecomment-6362795252) shows. Every direct call in the titles checked passes the constant timeout 0, retry 0 or 1 and flags 0 or 0x1, so none fails these checks. +- [sceNetResolverCreate](../../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) - non-zero `flags` fail with `EINVAL`, as [prosper's reading of the libSceNet module](https://github.com/mattias800/prosper/blob/6c09a34a092a00898fcead5f855200ca7c368cd2/prosper/src/hle/net/hle_net.cpp#L564-L578) gives. Every direct call in the titles checked passes the constant 0 as flags, so none reaches the check; one title also reaches the function through a tail jump, which is not read. - [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 75e854922d..96056dbce2 100644 --- a/docs/dev/debt/libSceNetCtl.md +++ b/docs/dev/debt/libSceNetCtl.md @@ -5,7 +5,7 @@ - [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 with an IPv4 address, preferring the default-route one: its address, netmask, gateway, MTU and first two IPv4 DNS servers. The device is always reported as wired, the link as connected, the IP configuration as DHCP and the HTTP proxy as off, and the Ethernet address is the one from `sceNetGetMacAddress`. A missing gateway or DNS server is `0.0.0.0`; the console value is unknown. The DHCP host name (12), PPPoE user name (13), HTTP proxy server (20) and Wi-Fi BSSID and SSID (5, 6) are empty strings and the other Wi-Fi codes (7-10) and the proxy port (21) are 0, as that configuration sets none of them; this follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/main/src/libs/network.cpp) and is not measured on hardware. One title (PPSA02225) calls code 12 at 0x1056f0f, 0xa0387f and 0xa26de5 and code 14 at 0x1056fa1; the meaning of the flag byte it checks is not shown. -- [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 +- [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 on a host with an IPv6 address every code goes through `NotImplemented_nid_no_patch` - [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. The host backend has no IPv6 service errors, 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 diff --git a/docs/dev/debt/libSceNgs2.native.md b/docs/dev/debt/libSceNgs2.native.md index 5d2473e0c9..4d96da6da2 100644 --- a/docs/dev/debt/libSceNgs2.native.md +++ b/docs/dev/debt/libSceNgs2.native.md @@ -2,6 +2,8 @@ ### Unknown function info +- [libSceNgs2.native pending exports](../../../core/libs/prx/libSceNgs2.native/Export.cpp) - 14 named callable exports are listed in the [PS5 3.20 export table](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceNgs2.native.c). Their signatures and behaviour are unverified; the declarations use the no-parameter unknown-signature convention and throw through `NotImplemented_nid_no_patch`. + - [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. @@ -28,7 +30,7 @@ - [NGS2 streaming PCM waveforms](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - appending to a block that is not a stream, streams of formats other than 16-bit PCM, skipped samples, repeats and partial frames throw, except one first `APPEND` block on a mono voice (see NGS2 waveform block flags). PPSA21402 streams music as one block whose `num_samples` is the whole track and whose first piece is 16 KiB, with `CONTINUE`, then pieces with flag `0x2` (`APPEND`). As on PS5 (16-bit and 32-bit float): a 16-bit PCM block without skipped samples or repeats whose `num_samples` exceed its data starts a stream, `APPEND` appends its data ignoring `num_samples`, the voice waits silent (flag EMPTY) while `CONTINUE` is set and all data played, and the waveform ends at `num_samples` or, once `CONTINUE` is cleared, at the end of the appended data. Run on the PS5 firmware 3.20 and 4.03 module and the PS4 11.00 module on a PC, not a console; no firmware in the repository - [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 rack block limit](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native) - the PS4 library applies no limit to sampler racks and keeps only the check that a custom sampler rack with 0 blocks returns `0x804A8053`. PS4 11.00 takes sampler racks with `max_waveform_blocks` 1 to 16 and returns `0x804A0106` otherwise (query 0 to 70, creates at 0, 1, 2, 4, 16, 17, 32, 33, 64, 70), but our PS4 library reads the 176-byte PS5 option base (the PS4 module's is 128), so a PS4 rack option fails the size check first. The order of the range check against the voice and channel count checks was not run; here it comes first. Measured on the PS5 firmware 3.20 and 4.03 `libSceNgs2.native` module run on a PC with chosen options, return codes only, not on a console, nothing from the firmware in the repository: sampler and custom sampler racks with 0 or above 32 return `0x804A8053` before the peak meter and buffer count checks; a NULL option was run for sampler racks only -- [NGS2 port volume](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - the clamp covers a sampler voice's ports between renders only; other voice types, controls buffered during a render and the PS4 library (same code) are unmeasured. A port past the count still throws for VoiceControl param 0x2, for RunCommands on a voice without 8 ports or in a batch mixing other commands with port volumes, and in sceNgs2VoiceGetPortInfo; a batch of port volumes only on a voice with 8 ports returns 0x804A8357. Measured on the PS5 firmware 3.20 and 4.03 libSceNgs2.native module run on a PC, not a console (return codes, read-backs, samples): param 0x2 and RunCommands command 6 clamp the volume to [0, 32] with NaN and -0 kept and return 0 for every value; GetPortInfo reads it back at once; command 6 reads its low 32 bits as a float whatever the value type and ignores numValues and flags; a batch naming a port of 8 or more on an 8-port rack returns 0x804A8357 and applies none +- [NGS2 port volume](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2.native) - gaps: our PS4 library renders +-1e-40 volumes as volume x input (nonzero subnormal), the PS4 11.00 module renders 0; command 6 is not in the PS4 module, so its use of the param 0x2 range is a host-only choice. A port past the count throws for param 0x2, for RunCommands on a voice without 8 ports or in a batch mixing other commands with port volumes, and in GetPortInfo. PS5 firmware 3.20 and 4.03 libSceNgs2.native on a PC, not a console: param 0x2 on a sampler, submixer, reverb, custom submixer or custom sampler voice (also from a submixer user-fx handler mid-render) clamps to [0, 32], NaN and -0 kept, rc 0; command 6 reads its low 32 bits as a float, ignoring numValues and flags; a batch naming port 8 or more on an 8-port rack returns 0x804A8357 and applies none. PS4 11.00 libSceNgs2.sprx on a PC, param 0x2 runs: [-4, 4], a negative volume inverts the signal. - [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 waveform block playback](../../../core/libs/prx/libSceNgs2.native/src/Render.cpp) has reference checks for finite PCM repeat counts 0 through 3, skip/sample windows, callbacks and six input formats using host kernel adapters, not console execution. Finite repeat counts specify total passes, with 0 and 1 both playing once. PCM reads the combined skip/sample window from the data start; ATRAC9 retains codec skip handling. The public callback repeat field stays zero in those checks. Extreme window overflow, fragmented compressed repeats and callback positions outside the tested schedules remain unverified. @@ -49,6 +51,8 @@ - [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 (this limit is kept for the custom mastering rack; only the custom submixer rack accepts them). 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 buffers](../../../core/libs/prx/libSceNgs2.native/src/Custom.cpp) - multiple buffers on samplers, built-in submixer channels above 8, inputs beyond 0/1 and multi-buffer chains beyond zero modules or one Gain throw. Default custom module order, UserFx extra-buffer selection and invalid port-buffer errors remain unknown. Input-to-buffer routing and grain clearing follow [the PS5 12.70 reading](https://github.com/boykopovar/AnyPS5/issues/8039#issuecomment-6391072574) (0x181a0); output-port selection follows [KytyPS5](https://github.com/KytyPS5/KytyPS5/blob/83f8bfd3c9c35fcb135ef6c33dfd88bbfe066ee2/src/libs/ngs2.cpp#L417-L419). These paths have not been compared with console output. Creation validation order and QueryBufferSize validation remain unverified; the query throws for options whose creation returns an error. - [NGS2 custom submixer rack channels](../../../core/libs/prx/libSceNgs2.native/src/Rack.cpp) (libSceNgs2.native, rack 0x4002) - a custom submixer rack accepts 9 to 37 channels, but its voices take at most 8 inputs and 8 outputs ([13.42](https://github.com/boykopovar/AnyPS5/pull/11347#issuecomment-6416673360): 9/9, 12/12 and 37/37 return `0x804A8052`), so a matrixless port above 8 channels throws and the extra rack channels' role is unknown +- [NGS2 custom Reverb](../../../core/libs/prx/libSceNgs2.native/src/Reverb.cpp) (libSceNgs2.native, module 0x1b) - NaN controls, low-rate/half-rate processing, multiple custom buffers and custom mastering modules remain unsupported. Numerical equivalence is not bit-exact: 3.20 and 13.60 DSP comparisons at 48 kHz cover both creation sizes and a hot parameter update; ordinary stereo differs by less than 2e-8 and strong HF attenuation by less than 8e-8 in those cases. The original 13.60 voice graph confirms initial/setup bypass, parameter retention with history cleared on fresh PLAY, and sampler completion ending the voice. These checks use host adapters and an equivalent SSE4a extraction lowering for Rosetta; they establish neither console execution nor a game unblock. + - [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. - [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 @@ -59,6 +63,7 @@ - [NGS2 external job managers](../../../core/libs/prx/libSceNgs2.native/src/System.cpp) - supports all four manager lifecycles and slot 0 rendering, with the 112-byte scheduler option, capacity checks, query/init error propagation, reverse cleanup after failed initialization and slot-order destruction. Custom and default-pool comparisons cover synchronous and deferred submissions, dependency barriers, concurrent workers, completion before callbacks and sampler state publication. Native tests use host kernel adapters, not console execution. The host retains job contexts until join. Very small native batch-storage reuse is not an equivalence oracle. - [NGS2 worker control updates](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) - Other setup and filter updates, raw module commands, rack creation, system grain/rate changes, exclusive locks and recursive rendering from active jobs still throw. Slots 1 to 3 have manager lifecycle support but no `sceNgs2Stream*` routing. Setups, blocks and patches buffer in call order until the next grain. Supported events and render parameters retain the current grain snapshot; port volume/matrix products ramp over the next grain. Queries expose pending port/matrix values and PLAY. Paused samplers finish decode-ahead before stopping. Matrix-info requires 5,480 bytes. Host-adapted native checks cover worker order, event reduction, copied matrices and PitchShift updates at 256 samples. Active 64-sample PitchShift reports reference buffer-integrity errors and is not an equivalence oracle. KILL/restart is checked before downstream mixing; other callback dependency positions remain unverified. +- [NGS2 PS4 buffered controls](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) (libSceNgs2) - gaps: `ngs2_core_jobbuffer` runs only the port volume cases (`over`) against the PS4 library; the other cases assert PS5 behaviour that PS4 11.00 contradicts, so they are PS5-only: PAUSE keeps state flags 0x5 on PS4 (here 0x7 from the second paused grain), an add on a closed waveform returns 0x804A0151 on PS4 (here 0x804A8054), and the 8-lane gain ramp differs in the last digits (RESULTS20 items 3 and 6, PS4 11.00 libSceNgs2.sprx on a PC, ngs2-oracle-m20). No PS4 case covers buffered matrix levels, patches or events. - [NGS2 mixer and custom UserFx worker controls](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) - Other custom-module parameters, including Gain, and raw worker command encodings still throw. Custom mastering modules and additional callback dependency positions remain unverified. Oversized inactive-master output buffers differ: the reference clears the full capacity; the host clears one grain. Supported mixer events, handler replacement and UserFx2 parameters preserve the current render snapshot. Submixer STOP fades its final grain; KILL suppresses current output and sends a final callback next grain. Paused callbacks continue. Mastering keeps its distinct envelope behavior. Failed UserFx2 edits remain in control storage without publishing to rendering; later controls see them. Native comparisons use host kernel adapters, not console execution. This does not establish full custom-FX or stream support. - [NGS2 UserFx2 activation and control reentry](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) - Additional callback dependency positions, UserFx2 work/state layouts and custom mastering modules remain unverified. Same-voice control/query reentry waits in the reference; the host throws, as it does for A-to-B-to-A cycles and destruction/rendering of a live control owner. These guards are host behavior, not native error codes. Modules activate only after control. Setup restores the initial parameter image without rerunning setup callbacks. Failed edits remain in control storage and activate on a later PLAY, including failures before first PLAY. Process callbacks buffer self-controls until the next grain; control callbacks can query/control independent voices. Native comparisons use host kernel adapters; guard tests are host-only, with reference waits independently observed. - [NGS2 cross-thread control/process callbacks](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) - Same-voice callback joins and cross-thread dependency cycles still wait in the reference and require caller coordination. Host-adapted native checks cover independent-voice handoffs, process self-handoffs, successful/failed controls under contention, query serialization, cleanup, system destruction and render/control overlap. Control callbacks retain per-voice ownership while releasing the global mutex; unrelated voices progress. API calls pin rack/system storage while waiting. Rendering waits before snapshot publication, without blocking peer controls. Handles become unavailable before rack cleanup. System destruction detaches live racks; explicit rack destruction later invalidates their handles. These checks use host kernel/TLS adapters, not console execution. @@ -72,7 +77,7 @@ - [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` - [NGS2 patch destinations](../../../core/libs/prx/libSceNgs2.native/src/Voice.cpp) - a rack handle with input 0 resolves to its first voice, matching the Dead Cells call in #4334; selection on multi-voice racks remains unverified. Input 1 is supported only for a voice handle in a two-input custom submixer. Nonzero rack-handle inputs, other unsupported inputs and nonzero inputs on disconnected ports throw because their contracts are unknown. GetPortInfo reports the resolved voice handle and selected input, with zero delay. Changes to a port destination or input while its source is rendered throw before mixing: callback routing order is unverified, and combining the old destination with the new input is invalid. -- [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 +- [NGS2 PCM waveform parsing](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - not measured on a console. Every depth keeps the 16-bit checks (channels, rate, block align, whole frames), which [prosper's reading of the module](https://github.com/mattias800/prosper/blob/40d95db32efe1ad00106a5b9a84e018fffa9ecff/prosper/src/hle/audio/ngs2_waveform.cpp#L194-L343) does not apply. A `smpl` loop ending past the last sample (the module's tail size wraps) and a `smpl` chunk too short for its loop count or first loop (the module reads the bytes after it) throw. Voices play only 16-bit PCM and ATRAC9. Integer PCM (tag 1 or the extensible PCM GUID) of 8, 16, 24 and 32 bits is 0x11, 0x12, 0x14 and 0x16, float (tag 3) of 32 and 64 bits 0x18 and 0x1a, other depths 0x804A8432. As in that reading, only the first loop's start and inclusive end are read, the loop block repeats forever and an end not after the start keeps one block - [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 - [NGS2 waveform format routing](../../../core/libs/prx/libSceNgs2.native/src/Atrac9.cpp) (libSceNgs2.native) - an extensible fmt chunk of 0x34 bytes whose GUID is neither PCM nor ATRAC9, which the module parses as ATRAC9 without checking its config, and the GUID of waveform type 0x43 throw. Otherwise a tag other than 1, 3 or 0xFFFE, an extensible fmt chunk under 0x28 bytes and an unknown GUID return `0x804A8432`, per [prosper's reading of the module](https://github.com/mattias800/prosper/blob/8dcb5dfdb9997ede7c76783995010d6abd1222f8/prosper/src/hle/audio/ngs2_waveform.cpp#L190-L208); not measured on a console - [sceNgs2VoiceControl sampler filter](../../../core/libs/prx/libSceNgs2.native/src/Filter.cpp) (libSceNgs2.native) - throws: a filter param sent during a render (PS4 11.00 and PS5 accept it from the next render), PRE_SEND 2+k with another port patched, RunCommands filter commands (not run), a rate change under a filter. PLAY on a playing or paused voice without a filter plays on (PS5 restarts it). Not run: non-DC PAUSE. Measured on the PS5 libSceNgs2.native, firmware 3.20 and 4.03, run on a PC, not a console (ngs2-oracle-m12, m18): its sinf/cosf came from the PC's libm (console libm not run); this takes double sin/cos diff --git a/docs/dev/debt/libSceNpCommon.md b/docs/dev/debt/libSceNpCommon.md index ed9c965f40..87850259c5 100644 --- a/docs/dev/debt/libSceNpCommon.md +++ b/docs/dev/debt/libSceNpCommon.md @@ -29,14 +29,14 @@ Pending import declarations are matched to names in the local libSceNpCommon.spr - [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`. -- [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`. +- [sceNpUtilParseJid and sceNpUtilJidToNpId](../../../core/libs/prx/libSceNpCommon/Strings.cpp) (libSceNpCommon) - read from `libSceNpCommon.sprx` of firmware 13.60 disassembly, not measured on a console, including two quirks a JID grammar would not have: the parser skips exactly one character after the domain (its trimmed trailing dots, a space or `/`) before the resource, and keeps everything after that as the resource without checking it. A domain is taken as `[A-Za-z0-9._-]+` and a user as `[A-Za-z0-9+._-]+` before `@`, with letters and digits from the C locale. `sceNpUtilJidToNpId` accepts only `XX.YY.