diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000..44971015c --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,45 @@ +relinker: + - changed-files: + - any-glob-to-any-file: 'core/relinker/**' + +shader: + - changed-files: + - any-glob-to-any-file: 'core/shader/**' + +agc: + - changed-files: + - any-glob-to-any-file: 'core/libs/prx/libSceAgc*/**' + +libc: + - changed-files: + - any-glob-to-any-file: 'core/libs/prx/libc/**' + +libkernel: + - changed-files: + - any-glob-to-any-file: 'core/libs/prx/libkernel/**' + +prx: + - changed-files: + - all-globs-to-any-file: + - 'core/libs/prx/**' + - '!core/libs/prx/libSceAgc*/**' + - '!core/libs/prx/libc/**' + - '!core/libs/prx/libkernel/**' + +build: + - changed-files: + - any-glob-to-any-file: + - '**/CMakeLists.txt' + - '**/*.cmake' + - '.gitmodules' + - '3rdparty/**' + +docs: + - changed-files: + - any-glob-to-any-file: + - '**/*.md' + - 'docs/**' + +ci: + - changed-files: + - any-glob-to-any-file: '.github/**' diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..f895a33e9 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +### What + +### Tested + + +### Checklist + +- [ ] Based on current `main`; no other open PR implements the same functions +- [ ] One topic per PR; follow-ups go in a new PR +- [ ] No comments in code except [technical debt](https://github.com/boykopovar/AnyPS5/blob/main/docs/CONVENTIONS.md) +- [ ] Unimplemented paths throw (`NotImplemented_nid_no_patch`); silent stubs are listed in [TechnicalDebt](https://github.com/boykopovar/AnyPS5/blob/main/docs/TechnicalDebt.md#silent-stubs) +- [ ] No notes, investigation `.md` files or images added to the repository (attach them to this PR) +- [ ] New third-party code is a submodule built from source +- [ ] Generic behaviour, not specific to one title +- [ ] Depends on: +- [ ] AI-assisted: diff --git a/.github/workflows/area-labels.yml b/.github/workflows/area-labels.yml new file mode 100644 index 000000000..fb0dbe9b8 --- /dev/null +++ b/.github/workflows/area-labels.yml @@ -0,0 +1,29 @@ +name: Area labels + +on: + schedule: + - cron: '30 * * * *' + workflow_dispatch: + +permissions: {} + +jobs: + label: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - id: prs + env: + GH_TOKEN: ${{ github.token }} + run: | + { + echo 'numbers<> "$GITHUB_OUTPUT" + - if: steps.prs.outputs.numbers != '' + uses: actions/labeler@v7 + with: + pr-number: ${{ steps.prs.outputs.numbers }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..c40f7ec45 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,75 @@ +name: Build + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: build-${{ github.ref }} + cancel-in-progress: true + +env: + CONFIGURE: >- + -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON + -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ + -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + +jobs: + linux: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v7 + with: + submodules: true + - uses: hendrikmuhs/ccache-action@v1.2.24 + with: + key: linux + save: ${{ github.event_name == 'push' }} + evict-old-files: job + - run: cmake -S . -B build $CONFIGURE + - run: cmake --build build + - run: cmake --build build --target libs + - run: ctest --test-dir build --output-on-failure --timeout 120 + + windows: + runs-on: windows-latest + timeout-minutes: 45 + env: + WINLIBS_RELEASE: 15.2.0posix-14.0.0-ucrt-r7 + WINLIBS_ZIP: winlibs-x86_64-posix-seh-gcc-15.2.0-mingw-w64ucrt-14.0.0-r7.zip + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v7 + with: + submodules: true + - id: toolchain + uses: actions/cache@v6 + with: + path: C:\winlibs + key: ${{ env.WINLIBS_ZIP }} + - if: steps.toolchain.outputs.cache-hit != 'true' + run: | + curl -sSfL -o "$RUNNER_TEMP/winlibs.zip" "https://github.com/brechtsanders/winlibs_mingw/releases/download/$WINLIBS_RELEASE/$WINLIBS_ZIP" + 7z x "$RUNNER_TEMP/winlibs.zip" -oC:/winlibs > /dev/null + - run: echo 'C:\winlibs\mingw64\bin' >> "$GITHUB_PATH" + - uses: hendrikmuhs/ccache-action@v1.2.24 + with: + key: windows + save: ${{ github.event_name == 'push' }} + evict-old-files: job + - shell: cmd + run: cmake -S . -B build %CONFIGURE% + - shell: cmd + run: cmake --build build + - shell: cmd + run: cmake --build build --target libs + - shell: cmd + run: ctest --test-dir build --output-on-failure --timeout 120 diff --git a/.github/workflows/conflict-label.yml b/.github/workflows/conflict-label.yml new file mode 100644 index 000000000..d6183c9eb --- /dev/null +++ b/.github/workflows/conflict-label.yml @@ -0,0 +1,21 @@ +name: Conflict label + +on: + push: + branches: [main] + schedule: + - cron: '0 * * * *' + workflow_dispatch: + +permissions: {} + +jobs: + label: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: eps1lon/actions-label-merge-conflict@v3 + with: + dirtyLabel: needs rebase + repoToken: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/progress-comment.yml b/.github/workflows/progress-comment.yml new file mode 100644 index 000000000..05568e459 --- /dev/null +++ b/.github/workflows/progress-comment.yml @@ -0,0 +1,42 @@ +name: Progress comment + +on: + workflow_run: + workflows: [Progress report] + types: [completed] + +permissions: + actions: read + pull-requests: write + +jobs: + comment: + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v8 + with: + name: progress-report + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + - env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + RUN_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + pr=$(cat pr) + [[ $pr =~ ^[0-9]+$ ]] || exit 1 + [ "$(gh pr view "$pr" --json headRefOid --jq .headRefOid)" = "$RUN_SHA" ] || exit 0 + marker='' + id=$(gh api "repos/$GH_REPO/issues/$pr/comments" --paginate \ + --jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$marker\"))) | .id" | head -n1) + if [ -f report.md ]; then + printf '%s\n%s' "$marker" "$(cat report.md)" > body.md + if [ -n "$id" ]; then + gh api -X PATCH "repos/$GH_REPO/issues/comments/$id" -F body=@body.md > /dev/null + else + gh pr comment "$pr" --body-file body.md + fi + elif [ -n "$id" ]; then + gh api -X DELETE "repos/$GH_REPO/issues/comments/$id" + fi diff --git a/.github/workflows/progress-report.yml b/.github/workflows/progress-report.yml new file mode 100644 index 000000000..3fbf7361b --- /dev/null +++ b/.github/workflows/progress-report.yml @@ -0,0 +1,34 @@ +name: Progress report + +on: + pull_request: + +permissions: + contents: read + +jobs: + report: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 2 + - env: + PR: ${{ github.event.pull_request.number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + out=$RUNNER_TEMP/out report=$RUNNER_TEMP/report + git worktree add --detach "$RUNNER_TEMP/base" HEAD^1 + python3 tools/progress.py --root "$RUNNER_TEMP/base" "$out/base" + python3 tools/progress.py "$out/head" + mkdir "$report" + echo "$PR" > "$report/pr" + body=$(python3 tools/progress.py --compare "$out/base/progress.json" "$out/head/progress.json") + if [ -n "$body" ]; then + printf '### Progress report (%s → %s)\n\n%s\n' "$(git rev-parse --short HEAD^1)" "${HEAD_SHA::7}" "$body" > "$report/report.md" + cat "$report/report.md" >> "$GITHUB_STEP_SUMMARY" + fi + - uses: actions/upload-artifact@v7 + with: + name: progress-report + path: ${{ runner.temp }}/report diff --git a/.github/workflows/progress.yml b/.github/workflows/progress.yml new file mode 100644 index 000000000..93e9f0600 --- /dev/null +++ b/.github/workflows/progress.yml @@ -0,0 +1,30 @@ +name: Progress + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + progress: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v7 + - run: python3 tools/progress.py out + - uses: actions/upload-pages-artifact@v5 + with: + path: out + - id: deployment + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..522ada2fd --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,126 @@ +name: Release + +on: + push: + tags: ['v*'] + +permissions: + contents: read + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +env: + RELEASE_TAG: ${{ github.ref_name }} + CONFIGURE: >- + -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON + -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ + -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + WINLIBS_RELEASE: 15.2.0posix-14.0.0-ucrt-r7 + WINLIBS_ZIP: winlibs-x86_64-posix-seh-gcc-15.2.0-mingw-w64ucrt-14.0.0-r7.zip + +jobs: + build: + name: Build and test (${{ matrix.platform }}) + strategy: + fail-fast: true + matrix: + include: + - platform: linux + runner: ubuntu-latest + timeout: 30 + - platform: windows + runner: windows-latest + timeout: 45 + runs-on: ${{ matrix.runner }} + timeout-minutes: ${{ matrix.timeout }} + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v7 + with: + submodules: true + - id: toolchain + if: matrix.platform == 'windows' + uses: actions/cache@v6 + with: + path: C:\winlibs + key: ${{ env.WINLIBS_ZIP }} + - if: matrix.platform == 'windows' && steps.toolchain.outputs.cache-hit != 'true' + run: | + curl -sSfL -o "$RUNNER_TEMP/winlibs.zip" "https://github.com/brechtsanders/winlibs_mingw/releases/download/$WINLIBS_RELEASE/$WINLIBS_ZIP" + 7z x "$RUNNER_TEMP/winlibs.zip" -oC:/winlibs + - if: matrix.platform == 'windows' + run: echo 'C:\winlibs\mingw64\bin' >> "$GITHUB_PATH" + - uses: hendrikmuhs/ccache-action@v1.2.24 + with: + key: ${{ matrix.platform }} + save: false + - name: Configure Linux + if: matrix.platform == 'linux' + run: cmake -S . -B build $CONFIGURE + - name: Configure Windows + if: matrix.platform == 'windows' + shell: cmd + run: cmake -S . -B build %CONFIGURE% + - name: Build project and tests (Linux) + if: matrix.platform == 'linux' + run: cmake --build build + - name: Build project and tests (Windows) + if: matrix.platform == 'windows' + shell: cmd + run: cmake --build build + - name: Run tests (Linux) + if: matrix.platform == 'linux' + run: ctest --test-dir build --output-on-failure --timeout 120 --stop-on-failure --no-tests=error + - name: Run tests (Windows) + if: matrix.platform == 'windows' + shell: cmd + run: ctest --test-dir build --output-on-failure --timeout 120 --stop-on-failure --no-tests=error + - name: Build patched libraries (Linux) + if: matrix.platform == 'linux' + run: cmake --build build --target libs + - name: Build patched libraries (Windows) + if: matrix.platform == 'windows' + shell: cmd + run: cmake --build build --target libs + - name: Package release files + run: python tools/package_release.py --platform "${{ matrix.platform }}" --build build --output dist --version "$RELEASE_TAG" + - uses: actions/upload-artifact@v7 + with: + name: release-${{ matrix.platform }} + path: dist/* + if-no-files-found: error + compression-level: 0 + + publish: + name: Publish release + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + steps: + - uses: actions/checkout@v7 + - uses: actions/download-artifact@v8 + with: + pattern: release-* + merge-multiple: true + path: dist + - name: Check release files and collect documentation + shell: bash + run: | + for file in "prx-windows-$RELEASE_TAG.zip" "prx-windows-$RELEASE_TAG.tar.gz" "prx-linux-$RELEASE_TAG.zip" "prx-linux-$RELEASE_TAG.tar.gz" "relinker-$RELEASE_TAG.exe" "relinker-$RELEASE_TAG"; do + test -s "dist/$file" + done + python tools/package_release.py --docs docs/user --output dist + chmod +x "dist/relinker-$RELEASE_TAG" + - name: Create draft and upload files + run: gh release create "$RELEASE_TAG" dist/* --verify-tag --draft --title "$RELEASE_TAG" --generate-notes + - name: Publish complete release + run: gh release edit "$RELEASE_TAG" --draft=false diff --git a/.gitmodules b/.gitmodules index a4bbf61a1..3c7f04fbe 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "3rdparty/LibAtrac9"] path = 3rdparty/LibAtrac9 url = https://github.com/shadps4-emu/ext-LibAtrac9.git +[submodule "3rdparty/freetype"] + path = 3rdparty/freetype + url = https://github.com/freetype/freetype.git diff --git a/3rdparty/freetype b/3rdparty/freetype new file mode 160000 index 000000000..42608f77f --- /dev/null +++ b/3rdparty/freetype @@ -0,0 +1 @@ +Subproject commit 42608f77f20749dd6ddc9e0536788eaad70ea4b5 diff --git a/CMakeLists.txt b/CMakeLists.txt index 62dd3d872..008b7a847 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,15 @@ set(CMAKE_EXE_LINKER_FLAGS "") add_subdirectory(3rdparty/SDL2) set(CMAKE_EXE_LINKER_FLAGS "${ANYPS5_EXE_LINKER_FLAGS}") +set(FT_DISABLE_ZLIB ON CACHE BOOL "" FORCE) +set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE) +set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE) +set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE) +set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE) +set(SKIP_INSTALL_ALL ON CACHE BOOL "" FORCE) +add_subdirectory(3rdparty/freetype EXCLUDE_FROM_ALL) +set_target_properties(freetype PROPERTIES POSITION_INDEPENDENT_CODE ON) + add_subdirectory(core/shader/recompiler) add_subdirectory(core/libs) add_subdirectory(core/relinker) diff --git a/README.md b/README.md index a98b5a96f..9d2bdc641 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,25 @@ Tool for automatic executables porting to Linux and Windows. Includes a [relinker](core/relinker) that converts executable to the target system's native format and implementations of [system prx libraries](core/libs/prx) suitable for dynamic linking. No emulation or separate runtime process. -`relinker --to-intel` rewrites the Zen 2-only instructions a title may contain (EXTRQ, INSERTQ, MOVNTSS, MOVNTSD) at relink time, in place when an equal-length Intel encoding exists and otherwise through a jump to a generated stub section, so Intel hosts need no runtime emulation. -By default the relinker also converts the title's own `sce_module`/`sce_modules` prx files into guest modules that take priority at link time (they require eager binding). A title whose `sce_module` ships its own `libc.prx`, such as Demon's Souls (PPSA01342), is relinked with `--skip-sce-module`: its libc would replace the project's [libc.prx](core/libs/prx/libc) (guest memory arena, heap, crash reporting) and imports kernel functions that are not implemented, so the executable fails to bind at startup. - -Releases will be published after the first full successful launch of at least one game. ## Status -Execution reaches `_start`, [stack unwinding](core/libs/prx/libc/src/exception/Unwind.cpp) and exception handling tables are built, reaches main. Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates. +[![libraries](https://boykopovar.github.io/AnyPS5/badge-libraries.svg)](https://boykopovar.github.io/AnyPS5/) [![shaders](https://boykopovar.github.io/AnyPS5/badge-shaders.svg)](https://boykopovar.github.io/AnyPS5/) -The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces SPIR-V, validated via [Spirv-Tools](3rdparty/SPIRV-Tools) when built with `ANYPS5_ENABLE_SPIRV_TOOLS`. +[![progress map](https://boykopovar.github.io/AnyPS5/progress.svg)](https://boykopovar.github.io/AnyPS5/) -The real game reaches the logo, main menu, and [gameplay](https://gist.github.com/user-attachments/assets/81d28e9b-c237-4545-b2ca-720071129816) with audio. +* System libraries: percentage of the functions known to the project so far (declared in [core/libs/prx](core/libs/prx)), not of every PS5 system function. The total grows as more functions are declared. -[Technical debt of the project](docs/TechnicalDebt.md), [code style conventions](docs/CONVENTIONS.md) +[List of verified games](docs/user/COMPATIBILITY.md) + +Dreaming Sarah (2D platformer) runs at a stable 60 fps on a GTX 1050 Ti / i5-7500 3.4GHz. + +Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates. + +The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces SPIR-V (validated via [Spirv-Tools](3rdparty/SPIRV-Tools) when built with `ANYPS5_ENABLE_SPIRV_TOOLS`). + +[Technical debt of the project](docs/dev/TechnicalDebt.md), [code style conventions](docs/dev/CONVENTIONS.md) ## Build @@ -28,9 +32,13 @@ The relinker uses only the C++20 standard library and should build with any conf The project targets maximum compiler portability. Support for additional compilers will be addressed after the first successful game launch. +## Compatibility + +See the [game compatibility list](docs/user/COMPATIBILITY.md) for tested games and known issues. + ## Input mapping -SDL-mapped game controllers are supported, including analog sticks and triggers. Keyboard and mouse controls can be configured with an `anyps5-input.ini` file. See [input mapping](docs/INPUT_MAPPING.md) for the supported devices and configuration format. +SDL-mapped game controllers are supported, including analog sticks and triggers. Keyboard and mouse controls can be configured with an `anyps5-input.ini` file. See [input mapping](docs/user/INPUT_MAPPING.md) for the supported devices and configuration format. ## Disclaimer diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index 97021bfbf..202d7a4a5 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -13,10 +13,12 @@ add_executable(nid_patcher ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/BinaryPatcherFactory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/main.cpp ) + target_include_directories(nid_patcher PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/nid/include) target_compile_features(nid_patcher PRIVATE cxx_std_20) if(MINGW) + target_link_options(nid_patcher PRIVATE -static -static-libgcc -static-libstdc++) add_compile_options(-fno-asynchronous-unwind-tables) endif() @@ -106,6 +108,17 @@ foreach(LIB_DIR ${LIBS_DIRS}) endif() endforeach() +set(LIBCOHTML_PATCHED ${PATCHED_DIR}/libcohtml.prx) +set(LIBCOHTML_PROSPERO_PATCHED ${PATCHED_DIR}/libcohtml.Prospero.prx) +add_custom_command( + OUTPUT ${LIBCOHTML_PROSPERO_PATCHED} + COMMAND ${CMAKE_COMMAND} -E copy "${LIBCOHTML_PATCHED}" "${LIBCOHTML_PROSPERO_PATCHED}" + DEPENDS ${LIBCOHTML_PATCHED} + VERBATIM +) +add_custom_target(libcohtml_prospero DEPENDS ${LIBCOHTML_PROSPERO_PATCHED}) +list(APPEND PATCHED_OUTPUTS ${LIBCOHTML_PROSPERO_PATCHED}) + add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS}) if(BUILD_TESTING) @@ -385,9 +398,17 @@ if(BUILD_TESTING) set_tests_properties(guest_rtc PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_executable(guest_font_tests tests/GuestFont.cpp) + target_include_directories(guest_font_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_font_tests PRIVATE libSceFont libSceFontFt libc) + add_test(NAME guest_font COMMAND guest_font_tests) + if(WIN32) + set_tests_properties(guest_font PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_executable(guest_json_tests tests/GuestJson.cpp) target_include_directories(guest_json_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(guest_json_tests PRIVATE libSceJson2) + target_link_libraries(guest_json_tests PRIVATE libSceJson2 libc) add_test(NAME guest_json COMMAND guest_json_tests) if(WIN32) set_tests_properties(guest_json PROPERTIES ENVIRONMENT_MODIFICATION @@ -395,7 +416,7 @@ if(BUILD_TESTING) endif() add_executable(fiber_tests tests/Fiber.cpp) target_include_directories(fiber_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - target_link_libraries(fiber_tests PRIVATE libSceFiber) + target_link_libraries(fiber_tests PRIVATE libSceFiber libc) add_test(NAME fiber COMMAND fiber_tests) if(WIN32) set_tests_properties(fiber PROPERTIES ENVIRONMENT_MODIFICATION diff --git a/core/libs/HitLog.hpp b/core/libs/HitLog.hpp new file mode 100644 index 000000000..e94e3228b --- /dev/null +++ b/core/libs/HitLog.hpp @@ -0,0 +1,18 @@ +#ifndef CORE_LIBS_HITLOG_HPP +#define CORE_LIBS_HITLOG_HPP + +#include +#include + +// Logs the first three calls of the surrounding call site, then stays silent (keeps guest logs usable). +#define APS5_HIT(tag, ...) \ + do { \ + static std::atomic aps5HitCount{0}; \ + if (aps5HitCount.fetch_add(1, std::memory_order_relaxed) < 3) { \ + std::fprintf(stderr, "[" tag "] " __VA_ARGS__); \ + std::fputc('\n', stderr); \ + std::fflush(stderr); \ + } \ + } while (0) + +#endif diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 51b0a9caf..2328a171c 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -1150,207 +1150,6 @@ struct PositionAndForm { std::uint32_t height; }; -using FontHandle = void*; -using FontLibrary = void*; -using FontLibrarySelection = void*; -using FontRenderer = void*; -using FontRendererSelection = void*; - -using FontMemoryDestroyCallback = void (*)(void*); - -struct FontMemoryInterface { - void* (*malloc_func)(std::size_t); - void (*free_func)(void*); -}; - -struct FontMemory { - std::uint16_t type; - std::uint16_t attr; - std::uint32_t size; - void* address; - void* mspace_object; - const FontMemoryInterface* mem_interface; - FontMemoryDestroyCallback destroy_callback; - void* destroy_object; - void* user_object; - void* parent_object; -}; - -using FontOpenDetail = void; - -struct FontHorizontalLayout { - float base_line_y; - float line_height; - float effect_height; -}; - -struct FontVerticalLayout { - float base_line_x; - float line_width; - float effect_width; -}; - -struct FontGlyphMetrics { - float width; - float height; - struct { - float bearing_x; - float bearing_y; - float advance; - } horizontal; - struct { - float bearing_x; - float bearing_y; - float advance; - } vertical; -}; - -struct FontRenderSurface { - void* buffer; - std::int32_t width_byte; - std::int8_t pixel_size_byte; - std::uint8_t system_ext0; - std::uint8_t system_ext1; - std::uint8_t system_ext2; - std::int32_t width; - std::int32_t height; - struct { - std::uint32_t x0; - std::uint32_t y0; - std::uint32_t x1; - std::uint32_t y1; - } scissor; - std::uint32_t system_use[22]; -}; - -struct FontRenderCharacter { std::uint8_t reserved[128]; }; - -struct FontTransImage { - std::uint8_t* address; - std::uint32_t width_byte; - std::uint32_t image_width; - std::uint32_t image_height; -}; - -struct FontSurfaceImage { - std::uint8_t* address; - std::uint32_t width_byte; - std::uint8_t pixel_size_byte; - std::uint8_t pixel_format; -}; - -struct FontGlyphImageMetrics { - float bearing_x; - float bearing_y; - float advance; - float stride; - std::uint32_t width; - std::uint32_t height; -}; - -struct FontRenderResult { - const FontTransImage* trans_image; - FontSurfaceImage surface_image; - struct { - std::uint32_t x; - std::uint32_t y; - std::uint32_t w; - std::uint32_t h; - } update_rect; - FontGlyphImageMetrics image_metrics; -}; - -using FontCreateStringOrdersFunction = void (*)(void*, void*); - -struct FontCreateStringDetail { - std::uint16_t detail_id; - std::uint8_t detail_type; - std::uint8_t detections; - std::uint32_t orders_option; - FontHandle default_font; - struct { - FontCreateStringOrdersFunction function; - void* object; - } orders; -}; - -struct FontGenerateGlyphDetail { std::uint8_t reserved[64]; }; - -struct FontString { void* system_use[32]; }; - -struct FontWriting { void* system_use[32]; }; - -struct FontWritingExtent { - float top; - float bottom; - float left; - float right; -}; - -struct FontWritingMetrics { - float advance_x; - float advance_y; - FontWritingExtent extent; -}; - -struct FontWritingStep { - float x; - float y; - float advance_x; - float advance_y; - FontHandle font; - struct { - std::uint32_t character_count : 8; - std::uint32_t invisible_glyph : 1; - } profile; - std::uint32_t glyph_code; - struct { - float x; - float y; - } positioning; - FontGlyphMetrics glyph_metrics; -}; - -struct FontWritingLine { std::uint8_t reserved[128]; }; - -struct FontWritingLineStep { - float x; - float y; - float advance_x; - float advance_y; - float spacing_progress; - void* writing_orderer; - struct { - float x; - float y; - } adjusting; - FontWritingMetrics metrics; -}; - -using FontTextParseFunction = int (*)(void*, std::uint32_t, void*); - -struct FontTextSource { - std::uint64_t system_use0; - const void* start; - const void* end; - const void* current; - FontTextParseFunction text_parser; - void* text_object; - FontHandle default_font; - void* system_use[5]; -}; - -struct FontTextCharacter { std::uint8_t reserved[64]; }; - -union FontTextCodes { - struct { - void* order; - std::uint32_t code; - std::uint32_t reserved; - } text; - void* system_reserved[8]; -}; - struct NetEtherAddr { std::uint8_t data[6]; }; union NetEpollData { diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp new file mode 100644 index 000000000..56dd9215f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AgcDriver { + +class CaptureTrace { +public: + static bool Enabled() { + static const bool enabled = std::getenv("APS5_CAPTURE_TRACE") != nullptr; + return enabled; + } + + template + static void Log(const char* format, TArgs... args) { + 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(); + trace.append(std::string(line.data(), static_cast(size))); + } + +private: + static CaptureTrace& instance() { + static CaptureTrace trace; + return trace; + } + + CaptureTrace() { + output.exceptions(std::ios::badbit | std::ios::failbit); + output.open("capture-trace.log", std::ios::binary); + worker = std::thread([this] { run(); }); + } + + ~CaptureTrace() { + { + std::lock_guard lock(mutex); + stopping = true; + } + changed.notify_one(); + worker.join(); + } + + void append(std::string line) { + 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 (line.starts_with("blit ")) flushRequested = true; + if (pending.size() >= 65536 || flushRequested) changed.notify_one(); + } + + 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; + } + 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(); + } + } + + std::ofstream output; + std::mutex mutex; + std::condition_variable changed; + std::string pending; + std::thread worker; + std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); + unsigned long long sequence = 0; + bool stopping = false; + bool flushRequested = false; +}; + +} diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp index d2fde19a7..665b5d159 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/Driver.hpp @@ -11,6 +11,7 @@ void Submit(const Packet* packet, std::uint32_t queue); } extern "C" void AgcDriverWaitIdle_nid_postfix(); +extern "C" void AgcDriverShutdown_nid_postfix(); extern "C" void AgcDriverSuspendPoint_nid_postfix(); extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp index c3fb1e15c..d56076311 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp @@ -21,6 +21,7 @@ public: // Fills the source image from another image of the same extent (any blittable format, layout // unchanged), leaving the source image ready to blit or read back. void RecordBlitInto(VkCommandBuffer commands, VkImage image, VkImageLayout layout, VkFilter filter); + void RecordBlitInto(VkCommandBuffer commands, VkImage image, VkImageLayout layout, VkFilter filter, std::uint32_t width, std::uint32_t height); // Copies the source image's texels (tightly packed rows of its format) into a host-readable buffer // and makes them visible to the host once the submission completes. void RecordReadback(VkCommandBuffer commands, VkBuffer destination); diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp index 65b272671..ec282a171 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp @@ -5,6 +5,7 @@ namespace AgcDriver { // Starts sampling the calling thread when APS5_SAMPLE_WORKER names an output file. void StartWorkerSampler(); +void StopWorkerSampler(); } diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index 4af28bfd6..df87df2d3 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" @@ -10,6 +11,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" +#include "prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp" #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/CpuTopology.hpp" #include "ControlFlow/RequestSerializer.hpp" @@ -459,24 +461,34 @@ public: void Shutdown() { stop(); - CheckFailure(); + std::lock_guard lock(mutex); + rethrowFailure(); } private: void stop() { require(!OnWorkerThread(), "worker cannot stop itself"); std::lock_guard shutdownLock(shutdownMutex); + if (stopped) return; + LibcRequestShutdown_nid_postfix(); { std::lock_guard lock(mutex); stopping = true; + for (auto& [queue, worker] : workers) { + worker.pending.clear(); + worker.queued.store(0, std::memory_order_release); + } } changed.notify_all(); - // No worker is added once `stopping` is set, so the map is stable while the threads finish. for (auto& [queue, worker] : workers) { if (worker.thread.joinable()) worker.thread.join(); } + StopWorkerSampler(); std::lock_guard gpuLock(GuestMemory::GpuMutex()); device.reset(); + replacedDevices.clear(); + Graphics::ShutdownGuestBufferWorkers(); + stopped = true; } public: @@ -505,7 +517,7 @@ public: { std::lock_guard lock(mutex); rethrowFailure(); - require(!stopping, "submission during shutdown"); + checkStopping(); require(accepted != std::numeric_limits::max(), "submission serial overflow"); for (std::size_t cursor = 0; cursor < submission.commands.size();) { const auto* words = submission.commands.data() + cursor; @@ -549,16 +561,17 @@ public: std::unique_lock lock(mutex); const auto target = accepted; ++idleWaiters; - changed.wait(lock, [&] { return failure != nullptr || completed >= target; }); + changed.wait(lock, [&] { return failure != nullptr || stopping || completed >= target; }); --idleWaiters; rethrowFailure(); + checkStopping(); } void SuspendPoint() { require(!OnWorkerThread(), "worker cannot suspend itself"); std::unique_lock lock(mutex); rethrowFailure(); - require(!stopping, "suspend during shutdown"); + checkStopping(); require(accepted != std::numeric_limits::max(), "submission serial overflow"); Submission boundary{}; boundary.serial = accepted + 1; @@ -577,7 +590,7 @@ public: require(output != nullptr, "null video output"); std::lock_guard lock(mutex); rethrowFailure(); - require(!stopping, "video output registration during shutdown"); + checkStopping(); require(outputs.emplace(handle, output).second, "video output already registered"); } @@ -591,9 +604,11 @@ public: // Called per packet and per poll of a wait: the flag keeps the driver mutex out of those loops // until a failure exists, and `failure` itself is still read under the mutex. void CheckFailure() { - if (!failed.load(std::memory_order_acquire)) return; - std::lock_guard lock(mutex); - rethrowFailure(); + if (failed.load(std::memory_order_acquire)) { + std::lock_guard lock(mutex); + rethrowFailure(); + } + checkStopping(); } void ReportFailure(std::exception_ptr error) { @@ -653,7 +668,6 @@ public: if (device == nullptr || device->Window() == nullptr) { if (device) { device->PrepareForReplacement(); - static std::vector> replacedDevices; replacedDevices.push_back(device); } device = std::make_shared(&window); @@ -721,6 +735,8 @@ public: timing.Mark("release_and_callback"); if (profile) reportPresents(waitedMs, inFlight); CheckFailure(); + } catch (const ProcessShutdown&) { + throw; } catch (...) { ReportFailure(std::current_exception()); throw; @@ -1449,13 +1465,16 @@ private: std::map queues; std::map> outputs; DevicePointer device; + std::vector> replacedDevices; + std::stop_token shutdownToken = LibcShutdownToken_nid_postfix(); std::uint64_t accepted = 0; std::uint64_t completed = 0; std::set completedOutOfOrder; std::exception_ptr failure; // Set once `failure` is, so hot loops can check for one without the mutex. std::atomic failed{false}; - bool stopping = false; + std::atomic stopping{false}; + bool stopped = false; bool resetGraphics = false; // Threads in WaitIdle (under `mutex`): a worker's completion notifies only while one waits. std::uint32_t idleWaiters = 0; @@ -1490,6 +1509,10 @@ private: } } + void checkStopping() const { + if (stopping.load(std::memory_order_acquire) || shutdownToken.stop_requested()) throw ProcessShutdown{}; + } + // Summarizes a rejected submission (packet names with counts and the first rejection reason per name). static void dumpPackets(std::span commands, const std::uint32_t* guest = nullptr) { std::map> summary; @@ -5621,6 +5644,7 @@ private: const auto opcode = (header >> 8u) & 0xffu; // Names this packet for the flush hook's sync attribution ([hooksync]); the flip is 0xffff. GuestMemory::SetCurrentPacket(header == FlipPacketHeader ? 0xffffu : opcode, submission.queue); + CaptureTrace::Log("packet submission=%llu queue=%x offset=%zu header=%08x words=%zu", static_cast(submission.serial), submission.queue, cursor, header, packet.size()); // Pending labels and full batches go to the GPU before this packet's own work starts // (see flushBetweenPackets); labels themselves only check the deadline, so a label // group shares one submission. Timed on its own, before the packet's timer starts. @@ -5930,13 +5954,14 @@ private: frame->IncludeSubmission(submission.serial, now, now, now, true); frame->SetFlip(submission.serial, cursor, now, now); frame->NoteFlipBatches(batchesAtFlip, unsignaledAtFlip); + CaptureTrace::Log("flip frame=%llu submission=%llu offset=%zu batch=%llu unsignaled=%llu", static_cast(frameSerial), static_cast(submission.serial), cursor, static_cast(batchesAtFlip), static_cast(unsignaledAtFlip)); submission.flips.at(cursor)->GpuReady(frame); } else if (opcode == 0x15) { - timed(&WorkerProfile::dispatchMs, [&] { tolerate("dispatch", [&] { dispatch(queue, packet, submission); }); }); + timed(&WorkerProfile::dispatchMs, [&] { dispatch(queue, packet, submission); }); Graphics::Recorder::CountRecordedWork(); finishDispatchPacket(false); } else if (opcode == 0x16) { - timed(&WorkerProfile::dispatchMs, [&] { tolerate("indirect dispatch", [&] { dispatchIndirect(queue, packet, submission); }); }); + timed(&WorkerProfile::dispatchMs, [&] { dispatchIndirect(queue, packet, submission); }); Graphics::Recorder::CountRecordedWork(); finishDispatchPacket(true); } else if (opcode == 0x3c || opcode == 0x93) { @@ -5994,6 +6019,7 @@ private: std::string rejected; const auto verdict = draw(queue, packet, submission, rejected); drawn = verdict == DrawVerdict::Drawn; + CaptureTrace::Log("draw submission=%llu queue=%x offset=%zu target=%llx mask=%x verdict=%d reason=%.256s", static_cast(submission.serial), submission.queue, cursor, static_cast(color), readRegister(queue.context, 0x8e), static_cast(verdict), rejected.c_str()); if (verdict == DrawVerdict::Rejected) { skipped(rejected); countSkip(Graphics::DrawSkip::Prechecked); @@ -6003,6 +6029,7 @@ private: std::fprintf(stderr, "[draw] target 0x%llx mask 0x%x ok\n",static_cast(color), readRegister(queue.context, 0x8e)); } } catch (const std::exception& error) { + CaptureTrace::Log("draw-error submission=%llu offset=%zu reason=%.256s", static_cast(submission.serial), cursor, error.what()); skipped(error.what()); countSkip(Graphics::DrawSkip::Thrown); } @@ -6111,7 +6138,7 @@ private: lock.lock(); } rethrowFailure(); - if (pending.empty()) { + if (stopping || shutdownToken.stop_requested() || pending.empty()) { break; } submission = std::move(pending.front()); @@ -6135,31 +6162,32 @@ private: else ++costs.notifiesSkipped; if (profile) costs.completeNs += static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - completeStart).count()); } + } catch (const ProcessShutdown&) { + submission = Submission{}; } catch (...) { const auto error = std::current_exception(); - try { - std::rethrow_exception(error); - } catch (const std::exception& reason) { - std::fprintf(stderr, "[gpu] worker failed: %s\n", reason.what()); - } catch (...) { - std::fprintf(stderr, "[gpu] worker failed with a non-standard exception\n"); - } - std::fflush(stderr); - std::terminate(); + ReportFailure(error); + for (const auto& [offset, flip] : submission.flips) flip->Fail(error); } } }; } -void Submit(const Packet* packet, std::uint32_t queue) { +void Submit(const Packet* packet, std::uint32_t queue) try { Driver::Get().Submit(packet, queue); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } void WaitIdle() { Driver::Get().WaitIdle(); } +void Shutdown() { + Driver::Get().Shutdown(); +} + void RegisterShader(const Shader* shader) { Driver::Get().RegisterShader(shader); } @@ -6194,16 +6222,26 @@ void ReportFailure(std::exception_ptr error) { } -extern "C" void AgcDriverWaitIdle_nid_postfix() { +extern "C" void AgcDriverWaitIdle_nid_postfix() try { AgcDriver::WaitIdle(); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader) { +extern "C" void AgcDriverShutdown_nid_postfix() { + AgcDriver::Shutdown(); +} + +extern "C" void AgcDriverRegisterShader_nid_postfix(const Shader* shader) try { AgcDriver::RegisterShader(shader); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -extern "C" void AgcDriverSuspendPoint_nid_postfix() { +extern "C" void AgcDriverSuspendPoint_nid_postfix() try { AgcDriver::SuspendPoint(); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } extern "C" void AgcDriverRegisterVideoOutput_nid_postfix(std::uint32_t handle, const std::shared_ptr& output) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/PresentationScaler.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/PresentationScaler.cpp index aa7cd9bc9..ecea37cbe 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/PresentationScaler.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/PresentationScaler.cpp @@ -79,7 +79,12 @@ void PresentationScaler::RecordUpload(VkCommandBuffer commands, VkBuffer uploadB } void PresentationScaler::RecordBlitInto(VkCommandBuffer commands, VkImage image, VkImageLayout layout, VkFilter filter) { + RecordBlitInto(commands, image, layout, filter, sourceWidth, sourceHeight); +} + +void PresentationScaler::RecordBlitInto(VkCommandBuffer commands, VkImage image, VkImageLayout layout, VkFilter filter, std::uint32_t width, std::uint32_t height) { Graphics::Require(sourceImage != VK_NULL_HANDLE && image != VK_NULL_HANDLE, "presentation image is unavailable"); + Graphics::Require(width != 0 && height != 0, "presentation blit extent must be non-zero"); const auto pipelineBarrier = context.Function("vkCmdPipelineBarrier"); VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; @@ -92,9 +97,9 @@ void PresentationScaler::RecordBlitInto(VkCommandBuffer commands, VkImage image, pipelineBarrier(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); VkImageBlit blit{}; blit.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; - blit.srcOffsets[1] = {static_cast(sourceWidth), static_cast(sourceHeight), 1}; + blit.srcOffsets[1] = {static_cast(width), static_cast(height), 1}; blit.dstSubresource = blit.srcSubresource; - blit.dstOffsets[1] = blit.srcOffsets[1]; + blit.dstOffsets[1] = {static_cast(sourceWidth), static_cast(sourceHeight), 1}; context.Function("vkCmdBlitImage")(commands, image, layout, sourceImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, filter); barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 1e34962ef..83bd0b373 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "BdaAbi.hpp" #include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" @@ -20,6 +21,9 @@ #endif #include #include +#include +#include +#include #include #include #include @@ -58,7 +62,70 @@ void require(bool condition, const char* reason) { // go. APS5_NO_RESOURCE_CACHE=1 builds every dispatch's resources as before. using ResourceCache = Graphics::ResourceCache; -void WriteFrameBmp(int index, std::uint32_t fullWidth, std::uint32_t fullHeight, std::span full); +std::uint32_t DumpScale(); +void WriteFrameBmp(int index, std::uint32_t fullWidth, std::uint32_t fullHeight, std::span full, std::uint32_t scale); + +class FrameDumpWriter { +public: + ~FrameDumpWriter() { + { + std::lock_guard lock(mutex); + stopping = true; + } + changed.notify_one(); + if (worker.joinable()) worker.join(); + } + + void Enqueue(int index, std::uint32_t width, std::uint32_t height, std::uint32_t scale, std::vector pixels) { + std::lock_guard lock(mutex); + require(!stopping, "frame dump writer has stopped"); + require(pixels.size() <= maxBytes - queuedBytes, "frame dump queue exceeded 64 MiB"); + if (!worker.joinable()) worker = std::thread([this] { run(); }); + const auto bytes = pixels.size(); + pending.push_back({index, width, height, scale, std::move(pixels)}); + queuedBytes += bytes; + changed.notify_one(); + } + +private: + struct Frame { + int index; + std::uint32_t width; + std::uint32_t height; + std::uint32_t scale; + std::vector pixels; + }; + + void run() { + try { + for (;;) { + std::deque batch; + { + std::unique_lock lock(mutex); + changed.wait(lock, [this] { return stopping || !pending.empty(); }); + if (pending.empty() && stopping) return; + batch.swap(pending); + } + for (const auto& frame : batch) { + WriteFrameBmp(frame.index, frame.width, frame.height, frame.pixels, frame.scale); + std::lock_guard lock(mutex); + queuedBytes -= frame.pixels.size(); + } + } + } catch (const std::exception& error) { + std::fprintf(stderr, "[gpu] frame dump writer failed: %s\n", error.what()); + std::terminate(); + } + } + + static constexpr std::size_t maxBytes = 64 * 1024 * 1024; + std::mutex mutex; + std::condition_variable changed; + std::deque pending; + std::thread worker; + std::size_t queuedBytes = 0; + bool stopping = false; +}; // The [present] line's counters (see the header); mutated under State::presentMutex. VulkanDevice::PresentStatistics presentCounters{}; @@ -175,6 +242,10 @@ struct VulkanDevice::State { VkCommandBuffer commands = VK_NULL_HANDLE; std::shared_ptr kept; std::unique_ptr dumpBuffer; + std::unique_ptr dumpScaler; + std::uint32_t dumpWidth = 0; + std::uint32_t dumpHeight = 0; + std::uint32_t dumpScale = 1; bool dumpRecorded = false; int dumpIndex = 0; std::uint32_t imageIndex = 0; @@ -207,7 +278,7 @@ struct VulkanDevice::State { bool queuePending = false; std::uint32_t queueIndex = 0; int nextDumpIndex = 0; - std::thread dumpWriter; + FrameDumpWriter dumpWriter; template TFunction InstanceFunction(const char* name) const { @@ -358,17 +429,14 @@ struct VulkanDevice::State { } void WriteFrameDump(const PresentSlot& slot) { - require(slot.dumpBuffer != nullptr && scaler != nullptr, "frame dump was not recorded"); - // The readback buffer is reused by the slot's next dump, so its bytes are copied out before - // the writer thread (one at a time) downscales and writes them. + require(slot.dumpBuffer != nullptr, "frame dump was not recorded"); std::vector full(slot.dumpBuffer->Bytes().begin(), slot.dumpBuffer->Bytes().end()); - if (dumpWriter.joinable()) dumpWriter.join(); const auto index = slot.dumpIndex; - const auto width = scaler->SourceWidth(); - const auto height = scaler->SourceHeight(); + const auto width = slot.dumpWidth; + const auto height = slot.dumpHeight; static const auto start = std::chrono::steady_clock::now(); std::fprintf(stderr, "[gpu] frame_%03d.bmp: %ux%u display read back on the GPU at %.1f s\n", index, width, height, std::chrono::duration(std::chrono::steady_clock::now() - start).count()); - dumpWriter = std::thread([index, width, height, pixels = std::move(full)] { WriteFrameBmp(index, width, height, pixels); }); + dumpWriter.Enqueue(index, width, height, slot.dumpScale, std::move(full)); } // Blocks on the fence, or polls it every 50 us with APS5_PRESENT_POLL_FENCE=1 (the difference @@ -402,8 +470,10 @@ struct VulkanDevice::State { } if (recorder) SettleRetired(true); } - if (dumpWriter.joinable()) dumpWriter.join(); - for (auto& slot : presentSlots) slot.dumpBuffer.reset(); + for (auto& slot : presentSlots) { + slot.dumpBuffer.reset(); + slot.dumpScaler.reset(); + } // Results still in the unit shadows reach guest memory before the device goes; the // slabs (kept by the publishing batch) are freed by the recorder's teardown. if (recorder) { @@ -1542,15 +1612,19 @@ namespace { std::uint32_t DumpScale() { static const std::uint32_t scale = [] { const char* value = std::getenv("APS5_DUMP_SCALE"); - const int parsed = value ? std::atoi(value) : 4; - return static_cast(parsed > 0 ? parsed : 1); + if (value == nullptr) return std::uint32_t{4}; + std::uint32_t parsed = 0; + const auto end = value + std::strlen(value); + const auto result = std::from_chars(value, end, parsed); + require(result.ec == std::errc{} && result.ptr == end && parsed > 0 && parsed <= 16384, "APS5_DUMP_SCALE must be between 1 and 16384"); + return parsed; }(); return scale; } // frame_.bmp: every DumpScale()-th pixel of a BGRA8 image, 32-bit top-down. -void WriteFrameBmp(int index, std::uint32_t fullWidth, std::uint32_t fullHeight, std::span full) { - const auto scale = DumpScale(); +void WriteFrameBmp(int index, std::uint32_t fullWidth, std::uint32_t fullHeight, std::span full, std::uint32_t scale) { + require(scale != 0 && full.size() == static_cast(fullWidth) * fullHeight * 4, "invalid frame dump pixels"); const std::uint32_t width = (fullWidth + scale - 1) / scale; const std::uint32_t height = (fullHeight + scale - 1) / scale; std::vector pixels(static_cast(width) * height * 4); @@ -1559,16 +1633,19 @@ void WriteFrameBmp(int index, std::uint32_t fullWidth, std::uint32_t fullHeight, } char name[32]; std::snprintf(name, sizeof(name), "frame_%03d.bmp", index); - if (std::FILE* file = std::fopen(name, "wb")) { + { + std::ofstream file; + file.exceptions(std::ios::failbit | std::ios::badbit); + file.open(name, std::ios::binary); const std::uint32_t imageBytes = width * height * 4; const std::uint32_t header[13] = {0, 0, 54, 40, width, static_cast(-static_cast(height)), 1u | (32u << 16u), 0, imageBytes, 2835, 2835, 0, 0}; const std::uint16_t magic = 0x4d42; const std::uint32_t fileBytes = 54 + imageBytes; - std::fwrite(&magic, 2, 1, file); - std::fwrite(&fileBytes, 4, 1, file); - std::fwrite(header + 1, 4, 12, file); - std::fwrite(pixels.data(), 1, pixels.size(), file); - std::fclose(file); + file.write(reinterpret_cast(&magic), 2); + file.write(reinterpret_cast(&fileBytes), 4); + file.write(reinterpret_cast(header + 1), 48); + file.write(reinterpret_cast(pixels.data()), static_cast(pixels.size())); + file.close(); } } @@ -1668,7 +1745,7 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) { if (dumps.dumped < dumps.limit) { if (dumps.cpu) { const auto full = ReadDisplayBuffer(buffer); - WriteFrameBmp(dumps.dumped++, buffer.width, buffer.height, full); + WriteFrameBmp(dumps.dumped++, buffer.width, buffer.height, full, DumpScale()); } else { state->nextDumpIndex = dumps.dumped++; dumpFrame = true; @@ -1679,6 +1756,7 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) { lastReport = std::chrono::steady_clock::now(); std::fprintf(stderr, "[flip] %llu presents from the resident image (%llu refreshed first), through guest memory: %llu not pending, %llu unsuitable; %llu GPU frame dumps\n", static_cast(residentPresents), static_cast(refreshedPresents), static_cast(notPending), static_cast(unsuitable), static_cast(gpuDumps)); } + CaptureTrace::Log("present dump=%d address=%llx width=%u height=%u resident=%d generation=%llu", dumpFrame ? state->nextDumpIndex : -1, static_cast(buffer.address), buffer.width, buffer.height, resident != nullptr, static_cast(resident ? resident->Generation() : 0)); if (!present(buffer.width, buffer.height, true, {}, &buffer, resident, filter, dumpFrame)) { // A dropped frame (swapchain out of date) keeps the dump numbering contiguous. if (dumpFrame) --dumps.dumped; @@ -1739,9 +1817,7 @@ bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu require(index < state->images.size() && index < state->rendered.size(), "acquired image index is out of range"); auto& rendered = state->rendered[index]; const bool clearOnly = pixels.empty() && display == nullptr; - // A resident image is blitted straight to the swapchain; a frame dump goes through the - // scaler's BGRA8 image so the readback has one format. - const bool direct = resident != nullptr && !dumpFrame; + const bool direct = resident != nullptr; // The scaler's source image, the color transfer's staging and the upload buffer are single // objects an in-flight blit through them may still read: the paths using or re-creating them // wait for every slot first. The game path did so before taking the mutex @@ -1763,6 +1839,7 @@ bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu SubmitRecorded(false); std::chrono::steady_clock::time_point lastSubmittedAt{}; const std::uint64_t batchesAtBlit = state->recorder ? state->recorder->NewestSubmitted(&lastSubmittedAt) : 0; + CaptureTrace::Log("blit dump=%d batch=%llu slot=%zu image=%u direct=%d", dumpFrame ? state->nextDumpIndex : -1, static_cast(batchesAtBlit), state->presentCursor, index, direct); timing.Mark("pixel_upload"); APS5_LOG_OUT_DEBUG("present source=%s bytes=%zu", pixels.empty() ? "clear" : "pixels", pixels.size()); auto commands = slot.commands; @@ -1848,6 +1925,15 @@ bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu Graphics::Recorder::CountBarriers(CommandClass::PresentBlit); if (direct) PresentationScaler::RecordBlitFrom(graphicsContext(), commands, resident->Image(), VK_IMAGE_LAYOUT_GENERAL, width, height, residentFilter, barrier.image, state->extent.width, state->extent.height); else state->scaler->RecordBlit(commands, barrier.image, state->extent.width, state->extent.height); + if (dumpFrame && direct) { + const auto scale = DumpScale(); + slot.dumpWidth = (width + scale - 1) / scale; + slot.dumpHeight = (height + scale - 1) / scale; + slot.dumpScale = 1; + if (!slot.dumpScaler) slot.dumpScaler = std::make_unique(graphicsContext(), VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM); + slot.dumpScaler->EnsureSourceImage(slot.dumpWidth, slot.dumpHeight); + slot.dumpScaler->RecordBlitInto(commands, resident->Image(), VK_IMAGE_LAYOUT_GENERAL, VK_FILTER_NEAREST, width, height); + } if (resident != nullptr) { // Later batches write the image again (the next frame into this buffer, a refresh): they // start after the blit has read it. @@ -1857,9 +1943,14 @@ bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu Graphics::Recorder::CountBarriers(CommandClass::PresentBlit); } if (dumpFrame) { - const auto dumpBytes = static_cast(state->scaler->SourceWidth()) * state->scaler->SourceHeight() * 4; + if (!direct) { + slot.dumpWidth = state->scaler->SourceWidth(); + slot.dumpHeight = state->scaler->SourceHeight(); + slot.dumpScale = DumpScale(); + } + const auto dumpBytes = static_cast(slot.dumpWidth) * slot.dumpHeight * 4; if (!slot.dumpBuffer || slot.dumpBuffer->Bytes().size() != dumpBytes) slot.dumpBuffer = std::make_unique(graphicsContext(), dumpBytes, VK_BUFFER_USAGE_TRANSFER_DST_BIT); - state->scaler->RecordReadback(commands, slot.dumpBuffer->Handle()); + (direct ? slot.dumpScaler : state->scaler)->RecordReadback(commands, slot.dumpBuffer->Handle()); } } barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; @@ -1938,8 +2029,7 @@ VulkanDevice::PresentStatistics VulkanDevice::PresentCounts() { bool VulkanDevice::PresentWaitsForSlots(const DisplayBuffer* buffer) const { if (buffer == nullptr || !state->SharedSlotInFlight()) return false; if (buffer->tilingMode == 1) return true; - const auto& dumps = Dumps(); - if ((dumps.dumped < dumps.limit && !dumps.cpu) || NoResidentPresent()) return true; + if (NoResidentPresent()) return true; VkFilter filter = VK_FILTER_LINEAR; bool pending = false; if (PresentableResident(graphicsContext(), *buffer, filter, pending) == nullptr) return true; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp index 7e4d7a381..e48bdbbbc 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/WorkerSampler.cpp @@ -9,8 +9,11 @@ #include #include #include +#include #include #include +#include +#include #include #include #endif @@ -20,6 +23,9 @@ namespace AgcDriver { #ifdef _WIN32 namespace { +std::jthread processSamplerThread; +std::jthread workerSamplerThread; + // Debug aid: APS5_SAMPLE_WORKER= samples the calling thread every millisecond and, every 20 s, // writes "module+offset self inclusive" lines to for offline symbolization with nm. struct Sampler { @@ -29,6 +35,10 @@ struct Sampler { std::map> counts; std::uint64_t samples = 0; + ~Sampler() noexcept(false) { + if (target != nullptr && !CloseHandle(target)) throw std::system_error(GetLastError(), std::system_category(), "Closing sampler thread handle"); + } + void sample() { CONTEXT context{}; context.ContextFlags = CONTEXT_FULL; @@ -137,12 +147,12 @@ struct ProcessSampler { void StartProcessSampler() { const char* path = std::getenv("APS5_SAMPLE_THREADS"); if (path == nullptr) return; - std::thread([path = std::string(path)] { - auto* sampler = new ProcessSampler(); + processSamplerThread = std::jthread([path = std::string(path)](std::stop_token token) { + auto sampler = std::make_unique(); sampler->path = path; sampler->self = GetCurrentThreadId(); auto flushed = std::chrono::steady_clock::now(); - for (;;) { + while (!token.stop_requested()) { std::this_thread::sleep_for(std::chrono::milliseconds(2)); sampler->sample(); if (std::chrono::steady_clock::now() - flushed > std::chrono::seconds(20)) { @@ -150,7 +160,8 @@ void StartProcessSampler() { flushed = std::chrono::steady_clock::now(); } } - }).detach(); + sampler->write(); + }); } } @@ -159,12 +170,12 @@ void StartWorkerSampler() { StartProcessSampler(); const char* path = std::getenv("APS5_SAMPLE_WORKER"); if (path == nullptr) return; - auto* sampler = new Sampler(); + auto sampler = std::make_unique(); sampler->path = path; - DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &sampler->target, THREAD_ALL_ACCESS, FALSE, 0); - std::thread([sampler] { + if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &sampler->target, THREAD_ALL_ACCESS, FALSE, 0)) throw std::system_error(GetLastError(), std::system_category(), "Duplicating sampler thread handle"); + workerSamplerThread = std::jthread([sampler = std::move(sampler)](std::stop_token token) { auto flushed = std::chrono::steady_clock::now(); - for (;;) { + while (!token.stop_requested()) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); sampler->sample(); if (std::chrono::steady_clock::now() - flushed > std::chrono::seconds(20)) { @@ -172,10 +183,19 @@ void StartWorkerSampler() { flushed = std::chrono::steady_clock::now(); } } - }).detach(); + sampler->write(); + }); +} + +void StopWorkerSampler() { + processSamplerThread.request_stop(); + workerSamplerThread.request_stop(); + if (processSamplerThread.joinable()) processSamplerThread.join(); + if (workerSamplerThread.joinable()) workerSamplerThread.join(); } #else void StartWorkerSampler() {} +void StopWorkerSampler() {} #endif } diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index bfe9cc0e0..aca08e13e 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -11,6 +11,8 @@ namespace AgcDriver::Graphics { +void ShutdownGuestBufferWorkers(); + struct GuestMemorySnapshot { std::uint64_t address; std::span bytes; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp index 67b0511e0..2092a2b68 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp @@ -101,6 +101,20 @@ public: ShaderResources& operator=(const ShaderResources&) = delete; VkDescriptorSetLayout Layout() const; void Bind(VkCommandBuffer commands, VkPipelineBindPoint bindPoint, VkPipelineLayout layout) const; + struct DrawBindings { + DrawBindings() = default; + DrawBindings(const DrawBindings&) = delete; + DrawBindings& operator=(const DrawBindings&) = delete; + struct Snapshot { + std::uint64_t address; + std::shared_ptr buffer; + }; + DescriptorCache* cache = nullptr; + DescriptorCache::SetAllocation allocation; + std::vector snapshots; + ~DrawBindings(); + }; + std::shared_ptr PrepareDrawBindings(Recorder& recorder) const; void WriteBack(); // Deferred completion: MarkGpuWrites registers the results the recorded work leaves on the GPU // (storage images stay there; buffer ranges are noted so CPU reads wait); WriteBackBuffers runs diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp index b90d4969a..73e9dfa33 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Draw.cpp @@ -8,6 +8,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp" #include "prx/libc/include/General.hpp" #include "Optimization/include/Optimization/ShaderStageInputInfo.hpp" @@ -998,6 +999,83 @@ struct RecordedDraw { // attachments with nothing recorded between them share one pass; the host-write and host-read // barriers are implicit in the submission and the fence. Then the kept objects, the completion // work and, for a waited-for draw, the recorder sync. +bool CaptureInputsEnabled() { + static const bool enabled = std::getenv("APS5_CAPTURE_INPUTS") != nullptr; + Require(!enabled || CaptureTrace::Enabled(), "APS5_CAPTURE_INPUTS requires APS5_CAPTURE_TRACE"); + return enabled; +} + +void captureInputs(const Context& context, Recorder& recorder, VkCommandBuffer commands, const ShaderResources& resources, const std::shared_ptr& bindings, std::uint64_t target) { + struct Sample { + std::uint64_t address; + std::size_t offset; + std::vector expected; + VkBuffer source; + VkDeviceSize sourceOffset; + }; + static unsigned long long nextDraw = 0; + const auto draw = ++nextDraw; + const auto batch = static_cast(recorder.Submissions() + 1); + std::vector samples; + std::size_t total = 0; + const auto addSample = [&](std::uint64_t address, std::size_t bytes, VkBuffer source, VkDeviceSize offset, const std::byte* expected) { + if (bytes == 0 || bytes > 512) return; + Require(offset % 4 == 0 && bytes % 4 == 0, "capture input is not aligned for a Vulkan buffer copy"); + Require(total + bytes <= 65536, "capture inputs exceed 64 KiB per draw"); + Sample sample{address, total, std::vector(bytes), source, offset}; + std::memcpy(sample.expected.data(), expected, bytes); + total += bytes; + samples.push_back(std::move(sample)); + }; + if (bindings != nullptr) { + for (const auto& snapshot : bindings->snapshots) { + const auto bytes = snapshot.buffer->Bytes(); + addSample(snapshot.address, bytes.size(), snapshot.buffer->Handle(), 0, bytes.data()); + } + } + for (const auto& [begin, end] : resources.InPlaceReads()) { + Require(end >= begin, "invalid capture input range"); + const auto bytes = static_cast(end - begin); + if (bytes == 0 || bytes > 512) continue; + if (bindings != nullptr && std::any_of(bindings->snapshots.begin(), bindings->snapshots.end(), [&](const auto& snapshot) { return snapshot.address < end && begin < snapshot.address + snapshot.buffer->Bytes().size(); })) continue; + if (resources.WritesOverlap(begin, bytes) || recorder.PendingWriteOverlaps(begin, bytes)) { + CaptureTrace::Log("input-skip draw=%llu batch=%llu address=%llx bytes=%zu reason=gpu-writer", draw, batch, static_cast(begin), bytes); + continue; + } + const auto* imported = HostImportFor(context, begin, bytes); + Require(imported != nullptr, "capture input has no host import"); + addSample(begin, bytes, imported->buffer, begin - imported->base, reinterpret_cast(begin)); + } + CaptureTrace::Log("input-capture draw=%llu batch=%llu target=%llx ranges=%zu bytes=%zu", draw, batch, static_cast(target), samples.size(), total); + if (samples.empty()) return; + auto readback = std::make_shared(context, total, VK_BUFFER_USAGE_TRANSFER_DST_BIT); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + const auto copy = context.Function("vkCmdCopyBuffer"); + for (const auto& sample : samples) { + const VkBufferCopy region{sample.sourceOffset, sample.offset, sample.expected.size()}; + copy(commands, sample.source, readback->Handle(), 1, ®ion); + } + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_HOST_READ_BIT | VK_ACCESS_SHADER_READ_BIT); + recorder.OnComplete([draw, batch, target, readback, samples = std::move(samples)] { + const auto actual = readback->Bytes(); + std::size_t differences = 0; + for (const auto& sample : samples) { + std::size_t changed = 0; + for (std::size_t index = 0; index < sample.expected.size(); ++index) { + const auto gpu = actual[sample.offset + index]; + if (sample.expected[index] == gpu) continue; + if (changed < 16) CaptureTrace::Log("input-byte draw=%llu batch=%llu address=%llx offset=%zu cpu=%02x gpu=%02x", draw, batch, static_cast(sample.address), index, std::to_integer(sample.expected[index]), std::to_integer(gpu)); + ++changed; + } + if (changed != 0) { + ++differences; + CaptureTrace::Log("input-mismatch draw=%llu batch=%llu target=%llx address=%llx bytes=%zu changed=%zu", draw, batch, static_cast(target), static_cast(sample.address), sample.expected.size(), changed); + } + } + CaptureTrace::Log("input-result draw=%llu batch=%llu ranges=%zu mismatched=%zu", draw, batch, samples.size(), differences); + }); +} + void recordDraw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span shaders, DrawInputs& inputs, RecordedDraw& record, DrawOutcome& outcome, DrawTimer& timer, double& ownWaitedMs) { auto* recorder = record.recorder; auto& resources = *record.resources; @@ -1030,10 +1108,13 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam if (recorder->HasQueuedKeyStores() && (resources.HoldsLease() || recorder->AnyQueuedKeyStore(touches))) recorder->FlushKeyStores(); // A queued label store over such memory likewise (Recorder::RecordStore). if (recorder->HasQueuedStores() && (resources.HoldsLease() || recorder->AnyQueuedStore(touches))) recorder->FlushStores(); - const bool continued = !readsTarget && !gpuIndirect && recorder->ContinuesRenderPass(passKey); + const auto drawBindings = resources.PrepareDrawBindings(*recorder); + const bool capture = CaptureInputsEnabled(); + const bool continued = !capture && !readsTarget && !gpuIndirect && recorder->ContinuesRenderPass(passKey); outcome.passContinued = continued; outcome.passBegun = !continued; const auto commands = continued ? recorder->CommandsInRenderPass() : recorder->Commands(); + if (capture) captureInputs(context, *recorder, commands, resources, drawBindings, record.targets.empty() || record.targets.front() == nullptr ? 0 : record.targets.front()->Descriptor().baseAddress); // The draw's [gputime] class range: from its first barrier to the pass's trailing barrier (a // continued draw lies inside its pass's range). const auto drawTiming = !continued ? recorder->BeginGpuTiming(CommandClass::Draw) : Recorder::NoTiming; @@ -1065,7 +1146,12 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam record.pipeline->Begin(commands, *record.framebuffer, state.renderExtent, state.viewport, state.scissor); } APS5_LOG_CHARS_OUT_DEBUG("Pipeline Begin OK"); - resources.Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout()); + if (drawBindings != nullptr) { + const auto set = drawBindings->allocation.set; + context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout(), 0, 1, &set, 0, nullptr); + } else { + resources.Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout()); + } APS5_LOG_CHARS_OUT_DEBUG("Resources bound"); if (record.pushBytes != nullptr) record.pipeline->PushConstants(commands, record.pushStages, *record.pushBytes); else record.pipeline->PushConstants(commands, shaders); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index a14325c52..7213c9085 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -4,6 +4,7 @@ #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/UnitShadow.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libc/include/CpuTopology.hpp" #ifdef _WIN32 #include @@ -16,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -381,19 +383,15 @@ bool compareBlock(const RefreshBlock& block) { return true; } -// Helper threads for the block compares. An address-based build refreshes every writable image range -// (~15 MiB in ~300 blocks) under the device lock, and that memcmp was most of its cost; the blocks -// are handed out by an atomic cursor to the helpers and the calling thread alike, and Run returns -// once every block was taken and finished. The threads are started on first use and never joined -// (they only wait on the condition variable). APS5_MIRROR_REFRESH_THREADS sets the helper count -// (default 3); APS5_NO_PARALLEL_MIRROR_REFRESH=1 compares on the calling thread only. class RefreshPool { public: static RefreshPool& Get() { - // Never destroyed (as WorkerSampler's): the helpers block on its condition variable, which a - // static destructor at std::exit would tear down under them. - static RefreshPool* pool = new RefreshPool; - return *pool; + if (instance == nullptr) instance = new RefreshPool; + return *instance; + } + + static void Shutdown() { + delete std::exchange(instance, nullptr); } // Compares `blocks` and returns how many were copied. @@ -428,19 +426,12 @@ private: const char* text = std::getenv("APS5_MIRROR_REFRESH_THREADS"); const auto requested = text != nullptr ? std::atoi(text) : 3; const auto wanted = disabled ? 0u : static_cast(std::clamp(requested, 0, 16)); - // Only started threads are counted: Run waits for `helpers` finishes, and a thread that could - // not be started would leave it waiting forever. for (unsigned i = 0; i < wanted; ++i) { - try { - std::thread([this] { - CpuTopology::PinHelperThread("mirror refresh"); - helper(); - }).detach(); - ++helpers; - } catch (const std::system_error& error) { - std::fprintf(stderr, "[gpu] mirror refresh helper %u not started: %s\n", i, error.what()); - break; - } + threads.emplace_back([this](std::stop_token token) { + CpuTopology::PinHelperThread("mirror refresh"); + helper(token); + }); + ++helpers; } } @@ -451,13 +442,13 @@ private: copiedBlocks.fetch_add(copied, std::memory_order_relaxed); } - void helper() { + void helper(std::stop_token token) { std::uint64_t seen = 0; for (;;) { std::span blocks; { std::unique_lock lock(mutex); - wake.wait(lock, [&] { return serial != seen; }); + if (!wake.wait(lock, token, [&] { return serial != seen; })) return; seen = serial; blocks = job; } @@ -470,16 +461,18 @@ private: } } + inline static RefreshPool* instance = nullptr; unsigned helpers = 0; std::mutex runMutex; std::mutex mutex; - std::condition_variable wake; + std::condition_variable_any wake; std::condition_variable done; std::span job; std::uint64_t serial = 0; unsigned finished = 0; std::atomic next{0}; std::atomic copiedBlocks{0}; + std::vector threads; }; // The device-lock work before [address, address + bytes) of a writable mirror can be compared with @@ -1356,6 +1349,10 @@ void reportStaging() { } +void ShutdownGuestBufferWorkers() { + RefreshPool::Shutdown(); +} + GuestBufferMemory::~GuestBufferMemory() { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; if (!profile) return; @@ -1767,6 +1764,8 @@ void GuestBufferMemory::copyRegion(Region& region, bool addressable) { void GuestBufferMemory::recordGpuCopies(std::span copies, bool addressable) { static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr; + static const bool capture = std::getenv("APS5_CAPTURE_GPU_COPIES") != nullptr; + Require(!capture || CaptureTrace::Enabled(), "GPU copy capture requires APS5_CAPTURE_TRACE"); auto* recorder = Recorder::Active(); Require(recorder != nullptr, "GPU buffer copies need an active recorder"); // A queued DCC key store or label store over a copied range lands before the copy reads it. @@ -1781,9 +1780,6 @@ void GuestBufferMemory::recordGpuCopies(std::span copies, bool ad for (const auto* region : copies) reads.emplace_back(region->begin, region->end); recorder->NoteAccess(Recorder::CommandClass::StagingIn, Recorder::Access{reads, {}, {}, VK_PIPELINE_STAGE_TRANSFER_BIT}); } - // Every earlier recorded store into the imports (dispatches in place, fills, GPU labels, - // GPU-direct write-backs and the flushes just recorded) and the host's own writes precede the - // copies. A CPU write made after the batch is submitted is the game's race, as on hardware. 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_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); Recorder::CountBarriers(Recorder::CommandClass::StagingIn); bool stagedAny = false; @@ -1803,10 +1799,47 @@ void GuestBufferMemory::recordGpuCopies(std::span copies, bool ad } if (profile) allocateUs.fetch_add(microsecondsSince(allocateStart), std::memory_order_relaxed); } - CopyBuffer(context, commands, region->copySource, region->begin - region->copySourceBase, region->buffer->Handle(), 0, bytes); - // The copy reads the import in place when the batch runs: a CPU store into the range before - // then would be copied instead of the bytes recorded here. - recorder->NotePendingRead(region->begin, static_cast(bytes), Recorder::ReadKind::GpuCopy); + std::vector expected; + const auto address = region->begin; + const auto batch = static_cast(recorder->Submissions() + 1); + const bool pendingWriter = recorder->PendingWriteOverlaps(address, static_cast(bytes)); + const bool writable = WritesOverlap(address, static_cast(bytes)); + std::shared_ptr inputSnapshot; + auto copySource = region->copySource; + auto copyOffset = region->begin - region->copySourceBase; + if (!pendingWriter && !writable) { + inputSnapshot = std::make_shared(context, static_cast(bytes), VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + std::memcpy(inputSnapshot->Bytes().data(), reinterpret_cast(address), static_cast(bytes)); + copySource = inputSnapshot->Handle(); + copyOffset = 0; + recorder->Keep(inputSnapshot); + CaptureTrace::Log("copy-input-snapshot batch=%llu address=%llx bytes=%llu", batch, static_cast(address), static_cast(bytes)); + } + if (capture && bytes == 32) { + CaptureTrace::Log("copy-input-candidate batch=%llu address=%llx bytes=%llu pendingWriter=%d writable=%d", batch, static_cast(address), static_cast(bytes), pendingWriter, writable); + if (inputSnapshot != nullptr) { + const auto snapshotBytes = inputSnapshot->Bytes(); + expected.assign(snapshotBytes.begin(), snapshotBytes.end()); + } + } + CopyBuffer(context, commands, copySource, copyOffset, region->buffer->Handle(), 0, bytes); + if (!expected.empty()) { + auto readback = std::make_shared(context, expected.size(), VK_BUFFER_USAGE_TRANSFER_DST_BIT); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + CopyBuffer(context, commands, region->buffer->Handle(), 0, readback->Handle(), 0, bytes); + RecordMemoryBarrier(context, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_HOST_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); + recorder->OnComplete([readback, expected = std::move(expected), address, batch] { + const auto actual = readback->Bytes(); + std::size_t changed = 0; + for (std::size_t index = 0; index < expected.size(); ++index) { + if (expected[index] == actual[index]) continue; + ++changed; + CaptureTrace::Log("copy-input-byte batch=%llu address=%llx offset=%zu cpu=%02x gpu=%02x", batch, static_cast(address), index, std::to_integer(expected[index]), std::to_integer(actual[index])); + } + CaptureTrace::Log("copy-input-result batch=%llu address=%llx bytes=%zu changed=%zu", batch, static_cast(address), expected.size(), changed); + }); + } + if (inputSnapshot == nullptr) recorder->NotePendingRead(region->begin, static_cast(bytes), Recorder::ReadKind::GpuCopy); // Kept by the batch at record time, as every other recorded target is: the caller keeps // the resources only after descriptor and pipeline work that may throw, and a released // buffer would go back to the pool (reused or destroyed) under this copy command. The diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp index d9184240f..f796220fd 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libc/include/HostThreadLocal.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" @@ -1894,6 +1895,7 @@ void Recorder::OnComplete(std::function action) { } bool Recorder::noteWrite(std::uint64_t address, std::size_t bytes, bool ownLabel) { + CaptureTrace::Log("buffer-write batch=%llu address=%llx bytes=%zu label=%d", static_cast(submissions + 1), static_cast(address), bytes, ownLabel); if (bytes == 0) return false; ensureOpen(); const auto end = address + bytes; @@ -2015,6 +2017,7 @@ bool Recorder::ReadTracking() { } void Recorder::NotePendingRead(std::uint64_t address, std::size_t bytes, ReadKind kind) { + CaptureTrace::Log("buffer-read batch=%llu address=%llx bytes=%zu kind=%d", static_cast(submissions + 1), static_cast(address), bytes, static_cast(kind)); if (bytes == 0 || !ReadTrackingEnabled()) return; ensureOpen(); open->reads.push_back({address, address + bytes, kind}); @@ -2025,6 +2028,7 @@ void Recorder::NotePendingReads(std::span(submissions + 1), static_cast(begin), static_cast(end - begin), static_cast(kind)); if (end > begin) open->reads.push_back({begin, end, kind}); } readsNoted.fetch_add(ranges.size(), std::memory_order_relaxed); @@ -2345,6 +2349,7 @@ void Recorder::Submit() { submission.pCommandBuffers = &batch->commands; // The timeline reaches this batch's serial when it completes (see WaitSerial). const std::uint64_t serial = submissions + 1; + CaptureTrace::Log("submit batch=%llu reads=%zu writes=%zu", static_cast(serial), batch->reads.size(), batch->writes.size()); VkTimelineSemaphoreSubmitInfoKHR timelineInfo{VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR}; timelineInfo.signalSemaphoreValueCount = 1; timelineInfo.pSignalSemaphoreValues = &serial; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp index f55e3ebf6..d84bf94de 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp" #include "prx/libSceAgcDriver/Graphics/include/Recorder.hpp" #include @@ -260,6 +261,7 @@ std::shared_ptr sampledStorageSource(const Context& context, con // `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& resource, VkComponentMapping components, std::uint64_t guestBytes = 0) { + CaptureTrace::Log("sampled-lookup address=%llx width=%u height=%u dcc=%llx", static_cast(resource.baseAddress), resource.width, resource.height, static_cast(resource.dccAddress)); static const bool disabled = std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; const bool profile = LookupOutcomes::Profiled(); const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; @@ -2460,6 +2462,70 @@ VkDescriptorSetLayout ShaderResources::Layout() const { return _layout; } +ShaderResources::DrawBindings::~DrawBindings() { + if (cache != nullptr && allocation.set != VK_NULL_HANDLE) cache->Free(allocation); +} + +std::shared_ptr ShaderResources::PrepareDrawBindings(Recorder& recorder) const { + if (_set == VK_NULL_HANDLE || usesBda) return {}; + const auto reads = guestMemory.InPlaceReads(); + auto result = std::make_shared(); + std::vector selected; + for (std::size_t index = 0; index < allocations.size(); ++index) { + const auto& item = allocations[index]; + if (!item.guest || item.written || guestMemory.WritesOverlap(item.address, item.size)) continue; + const bool direct = std::any_of(reads.begin(), reads.end(), [&](const auto& range) { return item.address >= range.first && item.address < range.second && item.size <= range.second - item.address; }); + if (!direct || recorder.PendingWriteOverlaps(item.address, item.size)) continue; + auto buffer = std::make_shared(context, item.size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT); + std::memcpy(buffer->Bytes().data(), reinterpret_cast(item.address), item.size); + selected.push_back(index); + result->snapshots.push_back({item.address, std::move(buffer)}); + CaptureTrace::Log("draw-snapshot batch=%llu address=%llx bytes=%zu", static_cast(recorder.Submissions() + 1), static_cast(item.address), item.size); + } + if (selected.empty()) return {}; + Require(context.descriptorCache != nullptr, "draw snapshots require a descriptor cache"); + std::map counts; + for (const auto& binding : bindings) counts[binding.layout.descriptorType] += binding.layout.descriptorCount; + std::vector sizes; + for (const auto& [type, count] : counts) sizes.push_back({type, count}); + result->cache = context.descriptorCache; + result->allocation = result->cache->Allocate(_layout, sizes); + Require(result->allocation.set != VK_NULL_HANDLE, "draw snapshot descriptor allocation failed"); + std::vector copies; + for (const auto& binding : bindings) { + VkCopyDescriptorSet copy{VK_STRUCTURE_TYPE_COPY_DESCRIPTOR_SET}; + copy.srcSet = _set; + copy.srcBinding = binding.layout.binding; + copy.dstSet = result->allocation.set; + copy.dstBinding = binding.layout.binding; + copy.descriptorCount = binding.layout.descriptorCount; + copies.push_back(copy); + } + const auto update = context.Resolved(&DeviceFunctions::updateDescriptorSets, "vkUpdateDescriptorSets"); + update(context.device, 0, nullptr, static_cast(copies.size()), copies.data()); + std::vector infos; + infos.reserve(selected.size()); + for (const auto& snapshot : result->snapshots) infos.push_back({snapshot.buffer->Handle(), 0, snapshot.buffer->Bytes().size()}); + std::vector writes; + for (const auto& binding : bindings) { + for (std::size_t element = 0; element < binding.allocations.size(); ++element) { + const auto found = std::find(selected.begin(), selected.end(), binding.allocations[element]); + if (found == selected.end()) continue; + VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + write.dstSet = result->allocation.set; + write.dstBinding = binding.layout.binding; + write.dstArrayElement = static_cast(element); + write.descriptorCount = 1; + write.descriptorType = binding.layout.descriptorType; + write.pBufferInfo = &infos[static_cast(found - selected.begin())]; + writes.push_back(write); + } + } + update(context.device, static_cast(writes.size()), writes.data(), 0, nullptr); + recorder.Keep(result); + return result; +} + void ShaderResources::Bind(VkCommandBuffer commands, VkPipelineBindPoint bindPoint, VkPipelineLayout layout) const { if (_set == VK_NULL_HANDLE) return; context.Resolved(&DeviceFunctions::cmdBindDescriptorSets, "vkCmdBindDescriptorSets")(commands, bindPoint, layout, 0, 1, &_set, 0, nullptr); diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp index da0d220ee..74f55c1a8 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp @@ -1,3 +1,4 @@ +#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp" #include "prx/libSceAgcDriver/Graphics/include/Texture.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceAgcDriver/Graphics/include/Resources.hpp" @@ -1062,6 +1063,7 @@ bool StorageTexture::Refresh() { if (profile) LookupOutcomes::Add(stamped ? LookupOutcomes::RefreshUnchanged : LookupOutcomes::RefreshCompared, start); return true; } + CaptureTrace::Log("refresh image=%llx bytes=%llu generation=%llu dirty=%d keysChanged=%d cpuWrote=%d", static_cast(descriptor.baseAddress), static_cast(guestBytes), static_cast(generation), dirty, keysChanged, cpuWrote); // Debug aid: APS5_TRACE_UPLOAD names why a storage image is uploaded again. static const bool traceUpload = std::getenv("APS5_TRACE_UPLOAD") != nullptr; if (traceUpload) { @@ -1115,6 +1117,7 @@ bool StorageTexture::Refresh() { if (changed[layer]) { pendingResults = true; if (blockUnits && droppable(layer)) { + CaptureTrace::Log("drop-unit image=%llx unit=%u generation=%llu stamped=%d keys=%d", static_cast(descriptor.baseAddress), layer, static_cast(layerGeneration[layer]), layer < stampedBlocks.size() && stampedBlocks[layer] != 0, static_cast(keys)); layerPending[layer] = false; dropped = true; continue; @@ -1167,6 +1170,7 @@ void traceKeyStore(const char* path, const GuestTextureResource& descriptor, std } void StorageTexture::upload(const std::vector* layers) { + CaptureTrace::Log("upload image=%llx bytes=%llu generation=%llu reason=%s partial=%d", static_cast(descriptor.baseAddress), static_cast(guestBytes), static_cast(generation), uploadReason, layers != nullptr); const bool profile = LookupOutcomes::Profiled(); const auto start = profile ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{}; const auto elementBytes = BytesPerElement(descriptor.format); @@ -1812,6 +1816,7 @@ void StorageTexture::refreshGeneration() { } 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)); static const bool eager = std::getenv("APS5_EAGER_WRITEBACK") != nullptr || std::getenv("APS5_NO_TEXTURE_CACHE") != nullptr; for (std::uint32_t layer = first; layer < first + count; ++layer) layerPending[layer] = true; // Results of this image now cover the alias's results borrowed into these units: this image @@ -2818,6 +2823,7 @@ void StorageTexture::writeBack(std::uint64_t address, std::size_t bytes) { } 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; PhaseTimer timer; struct Account { diff --git a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp index b2030621f..9beb24074 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Flip.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Flip.cpp @@ -41,6 +41,21 @@ struct State { AgcDriver::FlipInfo last{}; }; +class FlipRelease final { +public: + explicit FlipRelease(std::shared_ptr state) : state(std::move(state)) {} + ~FlipRelease() { Release(); } + void Release() { + { + std::lock_guard lock(state->mutex); + state->block = false; + } + state->changed.notify_all(); + } +private: + std::shared_ptr state; +}; + class Request final : public AgcDriver::IFlipRequest { public: explicit Request(std::shared_ptr value) : state(std::move(value)) { ++state->alive; } @@ -104,24 +119,23 @@ void testFlipAndBoundary() { output->state->block = true; output->state->checkSelfWait = true; } + std::future boundary; + FlipRelease release(output->state); submitFlip(); { std::unique_lock lock(output->state->mutex); check(output->state->changed.wait_for(lock, std::chrono::seconds(5), [&] { return output->state->entered; }), "worker did not reach flip"); check(output->state->last.argument == -0x123456789abcdefLL && output->state->last.index == -2, "decoded flip arguments changed"); } - auto boundary = std::async(std::launch::async, [] { AgcDriverSuspendPoint_nid_postfix(); }); - check(boundary.wait_for(std::chrono::milliseconds(30)) == std::future_status::timeout, "suspend completed before preceding work"); + boundary = std::async(std::launch::async, [] { AgcDriverSuspendPoint_nid_postfix(); }); + check(boundary.wait_for(std::chrono::seconds(5)) == std::future_status::ready, "suspend blocked on preceding work"); + boundary.get(); + check(output->state->ready == 0, "blocked flip completed before release"); AgcDriverUnregisterVideoOutput_nid_postfix(7, output); auto replacement = std::make_shared(); AgcDriverRegisterVideoOutput_nid_postfix(7, replacement); submitFlip(); - { - std::lock_guard lock(output->state->mutex); - output->state->block = false; - } - output->state->changed.notify_all(); - boundary.get(); + release.Release(); AgcDriverWaitIdle_nid_postfix(); check(output->state->ready == 1 && replacement->state->ready == 1, "registration lifetime or FIFO was lost"); check(output->state->failed == 0, "successful request failed"); @@ -140,6 +154,7 @@ void testFlipAndBoundary() { for (auto& producer : producers) producer.join(); for (auto error : errors) if (error) std::rethrow_exception(error); AgcDriverSuspendPoint_nid_postfix(); + AgcDriverWaitIdle_nid_postfix(); check(replacement->state->ready == 201, "concurrent submissions were lost"); AgcDriverUnregisterVideoOutput_nid_postfix(7, replacement); } @@ -151,10 +166,11 @@ void testFailure() { submitFlip(); std::array messages; std::vector waiters; - for (auto& message : messages) waiters.emplace_back([&message] { message = expectFailure([] { AgcDriverSuspendPoint_nid_postfix(); }); }); + for (auto& message : messages) waiters.emplace_back([&message] { message = expectFailure([] { AgcDriverWaitIdle_nid_postfix(); }); }); for (auto& waiter : waiters) waiter.join(); for (auto& message : messages) check(message == "intentional flip failure", "asynchronous failure was lost"); check(expectFailure([] { AgcDriverWaitIdle_nid_postfix(); }) == messages[0], "idle lost flip failure"); + check(expectFailure([] { AgcDriverSuspendPoint_nid_postfix(); }) == messages[0], "suspend lost flip failure"); check(expectFailure([] { submitFlip(); }) == messages[0], "submit lost flip failure"); check(output->state->ready == 0 && output->state->failed == 1, "failed flip was completed successfully"); AgcDriverUnregisterVideoOutput_nid_postfix(7, output); diff --git a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp index e18b7baf1..34198c47b 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Pm4.cpp @@ -1,6 +1,5 @@ #include "prx/libSceAgcDriver/Execution/include/Pm4.hpp" #include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" -#include "prx/libSceAgcDriver/Execution/include/MemoryAccessScope.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libc/include/Shutdown.hpp" @@ -308,13 +307,16 @@ void testMemorySynchronization() { std::uint32_t destination = 0; bool read = false; bool written = false; - } memory; + }; + static MemoryState memory; + memory = {}; AgcDriver::QueueState state; - const auto resolve = [](void* context, std::uint64_t address, std::size_t bytes, bool writable) { - auto& memory = *static_cast(context); + struct FlushHookReset { + ~FlushHookReset() { AgcDriver::GuestMemory::SetFlushHook(nullptr); } + } reset; + const auto resolve = [](std::uint64_t address, std::size_t bytes) { check(bytes == sizeof(std::uint32_t), "memory transfer resolved an unrelated range"); - if (writable) { - check(address == reinterpret_cast(&memory.destination), "memory transfer resolved an unrelated destination"); + if (address == reinterpret_cast(&memory.destination)) { memory.written = true; } else { check(address == reinterpret_cast(&memory.source), "memory transfer resolved an unrelated source"); @@ -322,7 +324,7 @@ void testMemorySynchronization() { memory.read = true; } }; - const AgcDriver::GuestMemory::MemoryAccessScope scope(&memory, resolve); + AgcDriver::GuestMemory::SetFlushHook(resolve); execute(state, makePacket(0x37, {0x100, low(&memory.destination), high(&memory.destination), 17})); check(memory.written && !memory.read && memory.destination == 17, "WRITE_DATA did not synchronize its destination"); for (const auto opcode : {0x40u, 0x50u}) { @@ -333,7 +335,7 @@ void testMemorySynchronization() { execute(state, packet); check(memory.read && memory.written && memory.destination == 42, "memory copy used stale data before range synchronization"); } - const AgcDriver::GuestMemory::MemoryAccessScope rejecting(&memory, [](void*, std::uint64_t, std::size_t, bool) { + AgcDriver::GuestMemory::SetFlushHook([](std::uint64_t, std::size_t) { throw std::runtime_error("range synchronization failed"); }); expectFailure([&] { execute(state, makePacket(0x37, {0x100, low(&memory.destination), high(&memory.destination), 99})); }, "range synchronization failed"); diff --git a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp index 881487a71..98eb4a645 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Recorder.cpp @@ -653,6 +653,44 @@ void resourceReadTests(const Device& device, Recorder& recorder) { Require(!recorder.PendingReadOverlaps(element, 16), "the build's read refuses after its batch ran"); recorder.Sync(); } + std::weak_ptr snapshotLifetime; + { + auto snapshotContext = context; + DescriptorCache cache(snapshotContext); + snapshotContext.descriptorCache = &cache; + Recorder snapshotRecorder(snapshotContext); + snapshotRecorder.Activate(); + ShaderResources resources(snapshotContext, compute); + auto first = resources.PrepareDrawBindings(snapshotRecorder); + Require(first != nullptr && first->snapshots.size() == 1, "read-only draw input was not snapshotted"); + std::memset(reinterpret_cast(element), 0x22, elementBytes); + auto second = resources.PrepareDrawBindings(snapshotRecorder); + Require(second != nullptr && second->snapshots.size() == 1, "cached draw input was not snapshotted again"); + Require(first->allocation.set != second->allocation.set, "in-flight draws share mutable descriptor bindings"); + std::memset(reinterpret_cast(element), 0x33, elementBytes); + auto downloaded = std::make_shared(snapshotContext, elementBytes * 2, VK_BUFFER_USAGE_TRANSFER_DST_BIT); + const auto commands = snapshotRecorder.Commands(); + RecordMemoryBarrier(snapshotContext, commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + const auto copy = snapshotContext.Function("vkCmdCopyBuffer"); + VkBufferCopy region{0, 0, elementBytes}; + copy(commands, first->snapshots[0].buffer->Handle(), downloaded->Handle(), 1, ®ion); + region.dstOffset = elementBytes; + copy(commands, second->snapshots[0].buffer->Handle(), downloaded->Handle(), 1, ®ion); + RecordMemoryBarrier(snapshotContext, commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT); + snapshotLifetime = first; + first.reset(); + second.reset(); + Require(!snapshotLifetime.expired(), "draw snapshot was released before its GPU batch"); + snapshotRecorder.Sync(); + const auto contents = downloaded->Bytes(); + Require(std::all_of(contents.begin(), contents.begin() + elementBytes, [](std::byte value) { return value == std::byte{0x11}; }), "first draw observed overwritten input"); + Require(std::all_of(contents.begin() + elementBytes, contents.end(), [](std::byte value) { return value == std::byte{0x22}; }), "second draw observed overwritten input"); + snapshotRecorder.NotePendingWrite(element, elementBytes); + Require(resources.PrepareDrawBindings(snapshotRecorder) == nullptr, "GPU-produced input was replaced with stale CPU memory"); + snapshotRecorder.Sync(); + } + Require(snapshotLifetime.expired(), "draw snapshots outlived their recorder"); + recorder.Activate(); { GuestAllocations::Mutation mutation; mutation.Remove(block); diff --git a/core/libs/prx/libSceAvPlayer.native/Export.cpp b/core/libs/prx/libSceAvPlayer.native/Export.cpp index 2c2e5bb90..bc3573e05 100644 --- a/core/libs/prx/libSceAvPlayer.native/Export.cpp +++ b/core/libs/prx/libSceAvPlayer.native/Export.cpp @@ -1,194 +1,631 @@ +// libSceAvPlayer: player lifecycle without media decoding. A source is accepted, the event callback sees the usual +// READY / PLAY transitions, and the video getters hand out frames of a fixed-length blank clip on a wall-clock +// media clock before the stream reports as finished (STOP). The frame budget matters: a title whose intro sequence +// is driven by the frame getters stops asking only once it has been given frames and then an end-of-stream, so +// answering every poll with "no frame" strands it in its own wait loop. No frame is ever decoded, so the picture +// is black. +#include +#include #include +#include #include +#include +#include +#include +#include +#include +#include + #include "SceTypes.hpp" +#include "HitLog.hpp" #include "prx/libc/include/General.hpp" +namespace { +constexpr std::int32_t kEventStop = 0x01; +constexpr std::int32_t kEventReady = 0x02; +constexpr std::int32_t kEventPlay = 0x03; +constexpr std::int32_t kEventPause = 0x04; +constexpr std::int32_t kEventWarning = 0x20; +constexpr std::int32_t kErrInvalidParams = static_cast(0x806a0001u); +constexpr std::int32_t kErrOperationFailed = static_cast(0x806a0002u); +constexpr std::uint32_t kWarningJumpComplete = 0x806a00a3u; +constexpr std::uint64_t kMagic = 0x5041564c50594c41ull; + +// The blank clip this player "plays". Both the stream info and every delivered frame are described by these +// values, so the title's own texture sizing and the surface it is handed cannot disagree. +constexpr std::uint32_t kFrameWidth = 1920; +constexpr std::uint32_t kFrameHeight = 1080; +// NV12 rows are pitched to 256 bytes; SceAvPlayerVideoEx::width publishes that coded extent, not the visible +// one, with the difference reported through cropRightOffset. +constexpr std::uint32_t kFramePitch = (kFrameWidth + 255u) & ~255u; +constexpr std::size_t kFrameBytes = static_cast(kFramePitch) * kFrameHeight * 3u / 2u; +// Limited-range black, not zero: Y=U=V=0 converts to mid green in both BT.601 and BT.709, and a title that +// samples the coded extent would read the pitch padding as a green stripe down the right edge. +constexpr std::uint8_t kBlackLuma = 0x10; +constexpr std::uint8_t kBlackChroma = 0x80; +constexpr std::uint64_t kFrameIntervalMs = 33; +constexpr std::uint64_t kFrameBudget = 120; +constexpr std::uint64_t kStreamDurationMs = kFrameBudget * kFrameIntervalMs; +constexpr double kFrameRate = 1000.0 / static_cast(kFrameIntervalMs); + +using EventCallback = void (APS5_VABI *)(void*, std::int32_t, std::int32_t, void*); +using TextureAllocator = void* (APS5_VABI *)(void*, std::uint32_t, std::uint32_t); +using TextureFree = void (APS5_VABI *)(void*, void*); + +// SceAvPlayerVideoEx. The offsets are asserted below because the guest reads them by position, not by name. +struct VideoDetailsEx { + std::uint32_t width; + std::uint32_t height; + float aspectRatio; + std::uint8_t language[4]; + std::uint8_t reserved0[4]; + std::uint32_t cropLeftOffset; + std::uint32_t cropRightOffset; + std::uint32_t cropTopOffset; + std::uint32_t cropBottomOffset; + std::uint32_t pitch; + std::uint8_t lumaBitDepth; + std::uint8_t chromaBitDepth; + std::uint8_t fullRange; + std::uint8_t reserved1[5]; + double frameRate; + std::uint32_t colourPrimaries; + std::uint32_t transferCharacteristics; + std::uint8_t reserved2[16]; +}; +static_assert(sizeof(VideoDetailsEx) == 80, "SceAvPlayerVideoEx ABI"); +static_assert(offsetof(VideoDetailsEx, cropLeftOffset) == 20, "SceAvPlayerVideoEx ABI"); +static_assert(offsetof(VideoDetailsEx, pitch) == 36, "SceAvPlayerVideoEx ABI"); +static_assert(offsetof(VideoDetailsEx, frameRate) == 48, "SceAvPlayerVideoEx ABI"); + +// SceAvPlayerFrameInfo / SceAvPlayerFrameInfoEx: the out-param of the video getters. The Ex variant carries +// the wide details union; both put their timestamp at 16 and their details at 24. +struct FrameInfo { + void* data; + std::uint8_t reserved[4]; + std::uint64_t timestamp; + std::uint32_t width; + std::uint32_t height; + float aspectRatio; + std::uint8_t language[4]; +}; +struct FrameInfoEx { + void* data; + std::uint8_t reserved[4]; + std::uint64_t timestamp; + VideoDetailsEx video; +}; +static_assert(sizeof(FrameInfo) == 40 && offsetof(FrameInfo, timestamp) == 16, "SceAvPlayerFrameInfo ABI"); +static_assert(sizeof(FrameInfoEx) == 104 && offsetof(FrameInfoEx, video) == 24, "SceAvPlayerFrameInfoEx ABI"); + +// Prefix of sceAvPlayerInitData / sceAvPlayerInitDataEx that this player reads. The Ex layout starts with its own size. +struct InitPrefix { + void* memObject; + void* alloc; + void* dealloc; + void* allocTexture; + void* deallocTexture; + void* fileObject; + void* fileOpen; + void* fileClose; + void* fileRead; + void* fileSize; + void* eventObject; + void* eventCallback; + std::int32_t debugLevel; + std::uint32_t basePriority; + std::int32_t numFramebuffers; + std::uint8_t autoStart; +}; + +struct Player { + std::uint64_t magic = kMagic; + void* eventObject = nullptr; + EventCallback callback = nullptr; + void* memObject = nullptr; + TextureAllocator allocTexture = nullptr; + TextureFree deallocTexture = nullptr; + bool autoStart = false; + bool haveSource = false; + bool playing = false; + bool paused = false; + bool stopFired = false; + bool looping = false; + bool noStorage = false; + std::int32_t numFramebuffers = 0; + // Media clock. A hardware decoder advances the stream on its own clock and a poll picks up whichever frame + // is due; counting polls instead stretched this 4-second blank clip over 80 seconds of a 1.5 fps run, + // because the title polls exactly once per host frame. bankedMs holds the media time reached at clockAnchor, + // and a cleared anchor means the clock is stopped (paused or not yet started) or re-anchors on next read. + std::chrono::steady_clock::time_point clockAnchor{}; + std::uint64_t bankedMs = 0; + std::uint64_t framesDelivered = 0; + std::string path; + std::vector frames; + std::mutex lock; +}; + +Player* Check(void* handle) { + Player* p = static_cast(handle); + return p != nullptr && p->magic == kMagic ? p : nullptr; +} + +// The callback runs guest code and may re-enter the player, so it is always invoked without the player lock held. +void Fire(Player* p, std::int32_t event, void* data = nullptr) { + if (p->callback == nullptr) return; + // Budgeted per event number rather than per call site: one shared APS5_HIT line would cap the whole + // census at three events of any kind, so the READY/PLAY pair that opens a movie eats the budget and the + // STOP that ends it is never reported. + static std::atomic fired[64]; + const auto slot = static_cast(event) < 64 ? static_cast(event) : 0u; + if (fired[slot].fetch_add(1) < 3) + std::fprintf(stderr, "[AVP] event 0x%02x\n", event); + p->callback(p->eventObject, event, 0, data); +} + +// Caller holds the lock. +std::uint64_t MediaTime(Player& p) { + const auto now = std::chrono::steady_clock::now(); + if (p.clockAnchor == std::chrono::steady_clock::time_point{}) { + if (!p.playing || p.paused) return p.bankedMs; + p.clockAnchor = now; + } + const auto elapsed = static_cast( + std::chrono::duration_cast(now - p.clockAnchor).count()); + return p.bankedMs + elapsed; +} + +// Frame due at the current media time, saturating at the end of the clip. +std::uint64_t CurrentFrame(Player& p) { + const auto frame = MediaTime(p) / kFrameIntervalMs; + return frame > kFrameBudget ? kFrameBudget : frame; +} + +// Bank the clock where it stands and stop it. +void HaltClock(Player& p) { + p.bankedMs = MediaTime(p); + p.clockAnchor = {}; +} + +// True once the blank clip has run out -- or was never going to run, because the title gave us no allocator to +// put its frames in. Both are the same thing to a polling title: no more frames will ever arrive. +bool StreamExhausted(Player& p) { return p.noStorage || CurrentFrame(p) >= kFrameBudget; } + +// There is no decoder, so this player "plays" a fixed-length blank clip instead of the source. On real hardware +// the end of the stream is reported by the poll that finds no more frames, and the STOP event fires on that same +// poll -- which is the only end-of-movie signal a title gets if it pumps the data getters instead of +// sceAvPlayerIsActive. A title with an intro-logo or splash-video sequence commonly does exactly that, so STOP +// has to come from the poll, and only once the clip has actually run out: a title that is waiting for frames +// ends its sequence on the end-of-stream that follows them, not on one that arrives before the first. +// Returns true when this call is the one that ends the stream, so the caller fires the event outside the lock. +bool EndStream(Player* p) { + std::lock_guard g(p->lock); + if (!p->playing || p->paused || p->stopFired) return false; + if (!StreamExhausted(*p)) return false; + // Once per player, and outside the APS5_HIT budget: this is the line that says whether a movie was + // allowed to finish or is stranded in a pause that never resumed. + std::fprintf(stderr, "[AVP] end of stream: %llu frames delivered, clock at %llu ms\n", + static_cast(CurrentFrame(*p)), + static_cast(MediaTime(*p))); + std::fflush(stderr); + HaltClock(*p); + p->playing = false; + p->stopFired = true; + p->haveSource = false; + return true; +} + +// The frame buffers come from the title's own texture allocator, exactly as on hardware: SceAvPlayerInitData +// hands it out alongside the memory pool precisely so frame storage lives in graphics memory, which is what lets +// the upload the title later issues from the delivered pointer be validated as guest memory. A player that +// supplies no such allocator has nowhere to put a frame, and that is reported once -- as an exhausted stream -- +// rather than retried on every poll. Caller holds the lock. +bool PrepareFrames(Player& p) { + if (!p.frames.empty() || p.noStorage) return !p.frames.empty(); + if (p.allocTexture == nullptr || p.memObject == nullptr) { + p.noStorage = true; + APS5_HIT("AVP", "no guest frame allocator: InitData supplied no texture allocator"); + return false; + } + unsigned requested = p.numFramebuffers > 0 ? static_cast(p.numFramebuffers) : 2u; + if (requested < 2u) requested = 2u; + if (requested > 8u) requested = 8u; + const auto planeBytes = static_cast(kFramePitch) * kFrameHeight; + for (unsigned i = 0; i < requested; ++i) { + auto* frame = static_cast( + p.allocTexture(p.memObject, 0x100, static_cast(kFrameBytes))); + if (frame == nullptr) break; + std::memset(frame, kBlackLuma, planeBytes); + std::memset(frame + planeBytes, kBlackChroma, kFrameBytes - planeBytes); + p.frames.push_back(frame); + } + if (p.frames.empty()) { + p.noStorage = true; + return false; + } + std::fprintf(stderr, "[AVP] blank clip: buffers=%zu bytes=%llu pitch=%u allocator=%p first=%p\n", + p.frames.size(), static_cast(kFrameBytes), kFramePitch, + reinterpret_cast(p.allocTexture), static_cast(p.frames.front())); + std::fflush(stderr); + return true; +} + +// Publishes whichever frame the media clock has reached. Returns false when the clip is over, which is the poll +// that reports the end of the stream. Every frame of this clip is the same blank image, so skipping ahead on a +// slow host costs a title nothing it can observe -- unlike a real movie, where the clock would have to be +// slowed to the host's pace instead. Caller holds the lock. +bool DeliverFrame(Player& p, void*& data, std::uint64_t& timestamp) { + if (!p.playing || p.paused || !PrepareFrames(p)) return false; + const auto frame = CurrentFrame(p); + if (frame >= kFrameBudget) return false; + data = p.frames[frame % p.frames.size()]; + timestamp = frame * kFrameIntervalMs; + if (frame + 1 > p.framesDelivered) { + const auto previous = p.framesDelivered; + p.framesDelivered = frame + 1; + // Every thirtieth frame *crossed*, not every thirtieth index: on a slow host the clock jumps + // straight over exact multiples, and an equality test there reports nothing at all. + if (previous / 30 != p.framesDelivered / 30 || p.framesDelivered == kFrameBudget) + std::fprintf(stderr, "[AVP] blank frame #%llu of %llu t=%llu ms\n", + static_cast(p.framesDelivered), + static_cast(kFrameBudget), + static_cast(timestamp)); + } + return true; +} + +// The wide video description of the blank clip. `width` publishes the coded extent (the pitch) and the +// difference from the visible picture goes out through cropRightOffset, so a title that sizes its luma texture +// from `pitch` and a title that crops `width` both end up with the visible frame. +void WriteVideoDetails(VideoDetailsEx& out) { + out = {}; + out.width = kFramePitch; + out.height = kFrameHeight; + out.aspectRatio = static_cast(kFrameWidth) / static_cast(kFrameHeight); + out.cropRightOffset = kFramePitch - kFrameWidth; + out.pitch = kFramePitch; + out.lumaBitDepth = 8; + out.chromaBitDepth = 8; + out.frameRate = kFrameRate; +} + +// Shared body of the two video getters. The frame is written through the guest's out-param in whichever of the +// two layouts the caller announced, and a poll that finds the clip spent is turned into the end-of-stream report +// on the way out -- outside the lock, because the STOP callback runs guest code. +Bool PollVideo(Player* p, void* frame_info, bool extended) { + if (frame_info == nullptr) return 0; + void* data = nullptr; + std::uint64_t timestamp = 0; + bool delivered = false; + bool wasPaused = false; + { + std::lock_guard g(p->lock); + wasPaused = p->paused; + delivered = DeliverFrame(*p, data, timestamp); + if (delivered) { + if (extended) { + FrameInfoEx out = {}; + out.data = data; + out.timestamp = timestamp; + WriteVideoDetails(out.video); + std::memcpy(frame_info, &out, sizeof(out)); + } else { + FrameInfo out = {}; + out.data = data; + out.timestamp = timestamp; + out.width = kFrameWidth; + out.height = kFrameHeight; + out.aspectRatio = static_cast(kFrameWidth) / static_cast(kFrameHeight); + std::memcpy(frame_info, &out, sizeof(out)); + } + return 1; + } + } + // Unbudgeted (unlike APS5_HIT above): every poll that finds the player paused, so the exact interleaving + // of the game's own Pause call against its own polling loop is visible instead of guessed at. + if (wasPaused) { + std::fprintf(stderr, "[AVPLIFE] poll found paused, no frame delivered\n"); + std::fflush(stderr); + } + if (EndStream(p)) Fire(p, kEventStop); + return 0; +} + +// HANDOFF13/14: HANDOFF12's still-open question was "why does the guest pause and never resume". These +// call-site logs (unbudgeted, unlike APS5_HIT -- we need every call, not just the first three) print the +// thread that called, the player pointer (multiple concurrent players would explain a stray pause on the +// wrong handle), and the media clock's state at the moment of the call. +void LogLifecycleCall(const char* site, Player* p) { + std::ostringstream tid; + tid << std::this_thread::get_id(); + std::lock_guard g(p->lock); + std::fprintf(stderr, "[AVPLIFE] %s p=%p tid=%s playing=%d paused=%d stopFired=%d haveSource=%d framesDelivered=%llu bankedMs=%llu clockRunning=%d\n", + site, static_cast(p), tid.str().c_str(), p->playing ? 1 : 0, + p->paused ? 1 : 0, p->stopFired ? 1 : 0, p->haveSource ? 1 : 0, + static_cast(p->framesDelivered), static_cast(p->bankedMs), + p->clockAnchor != std::chrono::steady_clock::time_point{} ? 1 : 0); + std::fflush(stderr); +} + +Player* CreateFrom(const InitPrefix* d) { + Player* p = new Player(); + if (d != nullptr) { + p->eventObject = d->eventObject; + p->callback = reinterpret_cast(d->eventCallback); + p->autoStart = d->autoStart != 0; + p->memObject = d->memObject; + p->allocTexture = reinterpret_cast(d->allocTexture); + p->deallocTexture = reinterpret_cast(d->deallocTexture); + p->numFramebuffers = d->numFramebuffers; + } + return p; +} + +int AddSourceCommon(Player* p, const char* path) { + if (p == nullptr || path == nullptr || *path == '\0') return kErrInvalidParams; + LogLifecycleCall("AddSource(before)", p); + bool play = false; + { + std::lock_guard g(p->lock); + p->path = path; + p->haveSource = true; + p->playing = p->autoStart; + p->paused = false; + p->stopFired = false; + p->bankedMs = 0; + p->clockAnchor = {}; + p->framesDelivered = 0; + play = p->autoStart; + } + APS5_HIT("AVP", "AddSource '%s' (no decode: %llu blank frames then end of stream)", path, + static_cast(kFrameBudget)); + Fire(p, kEventReady); + if (play) Fire(p, kEventPlay); + return 0; +} +} // namespace + extern "C" { -int APS5_VABI sceAvPlayerAddSource(AvPlayerInternal* h, const char* filename) { - (void)h; - (void)filename; - NotImplemented_nid_no_patch(__func__); +void* APS5_VABI sceAvPlayerInit(const void* init) { + APS5_HIT("AVP", "sceAvPlayerInit"); + return CreateFrom(static_cast(init)); +} +int APS5_VABI sceAvPlayerInitEx(const void* init_ex, void** handle) { + if (handle == nullptr) return kErrInvalidParams; + // sceAvPlayerInitDataEx begins with a 64-bit size, followed by the same allocator / file / event blocks. + const std::uint8_t* base = static_cast(init_ex); + *handle = CreateFrom(base != nullptr ? reinterpret_cast(base + 8) : nullptr); return 0; } - -int APS5_VABI sceAvPlayerAddSourceEx(AvPlayerInternal* h, uint32_t uri_type, const void* source_details) { - (void)h; - (void)uri_type; - (void)source_details; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerChangeStream(AvPlayerInternal* h, uint32_t old_stream_id, uint32_t new_stream_id) { - (void)h; - (void)old_stream_id; - (void)new_stream_id; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerClose(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -uint64_t APS5_VABI sceAvPlayerCurrentTime(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerDisableStream(AvPlayerInternal* h, uint32_t stream_id) { - (void)h; - (void)stream_id; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerEnableStream(AvPlayerInternal* h, uint32_t stream_id) { - (void)h; - (void)stream_id; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -Bool APS5_VABI sceAvPlayerGetAudioData(AvPlayerInternal* h, AvPlayerFrameInfo* audio_info) { - (void)h; - (void)audio_info; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -int APS5_VABI sceAvPlayerGetStreamInfo(AvPlayerInternal* h, uint32_t stream_id, void* info) { - (void)h; - (void)stream_id; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerGetStreamInfoEx(AvPlayerInternal* h, uint32_t stream_id, void* info) { - (void)h; - (void)stream_id; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -Bool APS5_VABI sceAvPlayerGetVideoDataEx(AvPlayerInternal* h, AvPlayerFrameInfoEx* video_info) { - (void)h; - (void)video_info; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -AvPlayerInternal* APS5_VABI sceAvPlayerInit(AvPlayerInitData* init) { - (void)init; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceAvPlayerInitEx(const void* init_ex, AvPlayerInternal** handle) { - (void)init_ex; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -Bool APS5_VABI sceAvPlayerIsActive(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -int APS5_VABI sceAvPlayerJumpToTime(AvPlayerInternal* h, uint64_t time_ms) { - (void)h; - (void)time_ms; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerPause(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerPostInit(AvPlayerInternal* h, const void* post_init) { - (void)h; +int APS5_VABI sceAvPlayerPostInit(void* h, const void* post_init) { (void)post_init; - NotImplemented_nid_no_patch(__func__); - return 0; + return Check(h) != nullptr ? 0 : kErrInvalidParams; } - -int APS5_VABI sceAvPlayerResume(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerSetAvailableBandwidth(AvPlayerInternal* h, uint32_t start_bandwidth, uint32_t minimum_bandwidth, uint32_t maximum_bandwidth) { - (void)h; - (void)start_bandwidth; - (void)minimum_bandwidth; - (void)maximum_bandwidth; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerSetAvSyncMode(AvPlayerInternal* h, uint32_t sync_mode) { - (void)h; - (void)sync_mode; - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceAvPlayerSetLogCallback(void* callback, void* user_data) { (void)callback; (void)user_data; - NotImplemented_nid_no_patch(__func__); return 0; } - -int APS5_VABI sceAvPlayerSetLooping(AvPlayerInternal* h, Bool loop) { - (void)h; - (void)loop; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAvPlayerAddSource(void* h, const char* filename) { + return AddSourceCommon(Check(h), filename); +} +int APS5_VABI sceAvPlayerAddSourceEx(void* h, std::uint32_t uri_type, const void* source_details) { + (void)uri_type; + // SceAvPlayerSourceDetails begins with { const char* name; uint32_t length; }. + const char* name = source_details != nullptr ? *static_cast(source_details) : nullptr; + return AddSourceCommon(Check(h), name); +} +int APS5_VABI sceAvPlayerStart(void* h) { + Player* p = Check(h); + if (p == nullptr) return kErrInvalidParams; + LogLifecycleCall("Start(before)", p); + bool play = false; + { + std::lock_guard g(p->lock); + if (p->haveSource && !p->playing && !p->stopFired) { p->playing = true; p->paused = false; play = true; } + } + if (play) Fire(p, kEventPlay); return 0; } - -int APS5_VABI sceAvPlayerSetTrickSpeed(AvPlayerInternal* h, int32_t trick_speed) { - (void)h; - (void)trick_speed; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerStart(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerStartEx(AvPlayerInternal* h, const void* start_info_ex) { - (void)h; +int APS5_VABI sceAvPlayerStartEx(void* h, const void* start_info_ex) { (void)start_info_ex; - NotImplemented_nid_no_patch(__func__); + return sceAvPlayerStart(h); +} +int APS5_VABI sceAvPlayerPause(void* h) { + Player* p = Check(h); + if (p == nullptr) return kErrInvalidParams; + LogLifecycleCall("Pause(before)", p); + bool notify = false; + { + std::lock_guard g(p->lock); + if (p->playing && !p->paused) { p->paused = true; HaltClock(*p); notify = true; } + } + if (notify) Fire(p, kEventPause); return 0; } - -int APS5_VABI sceAvPlayerStop(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAvPlayerResume(void* h) { + Player* p = Check(h); + if (p == nullptr) return kErrInvalidParams; + LogLifecycleCall("Resume(before)", p); + bool notify = false; + { + std::lock_guard g(p->lock); + if (p->playing && p->paused) { p->paused = false; notify = true; } + } + if (notify) Fire(p, kEventPlay); return 0; } - -int APS5_VABI sceAvPlayerStreamCount(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); +int APS5_VABI sceAvPlayerStop(void* h) { + Player* p = Check(h); + if (p == nullptr) return kErrInvalidParams; + LogLifecycleCall("Stop(before)", p); + bool notify = false; + { + std::lock_guard g(p->lock); + if (p->playing && !p->stopFired) { p->playing = false; HaltClock(*p); p->stopFired = true; notify = true; } + p->haveSource = false; + } + if (notify) Fire(p, kEventStop); + return 0; +} +int APS5_VABI sceAvPlayerClose(void* h) { + Player* p = Check(h); + if (p == nullptr) return kErrInvalidParams; + std::vector frames; + { + std::lock_guard g(p->lock); + p->magic = 0; + frames.swap(p->frames); + } + // The frame storage came from the title's allocator, so it goes back to it: a title that opens a player per + // movie would otherwise lose every buffer of the clip on each close. + if (p->deallocTexture != nullptr) + for (auto* frame : frames) p->deallocTexture(p->memObject, frame); + delete p; + return 0; +} +// bool: true while the player is presenting. See EndStream: the clip is finite, so once it has run out the poll +// that finds no frame left reports the end (STOP event) and IsActive turns false. Until then it stays true, which +// is what keeps a title that pumps the getters rather than IsActive from being told the movie ended early. +Bool APS5_VABI sceAvPlayerIsActive(void* h) { + Player* p = Check(h); + if (p == nullptr) return 0; + if (EndStream(p)) Fire(p, kEventStop); + std::lock_guard g(p->lock); + return p->playing ? 1 : 0; +} +// The clip's own media clock: it runs on wall time while the player presents, so it agrees with the timestamps +// on the frames handed out and with the duration the stream info reports. +std::uint64_t APS5_VABI sceAvPlayerCurrentTime(void* h) { + Player* p = Check(h); + if (p == nullptr) return 0; + std::lock_guard g(p->lock); + const auto time_ms = MediaTime(*p); + return time_ms > kStreamDurationMs ? kStreamDurationMs : time_ms; +} +// A seek within a clip whose frames are all identical is a clock move, and that is all the title can be +// waiting for: a pull loop that discards any frame no newer than the seek target would otherwise throw away +// every frame from here on. +int APS5_VABI sceAvPlayerJumpToTime(void* h, std::uint64_t time_ms) { + Player* p = Check(h); + if (p == nullptr) return kErrInvalidParams; + LogLifecycleCall("JumpToTime(before)", p); + std::fprintf(stderr, "[AVPLIFE] JumpToTime time_ms=%llu\n", static_cast(time_ms)); + std::fflush(stderr); + { + std::lock_guard g(p->lock); + p->bankedMs = time_ms > kStreamDurationMs ? kStreamDurationMs : time_ms; + p->clockAnchor = {}; + } + std::uint32_t warning = kWarningJumpComplete; + Fire(p, kEventWarning, &warning); + return 0; +} +int APS5_VABI sceAvPlayerSetLooping(void* h, Bool loop) { + Player* p = Check(h); + if (p == nullptr) return kErrInvalidParams; + p->looping = loop != 0; + return 0; +} +int APS5_VABI sceAvPlayerSetAvSyncMode(void* h, std::uint32_t sync_mode) { + (void)sync_mode; + return Check(h) != nullptr ? 0 : kErrInvalidParams; +} +int APS5_VABI sceAvPlayerSetTrickSpeed(void* h, std::int32_t trick_speed) { + (void)trick_speed; + return Check(h) != nullptr ? 0 : kErrInvalidParams; +} +int APS5_VABI sceAvPlayerSetAvailableBandwidth(void* h, std::uint32_t start_bandwidth, std::uint32_t minimum_bandwidth, std::uint32_t maximum_bandwidth) { + (void)start_bandwidth; + (void)minimum_bandwidth; + (void)maximum_bandwidth; + return Check(h) != nullptr ? 0 : kErrInvalidParams; +} +// One (placeholder) video stream once a source is present; the stream table below describes it as an empty 1920x1080 clip. +int APS5_VABI sceAvPlayerStreamCount(void* h) { + Player* p = Check(h); + if (p == nullptr) return kErrInvalidParams; + std::lock_guard g(p->lock); + return p->haveSource ? 1 : 0; +} +int APS5_VABI sceAvPlayerEnableStream(void* h, std::uint32_t stream_id) { + (void)stream_id; + return Check(h) != nullptr ? 0 : kErrInvalidParams; +} +int APS5_VABI sceAvPlayerDisableStream(void* h, std::uint32_t stream_id) { + (void)stream_id; + return Check(h) != nullptr ? 0 : kErrInvalidParams; +} +int APS5_VABI sceAvPlayerChangeStream(void* h, std::uint32_t old_stream_id, std::uint32_t new_stream_id) { + (void)old_stream_id; + (void)new_stream_id; + return Check(h) != nullptr ? 0 : kErrInvalidParams; +} +// SceAvPlayerStreamInfo (32 bytes): u32 type (1 video, 2 audio), u8 reserved[4], details[16], u64 duration (ms). +// Video details: u32 width, u32 height, float aspect, char language[4]. +int APS5_VABI sceAvPlayerGetStreamInfo(void* h, std::uint32_t stream_id, void* info) { + Player* p = Check(h); + if (p == nullptr || info == nullptr) return kErrInvalidParams; + std::lock_guard g(p->lock); + if (!p->haveSource || stream_id != 0) return kErrOperationFailed; + std::uint8_t* out = static_cast(info); + std::memset(out, 0, 32); + const std::uint32_t type = 1; + const std::uint32_t width = kFrameWidth; + const std::uint32_t height = kFrameHeight; + const float aspect = static_cast(kFrameWidth) / static_cast(kFrameHeight); + const std::uint64_t duration = kStreamDurationMs; + std::memcpy(out + 0, &type, 4); + std::memcpy(out + 8, &width, 4); + std::memcpy(out + 12, &height, 4); + std::memcpy(out + 16, &aspect, 4); + std::memcpy(out + 24, &duration, 8); + return 0; +} +// SceAvPlayerStreamInfoEx (104 bytes): u64 size, u32 type, reserved[4], details[80], u64 duration. The details +// union holds the same wide video description the frame getters publish, so a title that sizes its movie +// surfaces from the stream info instead of from a frame gets one answer, not two. +int APS5_VABI sceAvPlayerGetStreamInfoEx(void* h, std::uint32_t stream_id, void* info) { + Player* p = Check(h); + if (p == nullptr || info == nullptr) return kErrInvalidParams; + std::lock_guard g(p->lock); + if (!p->haveSource || stream_id != 0) return kErrOperationFailed; + std::uint8_t* out = static_cast(info); + std::uint64_t size = 0; + std::memcpy(&size, out, 8); + std::memset(out, 0, 104); + std::memcpy(out, &size, 8); + const std::uint32_t type = 1; + const std::uint64_t duration = kStreamDurationMs; + std::memcpy(out + 8, &type, 4); + VideoDetailsEx details; + WriteVideoDetails(details); + std::memcpy(out + 16, &details, sizeof(details)); + std::memcpy(out + 96, &duration, 8); + return 0; +} +// The data getters are the frame source as well as the end-of-stream report: they hand out whichever frame of +// the blank clip the media clock has reached, and the poll that finds the clip spent is the one that reports the +// stream as finished. Each site logs its own first three calls, so a run shows which of these the title pumps. +Bool APS5_VABI sceAvPlayerGetVideoData(void* h, void* video_info) { + Player* p = Check(h); + if (p == nullptr) return 0; + APS5_HIT("AVP", "GetVideoData"); + return PollVideo(p, video_info, false); +} +Bool APS5_VABI sceAvPlayerGetVideoDataEx(void* h, void* video_info) { + Player* p = Check(h); + if (p == nullptr) return 0; + APS5_HIT("AVP", "GetVideoDataEx"); + return PollVideo(p, video_info, true); +} +Bool APS5_VABI sceAvPlayerGetAudioData(void* h, void* audio_info) { + (void)audio_info; + Player* p = Check(h); + if (p == nullptr) return 0; + APS5_HIT("AVP", "GetAudioData"); + if (EndStream(p)) Fire(p, kEventStop); return 0; } - } diff --git a/core/libs/prx/libSceAvPlayer/Export.cpp b/core/libs/prx/libSceAvPlayer/Export.cpp index 25ed90a9d..06e4ac80c 100644 --- a/core/libs/prx/libSceAvPlayer/Export.cpp +++ b/core/libs/prx/libSceAvPlayer/Export.cpp @@ -1,177 +1 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" - -extern "C" { - -int APS5_VABI sceAvPlayerAddSource(AvPlayerInternal* h, const char* filename) { - (void)h; - (void)filename; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerAddSourceEx(AvPlayerInternal* h, uint32_t uri_type, const void* source_details) { - (void)h; - (void)uri_type; - (void)source_details; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerChangeStream(AvPlayerInternal* h, uint32_t old_stream_id, uint32_t new_stream_id) { - (void)h; - (void)old_stream_id; - (void)new_stream_id; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerClose(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -uint64_t APS5_VABI sceAvPlayerCurrentTime(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerDisableStream(AvPlayerInternal* h, uint32_t stream_id) { - (void)h; - (void)stream_id; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerEnableStream(AvPlayerInternal* h, uint32_t stream_id) { - (void)h; - (void)stream_id; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -Bool APS5_VABI sceAvPlayerGetAudioData(AvPlayerInternal* h, AvPlayerFrameInfo* audio_info) { - (void)h; - (void)audio_info; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -int APS5_VABI sceAvPlayerGetStreamInfo(AvPlayerInternal* h, uint32_t stream_id, void* info) { - (void)h; - (void)stream_id; - (void)info; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -Bool APS5_VABI sceAvPlayerGetVideoData(AvPlayerInternal* h, AvPlayerFrameInfo* video_info) { - (void)h; - (void)video_info; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -Bool APS5_VABI sceAvPlayerGetVideoDataEx(AvPlayerInternal* h, AvPlayerFrameInfoEx* video_info) { - (void)h; - (void)video_info; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -AvPlayerInternal* APS5_VABI sceAvPlayerInit(AvPlayerInitData* init) { - (void)init; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceAvPlayerInitEx(const void* init_ex, AvPlayerInternal** handle) { - (void)init_ex; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -Bool APS5_VABI sceAvPlayerIsActive(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -int APS5_VABI sceAvPlayerJumpToTime(AvPlayerInternal* h, uint64_t time_ms) { - (void)h; - (void)time_ms; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerPause(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerPostInit(AvPlayerInternal* h, const void* post_init) { - (void)h; - (void)post_init; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerResume(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerSetAvSyncMode(AvPlayerInternal* h, uint32_t sync_mode) { - (void)h; - (void)sync_mode; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerSetLogCallback(void* callback, void* user_data) { - (void)callback; - (void)user_data; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerSetLooping(AvPlayerInternal* h, Bool loop) { - (void)h; - (void)loop; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerSetTrickSpeed(AvPlayerInternal* h, int32_t trick_speed) { - (void)h; - (void)trick_speed; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerStart(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerStop(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceAvPlayerStreamCount(AvPlayerInternal* h) { - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -} +#include "prx/libSceAvPlayer.native/Export.cpp" diff --git a/core/libs/prx/libSceFont.native/CMakeLists.txt b/core/libs/prx/libSceFont.native/CMakeLists.txt new file mode 100644 index 000000000..af4d2df01 --- /dev/null +++ b/core/libs/prx/libSceFont.native/CMakeLists.txt @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/../libSceFont/FontLibrary.cmake) +add_sce_font_library(libSceFont.native) diff --git a/core/libs/prx/libSceFont.native/Export.cpp b/core/libs/prx/libSceFont.native/Export.cpp deleted file mode 100644 index 33f3d0fa4..000000000 --- a/core/libs/prx/libSceFont.native/Export.cpp +++ /dev/null @@ -1,520 +0,0 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" - -extern "C" { - -int APS5_VABI sceFontAttachDeviceCacheBuffer(FontLibrary library, void* buffer, uint32_t size) { - (void)library; - (void)buffer; - (void)size; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontBindRenderer(FontHandle font_handle, FontRenderer renderer) { - (void)font_handle; - (void)renderer; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCharacterGetBidiLevel(FontTextCharacter text_character, int* bidi_level) { - (void)text_character; - (void)bidi_level; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCharacterGetSyllableStringState(FontTextCharacter text_character, int* syllable_string_state) { - (void)text_character; - (void)syllable_string_state; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCharacterGetTextFontCode(FontTextCharacter text_character, FontHandle* font_handle, uint32_t* text_code) { - (void)text_character; - (void)font_handle; - (void)text_code; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCharacterGetTextOrder(FontTextCharacter text_character, void** text_order) { - (void)text_character; - (void)text_order; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -uint32_t APS5_VABI sceFontCharacterLooksFormatCharacters(FontTextCharacter text_character) { - (void)text_character; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -uint32_t APS5_VABI sceFontCharacterLooksWhiteSpace(FontTextCharacter text_character) { - (void)text_character; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -FontTextCharacter APS5_VABI sceFontCharacterRefersTextBack(FontTextCharacter text_character) { - (void)text_character; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -FontTextCharacter APS5_VABI sceFontCharacterRefersTextNext(FontTextCharacter text_character) { - (void)text_character; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(FontTextCharacter text_character, FontTextCharacter term_character, FontTextCodes* text_codes) { - (void)text_character; - (void)term_character; - (void)text_codes; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceFontCloseFont(FontHandle font_handle) { - (void)font_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateLibrary(const FontMemory* memory, FontLibrarySelection selection, FontLibrary* library) { - (void)memory; - (void)selection; - (void)library; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateLibraryWithEdition(const FontMemory* memory, FontLibrarySelection selection, uint64_t edition, FontLibrary* library) { - (void)memory; - (void)selection; - (void)edition; - (void)library; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateRendererWithEdition(const FontMemory* memory, FontRendererSelection selection, uint64_t edition, FontRenderer* renderer) { - (void)memory; - (void)selection; - (void)edition; - (void)renderer; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateString(const FontMemory* memory, FontTextSource* font_text_source, const FontCreateStringDetail* string_detail, FontString* font_string) { - (void)memory; - (void)font_text_source; - (void)string_detail; - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateWritingLine(const FontMemory* memory, int writing_form, const void* writing_line_detail, FontWritingLine* writing_line) { - (void)memory; - (void)writing_form; - (void)writing_line_detail; - (void)writing_line; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDefineAttribute(FontHandle font_handle, int attribute, int* old_attribute) { - (void)font_handle; - (void)attribute; - (void)old_attribute; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDeleteGlyph(const FontMemory* memory, void** font_glyph) { - (void)memory; - (void)font_glyph; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDestroyLibrary(FontLibrary* library) { - (void)library; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDestroyRenderer(FontRenderer* renderer) { - (void)renderer; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDestroyString(FontString* font_string) { - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDestroyWritingLine(FontWritingLine* writing_line) { - (void)writing_line; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGenerateCharGlyph(FontHandle font_handle, uint32_t code, const FontGenerateGlyphDetail* detail, void** font_glyph) { - (void)font_handle; - (void)code; - (void)detail; - (void)font_glyph; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGetCharGlyphMetrics(FontHandle font_handle, uint32_t code, FontGlyphMetrics* metrics) { - (void)font_handle; - (void)code; - (void)metrics; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGetHorizontalLayout(FontHandle font_handle, FontHorizontalLayout* layout) { - (void)font_handle; - (void)layout; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGetRenderCharGlyphMetrics(FontHandle font_handle, uint32_t code, FontGlyphMetrics* metrics) { - (void)font_handle; - (void)code; - (void)metrics; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGetVerticalLayout(FontHandle font_handle, FontVerticalLayout* layout) { - (void)font_handle; - (void)layout; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGlyphDefineAttribute(void* font_glyph, int attribute, int* old_attribute) { - (void)font_glyph; - (void)attribute; - (void)old_attribute; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontMemoryInit(FontMemory* font_memory, void* address, uint32_t size_byte, const FontMemoryInterface* mem_interface, void* mspace_object, FontMemoryDestroyCallback destroy_callback, void* destroy_object) { - (void)font_memory; - (void)address; - (void)size_byte; - (void)mem_interface; - (void)mspace_object; - (void)destroy_callback; - (void)destroy_object; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontMemoryTerm(FontMemory* font_memory) { - (void)font_memory; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontOpenFontInstance(FontHandle font_handle, void* setup_font, FontHandle* out_font_handle) { - (void)font_handle; - (void)setup_font; - (void)out_font_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontOpenFontMemory(FontLibrary library, const void* font_address, uint32_t font_size, const FontOpenDetail* detail, FontHandle* handle) { - (void)library; - (void)font_address; - (void)font_size; - (void)detail; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontOpenFontSet(FontLibrary library, uint32_t font_set_type, uint32_t open_mode, const FontOpenDetail* detail, FontHandle* handle) { - (void)library; - (void)font_set_type; - (void)open_mode; - (void)detail; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontRebindRenderer(FontHandle font_handle) { - (void)font_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontRenderCharGlyphImageHorizontal(FontHandle font_handle, uint32_t code, FontRenderSurface* surf, float x, float y, FontGlyphMetrics* metrics, FontRenderResult* result) { - (void)font_handle; - (void)code; - (void)surf; - (void)x; - (void)y; - (void)metrics; - (void)result; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -void APS5_VABI sceFontRenderSurfaceInit(FontRenderSurface* surf, void* buffer, int buf_width_byte, int pixel_size_byte, int width, int height) { - (void)surf; - (void)buffer; - (void)buf_width_byte; - (void)pixel_size_byte; - (void)width; - (void)height; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI sceFontRenderSurfaceSetScissor(FontRenderSurface* surf, uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1) { - (void)surf; - (void)x0; - (void)y0; - (void)x1; - (void)y1; - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceFontSetEffectSlant(FontHandle font_handle, float slant_ratio) { - (void)font_handle; - (void)slant_ratio; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetEffectWeight(FontHandle font_handle, float weight_x_scale, float weight_y_scale, uint32_t mode) { - (void)font_handle; - (void)weight_x_scale; - (void)weight_y_scale; - (void)mode; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetScalePixel(FontHandle font_handle, float w, float h) { - (void)font_handle; - (void)w; - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetupRenderEffectSlant(FontHandle font_handle, float slant_ratio) { - (void)font_handle; - (void)slant_ratio; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetupRenderEffectWeight(FontHandle font_handle, float weight_x_scale, float weight_y_scale, uint32_t mode) { - (void)font_handle; - (void)weight_x_scale; - (void)weight_y_scale; - (void)mode; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetupRenderScalePixel(FontHandle font_handle, float w, float h) { - (void)font_handle; - (void)w; - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -uint32_t APS5_VABI sceFontStringGetTerminateCode(FontString font_string) { - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -void* APS5_VABI sceFontStringGetTerminateOrder(FontString font_string) { - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceFontStringGetWritingForm(FontString font_string) { - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -FontRenderCharacter APS5_VABI sceFontStringRefersRenderCharacters(FontString font_string, FontTextCharacter start_character, FontTextCharacter last_character, uint32_t* character_count) { - (void)font_string; - (void)start_character; - (void)last_character; - (void)character_count; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -FontTextCharacter APS5_VABI sceFontStringRefersTextCharacters(FontString font_string, uint32_t* character_count) { - (void)font_string; - (void)character_count; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -int APS5_VABI sceFontSupportExternalFonts(FontLibrary library, uint32_t font_max, uint32_t formats) { - (void)library; - (void)font_max; - (void)formats; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSupportSystemFonts(FontLibrary library) { - (void)library; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -FontTextCodes* APS5_VABI sceFontTextCodesStepBack(FontTextCodes* text_codes_step) { - (void)text_codes_step; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes* text_codes_step) { - (void)text_codes_step; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceFontTextSourceInit(FontTextSource* font_text_source, const void* text_address, uint32_t text_size_byte, FontTextParseFunction text_parser, void* text_object) { - (void)font_text_source; - (void)text_address; - (void)text_size_byte; - (void)text_parser; - (void)text_object; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontTextSourceRewind(FontTextSource* font_text_source) { - (void)font_text_source; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontTextSourceSetDefaultFont(FontTextSource* font_text_source, FontHandle default_font) { - (void)font_text_source; - (void)default_font; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontTextSourceSetWritingForm(FontTextSource* font_text_source, int writing_form) { - (void)font_text_source; - (void)writing_form; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontUnbindRenderer(FontHandle font_handle) { - (void)font_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingGetRenderMetrics(FontWriting* font_writing, FontWritingMetrics* writing_metrics) { - (void)font_writing; - (void)writing_metrics; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingInit(FontWriting* font_writing, FontString font_string, FontRenderCharacter font_character) { - (void)font_writing; - (void)font_string; - (void)font_character; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingLineClear(FontWritingLine writing_line) { - (void)writing_line; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingLineGetOrderingSpace(FontWritingLine writing_line, float* head_space, float* inline_space, float* tail_space, float* advance_space) { - (void)writing_line; - (void)head_space; - (void)inline_space; - (void)tail_space; - (void)advance_space; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingLineGetRenderMetrics(FontWritingLine writing_line, FontWritingMetrics* writing_metrics) { - (void)writing_line; - (void)writing_metrics; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -FontWritingLineStep* APS5_VABI sceFontWritingLineRefersRenderStep(FontWritingLine writing_line) { - (void)writing_line; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceFontWritingLineWritesOrder(FontWritingLine writing_line, uint64_t writing_attribute, const FontWritingMetrics* writing_metrics, void* writing_orderer) { - (void)writing_line; - (void)writing_attribute; - (void)writing_metrics; - (void)writing_orderer; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* font_writing) { - (void)font_writing; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -FontTextCharacter APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting* font_writing, const void** letter_step) { - (void)font_writing; - (void)letter_step; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -int APS5_VABI sceFontWritingSetMaskInvisible(FontWriting* font_writing, int mask) { - (void)font_writing; - (void)mask; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -} diff --git a/core/libs/prx/libSceFont/CMakeLists.txt b/core/libs/prx/libSceFont/CMakeLists.txt new file mode 100644 index 000000000..ccbfe95e4 --- /dev/null +++ b/core/libs/prx/libSceFont/CMakeLists.txt @@ -0,0 +1,2 @@ +include(${CMAKE_CURRENT_SOURCE_DIR}/FontLibrary.cmake) +add_sce_font_library(libSceFont) diff --git a/core/libs/prx/libSceFont/Export.cpp b/core/libs/prx/libSceFont/Export.cpp deleted file mode 100644 index 33f3d0fa4..000000000 --- a/core/libs/prx/libSceFont/Export.cpp +++ /dev/null @@ -1,520 +0,0 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" - -extern "C" { - -int APS5_VABI sceFontAttachDeviceCacheBuffer(FontLibrary library, void* buffer, uint32_t size) { - (void)library; - (void)buffer; - (void)size; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontBindRenderer(FontHandle font_handle, FontRenderer renderer) { - (void)font_handle; - (void)renderer; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCharacterGetBidiLevel(FontTextCharacter text_character, int* bidi_level) { - (void)text_character; - (void)bidi_level; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCharacterGetSyllableStringState(FontTextCharacter text_character, int* syllable_string_state) { - (void)text_character; - (void)syllable_string_state; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCharacterGetTextFontCode(FontTextCharacter text_character, FontHandle* font_handle, uint32_t* text_code) { - (void)text_character; - (void)font_handle; - (void)text_code; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCharacterGetTextOrder(FontTextCharacter text_character, void** text_order) { - (void)text_character; - (void)text_order; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -uint32_t APS5_VABI sceFontCharacterLooksFormatCharacters(FontTextCharacter text_character) { - (void)text_character; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -uint32_t APS5_VABI sceFontCharacterLooksWhiteSpace(FontTextCharacter text_character) { - (void)text_character; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -FontTextCharacter APS5_VABI sceFontCharacterRefersTextBack(FontTextCharacter text_character) { - (void)text_character; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -FontTextCharacter APS5_VABI sceFontCharacterRefersTextNext(FontTextCharacter text_character) { - (void)text_character; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -FontTextCodes* APS5_VABI sceFontCharactersRefersTextCodes(FontTextCharacter text_character, FontTextCharacter term_character, FontTextCodes* text_codes) { - (void)text_character; - (void)term_character; - (void)text_codes; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceFontCloseFont(FontHandle font_handle) { - (void)font_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateLibrary(const FontMemory* memory, FontLibrarySelection selection, FontLibrary* library) { - (void)memory; - (void)selection; - (void)library; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateLibraryWithEdition(const FontMemory* memory, FontLibrarySelection selection, uint64_t edition, FontLibrary* library) { - (void)memory; - (void)selection; - (void)edition; - (void)library; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateRendererWithEdition(const FontMemory* memory, FontRendererSelection selection, uint64_t edition, FontRenderer* renderer) { - (void)memory; - (void)selection; - (void)edition; - (void)renderer; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateString(const FontMemory* memory, FontTextSource* font_text_source, const FontCreateStringDetail* string_detail, FontString* font_string) { - (void)memory; - (void)font_text_source; - (void)string_detail; - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontCreateWritingLine(const FontMemory* memory, int writing_form, const void* writing_line_detail, FontWritingLine* writing_line) { - (void)memory; - (void)writing_form; - (void)writing_line_detail; - (void)writing_line; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDefineAttribute(FontHandle font_handle, int attribute, int* old_attribute) { - (void)font_handle; - (void)attribute; - (void)old_attribute; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDeleteGlyph(const FontMemory* memory, void** font_glyph) { - (void)memory; - (void)font_glyph; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDestroyLibrary(FontLibrary* library) { - (void)library; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDestroyRenderer(FontRenderer* renderer) { - (void)renderer; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDestroyString(FontString* font_string) { - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontDestroyWritingLine(FontWritingLine* writing_line) { - (void)writing_line; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGenerateCharGlyph(FontHandle font_handle, uint32_t code, const FontGenerateGlyphDetail* detail, void** font_glyph) { - (void)font_handle; - (void)code; - (void)detail; - (void)font_glyph; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGetCharGlyphMetrics(FontHandle font_handle, uint32_t code, FontGlyphMetrics* metrics) { - (void)font_handle; - (void)code; - (void)metrics; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGetHorizontalLayout(FontHandle font_handle, FontHorizontalLayout* layout) { - (void)font_handle; - (void)layout; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGetRenderCharGlyphMetrics(FontHandle font_handle, uint32_t code, FontGlyphMetrics* metrics) { - (void)font_handle; - (void)code; - (void)metrics; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGetVerticalLayout(FontHandle font_handle, FontVerticalLayout* layout) { - (void)font_handle; - (void)layout; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontGlyphDefineAttribute(void* font_glyph, int attribute, int* old_attribute) { - (void)font_glyph; - (void)attribute; - (void)old_attribute; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontMemoryInit(FontMemory* font_memory, void* address, uint32_t size_byte, const FontMemoryInterface* mem_interface, void* mspace_object, FontMemoryDestroyCallback destroy_callback, void* destroy_object) { - (void)font_memory; - (void)address; - (void)size_byte; - (void)mem_interface; - (void)mspace_object; - (void)destroy_callback; - (void)destroy_object; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontMemoryTerm(FontMemory* font_memory) { - (void)font_memory; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontOpenFontInstance(FontHandle font_handle, void* setup_font, FontHandle* out_font_handle) { - (void)font_handle; - (void)setup_font; - (void)out_font_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontOpenFontMemory(FontLibrary library, const void* font_address, uint32_t font_size, const FontOpenDetail* detail, FontHandle* handle) { - (void)library; - (void)font_address; - (void)font_size; - (void)detail; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontOpenFontSet(FontLibrary library, uint32_t font_set_type, uint32_t open_mode, const FontOpenDetail* detail, FontHandle* handle) { - (void)library; - (void)font_set_type; - (void)open_mode; - (void)detail; - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontRebindRenderer(FontHandle font_handle) { - (void)font_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontRenderCharGlyphImageHorizontal(FontHandle font_handle, uint32_t code, FontRenderSurface* surf, float x, float y, FontGlyphMetrics* metrics, FontRenderResult* result) { - (void)font_handle; - (void)code; - (void)surf; - (void)x; - (void)y; - (void)metrics; - (void)result; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -void APS5_VABI sceFontRenderSurfaceInit(FontRenderSurface* surf, void* buffer, int buf_width_byte, int pixel_size_byte, int width, int height) { - (void)surf; - (void)buffer; - (void)buf_width_byte; - (void)pixel_size_byte; - (void)width; - (void)height; - NotImplemented_nid_no_patch(__func__); -} - -void APS5_VABI sceFontRenderSurfaceSetScissor(FontRenderSurface* surf, uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1) { - (void)surf; - (void)x0; - (void)y0; - (void)x1; - (void)y1; - NotImplemented_nid_no_patch(__func__); -} - -int APS5_VABI sceFontSetEffectSlant(FontHandle font_handle, float slant_ratio) { - (void)font_handle; - (void)slant_ratio; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetEffectWeight(FontHandle font_handle, float weight_x_scale, float weight_y_scale, uint32_t mode) { - (void)font_handle; - (void)weight_x_scale; - (void)weight_y_scale; - (void)mode; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetScalePixel(FontHandle font_handle, float w, float h) { - (void)font_handle; - (void)w; - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetupRenderEffectSlant(FontHandle font_handle, float slant_ratio) { - (void)font_handle; - (void)slant_ratio; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetupRenderEffectWeight(FontHandle font_handle, float weight_x_scale, float weight_y_scale, uint32_t mode) { - (void)font_handle; - (void)weight_x_scale; - (void)weight_y_scale; - (void)mode; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSetupRenderScalePixel(FontHandle font_handle, float w, float h) { - (void)font_handle; - (void)w; - (void)h; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -uint32_t APS5_VABI sceFontStringGetTerminateCode(FontString font_string) { - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -void* APS5_VABI sceFontStringGetTerminateOrder(FontString font_string) { - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceFontStringGetWritingForm(FontString font_string) { - (void)font_string; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -FontRenderCharacter APS5_VABI sceFontStringRefersRenderCharacters(FontString font_string, FontTextCharacter start_character, FontTextCharacter last_character, uint32_t* character_count) { - (void)font_string; - (void)start_character; - (void)last_character; - (void)character_count; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -FontTextCharacter APS5_VABI sceFontStringRefersTextCharacters(FontString font_string, uint32_t* character_count) { - (void)font_string; - (void)character_count; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -int APS5_VABI sceFontSupportExternalFonts(FontLibrary library, uint32_t font_max, uint32_t formats) { - (void)library; - (void)font_max; - (void)formats; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontSupportSystemFonts(FontLibrary library) { - (void)library; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -FontTextCodes* APS5_VABI sceFontTextCodesStepBack(FontTextCodes* text_codes_step) { - (void)text_codes_step; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -FontTextCodes* APS5_VABI sceFontTextCodesStepNext(FontTextCodes* text_codes_step) { - (void)text_codes_step; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceFontTextSourceInit(FontTextSource* font_text_source, const void* text_address, uint32_t text_size_byte, FontTextParseFunction text_parser, void* text_object) { - (void)font_text_source; - (void)text_address; - (void)text_size_byte; - (void)text_parser; - (void)text_object; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontTextSourceRewind(FontTextSource* font_text_source) { - (void)font_text_source; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontTextSourceSetDefaultFont(FontTextSource* font_text_source, FontHandle default_font) { - (void)font_text_source; - (void)default_font; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontTextSourceSetWritingForm(FontTextSource* font_text_source, int writing_form) { - (void)font_text_source; - (void)writing_form; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontUnbindRenderer(FontHandle font_handle) { - (void)font_handle; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingGetRenderMetrics(FontWriting* font_writing, FontWritingMetrics* writing_metrics) { - (void)font_writing; - (void)writing_metrics; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingInit(FontWriting* font_writing, FontString font_string, FontRenderCharacter font_character) { - (void)font_writing; - (void)font_string; - (void)font_character; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingLineClear(FontWritingLine writing_line) { - (void)writing_line; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingLineGetOrderingSpace(FontWritingLine writing_line, float* head_space, float* inline_space, float* tail_space, float* advance_space) { - (void)writing_line; - (void)head_space; - (void)inline_space; - (void)tail_space; - (void)advance_space; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceFontWritingLineGetRenderMetrics(FontWritingLine writing_line, FontWritingMetrics* writing_metrics) { - (void)writing_line; - (void)writing_metrics; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -FontWritingLineStep* APS5_VABI sceFontWritingLineRefersRenderStep(FontWritingLine writing_line) { - (void)writing_line; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -int APS5_VABI sceFontWritingLineWritesOrder(FontWritingLine writing_line, uint64_t writing_attribute, const FontWritingMetrics* writing_metrics, void* writing_orderer) { - (void)writing_line; - (void)writing_attribute; - (void)writing_metrics; - (void)writing_orderer; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* font_writing) { - (void)font_writing; - NotImplemented_nid_no_patch(__func__); - return nullptr; -} - -FontTextCharacter APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting* font_writing, const void** letter_step) { - (void)font_writing; - (void)letter_step; - NotImplemented_nid_no_patch(__func__); - return {}; -} - -int APS5_VABI sceFontWritingSetMaskInvisible(FontWriting* font_writing, int mask) { - (void)font_writing; - (void)mask; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -} diff --git a/core/libs/prx/libSceFont/FontLibrary.cmake b/core/libs/prx/libSceFont/FontLibrary.cmake new file mode 100644 index 000000000..26e287f39 --- /dev/null +++ b/core/libs/prx/libSceFont/FontLibrary.cmake @@ -0,0 +1,21 @@ +function(add_sce_font_library target) + set(fontDir ${CMAKE_CURRENT_FUNCTION_LIST_DIR}) + add_library(${target} SHARED EXCLUDE_FROM_ALL + ${fontDir}/src/Layout.cpp + ${fontDir}/src/Library.cpp + ${fontDir}/src/Open.cpp + ${fontDir}/src/Render.cpp + ${fontDir}/src/State.cpp + ${fontDir}/src/Style.cpp + ${fontDir}/src/Text.cpp + ${fontDir}/src/Unimplemented.cpp + ) + target_include_directories(${target} PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(${target} PRIVATE freetype libc) + set_target_properties(${target} PROPERTIES + CXX_EXTENSIONS OFF + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + ) + configure_windows_unwind(${target}) +endfunction() diff --git a/core/libs/prx/libSceFont/include/FontDriver.hpp b/core/libs/prx/libSceFont/include/FontDriver.hpp new file mode 100644 index 000000000..e58ae256e --- /dev/null +++ b/core/libs/prx/libSceFont/include/FontDriver.hpp @@ -0,0 +1,360 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTDRIVER_HPP +#define CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTDRIVER_HPP + +#include +#include +#include +#include + +#include "prx/libSceFont/include/FontTypes.hpp" + +namespace Font { + +constexpr std::uint16_t MEMORY_MAGIC = 0x0F00; +constexpr std::uint16_t LIBRARY_MAGIC = 0x0F01; +constexpr std::uint16_t HANDLE_MAGIC = 0x0F02; +constexpr std::uint16_t GLYPH_MAGIC = 0x0F03; +constexpr std::uint16_t RENDERER_MAGIC = 0x0F07; +constexpr std::uint16_t STYLE_FRAME_MAGIC = 0x0F09; + +constexpr std::uint32_t LOCK_BIT = 0x80000000u; +constexpr std::uint32_t OPEN_BIT = 0x40000000u; +constexpr std::uint32_t COUNT_MASK = 0x0FFFFFFFu; + +constexpr std::size_t HORIZONTAL_LAYOUT_SIZE = 0x30; +constexpr std::size_t HORIZONTAL_LINE_ADVANCE = 0x00; +constexpr std::size_t HORIZONTAL_BASELINE = 0x04; +constexpr std::size_t HORIZONTAL_X_BOUND_LO = 0x08; +constexpr std::size_t HORIZONTAL_X_BOUND_HI = 0x0C; +constexpr std::size_t HORIZONTAL_MAX_ADVANCE_WIDTH = 0x10; +constexpr std::size_t HORIZONTAL_CARET_RISE_ADJUST = 0x14; +constexpr std::size_t HORIZONTAL_EFFECT_HEIGHT = 0x18; +constexpr std::size_t HORIZONTAL_HALF_EFFECT_WIDTH = 0x1C; +constexpr std::size_t HORIZONTAL_LEFT_ADJUST = 0x20; + +constexpr std::size_t VERTICAL_LAYOUT_SIZE = 0x20; +constexpr std::size_t VERTICAL_COLUMN_ADVANCE = 0x00; +constexpr std::size_t VERTICAL_BASELINE_OFFSET_X = 0x04; +constexpr std::size_t VERTICAL_METRICS_0X08 = 0x08; +constexpr std::size_t VERTICAL_METRICS_0X0C = 0x0C; +constexpr std::size_t VERTICAL_ADVANCE_HEIGHT = 0x10; +constexpr std::size_t VERTICAL_DECORATION_SPAN = 0x14; +constexpr std::size_t VERTICAL_DECORATION_0X08 = 0x18; +constexpr std::size_t VERTICAL_DECORATION_0X0C = 0x1C; + +inline float LoadFloat(const std::uint8_t* base, std::size_t offset) { + float value; + std::memcpy(&value, base + offset, sizeof(value)); + return value; +} + +inline void StoreFloat(std::uint8_t* base, std::size_t offset, float value) { + std::memcpy(base + offset, &value, sizeof(value)); +} + +struct StyleStateBlock { + std::uint32_t dpi_x; + std::uint32_t dpi_y; + std::uint32_t scale_unit; + std::uint32_t reserved_0x0c; + float scale_w; + float scale_h; + float effect_weight_x; + float effect_weight_y; + float slant_ratio; + float reserved_0x24; +}; + +struct CachedStyle { + StyleStateBlock state; + std::uint32_t reserved_0x28; + std::uint32_t layout_cache_state; + std::uint32_t cache_flags_and_direction; + std::uint32_t cache_lock_word; + std::uint8_t layout_cache_bytes[0x20]; + std::uint32_t reserved_0x58; + std::uint32_t cached_scalar_bits; +}; + +struct FontHandleOpenInfo { + std::uint32_t unique_id_packed; + std::uint32_t ctx_entry_index; + std::uint32_t sub_font_index; + std::uint32_t fontset_flags; + const void* fontset_record; + std::uint64_t reserved_0x18; +}; + +struct FontHandleNative { + std::uint16_t magic; + std::uint16_t flags; + std::uint32_t lock_word; + FontHandleOpenInfo open_info; + FontLibrary library; + void* renderer; + std::uint8_t reserved_0x38[0x08]; + StyleStateBlock style; + CachedStyle cached_style; + std::uint16_t metricA; + std::uint16_t metricB; + std::uint8_t reserved_0xcc[0x1C]; + std::uint8_t reserved_0xe8[0x08]; + FontHandle prevFont; + FontHandle nextFont; +}; + +struct FontObj { + std::uint32_t refcount; + std::uint32_t reserved_0x04; + std::uint32_t reserved_0x08; + std::uint32_t sub_font_index; + FontObj* prev; + FontObj* next; + std::uint8_t reserved_0x20[0x08]; + void* open_ctx_0x28; + void* ft_face; + FontHandle font_handle; + std::int32_t shift_units_x; + std::int32_t shift_units_y; + std::uint64_t layout_seed_pair; + float scale_x_0x50; + float scale_y_0x54; + void* ft_ctx_0x58; + std::uint8_t reserved_0x60[0x04]; + std::int32_t cached_glyph_index_0x64; + std::uint64_t cached_units_x_0x68; + std::uint64_t cached_units_y_0x70; + std::int64_t shift_cache_x; + std::int64_t shift_cache_y; + std::array layout_seed_vec; + std::array layout_scale_vec; + std::uint8_t reserved_0xA8[0x130 - 0xA8]; + std::uint32_t glyph_cfg_word_0x130; + std::uint8_t glyph_cfg_mode_0x134; + std::uint8_t glyph_cfg_byte_0x135; + std::uint8_t glyph_cfg_byte_0x136; + std::uint8_t reserved_0x137[0x200 - 0x137]; +}; + +struct FontCtxHeader { + std::uint32_t lock_word; + std::uint32_t max_entries; + void* base; + std::uint8_t reserved_0x10[0x10]; +}; + +struct FontCtxEntry { + std::uint32_t reserved_0x00; + std::uint32_t active; + std::uint64_t font_address; + std::uint32_t unique_id; + std::uint32_t lock_mode1; + std::uint32_t lock_mode3; + std::uint32_t lock_mode2; + void* obj_mode1; + void* obj_mode3; + void* obj_mode2; + std::uint8_t reserved_0x38[0x08]; +}; + +struct SysDriver; + +struct FontLibNative { + std::uint16_t magic; + std::uint16_t reserved0; + std::uint32_t lock_word; + std::uint32_t flags; + std::uint32_t feature_word; + std::uint32_t mem_kind; + std::uint32_t region_size; + void* region_base; + void* alloc_ctx; + const FontMemoryInterface* iface; + std::uint8_t reserved_0x30[0x20]; + FontAllocFunction alloc_fn; + FontFreeFunction dealloc_fn; + FontReallocFunction realloc_fn; + FontCallocFunction calloc_fn; + std::uint8_t reserved_0x70[0x10]; + const SysDriver* sys_driver; + void* fontset_registry; + std::uint8_t reserved_0x90[0x10]; + void* sysfonts_ctx; + void* external_fonts_ctx; + std::uint32_t* device_cache_buf; + std::uint8_t reserved_0xb8[0x04]; + std::uint32_t workspace_size; + void* workspace; + void* sysfont_desc_ptr; + std::uint64_t sysfont_flags; + FontHandle* list_head_ptr; + FontHandle list_head; + std::uint8_t reserved_0xe8[0x18]; +}; + +using DriverPixelResolutionFunction = std::uint32_t (APS5_VABI*)(); +using DriverInitFunction = int (APS5_VABI*)(const FontMemory* memory, FontLibNative* library); +using DriverTermFunction = int (APS5_VABI*)(FontLibNative* library); +using DriverSupportFunction = int (APS5_VABI*)(FontLibNative* library, std::uint32_t formats); +using DriverOpenFunction = int (APS5_VABI*)(FontLibNative* library, std::uint32_t mode, const void* fontAddress, std::uint32_t fontSize, std::uint32_t subFontIndex, std::uint32_t uniqueWord, FontObj** inoutFontObj); +using DriverCloseFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t flags); +using DriverScaleFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint16_t* outUnitsPerEm, float* outScale); +using DriverMetricFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t metricId, std::uint16_t* outMetric); +using DriverGlyphsCountFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t* outCount); +using DriverGlyphIndexFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t codepoint, std::uint32_t* outGlyphIndex); +using DriverSetCharWithDpiFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t dpiX, std::uint32_t dpiY, float scaleX, float scaleY, float* outScaleX, float* outScaleY); +using DriverSetCharDefaultDpiFunction = int (APS5_VABI*)(FontObj* fontObj, float scaleX, float scaleY, float* outScaleX, float* outScaleY); +using DriverComputeLayoutFunction = int (APS5_VABI*)(FontObj* fontObj, const StyleStateBlock* style, std::uint8_t* outWords); +using DriverLoadGlyphCachedFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t glyphIndex, std::int32_t mode, std::uint64_t* outWords); +using DriverGetGlyphMetricsFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t* optParam2, std::uint8_t mode, std::uint8_t* outParams, FontGlyphMetrics* outMetrics); +using DriverApplyGlyphAdjustFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t p2, std::uint32_t glyphIndex, std::int32_t p4, std::int32_t p5, std::uint32_t* inoutGlyphIndex); +using DriverConfigureGlyphFunction = int (APS5_VABI*)(FontObj* fontObj, std::uint32_t* inParams, std::int32_t mode, std::uint32_t* inoutState); + +struct SysDriver { + std::uint32_t magic; + std::uint32_t reserved; + void* reserved_ptr1; + DriverPixelResolutionFunction pixel_resolution; + DriverInitFunction init; + DriverTermFunction term; + DriverSupportFunction support_formats; + std::uint8_t reserved_0x30[0x08]; + DriverOpenFunction open; + DriverCloseFunction close; + std::uint8_t reserved_0x48[0x08]; + DriverScaleFunction scale; + std::uint8_t reserved_0x58[0x08]; + DriverMetricFunction metric; + DriverGlyphsCountFunction glyphs_count; + std::uint8_t reserved_0x70[0x08]; + DriverGlyphIndexFunction glyph_index; + DriverSetCharWithDpiFunction set_char_with_dpi; + DriverSetCharDefaultDpiFunction set_char_default_dpi; + std::uint8_t reserved_0x90[0x10]; + DriverComputeLayoutFunction compute_layout; + DriverLoadGlyphCachedFunction load_glyph_cached; + std::uint8_t reserved_0xb0[0x08]; + DriverGetGlyphMetricsFunction get_glyph_metrics; + std::uint8_t reserved_0xc0[0x20]; + DriverApplyGlyphAdjustFunction apply_glyph_adjust; + std::uint8_t reserved_0xe8[0x20]; + DriverConfigureGlyphFunction configure_glyph; + std::uint8_t reserved_0x110[0x08]; + DriverComputeLayoutFunction compute_layout_alt; +}; + +struct RendererNative { + std::uint16_t magic; + std::uint16_t reserved02; + std::uint8_t reserved_04[0x08]; + std::uint8_t feature_byte_0x0c; + std::uint8_t reserved_0d[0x03]; + std::uint32_t mem_kind; + std::uint32_t region_size; + void* region_base; + void* alloc_ctx; + const FontMemoryInterface* mem_iface; + std::uint8_t reserved_30[0x20]; + FontAllocFunction alloc_fn; + FontFreeFunction free_fn; + FontReallocFunction realloc_fn; + FontCallocFunction calloc_fn; + std::uint8_t reserved_70[0x10]; + void* selection; + std::uint8_t reserved_88[0x08]; + std::uint64_t outline_magic_0x90; + void* workspace; + std::uint64_t workspace_size; + std::uint32_t reserved_a8; + std::uint32_t outline_policy_flag; +}; + +struct RendererFtBackend { + void* renderer_header_0x10; + std::uintptr_t unknown_0x08; + std::uintptr_t unknown_0x10; + std::uintptr_t unknown_0x18; + void* initialized_marker; + std::uint8_t reserved_0x28[0x20]; +}; + +struct RendererFt { + RendererNative base; + std::uint8_t reserved_0x0B0[0x70]; + RendererFtBackend ft_backend; +}; + +using RendererCreateFunction = int (APS5_VABI*)(RendererNative* renderer); +using RendererDestroyFunction = int (APS5_VABI*)(RendererNative* renderer); +using RendererQueryFunction = std::uint64_t (APS5_VABI*)(RendererNative* renderer, std::uint8_t* params, std::int64_t* outPtr, std::uint8_t* outVector); + +struct RendererSelection { + std::uint32_t magic; + std::uint32_t size; + RendererCreateFunction create_fn; + RendererDestroyFunction destroy_fn; + RendererQueryFunction query_fn; +}; + +static_assert(sizeof(StyleStateBlock) == 0x28); +static_assert(offsetof(StyleStateBlock, scale_w) == 0x10); +static_assert(offsetof(StyleStateBlock, slant_ratio) == 0x20); +static_assert(sizeof(CachedStyle) == 0x60); +static_assert(offsetof(CachedStyle, layout_cache_state) == 0x2C); +static_assert(offsetof(CachedStyle, cache_lock_word) == 0x34); +static_assert(offsetof(CachedStyle, layout_cache_bytes) == 0x38); +static_assert(offsetof(CachedStyle, cached_scalar_bits) == 0x5C); +static_assert(sizeof(FontHandleOpenInfo) == 0x20); +static_assert(sizeof(FontHandleNative) == 0x100); +static_assert(offsetof(FontHandleNative, open_info) == 0x08); +static_assert(offsetof(FontHandleNative, library) == 0x28); +static_assert(offsetof(FontHandleNative, renderer) == 0x30); +static_assert(offsetof(FontHandleNative, style) == 0x40); +static_assert(offsetof(FontHandleNative, cached_style) == 0x68); +static_assert(offsetof(FontHandleNative, metricA) == 0xC8); +static_assert(offsetof(FontHandleNative, prevFont) == 0xF0); +static_assert(sizeof(FontObj) == 0x200); +static_assert(offsetof(FontObj, ft_face) == 0x30); +static_assert(offsetof(FontObj, shift_cache_x) == 0x78); +static_assert(offsetof(FontObj, layout_scale_vec) == 0x98); +static_assert(offsetof(FontObj, glyph_cfg_word_0x130) == 0x130); +static_assert(sizeof(FontCtxHeader) == 0x20); +static_assert(sizeof(FontCtxEntry) == 0x40); +static_assert(offsetof(FontCtxEntry, obj_mode2) == 0x30); +static_assert(sizeof(FontLibNative) == 0x100); +static_assert(offsetof(FontLibNative, alloc_ctx) == 0x20); +static_assert(offsetof(FontLibNative, iface) == 0x28); +static_assert(offsetof(FontLibNative, alloc_fn) == 0x50); +static_assert(offsetof(FontLibNative, sys_driver) == 0x80); +static_assert(offsetof(FontLibNative, fontset_registry) == 0x88); +static_assert(offsetof(FontLibNative, sysfonts_ctx) == 0xA0); +static_assert(offsetof(FontLibNative, external_fonts_ctx) == 0xA8); +static_assert(offsetof(FontLibNative, device_cache_buf) == 0xB0); +static_assert(offsetof(FontLibNative, workspace) == 0xC0); +static_assert(offsetof(FontLibNative, sysfont_flags) == 0xD0); +static_assert(offsetof(FontLibNative, list_head_ptr) == 0xD8); +static_assert(offsetof(FontLibNative, list_head) == 0xE0); +static_assert(sizeof(SysDriver) == 0x120); +static_assert(offsetof(SysDriver, pixel_resolution) == 0x10); +static_assert(offsetof(SysDriver, open) == 0x38); +static_assert(offsetof(SysDriver, scale) == 0x50); +static_assert(offsetof(SysDriver, glyph_index) == 0x78); +static_assert(offsetof(SysDriver, compute_layout) == 0xA0); +static_assert(offsetof(SysDriver, get_glyph_metrics) == 0xB8); +static_assert(offsetof(SysDriver, apply_glyph_adjust) == 0xE0); +static_assert(offsetof(SysDriver, configure_glyph) == 0x108); +static_assert(offsetof(SysDriver, compute_layout_alt) == 0x118); +static_assert(sizeof(RendererNative) == 0xB0); +static_assert(offsetof(RendererNative, alloc_fn) == 0x50); +static_assert(offsetof(RendererNative, selection) == 0x80); +static_assert(offsetof(RendererNative, outline_policy_flag) == 0xAC); +static_assert(sizeof(RendererFtBackend) == 0x48); +static_assert(sizeof(RendererFt) == 0x168); +static_assert(offsetof(RendererFt, ft_backend) == 0x120); + +} + +#endif diff --git a/core/libs/prx/libSceFont/include/FontFreeType.hpp b/core/libs/prx/libSceFont/include/FontFreeType.hpp new file mode 100644 index 000000000..eeef92b9f --- /dev/null +++ b/core/libs/prx/libSceFont/include/FontFreeType.hpp @@ -0,0 +1,151 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTFREETYPE_HPP +#define CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTFREETYPE_HPP + +#include +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H + +namespace Font { + +constexpr float ONE_OVER_64 = 1.0f / 64.0f; + +inline std::int32_t TruncateFloatToInt(float value) { + if (!std::isfinite(value) || value > 2147483647.0f || value < -2147483648.0f) return std::numeric_limits::min(); + return static_cast(value); +} + +inline std::int32_t RoundMulFixed(std::int64_t value, std::int64_t fixed16) { + const std::int64_t product = value * fixed16; + const std::int64_t signAdjust = ((~value) >> 63) * -0x10000LL; + const std::int64_t base = signAdjust + product; + std::int64_t rounded = base - 0x8000LL; + if (rounded < 0) rounded = base + 0x7FFFLL; + return static_cast(static_cast(rounded) >> 16); +} + +inline std::int32_t TruncFixed(std::int64_t fixed16) { + std::int64_t value = fixed16; + if (fixed16 < 0) value = fixed16 + 0xFFFFLL; + return static_cast(value >> 16); +} + +inline std::int64_t TruncMulUnits(std::int64_t fixed16, std::uint16_t units) { + const std::int64_t product = fixed16 * static_cast(units); + std::int64_t rounded = product; + if (product < 0) rounded = product + 0xFFFFLL; + return static_cast(static_cast(rounded >> 16)); +} + +inline float FixedMulUnitsToF26Dot6(std::int64_t fixed16, std::uint16_t unitsPerEm) { + const std::int64_t product = fixed16 * static_cast(unitsPerEm); + std::int64_t rounded = product + 0xFFFF; + if (product >= 0) rounded = product; + return static_cast(rounded >> 16) * ONE_OVER_64; +} + +inline int FloorToInt(float value) { + int result = static_cast(std::trunc(value)); + if (static_cast(result) > value) --result; + return result; +} + +inline int CeilToInt(float value) { + int result = static_cast(std::trunc(value)); + if (static_cast(result) < value) ++result; + return result; +} + +inline FT_UInt ResolveGlyphIndexWithFallback(FT_Face face, std::uint32_t codepoint) { + if (!face) return 0; + const auto code = static_cast(codepoint); + const FT_CharMap original = face->charmap; + FT_UInt glyphIndex = FT_Get_Char_Index(face, code); + if (glyphIndex != 0 || !face->charmaps || face->num_charmaps <= 1) return glyphIndex; + for (int i = 0; i < face->num_charmaps; ++i) { + const FT_CharMap charmap = face->charmaps[i]; + if (!charmap || charmap == original) continue; + if (FT_Set_Charmap(face, charmap) != 0) continue; + glyphIndex = FT_Get_Char_Index(face, code); + if (glyphIndex != 0) break; + } + if (original && face->charmap != original) FT_Set_Charmap(face, original); + return glyphIndex; +} + +inline FT_F26Dot6 ClampPpem(FT_F26Dot6 value) { + constexpr FT_F26Dot6 maxPpem = static_cast(65535 * 64); + return std::clamp(value, static_cast(-maxPpem), maxPpem); +} + +inline FT_Error SetCharSizeCompat(FT_Face face, FT_F26Dot6 charW, FT_F26Dot6 charH, std::uint32_t dpiX, std::uint32_t dpiY, FT_F26Dot6* usedW = nullptr, FT_F26Dot6* usedH = nullptr) { + const auto writeUsed = [&](FT_F26Dot6 w, FT_F26Dot6 h) { + if (usedW) *usedW = w; + if (usedH) *usedH = h; + }; + if (!face) { + writeUsed(charW, charH); + return FT_Err_Invalid_Face_Handle; + } + FT_Error error = FT_Set_Char_Size(face, charW, charH, dpiX, dpiY); + if (error == 0) { + writeUsed(charW, charH); + return 0; + } + const FT_F26Dot6 clampedW = ClampPpem(charW); + const FT_F26Dot6 clampedH = ClampPpem(charH); + if (clampedW != charW || clampedH != charH) { + error = FT_Set_Char_Size(face, clampedW, clampedH, dpiX, dpiY); + if (error == 0) { + writeUsed(clampedW, clampedH); + return 0; + } + } + if (face->num_fixed_sizes <= 0 || !face->available_sizes) { + writeUsed(charW, charH); + return error; + } + const double requestW = static_cast(std::llabs(static_cast(charW))) / 64.0; + const double requestH = static_cast(std::llabs(static_cast(charH))) / 64.0; + const double targetW = requestW > 0.0 ? requestW : requestH; + const double targetH = requestH > 0.0 ? requestH : requestW; + if (targetW <= 0.0 || targetH <= 0.0) { + writeUsed(charW, charH); + return error; + } + int bestIndex = -1; + double bestScore = std::numeric_limits::infinity(); + for (int i = 0; i < face->num_fixed_sizes; ++i) { + const FT_Bitmap_Size& strike = face->available_sizes[i]; + const double strikeW = std::max(1.0, static_cast(strike.x_ppem) / 64.0); + const double strikeH = std::max(1.0, static_cast(strike.y_ppem) / 64.0); + const double score = std::abs(strikeW - targetW) / targetW + std::abs(strikeH - targetH) / targetH; + if (score < bestScore) { + bestScore = score; + bestIndex = i; + } + } + if (bestIndex < 0) { + writeUsed(charW, charH); + return error; + } + error = FT_Select_Size(face, bestIndex); + if (error == 0) { + const FT_Bitmap_Size& strike = face->available_sizes[bestIndex]; + writeUsed(strike.x_ppem, strike.y_ppem); + return 0; + } + writeUsed(charW, charH); + return error; +} + +} + +#endif diff --git a/core/libs/prx/libSceFont/include/FontInternal.hpp b/core/libs/prx/libSceFont/include/FontInternal.hpp new file mode 100644 index 000000000..426a21bee --- /dev/null +++ b/core/libs/prx/libSceFont/include/FontInternal.hpp @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTINTERNAL_HPP +#define CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTINTERNAL_HPP + +#include +#include +#include + +#include +#include FT_FREETYPE_H + +#include "prx/libSceFont/include/FontDriver.hpp" + +namespace Font { + +constexpr std::uint8_t STYLE_FRAME_FLAG_SCALE = 0x01; +constexpr std::uint8_t STYLE_FRAME_FLAG_SLANT = 0x02; +constexpr std::uint8_t STYLE_FRAME_FLAG_WEIGHT = 0x04; +constexpr float POINTS_PER_INCH = 72.0f; + +struct FontState { + std::vector faceData; + FT_Face face = nullptr; + float scaleW = 16.0f; + float scaleH = 16.0f; + + FontState() = default; + FontState(const FontState&) = delete; + FontState& operator=(const FontState&) = delete; + ~FontState(); +}; + +struct GeneratedGlyph { + FontGlyphOpaque glyph{}; + FontGlyphMetrics metrics{}; + FontGlyphMetricsHorizontal metricsHorizontal{}; + FontGlyphMetricsHorizontalX metricsHorizontalX{}; + FontGlyphMetricsHorizontalAdvance metricsHorizontalAdvance{}; + std::uint32_t codepoint = 0; + FontHandle owner = nullptr; + FontGlyphOutline outline{}; + std::vector outlinePoints; + std::vector outlineTags; + std::vector outlineContours; + bool metricsInitialized = false; + bool outlineInitialized = false; +}; + +struct RenderSurfaceSystemUse { + FontStyleFrame* styleframe; + float catchedScale; +}; + +void Backoff(); +std::uint32_t AcquireWordLock(std::uint32_t& word); +FontHandleNative* GetNativeFont(FontHandle handle); +bool AcquireLibraryLock(FontLibNative* library, std::uint32_t& previous); +void ReleaseLibraryLock(FontLibNative* library, std::uint32_t previous); +bool AcquireFontLock(FontHandleNative* font, std::uint32_t& previous); +void ReleaseFontLock(FontHandleNative* font, std::uint32_t previous); +bool AcquireCachedStyleLock(FontHandleNative* font, std::uint32_t& previous); +void ReleaseCachedStyleLock(FontHandleNative* font, std::uint32_t previous); +void LinkFontToLibrary(FontLibNative* library, FontHandle handle); +void UnlinkFontFromLibrary(FontLibNative* library, FontHandle handle); + +std::uint32_t* EntryLockWord(FontCtxEntry* entry, std::uint32_t modeLow); +void** EntryObjectSlot(FontCtxEntry* entry, std::uint32_t modeLow); +FontCtxEntry* AcquireFontCtxEntry(void* ctx, std::uint32_t index, std::uint32_t modeLow, FontObj** outObject, std::uint32_t* outLockWord); +void ReleaseFontCtxEntryLock(FontCtxEntry* entry, std::uint32_t modeLow, std::uint32_t lockWord); +FontObj* FindSubFont(FontObj* head, std::uint32_t subFontIndex); +void ReleaseFontObjectsForHandle(FontHandleNative* font); + +FontState* TryGetState(FontHandle handle); +FontState& ResetState(FontHandle handle); +void RemoveState(FontHandle handle); +void LoadStateFace(FontState& state, std::vector bytes, std::uint32_t subFontIndex); + +void TrackGeneratedGlyph(FontGlyph glyph); +bool ForgetGeneratedGlyph(FontGlyph glyph); +GeneratedGlyph* TryGetGeneratedGlyph(FontGlyph glyph); +void PopulateGlyphMetricVariants(GeneratedGlyph& glyph); +void BuildBoundingOutline(GeneratedGlyph& glyph); +bool BuildTrueOutline(GeneratedGlyph& glyph); + +int StyleStateSetScalePixel(StyleStateBlock* style, float w, float h); +int StyleStateSetScalePoint(StyleStateBlock* style, float w, float h); +int StyleStateGetScalePixel(const StyleStateBlock* style, float* w, float* h); +int StyleStateGetScalePoint(const StyleStateBlock* style, float* w, float* h); +int StyleStateSetDpi(StyleStateBlock* style, std::uint32_t hDpi, std::uint32_t vDpi); +int StyleStateSetSlantRatio(StyleStateBlock* style, float slantRatio); +int StyleStateGetSlantRatio(const StyleStateBlock* style, float* slantRatio); +int StyleStateSetWeightScale(StyleStateBlock* style, float weightXScale, float weightYScale); +int StyleStateGetWeightScale(const StyleStateBlock* style, float* weightXScale, float* weightYScale, std::uint32_t* mode); +bool ValidStyleFrame(const FontStyleFrame* frame); + +std::uint8_t CachedStyleCacheFlags(const CachedStyle& style); +void CachedStyleSetCacheFlags(CachedStyle& style, std::uint8_t flags); +void CachedStyleSetDirectionWord(CachedStyle& style, std::uint16_t word); +float CachedStyleGetScalar(const CachedStyle& style); +void CachedStyleSetScalar(CachedStyle& style, float value); + +void ClearRenderOutputs(FontGlyphMetrics* metrics, FontRenderOutput* result); +int ComputeHorizontalLayout(FontHandle handle, const StyleStateBlock* style, std::uint8_t* outWords); +int ComputeVerticalLayout(FontHandle handle, const StyleStateBlock* style, std::uint8_t* outWords); +int GetCharGlyphMetrics(FontHandle handle, std::uint32_t code, FontGlyphMetrics* metrics, bool useCachedStyle); +int RenderCharGlyphImageCore(FontHandle handle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result); + +} + +#endif diff --git a/core/libs/prx/libSceFont/include/FontTypes.hpp b/core/libs/prx/libSceFont/include/FontTypes.hpp new file mode 100644 index 000000000..d65cb4b6c --- /dev/null +++ b/core/libs/prx/libSceFont/include/FontTypes.hpp @@ -0,0 +1,432 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTTYPES_HPP +#define CORE_LIBS_PRX_LIBSCEFONT_INCLUDE_FONTTYPES_HPP + +#include +#include + +#include "prx/libc/include/General.hpp" + +constexpr int SCE_FONT_OK = 0; +constexpr int SCE_FONT_ERROR_FATAL = static_cast(0x80460001); +constexpr int SCE_FONT_ERROR_INVALID_PARAMETER = static_cast(0x80460002); +constexpr int SCE_FONT_ERROR_INVALID_MEMORY = static_cast(0x80460003); +constexpr int SCE_FONT_ERROR_INVALID_LIBRARY = static_cast(0x80460004); +constexpr int SCE_FONT_ERROR_INVALID_FONT_HANDLE = static_cast(0x80460005); +constexpr int SCE_FONT_ERROR_INVALID_GLYPH = static_cast(0x80460006); +constexpr int SCE_FONT_ERROR_INVALID_RENDERER = static_cast(0x80460007); +constexpr int SCE_FONT_ERROR_INVALID_TEXT_SOURCE = static_cast(0x80460008); +constexpr int SCE_FONT_ERROR_INVALID_STRING = static_cast(0x80460009); +constexpr int SCE_FONT_ERROR_INVALID_WRITING = static_cast(0x8046000A); +constexpr int SCE_FONT_ERROR_INVALID_WORDS = static_cast(0x8046000B); +constexpr int SCE_FONT_ERROR_ALLOCATION_FAILED = static_cast(0x80460010); +constexpr int SCE_FONT_ERROR_FS_OPEN_FAILED = static_cast(0x80460011); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_LIBRARY = static_cast(0x80460018); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_FORMAT = static_cast(0x80460019); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_FUNCTION = static_cast(0x80460020); +constexpr int SCE_FONT_ERROR_ALREADY_SPECIFIED = static_cast(0x80460021); +constexpr int SCE_FONT_ERROR_ALREADY_ATTACHED = static_cast(0x80460022); +constexpr int SCE_FONT_ERROR_ALREADY_OPENED = static_cast(0x80460023); +constexpr int SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER = static_cast(0x80460025); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_FONTSET = static_cast(0x80460031); +constexpr int SCE_FONT_ERROR_FONT_OPEN_MAX = static_cast(0x80460033); +constexpr int SCE_FONT_ERROR_FONT_OPEN_FAILED = static_cast(0x80460036); +constexpr int SCE_FONT_ERROR_FONT_CLOSE_FAILED = static_cast(0x80460037); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY = static_cast(0x80460040); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_CODE = static_cast(0x80460041); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_GLYPH = static_cast(0x80460042); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_SCRIPT = static_cast(0x80460043); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_LANGUAGE = static_cast(0x80460044); +constexpr int SCE_FONT_ERROR_NO_SUPPORT_SURFACE = static_cast(0x80460050); +constexpr int SCE_FONT_ERROR_UNSET_PARAMETER = static_cast(0x80460058); +constexpr int SCE_FONT_ERROR_FUNCTIONAL_LIMIT = static_cast(0x8046005C); +constexpr int SCE_FONT_ERROR_ALREADY_BOUND_RENDERER = static_cast(0x80460060); +constexpr int SCE_FONT_ERROR_NOT_BOUND_RENDERER = static_cast(0x80460061); +constexpr int SCE_FONT_ERROR_RENDERER_ALLOCATION_FAILED = static_cast(0x80460063); +constexpr int SCE_FONT_ERROR_RENDERER_ALLOCATION_LIMITED = static_cast(0x80460064); +constexpr int SCE_FONT_ERROR_RENDERER_RENDER_FAILED = static_cast(0x80460065); + +struct FontHandleOpaque { + std::uint32_t reserved[64]; +}; + +using FontHandle = FontHandleOpaque*; +using FontLibrary = void*; +using FontRenderer = void*; + +struct FontMemory; + +using FontAllocFunction = void* (APS5_VABI*)(void* object, std::uint32_t size); +using FontFreeFunction = void (APS5_VABI*)(void* object, void* p); +using FontReallocFunction = void* (APS5_VABI*)(void* object, void* p, std::uint32_t newSize); +using FontCallocFunction = void* (APS5_VABI*)(void* object, std::uint32_t nBlock, std::uint32_t size); +using FontMspaceCreateFunction = void* (APS5_VABI*)(void* parent, const char* name, void* address, std::uint32_t size, std::uint32_t attr); +using FontMspaceDestroyFunction = void (APS5_VABI*)(void* parent, void* mspace); +using FontMemoryDestroyFunction = void (APS5_VABI*)(FontMemory* fontMemory, void* object, void* destroyArg); + +struct FontMemoryInterface { + FontAllocFunction alloc; + FontFreeFunction dealloc; + FontReallocFunction realloc_fn; + FontCallocFunction calloc_fn; + FontMspaceCreateFunction mspace_create; + FontMspaceDestroyFunction mspace_destroy; +}; + +struct FontMemory { + std::uint16_t mem_kind; + std::uint16_t attr_bits; + std::uint32_t region_size; + void* region_base; + void* mspace_handle; + const FontMemoryInterface* iface; + FontMemoryDestroyFunction on_destroy; + void* destroy_ctx; + void* some_ctx1; + void* some_ctx2; +}; + +struct FontOpenDetail { + std::uint16_t tag; + std::uint16_t pad16; + std::uint32_t flags; + std::uint32_t subfont_index; + std::int32_t unique_id; + const void* reserved_ptr2; + const void* reserved_ptr1; +}; + +struct FontGlyphMetrics { + float width; + float height; + struct { + float bearingX; + float bearingY; + float advance; + } Horizontal; + struct { + float bearingX; + float bearingY; + float advance; + } Vertical; +}; + +struct FontGlyphMetricsHorizontal { + float width; + float height; + struct { + float bearing_x; + float bearing_y; + float advance; + } horizontal; +}; + +struct FontGlyphMetricsHorizontalX { + float width; + struct { + float bearing_x; + float advance; + } horizontal; +}; + +struct FontGlyphMetricsHorizontalAdvance { + struct { + float advance; + } horizontal; +}; + +struct FontKerning { + float offsetX; + float offsetY; + float positionX; + float positionY; +}; + +struct FontGlyphImageMetrics { + float bearingX; + float bearingY; + float advance; + float stride; + std::uint32_t width; + std::uint32_t height; +}; + +struct FontGenerateGlyphDetail { + std::uint16_t id; + std::uint16_t res0; + std::uint16_t form_options; + std::uint8_t glyph_form; + std::uint8_t metrics_form; + const FontMemory* mem; + void* res1; + void* res2; +}; + +struct FontGlyphOutlinePoint { + float x; + float y; +}; + +struct FontGlyphOutline { + std::int16_t contours_cnt; + std::int16_t points_cnt; + std::uint32_t outline_flags; + FontGlyphOutlinePoint* points_ptr; + std::uint8_t* tags_ptr; + std::uint16_t* contour_end_idx; +}; + +struct FontGlyphOpaque { + std::uint16_t magic; + std::uint16_t flags; + std::uint8_t glyph_form; + std::uint8_t metrics_form; + std::uint16_t em_size; + std::uint16_t baseline; + std::uint16_t height_px; + std::uint16_t origin_x; + std::uint16_t origin_y; + float scale_x; + float base_scale; + const FontMemory* memory; +}; + +using FontGlyph = FontGlyphOpaque*; + +struct FontResultStage { + std::uint8_t* p_00; + std::uint32_t u32_08; + std::uint32_t u32_0C; + std::uint32_t u32_10; +}; + +struct FontSurfaceImage { + std::uint8_t* address; + std::uint32_t widthByte; + std::uint8_t pixelSizeByte; + std::uint8_t pixelFormat; + std::uint16_t pad16; +}; + +struct FontRenderOutput { + const FontResultStage* stage; + FontSurfaceImage SurfaceImage; + struct { + std::uint32_t x; + std::uint32_t y; + std::uint32_t w; + std::uint32_t h; + } UpdateRect; + FontGlyphImageMetrics ImageMetrics; +}; + +struct FontTextCharacter { + FontTextCharacter* prev; + FontTextCharacter* next; + void* textOrder; + FontHandle font; + void* shapeEntry; + std::uint32_t characterCode; + std::uint8_t reserved_0x2c[4]; + std::uint8_t clusterSpan; + std::uint8_t clusterIndex; + std::uint8_t clusterKind; + std::int8_t synthetic; + std::uint8_t reserved_0x34[4]; + std::uint64_t flags; + std::uint8_t reserved_0x40[136]; +}; + +struct FontRenderSurface { + void* buffer; + std::int32_t widthByte; + std::int8_t pixelSizeByte; + std::uint8_t pad0; + std::uint8_t styleFlag; + std::uint8_t pad2; + std::int32_t width; + std::int32_t height; + std::uint32_t sc_x0; + std::uint32_t sc_y0; + std::uint32_t sc_x1; + std::uint32_t sc_y1; + std::uint64_t reserved_q[11]; +}; + +struct FontStyleFrame { + std::uint16_t magic; + std::uint8_t flags1; + std::uint8_t flags2; + std::uint32_t hDpi; + std::uint32_t vDpi; + std::uint32_t scaleUnit; + float baseScale; + float scalePixelW; + float scalePixelH; + float effectWeightX; + float effectWeightY; + float slantRatio; + std::uint32_t reserved_0x28; + std::uint32_t layout_cache_state; + std::uint32_t cache_flags_and_direction; + std::uint32_t cache_lock_word; + std::uint8_t layout_cache_bytes[0x20]; + std::uint32_t reserved_0x58; + std::uint32_t cached_scalar_bits; +}; + +struct FontTextSource; + +union FontTextParseResult { + void* reserved[4]; + struct { + FontHandle font; + std::uint32_t code; + } FontCode; + struct { + void* reserved; + std::uint32_t terminateCode; + } Terminate; + struct { + void* reserved; + std::int32_t errorCode; + } Error; +}; + +using FontTextParseFunction = std::int32_t (APS5_VABI*)(FontTextSource* fontTextSource, void** order, FontTextParseResult* result); + +struct FontTextSource { + std::uint64_t systemUse0; + const void* start; + const void* end; + const void* current; + FontTextParseFunction textParser; + void* textObject; + FontHandle defaultFont; + void* systemUse[5]; +}; + +struct FontCreateStringDetail { + std::uint16_t detailId; + std::uint8_t detailType; + std::uint8_t detections; + std::uint32_t ordersOption; + FontHandle defaultFont; + union { + struct { + void* function; + void* object; + } Callback; + } Orders; +}; + +struct FontStringOpaque; +using FontString = FontStringOpaque*; + +struct FontWriting { + void* systemUse[32]; +}; + +struct FontWritingProfile { + std::uint32_t characterCount : 8; + std::uint32_t invisibleGlyph : 1; + std::uint32_t reserved : 23; +}; + +struct FontWritingStep { + float x; + float y; + float advanceX; + float advanceY; + FontHandle font; + FontWritingProfile Profile; + std::uint32_t glyphCode; + struct { + float x; + float y; + } Positioning; + FontGlyphMetrics GlyphMetrics; +}; + +struct FontWritingLetterStep { + float x; + float y; + float advanceX; + float advanceY; + struct { + std::uint32_t textsCount; + std::uint32_t textsIndex; + std::uint32_t glyphsCount; + std::uint32_t glyphsIndex; + std::uint8_t baseComponentsCount; + std::uint8_t baseComponentsIndex; + std::uint8_t baseFocusCount; + std::uint8_t baseFocusIndex; + std::uint8_t oppositeDirection; + std::uint8_t characterTextCount; + std::uint8_t baseTextCount; + std::uint8_t markTextCount; + std::uint32_t marksTextCount; + std::uint32_t marksTextNumber; + std::uint32_t reserved[3]; + std::int32_t textLetterOffset; + } Components; +}; + +struct FontWritingExtent { + float top; + float bottom; + float left; + float right; +}; + +struct FontWritingMetrics { + float advanceX; + float advanceY; + FontWritingExtent Extent; +}; + +struct FontHorizontalLayout { + float baselineOffset; + float lineAdvance; + float decorationExtent; +}; + +struct FontVerticalLayout { + float baselineOffsetX; + float columnAdvance; + float decorationSpan; +}; + +static_assert(sizeof(FontHandleOpaque) == 0x100); +static_assert(sizeof(FontMemory) == 0x40); +static_assert(sizeof(FontMemoryInterface) == 0x30); +static_assert(sizeof(FontOpenDetail) == 0x20); +static_assert(sizeof(FontGlyphMetrics) == 0x20); +static_assert(sizeof(FontKerning) == 0x10); +static_assert(sizeof(FontGlyphImageMetrics) == 0x18); +static_assert(sizeof(FontGenerateGlyphDetail) == 0x20); +static_assert(sizeof(FontResultStage) == 0x18); +static_assert(sizeof(FontSurfaceImage) == 0x10); +static_assert(sizeof(FontRenderOutput) == 0x40); +static_assert(sizeof(FontTextCharacter) == 0xC8); +static_assert(offsetof(FontTextCharacter, flags) == 0x38); +static_assert(sizeof(FontRenderSurface) == 0x80); +static_assert(sizeof(FontStyleFrame) == 0x60); +static_assert(offsetof(FontStyleFrame, scalePixelW) == 0x14); +static_assert(offsetof(FontStyleFrame, cache_lock_word) == 0x34); +static_assert(offsetof(FontStyleFrame, reserved_0x58) == 0x58); +static_assert(sizeof(FontTextSource) == 0x60); +static_assert(offsetof(FontTextSource, textParser) == 0x20); +static_assert(offsetof(FontTextSource, defaultFont) == 0x30); +static_assert(offsetof(FontTextSource, systemUse) == 0x38); +static_assert(sizeof(FontCreateStringDetail) == 0x20); +static_assert(sizeof(FontWriting) == 0x100); +static_assert(sizeof(FontWritingProfile) == sizeof(std::uint32_t)); +static_assert(sizeof(FontWritingStep) == 0x48); +static_assert(sizeof(FontWritingLetterStep) == 0x40); +static_assert(sizeof(FontWritingMetrics) == 0x18); + +#endif diff --git a/core/libs/prx/libSceFont/src/Layout.cpp b/core/libs/prx/libSceFont/src/Layout.cpp new file mode 100644 index 000000000..7b76c46d0 --- /dev/null +++ b/core/libs/prx/libSceFont/src/Layout.cpp @@ -0,0 +1,522 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H +#include FT_OUTLINE_H + +#include "prx/libSceFont/include/FontFreeType.hpp" +#include "prx/libSceFont/include/FontInternal.hpp" + +namespace { + +using namespace Font; + +struct FloatPair { + float lo = 0.0f; + float hi = 0.0f; +}; + +bool OrderedLess(float a, float b) { + return !std::isnan(a) && !std::isnan(b) && a < b; +} + +float MaxSs(float a, float b) { + if (std::isnan(a) || std::isnan(b)) return b; + if (a > b) return a; + if (a < b) return b; + if (a == 0.0f && b == 0.0f && std::signbit(a) != std::signbit(b)) return std::signbit(a) ? b : a; + return a; +} + +float FlipSign(float value) { + return std::bit_cast(std::bit_cast(value) ^ 0x80000000u); +} + +void UpdateShiftCache(FontObj* obj) { + obj->shift_cache_x = obj->shift_units_x; + obj->shift_cache_y = obj->shift_units_y; + const auto seedLow = static_cast(static_cast(obj->layout_seed_pair & 0xFFFFFFFFu)); + const auto seedHigh = static_cast(static_cast(obj->layout_seed_pair >> 32)); + obj->layout_seed_vec[0] = static_cast(static_cast(seedLow)); + obj->layout_seed_vec[1] = static_cast(static_cast(seedHigh)); + obj->layout_scale_vec[0] = 0x10000; + obj->layout_scale_vec[1] = 0x10000; +} + +int SetDriverCharSize(const SysDriver* driver, FontObj* obj, const StyleStateBlock* style, float& scaleX, float& scaleY) { + if (style->scale_unit == 0) return driver->set_char_default_dpi(obj, scaleX, scaleY, &scaleX, &scaleY); + return driver->set_char_with_dpi(obj, style->dpi_x, style->dpi_y, scaleX, scaleY, &scaleX, &scaleY); +} + +void ClearHorizontalLayout(std::uint8_t* outWords) { + if (!outWords) return; + std::memset(outWords, 0, 0x10); + std::memset(outWords + 0x18, 0, 0x0C); +} + +void ClearVerticalLayout(std::uint8_t* outWords) { + if (!outWords) return; + std::memset(outWords, 0, 0x10); + std::memset(outWords + 0x14, 0, 0x0C); +} + +const StyleStateBlock* ResolveSurfaceStyleState(const StyleStateBlock* cachedState, const FontRenderSurface* surface, StyleStateBlock& temporary) { + const auto* systemUse = reinterpret_cast(surface->reserved_q); + if ((surface->styleFlag & 0x1) == 0 || !ValidStyleFrame(systemUse->styleframe)) return cachedState; + const FontStyleFrame* frame = systemUse->styleframe; + temporary = *cachedState; + if ((frame->flags1 & STYLE_FRAME_FLAG_SCALE) != 0) { + temporary.scale_unit = frame->scaleUnit; + temporary.scale_w = frame->scalePixelW; + temporary.scale_h = frame->scalePixelH; + temporary.dpi_x = frame->hDpi ? frame->hDpi : 0x48; + temporary.dpi_y = frame->vDpi ? frame->vDpi : 0x48; + } else { + const std::uint32_t wantX = frame->hDpi == 0 ? temporary.dpi_x : frame->hDpi; + const std::uint32_t wantY = frame->vDpi == 0 ? temporary.dpi_y : frame->vDpi; + temporary.dpi_x = wantX != 0 ? 0x48 : 0; + temporary.dpi_y = wantY != 0 ? 0x48 : 0; + } + if ((frame->flags1 & STYLE_FRAME_FLAG_SLANT) != 0) temporary.slant_ratio = frame->slantRatio; + if ((frame->flags1 & STYLE_FRAME_FLAG_WEIGHT) != 0) { + temporary.effect_weight_x = frame->effectWeightX; + temporary.effect_weight_y = frame->effectWeightY; + } + return &temporary; +} + +void FillImageMetrics(FontRenderOutput* result, const FontGlyphMetrics* metrics, float x, float y) { + const float leftF = x + metrics->Horizontal.bearingX; + const float topF = y + metrics->Horizontal.bearingY; + const float rightF = leftF + metrics->width; + const float bottomF = topF - metrics->height; + const int left = FloorToInt(leftF); + const int top = CeilToInt(topF); + const int right = CeilToInt(rightF); + const int bottom = FloorToInt(bottomF); + const float advanceF = x + metrics->Horizontal.advance; + const int advance = CeilToInt(advanceF); + result->ImageMetrics.bearingX = static_cast(left) - x; + result->ImageMetrics.bearingY = static_cast(top) - y; + result->ImageMetrics.advance = static_cast(advance) - x; + int stride = advance; + if (advanceF != static_cast(advance)) { + const int candidate = static_cast((static_cast(right) - rightF) + advanceF); + stride = right + 1; + if (advanceF <= rightF) stride = candidate; + if (stride < candidate) stride = candidate; + } + result->ImageMetrics.stride = static_cast(stride) - x; + result->ImageMetrics.width = static_cast(std::max(0, right - left)); + result->ImageMetrics.height = static_cast(std::max(0, top - bottom)); +} + +int RenderGlyphIndexToSurface(FontObj& obj, std::uint32_t glyphIndex, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { + ClearRenderOutputs(metrics, result); + if (!surface->buffer || surface->width <= 0 || surface->height <= 0 || surface->widthByte <= 0 || surface->pixelSizeByte <= 0) return SCE_FONT_ERROR_NO_SUPPORT_SURFACE; + const int bytesPerPixel = surface->pixelSizeByte; + if (bytesPerPixel != 1 && bytesPerPixel != 4) return SCE_FONT_ERROR_NO_SUPPORT_SURFACE; + const auto face = static_cast(obj.ft_face); + if (!face || !face->size) return SCE_FONT_ERROR_NO_SUPPORT_GLYPH; + FT_Vector delta{}; + delta.x = static_cast(static_cast((x - std::floor(x)) * 64.0f)); + delta.y = static_cast(-static_cast((y - std::floor(y)) * 64.0f)); + if (obj.shift_units_x != 0 || obj.shift_units_y != 0) { + delta.x += static_cast(RoundMulFixed(obj.shift_units_x, static_cast(face->size->metrics.x_scale))); + delta.y += static_cast(RoundMulFixed(obj.shift_units_y, static_cast(face->size->metrics.y_scale))); + } + FT_Set_Transform(face, nullptr, &delta); + const FT_Error error = FT_Load_Glyph(face, glyphIndex, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_VERTICAL_LAYOUT | FT_LOAD_RENDER); + FT_Set_Transform(face, nullptr, nullptr); + if (error != 0 || !face->glyph) return SCE_FONT_ERROR_NO_SUPPORT_GLYPH; + const FT_GlyphSlot slot = face->glyph; + const int glyphW = static_cast(slot->bitmap.width); + const int glyphH = static_cast(slot->bitmap.rows); + metrics->width = static_cast(slot->metrics.width) / 64.0f; + metrics->height = static_cast(slot->metrics.height) / 64.0f; + metrics->Horizontal.bearingX = static_cast(slot->metrics.horiBearingX) / 64.0f; + metrics->Horizontal.bearingY = static_cast(slot->metrics.horiBearingY) / 64.0f; + metrics->Horizontal.advance = static_cast(slot->metrics.horiAdvance) / 64.0f; + std::vector bitmap(static_cast(glyphW) * static_cast(glyphH)); + if (glyphW > 0 && glyphH > 0) { + const int pitch = slot->bitmap.pitch; + const auto* source = slot->bitmap.buffer; + for (int row = 0; row < glyphH; ++row) { + const std::uint8_t* sourceRow = source + static_cast(row) * pitch; + std::uint8_t* targetRow = bitmap.data() + static_cast(row) * static_cast(glyphW); + if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { + for (int column = 0; column < glyphW; ++column) targetRow[column] = (sourceRow[column >> 3] & (0x80u >> (column & 7))) ? 0xFF : 0x00; + } else { + std::memcpy(targetRow, sourceRow, static_cast(glyphW)); + } + } + } + const int destX = static_cast(std::floor(x)) + slot->bitmap_left; + const int destY = static_cast(std::floor(y)) - slot->bitmap_top; + const int clipX0 = std::clamp(static_cast(surface->sc_x0), 0, surface->width); + const int clipY0 = std::clamp(static_cast(surface->sc_y0), 0, surface->height); + const int clipX1 = std::clamp(static_cast(surface->sc_x1), 0, surface->width); + const int clipY1 = std::clamp(static_cast(surface->sc_y1), 0, surface->height); + int updateX = destX; + int updateY = destY; + int updateW = 0; + int updateH = 0; + if (glyphW > 0 && glyphH > 0 && clipX1 > clipX0 && clipY1 > clipY0) { + auto* targetBase = static_cast(surface->buffer); + const int startRow = std::max(destY, clipY0); + const int endRow = std::min(destY + glyphH, clipY1); + const int startColumn = std::max(destX, clipX0); + const int endColumn = std::min(destX + glyphW, clipX1); + updateX = startColumn; + updateY = startRow; + updateW = std::max(0, endColumn - startColumn); + updateH = std::max(0, endRow - startRow); + for (int row = startRow; row < endRow; ++row) { + const std::uint8_t* sourceRow = bitmap.data() + static_cast(row - destY) * static_cast(glyphW); + std::uint8_t* targetRow = targetBase + static_cast(row) * static_cast(surface->widthByte); + for (int column = startColumn; column < endColumn; ++column) { + const std::uint8_t coverage = sourceRow[column - destX]; + std::memset(targetRow + static_cast(column) * static_cast(bytesPerPixel), coverage, static_cast(bytesPerPixel)); + } + } + } + result->stage = nullptr; + result->SurfaceImage.widthByte = static_cast(surface->widthByte); + result->SurfaceImage.pixelSizeByte = static_cast(surface->pixelSizeByte); + result->SurfaceImage.pixelFormat = 0; + result->SurfaceImage.pad16 = 0; + result->UpdateRect.x = static_cast(std::max(updateX, 0)); + result->UpdateRect.y = static_cast(std::max(updateY, 0)); + result->UpdateRect.w = static_cast(std::max(updateW, 0)); + result->UpdateRect.h = static_cast(std::max(updateH, 0)); + result->SurfaceImage.address = static_cast(surface->buffer) + static_cast(result->UpdateRect.y) * static_cast(surface->widthByte) + static_cast(result->UpdateRect.x) * static_cast(bytesPerPixel); + FillImageMetrics(result, metrics, x, y); + return SCE_FONT_OK; +} + +} + +int Font::ComputeHorizontalLayout(FontHandle handle, const StyleStateBlock* style, std::uint8_t* outWords) { + ClearHorizontalLayout(outWords); + const auto fail = [&] { + ClearHorizontalLayout(outWords); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + }; + auto* font = GetNativeFont(handle); + if (!font) return fail(); + auto* library = static_cast(font->library); + if (!library || !library->external_fonts_ctx) return fail(); + const SysDriver* driver = library->sys_driver; + if (!driver || !driver->set_char_with_dpi || !driver->set_char_default_dpi || !driver->compute_layout || !style) return fail(); + const std::uint32_t modeLow = font->flags & 0x0Fu; + const std::uint32_t fontId = font->open_info.ctx_entry_index; + float baselineMax = 0.0f; + float deltaMax = 0.0f; + float effectForBaseline = 0.0f; + float effectForDelta = 0.0f; + FloatPair accumulatedAdjust{}; + FloatPair accumulatedBounds{}; + if (static_cast(fontId) >= 0) { + FontObj* head = nullptr; + std::uint32_t lockWord = 0; + auto* entry = AcquireFontCtxEntry(library->external_fonts_ctx, fontId, modeLow, &head, &lockWord); + if (!entry) return fail(); + FontObj* match = FindSubFont(head, font->open_info.sub_font_index); + int rc = SCE_FONT_ERROR_FATAL; + std::uint8_t layout[HORIZONTAL_LAYOUT_SIZE] = {}; + if ((lockWord & OPEN_BIT) != 0 && (lockWord & COUNT_MASK) != 0 && match) { + float scaleX = style->scale_w; + float scaleY = style->scale_h; + match->font_handle = handle; + match->shift_units_x = 0; + match->shift_units_y = 0; + rc = SetDriverCharSize(driver, match, style, scaleX, scaleY); + if (rc == SCE_FONT_OK) { + UpdateShiftCache(match); + rc = driver->compute_layout(match, style, layout); + } + } + ReleaseFontCtxEntryLock(entry, modeLow, lockWord); + if (rc != SCE_FONT_OK) return fail(); + const float lineAdvance = LoadFloat(layout, HORIZONTAL_LINE_ADVANCE); + const float baseline = LoadFloat(layout, HORIZONTAL_BASELINE); + const float effectHeight = LoadFloat(layout, HORIZONTAL_EFFECT_HEIGHT); + const FloatPair bounds{LoadFloat(layout, HORIZONTAL_X_BOUND_LO), LoadFloat(layout, HORIZONTAL_X_BOUND_HI)}; + const FloatPair adjust{LoadFloat(layout, HORIZONTAL_HALF_EFFECT_WIDTH), LoadFloat(layout, HORIZONTAL_LEFT_ADJUST)}; + const FloatPair previousAdjust = accumulatedAdjust; + const FloatPair previousBounds = accumulatedBounds; + const bool baselineUpdate = OrderedLess(baselineMax, baseline); + baselineMax = MaxSs(baseline, baselineMax); + if (baselineUpdate) effectForBaseline = effectHeight; + const float delta = lineAdvance - baseline; + const bool deltaUpdate = OrderedLess(deltaMax, delta); + deltaMax = MaxSs(delta, deltaMax); + if (deltaUpdate) effectForDelta = effectHeight; + const FloatPair difference{bounds.lo - adjust.hi, bounds.hi - adjust.lo}; + const FloatPair sum{previousAdjust.lo + previousBounds.lo, previousAdjust.hi + previousBounds.hi}; + if (OrderedLess(difference.lo, sum.lo)) { + accumulatedBounds.lo = bounds.lo; + accumulatedAdjust.lo = FlipSign(adjust.hi); + } + if (OrderedLess(difference.hi, sum.hi)) { + accumulatedBounds.hi = bounds.hi; + accumulatedAdjust.hi = adjust.lo; + } + } + if (!outWords) return fail(); + StoreFloat(outWords, HORIZONTAL_BASELINE, baselineMax); + StoreFloat(outWords, HORIZONTAL_LINE_ADVANCE, baselineMax + deltaMax); + StoreFloat(outWords, HORIZONTAL_X_BOUND_LO, accumulatedBounds.lo); + StoreFloat(outWords, HORIZONTAL_X_BOUND_HI, accumulatedBounds.hi); + StoreFloat(outWords, HORIZONTAL_EFFECT_HEIGHT, MaxSs(effectForBaseline, effectForDelta)); + StoreFloat(outWords, HORIZONTAL_LEFT_ADJUST, accumulatedAdjust.lo); + StoreFloat(outWords, HORIZONTAL_HALF_EFFECT_WIDTH, accumulatedAdjust.hi); + return SCE_FONT_OK; +} + +int Font::ComputeVerticalLayout(FontHandle handle, const StyleStateBlock* style, std::uint8_t* outWords) { + const auto fail = [&](int rc) { + ClearVerticalLayout(outWords); + return rc; + }; + auto* font = GetNativeFont(handle); + auto* library = font ? static_cast(font->library) : nullptr; + if (!library || !library->external_fonts_ctx || !library->sys_driver) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); + const SysDriver* driver = library->sys_driver; + const std::uint32_t modeLow = font->flags & 0x0Fu; + const std::uint32_t fontId = font->open_info.ctx_entry_index; + float accNegOffsetMax = 0.0f; + float accSumMax = 0.0f; + float spanForNegOffset = 0.0f; + float spanForSum = 0.0f; + float accMetrics08Max = 0.0f; + float accMetrics0CMax = 0.0f; + float accDiffMin = 0.0f; + float accTempMin = 0.0f; + if (static_cast(fontId) >= 0) { + FontObj* head = nullptr; + std::uint32_t lockWord = 0; + auto* entry = AcquireFontCtxEntry(library->external_fonts_ctx, fontId, modeLow, &head, &lockWord); + FontObj* obj = FindSubFont(head, font->open_info.sub_font_index); + if (!entry || !obj) { + if (entry) ReleaseFontCtxEntryLock(entry, modeLow, lockWord); + return fail(SCE_FONT_ERROR_FATAL); + } + int rc = SCE_FONT_ERROR_FATAL; + if ((lockWord & OPEN_BIT) != 0 && (lockWord & COUNT_MASK) != 0) { + float scaleX = style ? style->scale_w : 0.0f; + float scaleY = style ? style->scale_h : 0.0f; + obj->font_handle = handle; + obj->shift_units_x = 0; + obj->shift_units_y = 0; + int callRc = SCE_FONT_ERROR_FATAL; + if (style && style->scale_unit == 0) { + if (driver->set_char_default_dpi) callRc = driver->set_char_default_dpi(obj, scaleX, scaleY, &scaleX, &scaleY); + } else if (driver->set_char_with_dpi) { + callRc = driver->set_char_with_dpi(obj, style ? style->dpi_x : 0u, style ? style->dpi_y : 0u, scaleX, scaleY, &scaleX, &scaleY); + } + std::uint8_t layout[VERTICAL_LAYOUT_SIZE] = {}; + if (callRc == SCE_FONT_OK) { + UpdateShiftCache(obj); + callRc = driver->compute_layout_alt ? driver->compute_layout_alt(obj, style, layout) : SCE_FONT_ERROR_FATAL; + } + if (callRc == SCE_FONT_OK) { + const float metrics00 = LoadFloat(layout, VERTICAL_COLUMN_ADVANCE); + const float offsetCandidate = LoadFloat(layout, VERTICAL_BASELINE_OFFSET_X); + const float metrics08 = LoadFloat(layout, VERTICAL_METRICS_0X08); + const float metrics0C = LoadFloat(layout, VERTICAL_METRICS_0X0C); + const float spanCandidate = LoadFloat(layout, VERTICAL_DECORATION_SPAN); + const float extra08 = LoadFloat(layout, VERTICAL_DECORATION_0X08); + const float extra0C = LoadFloat(layout, VERTICAL_DECORATION_0X0C); + const float negOffset = -offsetCandidate; + const float sum = offsetCandidate + metrics00; + if (accNegOffsetMax < negOffset) spanForNegOffset = spanCandidate; + accNegOffsetMax = std::max(accNegOffsetMax, negOffset); + if (accSumMax < sum) spanForSum = spanCandidate; + accSumMax = std::max(accSumMax, sum); + accDiffMin = std::min(extra08 - offsetCandidate, accDiffMin); + accMetrics08Max = std::max(metrics08, accMetrics08Max); + accTempMin = std::min(sum + extra0C, accDiffMin); + accMetrics0CMax = std::max(metrics0C, accMetrics0CMax); + rc = SCE_FONT_OK; + } else { + rc = callRc; + } + } + ReleaseFontCtxEntryLock(entry, modeLow, lockWord); + if (rc != SCE_FONT_OK) return fail(rc); + } + if (outWords) { + StoreFloat(outWords, VERTICAL_COLUMN_ADVANCE, accNegOffsetMax + accSumMax); + StoreFloat(outWords, VERTICAL_BASELINE_OFFSET_X, -accNegOffsetMax); + StoreFloat(outWords, VERTICAL_METRICS_0X08, accMetrics08Max); + StoreFloat(outWords, VERTICAL_METRICS_0X0C, accMetrics0CMax); + StoreFloat(outWords, VERTICAL_DECORATION_SPAN, std::max(spanForSum, spanForNegOffset)); + StoreFloat(outWords, VERTICAL_DECORATION_0X08, accTempMin - accNegOffsetMax); + StoreFloat(outWords, VERTICAL_DECORATION_0X0C, -accSumMax); + } + return SCE_FONT_OK; +} + +int Font::GetCharGlyphMetrics(FontHandle handle, std::uint32_t code, FontGlyphMetrics* metrics, bool useCachedStyle) { + const auto clearMetrics = [&] { + if (metrics) *metrics = {}; + }; + auto* font = GetNativeFont(handle); + if (!font || font->magic != HANDLE_MAGIC) { + clearMetrics(); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + if (code == 0) { + clearMetrics(); + return SCE_FONT_ERROR_NO_SUPPORT_CODE; + } + if (!metrics) return SCE_FONT_ERROR_INVALID_PARAMETER; + std::uint32_t previousFontLock = 0; + if (!AcquireFontLock(font, previousFontLock)) { + clearMetrics(); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + std::uint32_t previousCachedLock = 0; + if (useCachedStyle && !AcquireCachedStyleLock(font, previousCachedLock)) { + ReleaseFontLock(font, previousFontLock); + clearMetrics(); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + const auto finish = [&](int rc) { + if (useCachedStyle) ReleaseCachedStyleLock(font, previousCachedLock); + ReleaseFontLock(font, previousFontLock); + if (rc != SCE_FONT_OK) clearMetrics(); + return rc; + }; + FontState* state = TryGetState(handle); + if (!state) return finish(SCE_FONT_ERROR_INVALID_FONT_HANDLE); + if (useCachedStyle && !font->renderer) return finish(SCE_FONT_ERROR_NOT_BOUND_RENDERER); + const StyleStateBlock* style = useCachedStyle ? &font->cached_style.state : &font->style; + float scaleW = 0.0f; + float scaleH = 0.0f; + const int scaleRc = StyleStateGetScalePixel(style, &scaleW, &scaleH); + if (scaleRc != SCE_FONT_OK) return finish(scaleRc); + const FT_Face face = state->face; + if (!face) return finish(SCE_FONT_ERROR_NO_SUPPORT_FUNCTION); + const FT_UInt glyphIndex = ResolveGlyphIndexWithFallback(face, code); + if (glyphIndex == 0) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH); + const auto charW = static_cast(static_cast(scaleW * 64.0f)); + const auto charH = static_cast(static_cast(scaleH * 64.0f)); + FT_F26Dot6 usedW = charW; + FT_F26Dot6 usedH = charH; + if (SetCharSizeCompat(face, charW, charH, 72, 72, &usedW, &usedH) != 0) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH); + FT_Set_Transform(face, nullptr, nullptr); + if (FT_Load_Glyph(face, glyphIndex, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_VERTICAL_LAYOUT) != 0) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH); + const auto ratio = [](FT_F26Dot6 requested, FT_F26Dot6 applied) { + if (requested == 0 || applied == 0) return 1.0f; + const double value = static_cast(std::llabs(static_cast(requested))) / static_cast(std::llabs(static_cast(applied))); + return std::isfinite(value) && value > 0.0 ? static_cast(value) : 1.0f; + }; + const float ratioX = ratio(charW, usedW); + const float ratioY = ratio(charH, usedH); + const FT_GlyphSlot slot = face->glyph; + metrics->width = static_cast(slot->metrics.width) / 64.0f * ratioX; + metrics->height = static_cast(slot->metrics.height) / 64.0f * ratioY; + metrics->Horizontal.bearingX = static_cast(slot->metrics.horiBearingX) / 64.0f * ratioX; + metrics->Horizontal.bearingY = static_cast(slot->metrics.horiBearingY) / 64.0f * ratioY; + metrics->Horizontal.advance = static_cast(slot->metrics.horiAdvance) / 64.0f * ratioX; + metrics->Vertical.bearingX = 0.0f; + metrics->Vertical.bearingY = 0.0f; + metrics->Vertical.advance = 0.0f; + return finish(SCE_FONT_OK); +} + +int Font::RenderCharGlyphImageCore(FontHandle handle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { + const auto fail = [&](int rc) { + ClearRenderOutputs(metrics, result); + return rc; + }; + if (!handle) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); + if (code == 0) return fail(SCE_FONT_ERROR_NO_SUPPORT_CODE); + if (!surface || !metrics || !result) return fail(SCE_FONT_ERROR_INVALID_PARAMETER); + auto* font = GetNativeFont(handle); + if (font->magic != HANDLE_MAGIC) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); + if (!font->renderer) return fail(SCE_FONT_ERROR_NOT_BOUND_RENDERER); + auto* library = static_cast(font->library); + if (!library || library->magic != LIBRARY_MAGIC) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); + const SysDriver* driver = library->sys_driver; + if (!driver || !driver->glyph_index || !driver->set_char_with_dpi || !driver->set_char_default_dpi) return fail(SCE_FONT_ERROR_FATAL); + const std::uint32_t modeLow = font->flags & 0x0Fu; + StyleStateBlock surfaceStyle{}; + const StyleStateBlock* style = ResolveSurfaceStyleState(&font->cached_style.state, surface, surfaceStyle); + FontObj* obj = nullptr; + std::uint32_t lockWord = 0; + auto* entry = AcquireFontCtxEntry(library->external_fonts_ctx, font->open_info.ctx_entry_index, modeLow, &obj, &lockWord); + obj = FindSubFont(obj, font->open_info.sub_font_index); + if (!entry || !obj) { + if (entry) ReleaseFontCtxEntryLock(entry, modeLow, lockWord); + return fail(SCE_FONT_ERROR_FATAL); + } + const auto finish = [&](int rc) { + ReleaseFontCtxEntryLock(entry, modeLow, lockWord); + if (rc != SCE_FONT_OK) ClearRenderOutputs(metrics, result); + return rc; + }; + if ((lockWord & OPEN_BIT) == 0 || (lockWord & COUNT_MASK) == 0) return finish(SCE_FONT_ERROR_FATAL); + obj->reserved_0x04 = static_cast(font->flags >> 15); + obj->font_handle = handle; + obj->shift_units_x = 0; + obj->shift_units_y = 0; + obj->layout_seed_pair = 0; + StyleStateGetScalePixel(style, &obj->scale_x_0x50, &obj->scale_y_0x54); + float outScaleX = 0.0f; + float outScaleY = 0.0f; + int rc; + if (style->scale_unit == 0) { + rc = driver->set_char_default_dpi(obj, style->scale_w, style->scale_h, &outScaleX, &outScaleY); + } else { + rc = driver->set_char_with_dpi(obj, style->dpi_x ? style->dpi_x : 0x48, style->dpi_y ? style->dpi_y : 0x48, style->scale_w, style->scale_h, &outScaleX, &outScaleY); + } + if (rc != SCE_FONT_OK) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH); + std::uint32_t glyphIndex = 0; + rc = driver->glyph_index(obj, code, &glyphIndex); + if (rc != SCE_FONT_OK || glyphIndex == 0) return finish(SCE_FONT_ERROR_NO_SUPPORT_GLYPH); + return finish(RenderGlyphIndexToSurface(*obj, glyphIndex, surface, x, y, metrics, result)); +} + +bool Font::BuildTrueOutline(GeneratedGlyph& glyph) { + FontState* state = TryGetState(glyph.owner); + if (!state || !state->face) return false; + const auto* font = GetNativeFont(glyph.owner); + if (font->magic != HANDLE_MAGIC) return false; + const FT_Face face = state->face; + const FT_UInt glyphIndex = FT_Get_Char_Index(face, glyph.codepoint); + if (glyphIndex == 0) return false; + const auto charW = static_cast(static_cast(glyph.glyph.scale_x * 64.0f)); + const auto charH = static_cast(static_cast(glyph.glyph.base_scale * 64.0f)); + if (FT_Set_Char_Size(face, charW, charH, 72, 72) != 0) return false; + FT_Set_Transform(face, nullptr, nullptr); + if (FT_Load_Glyph(face, glyphIndex, FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP) != 0) return false; + const FT_GlyphSlot slot = face->glyph; + if (!slot || slot->format != FT_GLYPH_FORMAT_OUTLINE) return false; + const FT_Outline& outline = slot->outline; + if (outline.n_points <= 0 || outline.n_contours <= 0 || !outline.points || !outline.tags || !outline.contours) return false; + glyph.outlinePoints.clear(); + glyph.outlineTags.clear(); + glyph.outlineContours.clear(); + for (int i = 0; i < outline.n_points; ++i) { + glyph.outlinePoints.push_back({static_cast(outline.points[i].x) / 64.0f, static_cast(outline.points[i].y) / 64.0f}); + glyph.outlineTags.push_back(FT_CURVE_TAG(outline.tags[i]) == FT_CURVE_TAG_ON ? 1 : 0); + } + for (int i = 0; i < outline.n_contours; ++i) glyph.outlineContours.push_back(static_cast(outline.contours[i])); + glyph.outline.points_ptr = glyph.outlinePoints.data(); + glyph.outline.tags_ptr = glyph.outlineTags.data(); + glyph.outline.contour_end_idx = glyph.outlineContours.data(); + glyph.outline.points_cnt = static_cast(glyph.outlinePoints.size()); + glyph.outline.contours_cnt = static_cast(glyph.outlineContours.size()); + glyph.outlineInitialized = true; + return true; +} diff --git a/core/libs/prx/libSceFont/src/Library.cpp b/core/libs/prx/libSceFont/src/Library.cpp new file mode 100644 index 000000000..46b421dcb --- /dev/null +++ b/core/libs/prx/libSceFont/src/Library.cpp @@ -0,0 +1,378 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include "prx/libSceFont/include/FontInternal.hpp" + +namespace { + +using namespace Font; + +std::uint32_t EffectiveEdition(std::uint64_t edition) { + return static_cast(edition >> 32); +} + +std::uint32_t* AcquireDeviceCache(FontLibNative* library) { + auto* const busy = reinterpret_cast(std::numeric_limits::max()); + std::atomic_ref slot(library->device_cache_buf); + for (;;) { + std::uint32_t* current = slot.load(std::memory_order_acquire); + if (current != busy && slot.compare_exchange_weak(current, busy, std::memory_order_acq_rel)) return current; + Backoff(); + } +} + +void ReleaseDeviceCache(FontLibNative* library, std::uint32_t* cache) { + std::atomic_ref(library->device_cache_buf).store(cache, std::memory_order_release); +} + +void* AcquireRendererSelection(RendererNative* renderer) { + auto* const busy = reinterpret_cast(std::numeric_limits::max()); + std::atomic_ref slot(renderer->selection); + for (;;) { + void* current = slot.load(std::memory_order_acquire); + if (current != busy && slot.compare_exchange_weak(current, busy, std::memory_order_acq_rel)) return current; + Backoff(); + } +} + +void* CreateFontContext(FontLibNative* library, std::uint32_t entries, std::uint32_t size) { + void* ctx = library->iface->alloc(library->alloc_ctx, size); + if (!ctx) return nullptr; + std::memset(ctx, 0, size); + auto* header = static_cast(ctx); + header->lock_word = 0; + header->max_entries = entries; + header->base = entries != 0 ? static_cast(ctx) + sizeof(FontCtxHeader) : nullptr; + return ctx; +} + +int SupportFonts(FontLibrary library, void* FontLibNative::*slot, std::uint32_t entries, std::uint32_t size, std::uint32_t formats) { + auto* lib = static_cast(library); + if (!lib || lib->magic != LIBRARY_MAGIC) return SCE_FONT_ERROR_INVALID_LIBRARY; + std::uint32_t previous = 0; + if (!AcquireLibraryLock(lib, previous)) return SCE_FONT_ERROR_INVALID_LIBRARY; + const auto finish = [&](int rc) { + ReleaseLibraryLock(lib, previous); + return rc; + }; + if (lib->*slot) return finish(SCE_FONT_ERROR_ALREADY_SPECIFIED); + void* ctx = CreateFontContext(lib, entries, size); + if (!ctx) return finish(SCE_FONT_ERROR_ALLOCATION_FAILED); + if (!lib->sys_driver || !lib->sys_driver->support_formats) { + lib->iface->dealloc(lib->alloc_ctx, ctx); + return finish(SCE_FONT_ERROR_INVALID_LIBRARY); + } + const int rc = lib->sys_driver->support_formats(lib, formats); + if (rc != SCE_FONT_OK) { + lib->iface->dealloc(lib->alloc_ctx, ctx); + return finish(rc); + } + lib->*slot = ctx; + return finish(SCE_FONT_OK); +} + +} + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceFontMemoryInit(FontMemory* memory, void* regionAddress, std::uint32_t regionSize, const FontMemoryInterface* iface, void* mspaceObject, FontMemoryDestroyFunction destroyCallback, void* destroyObject) { + if (!memory) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (!iface) { + memory->mem_kind = 0; + if (!regionAddress || regionSize == 0) return SCE_FONT_ERROR_INVALID_PARAMETER; + } + memory->mem_kind = MEMORY_MAGIC; + memory->attr_bits = 0; + memory->region_size = regionSize; + memory->region_base = regionAddress; + memory->mspace_handle = mspaceObject; + memory->iface = iface; + memory->on_destroy = destroyCallback; + memory->destroy_ctx = destroyObject; + memory->some_ctx1 = nullptr; + memory->some_ctx2 = mspaceObject; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontMemoryTerm(FontMemory* memory) { + if (!memory) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (memory->mem_kind != MEMORY_MAGIC) return SCE_FONT_ERROR_INVALID_MEMORY; + if (static_cast(memory->attr_bits & 0xFF) < 0) { + if (memory->iface && memory->iface->mspace_destroy) memory->iface->mspace_destroy(memory->some_ctx2, memory->mspace_handle); + std::memset(memory, 0, sizeof(*memory)); + return SCE_FONT_OK; + } + if (memory->on_destroy) { + memory->mem_kind = 0; + memory->on_destroy(memory, memory->mspace_handle, memory->destroy_ctx); + return SCE_FONT_OK; + } + std::memset(memory, 0, sizeof(*memory)); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontCreateLibraryWithEdition(const FontMemory* memory, const void* createParams, std::uint64_t edition, FontLibrary* pLibrary) { + if (pLibrary) *pLibrary = nullptr; + if (!memory) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (memory->mem_kind != MEMORY_MAGIC || !memory->iface || !memory->iface->alloc || !memory->iface->dealloc) return SCE_FONT_ERROR_INVALID_MEMORY; + if (!createParams || !pLibrary) return SCE_FONT_ERROR_INVALID_PARAMETER; + const auto allocFn = memory->iface->alloc; + const auto freeFn = memory->iface->dealloc; + auto* lib = static_cast(allocFn(memory->mspace_handle, sizeof(FontLibNative))); + if (!lib) return SCE_FONT_ERROR_ALLOCATION_FAILED; + void* workspace = allocFn(memory->mspace_handle, 0x4000); + if (!workspace) { + freeFn(memory->mspace_handle, lib); + return SCE_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(lib, 0, sizeof(FontLibNative)); + lib->mem_kind = MEMORY_MAGIC; + lib->region_size = memory->region_size; + lib->region_base = memory->region_base; + lib->alloc_ctx = memory->mspace_handle; + lib->iface = memory->iface; + lib->alloc_fn = memory->iface->alloc; + lib->dealloc_fn = memory->iface->dealloc; + lib->realloc_fn = memory->iface->realloc_fn; + lib->calloc_fn = memory->iface->calloc_fn; + lib->sys_driver = static_cast(createParams); + lib->workspace_size = 0x4000; + lib->workspace = workspace; + lib->list_head_ptr = &lib->list_head; + lib->list_head = nullptr; + const auto initFn = lib->sys_driver->init; + const int rc = initFn ? initFn(memory, lib) : SCE_FONT_ERROR_INVALID_PARAMETER; + if (rc != SCE_FONT_OK) { + freeFn(memory->mspace_handle, workspace); + freeFn(memory->mspace_handle, lib); + return rc; + } + const std::uint32_t version = EffectiveEdition(edition); + if (version > 0x92FFFFu) { + if (version < 0x1500000u) { + lib->feature_word |= 1u | 2u | 4u; + } else if (version < 0x2000000u) { + lib->feature_word |= 2u | 4u; + } else if (version <= 0x2000070u) { + lib->feature_word |= 4u; + } + } + lib->magic = LIBRARY_MAGIC; + *pLibrary = lib; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontCreateLibrary(const FontMemory* memory, const void* createParams, FontLibrary* pLibrary) { + return sceFontCreateLibraryWithEdition(memory, createParams, 0, pLibrary); +} + +int APS5_VABI sceFontDestroyLibrary(FontLibrary* pLibrary) { + if (!pLibrary) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* lib = static_cast(*pLibrary); + if (!lib || lib->magic != LIBRARY_MAGIC) return SCE_FONT_ERROR_INVALID_LIBRARY; + if (lib->sys_driver && lib->sys_driver->term) lib->sys_driver->term(lib); + const auto freeFn = lib->iface->dealloc; + void* allocCtx = lib->alloc_ctx; + if ((lib->flags & 1u) != 0 && lib->device_cache_buf) freeFn(allocCtx, lib->device_cache_buf); + if (lib->external_fonts_ctx) freeFn(allocCtx, lib->external_fonts_ctx); + if (lib->sysfonts_ctx) freeFn(allocCtx, lib->sysfonts_ctx); + if (lib->workspace) freeFn(allocCtx, lib->workspace); + lib->magic = 0; + freeFn(allocCtx, lib); + *pLibrary = nullptr; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontCreateRendererWithEdition(const FontMemory* memory, const void* createParams, std::uint64_t edition, FontRenderer* pRenderer) { + if (!memory) { + if (pRenderer) *pRenderer = nullptr; + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + int rc = SCE_FONT_ERROR_INVALID_MEMORY; + if (memory->mem_kind == MEMORY_MAGIC && memory->iface && memory->iface->alloc && memory->iface->dealloc) { + rc = SCE_FONT_ERROR_INVALID_PARAMETER; + if (createParams && pRenderer) { + const auto allocFn = memory->iface->alloc; + const auto freeFn = memory->iface->dealloc; + const auto* selection = static_cast(createParams); + void* rendererMemory = allocFn(memory->mspace_handle, selection->size); + void* workspace = allocFn(memory->mspace_handle, 0x4000); + rc = SCE_FONT_ERROR_ALLOCATION_FAILED; + if (rendererMemory && workspace) { + auto* renderer = static_cast(rendererMemory); + std::memset(renderer, 0, offsetof(RendererNative, mem_kind)); + renderer->magic = RENDERER_MAGIC; + renderer->mem_kind = MEMORY_MAGIC; + renderer->region_size = memory->region_size; + renderer->region_base = memory->region_base; + renderer->alloc_ctx = memory->mspace_handle; + renderer->mem_iface = memory->iface; + renderer->alloc_fn = memory->iface->alloc; + renderer->free_fn = memory->iface->dealloc; + renderer->realloc_fn = memory->iface->realloc_fn; + renderer->calloc_fn = memory->iface->calloc_fn; + renderer->selection = const_cast(createParams); + renderer->outline_magic_0x90 = 0x400000000000ull; + renderer->workspace = workspace; + renderer->workspace_size = 0x4000; + renderer->reserved_a8 = 0; + renderer->outline_policy_flag = 0; + rc = selection->create_fn ? selection->create_fn(renderer) : SCE_FONT_ERROR_FATAL; + if (rc == SCE_FONT_OK) { + if (EffectiveEdition(edition) - 0x930000u < 0x1BD0000u) { + renderer->feature_byte_0x0c |= 1u; + renderer->outline_policy_flag = 1; + } + *pRenderer = renderer; + return SCE_FONT_OK; + } + } + if (workspace) freeFn(memory->mspace_handle, workspace); + if (rendererMemory) freeFn(memory->mspace_handle, rendererMemory); + } + } + if (pRenderer) *pRenderer = nullptr; + return rc; +} + +int APS5_VABI sceFontCreateRenderer(const FontMemory* memory, const void* createParams, FontRenderer* pRenderer) { + return sceFontCreateRendererWithEdition(memory, createParams, 0, pRenderer); +} + +int APS5_VABI sceFontDestroyRenderer(FontRenderer* pRenderer) { + if (!pRenderer) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* renderer = static_cast(*pRenderer); + if (!renderer || renderer->magic != RENDERER_MAGIC) return SCE_FONT_ERROR_INVALID_RENDERER; + const auto* selection = static_cast(AcquireRendererSelection(renderer)); + int rc = SCE_FONT_ERROR_FATAL; + if (selection && selection->destroy_fn) rc = selection->destroy_fn(renderer); + renderer->selection = nullptr; + const auto freeFn = renderer->free_fn; + void* allocCtx = renderer->alloc_ctx; + if (renderer->workspace) freeFn(allocCtx, renderer->workspace); + freeFn(allocCtx, renderer); + *pRenderer = nullptr; + return rc; +} + +int APS5_VABI sceFontRendererGetOutlineBufferSize(FontRenderer fontRenderer, std::uint32_t* size) { + if (!size) return SCE_FONT_ERROR_INVALID_PARAMETER; + *size = 0; + auto* renderer = static_cast(fontRenderer); + if (!renderer || renderer->magic != RENDERER_MAGIC) return SCE_FONT_ERROR_INVALID_RENDERER; + *size = static_cast(renderer->workspace_size); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontRendererResetOutlineBuffer(FontRenderer fontRenderer) { + auto* renderer = static_cast(fontRenderer); + if (!renderer || renderer->magic != RENDERER_MAGIC) return SCE_FONT_ERROR_INVALID_RENDERER; + if (renderer->workspace && renderer->workspace_size) std::memset(renderer->workspace, 0, static_cast(renderer->workspace_size)); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontRendererSetOutlineBufferPolicy(FontRenderer fontRenderer, std::uint64_t bufferPolicy, std::uint32_t basalSize, std::uint32_t limitSize) { + (void)bufferPolicy; + auto* renderer = static_cast(fontRenderer); + if (!renderer || renderer->magic != RENDERER_MAGIC) return SCE_FONT_ERROR_INVALID_RENDERER; + if (limitSize != 0 && basalSize > limitSize) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (!renderer->alloc_fn || !renderer->free_fn || !renderer->alloc_ctx) return SCE_FONT_ERROR_INVALID_MEMORY; + auto desiredSize = std::max(static_cast(renderer->workspace_size), static_cast(basalSize)); + if (limitSize != 0) desiredSize = std::min(desiredSize, static_cast(limitSize)); + if (desiredSize == 0) desiredSize = 0x4000; + if (!renderer->workspace || renderer->workspace_size != desiredSize) { + void* workspace = renderer->alloc_fn(renderer->alloc_ctx, static_cast(desiredSize)); + if (!workspace) return SCE_FONT_ERROR_ALLOCATION_FAILED; + if (renderer->workspace) renderer->free_fn(renderer->alloc_ctx, renderer->workspace); + renderer->workspace = workspace; + renderer->workspace_size = desiredSize; + } + return SCE_FONT_OK; +} + +int APS5_VABI sceFontAttachDeviceCacheBuffer(FontLibrary library, void* buffer, std::uint32_t size) { + auto* lib = static_cast(library); + if (!lib || lib->magic != LIBRARY_MAGIC) return SCE_FONT_ERROR_INVALID_LIBRARY; + std::uint32_t previous = 0; + if (!AcquireLibraryLock(lib, previous)) return SCE_FONT_ERROR_INVALID_LIBRARY; + std::uint32_t* current = AcquireDeviceCache(lib); + std::uint32_t* stored = current; + int rc; + if (current) { + rc = SCE_FONT_ERROR_ALREADY_ATTACHED; + } else if (size < 0x1020) { + rc = SCE_FONT_ERROR_INVALID_PARAMETER; + } else { + auto* header = static_cast(buffer); + std::uint32_t owned = 0; + if (!header) { + header = static_cast(lib->iface->alloc(lib->alloc_ctx, size)); + owned = 1; + } + if (!header) { + rc = SCE_FONT_ERROR_ALLOCATION_FAILED; + } else { + const std::uint32_t pageCount = (size - 0x1000) >> 12; + header[0] = size; + header[1] = pageCount; + header[2] = 0; + header[3] = pageCount; + const std::uint64_t pageInfo = 0x0FF800001000ull; + std::memcpy(header + 4, &pageInfo, sizeof(pageInfo)); + if (size - 0x1000 > 0x0FFF) { + lib->flags |= owned; + stored = header; + rc = SCE_FONT_OK; + } else { + if (owned) lib->iface->dealloc(lib->alloc_ctx, header); + rc = SCE_FONT_ERROR_INVALID_PARAMETER; + } + } + } + ReleaseDeviceCache(lib, stored); + ReleaseLibraryLock(lib, previous); + return rc; +} + +int APS5_VABI sceFontClearDeviceCache(FontLibrary library) { + auto* lib = static_cast(library); + if (!lib || lib->magic != LIBRARY_MAGIC) return SCE_FONT_ERROR_INVALID_LIBRARY; + std::uint32_t previous = 0; + if (!AcquireLibraryLock(lib, previous)) return SCE_FONT_ERROR_INVALID_LIBRARY; + std::uint32_t* current = AcquireDeviceCache(lib); + int rc = SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER; + if (current) { + current[3] = current[1]; + current[2] = 0; + rc = SCE_FONT_OK; + } + ReleaseDeviceCache(lib, current); + ReleaseLibraryLock(lib, previous); + return rc; +} + +int APS5_VABI sceFontSupportExternalFonts(FontLibrary library, std::uint32_t fontMax, std::uint32_t formats) { + return SupportFonts(library, &FontLibNative::external_fonts_ctx, fontMax, (fontMax << 6) | 0x20u, formats); +} + +int APS5_VABI sceFontSupportSystemFonts(FontLibrary library) { + return SupportFonts(library, &FontLibNative::sysfonts_ctx, 0x40, 0x1020, 0x52); +} + +int APS5_VABI sceFontSetFontsOpenMode(FontLibrary library, std::uint32_t openMode) { + auto* lib = static_cast(library); + if (!lib || lib->magic != LIBRARY_MAGIC) return SCE_FONT_ERROR_INVALID_LIBRARY; + if (openMode > 2) return SCE_FONT_ERROR_INVALID_PARAMETER; + return SCE_FONT_OK; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceFont/src/Open.cpp b/core/libs/prx/libSceFont/src/Open.cpp new file mode 100644 index 000000000..b92f7ebf9 --- /dev/null +++ b/core/libs/prx/libSceFont/src/Open.cpp @@ -0,0 +1,509 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "prx/libSceFont/include/FontInternal.hpp" + +namespace { + +using namespace Font; + +constexpr std::array SYSTEM_FONT_SET_TYPES = { + 0x18070043u, 0x18070044u, 0x18070045u, 0x18070047u, 0x18070053u, 0x18070054u, + 0x18070055u, 0x18070057u, 0x180700C3u, 0x180700C4u, 0x180700C5u, 0x180700C7u, + 0x18070444u, 0x18070447u, 0x18070454u, 0x18070457u, 0x180704C4u, 0x180704C7u, + 0x18071053u, 0x18071054u, 0x18071055u, 0x18071057u, 0x18071454u, 0x18071457u, + 0x18072444u, 0x18072447u, 0x180724C4u, 0x180724C7u, 0x18073454u, 0x18073457u, + 0x180734D4u, 0x180734D7u, 0x18078044u, 0x180780C4u, 0x18079054u, 0x1807A044u, + 0x1807A0C4u, 0x1807A444u, 0x1807A4C4u, 0x1807AC44u, 0x1807ACC4u, 0x1807B054u, + 0x1807B0D4u, 0x1807B454u, 0x1807B4D4u, 0x1807BC54u, 0x1807BCD4u, 0x18080444u, + 0x18080447u, 0x18080454u, 0x18080457u, 0x180804C4u, 0x180804C7u, 0x18081454u, + 0x18081457u, 0x18082444u, 0x18082447u, 0x180824C4u, 0x180824C7u, 0x18083454u, + 0x18083457u, 0x180834D4u, 0x180834D7u, 0x1808A444u, 0x1808A4C4u, 0x1808B454u, + 0x1808B4D4u, 0x180C8044u, 0x180C80C4u, 0x180C9054u, 0x180CA044u, 0x180CA0C4u, + 0x180CAC44u, 0x180CACC4u, 0x180CB054u, 0x180CB0D4u, 0x180CBC54u, 0x180CBCD4u, + 0x18170043u, 0x18170044u, 0x18170045u, 0x18170047u, 0x18170444u, 0x18170447u, + 0x18370044u, 0x18370047u, 0x18370444u, 0x18370447u, +}; + +constexpr std::uint16_t HANDLE_OWNED = 0x10; + +struct OpenRequest { + FontLibNative* library; + std::uint32_t modeLow; + std::uint32_t driverMode; + const void* source; + std::uint32_t sourceSize; + bool matchAddress; + std::uint32_t subFontIndex; + std::int32_t uniqueId; +}; + +FontHandle PrepareHandle(FontLibNative* library, FontHandle existing) { + if (existing) { + GetNativeFont(existing)->flags = 0; + RemoveState(existing); + return existing; + } + auto handle = static_cast(library->iface->alloc(library->alloc_ctx, sizeof(FontHandleOpaque))); + if (!handle) return nullptr; + std::memset(handle, 0, sizeof(FontHandleOpaque)); + GetNativeFont(handle)->flags = HANDLE_OWNED; + return handle; +} + +void DiscardHandle(FontLibNative* library, FontHandle handle) { + auto* font = GetNativeFont(handle); + font->magic = 0; + font->open_info = {}; + font->library = library; + const std::uint16_t previousFlags = font->flags; + font->flags = 0; + RemoveState(handle); + if ((previousFlags & HANDLE_OWNED) != 0) library->iface->dealloc(library->alloc_ctx, handle); +} + +void InitializeOpenedHandle(FontHandleNative* font, FontLibNative* library, FontObj* obj, std::uint32_t modeLow, std::uint32_t packedId, std::uint32_t entryIndex, std::uint32_t subFontIndex) { + const SysDriver* driver = library->sys_driver; + font->magic = HANDLE_MAGIC; + font->flags = static_cast(font->flags | modeLow); + font->open_info = {}; + font->open_info.unique_id_packed = packedId; + font->open_info.ctx_entry_index = entryIndex; + font->open_info.sub_font_index = subFontIndex; + font->library = library; + font->renderer = nullptr; + std::memset(font->reserved_0x38, 0, sizeof(font->reserved_0x38)); + std::memset(&font->cached_style, 0, sizeof(font->cached_style)); + std::memset(font->reserved_0xcc, 0, sizeof(font->reserved_0xcc)); + std::memset(font->reserved_0xe8, 0, sizeof(font->reserved_0xe8)); + font->prevFont = nullptr; + font->nextFont = nullptr; + std::uint16_t metric[2] = {}; + driver->metric(obj, 0x0E00, metric); + font->metricA = metric[0]; + driver->metric(obj, 0xEA00, metric); + font->metricB = metric[0]; + float scale = 0.0f; + driver->scale(obj, metric, &scale); + font->style = {}; + font->style.dpi_x = 0x48; + font->style.dpi_y = 0x48; + font->style.scale_w = scale; + font->style.scale_h = scale; +} + +void SetOpenFlags(FontLibNative* library, std::uint32_t modeLow) { + const std::uint32_t flags = library->flags; + const std::uint32_t firstBit = modeLow == 2 ? 0x100000u : 0x200000u; + const std::uint32_t secondBit = modeLow == 2 ? 0x10000u : 0x20000u; + if ((flags & firstBit) != 0) library->sysfont_flags |= 1; + if ((flags & secondBit) != 0) library->sysfont_flags |= 2; +} + +std::uint32_t ClaimExternalEntry(FontCtxHeader* header, const OpenRequest& request) { + AcquireWordLock(header->lock_word); + auto* entries = static_cast(header->base); + std::uint32_t entryIndex = 0xFFFFFFFFu; + if (header->max_entries != 0 && entries) { + const auto uniqueId = static_cast(request.uniqueId); + const auto address = request.matchAddress ? reinterpret_cast(request.source) : 0; + std::uint32_t firstFree = 0xFFFFFFFFu; + for (std::uint32_t i = 0; i < header->max_entries; ++i) { + const auto& entry = entries[i]; + if ((entry.active != 0 && entry.unique_id == uniqueId) || (request.matchAddress && entry.font_address == address)) { + entryIndex = i; + break; + } + if (firstFree == 0xFFFFFFFFu && entry.active == 0) firstFree = i; + } + if (entryIndex == 0xFFFFFFFFu && firstFree != 0xFFFFFFFFu) { + entryIndex = firstFree; + auto& entry = entries[entryIndex]; + entry = {}; + entry.active = 1; + entry.font_address = address; + entry.unique_id = uniqueId != 0xFFFFFFFFu ? uniqueId : (entryIndex ^ 0x80000000u); + } + } + std::atomic_ref(header->lock_word).store(0, std::memory_order_release); + return entryIndex; +} + +int OpenExternalFont(const OpenRequest& request, std::uint32_t libraryLock, FontHandle* pFontHandle) { + FontLibNative* library = request.library; + FontHandle handle = PrepareHandle(library, *pFontHandle); + if (!handle) { + ReleaseLibraryLock(library, libraryLock); + *pFontHandle = nullptr; + return SCE_FONT_ERROR_ALLOCATION_FAILED; + } + const auto fail = [&](int rc) { + DiscardHandle(library, handle); + ReleaseLibraryLock(library, libraryLock); + *pFontHandle = nullptr; + return rc; + }; + auto* header = static_cast(library->external_fonts_ctx); + const std::uint32_t entryIndex = ClaimExternalEntry(header, request); + if (entryIndex == 0xFFFFFFFFu) return fail(SCE_FONT_ERROR_FONT_OPEN_MAX); + const std::uint32_t packedId = request.uniqueId == -1 ? entryIndex + 0x40000000u : (static_cast(request.uniqueId) | 0x80000000u); + FontObj* fontObj = nullptr; + std::uint32_t lockWord = 0; + auto* entry = AcquireFontCtxEntry(header, entryIndex, request.modeLow, &fontObj, &lockWord); + if (!entry) return fail(SCE_FONT_ERROR_FONT_OPEN_MAX); + const SysDriver* driver = library->sys_driver; + if (!driver->open || !driver->metric || !driver->scale) { + ReleaseFontCtxEntryLock(entry, request.modeLow, lockWord); + return fail(SCE_FONT_ERROR_INVALID_LIBRARY); + } + int rc = SCE_FONT_OK; + bool needOpen = (lockWord & OPEN_BIT) == 0; + if (!needOpen) { + rc = SCE_FONT_ERROR_FONT_OPEN_MAX; + if ((lockWord & COUNT_MASK) != COUNT_MASK) { + if (FontObj* node = FindSubFont(fontObj, request.subFontIndex)) { + ++node->refcount; + fontObj = node; + rc = SCE_FONT_OK; + } else { + needOpen = true; + } + } + } + if (needOpen) { + SetOpenFlags(library, request.modeLow); + rc = driver->open(library, request.driverMode, request.source, request.sourceSize, request.subFontIndex, packedId, &fontObj); + library->sysfont_flags = 0; + if (rc == SCE_FONT_OK) { + *EntryObjectSlot(entry, request.modeLow) = fontObj; + lockWord |= OPEN_BIT; + } + } + if (rc == SCE_FONT_OK) { + InitializeOpenedHandle(GetNativeFont(handle), library, fontObj, request.modeLow, packedId, entryIndex, request.subFontIndex); + ++lockWord; + *pFontHandle = handle; + } + ReleaseFontCtxEntryLock(entry, request.modeLow, lockWord); + if (rc != SCE_FONT_OK) return fail(rc); + if ((GetNativeFont(handle)->flags & HANDLE_OWNED) != 0) LinkFontToLibrary(library, handle); + ReleaseLibraryLock(library, libraryLock); + return SCE_FONT_OK; +} + +int BeginOpen(FontLibrary library, FontHandle* pFontHandle, std::uint32_t& libraryLock) { + auto* lib = static_cast(library); + if (!lib || lib->magic != LIBRARY_MAGIC || !AcquireLibraryLock(lib, libraryLock)) { + if (pFontHandle) *pFontHandle = nullptr; + return SCE_FONT_ERROR_INVALID_LIBRARY; + } + int rc = SCE_FONT_OK; + if (!lib->fontset_registry || !lib->sys_driver || !lib->iface || !lib->iface->alloc || !lib->iface->dealloc) { + rc = SCE_FONT_ERROR_INVALID_LIBRARY; + } else if (!lib->external_fonts_ctx) { + rc = SCE_FONT_ERROR_NO_SUPPORT_FUNCTION; + } + if (rc != SCE_FONT_OK) { + ReleaseLibraryLock(lib, libraryLock); + if (pFontHandle) *pFontHandle = nullptr; + } + return rc; +} + +bool ReadFileBytes(const std::filesystem::path& path, std::vector& bytes) { + std::ifstream file(path, std::ios::binary); + if (!file) return false; + bytes.assign(std::istreambuf_iterator(file), std::istreambuf_iterator()); + return !file.bad(); +} + +void CopyStyleToCache(FontHandleNative* font) { + std::memcpy(&font->cached_style.state, &font->style, sizeof(StyleStateBlock)); +} + +} + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceFontOpenFontMemory(FontLibrary library, const void* fontAddress, std::uint32_t fontSize, const FontOpenDetail* openDetail, FontHandle* pFontHandle) { + std::uint32_t libraryLock = 0; + const int beginRc = BeginOpen(library, pFontHandle, libraryLock); + if (beginRc != SCE_FONT_OK) return beginRc; + auto* lib = static_cast(library); + const std::uint32_t subFontIndex = openDetail ? openDetail->subfont_index : 0u; + const std::int32_t uniqueId = openDetail ? openDetail->unique_id : -1; + if (!fontAddress || fontSize == 0 || uniqueId < -1 || !pFontHandle) { + ReleaseLibraryLock(lib, libraryLock); + if (pFontHandle) *pFontHandle = nullptr; + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + const OpenRequest request{lib, 1, 1, fontAddress, fontSize, true, subFontIndex, uniqueId}; + const int rc = OpenExternalFont(request, libraryLock, pFontHandle); + if (rc != SCE_FONT_OK) return rc; + const auto* bytes = static_cast(fontAddress); + LoadStateFace(ResetState(*pFontHandle), std::vector(bytes, bytes + fontSize), subFontIndex); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontOpenFontFile(FontLibrary library, const char* path, std::uint32_t openMode, const FontOpenDetail* openDetail, FontHandle* pFontHandle) { + if (!library) { + if (pFontHandle) *pFontHandle = nullptr; + return SCE_FONT_ERROR_INVALID_LIBRARY; + } + if (!pFontHandle) return SCE_FONT_ERROR_INVALID_PARAMETER; + const std::filesystem::path hostPath = path ? ResolvePath_nid_no_patch(path) : std::filesystem::path{}; + const std::string hostPathString = hostPath.string(); + std::uint32_t libraryLock = 0; + const int beginRc = BeginOpen(library, pFontHandle, libraryLock); + if (beginRc != SCE_FONT_OK) return beginRc; + auto* lib = static_cast(library); + const std::uint32_t subFontIndex = openDetail ? openDetail->subfont_index : 0u; + const std::int32_t uniqueId = openDetail ? openDetail->unique_id : -1; + const std::uint32_t modeLow = openMode & 0x0Fu; + if (!path || uniqueId < -1 || modeLow < 1 || modeLow > 3 || hostPathString.empty()) { + ReleaseLibraryLock(lib, libraryLock); + *pFontHandle = nullptr; + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + const OpenRequest request{lib, modeLow, modeLow + 4, hostPathString.c_str(), 0, false, subFontIndex, uniqueId}; + const int rc = OpenExternalFont(request, libraryLock, pFontHandle); + if (rc != SCE_FONT_OK) return rc; + std::vector bytes; + FontState& state = ResetState(*pFontHandle); + if (ReadFileBytes(hostPath, bytes)) LoadStateFace(state, std::move(bytes), subFontIndex); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontOpenFontInstance(FontHandle fontHandle, FontHandle setupFont, FontHandle* pFontHandle) { + if (!fontHandle) { + if (pFontHandle) *pFontHandle = nullptr; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + if (!setupFont && !pFontHandle) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* source = GetNativeFont(fontHandle); + std::uint32_t sourceLock = 0; + if (source->magic != HANDLE_MAGIC || !AcquireFontLock(source, sourceLock)) { + if (pFontHandle) *pFontHandle = nullptr; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + const auto fail = [&](int rc) { + ReleaseFontLock(source, sourceLock); + if (pFontHandle) *pFontHandle = nullptr; + return rc; + }; + auto* library = static_cast(source->library); + if (!library || library->magic != LIBRARY_MAGIC) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); + const std::uint32_t modeLow = source->flags & 0x0Fu; + const std::uint32_t subFontIndex = source->open_info.sub_font_index; + const std::uint32_t entryIndex = source->open_info.ctx_entry_index; + auto* header = static_cast(library->external_fonts_ctx); + if (!header) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); + FontHandle target = setupFont; + bool owned = false; + if (!target) { + if (!library->iface || !library->iface->alloc || !library->iface->dealloc) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE); + target = static_cast(library->iface->alloc(library->alloc_ctx, sizeof(FontHandleOpaque))); + if (!target) return fail(SCE_FONT_ERROR_ALLOCATION_FAILED); + owned = true; + } else { + RemoveState(target); + } + std::memcpy(target, fontHandle, sizeof(FontHandleOpaque)); + auto* font = GetNativeFont(target); + font->lock_word = 0; + font->cached_style.cache_lock_word = 0; + font->flags = static_cast((font->flags & 0xFF0Fu) | (owned ? HANDLE_OWNED : 0u)); + auto* entries = static_cast(header->base); + if (!entries || header->max_entries < 1) { + font->magic = 0; + if (owned) library->iface->dealloc(library->alloc_ctx, target); + return fail(SCE_FONT_ERROR_INVALID_PARAMETER); + } + int rc = SCE_FONT_OK; + if (static_cast(entryIndex) >= 0) { + if (entryIndex >= header->max_entries || modeLow < 1 || modeLow > 3) { + rc = SCE_FONT_ERROR_INVALID_PARAMETER; + } else { + FontCtxEntry* entry = entries + entryIndex; + std::uint32_t* lockWord = EntryLockWord(entry, modeLow); + const std::uint32_t previous = AcquireWordLock(*lockWord); + FontObj* node = FindSubFont(static_cast(*EntryObjectSlot(entry, modeLow)), subFontIndex); + std::uint32_t updated = previous; + if ((previous & OPEN_BIT) == 0) { + rc = SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } else if ((previous & COUNT_MASK) == COUNT_MASK) { + rc = SCE_FONT_ERROR_FONT_OPEN_MAX; + } else if (!node) { + rc = SCE_FONT_ERROR_FATAL; + } else { + ++node->refcount; + ++updated; + } + std::atomic_ref(*lockWord).store(updated & ~LOCK_BIT, std::memory_order_release); + } + } + if (rc != SCE_FONT_OK) { + font->magic = 0; + font->open_info = {}; + font->library = library; + const std::uint16_t previousFlags = font->flags; + font->flags = 0; + if ((previousFlags & HANDLE_OWNED) != 0) library->iface->dealloc(library->alloc_ctx, target); + RemoveState(target); + return fail(rc); + } + if (owned) LinkFontToLibrary(library, target); + ReleaseFontLock(source, sourceLock); + FontState& targetState = ResetState(target); + if (FontState* sourceState = TryGetState(fontHandle)) { + targetState.scaleW = sourceState->scaleW; + targetState.scaleH = sourceState->scaleH; + if (!sourceState->faceData.empty()) LoadStateFace(targetState, sourceState->faceData, subFontIndex); + } + if (pFontHandle) *pFontHandle = target; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontOpenFontSet(FontLibrary library, std::uint32_t fontSetType, std::uint32_t openMode, const FontOpenDetail* openDetail, FontHandle* pFontHandle) { + (void)openDetail; + auto* lib = static_cast(library); + if (!lib || lib->magic != LIBRARY_MAGIC) { + if (pFontHandle) *pFontHandle = nullptr; + return SCE_FONT_ERROR_INVALID_LIBRARY; + } + if (!pFontHandle) return SCE_FONT_ERROR_INVALID_PARAMETER; + std::uint32_t libraryLock = 0; + if (!AcquireLibraryLock(lib, libraryLock)) { + *pFontHandle = nullptr; + return SCE_FONT_ERROR_INVALID_LIBRARY; + } + int rc; + if (openMode != 1 && openMode != 2 && openMode != 3) { + rc = SCE_FONT_ERROR_INVALID_PARAMETER; + } else if (!lib->fontset_registry || !lib->sys_driver) { + rc = SCE_FONT_ERROR_INVALID_LIBRARY; + } else if (!lib->iface || !lib->iface->alloc || !lib->iface->dealloc) { + rc = SCE_FONT_ERROR_INVALID_MEMORY; + } else if (!lib->sysfonts_ctx) { + rc = SCE_FONT_ERROR_NO_SUPPORT_FUNCTION; + } else if (std::find(SYSTEM_FONT_SET_TYPES.begin(), SYSTEM_FONT_SET_TYPES.end(), fontSetType) == SYSTEM_FONT_SET_TYPES.end()) { + rc = SCE_FONT_ERROR_NO_SUPPORT_FONTSET; + } else { + rc = SCE_FONT_ERROR_FONT_OPEN_FAILED; + } + ReleaseLibraryLock(lib, libraryLock); + *pFontHandle = nullptr; + return rc; +} + +int APS5_VABI sceFontCloseFont(FontHandle fontHandle) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + auto* library = static_cast(font->library); + std::uint32_t libraryLock = 0; + if (!library || library->magic != LIBRARY_MAGIC || !AcquireLibraryLock(library, libraryLock)) { + ReleaseFontLock(font, fontLock); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + ReleaseFontObjectsForHandle(font); + const std::uint16_t flags = font->flags; + if ((flags & HANDLE_OWNED) != 0) UnlinkFontFromLibrary(library, fontHandle); + RemoveState(fontHandle); + font->magic = 0; + font->open_info = {}; + font->renderer = nullptr; + font->flags = 0; + font->lock_word = 0; + if ((flags & HANDLE_OWNED) != 0) library->iface->dealloc(library->alloc_ctx, fontHandle); + ReleaseLibraryLock(library, libraryLock); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontGetLibrary(FontHandle fontHandle, FontLibrary* pLibrary) { + if (!pLibrary) return SCE_FONT_ERROR_INVALID_PARAMETER; + const auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != HANDLE_MAGIC) { + *pLibrary = nullptr; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + *pLibrary = font->library; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontBindRenderer(FontHandle fontHandle, FontRenderer renderer) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + std::uint32_t cachedLock = 0; + if (!AcquireCachedStyleLock(font, cachedLock)) { + ReleaseFontLock(font, fontLock); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + int rc = SCE_FONT_ERROR_ALREADY_BOUND_RENDERER; + if (!font->renderer) { + rc = SCE_FONT_ERROR_INVALID_RENDERER; + if (renderer && static_cast(renderer)->magic == RENDERER_MAGIC) { + CopyStyleToCache(font); + font->renderer = renderer; + rc = SCE_FONT_OK; + } + } + ReleaseCachedStyleLock(font, cachedLock); + ReleaseFontLock(font, fontLock); + return rc; +} + +int APS5_VABI sceFontRebindRenderer(FontHandle fontHandle) { + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != HANDLE_MAGIC) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + std::uint32_t fontLock = 0; + if (!AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + std::uint32_t cachedLock = 0; + if (!AcquireCachedStyleLock(font, cachedLock)) { + ReleaseFontLock(font, fontLock); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + int rc; + if (!font->renderer) { + rc = SCE_FONT_ERROR_NOT_BOUND_RENDERER; + } else if (static_cast(font->renderer)->magic != RENDERER_MAGIC) { + rc = SCE_FONT_ERROR_FATAL; + } else { + CopyStyleToCache(font); + rc = SCE_FONT_OK; + } + ReleaseCachedStyleLock(font, cachedLock); + ReleaseFontLock(font, fontLock); + return rc; +} + +int APS5_VABI sceFontUnbindRenderer(FontHandle fontHandle) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t cachedLock = 0; + if (!font || !AcquireCachedStyleLock(font, cachedLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + int rc = SCE_FONT_ERROR_NOT_BOUND_RENDERER; + if (font->renderer) { + font->renderer = nullptr; + rc = SCE_FONT_OK; + } + ReleaseCachedStyleLock(font, cachedLock); + return rc; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceFont/src/Render.cpp b/core/libs/prx/libSceFont/src/Render.cpp new file mode 100644 index 000000000..74cdde106 --- /dev/null +++ b/core/libs/prx/libSceFont/src/Render.cpp @@ -0,0 +1,449 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H + +#include "prx/libSceFont/include/FontInternal.hpp" + +namespace { + +using namespace Font; + +constexpr std::uint16_t GENERATE_GLYPH_DETAIL_ID = 0x0FD3; + +std::uint16_t ClampToU16(float value) { + if (value <= 0.0f) return 0; + return static_cast(std::lround(std::min(value, static_cast(std::numeric_limits::max())))); +} + +std::uint8_t* LayoutCacheBytes(FontHandleNative* font) { + return reinterpret_cast(&font->cached_style) + offsetof(CachedStyle, layout_cache_bytes); +} + +RenderSurfaceSystemUse* SurfaceSystemUse(FontRenderSurface* surface) { + return reinterpret_cast(surface->reserved_q); +} + +bool SurfaceScaleFrame(FontRenderSurface* surface, StyleStateBlock& state, int& rc) { + if (!surface || (surface->styleFlag & 0x1) == 0) return false; + const FontStyleFrame* frame = SurfaceSystemUse(surface)->styleframe; + if (!frame || (frame->flags1 & STYLE_FRAME_FLAG_SCALE) == 0) return false; + state = {}; + state.dpi_x = frame->hDpi; + state.dpi_y = frame->vDpi; + if ((frame->flags1 & STYLE_FRAME_FLAG_SLANT) != 0) state.slant_ratio = frame->slantRatio; + if ((frame->flags1 & STYLE_FRAME_FLAG_WEIGHT) != 0) { + state.effect_weight_x = frame->effectWeightX; + state.effect_weight_y = frame->effectWeightY; + } + StyleStateBlock frameScale{}; + frameScale.dpi_x = frame->hDpi; + frameScale.dpi_y = frame->vDpi; + frameScale.scale_unit = frame->scaleUnit; + frameScale.scale_w = frame->scalePixelW; + frameScale.scale_h = frame->scalePixelH; + rc = StyleStateGetScalePixel(&frameScale, &state.scale_w, &state.scale_h); + return true; +} + +int CachedBaseline(FontHandle handle, FontHandleNative* font, float& baseline) { + int rc = SCE_FONT_OK; + const std::uint8_t flags = CachedStyleCacheFlags(font->cached_style); + if ((flags & 0x1) == 0) { + rc = ComputeHorizontalLayout(handle, &font->cached_style.state, LayoutCacheBytes(font)); + if (rc == SCE_FONT_OK) CachedStyleSetCacheFlags(font->cached_style, static_cast(flags | 0x1)); + } + if (rc == SCE_FONT_OK) baseline = LoadFloat(LayoutCacheBytes(font), HORIZONTAL_BASELINE); + return rc; +} + +int CachedColumnOffset(FontHandle handle, FontHandleNative* font, float& offset) { + int rc = SCE_FONT_OK; + const std::uint8_t flags = CachedStyleCacheFlags(font->cached_style); + if ((flags & 0x2) == 0) { + std::uint8_t layout[VERTICAL_LAYOUT_SIZE] = {}; + rc = ComputeVerticalLayout(handle, &font->cached_style.state, layout); + if (rc == SCE_FONT_OK) { + offset = LoadFloat(layout, VERTICAL_BASELINE_OFFSET_X); + CachedStyleSetScalar(font->cached_style, offset); + CachedStyleSetCacheFlags(font->cached_style, static_cast(flags | 0x2)); + } + } + if (rc == SCE_FONT_OK && (CachedStyleCacheFlags(font->cached_style) & 0x2) != 0) offset = CachedStyleGetScalar(font->cached_style); + return rc; +} + +int RenderDirectional(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result, std::uint16_t direction) { + auto* font = GetNativeFont(fontHandle); + if (!font || font->magic != HANDLE_MAGIC) { + ClearRenderOutputs(metrics, result); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + if (code == 0) { + ClearRenderOutputs(metrics, result); + return SCE_FONT_ERROR_NO_SUPPORT_CODE; + } + if (!surface || !metrics || !result) { + ClearRenderOutputs(metrics, result); + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + std::uint32_t fontLock = 0; + if (!AcquireFontLock(font, fontLock)) { + ClearRenderOutputs(metrics, result); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + CachedStyleSetDirectionWord(font->cached_style, direction); + const int rc = RenderCharGlyphImageCore(fontHandle, code, surface, x, y, metrics, result); + ReleaseFontLock(font, fontLock); + if (rc != SCE_FONT_OK) ClearRenderOutputs(metrics, result); + return rc; +} + +} + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceFontGetCharGlyphMetrics(FontHandle fontHandle, std::uint32_t code, FontGlyphMetrics* metrics) { + return GetCharGlyphMetrics(fontHandle, code, metrics, false); +} + +int APS5_VABI sceFontGetRenderCharGlyphMetrics(FontHandle fontHandle, std::uint32_t code, FontGlyphMetrics* metrics) { + return GetCharGlyphMetrics(fontHandle, code, metrics, true); +} + +int APS5_VABI sceFontGetHorizontalLayout(FontHandle fontHandle, FontHorizontalLayout* layout) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) { + if (layout) *layout = {}; + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + if (!layout) { + ReleaseFontLock(font, fontLock); + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + std::uint8_t blocks[HORIZONTAL_LAYOUT_SIZE] = {}; + const int rc = ComputeHorizontalLayout(fontHandle, &font->style, blocks); + ReleaseFontLock(font, fontLock); + if (rc != SCE_FONT_OK) { + *layout = {}; + return rc; + } + layout->baselineOffset = LoadFloat(blocks, HORIZONTAL_BASELINE); + layout->lineAdvance = LoadFloat(blocks, HORIZONTAL_LINE_ADVANCE); + layout->decorationExtent = LoadFloat(blocks, HORIZONTAL_EFFECT_HEIGHT); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontGetVerticalLayout(FontHandle fontHandle, FontVerticalLayout* layout) { + int rc = SCE_FONT_ERROR_INVALID_FONT_HANDLE; + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (font && font->magic == HANDLE_MAGIC && AcquireFontLock(font, fontLock)) { + if (!layout) { + rc = SCE_FONT_ERROR_INVALID_PARAMETER; + } else { + std::uint8_t blocks[VERTICAL_LAYOUT_SIZE] = {}; + rc = ComputeVerticalLayout(fontHandle, &font->style, blocks); + if (rc == SCE_FONT_OK) { + layout->baselineOffsetX = LoadFloat(blocks, VERTICAL_BASELINE_OFFSET_X); + layout->columnAdvance = LoadFloat(blocks, VERTICAL_COLUMN_ADVANCE); + layout->decorationSpan = LoadFloat(blocks, VERTICAL_DECORATION_SPAN); + } + } + ReleaseFontLock(font, fontLock); + if (rc == SCE_FONT_OK) return rc; + } + if (layout) *layout = {}; + return rc; +} + +int APS5_VABI sceFontGetKerning(FontHandle fontHandle, std::uint32_t preCode, std::uint32_t code, FontKerning* kerning) { + if (!kerning) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + *kerning = {}; + const FontState* state = TryGetState(fontHandle); + const FT_Face face = state ? state->face : nullptr; + const FT_UInt previousGlyph = face ? FT_Get_Char_Index(face, preCode) : 0; + const FT_UInt glyph = face ? FT_Get_Char_Index(face, code) : 0; + if (face && previousGlyph != 0 && glyph != 0) { + const auto charW = static_cast(static_cast(state->scaleW * 64.0f)); + const auto charH = static_cast(static_cast(state->scaleH * 64.0f)); + FT_Set_Char_Size(face, charW, charH, 72, 72); + FT_Vector delta{}; + FT_Get_Kerning(face, previousGlyph, glyph, FT_KERNING_DEFAULT, &delta); + kerning->offsetX = static_cast(delta.x) / 64.0f; + } + ReleaseFontLock(font, fontLock); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontRenderCharGlyphImage(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) { + ClearRenderOutputs(metrics, result); + return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + } + int preRc = SCE_FONT_OK; + StyleStateBlock frameState{}; + const bool horizontal = static_cast(font->flags) >= 0; + float xUsed = x; + float yUsed = y; + if (horizontal) { + float baseline = 0.0f; + if (!SurfaceScaleFrame(surface, frameState, preRc)) { + preRc = CachedBaseline(fontHandle, font, baseline); + } else { + std::uint8_t layout[HORIZONTAL_LAYOUT_SIZE] = {}; + if (preRc == SCE_FONT_OK) preRc = ComputeHorizontalLayout(fontHandle, &frameState, layout); + if (preRc == SCE_FONT_OK) { + baseline = LoadFloat(layout, HORIZONTAL_BASELINE); + SurfaceSystemUse(surface)->catchedScale = baseline; + } + } + yUsed = y + baseline; + CachedStyleSetDirectionWord(font->cached_style, 1); + } else { + float offset = 0.0f; + if (!SurfaceScaleFrame(surface, frameState, preRc)) { + preRc = CachedColumnOffset(fontHandle, font, offset); + } else { + std::uint8_t layout[VERTICAL_LAYOUT_SIZE] = {}; + if (preRc == SCE_FONT_OK) preRc = ComputeVerticalLayout(fontHandle, &frameState, layout); + if (preRc == SCE_FONT_OK) { + offset = LoadFloat(layout, VERTICAL_BASELINE_OFFSET_X); + SurfaceSystemUse(surface)->catchedScale = offset; + } else { + preRc = CachedColumnOffset(fontHandle, font, offset); + } + } + xUsed = x + offset; + CachedStyleSetDirectionWord(font->cached_style, 2); + } + int rc; + if (code == 0) { + rc = SCE_FONT_ERROR_NO_SUPPORT_CODE; + } else if (!surface || !metrics || !result) { + rc = SCE_FONT_ERROR_INVALID_PARAMETER; + } else if (preRc != SCE_FONT_OK) { + rc = preRc; + } else { + rc = RenderCharGlyphImageCore(fontHandle, code, surface, xUsed, yUsed, metrics, result); + } + ReleaseFontLock(font, fontLock); + if (rc != SCE_FONT_OK) ClearRenderOutputs(metrics, result); + return rc; +} + +int APS5_VABI sceFontRenderCharGlyphImageHorizontal(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { + return RenderDirectional(fontHandle, code, surface, x, y, metrics, result, 1); +} + +int APS5_VABI sceFontRenderCharGlyphImageVertical(FontHandle fontHandle, std::uint32_t code, FontRenderSurface* surface, float x, float y, FontGlyphMetrics* metrics, FontRenderOutput* result) { + return RenderDirectional(fontHandle, code, surface, x, y, metrics, result, 2); +} + +void APS5_VABI sceFontRenderSurfaceInit(FontRenderSurface* renderSurface, void* buffer, int bufWidthByte, int pixelSizeByte, int widthPixel, int heightPixel) { + if (!renderSurface) return; + const auto width = static_cast(std::max(widthPixel, 0)); + const auto height = static_cast(std::max(heightPixel, 0)); + renderSurface->buffer = buffer; + renderSurface->widthByte = bufWidthByte; + renderSurface->pixelSizeByte = static_cast(pixelSizeByte); + renderSurface->pad0 = 0; + renderSurface->styleFlag = 0; + renderSurface->pad2 = 0; + renderSurface->width = static_cast(width); + renderSurface->height = static_cast(height); + renderSurface->sc_x0 = 0; + renderSurface->sc_y0 = 0; + renderSurface->sc_x1 = width; + renderSurface->sc_y1 = height; +} + +void APS5_VABI sceFontRenderSurfaceSetScissor(FontRenderSurface* renderSurface, int x0, int y0, int w, int h) { + if (!renderSurface) return; + const auto surfaceW = static_cast(renderSurface->width); + if (surfaceW != 0) { + std::uint32_t x1; + std::uint32_t left; + auto width = static_cast(w); + if (x0 < 0) { + x1 = width + static_cast(x0); + if (surfaceW < x1) x1 = surfaceW; + if (width <= static_cast(-x0)) x1 = 0; + left = 0; + } else { + x1 = surfaceW; + left = surfaceW; + if (static_cast(x0) <= surfaceW) { + if (surfaceW < width) width = surfaceW; + x1 = width + static_cast(x0); + left = static_cast(x0); + if (surfaceW < x1) x1 = surfaceW; + } + } + renderSurface->sc_x0 = left; + renderSurface->sc_x1 = x1; + } + const auto surfaceH = static_cast(renderSurface->height); + if (surfaceH == 0) return; + std::uint32_t top; + std::uint32_t y1 = surfaceH; + auto height = static_cast(h); + if (y0 < 0) { + top = 0; + if (height <= static_cast(-y0)) { + renderSurface->sc_y0 = 0; + renderSurface->sc_y1 = 0; + return; + } + } else { + if (surfaceH < static_cast(y0)) { + renderSurface->sc_y0 = surfaceH; + renderSurface->sc_y1 = y1; + return; + } + top = static_cast(y0); + if (surfaceH < height) height = surfaceH; + } + const std::uint32_t candidate = height + static_cast(y0); + if (candidate <= surfaceH) y1 = candidate; + renderSurface->sc_y0 = top; + renderSurface->sc_y1 = y1; +} + +int APS5_VABI sceFontRenderSurfaceSetStyleFrame(FontRenderSurface* renderSurface, FontStyleFrame* styleFrame) { + if (!renderSurface) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (!styleFrame) { + renderSurface->styleFlag &= static_cast(~0x1u); + renderSurface->reserved_q[0] = 0; + renderSurface->reserved_q[1] = 0; + return SCE_FONT_OK; + } + if (styleFrame->magic != STYLE_FRAME_MAGIC) return SCE_FONT_ERROR_INVALID_PARAMETER; + renderSurface->styleFlag |= 0x1; + renderSurface->reserved_q[0] = reinterpret_cast(styleFrame); + renderSurface->reserved_q[1] = 0; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontGenerateCharGlyph(FontHandle fontHandle, std::uint32_t code, const FontGenerateGlyphDetail* detail, FontGlyph* pGlyph) { + if (!pGlyph) return SCE_FONT_ERROR_INVALID_PARAMETER; + *pGlyph = nullptr; + if (!fontHandle) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + if (code == 0) return SCE_FONT_ERROR_NO_SUPPORT_CODE; + const FontState* state = TryGetState(fontHandle); + if (!state) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + const std::uint8_t glyphForm = detail ? detail->glyph_form : 0; + const std::uint8_t metricsForm = detail ? detail->metrics_form : 0; + const std::uint16_t formOptions = detail ? detail->form_options : 0; + const FontMemory* glyphMemory = detail ? detail->mem : nullptr; + if (detail && detail->id != GENERATE_GLYPH_DETAIL_ID) return SCE_FONT_ERROR_INVALID_PARAMETER; + if ((formOptions & static_cast(~0x11u)) != 0) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (glyphForm == 0 && metricsForm != 0) return SCE_FONT_ERROR_INVALID_PARAMETER; + if ((glyphForm != 0 && metricsForm == 0) || glyphForm > 1 || metricsForm > 4) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (glyphMemory && glyphMemory->mem_kind != MEMORY_MAGIC) return SCE_FONT_ERROR_INVALID_PARAMETER; + FontGlyphMetrics metrics{}; + const int rc = GetCharGlyphMetrics(fontHandle, code, &metrics, false); + if (rc != SCE_FONT_OK) return rc; + auto* generated = new (std::nothrow) GeneratedGlyph(); + if (!generated) return SCE_FONT_ERROR_ALLOCATION_FAILED; + generated->codepoint = code; + generated->metrics = metrics; + generated->glyph.magic = GLYPH_MAGIC; + generated->glyph.flags = formOptions; + generated->glyph.glyph_form = glyphForm; + generated->glyph.metrics_form = metricsForm; + generated->glyph.em_size = ClampToU16(state->scaleH); + generated->glyph.baseline = ClampToU16(metrics.Horizontal.bearingY); + generated->glyph.height_px = ClampToU16(metrics.height); + generated->glyph.origin_x = 0; + generated->glyph.origin_y = generated->glyph.baseline; + generated->glyph.scale_x = state->scaleW; + generated->glyph.base_scale = state->scaleH; + generated->glyph.memory = glyphMemory; + generated->owner = fontHandle; + generated->outline.outline_flags = generated->glyph.flags; + TrackGeneratedGlyph(&generated->glyph); + *pGlyph = &generated->glyph; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontDeleteGlyph(const FontMemory* memory, FontGlyph* pGlyph) { + (void)memory; + if (!pGlyph) return SCE_FONT_ERROR_INVALID_PARAMETER; + const FontGlyph glyph = *pGlyph; + if (!glyph || glyph->magic != GLYPH_MAGIC || !ForgetGeneratedGlyph(glyph)) return SCE_FONT_ERROR_INVALID_GLYPH; + delete reinterpret_cast(glyph); + *pGlyph = nullptr; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontGlyphGetGlyphForm(FontGlyph glyph) { + if (!glyph || glyph->magic != GLYPH_MAGIC) return SCE_FONT_ERROR_INVALID_GLYPH; + return glyph->glyph_form; +} + +int APS5_VABI sceFontGlyphGetMetricsForm(FontGlyph glyph) { + if (!glyph || glyph->magic != GLYPH_MAGIC) return SCE_FONT_ERROR_INVALID_GLYPH; + return glyph->metrics_form; +} + +int APS5_VABI sceFontGlyphGetScalePixel(FontGlyph glyph, float* w, float* h) { + if (!glyph || glyph->magic != GLYPH_MAGIC || (!w && !h)) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (w) *w = glyph->scale_x; + if (h) *h = glyph->base_scale; + return SCE_FONT_OK; +} + +const FontGlyphMetrics* APS5_VABI sceFontGlyphRefersMetrics(FontGlyph glyph) { + auto* generated = TryGetGeneratedGlyph(glyph); + return generated ? &generated->metrics : nullptr; +} + +const FontGlyphMetricsHorizontal* APS5_VABI sceFontGlyphRefersMetricsHorizontal(FontGlyph glyph) { + auto* generated = TryGetGeneratedGlyph(glyph); + if (!generated) return nullptr; + PopulateGlyphMetricVariants(*generated); + return &generated->metricsHorizontal; +} + +const FontGlyphMetricsHorizontalAdvance* APS5_VABI sceFontGlyphRefersMetricsHorizontalAdvance(FontGlyph glyph) { + auto* generated = TryGetGeneratedGlyph(glyph); + if (!generated) return nullptr; + PopulateGlyphMetricVariants(*generated); + return &generated->metricsHorizontalAdvance; +} + +const FontGlyphMetricsHorizontalX* APS5_VABI sceFontGlyphRefersMetricsHorizontalX(FontGlyph glyph) { + auto* generated = TryGetGeneratedGlyph(glyph); + if (!generated) return nullptr; + PopulateGlyphMetricVariants(*generated); + return &generated->metricsHorizontalX; +} + +FontGlyphOutline* APS5_VABI sceFontGlyphRefersOutline(FontGlyph glyph) { + if (!glyph || glyph->magic != GLYPH_MAGIC || glyph->glyph_form != 1) return nullptr; + auto* generated = TryGetGeneratedGlyph(glyph); + if (!generated) return nullptr; + if (!generated->outlineInitialized && !BuildTrueOutline(*generated)) BuildBoundingOutline(*generated); + return &generated->outline; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceFont/src/State.cpp b/core/libs/prx/libSceFont/src/State.cpp new file mode 100644 index 000000000..58b7e0e49 --- /dev/null +++ b/core/libs/prx/libSceFont/src/State.cpp @@ -0,0 +1,433 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "prx/libSceFont/include/FontInternal.hpp" + +namespace { + +std::mutex stateMutex; +std::unordered_map> fontStates; +std::mutex glyphMutex; +std::unordered_set generatedGlyphs; + +FT_Library HostFreeTypeLibrary() { + static const FT_Library library = [] { + FT_Library created = nullptr; + if (FT_Init_FreeType(&created) != 0) return static_cast(nullptr); + return created; + }(); + return library; +} + +bool AcquireMagicLock(std::uint32_t& word, const std::uint16_t& magic, std::uint16_t expectedMagic, std::uint32_t& previous) { + for (;;) { + const std::uint32_t current = std::atomic_ref(word).load(std::memory_order_acquire); + if (magic != expectedMagic) return false; + if (static_cast(current) >= 0) { + std::uint32_t expected = current; + if (std::atomic_ref(word).compare_exchange_weak(expected, current | Font::LOCK_BIT, std::memory_order_acq_rel)) { + previous = current; + return true; + } + } + Font::Backoff(); + } +} + +void ReleaseWord(std::uint32_t& word, std::uint32_t previous) { + std::atomic_ref(word).store(previous & ~Font::LOCK_BIT, std::memory_order_release); +} + +} + +Font::FontState::~FontState() { + if (face) FT_Done_Face(face); +} + +void Font::Backoff() { + std::this_thread::sleep_for(std::chrono::microseconds(30)); +} + +std::uint32_t Font::AcquireWordLock(std::uint32_t& word) { + for (;;) { + const std::uint32_t current = std::atomic_ref(word).load(std::memory_order_acquire); + if (static_cast(current) >= 0) { + std::uint32_t expected = current; + if (std::atomic_ref(word).compare_exchange_weak(expected, current | LOCK_BIT, std::memory_order_acq_rel)) return current; + } + Backoff(); + } +} + +Font::FontHandleNative* Font::GetNativeFont(FontHandle handle) { + return reinterpret_cast(handle); +} + +bool Font::AcquireLibraryLock(FontLibNative* library, std::uint32_t& previous) { + return library && AcquireMagicLock(library->lock_word, library->magic, LIBRARY_MAGIC, previous); +} + +void Font::ReleaseLibraryLock(FontLibNative* library, std::uint32_t previous) { + if (library) ReleaseWord(library->lock_word, previous); +} + +bool Font::AcquireFontLock(FontHandleNative* font, std::uint32_t& previous) { + return font && AcquireMagicLock(font->lock_word, font->magic, HANDLE_MAGIC, previous); +} + +void Font::ReleaseFontLock(FontHandleNative* font, std::uint32_t previous) { + if (font) ReleaseWord(font->lock_word, previous); +} + +bool Font::AcquireCachedStyleLock(FontHandleNative* font, std::uint32_t& previous) { + return font && AcquireMagicLock(font->cached_style.cache_lock_word, font->magic, HANDLE_MAGIC, previous); +} + +void Font::ReleaseCachedStyleLock(FontHandleNative* font, std::uint32_t previous) { + if (font) ReleaseWord(font->cached_style.cache_lock_word, previous); +} + +namespace { + +FontHandle* AcquireFontList(Font::FontLibNative* library) { + auto* const busy = reinterpret_cast(std::numeric_limits::max()); + std::atomic_ref slot(library->list_head_ptr); + for (;;) { + FontHandle* current = slot.load(std::memory_order_acquire); + if (current != busy && slot.compare_exchange_weak(current, busy, std::memory_order_acq_rel)) return current; + Font::Backoff(); + } +} + +void ReleaseFontList(Font::FontLibNative* library, FontHandle* head) { + std::atomic_ref(library->list_head_ptr).store(head, std::memory_order_release); +} + +} + +void Font::LinkFontToLibrary(FontLibNative* library, FontHandle handle) { + FontHandle* head = AcquireFontList(library); + if (head) { + const FontHandle oldHead = *head; + auto* font = GetNativeFont(handle); + font->prevFont = nullptr; + font->nextFont = oldHead; + if (oldHead) GetNativeFont(oldHead)->prevFont = handle; + *head = handle; + } + ReleaseFontList(library, head); +} + +void Font::UnlinkFontFromLibrary(FontLibNative* library, FontHandle handle) { + FontHandle* head = AcquireFontList(library); + auto* font = GetNativeFont(handle); + if (head) { + if (font->prevFont) { + GetNativeFont(font->prevFont)->nextFont = font->nextFont; + } else if (*head == handle) { + *head = font->nextFont; + } + if (font->nextFont) GetNativeFont(font->nextFont)->prevFont = font->prevFont; + } + font->prevFont = nullptr; + font->nextFont = nullptr; + ReleaseFontList(library, head); +} + +std::uint32_t* Font::EntryLockWord(FontCtxEntry* entry, std::uint32_t modeLow) { + if (modeLow == 3) return &entry->lock_mode3; + if (modeLow == 1) return &entry->lock_mode1; + return &entry->lock_mode2; +} + +void** Font::EntryObjectSlot(FontCtxEntry* entry, std::uint32_t modeLow) { + if (modeLow == 3) return &entry->obj_mode3; + if (modeLow == 1) return &entry->obj_mode1; + return &entry->obj_mode2; +} + +Font::FontCtxEntry* Font::AcquireFontCtxEntry(void* ctx, std::uint32_t index, std::uint32_t modeLow, FontObj** outObject, std::uint32_t* outLockWord) { + if (!ctx || !outObject || !outLockWord) return nullptr; + auto* header = static_cast(ctx); + if (index >= header->max_entries || !header->base) { + *outLockWord = 0; + *outObject = nullptr; + return nullptr; + } + auto* entry = static_cast(header->base) + index; + if (modeLow != 1 && modeLow != 2 && modeLow != 3) { + *outLockWord = 0; + *outObject = nullptr; + return entry; + } + *outLockWord = AcquireWordLock(*EntryLockWord(entry, modeLow)) | LOCK_BIT; + *outObject = static_cast(*EntryObjectSlot(entry, modeLow)); + return entry; +} + +void Font::ReleaseFontCtxEntryLock(FontCtxEntry* entry, std::uint32_t modeLow, std::uint32_t lockWord) { + if (!entry) return; + if ((lockWord & COUNT_MASK) == 0) *EntryObjectSlot(entry, modeLow) = nullptr; + if ((lockWord & COUNT_MASK) == 0 && !entry->obj_mode1 && !entry->obj_mode3 && !entry->obj_mode2) { + entry->unique_id = 0; + entry->active = 0; + } + ReleaseWord(*EntryLockWord(entry, modeLow), lockWord); +} + +Font::FontObj* Font::FindSubFont(FontObj* head, std::uint32_t subFontIndex) { + for (auto* node = head; node; node = node->next) { + if (node->sub_font_index == subFontIndex) return node; + } + return nullptr; +} + +void Font::ReleaseFontObjectsForHandle(FontHandleNative* font) { + auto* library = static_cast(font->library); + if (!library || library->magic != LIBRARY_MAGIC || !library->sys_driver || !library->sys_driver->close) return; + const std::uint32_t fontId = font->open_info.ctx_entry_index; + if (static_cast(fontId) < 0) return; + const std::uint32_t modeLow = font->flags & 0x0Fu; + FontObj* head = nullptr; + std::uint32_t lockWord = 0; + auto* entry = AcquireFontCtxEntry(library->external_fonts_ctx, fontId, modeLow, &head, &lockWord); + if (!entry) return; + FontObj* match = FindSubFont(head, font->open_info.sub_font_index); + const std::uint32_t openFlag = lockWord & OPEN_BIT; + const std::uint32_t countBits = lockWord & COUNT_MASK; + if (openFlag != 0 && countBits != 0 && match) { + const std::uint32_t refcount = match->refcount; + const std::uint32_t decremented = lockWord - 1; + lockWord = decremented; + if (refcount == 1) { + FontObj* next = match->next; + FontObj* prev = match->prev; + library->sys_driver->close(match, 0); + if (!prev) *EntryObjectSlot(entry, modeLow) = next; + } else if (refcount != 0) { + match->refcount = refcount - 1; + } + if (countBits == 1) lockWord = ~openFlag & decremented; + } + ReleaseFontCtxEntryLock(entry, modeLow, lockWord); +} + +Font::FontState* Font::TryGetState(FontHandle handle) { + if (!handle) return nullptr; + std::lock_guard lock(stateMutex); + const auto it = fontStates.find(handle); + return it == fontStates.end() ? nullptr : it->second.get(); +} + +Font::FontState& Font::ResetState(FontHandle handle) { + std::lock_guard lock(stateMutex); + auto& slot = fontStates[handle]; + slot = std::make_unique(); + return *slot; +} + +void Font::RemoveState(FontHandle handle) { + if (!handle) return; + std::lock_guard lock(stateMutex); + fontStates.erase(handle); +} + +void Font::LoadStateFace(FontState& state, std::vector bytes, std::uint32_t subFontIndex) { + if (state.face) { + FT_Done_Face(state.face); + state.face = nullptr; + } + state.faceData = std::move(bytes); + const FT_Library library = HostFreeTypeLibrary(); + if (!library || state.faceData.empty()) return; + FT_Face face = nullptr; + if (FT_New_Memory_Face(library, state.faceData.data(), static_cast(state.faceData.size()), static_cast(subFontIndex), &face) != 0) return; + FT_Select_Charmap(face, FT_ENCODING_UNICODE); + state.face = face; +} + +void Font::TrackGeneratedGlyph(FontGlyph glyph) { + std::lock_guard lock(glyphMutex); + generatedGlyphs.insert(glyph); +} + +bool Font::ForgetGeneratedGlyph(FontGlyph glyph) { + std::lock_guard lock(glyphMutex); + return generatedGlyphs.erase(glyph) > 0; +} + +Font::GeneratedGlyph* Font::TryGetGeneratedGlyph(FontGlyph glyph) { + if (!glyph || glyph->magic != GLYPH_MAGIC) return nullptr; + std::lock_guard lock(glyphMutex); + if (!generatedGlyphs.contains(glyph)) return nullptr; + return reinterpret_cast(glyph); +} + +void Font::PopulateGlyphMetricVariants(GeneratedGlyph& glyph) { + if (glyph.metricsInitialized) return; + glyph.metricsHorizontal.width = glyph.metrics.width; + glyph.metricsHorizontal.height = glyph.metrics.height; + glyph.metricsHorizontal.horizontal.bearing_x = glyph.metrics.Horizontal.bearingX; + glyph.metricsHorizontal.horizontal.bearing_y = glyph.metrics.Horizontal.bearingY; + glyph.metricsHorizontal.horizontal.advance = glyph.metrics.Horizontal.advance; + glyph.metricsHorizontalX.width = glyph.metrics.width; + glyph.metricsHorizontalX.horizontal.bearing_x = glyph.metrics.Horizontal.bearingX; + glyph.metricsHorizontalX.horizontal.advance = glyph.metrics.Horizontal.advance; + glyph.metricsHorizontalAdvance.horizontal.advance = glyph.metrics.Horizontal.advance; + glyph.metricsInitialized = true; +} + +void Font::BuildBoundingOutline(GeneratedGlyph& glyph) { + const float left = glyph.metrics.Horizontal.bearingX; + const float top = glyph.metrics.Horizontal.bearingY; + const float right = left + glyph.metrics.width; + const float bottom = top - glyph.metrics.height; + glyph.outlinePoints = {{left, top}, {right, top}, {right, bottom}, {left, bottom}}; + glyph.outlineTags.assign(glyph.outlinePoints.size(), 1); + glyph.outlineContours = {static_cast(glyph.outlinePoints.size() - 1)}; + glyph.outline.points_ptr = glyph.outlinePoints.data(); + glyph.outline.tags_ptr = glyph.outlineTags.data(); + glyph.outline.contour_end_idx = glyph.outlineContours.data(); + glyph.outline.points_cnt = static_cast(glyph.outlinePoints.size()); + glyph.outline.contours_cnt = static_cast(glyph.outlineContours.size()); + glyph.outlineInitialized = true; +} + +bool Font::ValidStyleFrame(const FontStyleFrame* frame) { + return frame && frame->magic == STYLE_FRAME_MAGIC; +} + +int Font::StyleStateSetScalePixel(StyleStateBlock* style, float w, float h) { + if (!style) return 0; + if (style->scale_w == w && style->scale_h == h && style->scale_unit == 0) return 0; + style->scale_unit = 0; + style->scale_w = w; + style->scale_h = h; + return 1; +} + +int Font::StyleStateSetScalePoint(StyleStateBlock* style, float w, float h) { + if (!style) return 0; + if (style->scale_w == w && style->scale_h == h && style->scale_unit == 1) return 0; + style->scale_unit = 1; + style->scale_w = w; + style->scale_h = h; + return 1; +} + +int Font::StyleStateGetScalePixel(const StyleStateBlock* style, float* w, float* h) { + if (!style || (!w && !h)) return SCE_FONT_ERROR_INVALID_PARAMETER; + const bool points = style->scale_unit != 0; + if (w) { + float value = style->scale_w; + if (points && style->dpi_x != 0) value *= static_cast(style->dpi_x) / POINTS_PER_INCH; + *w = value; + } + if (h) { + float value = style->scale_h; + if (points && style->dpi_y != 0) value *= static_cast(style->dpi_y) / POINTS_PER_INCH; + *h = value; + } + return SCE_FONT_OK; +} + +int Font::StyleStateGetScalePoint(const StyleStateBlock* style, float* w, float* h) { + if (!style || (!w && !h)) return SCE_FONT_ERROR_INVALID_PARAMETER; + const bool pixels = style->scale_unit == 0; + if (w) { + float value = style->scale_w; + if (pixels && style->dpi_x != 0) value *= POINTS_PER_INCH / static_cast(style->dpi_x); + *w = value; + } + if (h) { + float value = style->scale_h; + if (pixels && style->dpi_y != 0) value *= POINTS_PER_INCH / static_cast(style->dpi_y); + *h = value; + } + return SCE_FONT_OK; +} + +int Font::StyleStateSetDpi(StyleStateBlock* style, std::uint32_t hDpi, std::uint32_t vDpi) { + if (!style) return 0; + if (hDpi == 0) hDpi = 0x48; + if (vDpi == 0) vDpi = 0x48; + if (style->dpi_x == hDpi && style->dpi_y == vDpi) return 0; + style->dpi_x = hDpi; + style->dpi_y = vDpi; + return 1; +} + +int Font::StyleStateSetSlantRatio(StyleStateBlock* style, float slantRatio) { + if (!style || style->slant_ratio == slantRatio) return 0; + style->slant_ratio = std::clamp(slantRatio, -1.0f, 1.0f); + return 1; +} + +int Font::StyleStateGetSlantRatio(const StyleStateBlock* style, float* slantRatio) { + if (!style || !slantRatio) return SCE_FONT_ERROR_INVALID_PARAMETER; + *slantRatio = style->slant_ratio; + return SCE_FONT_OK; +} + +int Font::StyleStateSetWeightScale(StyleStateBlock* style, float weightXScale, float weightYScale) { + if (!style) return 0; + const float deltaX = std::clamp(weightXScale - 1.0f, -0.04f, 0.04f); + const float deltaY = std::clamp(weightYScale - 1.0f, -0.04f, 0.04f); + bool changed = false; + if (style->effect_weight_x != deltaX) { + style->effect_weight_x = deltaX; + changed = true; + } + if (style->effect_weight_y != deltaY) { + style->effect_weight_y = deltaY; + changed = true; + } + return changed ? 1 : 0; +} + +int Font::StyleStateGetWeightScale(const StyleStateBlock* style, float* weightXScale, float* weightYScale, std::uint32_t* mode) { + if (!weightXScale && !weightYScale && !mode) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (!style) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (weightXScale) *weightXScale = style->effect_weight_x + 1.0f; + if (weightYScale) *weightYScale = style->effect_weight_y + 1.0f; + if (mode) *mode = 0; + return SCE_FONT_OK; +} + +std::uint8_t Font::CachedStyleCacheFlags(const CachedStyle& style) { + return static_cast(style.cache_flags_and_direction & 0xFFu); +} + +void Font::CachedStyleSetCacheFlags(CachedStyle& style, std::uint8_t flags) { + style.cache_flags_and_direction = (style.cache_flags_and_direction & 0xFFFFFF00u) | flags; +} + +void Font::CachedStyleSetDirectionWord(CachedStyle& style, std::uint16_t word) { + style.cache_flags_and_direction = (style.cache_flags_and_direction & 0x0000FFFFu) | (static_cast(word) << 16); +} + +float Font::CachedStyleGetScalar(const CachedStyle& style) { + float value; + std::memcpy(&value, &style.cached_scalar_bits, sizeof(value)); + return value; +} + +void Font::CachedStyleSetScalar(CachedStyle& style, float value) { + std::memcpy(&style.cached_scalar_bits, &value, sizeof(value)); +} + +void Font::ClearRenderOutputs(FontGlyphMetrics* metrics, FontRenderOutput* result) { + if (metrics) *metrics = {}; + if (result) *result = {}; +} diff --git a/core/libs/prx/libSceFont/src/Style.cpp b/core/libs/prx/libSceFont/src/Style.cpp new file mode 100644 index 000000000..42c4d9194 --- /dev/null +++ b/core/libs/prx/libSceFont/src/Style.cpp @@ -0,0 +1,303 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "prx/libSceFont/include/FontInternal.hpp" + +namespace { + +using namespace Font; + +template +int UpdateFontStyle(FontHandle fontHandle, Setter&& setter) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || font->magic != HANDLE_MAGIC || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + if (setter(font) != 0) font->cached_style.layout_cache_state = 0; + ReleaseFontLock(font, fontLock); + return SCE_FONT_OK; +} + +template +int UpdateRenderStyle(FontHandle fontHandle, Setter&& setter) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t cachedLock = 0; + if (!font || font->magic != HANDLE_MAGIC || !AcquireCachedStyleLock(font, cachedLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + int rc = SCE_FONT_ERROR_NOT_BOUND_RENDERER; + if (font->renderer) rc = setter(font); + ReleaseCachedStyleLock(font, cachedLock); + return rc; +} + +template +int ReadRenderStyle(FontHandle fontHandle, bool checkMagic, Getter&& getter) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t cachedLock = 0; + if (!font || (checkMagic && font->magic != HANDLE_MAGIC) || !AcquireCachedStyleLock(font, cachedLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + const int rc = font->renderer ? getter(font) : SCE_FONT_ERROR_NOT_BOUND_RENDERER; + ReleaseCachedStyleLock(font, cachedLock); + return rc; +} + +template +int ReadFontStyle(FontHandle fontHandle, bool checkMagic, Getter&& getter) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || (checkMagic && font->magic != HANDLE_MAGIC) || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + const int rc = getter(font); + ReleaseFontLock(font, fontLock); + return rc; +} + +void ResetScaleOutputs(float* w, float* h) { + if (w) *w = 0.0f; + if (h) *h = 0.0f; +} + +void ResetWeightOutputs(float* weightXScale, float* weightYScale, std::uint32_t* mode) { + if (weightXScale) *weightXScale = 1.0f; + if (weightYScale) *weightYScale = 1.0f; + if (mode) *mode = 0; +} + +float ClampWeightDelta(float scale) { + return std::clamp(scale - 1.0f, -0.04f, 0.04f); +} + +} + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceFontSetScalePixel(FontHandle fontHandle, float w, float h) { + return UpdateFontStyle(fontHandle, [&](FontHandleNative* font) { + const int changed = StyleStateSetScalePixel(&font->style, w, h); + if (FontState* state = TryGetState(fontHandle)) { + state->scaleW = w; + state->scaleH = h; + } + return changed; + }); +} + +int APS5_VABI sceFontSetScalePoint(FontHandle fontHandle, float w, float h) { + return UpdateFontStyle(fontHandle, [&](FontHandleNative* font) { return StyleStateSetScalePoint(&font->style, w, h); }); +} + +int APS5_VABI sceFontSetResolutionDpi(FontHandle fontHandle, std::uint32_t hDpi, std::uint32_t vDpi) { + auto* font = GetNativeFont(fontHandle); + std::uint32_t fontLock = 0; + if (!font || !AcquireFontLock(font, fontLock)) return SCE_FONT_ERROR_INVALID_FONT_HANDLE; + if (StyleStateSetDpi(&font->style, hDpi, vDpi) != 0) font->cached_style.layout_cache_state = 0; + ReleaseFontLock(font, fontLock); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontSetEffectSlant(FontHandle fontHandle, float slantRatio) { + return UpdateFontStyle(fontHandle, [&](FontHandleNative* font) { return StyleStateSetSlantRatio(&font->style, slantRatio); }); +} + +int APS5_VABI sceFontSetEffectWeight(FontHandle fontHandle, float weightXScale, float weightYScale, std::uint32_t mode) { + if (mode != 0) return SCE_FONT_ERROR_INVALID_PARAMETER; + return UpdateFontStyle(fontHandle, [&](FontHandleNative* font) { return StyleStateSetWeightScale(&font->style, weightXScale, weightYScale); }); +} + +int APS5_VABI sceFontGetScalePixel(FontHandle fontHandle, float* w, float* h) { + const int rc = ReadFontStyle(fontHandle, true, [&](FontHandleNative* font) { return StyleStateGetScalePixel(&font->style, w, h); }); + if (rc != SCE_FONT_OK) ResetScaleOutputs(w, h); + return rc; +} + +int APS5_VABI sceFontGetScalePoint(FontHandle fontHandle, float* w, float* h) { + const int rc = ReadFontStyle(fontHandle, false, [&](FontHandleNative* font) { return StyleStateGetScalePoint(&font->style, w, h); }); + if (rc != SCE_FONT_OK) ResetScaleOutputs(w, h); + return rc; +} + +int APS5_VABI sceFontGetEffectSlant(FontHandle fontHandle, float* slantRatio) { + const int rc = ReadFontStyle(fontHandle, true, [&](FontHandleNative* font) { return StyleStateGetSlantRatio(&font->style, slantRatio); }); + if (rc != SCE_FONT_OK && slantRatio) *slantRatio = 0.0f; + return rc; +} + +int APS5_VABI sceFontGetEffectWeight(FontHandle fontHandle, float* weightXScale, float* weightYScale, std::uint32_t* mode) { + const int rc = ReadFontStyle(fontHandle, true, [&](FontHandleNative* font) { return StyleStateGetWeightScale(&font->style, weightXScale, weightYScale, mode); }); + if (rc != SCE_FONT_OK) ResetWeightOutputs(weightXScale, weightYScale, mode); + return rc; +} + +int APS5_VABI sceFontSetupRenderScalePixel(FontHandle fontHandle, float w, float h) { + return UpdateRenderStyle(fontHandle, [&](FontHandleNative* font) { + if (StyleStateSetScalePixel(&font->cached_style.state, w, h) != 0) font->cached_style.cache_flags_and_direction &= 0xFFFF0000u; + return SCE_FONT_OK; + }); +} + +int APS5_VABI sceFontSetupRenderScalePoint(FontHandle fontHandle, float w, float h) { + return UpdateRenderStyle(fontHandle, [&](FontHandleNative* font) { + if (StyleStateSetScalePoint(&font->cached_style.state, w, h) != 0) font->cached_style.cache_flags_and_direction &= 0xFFFF0000u; + return SCE_FONT_OK; + }); +} + +int APS5_VABI sceFontSetupRenderEffectSlant(FontHandle fontHandle, float slantRatio) { + return UpdateRenderStyle(fontHandle, [&](FontHandleNative* font) { + if (StyleStateSetSlantRatio(&font->cached_style.state, slantRatio) != 0) font->cached_style.cache_flags_and_direction &= 0xFFFF0000u; + return SCE_FONT_OK; + }); +} + +int APS5_VABI sceFontSetupRenderEffectWeight(FontHandle fontHandle, float weightXScale, float weightYScale, std::uint32_t mode) { + return UpdateRenderStyle(fontHandle, [&](FontHandleNative* font) { + if (mode != 0) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (StyleStateSetWeightScale(&font->cached_style.state, weightXScale, weightYScale) != 0) font->cached_style.cache_flags_and_direction &= 0xFFFF0000u; + return SCE_FONT_OK; + }); +} + +int APS5_VABI sceFontGetRenderScalePixel(FontHandle fontHandle, float* w, float* h) { + const int rc = ReadRenderStyle(fontHandle, false, [&](FontHandleNative* font) { return StyleStateGetScalePixel(&font->cached_style.state, w, h); }); + if (rc != SCE_FONT_OK) ResetScaleOutputs(w, h); + return rc; +} + +int APS5_VABI sceFontGetRenderScalePoint(FontHandle fontHandle, float* w, float* h) { + const int rc = ReadRenderStyle(fontHandle, false, [&](FontHandleNative* font) { return StyleStateGetScalePoint(&font->cached_style.state, w, h); }); + if (rc != SCE_FONT_OK) ResetScaleOutputs(w, h); + return rc; +} + +int APS5_VABI sceFontGetRenderEffectSlant(FontHandle fontHandle, float* slantRatio) { + const int rc = ReadRenderStyle(fontHandle, true, [&](FontHandleNative* font) { return StyleStateGetSlantRatio(&font->cached_style.state, slantRatio); }); + if (rc != SCE_FONT_OK && slantRatio) *slantRatio = 0.0f; + return rc; +} + +int APS5_VABI sceFontGetRenderEffectWeight(FontHandle fontHandle, float* weightXScale, float* weightYScale, std::uint32_t* mode) { + const int rc = ReadRenderStyle(fontHandle, true, [&](FontHandleNative* font) { return StyleStateGetWeightScale(&font->cached_style.state, weightXScale, weightYScale, mode); }); + if (rc != SCE_FONT_OK) ResetWeightOutputs(weightXScale, weightYScale, mode); + return rc; +} + +int APS5_VABI sceFontStyleFrameInit(FontStyleFrame* styleFrame) { + if (!styleFrame) return SCE_FONT_ERROR_INVALID_PARAMETER; + std::memset(&styleFrame->scaleUnit, 0, 0x20); + std::memset(&styleFrame->layout_cache_state, 0, 0x20); + std::memset(&styleFrame->layout_cache_bytes[0x08], 0, 0x20); + styleFrame->magic = STYLE_FRAME_MAGIC; + styleFrame->flags1 = 0; + styleFrame->flags2 = 0; + styleFrame->hDpi = 0x48; + styleFrame->vDpi = 0x48; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameSetScalePixel(FontStyleFrame* styleFrame, float w, float h) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + styleFrame->scaleUnit = 0; + styleFrame->scalePixelW = w; + styleFrame->scalePixelH = h; + styleFrame->flags1 |= STYLE_FRAME_FLAG_SCALE; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameSetScalePoint(FontStyleFrame* styleFrame, float w, float h) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + styleFrame->scaleUnit = 1; + styleFrame->scalePixelW = w; + styleFrame->scalePixelH = h; + styleFrame->flags1 |= STYLE_FRAME_FLAG_SCALE; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameSetResolutionDpi(FontStyleFrame* styleFrame, std::uint32_t hDpi, std::uint32_t vDpi) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + styleFrame->hDpi = hDpi == 0 ? 0x48 : hDpi; + styleFrame->vDpi = vDpi == 0 ? 0x48 : vDpi; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameSetEffectSlant(FontStyleFrame* styleFrame, float slantRatio) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + styleFrame->slantRatio = std::clamp(slantRatio, -1.0f, 1.0f); + styleFrame->flags1 |= STYLE_FRAME_FLAG_SLANT; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameSetEffectWeight(FontStyleFrame* styleFrame, float weightXScale, float weightYScale, std::uint32_t mode) { + if (!ValidStyleFrame(styleFrame) || mode != 0) return SCE_FONT_ERROR_INVALID_PARAMETER; + styleFrame->effectWeightX = ClampWeightDelta(weightXScale); + styleFrame->effectWeightY = ClampWeightDelta(weightYScale); + styleFrame->flags1 |= STYLE_FRAME_FLAG_WEIGHT; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameUnsetScale(FontStyleFrame* styleFrame) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + styleFrame->flags1 &= static_cast(~STYLE_FRAME_FLAG_SCALE); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameUnsetEffectSlant(FontStyleFrame* styleFrame) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + styleFrame->flags1 &= static_cast(~STYLE_FRAME_FLAG_SLANT); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameUnsetEffectWeight(FontStyleFrame* styleFrame) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + styleFrame->flags1 &= static_cast(~STYLE_FRAME_FLAG_WEIGHT); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameGetScalePixel(const FontStyleFrame* styleFrame, float* w, float* h) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + if ((styleFrame->flags1 & STYLE_FRAME_FLAG_SCALE) == 0) return SCE_FONT_ERROR_UNSET_PARAMETER; + if (!w && !h) return SCE_FONT_ERROR_INVALID_PARAMETER; + const bool points = styleFrame->scaleUnit != 0; + if (w) *w = points && styleFrame->hDpi != 0 ? styleFrame->scalePixelW * (static_cast(styleFrame->hDpi) / POINTS_PER_INCH) : styleFrame->scalePixelW; + if (h) *h = points && styleFrame->vDpi != 0 ? styleFrame->scalePixelH * (static_cast(styleFrame->vDpi) / POINTS_PER_INCH) : styleFrame->scalePixelH; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameGetScalePoint(const FontStyleFrame* styleFrame, float* w, float* h) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + if ((styleFrame->flags1 & STYLE_FRAME_FLAG_SCALE) == 0) return SCE_FONT_ERROR_UNSET_PARAMETER; + if (!w && !h) return SCE_FONT_ERROR_INVALID_PARAMETER; + const bool pixels = styleFrame->scaleUnit == 0; + if (w) *w = pixels && styleFrame->hDpi != 0 ? styleFrame->scalePixelW * (POINTS_PER_INCH / static_cast(styleFrame->hDpi)) : styleFrame->scalePixelW; + if (h) *h = pixels && styleFrame->vDpi != 0 ? styleFrame->scalePixelH * (POINTS_PER_INCH / static_cast(styleFrame->vDpi)) : styleFrame->scalePixelH; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameGetResolutionDpi(const FontStyleFrame* styleFrame, std::uint32_t* hDpi, std::uint32_t* vDpi) { + if (!ValidStyleFrame(styleFrame) || (!hDpi && !vDpi)) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (hDpi) *hDpi = styleFrame->hDpi; + if (vDpi) *vDpi = styleFrame->vDpi; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameGetEffectSlant(const FontStyleFrame* styleFrame, float* slantRatio) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + if ((styleFrame->flags1 & STYLE_FRAME_FLAG_SLANT) == 0) return SCE_FONT_ERROR_UNSET_PARAMETER; + if (!slantRatio) return SCE_FONT_ERROR_INVALID_PARAMETER; + *slantRatio = styleFrame->slantRatio; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontStyleFrameGetEffectWeight(const FontStyleFrame* styleFrame, float* weightXScale, float* weightYScale, std::uint32_t* mode) { + if (!ValidStyleFrame(styleFrame)) return SCE_FONT_ERROR_INVALID_PARAMETER; + if ((styleFrame->flags1 & STYLE_FRAME_FLAG_WEIGHT) == 0) return SCE_FONT_ERROR_UNSET_PARAMETER; + if (!weightXScale && !weightYScale && !mode) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (weightXScale) *weightXScale = styleFrame->effectWeightX + 1.0f; + if (weightYScale) *weightYScale = styleFrame->effectWeightY + 1.0f; + if (mode) *mode = 0; + return SCE_FONT_OK; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceFont/src/Text.cpp b/core/libs/prx/libSceFont/src/Text.cpp new file mode 100644 index 000000000..5a43ff63f --- /dev/null +++ b/core/libs/prx/libSceFont/src/Text.cpp @@ -0,0 +1,633 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +#include "prx/libSceFont/include/FontInternal.hpp" + +namespace { + +using namespace Font; + +constexpr std::uint16_t TEXT_SOURCE_MAGIC = 0x0F04; +constexpr std::uint16_t FONT_STRING_MAGIC = 0x0F05; +constexpr std::uint16_t FONT_WRITING_MAGIC = 0x0F06; +constexpr std::uint16_t CREATE_STRING_DETAIL_ID = 0x0FD4; +constexpr std::uint32_t WRITING_FORM_HORIZONTAL = 0x10; +constexpr std::uint32_t WRITING_FORM_HORIZONTAL_LTR = 0x12; +constexpr std::int32_t PARSER_RESULT_ERROR = -1; +constexpr std::int32_t PARSER_RESULT_TERMINATE = 0; +constexpr std::int32_t PARSER_RESULT_FONT_CODE = 1; +constexpr std::size_t MAX_PARSED_CHARACTERS = 4096; +constexpr std::int32_t WRITING_MASK_NONE = 0; +constexpr std::int32_t WRITING_MASK_FORMAT_CHARACTERS = 1; +constexpr std::uint64_t CHARACTER_FLAG_FORMAT = 1ull << 42; + +struct CharacterStorage { + std::atomic refCount{1}; + std::vector characters; +}; + +struct FontStringData { + std::uint16_t magic = FONT_STRING_MAGIC; + std::uint8_t flags = 0; + std::uint8_t reserved0 = 0; + std::uint32_t writingForm = WRITING_FORM_HORIZONTAL; + const FontMemory* memory = nullptr; + FontTextSource* source = nullptr; + FontHandle defaultFont = nullptr; + std::uint32_t terminateCode = 0; + void* terminateOrder = nullptr; + std::uint32_t textCount = 0; + std::uint32_t renderCount = 0; + CharacterStorage* storage = nullptr; +}; + +struct FontWritingData { + std::uint16_t magic = FONT_WRITING_MAGIC; + std::uint16_t reserved0 = 0; + std::uint32_t writingForm = WRITING_FORM_HORIZONTAL; + FontString string = nullptr; + CharacterStorage* storage = nullptr; + FontHandle defaultFont = nullptr; + std::size_t nextIndex = 0; + std::size_t currentIndex = 0; + float penX = 0.0f; + float penY = 0.0f; + bool hasAggregateMetrics = false; + bool hasCurrentStep = false; + bool maskFormatCharacters = true; + bool ownsStorage = false; + FontWritingStep step{}; + FontWritingMetrics metrics{}; + FontGlyphMetrics rawGlyphMetrics{}; + bool hasRawGlyphMetrics = false; +}; + +static_assert(sizeof(FontWritingData) <= sizeof(FontWriting)); + +FontStringData* GetStringData(FontString fontString) { + auto* data = reinterpret_cast(fontString); + return data && data->magic == FONT_STRING_MAGIC ? data : nullptr; +} + +FontWritingData* GetWritingData(FontWriting* fontWriting) { + if (!fontWriting) return nullptr; + auto* data = reinterpret_cast(fontWriting->systemUse); + return data->magic == FONT_WRITING_MAGIC ? data : nullptr; +} + +void ReleaseStorage(CharacterStorage*& storage) { + if (!storage) return; + if (storage->refCount.fetch_sub(1, std::memory_order_acq_rel) == 1) delete storage; + storage = nullptr; +} + +void ReleaseWritingStorage(FontWritingData& writing) { + if (!writing.ownsStorage) return; + ReleaseStorage(writing.storage); + writing.ownsStorage = false; +} + +std::uint16_t TextSourceMagic(const FontTextSource* source) { + return static_cast(source->systemUse0 & 0xFFFFu); +} + +bool IsWhitespaceCode(std::uint32_t code) { + switch (code) { + case 0x0009: case 0x000A: case 0x000B: case 0x000C: case 0x000D: case 0x0020: case 0x0085: + case 0x00A0: case 0x1680: case 0x2028: case 0x2029: case 0x202F: case 0x205F: case 0x3000: + return true; + default: + return code >= 0x2000 && code <= 0x200A; + } +} + +bool IsFormatCode(std::uint32_t code) { + switch (code) { + case 0x00AD: case 0x061C: case 0x200B: case 0x200C: case 0x200D: case 0x200E: case 0x200F: + case 0x2060: case 0x2061: case 0x2062: case 0x2063: case 0x2064: case 0x2066: case 0x2067: + case 0x2068: case 0x2069: case 0xFE0E: case 0xFE0F: case 0xFEFF: + return true; + default: + return false; + } +} + +FontTextCharacter MakeCharacter(FontHandle font, std::uint32_t code, void* textOrder) { + FontTextCharacter character{}; + character.textOrder = textOrder; + character.font = font; + character.characterCode = code; + character.clusterSpan = 1; + character.flags = static_cast(IsWhitespaceCode(code) ? 0x0E : 0) << 8; + if (IsFormatCode(code)) character.flags |= CHARACTER_FLAG_FORMAT; + return character; +} + +void LinkCharacters(std::vector& characters) { + for (std::size_t i = 0; i < characters.size(); ++i) { + characters[i].prev = i == 0 ? nullptr : &characters[i - 1]; + characters[i].next = i + 1 < characters.size() ? &characters[i + 1] : nullptr; + } +} + +FontTextCharacter* TextCharacters(FontStringData* data) { + return data && data->storage && !data->storage->characters.empty() ? data->storage->characters.data() : nullptr; +} + +std::uint32_t LevelOf(const FontTextCharacter* character) { + return static_cast((character->flags >> 24) & 0xFFu); +} + +void FillWritingMetricsFromGlyph(FontWritingData& writing, const FontGlyphMetrics& glyphMetrics) { + writing.rawGlyphMetrics = glyphMetrics; + writing.hasRawGlyphMetrics = true; + writing.step.advanceX = glyphMetrics.Horizontal.advance; + writing.step.advanceY = glyphMetrics.Vertical.advance; + writing.step.GlyphMetrics = glyphMetrics; + writing.step.Positioning.x = writing.step.x; + writing.step.Positioning.y = writing.step.y; + writing.step.GlyphMetrics.width = writing.step.x; + writing.step.GlyphMetrics.height = writing.step.y; +} + +bool PopulateWritingStep(FontWritingData& writing, std::size_t index) { + if (!writing.storage || index >= writing.storage->characters.size()) return false; + const FontTextCharacter& character = writing.storage->characters[index]; + const FontHandle font = character.font ? character.font : writing.defaultFont; + writing.step.x = writing.penX; + writing.step.y = writing.penY; + writing.step.advanceX = 0.0f; + writing.step.advanceY = 0.0f; + writing.step.font = font; + writing.step.Profile = {}; + writing.step.Profile.characterCount = 1; + writing.step.glyphCode = character.characterCode; + writing.step.Positioning.x = 0.0f; + writing.step.Positioning.y = 0.0f; + writing.step.GlyphMetrics = {}; + writing.rawGlyphMetrics = {}; + writing.hasRawGlyphMetrics = false; + if ((character.flags & CHARACTER_FLAG_FORMAT) != 0 && writing.maskFormatCharacters) writing.step.Profile.invisibleGlyph = 1; + if (font && character.characterCode != 0) { + FontGlyphMetrics glyphMetrics{}; + int rc = GetCharGlyphMetrics(font, character.characterCode, &glyphMetrics, true); + if (rc != SCE_FONT_OK) rc = GetCharGlyphMetrics(font, character.characterCode, &glyphMetrics, false); + if (rc == SCE_FONT_OK) FillWritingMetricsFromGlyph(writing, glyphMetrics); + } + writing.penX += writing.step.advanceX; + writing.penY += writing.step.advanceY; + return true; +} + +void AccumulateWritingMetrics(FontWritingData& writing) { + const FontWritingMetrics previous = writing.metrics; + const FontGlyphMetrics& glyphMetrics = writing.hasRawGlyphMetrics ? writing.rawGlyphMetrics : writing.step.GlyphMetrics; + const float left = writing.step.x + glyphMetrics.Horizontal.bearingX; + const float right = left + glyphMetrics.width; + const float top = writing.step.y - glyphMetrics.Horizontal.bearingY; + const float bottom = top + glyphMetrics.height; + writing.metrics.advanceX = writing.step.x + writing.step.advanceX; + writing.metrics.advanceY = writing.step.y + writing.step.advanceY; + if (!writing.hasAggregateMetrics) { + writing.metrics.Extent = {top, bottom, left, right}; + writing.hasAggregateMetrics = true; + return; + } + writing.metrics.Extent.left = std::min(previous.Extent.left, left); + writing.metrics.Extent.right = std::max(previous.Extent.right, right); + writing.metrics.Extent.top = std::min(previous.Extent.top, top); + writing.metrics.Extent.bottom = std::max(previous.Extent.bottom, bottom); +} + +} + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceFontTextSourceInit(FontTextSource* textSource, const void* textAddress, std::uint32_t textSizeByte, FontTextParseFunction textParser, void* textObject) { + if (!textSource) return SCE_FONT_ERROR_INVALID_PARAMETER; + std::memset(textSource, 0, sizeof(*textSource)); + if (!textParser) return SCE_FONT_ERROR_INVALID_PARAMETER; + const void* end = textSizeByte != 0 ? static_cast(textAddress) + textSizeByte : nullptr; + textSource->systemUse0 = (static_cast(WRITING_FORM_HORIZONTAL) << 32) | TEXT_SOURCE_MAGIC; + textSource->start = textAddress; + textSource->end = end; + textSource->current = textAddress; + textSource->textParser = textParser; + textSource->textObject = textObject; + textSource->defaultFont = nullptr; + textSource->systemUse[0] = const_cast(textAddress); + textSource->systemUse[1] = const_cast(end); + textSource->systemUse[2] = reinterpret_cast(textParser); + textSource->systemUse[3] = textObject; + textSource->systemUse[4] = nullptr; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontTextSourceRewind(FontTextSource* textSource) { + if (!textSource || TextSourceMagic(textSource) != TEXT_SOURCE_MAGIC) return SCE_FONT_ERROR_INVALID_PARAMETER; + textSource->start = textSource->systemUse[0]; + textSource->end = textSource->systemUse[1]; + textSource->current = textSource->systemUse[0]; + textSource->textParser = reinterpret_cast(textSource->systemUse[2]); + textSource->textObject = textSource->systemUse[3]; + textSource->defaultFont = static_cast(textSource->systemUse[4]); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontTextSourceSetDefaultFont(FontTextSource* textSource, FontHandle defaultFont) { + if (!textSource || TextSourceMagic(textSource) != TEXT_SOURCE_MAGIC) return SCE_FONT_ERROR_INVALID_PARAMETER; + textSource->defaultFont = defaultFont; + textSource->systemUse[4] = defaultFont; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontTextSourceSetWritingForm(FontTextSource* textSource, std::int32_t writingForm) { + if (!textSource || TextSourceMagic(textSource) != TEXT_SOURCE_MAGIC) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (writingForm < static_cast(WRITING_FORM_HORIZONTAL) || writingForm > static_cast(WRITING_FORM_HORIZONTAL_LTR)) return SCE_FONT_ERROR_INVALID_PARAMETER; + textSource->systemUse0 = (textSource->systemUse0 & 0xFFFFFFFFull) | (static_cast(writingForm) << 32); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontCreateString(const FontMemory* fontMemory, FontTextSource* textSource, const FontCreateStringDetail* detail, FontString* pFontString) { + if (!fontMemory || !textSource || !pFontString || !textSource->textParser) { + if (pFontString) *pFontString = nullptr; + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + *pFontString = nullptr; + if (TextSourceMagic(textSource) != TEXT_SOURCE_MAGIC) return SCE_FONT_ERROR_INVALID_TEXT_SOURCE; + if (fontMemory->mem_kind != MEMORY_MAGIC || !fontMemory->iface || !fontMemory->iface->alloc || !fontMemory->iface->dealloc) return SCE_FONT_ERROR_INVALID_MEMORY; + if (detail) { + constexpr std::uint32_t validOrders = (1u << 0) | (1u << 1) | (1u << 2) | (1u << 31); + if (detail->detailId != CREATE_STRING_DETAIL_ID || detail->detailType > 1 || (detail->detections & ~0x07u) != 0 || (detail->ordersOption & ~validOrders) != 0) return SCE_FONT_ERROR_INVALID_PARAMETER; + } + void* raw = fontMemory->iface->alloc(fontMemory->mspace_handle, sizeof(FontStringData)); + if (!raw) return SCE_FONT_ERROR_ALLOCATION_FAILED; + auto* data = new (raw) FontStringData{}; + data->memory = fontMemory; + data->source = textSource; + data->defaultFont = textSource->defaultFont; + data->storage = new CharacterStorage{}; + if (!data->defaultFont && detail) data->defaultFont = detail->defaultFont; + data->writingForm = static_cast(textSource->systemUse0 >> 32); + std::int32_t state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE; + void* textOrder = nullptr; + for (std::size_t parsed = 0; parsed < MAX_PARSED_CHARACTERS; ++parsed) { + FontTextParseResult result{}; + state = textSource->textParser(textSource, &textOrder, &result); + if (state == PARSER_RESULT_FONT_CODE) { + const FontHandle font = result.FontCode.font ? result.FontCode.font : data->defaultFont; + if (!font) { + state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE; + break; + } + data->storage->characters.push_back(MakeCharacter(font, result.FontCode.code, textOrder)); + continue; + } + if (state == PARSER_RESULT_TERMINATE) { + data->terminateOrder = textOrder; + data->terminateCode = result.Terminate.terminateCode; + state = SCE_FONT_OK; + } else if (state == PARSER_RESULT_ERROR) { + state = result.Error.errorCode < 0 ? result.Error.errorCode : SCE_FONT_ERROR_INVALID_TEXT_SOURCE; + } else { + state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE; + } + break; + } + if (state == PARSER_RESULT_FONT_CODE) state = SCE_FONT_ERROR_INVALID_TEXT_SOURCE; + if (state != SCE_FONT_OK) { + ReleaseStorage(data->storage); + data->~FontStringData(); + fontMemory->iface->dealloc(fontMemory->mspace_handle, raw); + return state; + } + LinkCharacters(data->storage->characters); + data->textCount = static_cast(data->storage->characters.size()); + data->renderCount = data->textCount; + *pFontString = reinterpret_cast(data); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontDestroyString(FontString* pFontString) { + if (!pFontString) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* data = GetStringData(*pFontString); + if (!data) { + *pFontString = nullptr; + return SCE_FONT_ERROR_INVALID_STRING; + } + const FontMemory* memory = data->memory; + ReleaseStorage(data->storage); + data->~FontStringData(); + memory->iface->dealloc(memory->mspace_handle, data); + *pFontString = nullptr; + return SCE_FONT_OK; +} + +std::uint32_t APS5_VABI sceFontStringGetTerminateCode(FontString fontString) { + const auto* data = GetStringData(fontString); + return data ? data->terminateCode : 0; +} + +void* APS5_VABI sceFontStringGetTerminateOrder(FontString fontString) { + const auto* data = GetStringData(fontString); + return data ? data->terminateOrder : nullptr; +} + +int APS5_VABI sceFontStringGetWritingForm(FontString fontString) { + const auto* data = GetStringData(fontString); + return data ? static_cast(data->writingForm) : 0; +} + +FontTextCharacter* APS5_VABI sceFontStringRefersTextCharacters(FontString fontString, std::uint32_t* characterCount) { + auto* data = GetStringData(fontString); + if (!data || data->textCount == 0) { + if (characterCount) *characterCount = 0; + return nullptr; + } + if (characterCount) *characterCount = data->textCount; + return TextCharacters(data); +} + +const FontTextCharacter* APS5_VABI sceFontStringRefersRenderCharacters(FontString fontString, FontTextCharacter* startCharacter, FontTextCharacter* lastCharacter, std::uint32_t* characterCount) { + auto* data = GetStringData(fontString); + if (!data || !characterCount || data->renderCount == 0) { + if (characterCount) *characterCount = 0; + return nullptr; + } + FontTextCharacter* characters = TextCharacters(data); + if (!characters) { + *characterCount = 0; + return nullptr; + } + const FontTextCharacter* begin = characters; + const FontTextCharacter* end = characters + data->renderCount; + if (!startCharacter) startCharacter = characters; + if (startCharacter < begin || startCharacter >= end || (lastCharacter && (lastCharacter < begin || lastCharacter >= end))) { + *characterCount = 0; + return nullptr; + } + FontTextCharacter* probe = startCharacter; + if (startCharacter->clusterKind > 1) { + do { + startCharacter = probe; + probe = startCharacter->prev; + if (!probe) break; + } while (probe->synthetic < 0); + } + auto startFlags = static_cast(startCharacter->flags); + const std::uint32_t paragraphLevel = (data->writingForm >> 8) & 1u; + if ((startFlags & 0xFF00u) == 0x100u && startCharacter->prev) { + if (startCharacter->prev->clusterKind == 1) startCharacter = startCharacter->prev; + startFlags = static_cast(startCharacter->flags); + } + std::uint32_t startLevel = startFlags >> 24; + std::uint32_t lastLevel = startLevel; + std::uint32_t minLevel = startLevel; + std::uint32_t count = startCharacter->clusterIndex == 0 ? 1u : 0u; + probe = startCharacter; + while (probe != lastCharacter) { + probe = probe->next; + if (!probe) { + if (lastCharacter) { + *characterCount = 0; + return nullptr; + } + break; + } + if (probe->clusterIndex == 0) { + lastLevel = LevelOf(probe); + if (lastLevel < minLevel && ((lastLevel ^ paragraphLevel) & 1u) == 0) minLevel = lastLevel; + ++count; + } + } + if (startLevel != lastLevel || startLevel != minLevel) { + probe = startCharacter->prev; + while (probe) { + if (probe->clusterIndex == 0) { + if (LevelOf(probe) <= minLevel) break; + ++count; + } + startCharacter = probe; + probe = probe->prev; + } + if (lastCharacter) { + probe = lastCharacter->next; + while (probe) { + if (probe->clusterIndex == 0) { + if (LevelOf(probe) <= minLevel) break; + ++count; + lastCharacter = probe; + } + probe = probe->next; + } + } + } + if (lastCharacter) { + probe = lastCharacter->next; + std::uint32_t clusterTrim = 0; + while (probe) { + std::uint32_t nextTrim = clusterTrim; + if (probe->clusterIndex == 0) { + if (probe->synthetic == 0) break; + nextTrim = 0; + FontTextCharacter* nextLast = probe; + if (probe->synthetic < 1) { + if (probe->clusterKind == 1) break; + nextTrim = clusterTrim + 1; + nextLast = lastCharacter; + } + lastCharacter = nextLast; + ++count; + } + probe = probe->next; + count -= nextTrim; + clusterTrim = nextTrim; + } + } + startLevel = LevelOf(startCharacter); + if (startLevel != paragraphLevel) { + probe = startCharacter; + if (!lastCharacter || LevelOf(lastCharacter) != startLevel) { + do { + probe = probe->next; + if (!probe) { + if (lastCharacter) startCharacter = nullptr; + break; + } + FontTextCharacter* best = startCharacter; + std::uint32_t bestLevel = startLevel; + if (probe->clusterIndex == 0) { + const std::uint32_t probeLevel = LevelOf(probe); + if (probeLevel <= paragraphLevel) break; + if ((startLevel & 1u) == paragraphLevel && probeLevel < startLevel && (probeLevel & 1u) != paragraphLevel) { + best = probe; + bestLevel = probeLevel; + } + } + startLevel = bestLevel; + startCharacter = best; + } while (probe != lastCharacter); + } else if ((LevelOf(startCharacter) & 1u) != paragraphLevel) { + startCharacter = lastCharacter; + } + } + if (!startCharacter) count = 0; + *characterCount = count; + return startCharacter; +} + +int APS5_VABI sceFontCharacterGetBidiLevel(const FontTextCharacter* textCharacter, int* bidiLevel) { + if (!textCharacter || !bidiLevel) return SCE_FONT_ERROR_INVALID_PARAMETER; + *bidiLevel = static_cast(LevelOf(textCharacter)); + return SCE_FONT_OK; +} + +int APS5_VABI sceFontCharacterGetTextFontCode(const FontTextCharacter* textCharacter, FontHandle* pFontHandle, std::uint32_t* textCode) { + if (!textCharacter) { + if (pFontHandle) *pFontHandle = nullptr; + if (textCode) *textCode = 0; + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + if (!pFontHandle || !textCode) return SCE_FONT_ERROR_INVALID_PARAMETER; + *pFontHandle = textCharacter->font; + *textCode = textCharacter->characterCode; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontCharacterGetTextOrder(const FontTextCharacter* textCharacter, void** pTextOrder) { + if (!pTextOrder) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (!textCharacter) { + *pTextOrder = nullptr; + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + *pTextOrder = textCharacter->textOrder; + return SCE_FONT_OK; +} + +std::uint32_t APS5_VABI sceFontCharacterLooksFormatCharacters(const FontTextCharacter* textCharacter) { + if (!textCharacter) return 0; + return (textCharacter->flags & CHARACTER_FLAG_FORMAT) != 0 ? textCharacter->characterCode : 0; +} + +std::uint32_t APS5_VABI sceFontCharacterLooksWhiteSpace(const FontTextCharacter* textCharacter) { + if (!textCharacter) return 0; + return ((textCharacter->flags >> 8) & 0xFFu) == 0x0E ? textCharacter->characterCode : 0; +} + +FontTextCharacter* APS5_VABI sceFontCharacterRefersTextBack(const FontTextCharacter* textCharacter) { + if (!textCharacter) return nullptr; + for (FontTextCharacter* current = textCharacter->prev; current; current = current->prev) { + if (current->synthetic == 0 && current->clusterIndex == 0) return current; + } + return nullptr; +} + +FontTextCharacter* APS5_VABI sceFontCharacterRefersTextNext(const FontTextCharacter* textCharacter) { + if (!textCharacter) return nullptr; + for (FontTextCharacter* current = textCharacter->next; current; current = current->next) { + if (current->synthetic == 0 && current->clusterIndex == 0) return current; + } + return nullptr; +} + +int APS5_VABI sceFontWritingInit(FontWriting* fontWriting, FontString fontString, const FontTextCharacter* fontCharacter) { + if (!fontWriting || !fontString || !fontCharacter) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* data = GetStringData(fontString); + if (!data) return SCE_FONT_ERROR_INVALID_STRING; + if (auto* previous = GetWritingData(fontWriting)) ReleaseWritingStorage(*previous); + std::memset(fontWriting, 0, sizeof(*fontWriting)); + auto* writing = new (fontWriting->systemUse) FontWritingData{}; + writing->string = fontString; + writing->storage = data->storage; + writing->defaultFont = data->defaultFont; + if (writing->storage) writing->storage->refCount.fetch_add(1, std::memory_order_relaxed); + writing->ownsStorage = true; + writing->writingForm = data->writingForm; + const FontTextCharacter* characters = TextCharacters(data); + if (!characters || fontCharacter < characters || fontCharacter >= characters + data->storage->characters.size()) { + ReleaseWritingStorage(*writing); + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + writing->nextIndex = static_cast(fontCharacter - characters); + writing->currentIndex = writing->nextIndex; + writing->hasCurrentStep = false; + if (!PopulateWritingStep(*writing, writing->nextIndex)) { + ReleaseWritingStorage(*writing); + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + writing->penX = 0.0f; + writing->penY = 0.0f; + writing->hasAggregateMetrics = false; + writing->metrics = {}; + return SCE_FONT_OK; +} + +int APS5_VABI sceFontWritingGetRenderMetrics(FontWriting* fontWriting, FontWritingMetrics* writingMetrics) { + if (!fontWriting || !writingMetrics) { + if (writingMetrics) *writingMetrics = {}; + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + const auto* writing = GetWritingData(fontWriting); + if (!writing) { + *writingMetrics = {}; + return SCE_FONT_ERROR_INVALID_WRITING; + } + *writingMetrics = writing->metrics; + return SCE_FONT_OK; +} + +const FontWritingStep* APS5_VABI sceFontWritingRefersRenderStep(FontWriting* fontWriting) { + auto* writing = GetWritingData(fontWriting); + if (!writing || !writing->storage || writing->nextIndex >= writing->storage->characters.size()) return nullptr; + if (!PopulateWritingStep(*writing, writing->nextIndex)) return nullptr; + writing->currentIndex = writing->nextIndex; + writing->hasCurrentStep = true; + AccumulateWritingMetrics(*writing); + ++writing->nextIndex; + return &writing->step; +} + +FontTextCharacter* APS5_VABI sceFontWritingRefersRenderStepCharacter(FontWriting* fontWriting, FontWritingLetterStep* letterStep) { + if (letterStep) std::memset(letterStep, 0, sizeof(*letterStep)); + auto* writing = GetWritingData(fontWriting); + if (!writing || !writing->hasCurrentStep || !writing->storage || writing->currentIndex >= writing->storage->characters.size()) return nullptr; + if (letterStep) { + letterStep->x = writing->step.x; + letterStep->y = writing->step.y; + letterStep->advanceX = writing->step.advanceX; + letterStep->advanceY = writing->step.advanceY; + letterStep->Components.textsCount = 1; + letterStep->Components.textsIndex = static_cast(writing->currentIndex); + letterStep->Components.glyphsCount = 1; + letterStep->Components.glyphsIndex = static_cast(writing->currentIndex); + letterStep->Components.characterTextCount = static_cast(std::max(writing->step.Profile.characterCount, 1)); + } + return &writing->storage->characters[writing->currentIndex]; +} + +int APS5_VABI sceFontWritingSetMaskInvisible(FontWriting* fontWriting, std::int32_t mask) { + auto* writing = GetWritingData(fontWriting); + if (!writing) return SCE_FONT_ERROR_INVALID_WRITING; + if (mask == WRITING_MASK_FORMAT_CHARACTERS) { + writing->maskFormatCharacters = false; + return SCE_FONT_OK; + } + if (mask == WRITING_MASK_NONE) { + writing->maskFormatCharacters = true; + return SCE_FONT_OK; + } + return SCE_FONT_ERROR_INVALID_PARAMETER; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceFont/src/Unimplemented.cpp b/core/libs/prx/libSceFont/src/Unimplemented.cpp new file mode 100644 index 000000000..de6b6e3d0 --- /dev/null +++ b/core/libs/prx/libSceFont/src/Unimplemented.cpp @@ -0,0 +1,477 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "prx/libc/include/General.hpp" + +#pragma GCC visibility push(default) + +extern "C" { + +int APS5_VABI sceFontCharacterGetSyllableStringState() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontCharactersRefersTextCodes() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontControl() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontCreateGraphicsDevice() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontCreateGraphicsService() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontCreateGraphicsServiceWithEdition() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontCreateWords() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontCreateWritingLine() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontDefineAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontDestroyGraphicsDevice() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontDestroyGraphicsService() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontDestroyWords() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontDestroyWritingLine() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontDettachDeviceCacheBuffer() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetCharGlyphCode() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetFontGlyphsCount() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetFontGlyphsOutlineProfile() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetFontMetrics() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetFontResolution() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetFontStyleInformation() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetGlyphExpandBufferState() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetPixelResolution() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetRenderScaledKerning() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetResolutionDpi() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetScriptLanguage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGetTypographicDesign() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGlyphDefineAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGlyphGetAttribute() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGlyphRenderImage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGlyphRenderImageHorizontal() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGlyphRenderImageVertical() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsBeginFrame() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsDrawingCancel() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsDrawingFinish() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsEndFrame() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsExchangeResource() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsFillMethodInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsFillPlotInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsFillPlotSetLayout() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsFillPlotSetMapping() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsFillRatesInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsFillRatesSetFillEffect() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsFillRatesSetLayout() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsFillRatesSetMapping() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsGetDeviceUsage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsRegionInit() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsRegionInitCircular() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsRegionInitRoundish() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsRelease() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsRenderResource() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetFramePolicy() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupClipping() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupColorRates() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupFillMethod() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupFillRates() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupGlyphFill() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupGlyphFillPlot() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupHandleDefault() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupLocation() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupPositioning() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupRotation() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupScaling() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupShapeFill() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsSetupShapeFillPlot() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsStructureCanvas() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsStructureCanvasSequence() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsStructureDesign() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsStructureDesignResource() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsStructureSurfaceTexture() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateClipping() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateColorRates() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateFillMethod() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateFillRates() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateGlyphFill() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateGlyphFillPlot() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateLocation() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdatePositioning() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateRotation() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateScaling() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateShapeFill() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontGraphicsUpdateShapeFillPlot() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontSetScriptLanguage() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontSetTypographicDesign() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontSupportGlyphs() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontTextCodesStepBack() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontTextCodesStepNext() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontWordsFindWordCharacters() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontWritingLineClear() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontWritingLineGetOrderingSpace() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontWritingLineGetRenderMetrics() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontWritingLineRefersRenderStep() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontWritingLineWritesOrder() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceFontFt/CMakeLists.txt b/core/libs/prx/libSceFontFt/CMakeLists.txt new file mode 100644 index 000000000..6428668cd --- /dev/null +++ b/core/libs/prx/libSceFontFt/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(libSceFontFt SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Driver.cpp +) + +target_include_directories(libSceFontFt PRIVATE ${LIBS_INCLUDE_DIR}) +target_link_libraries(libSceFontFt PRIVATE freetype libc) +set_target_properties(libSceFontFt PROPERTIES + CXX_EXTENSIONS OFF + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON +) +configure_windows_unwind(libSceFontFt) diff --git a/core/libs/prx/libSceFontFt/Export.cpp b/core/libs/prx/libSceFontFt/Export.cpp index a15c312e4..d34a3ca55 100644 --- a/core/libs/prx/libSceFontFt/Export.cpp +++ b/core/libs/prx/libSceFontFt/Export.cpp @@ -1,20 +1,116 @@ -#include -#include -#include "SceTypes.hpp" +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "prx/libSceFontFt/include/FontFtDriver.hpp" #include "prx/libc/include/General.hpp" +#pragma GCC visibility push(default) + extern "C" { -void* APS5_VABI sceFontSelectLibraryFt(int value) { - (void)value; - NotImplemented_nid_no_patch(__func__); - return nullptr; +const Font::SysDriver* APS5_VABI sceFontSelectLibraryFt(int value) { + return value == 0 ? FontFt::DriverTable() : nullptr; } -void* APS5_VABI sceFontSelectRendererFt(int value) { - (void)value; - NotImplemented_nid_no_patch(__func__); - return nullptr; +const Font::RendererSelection* APS5_VABI sceFontSelectRendererFt(int value) { + return value == 0 ? FontFt::RendererTable() : nullptr; +} + +int APS5_VABI sceFontFtInitAliases() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSetAliasFont() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSetAliasPath() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportBdf() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportCid() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportFontFormats() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportOpenType() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportOpenTypeOtf() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportOpenTypeTtf() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportPcf() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportPfr() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportSystemFonts() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportTrueType() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportTrueTypeGx() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportType1() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportType42() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtSupportWinFonts() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontFtTermAliases() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceFontSelectGlyphsFt() { + NotImplemented_nid_no_patch(__func__); + return 0; } } + +#pragma GCC visibility pop diff --git a/core/libs/prx/libSceFontFt/include/FontFtDriver.hpp b/core/libs/prx/libSceFontFt/include/FontFtDriver.hpp new file mode 100644 index 000000000..87e7e9293 --- /dev/null +++ b/core/libs/prx/libSceFontFt/include/FontFtDriver.hpp @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef CORE_LIBS_PRX_LIBSCEFONTFT_INCLUDE_FONTFTDRIVER_HPP +#define CORE_LIBS_PRX_LIBSCEFONTFT_INCLUDE_FONTFTDRIVER_HPP + +#include "prx/libSceFont/include/FontDriver.hpp" + +namespace FontFt { + +const Font::SysDriver* DriverTable(); +const Font::RendererSelection* RendererTable(); + +} + +#endif diff --git a/core/libs/prx/libSceFontFt/src/Driver.cpp b/core/libs/prx/libSceFontFt/src/Driver.cpp new file mode 100644 index 000000000..71c1ff144 --- /dev/null +++ b/core/libs/prx/libSceFontFt/src/Driver.cpp @@ -0,0 +1,711 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H +#include FT_MODULE_H +#include FT_SYSTEM_H +#include FT_TRUETYPE_TABLES_H + +#include "prx/libSceFont/include/FontFreeType.hpp" +#include "prx/libSceFontFt/include/FontFtDriver.hpp" + +namespace { + +using namespace Font; + +struct FtLibraryContext { + void* allocCtx; + const FontMemoryInterface* iface; + FT_Memory memory; + FT_Library library; +}; + +void* FtAlloc(FT_Memory memory, long size) { + if (!memory || size <= 0) return nullptr; + auto* ctx = static_cast(memory->user); + if (!ctx || !ctx->iface || !ctx->iface->alloc) return nullptr; + return ctx->iface->alloc(ctx->allocCtx, static_cast(size)); +} + +void FtFree(FT_Memory memory, void* block) { + if (!memory || !block) return; + auto* ctx = static_cast(memory->user); + if (!ctx || !ctx->iface || !ctx->iface->dealloc) return; + ctx->iface->dealloc(ctx->allocCtx, block); +} + +void* FtRealloc(FT_Memory memory, long currentSize, long newSize, void* block) { + if (!memory) return nullptr; + auto* ctx = static_cast(memory->user); + if (!ctx || !ctx->iface) return nullptr; + if (ctx->iface->realloc_fn) return ctx->iface->realloc_fn(ctx->allocCtx, block, static_cast(newSize)); + if (newSize <= 0) { + FtFree(memory, block); + return nullptr; + } + void* result = FtAlloc(memory, newSize); + if (!result) return nullptr; + if (block && currentSize > 0) std::memcpy(result, block, static_cast(std::min(currentSize, newSize))); + FtFree(memory, block); + return result; +} + +void WriteU16(std::uint8_t* out, std::size_t offset, std::uint16_t value) { + std::memcpy(out + offset, &value, sizeof(value)); +} + +void WriteU32(std::uint8_t* out, std::size_t offset, std::uint32_t value) { + std::memcpy(out + offset, &value, sizeof(value)); +} + +void WriteS32(std::uint8_t* out, std::size_t offset, std::int32_t value) { + std::memcpy(out + offset, &value, sizeof(value)); +} + +void WritePointer(std::uint8_t* out, std::size_t offset, const void* value) { + std::memcpy(out + offset, &value, sizeof(value)); +} + +float FromF26Dot6(std::int64_t value) { + return static_cast(value) * ONE_OVER_64; +} + +FT_Face FaceOf(const FontObj* obj) { + return static_cast(obj->ft_face); +} + +std::uint32_t APS5_VABI LibraryGetPixelResolution() { + return 0x40; +} + +int APS5_VABI LibraryInit(const FontMemory* memory, FontLibNative* library) { + if (!memory || !library) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (!memory->iface || !memory->iface->alloc || !memory->iface->dealloc) return SCE_FONT_ERROR_INVALID_MEMORY; + const auto allocFn = memory->iface->alloc; + const auto freeFn = memory->iface->dealloc; + void* allocCtx = memory->mspace_handle; + auto* ctx = static_cast(allocFn(allocCtx, sizeof(FtLibraryContext))); + if (!ctx) return SCE_FONT_ERROR_ALLOCATION_FAILED; + std::memset(ctx, 0, sizeof(FtLibraryContext)); + ctx->allocCtx = allocCtx; + ctx->iface = memory->iface; + auto* ftMemory = static_cast(allocFn(allocCtx, sizeof(FT_MemoryRec_))); + if (!ftMemory) { + freeFn(allocCtx, ctx); + return SCE_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(ftMemory, 0, sizeof(*ftMemory)); + ftMemory->user = ctx; + ftMemory->alloc = &FtAlloc; + ftMemory->free = &FtFree; + ftMemory->realloc = &FtRealloc; + ctx->memory = ftMemory; + FT_Library ftLibrary = nullptr; + if (FT_New_Library(ftMemory, &ftLibrary) != 0 || !ftLibrary) { + freeFn(allocCtx, ftMemory); + freeFn(allocCtx, ctx); + return SCE_FONT_ERROR_ALLOCATION_FAILED; + } + FT_Add_Default_Modules(ftLibrary); + ctx->library = ftLibrary; + library->fontset_registry = ctx; + library->flags = 0x60000000; + return SCE_FONT_OK; +} + +int APS5_VABI LibraryTerm(FontLibNative* library) { + if (!library) return SCE_FONT_ERROR_INVALID_PARAMETER; + const auto freeFn = library->iface ? library->iface->dealloc : nullptr; + if (!freeFn) return SCE_FONT_ERROR_INVALID_PARAMETER; + auto* ctx = static_cast(library->fontset_registry); + if (!ctx) return SCE_FONT_ERROR_INVALID_PARAMETER; + if (ctx->library) { + FT_Done_Library(ctx->library); + ctx->library = nullptr; + } + if (ctx->memory) { + freeFn(library->alloc_ctx, ctx->memory); + ctx->memory = nullptr; + } + freeFn(library->alloc_ctx, ctx); + library->fontset_registry = nullptr; + return SCE_FONT_OK; +} + +int APS5_VABI LibrarySupport(FontLibNative* library, std::uint32_t formats) { + (void)formats; + if (!library || !library->fontset_registry) return SCE_FONT_ERROR_INVALID_PARAMETER; + return SCE_FONT_OK; +} + +int APS5_VABI LibraryOpenFont(FontLibNative* library, std::uint32_t mode, const void* fontAddress, std::uint32_t fontSize, std::uint32_t subFontIndex, std::uint32_t uniqueWord, FontObj** inoutFontObj) { + (void)uniqueWord; + if (!library || !fontAddress || !inoutFontObj) return SCE_FONT_ERROR_INVALID_PARAMETER; + const auto allocFn = library->iface ? library->iface->alloc : nullptr; + const auto freeFn = library->iface ? library->iface->dealloc : nullptr; + if (!allocFn || !freeFn) return SCE_FONT_ERROR_INVALID_PARAMETER; + const bool fromMemory = mode == 1; + if (fromMemory) { + if (fontSize == 0) return SCE_FONT_ERROR_INVALID_PARAMETER; + } else if (mode == 5 || mode == 6 || mode == 7) { + if (static_cast(fontAddress)[0] == '\0') return SCE_FONT_ERROR_INVALID_PARAMETER; + } else { + return SCE_FONT_ERROR_INVALID_PARAMETER; + } + auto* ctx = static_cast(library->fontset_registry); + if (!ctx || !ctx->library) return SCE_FONT_ERROR_INVALID_LIBRARY; + FT_Face face = nullptr; + FT_Error error; + if (fromMemory) { + error = FT_New_Memory_Face(ctx->library, static_cast(fontAddress), static_cast(fontSize), static_cast(subFontIndex), &face); + } else { + error = FT_New_Face(ctx->library, static_cast(fontAddress), static_cast(subFontIndex), &face); + } + if (error != 0 || !face) { + if (fromMemory || error == FT_Err_Unknown_File_Format) return SCE_FONT_ERROR_NO_SUPPORT_FORMAT; + return SCE_FONT_ERROR_FS_OPEN_FAILED; + } + FT_Select_Charmap(face, FT_ENCODING_UNICODE); + auto* obj = static_cast(allocFn(library->alloc_ctx, sizeof(FontObj))); + if (!obj) { + FT_Done_Face(face); + return SCE_FONT_ERROR_ALLOCATION_FAILED; + } + std::memset(obj, 0, sizeof(FontObj)); + obj->refcount = 1; + obj->sub_font_index = subFontIndex; + obj->prev = nullptr; + obj->next = *inoutFontObj; + if (obj->next) obj->next->prev = obj; + obj->ft_face = face; + obj->ft_ctx_0x58 = &ctx->library; + *inoutFontObj = obj; + return SCE_FONT_OK; +} + +int APS5_VABI LibraryCloseFont(FontObj* obj, std::uint32_t flags) { + (void)flags; + if (!obj) return SCE_FONT_ERROR_FATAL; + if (obj->refcount > 1) { + --obj->refcount; + return SCE_FONT_OK; + } + FT_Face face = FaceOf(obj); + FtLibraryContext* ctx = face && face->memory ? static_cast(face->memory->user) : nullptr; + const auto freeFn = ctx && ctx->iface ? ctx->iface->dealloc : nullptr; + if (face) { + FT_Done_Face(face); + obj->ft_face = nullptr; + } + if (!freeFn) return SCE_FONT_ERROR_FATAL; + FontObj* next = obj->next; + FontObj* prev = obj->prev; + if (prev) prev->next = next; + if (next) next->prev = prev; + freeFn(ctx->allocCtx, obj); + return SCE_FONT_OK; +} + +int APS5_VABI LibraryGetFaceScale(FontObj* obj, std::uint16_t* outUnitsPerEm, float* outScale) { + if (!obj || !outUnitsPerEm || !outScale) return SCE_FONT_ERROR_FATAL; + const FT_Face face = FaceOf(obj); + if (!face) return SCE_FONT_ERROR_FATAL; + const std::uint16_t units = face->units_per_EM; + *outUnitsPerEm = units; + *outScale = static_cast(units) * ONE_OVER_64; + return SCE_FONT_OK; +} + +int APS5_VABI LibraryGetFaceMetric(FontObj* obj, std::uint32_t metricId, std::uint16_t* outMetric) { + if (!obj || !outMetric) return SCE_FONT_ERROR_FATAL; + const FT_Face face = FaceOf(obj); + if (!face) return SCE_FONT_ERROR_FATAL; + const std::uint16_t units = face->units_per_EM; + if (metricId == 0x0E00) { + *outMetric = units; + return SCE_FONT_OK; + } + if (metricId == 0xEA00) { + if (const auto* os2 = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_OS2))) { + *outMetric = static_cast(os2->sTypoAscender); + return SCE_FONT_OK; + } + *outMetric = units; + return SCE_FONT_ERROR_NO_SUPPORT_FUNCTION; + } + *outMetric = 0; + return SCE_FONT_OK; +} + +int APS5_VABI LibraryGetGlyphIndex(FontObj* obj, std::uint32_t codepoint, std::uint32_t* outGlyphIndex) { + if (!obj || !outGlyphIndex) return SCE_FONT_ERROR_FATAL; + const FT_Face face = FaceOf(obj); + if (!face) { + *outGlyphIndex = 0; + return SCE_FONT_ERROR_FATAL; + } + const std::uint32_t glyphIndex = ResolveGlyphIndexWithFallback(face, codepoint); + *outGlyphIndex = glyphIndex; + return glyphIndex ? SCE_FONT_OK : SCE_FONT_ERROR_NO_SUPPORT_GLYPH; +} + +int APS5_VABI LibrarySetCharSizeWithDpi(FontObj* obj, std::uint32_t dpiX, std::uint32_t dpiY, float scaleX, float scaleY, float* outScaleX, float* outScaleY) { + if (!obj || !outScaleX || !outScaleY) return SCE_FONT_ERROR_FATAL; + const FT_Face face = FaceOf(obj); + if (!face) return SCE_FONT_ERROR_FATAL; + const auto charW = static_cast(static_cast(scaleX * 64.0f)); + const auto charH = static_cast(static_cast(scaleY * 64.0f)); + if (SetCharSizeCompat(face, charW, charH, dpiX, dpiY) != 0) return SCE_FONT_ERROR_FATAL; + if (!face->size) return SCE_FONT_ERROR_FATAL; + const std::uint16_t units = face->units_per_EM; + *outScaleX = FixedMulUnitsToF26Dot6(static_cast(face->size->metrics.x_scale), units); + *outScaleY = FixedMulUnitsToF26Dot6(static_cast(face->size->metrics.y_scale), units); + return SCE_FONT_OK; +} + +int APS5_VABI LibrarySetCharSizeDefaultDpi(FontObj* obj, float scaleX, float scaleY, float* outScaleX, float* outScaleY) { + if (!obj || !outScaleX || !outScaleY) return SCE_FONT_ERROR_FATAL; + return LibrarySetCharSizeWithDpi(obj, 0x48, 0x48, scaleX, scaleY, outScaleX, outScaleY); +} + +int APS5_VABI LibraryComputeLayout(FontObj* obj, const StyleStateBlock* style, std::uint8_t* outWords) { + if (!obj || !style || !outWords) return SCE_FONT_ERROR_FATAL; + const FT_Face face = FaceOf(obj); + if (!face || !face->size) return SCE_FONT_ERROR_FATAL; + const float effectWidth = style->effect_weight_x; + const float effectHeight = style->effect_weight_y; + const float slant = style->slant_ratio; + const auto yScale = static_cast(face->size->metrics.y_scale); + const auto xScale = static_cast(face->size->metrics.x_scale); + const std::int64_t yShift = obj->shift_cache_y; + const std::int64_t xShift = obj->shift_cache_x; + const auto unitsPerEm = static_cast(static_cast(face->units_per_EM)); + std::int32_t yMinPx = RoundMulFixed(static_cast(face->bbox.yMin) + yShift, yScale); + std::int32_t yMaxPx = RoundMulFixed(static_cast(face->bbox.yMax) + yShift, yScale); + std::int32_t halfEffectWidthPx = 0; + std::int32_t leftAdjustPx = 0; + if (effectWidth != 0.0f) { + const std::int32_t unitsScaledX = TruncFixed(unitsPerEm * xScale); + halfEffectWidthPx = TruncateFloatToInt(effectWidth * static_cast(unitsScaledX)) / 2; + leftAdjustPx = -halfEffectWidthPx; + } + float outEffectHeight = 0.0f; + if (effectHeight != 0.0f) { + const std::int32_t unitsScaledY = TruncFixed(unitsPerEm * yScale); + const std::int32_t halfEffectHeightPx = TruncateFloatToInt(effectHeight * static_cast(unitsScaledY)) / 2; + outEffectHeight = static_cast(halfEffectHeightPx) * ONE_OVER_64; + yMinPx -= halfEffectHeightPx; + yMaxPx += halfEffectHeightPx; + } + if (slant != 0.0f) { + const auto shear = static_cast(TruncateFloatToInt(slant * 65536.0f)); + leftAdjustPx += RoundMulFixed(yMinPx, shear); + halfEffectWidthPx += RoundMulFixed(yMaxPx, shear); + } + StoreFloat(outWords, HORIZONTAL_EFFECT_HEIGHT, outEffectHeight); + StoreFloat(outWords, HORIZONTAL_LEFT_ADJUST, static_cast(leftAdjustPx) * ONE_OVER_64); + StoreFloat(outWords, HORIZONTAL_HALF_EFFECT_WIDTH, static_cast(halfEffectWidthPx) * ONE_OVER_64); + const std::int32_t xMinPx = RoundMulFixed(static_cast(face->bbox.xMin) + xShift, xScale); + const std::int32_t xMaxPx = RoundMulFixed(static_cast(face->bbox.xMax) + xShift, xScale); + StoreFloat(outWords, HORIZONTAL_LINE_ADVANCE, static_cast(yMaxPx - yMinPx) * ONE_OVER_64); + StoreFloat(outWords, HORIZONTAL_BASELINE, static_cast(yMaxPx) * ONE_OVER_64); + StoreFloat(outWords, HORIZONTAL_X_BOUND_LO, static_cast(xMinPx + leftAdjustPx) * ONE_OVER_64); + StoreFloat(outWords, HORIZONTAL_X_BOUND_HI, static_cast(xMaxPx + halfEffectWidthPx) * ONE_OVER_64); + const std::int32_t maxAdvanceWidthPx = RoundMulFixed(static_cast(face->max_advance_width) + xShift, xScale); + StoreFloat(outWords, HORIZONTAL_MAX_ADVANCE_WIDTH, static_cast(maxAdvanceWidthPx) * ONE_OVER_64); + float caretRiseAdjust = 0.0f; + if (const auto* hhea = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_HHEA))) { + const std::int64_t caretRiseUnits = xShift + static_cast(hhea->caret_Slope_Rise); + const std::int32_t caretRisePx = TruncFixed(caretRiseUnits * xScale); + caretRiseAdjust = static_cast(caretRisePx - halfEffectWidthPx) * ONE_OVER_64; + } + StoreFloat(outWords, HORIZONTAL_CARET_RISE_ADJUST, caretRiseAdjust); + return SCE_FONT_OK; +} + +int APS5_VABI LibraryComputeLayoutAlt(FontObj* obj, const StyleStateBlock* style, std::uint8_t* outWords) { + if (!obj || !style || !outWords) return SCE_FONT_ERROR_FATAL; + const FT_Face face = FaceOf(obj); + if (!face || !face->size) return SCE_FONT_ERROR_FATAL; + const float effectWidth = style->effect_weight_x; + const float effectHeight = style->effect_weight_y; + const float slant = style->slant_ratio; + const auto xScale = static_cast(face->size->metrics.x_scale); + const auto yScale = static_cast(face->size->metrics.y_scale); + const auto* vhea = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_VHEA)); + const std::int64_t yShift = obj->shift_cache_y; + const std::int64_t xShift = obj->shift_cache_x; + const auto units = static_cast(face->units_per_EM); + std::int64_t yAscender = 0; + std::int64_t yDescender = 0; + if (vhea) { + yAscender = static_cast(vhea->Ascender) + yShift; + yDescender = static_cast(vhea->Descender) + yShift; + } + const std::int32_t scaledLeft = RoundMulFixed(static_cast(face->bbox.xMin) + xShift, xScale); + const std::int32_t scaledRight = RoundMulFixed(static_cast(face->bbox.xMax) + xShift, xScale); + std::int32_t xMinScaled = scaledLeft; + std::int32_t xAbsMax = -xMinScaled; + if (xAbsMax < scaledRight) { + xAbsMax = scaledRight; + xMinScaled = -scaledRight; + } + float outEffectWidth = 0.0f; + if (effectWidth != 0.0f) { + const auto unitsScaled = static_cast(TruncMulUnits(xScale, units)); + const std::int32_t effect = TruncateFloatToInt(effectWidth * static_cast(unitsScaled)) / 2; + outEffectWidth = static_cast(effect) * ONE_OVER_64; + } + if (effectHeight != 0.0f) { + const auto unitsScaled = static_cast(TruncMulUnits(yScale, units)); + const std::int32_t delta = TruncateFloatToInt(effectHeight * static_cast(unitsScaled)) / 2; + yAscender -= delta; + yDescender += delta; + } + float outSlantA = 0.0f; + float outSlantB = 0.0f; + if (slant != 0.0f) { + const auto shear = static_cast(TruncateFloatToInt(slant * 65536.0f)); + const std::int64_t signAdjustMin = yAscender < 1 ? 0x10000LL : 0LL; + std::int64_t roundedMin = signAdjustMin + (-yAscender) * shear - 0x8000LL; + if (roundedMin < 0) roundedMin = signAdjustMin + 0x7FFFLL + (-yAscender) * shear; + const std::int64_t signAdjustMax = yDescender >= 0 ? 0x10000LL : 0LL; + std::int64_t roundedMax = signAdjustMax + yDescender * shear - 0x8000LL; + if (roundedMax < 0) roundedMax = signAdjustMax + 0x7FFFLL + yDescender * shear; + const auto roundedMaxBits = static_cast(roundedMax); + const auto roundedMinBits = static_cast(roundedMin); + const std::uint64_t roundedMaxHigh = roundedMaxBits >> 16; + const bool maxRoundBitClear = ((roundedMaxBits >> 15) & 1u) == 0; + std::uint64_t selectedHigh = roundedMaxHigh; + auto selectedLow = static_cast(roundedMinBits >> 16); + if (maxRoundBitClear && roundedMaxHigh != 0) { + selectedHigh = roundedMinBits >> 16; + selectedLow = static_cast(roundedMaxBits >> 16); + } + outSlantA = static_cast(selectedLow) * ONE_OVER_64; + outSlantB = static_cast(static_cast(selectedHigh)) * ONE_OVER_64; + } + StoreFloat(outWords, VERTICAL_COLUMN_ADVANCE, static_cast(xAbsMax - xMinScaled) * ONE_OVER_64); + StoreFloat(outWords, VERTICAL_BASELINE_OFFSET_X, static_cast(-xAbsMax) * ONE_OVER_64); + StoreFloat(outWords, VERTICAL_METRICS_0X08, static_cast(RoundMulFixed(yAscender, xScale)) * ONE_OVER_64); + StoreFloat(outWords, VERTICAL_METRICS_0X0C, static_cast(RoundMulFixed(yDescender, xScale)) * ONE_OVER_64); + const std::int32_t advanceHeight = RoundMulFixed(static_cast(face->max_advance_height) + yShift, yScale); + StoreFloat(outWords, VERTICAL_ADVANCE_HEIGHT, static_cast(advanceHeight) * ONE_OVER_64); + StoreFloat(outWords, VERTICAL_DECORATION_SPAN, outEffectWidth); + StoreFloat(outWords, VERTICAL_DECORATION_0X08, outSlantB); + StoreFloat(outWords, VERTICAL_DECORATION_0X0C, outSlantA); + return SCE_FONT_OK; +} + +void StoreSeedVector(FontObj* obj) { + const auto seedLow = static_cast(static_cast(obj->layout_seed_pair & 0xFFFFFFFFu)); + const auto seedHigh = static_cast(static_cast(obj->layout_seed_pair >> 32)); + obj->layout_seed_vec[0] = static_cast(static_cast(seedLow)); + obj->layout_seed_vec[1] = static_cast(static_cast(seedHigh)); +} + +void WriteOutlineSizes(FT_GlyphSlot slot, std::uint64_t* outWords) { + const auto contours = static_cast(slot->outline.n_contours); + const auto points = static_cast(slot->outline.n_points); + const std::uint32_t size4 = points * 0x10u + 0x68u + ((points + 0x0Fu + contours * 2u) & 0xFFFFFFF0u); + const std::uint32_t size8 = ((points + 0x2Bu + contours * 2u) & 0xFFFFFFFCu) + 0x10u + points * 4u; + auto* out = reinterpret_cast(outWords); + WriteU16(out, 0, static_cast(contours)); + WriteU16(out, 2, static_cast(points)); + WriteU32(out, 4, size4); + WriteU32(out, 8, size8); +} + +int APS5_VABI LibraryLoadGlyphCached(FontObj* obj, std::uint32_t glyphIndex, std::int32_t mode, std::uint64_t* outWords) { + if (outWords) { + outWords[0] = 0; + outWords[1] = 0; + } + if (!obj || !outWords) return SCE_FONT_ERROR_FATAL; + const FT_Face face = FaceOf(obj); + if (!face || !face->glyph) return SCE_FONT_ERROR_FATAL; + const auto unitsPerEm = static_cast(face->units_per_EM); + const bool cachedMatch = obj->cached_glyph_index_0x64 == static_cast(glyphIndex) && obj->cached_units_x_0x68 == unitsPerEm && obj->cached_units_y_0x70 == obj->cached_units_x_0x68 && mode == 0; + if (cachedMatch) { + obj->shift_cache_x = obj->shift_units_x; + obj->shift_cache_y = obj->shift_units_y; + StoreSeedVector(obj); + WriteOutlineSizes(face->glyph, outWords); + return SCE_FONT_OK; + } + const FT_Int32 loadFlags = (mode == 0 ? FT_LOAD_NO_SCALE : 0) | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_VERTICAL_LAYOUT; + const FT_Error error = FT_Load_Glyph(face, glyphIndex, loadFlags); + if (error != 0) { + obj->cached_glyph_index_0x64 = 0; + obj->cached_units_x_0x68 = 0; + obj->cached_units_y_0x70 = 0; + obj->layout_seed_vec = {}; + obj->layout_scale_vec = {}; + return error == FT_Err_Out_Of_Memory ? SCE_FONT_ERROR_ALLOCATION_FAILED : SCE_FONT_ERROR_NO_SUPPORT_GLYPH; + } + if (!face->glyph) return SCE_FONT_ERROR_FATAL; + obj->cached_glyph_index_0x64 = static_cast(glyphIndex); + if (mode == 0) { + obj->cached_units_x_0x68 = unitsPerEm; + obj->cached_units_y_0x70 = unitsPerEm; + obj->shift_cache_x = obj->shift_units_x; + obj->shift_cache_y = obj->shift_units_y; + StoreSeedVector(obj); + obj->layout_scale_vec[0] = 0x10000; + obj->layout_scale_vec[1] = 0x10000; + } else { + if (!face->size) return SCE_FONT_ERROR_FATAL; + const auto xScale = static_cast(face->size->metrics.x_scale); + const auto yScale = static_cast(face->size->metrics.y_scale); + obj->cached_units_x_0x68 = static_cast(TruncMulUnits(xScale, unitsPerEm)); + obj->cached_units_y_0x70 = static_cast(TruncMulUnits(yScale, unitsPerEm)); + obj->shift_cache_x = RoundMulFixed(obj->shift_units_x, xScale); + obj->shift_cache_y = RoundMulFixed(obj->shift_units_y, yScale); + const auto seedLow = static_cast(static_cast(obj->layout_seed_pair & 0xFFFFFFFFu)); + const auto seedHigh = static_cast(static_cast(obj->layout_seed_pair >> 32)); + obj->layout_seed_vec[0] = static_cast(static_cast(RoundMulFixed(seedLow, xScale))); + obj->layout_seed_vec[1] = static_cast(static_cast(RoundMulFixed(seedHigh, yScale))); + obj->layout_scale_vec[0] = static_cast(xScale); + obj->layout_scale_vec[1] = static_cast(yScale); + } + WriteOutlineSizes(face->glyph, outWords); + return SCE_FONT_OK; +} + +std::int64_t RoundMulFixed64(std::int64_t value, std::int64_t fixed16) { + return static_cast(RoundMulFixed(value, fixed16)); +} + +int APS5_VABI LibraryGetGlyphMetrics(FontObj* obj, std::uint32_t* optParam2, std::uint8_t mode, std::uint8_t* outParams, FontGlyphMetrics* outMetrics) { + if (!obj || !outParams || !outMetrics) return SCE_FONT_ERROR_FATAL; + const FT_Face face = FaceOf(obj); + if (!face || !face->glyph || !face->size) return SCE_FONT_ERROR_FATAL; + const FT_GlyphSlot slot = face->glyph; + std::memset(outParams, 0, 0x48); + const void* outlineBlob = &slot->outline; + outParams[0] = 0xF2; + WritePointer(outParams, 0x08, slot); + if (optParam2) { + outlineBlob = optParam2; + outParams[0] = 0xF0; + WritePointer(outParams, 0x08, optParam2); + } + const auto faceFlagBit = static_cast((static_cast(face->face_flags) >> 5) & 1u); + const bool hasSeedVector = obj->layout_seed_vec[0] != 0 || obj->layout_seed_vec[1] != 0; + outParams[2] = static_cast(0xF0u | (hasSeedVector ? 0x08u : 0u) | faceFlagBit); + outParams[3] = 0; + WriteU16(outParams, 0x04, static_cast(face->units_per_EM)); + WritePointer(outParams, 0x10, outlineBlob); + WritePointer(outParams, 0x18, outMetrics); + float styleShiftX = 0.0f; + float styleShiftY = 0.0f; + if (obj->reserved_0x04 != 0) { + styleShiftX = static_cast(slot->metrics.vertBearingX - slot->metrics.horiBearingX); + styleShiftY = static_cast(-(slot->metrics.horiBearingY + slot->metrics.vertBearingY)); + outParams[1] = 1; + } else { + outParams[1] = 0; + } + StoreFloat(outParams, 0x20, styleShiftX); + StoreFloat(outParams, 0x24, styleShiftY); + StoreFloat(outParams, 0x28, 1.0f); + const auto shiftX = static_cast(obj->shift_cache_x); + const auto shiftY = static_cast(obj->shift_cache_y); + const auto seedX = static_cast(obj->layout_seed_vec[0]); + const auto seedY = static_cast(obj->layout_seed_vec[1]); + WriteS32(outParams, 0x2C, shiftX); + WriteS32(outParams, 0x30, shiftY); + WriteS32(outParams, 0x34, seedX); + WriteS32(outParams, 0x38, seedY); + StoreFloat(outParams, 0x3C, static_cast(static_cast(obj->cached_units_x_0x68)) * ONE_OVER_64); + StoreFloat(outParams, 0x40, static_cast(static_cast(obj->cached_units_y_0x70)) * ONE_OVER_64); + WriteU32(outParams, 0x44, 0); + auto xScale = static_cast(face->size->metrics.x_scale); + auto yScale = static_cast(face->size->metrics.y_scale); + const auto objScaleX = static_cast(obj->layout_scale_vec[0]); + const auto objScaleY = static_cast(obj->layout_scale_vec[1]); + if (objScaleX != 0 && objScaleX != 0x10000) xScale = (xScale << 16) / objScaleX; + if (objScaleY != 0 && objScaleY != 0x10000) yScale = (yScale << 16) / objScaleY; + const bool applyScale = (mode & 0x0Fu) != 0; + const auto scaleIf = [&](std::int64_t value, std::int64_t scale) { + return applyScale ? RoundMulFixed64(value, scale) : value; + }; + const auto widthRaw = static_cast(slot->metrics.width); + const auto heightRaw = static_cast(slot->metrics.height); + const auto horiBearingXRaw = static_cast(slot->metrics.horiBearingX); + const auto horiAdvanceRaw = static_cast(slot->metrics.horiAdvance); + const auto vertBearingXRaw = static_cast(slot->metrics.vertBearingX); + const auto vertBearingYRaw = static_cast(slot->metrics.vertBearingY); + const auto vertAdvanceRaw = static_cast(slot->metrics.vertAdvance); + const std::int64_t horiAdvanceUnscaled = seedX + horiAdvanceRaw; + const std::int64_t vertAdvanceUnscaled = seedY + vertAdvanceRaw; + slot->metrics.horiAdvance = static_cast(horiAdvanceUnscaled); + slot->metrics.vertAdvance = static_cast(vertAdvanceUnscaled); + if (widthRaw == 0 && heightRaw == 0) { + *outMetrics = {}; + outMetrics->Horizontal.advance = FromF26Dot6(scaleIf(horiAdvanceUnscaled, xScale)); + outMetrics->Vertical.advance = FromF26Dot6(scaleIf(vertAdvanceUnscaled, yScale)); + return SCE_FONT_OK; + } + const std::int64_t topShift = heightRaw == 0 ? 0 : shiftY; + std::int64_t left = horiBearingXRaw + shiftX; + std::int64_t top = vertBearingYRaw + topShift; + std::int64_t right = left + widthRaw; + std::int64_t bottom = top - heightRaw; + left = scaleIf(left, xScale); + top = scaleIf(top, yScale); + right = scaleIf(right, xScale); + bottom = scaleIf(bottom, yScale); + const std::int64_t horiAdvance = scaleIf(horiAdvanceUnscaled, xScale); + const std::int64_t vertAdvance = scaleIf(vertAdvanceUnscaled, yScale); + const std::int64_t outWidth = right - left; + const std::int64_t outHeight = top - bottom; + std::int64_t vertBearingX; + std::int64_t vertBearingY; + if ((static_cast(face->face_flags) & 0x20u) == 0) { + vertBearingX = left - horiAdvance / 2; + vertBearingY = (vertAdvance - outHeight) / 2; + } else { + if (seedX == 0) { + vertBearingX = scaleIf(shiftX + vertBearingXRaw, xScale); + } else { + slot->metrics.vertBearingX = static_cast(horiBearingXRaw - horiAdvanceUnscaled / 2); + vertBearingX = left - horiAdvance / 2; + StoreFloat(outParams, 0x20, static_cast(-(horiAdvanceUnscaled / 2))); + } + vertBearingY = scaleIf(topShift + vertBearingYRaw, yScale); + } + outMetrics->width = FromF26Dot6(outWidth); + outMetrics->height = FromF26Dot6(outHeight); + outMetrics->Horizontal.bearingX = FromF26Dot6(left); + outMetrics->Horizontal.bearingY = FromF26Dot6(top); + outMetrics->Horizontal.advance = FromF26Dot6(horiAdvance); + outMetrics->Vertical.bearingX = FromF26Dot6(vertBearingX); + outMetrics->Vertical.bearingY = FromF26Dot6(vertBearingY); + outMetrics->Vertical.advance = FromF26Dot6(vertAdvance); + if ((mode & 0x06u) != 0 && obj->font_handle) { + const auto* handle = reinterpret_cast(obj->font_handle); + const StyleStateBlock* style = (mode & 0x04u) ? &handle->cached_style.state : &handle->style; + const float slantRatio = style->slant_ratio; + if ((slantRatio != 0.0f || std::isnan(slantRatio)) && slot->outline.points && slot->outline.n_points > 0) { + const auto shear = static_cast(TruncateFloatToInt(slantRatio * 65536.0f)); + std::int64_t minX = std::numeric_limits::max(); + std::int64_t maxX = std::numeric_limits::min(); + for (int i = 0; i < slot->outline.n_points; ++i) { + const FT_Vector point = slot->outline.points[i]; + const std::int64_t px = scaleIf(static_cast(point.x) + shiftX, xScale); + const std::int64_t py = scaleIf(static_cast(point.y) + shiftY, yScale); + const std::int64_t slantedX = px + RoundMulFixed64(py, shear); + minX = std::min(minX, slantedX); + maxX = std::max(maxX, slantedX); + } + if (minX <= maxX) { + outMetrics->width = FromF26Dot6(maxX - minX); + outMetrics->Horizontal.bearingX = FromF26Dot6(minX); + } + } + const float effectX = style->effect_weight_x; + const float effectY = style->effect_weight_y; + if (effectX != 0.0f || std::isnan(effectX) || effectY != 0.0f || std::isnan(effectY)) { + const float dx = effectX * obj->scale_x_0x50 * 0.5f; + const float dy = effectY * obj->scale_y_0x54 * 0.5f; + outMetrics->width = outMetrics->width + dx + dx; + outMetrics->height = outMetrics->height + dy + dy; + outMetrics->Horizontal.bearingX -= dx; + outMetrics->Horizontal.bearingY += dy; + outMetrics->Vertical.bearingX -= dx; + outMetrics->Vertical.bearingY -= dy; + if (outMetrics->Horizontal.advance != 0.0f || std::isnan(outMetrics->Horizontal.advance)) outMetrics->Horizontal.advance += dx; + if (outMetrics->Vertical.advance != 0.0f || std::isnan(outMetrics->Vertical.advance)) outMetrics->Vertical.advance += dy; + } + } + return SCE_FONT_OK; +} + +int APS5_VABI LibraryApplyGlyphAdjust(FontObj* obj, std::uint32_t p2, std::uint32_t glyphIndex, std::int32_t p4, std::int32_t p5, std::uint32_t* inoutGlyphIndex) { + (void)p2; + if (!obj || !inoutGlyphIndex) return SCE_FONT_ERROR_FATAL; + *inoutGlyphIndex = glyphIndex; + obj->shift_units_x = p4; + obj->shift_units_y = p5; + obj->layout_seed_pair = 0; + return SCE_FONT_OK; +} + +int APS5_VABI LibraryConfigureGlyph(FontObj* obj, std::uint32_t* inParams, std::int32_t mode, std::uint32_t* inoutState) { + if (!obj || !inParams || !inoutState) return SCE_FONT_ERROR_FATAL; + if (*inParams != 0) reinterpret_cast(inoutState)[3] |= 0x80; + obj->glyph_cfg_word_0x130 = *inParams; + obj->glyph_cfg_mode_0x134 = static_cast(mode); + obj->glyph_cfg_byte_0x136 = 0; + obj->glyph_cfg_byte_0x135 = 0; + return SCE_FONT_OK; +} + +int APS5_VABI RendererCreate(RendererNative* renderer) { + if (!renderer) return SCE_FONT_ERROR_INVALID_RENDERER; + auto* ft = reinterpret_cast(renderer); + ft->ft_backend.renderer_header_0x10 = &ft->base.mem_kind; + ft->ft_backend.unknown_0x08 = 0; + ft->ft_backend.unknown_0x10 = 0; + ft->ft_backend.unknown_0x18 = 0; + ft->ft_backend.initialized_marker = ft; + return SCE_FONT_OK; +} + +int APS5_VABI RendererDestroy(RendererNative* renderer) { + if (!renderer) return SCE_FONT_ERROR_INVALID_RENDERER; + reinterpret_cast(renderer)->ft_backend.initialized_marker = nullptr; + return SCE_FONT_OK; +} + +std::uint64_t APS5_VABI RendererQuery(RendererNative* renderer, std::uint8_t* params, std::int64_t* outPtr, std::uint8_t* outVector) { + (void)renderer; + (void)params; + if (outVector) std::memset(outVector, 0, 16); + if (outPtr) *outPtr = 0; + return 0; +} + +SysDriver MakeDriverTable() { + SysDriver driver{}; + driver.pixel_resolution = &LibraryGetPixelResolution; + driver.init = &LibraryInit; + driver.term = &LibraryTerm; + driver.support_formats = &LibrarySupport; + driver.open = &LibraryOpenFont; + driver.close = &LibraryCloseFont; + driver.scale = &LibraryGetFaceScale; + driver.metric = &LibraryGetFaceMetric; + driver.glyph_index = &LibraryGetGlyphIndex; + driver.set_char_with_dpi = &LibrarySetCharSizeWithDpi; + driver.set_char_default_dpi = &LibrarySetCharSizeDefaultDpi; + driver.compute_layout = &LibraryComputeLayout; + driver.compute_layout_alt = &LibraryComputeLayoutAlt; + driver.load_glyph_cached = &LibraryLoadGlyphCached; + driver.get_glyph_metrics = &LibraryGetGlyphMetrics; + driver.apply_glyph_adjust = &LibraryApplyGlyphAdjust; + driver.configure_glyph = &LibraryConfigureGlyph; + return driver; +} + +const SysDriver driverTable = MakeDriverTable(); +const RendererSelection rendererTable{0, sizeof(RendererFt), &RendererCreate, &RendererDestroy, &RendererQuery}; + +} + +const Font::SysDriver* FontFt::DriverTable() { + return &driverTable; +} + +const Font::RendererSelection* FontFt::RendererTable() { + return &rendererTable; +} diff --git a/core/libs/prx/libSceMouse/CMakeLists.txt b/core/libs/prx/libSceMouse/CMakeLists.txt new file mode 100644 index 000000000..76b0c4fe8 --- /dev/null +++ b/core/libs/prx/libSceMouse/CMakeLists.txt @@ -0,0 +1,22 @@ +add_library(libSceMouse SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/mouse_impl.cpp +) +target_include_directories(libSceMouse PRIVATE ${LIBS_INCLUDE_DIR}) +set_target_properties(libSceMouse PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(libSceMouse) + +if(BUILD_TESTING) + add_executable(mouse_tests + ${CMAKE_CURRENT_SOURCE_DIR}/tests/Mouse.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../libSceVideoOut/src/MouseInput.cpp + ) + target_include_directories(mouse_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include) + target_link_libraries(mouse_tests PRIVATE libSceMouse libkernel libc) + configure_windows_unwind(mouse_tests) + add_test(NAME mouse_input COMMAND mouse_tests) + if(WIN32) + set_tests_properties(mouse_input PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceMouse/Export.cpp b/core/libs/prx/libSceMouse/Export.cpp index 5e6120682..9d820d245 100644 --- a/core/libs/prx/libSceMouse/Export.cpp +++ b/core/libs/prx/libSceMouse/Export.cpp @@ -1,36 +1,25 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" +#include "prx/libSceMouse/include/MouseState.hpp" extern "C" { -int APS5_VABI sceMouseClose(int32_t handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseInit() { + return Mouse::Initialize(); } -int APS5_VABI sceMouseInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseOpen(int userId, std::int32_t type, std::int32_t index, const void* param) { + return Mouse::Open(userId, type, index, static_cast(param)); } -int APS5_VABI sceMouseOpen(int user_id, int32_t type, int32_t index, const void* param) { - (void)user_id; - (void)type; - (void)index; - (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseClose(std::int32_t handle) { + return Mouse::Close(handle); } -int APS5_VABI sceMouseRead(int32_t handle, MouseData* data, int32_t num) { - (void)handle; - (void)data; - (void)num; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseRead(std::int32_t handle, MouseData* data, std::int32_t num) { + return Mouse::Read(handle, data, num); +} + +void MousePublishInput_nid_postfix(const MouseInputEvent& event) { + Mouse::Publish(event); } } diff --git a/core/libs/prx/libSceMouse/include/MouseState.hpp b/core/libs/prx/libSceMouse/include/MouseState.hpp new file mode 100644 index 000000000..bf7d57a09 --- /dev/null +++ b/core/libs/prx/libSceMouse/include/MouseState.hpp @@ -0,0 +1,28 @@ +#ifndef CORE_LIBS_PRX_LIBSCEMOUSE_MOUSESTATE_HPP +#define CORE_LIBS_PRX_LIBSCEMOUSE_MOUSESTATE_HPP + +#include "prx/libSceMouse/include/mouse_structs.h" + +struct MouseInputEvent { + std::int32_t x = 0; + std::int32_t y = 0; + std::int32_t wheel = 0; + std::int32_t tilt = 0; + std::uint32_t button = 0; + bool pressed = false; + bool resetButtons = false; + bool connectionChange = false; + bool connected = true; +}; + +namespace Mouse { +int Initialize(); +int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam* param); +int Close(std::int32_t handle); +int Read(std::int32_t handle, MouseData* data, std::int32_t num); +void Publish(const MouseInputEvent& event); +} + +extern "C" void MousePublishInput_nid_postfix(const MouseInputEvent& event); + +#endif diff --git a/core/libs/prx/libSceMouse/include/mouse_structs.h b/core/libs/prx/libSceMouse/include/mouse_structs.h new file mode 100644 index 000000000..30c7d3a8f --- /dev/null +++ b/core/libs/prx/libSceMouse/include/mouse_structs.h @@ -0,0 +1,34 @@ +#ifndef CORE_LIBS_PRX_LIBSCEMOUSE_MOUSE_STRUCTS_H +#define CORE_LIBS_PRX_LIBSCEMOUSE_MOUSE_STRUCTS_H + +#include +#include +#include "SceTypes.hpp" + +struct MouseOpenParam { + std::uint8_t behaviorFlag; + std::uint8_t reserved[7]; +}; + +constexpr int MOUSE_OK = 0; +constexpr int MOUSE_ERROR_INVALID_ARG = static_cast(0x80df0001u); +constexpr int MOUSE_ERROR_INVALID_HANDLE = static_cast(0x80df0003u); +constexpr int MOUSE_ERROR_ALREADY_OPENED = static_cast(0x80df0004u); +constexpr int MOUSE_ERROR_NOT_INITIALIZED = static_cast(0x80df0005u); +constexpr int MOUSE_MAX_DATA_NUM = 64; +constexpr int MOUSE_HANDLE = 1; +constexpr std::uint8_t MOUSE_OPEN_PARAM_MERGED = 1; + +static_assert(sizeof(MouseOpenParam) == 8); +static_assert(sizeof(MouseData) == 40); +static_assert(alignof(MouseData) == 8); +static_assert(offsetof(MouseData, timestamp) == 0); +static_assert(offsetof(MouseData, connected) == 8); +static_assert(offsetof(MouseData, buttons) == 12); +static_assert(offsetof(MouseData, x_axis) == 16); +static_assert(offsetof(MouseData, y_axis) == 20); +static_assert(offsetof(MouseData, wheel) == 24); +static_assert(offsetof(MouseData, tilt) == 28); +static_assert(offsetof(MouseData, reserved) == 32); + +#endif diff --git a/core/libs/prx/libSceMouse/src/mouse_impl.cpp b/core/libs/prx/libSceMouse/src/mouse_impl.cpp new file mode 100644 index 000000000..5bcf78e72 --- /dev/null +++ b/core/libs/prx/libSceMouse/src/mouse_impl.cpp @@ -0,0 +1,106 @@ +#include "prx/libSceMouse/include/MouseState.hpp" +#include "prx/libkernel/Time/include/Time.hpp" + +#include +#include +#include + +namespace { +std::mutex mouseMutex; +std::array history{}; +int head = 0; +int count = 0; +bool initialized = false; +bool opened = false; +bool connected = true; +std::uint32_t buttons = 0; + +void enqueue(const MouseData& data) { + if (count == MOUSE_MAX_DATA_NUM) { + head = (head + 1) % MOUSE_MAX_DATA_NUM; + --count; + } + history[(head + count) % MOUSE_MAX_DATA_NUM] = data; + ++count; +} +} + +namespace Mouse { + +int Initialize() { + std::lock_guard lock(mouseMutex); + initialized = true; + return MOUSE_OK; +} + +int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam* param) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (userId < 0 || type != 0 || index != 0 || + (param != nullptr && (param->behaviorFlag & ~MOUSE_OPEN_PARAM_MERGED) != 0)) { + return MOUSE_ERROR_INVALID_ARG; + } + if (opened) return MOUSE_ERROR_ALREADY_OPENED; + opened = true; + buttons = 0; + head = 0; + count = 0; + MouseData initial{}; + initial.timestamp = sceKernelGetProcessTime(); + initial.connected = connected; + enqueue(initial); + return MOUSE_HANDLE; +} + +int Close(std::int32_t handle) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + opened = false; + buttons = 0; + head = 0; + count = 0; + return MOUSE_OK; +} + +int Read(std::int32_t handle, MouseData* data, std::int32_t num) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + if (data == nullptr || num <= 0 || num > MOUSE_MAX_DATA_NUM) return MOUSE_ERROR_INVALID_ARG; + const int available = std::min(count, num); + for (int i = 0; i < available; ++i) { + data[i] = history[head]; + head = (head + 1) % MOUSE_MAX_DATA_NUM; + } + count -= available; + return available; +} + +void Publish(const MouseInputEvent& event) { + std::lock_guard lock(mouseMutex); + if (event.connectionChange) { + if (connected == event.connected) return; + connected = event.connected; + if (!connected) buttons = 0; + } else if (!connected) { + return; + } + if (!opened) return; + if (event.resetButtons) buttons = 0; + if (event.button != 0) { + if (event.pressed) buttons |= event.button; + else buttons &= ~event.button; + } + MouseData data{}; + data.timestamp = sceKernelGetProcessTime(); + data.connected = connected; + data.buttons = buttons; + data.x_axis = event.x; + data.y_axis = event.y; + data.wheel = event.wheel; + data.tilt = event.tilt; + enqueue(data); +} + +} diff --git a/core/libs/prx/libSceMouse/tests/Mouse.cpp b/core/libs/prx/libSceMouse/tests/Mouse.cpp new file mode 100644 index 000000000..e01f3f9b4 --- /dev/null +++ b/core/libs/prx/libSceMouse/tests/Mouse.cpp @@ -0,0 +1,108 @@ +#include "prx/libSceMouse/include/mouse_structs.h" +#include "prx/libSceMouse/include/MouseState.hpp" +#include "prx/libSceVideoOut/include/MouseInput.hpp" +#include "SDL_events.h" +#include "SDL_mouse.h" + +#include + +extern "C" { +int APS5_VABI sceMouseInit(); +int APS5_VABI sceMouseOpen(int, std::int32_t, std::int32_t, const void*); +int APS5_VABI sceMouseClose(std::int32_t); +int APS5_VABI sceMouseRead(std::int32_t, MouseData*, std::int32_t); +} + +static void Require(bool value) { if (!value) std::abort(); } + +int main() { + MouseData data[64]{}; + Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_ERROR_NOT_INITIALIZED); + Require(sceMouseInit() == MOUSE_OK); + Require(sceMouseInit() == MOUSE_OK); + Require(sceMouseOpen(1, 0, 1, nullptr) == MOUSE_ERROR_INVALID_ARG); + MouseOpenParam unsupported{}; + unsupported.behaviorFlag = 2; + Require(sceMouseOpen(1, 0, 0, &unsupported) == MOUSE_ERROR_INVALID_ARG); + Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_HANDLE); + Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_ERROR_ALREADY_OPENED); + Require(sceMouseRead(42, data, 1) == MOUSE_ERROR_INVALID_HANDLE); + Require(sceMouseRead(MOUSE_HANDLE, nullptr, 1) == MOUSE_ERROR_INVALID_ARG); + Require(sceMouseRead(MOUSE_HANDLE, data, 65) == MOUSE_ERROR_INVALID_ARG); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1); + Require(data[0].connected && data[0].buttons == 0); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + + MouseInput input; + SDL_Event event{}; + event.type = SDL_MOUSEMOTION; + event.motion.windowID = 7; + event.motion.xrel = 9; + event.motion.yrel = -4; + input.HandleEvent(event, 8); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + input.HandleEvent(event, 7); + + event = {}; + event.type = SDL_MOUSEBUTTONDOWN; + event.button.windowID = 7; + event.button.button = SDL_BUTTON_RIGHT; + input.HandleEvent(event, 7); + event = {}; + event.type = SDL_MOUSEWHEEL; + event.wheel.windowID = 7; + event.wheel.x = 2; + event.wheel.y = -3; + event.wheel.direction = SDL_MOUSEWHEEL_FLIPPED; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 3) == 3); + Require(data[0].x_axis == 9 && data[0].y_axis == -4 && data[0].buttons == 0); + Require(data[1].buttons == 2); + Require(data[2].buttons == 2 && data[2].wheel == 3 && data[2].tilt == -2); + Require(data[0].timestamp <= data[2].timestamp); + event = {}; + event.type = SDL_MOUSEBUTTONDOWN; + event.button.windowID = 7; + event.button.button = SDL_BUTTON_X1; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + + event = {}; + event.type = SDL_WINDOWEVENT; + event.window.windowID = 7; + event.window.event = SDL_WINDOWEVENT_FOCUS_LOST; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1); + Require(data[0].connected && data[0].buttons == 0); + event.type = SDL_MOUSEMOTION; + event.motion.xrel = 5; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + event.type = SDL_WINDOWEVENT; + event.window.event = SDL_WINDOWEVENT_FOCUS_GAINED; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + event.window.event = SDL_WINDOWEVENT_CLOSE; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && !data[0].connected); + event.window.event = SDL_WINDOWEVENT_FOCUS_GAINED; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && data[0].connected); + + for (int i = 0; i < 70; ++i) { + MouseInputEvent motion{}; + motion.x = i; + MousePublishInput_nid_postfix(motion); + } + Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 64); + Require(data[0].x_axis == 6 && data[63].x_axis == 69); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == MOUSE_ERROR_INVALID_HANDLE); + Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_ERROR_INVALID_HANDLE); + MouseOpenParam merged{}; + merged.behaviorFlag = MOUSE_OPEN_PARAM_MERGED; + Require(sceMouseOpen(1, 0, 0, &merged) == MOUSE_HANDLE); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && data[0].buttons == 0); + Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK); +} diff --git a/core/libs/prx/libSceMsgDialog/Export.cpp b/core/libs/prx/libSceMsgDialog/Export.cpp index f826fe36a..1e02b26ea 100644 --- a/core/libs/prx/libSceMsgDialog/Export.cpp +++ b/core/libs/prx/libSceMsgDialog/Export.cpp @@ -1,66 +1,173 @@ +// libSceMsgDialog: message dialog state machine (NONE -> INITIALIZED -> RUNNING -> FINISHED) without a visible dialog. +// The dialog "closes" with the affirmative button after a couple of status polls, so a title waiting for the user +// never blocks. The message text is logged for the first calls. +#include #include +#include #include +#include +#include +#include + #include "SceTypes.hpp" +#include "HitLog.hpp" #include "prx/libc/include/General.hpp" +#ifdef _WIN32 +#include +#endif + +namespace { +constexpr int kStatusNone = 0; +constexpr int kStatusInitialized = 1; +constexpr int kStatusRunning = 2; +constexpr int kStatusFinished = 3; + +constexpr int kErrOk = 0; +constexpr int kErrNotInitialized = static_cast(0x80B80003u); +constexpr int kErrNotFinished = static_cast(0x80B80005u); +constexpr int kErrBusy = static_cast(0x80B80007u); +constexpr int kErrNotRunning = static_cast(0x80B8000Bu); +constexpr int kErrArgNull = static_cast(0x80B8000Du); + +constexpr int kModeUserMsg = 1; +constexpr int kModeProgressBar = 2; +constexpr int kModeSystemMsg = 3; + +constexpr int kButtonTypeOk = 0; +constexpr int kButtonTypeYesNo = 1; +constexpr int kButtonTypeNone = 2; +constexpr int kButtonTypeOkCancel = 3; +constexpr int kButtonTypeYesNoFocusNo = 4; + +constexpr int kButtonIdOk = 1; // OK / YES / BUTTON1 +constexpr int kButtonIdNo = 2; // NO / BUTTON2 +constexpr int kResultOk = 0; + +std::mutex g_lock; +int g_status = kStatusNone; +int g_mode = kModeUserMsg; +int g_buttonId = kButtonIdOk; +int g_pollsLeft = 0; +std::chrono::steady_clock::time_point g_openedAt; + +bool Readable(const void* p, std::size_t n) { +#ifdef _WIN32 + if (p == nullptr) return false; + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(p, &mbi, sizeof(mbi)) == 0 || mbi.State != MEM_COMMIT) return false; + if ((mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) return false; + const auto end = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; + return reinterpret_cast(p) + n <= end; +#else + return p != nullptr; +#endif +} + +// Best-effort inspection of SceMsgDialogParam: { SceCommonDialogBaseParam base (0x30); size_t size; int mode (0x38); +// UserMessageParam* userMsg (0x40); ProgressBarParam* progress (0x48); SystemMessageParam* system (0x50); ... }. +void Inspect(const void* param) { + const std::uint8_t* bytes = static_cast(param); + g_mode = kModeUserMsg; + g_buttonId = kButtonIdOk; + if (!Readable(bytes, 0x60)) return; + std::int32_t mode = 0; + std::memcpy(&mode, bytes + 0x38, 4); + if (mode >= kModeUserMsg && mode <= kModeSystemMsg) g_mode = mode; + if (g_mode != kModeUserMsg) return; + const std::uint8_t* userMsg = nullptr; + std::memcpy(&userMsg, bytes + 0x40, 8); + if (!Readable(userMsg, 16)) return; + std::int32_t buttonType = 0; + std::memcpy(&buttonType, userMsg, 4); + const char* msg = nullptr; + std::memcpy(&msg, userMsg + 8, 8); + if (buttonType == kButtonTypeYesNoFocusNo) g_buttonId = kButtonIdNo; + if (Readable(msg, 4)) { + char text[161] = {}; + for (int i = 0; i < 160 && Readable(msg + i, 1) && msg[i] != '\0'; ++i) text[i] = (msg[i] >= 0x20 || msg[i] == '\n') ? msg[i] : '?'; + APS5_HIT("MSGDIALOG", "open: buttons=%d text=\"%s\" -> auto answer", buttonType, text); + } +} + +int Poll() { + std::lock_guard g(g_lock); + if (g_status == kStatusRunning) { + bool done = false; + if (g_mode == kModeProgressBar) { + // Progress dialogs are ended by the title (sceMsgDialogClose); the cap only prevents a wait without end. + done = std::chrono::steady_clock::now() - g_openedAt > std::chrono::seconds(30); + } else { + done = --g_pollsLeft <= 0; + } + if (done) g_status = kStatusFinished; + } + return g_status; +} +} // namespace + extern "C" { -int APS5_VABI sceMsgDialogClose(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceMsgDialogGetResult(void* result) { - (void)result; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceMsgDialogGetStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceMsgDialogInitialize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + if (g_status == kStatusNone) g_status = kStatusInitialized; + APS5_HIT("MSGDIALOG", "sceMsgDialogInitialize"); + return kErrOk; +} +int APS5_VABI sceMsgDialogTerminate(void) { + std::lock_guard g(g_lock); + const bool was = g_status != kStatusNone; + g_status = kStatusNone; + return was ? kErrOk : kErrNotInitialized; } - int APS5_VABI sceMsgDialogOpen(const void* param) { - (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; + if (param == nullptr) return kErrArgNull; + std::lock_guard g(g_lock); + if (g_status == kStatusRunning) return kErrBusy; + // A title that skipped sceMsgDialogInitialize still gets its dialog (and its FINISHED status). + Inspect(param); + g_pollsLeft = 2; + g_openedAt = std::chrono::steady_clock::now(); + g_status = kStatusRunning; + return kErrOk; } - -int APS5_VABI sceMsgDialogProgressBarInc(int target, uint32_t delta) { +int APS5_VABI sceMsgDialogGetStatus(void) { + return Poll(); +} +int APS5_VABI sceMsgDialogUpdateStatus(void) { + return Poll(); +} +// SceMsgDialogResult: { int32 mode; int32 result; int32 buttonId; uint8 reserved[32] }. +int APS5_VABI sceMsgDialogGetResult(void* result) { + if (result == nullptr) return kErrArgNull; + std::lock_guard g(g_lock); + if (g_status != kStatusFinished) return kErrNotFinished; + std::int32_t out[3] = {g_mode, kResultOk, g_buttonId}; + std::memcpy(result, out, sizeof(out)); + return kErrOk; +} +int APS5_VABI sceMsgDialogClose(void) { + std::lock_guard g(g_lock); + if (g_status != kStatusRunning) return kErrNotRunning; + g_status = kStatusFinished; + return kErrOk; +} +int APS5_VABI sceMsgDialogProgressBarInc(int target, std::uint32_t delta) { (void)target; (void)delta; - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + return g_status == kStatusRunning ? kErrOk : kErrNotRunning; } - int APS5_VABI sceMsgDialogProgressBarSetMsg(int target, const char* msg) { (void)target; (void)msg; - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + return g_status == kStatusRunning ? kErrOk : kErrNotRunning; } - -int APS5_VABI sceMsgDialogProgressBarSetValue(int target, uint32_t rate) { +int APS5_VABI sceMsgDialogProgressBarSetValue(int target, std::uint32_t rate) { (void)target; (void)rate; - NotImplemented_nid_no_patch(__func__); - return 0; + std::lock_guard g(g_lock); + return g_status == kStatusRunning ? kErrOk : kErrNotRunning; } - -int APS5_VABI sceMsgDialogTerminate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceMsgDialogUpdateStatus(void) { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index bb4b9bab3..53d25dc66 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -1,339 +1,588 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" #include #include #include +#include #include +#include +#include #include -#include #include +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" -// No network is emulated: pools, resolvers and epoll instances exist, but sockets cannot be -// created, so every socket operation fails with the guest's EBADF. -static constexpr int GUEST_EINTR = 4; -static constexpr int GUEST_EBADF = 9; -static constexpr int GUEST_EINVAL = 22; -static constexpr int GUEST_ENOSPC = 28; -static constexpr int GUEST_EAFNOSUPPORT = 47; -static constexpr int GUEST_ENETDOWN = 50; -static constexpr int GUEST_EHOSTUNREACH = 65; -static constexpr int GUEST_AF_INET = 2; +// Offline network stack: no link is ever up. Sockets are local bookkeeping objects (create/bind/listen/options +// succeed like they do on a console without a connection), anything that needs a peer fails with the same errno a +// console with no route gives, and blocking calls return on their timeout, an abort or a close — infinite +// ("wait forever") timeouts are serviced in bounded slices so the inert stack can never deadlock a caller. -static thread_local int g_netErrno = 0; -static std::atomic g_nextId{1}; +namespace { +// FreeBSD errno numbers as reported through sceNetErrnoLoc (SCE_NET_ERROR_* is 0x80410100 + errno). +constexpr int NET_ENOENT = 2; +constexpr int NET_EBADF = 9; +constexpr int NET_EINVAL = 22; +constexpr int NET_EAGAIN = 35; +constexpr int NET_ENOTSOCK = 38; +constexpr int NET_EOPNOTSUPP = 45; +constexpr int NET_EPROTONOSUPPORT = 43; +constexpr int NET_EAFNOSUPPORT = 47; +constexpr int NET_EADDRINUSE = 48; +constexpr int NET_ENETUNREACH = 51; +constexpr int NET_ECONNABORTED = 53; +constexpr int NET_ENOTCONN = 57; +constexpr int NET_ETIMEDOUT = 60; +constexpr int NET_ECONNREFUSED = 61; +constexpr int NET_ERROR_RESOLVER_ENODNS = static_cast(0x804101E1u); -struct Epoll { - std::mutex lock; - std::condition_variable changed; +constexpr int NET_AF_INET = 2; +constexpr int NET_AF_INET6 = 28; +constexpr int NET_SOCK_STREAM = 1; +constexpr int NET_SOCK_DGRAM = 2; +constexpr int NET_SOCK_RAW = 3; +constexpr int NET_SOL_SOCKET = 0xFFFF; +constexpr int NET_SO_SNDTIMEO = 0x1005; +constexpr int NET_SO_RCVTIMEO = 0x1006; +constexpr int NET_SO_NBIO = 0x1200; + +struct Sock { + int family = 0; + int type = 0; + bool nonblock = false; + bool bound = false; + bool listening = false; bool aborted = false; + std::uint16_t port = 0; // network byte order + std::uint32_t addr = 0; // network byte order + int rcv_timeout_us = 0; + int snd_timeout_us = 0; }; -static std::mutex g_epollLock; -static std::map> g_epolls; +std::mutex g_mutex; +std::condition_variable g_cv; +std::map g_socks; +std::set g_epolls; +std::set g_pools; +std::set g_resolvers; +int g_next_sock = 32; +int g_next_epoll = 0x4000; +int g_next_pool = 1; +int g_next_resolver = 0x100; +std::uint16_t g_next_port = 49152; +bool g_net_inited = false; -static int Fail(int error) { - g_netErrno = error; - return static_cast(0x80410100u | static_cast(error)); +int* errno_slot() { + static thread_local int e = 0; + return &e; } -static std::shared_ptr FindEpoll(int eid) { - std::lock_guard lock(g_epollLock); - const auto found = g_epolls.find(eid); - return found == g_epolls.end() ? nullptr : found->second; +int fail(int err) { + *errno_slot() = err; + return -1; } -static void AbortEpoll(Epoll& epoll) { - { - std::lock_guard lock(epoll.lock); - epoll.aborted = true; +void log_soft(const char* func, const char* what) { + static std::mutex mtx; + static std::map hits; + std::lock_guard lk(mtx); + if (++hits[func] > 3) { + return; } - epoll.changed.notify_all(); + std::fprintf(stderr, "[SOFT-NET] %s -> %s\n", func, what); + std::fflush(stderr); } +std::uint16_t swap16(std::uint16_t v) { return static_cast((v << 8) | (v >> 8)); } +std::uint32_t swap32(std::uint32_t v) { + return (v << 24) | ((v & 0xFF00u) << 8) | ((v >> 8) & 0xFF00u) | (v >> 24); +} + +// Waits on the socket until the timeout expires or it is closed/aborted. +// timeout_us <= 0 nominally means "wait forever", but on an inert stack nothing can ever arrive, so such a +// wait is bounded to INFINITE_WAIT_SLICE_US per iteration and then reports EAGAIN: the caller keeps correct +// blocking-loop semantics without the process being able to deadlock on the network. +// Returns 0 on timeout, -1 with errno set when the socket went away or was aborted. +int block_on(std::unique_lock& lk, int fd, int timeout_us) { + constexpr int INFINITE_WAIT_SLICE_US = 100000; // 100 ms + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::microseconds(timeout_us > 0 ? timeout_us : INFINITE_WAIT_SLICE_US); + for (;;) { + auto it = g_socks.find(fd); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + if (it->second.aborted) { + it->second.aborted = false; + return fail(NET_ECONNABORTED); + } + if (g_cv.wait_until(lk, deadline) == std::cv_status::timeout) { + return fail(NET_EAGAIN); + } + } +} +} // namespace + extern "C" { -int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) { - (void)s; - (void)addr; - (void)addrlen; - return Fail(GUEST_EBADF); -} - -int APS5_VABI sceNetBind_nid_postfix(int s, const void* addr, uint32_t addrlen) { - (void)s; - (void)addr; - (void)addrlen; - return Fail(GUEST_EBADF); -} - -int APS5_VABI sceNetEpollControl(int eid, int op, int id, const NetEpollEvent* event) { - (void)op; - (void)id; - (void)event; - if (!FindEpoll(eid)) return Fail(GUEST_EBADF); - return Fail(GUEST_EBADF); -} - -int APS5_VABI sceNetEpollCreate(const char* name, int flags) { - (void)name; - (void)flags; - const int id = g_nextId.fetch_add(1, std::memory_order_relaxed); - std::lock_guard lock(g_epollLock); - g_epolls[id] = std::make_shared(); - return id; -} - -int APS5_VABI sceNetEpollDestroy(int eid) { - std::lock_guard lock(g_epollLock); - const auto found = g_epolls.find(eid); - if (found == g_epolls.end()) return Fail(GUEST_EBADF); - AbortEpoll(*found->second); - g_epolls.erase(found); - return 0; -} - -int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int timeout) { - (void)events; - (void)maxevents; - auto epoll = FindEpoll(eid); - if (!epoll) return Fail(GUEST_EBADF); - std::unique_lock lock(epoll->lock); - const auto aborted = [&] { return epoll->aborted; }; - if (timeout < 0) epoll->changed.wait(lock, aborted); - else epoll->changed.wait_for(lock, std::chrono::microseconds(timeout), aborted); - if (epoll->aborted) { - epoll->aborted = false; - return Fail(GUEST_EINTR); - } - return 0; -} - -int APS5_VABI sceNetEtherNtostr(const NetEtherAddr* n, char* str, size_t len) { - (void)n; - (void)str; - (void)len; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNetGetMacAddress(NetEtherAddr* addr, int flags) { - (void)addr; - (void)flags; - NotImplemented_nid_no_patch(__func__); - return 0; -} - -int APS5_VABI sceNetGetSockInfo(int s, void* info, int n, int flags) { - (void)s; - (void)info; - (void)n; - (void)flags; - return Fail(GUEST_EBADF); -} - -int APS5_VABI sceNetGetsockname(int s, void* addr, uint32_t* addrlen) { - (void)s; - (void)addr; - (void)addrlen; - return Fail(GUEST_EBADF); -} - -uint32_t APS5_VABI sceNetHtonl_nid_postfix(uint32_t host32) { - return __builtin_bswap32(host32); -} - -uint16_t APS5_VABI sceNetHtons_nid_postfix(uint16_t host16) { - return __builtin_bswap16(host16); -} - -const char* APS5_VABI sceNetInetNtop(int af, const void* src, char* dst, uint32_t size) { - if (af != GUEST_AF_INET || !src || !dst) { - Fail(GUEST_EAFNOSUPPORT); - return nullptr; - } - const auto* bytes = static_cast(src); - if (std::snprintf(dst, size, "%u.%u.%u.%u", bytes[0], bytes[1], bytes[2], bytes[3]) >= static_cast(size)) { - Fail(GUEST_ENOSPC); - return nullptr; - } - return dst; -} - -int APS5_VABI sceNetInetPton(int af, const char* src, void* dst) { - if (af != GUEST_AF_INET) return Fail(GUEST_EAFNOSUPPORT); - if (!src || !dst) return Fail(GUEST_EINVAL); - unsigned parts[4]; - char trailing; - if (std::sscanf(src, "%u.%u.%u.%u%c", &parts[0], &parts[1], &parts[2], &parts[3], &trailing) != 4) return 0; - auto* bytes = static_cast(dst); - for (int index = 0; index < 4; ++index) { - if (parts[index] > 255) return 0; - bytes[index] = static_cast(parts[index]); - } - return 1; +int* APS5_VABI sceNetErrnoLoc(void) { + return errno_slot(); } int APS5_VABI sceNetInit_nid_postfix(void) { + std::lock_guard lk(g_mutex); + g_net_inited = true; return 0; } -int APS5_VABI sceNetListen(int s, int backlog) { - (void)s; - (void)backlog; - return Fail(GUEST_EBADF); -} - -uint32_t APS5_VABI sceNetNtohl_nid_postfix(uint32_t net32) { - return __builtin_bswap32(net32); -} - -uint16_t APS5_VABI sceNetNtohs_nid_postfix(uint16_t net16) { - return __builtin_bswap16(net16); +int APS5_VABI sceNetTerm(void) { + std::lock_guard lk(g_mutex); + g_net_inited = false; + return 0; } int APS5_VABI sceNetPoolCreate(const char* name, int size, int flags) { (void)name; (void)flags; - if (size <= 0) return Fail(GUEST_EINVAL); - return g_nextId.fetch_add(1, std::memory_order_relaxed); + if (size <= 0) { + return fail(NET_EINVAL); + } + std::lock_guard lk(g_mutex); + const int id = g_next_pool++; + g_pools.insert(id); + return id; } int APS5_VABI sceNetPoolDestroy(int memid) { - (void)memid; - return 0; -} - -int APS5_VABI sceNetResolverCreate(const char* name, int memid, int flags) { - (void)name; - (void)memid; - (void)flags; - return g_nextId.fetch_add(1, std::memory_order_relaxed); -} - -int APS5_VABI sceNetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout, int retry, int flags) { - (void)rid; - (void)hostname; - (void)addr; - (void)timeout; - (void)retry; - (void)flags; - return Fail(GUEST_EHOSTUNREACH); -} - -int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval, uint32_t optlen) { - (void)s; - (void)level; - (void)optname; - (void)optval; - (void)optlen; - return Fail(GUEST_EBADF); -} - -int APS5_VABI sceNetShutdown(int s, int how) { - (void)s; - (void)how; - return Fail(GUEST_EBADF); + std::lock_guard lk(g_mutex); + return g_pools.erase(memid) != 0 ? 0 : fail(NET_EBADF); } int APS5_VABI sceNetSocket(const char* name, int family, int type, int protocol) { (void)name; - (void)family; - (void)type; (void)protocol; - return Fail(GUEST_ENETDOWN); + if (family != NET_AF_INET && family != NET_AF_INET6) { + return fail(NET_EAFNOSUPPORT); + } + if (type != NET_SOCK_STREAM && type != NET_SOCK_DGRAM && type != NET_SOCK_RAW) { + return fail(NET_EPROTONOSUPPORT); + } + std::lock_guard lk(g_mutex); + const int fd = g_next_sock++; + Sock s; + s.family = family; + s.type = type; + g_socks[fd] = s; + return fd; } int APS5_VABI sceNetSocketClose(int s) { - (void)s; - return Fail(GUEST_EBADF); -} - -int APS5_VABI sceNetConnect(int s, const void* addr, uint32_t addrlen) { - (void)s; - (void)addr; - (void)addrlen; - return Fail(GUEST_EBADF); -} - -int APS5_VABI sceNetEpollAbort(int eid, int flags) { - (void)flags; - auto epoll = FindEpoll(eid); - if (!epoll) return Fail(GUEST_EBADF); - AbortEpoll(*epoll); + std::lock_guard lk(g_mutex); + if (g_socks.erase(s) == 0) { + return fail(NET_EBADF); + } + g_cv.notify_all(); return 0; } -int* APS5_VABI sceNetErrnoLoc(void) { - return &g_netErrno; +int APS5_VABI sceNetSocketAbort(int s, int flags) { + (void)flags; + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + it->second.aborted = true; + g_cv.notify_all(); + return 0; } -int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint32_t* optlen) { - (void)s; - (void)level; - (void)optname; - (void)optval; - (void)optlen; - return Fail(GUEST_EBADF); +int APS5_VABI sceNetBind_nid_postfix(int s, const void* addr, uint32_t addrlen) { + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + if (addr == nullptr || addrlen < 8) { + return fail(NET_EINVAL); + } + if (it->second.bound) { + return fail(NET_EINVAL); + } + // sockaddr_in: len, family, port (BE), addr (BE) + std::uint16_t port = 0; + std::uint32_t ip = 0; + std::memcpy(&port, static_cast(addr) + 2, 2); + std::memcpy(&ip, static_cast(addr) + 4, 4); + if (port == 0) { + port = swap16(g_next_port++); + } else { + for (const auto& kv : g_socks) { + if (kv.first != s && kv.second.bound && kv.second.port == port && kv.second.type == it->second.type) { + return fail(NET_EADDRINUSE); + } + } + } + it->second.bound = true; + it->second.port = port; + it->second.addr = ip; + return 0; } -int APS5_VABI sceNetRecv(int s, void* buf, size_t len, int flags) { - (void)s; +int APS5_VABI sceNetListen(int s, int backlog) { + (void)backlog; + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + if (it->second.type != NET_SOCK_STREAM) { + return fail(NET_EOPNOTSUPP); + } + if (!it->second.bound) { + it->second.bound = true; + it->second.port = swap16(g_next_port++); + } + it->second.listening = true; + return 0; +} + +int APS5_VABI sceNetAccept(int s, void* addr, uint32_t* addrlen) { + (void)addr; + (void)addrlen; + std::unique_lock lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + if (!it->second.listening) { + return fail(NET_EINVAL); + } + if (it->second.nonblock) { + return fail(NET_EAGAIN); + } + // No peer can ever arrive: wait for close/abort like an idle listener. + const int rc = block_on(lk, s, it->second.rcv_timeout_us); + return rc; +} + +int APS5_VABI sceNetConnect(int s, const void* addr, uint32_t addrlen) { + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + if (addr == nullptr || addrlen < 8) { + return fail(NET_EINVAL); + } + std::uint32_t ip = 0; + std::memcpy(&ip, static_cast(addr) + 4, 4); + if ((swap32(ip) >> 24) == 127) { + return fail(NET_ECONNREFUSED); // loopback with no listener + } + return fail(NET_ENETUNREACH); +} + +static int recv_common(int s) { + std::unique_lock lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + if (it->second.type == NET_SOCK_STREAM) { + return fail(NET_ENOTCONN); + } + if (it->second.nonblock) { + return fail(NET_EAGAIN); + } + return block_on(lk, s, it->second.rcv_timeout_us); +} + +int64_t APS5_VABI sceNetRecv(int s, void* buf, size_t len, int flags) { (void)buf; (void)len; (void)flags; - return Fail(GUEST_EBADF); + return recv_common(s); } -int APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* from, uint32_t* fromlen) { - (void)s; +int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* from, uint32_t* fromlen) { (void)buf; (void)len; (void)flags; (void)from; (void)fromlen; - return Fail(GUEST_EBADF); + return recv_common(s); } -int APS5_VABI sceNetResolverDestroy(int rid) { - (void)rid; +static int send_common(int s) { + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + return fail(it->second.type == NET_SOCK_STREAM ? NET_ENOTCONN : NET_ENETUNREACH); +} + +int64_t APS5_VABI sceNetSend(int s, const void* buf, size_t len, int flags) { + (void)buf; + (void)len; + (void)flags; + return send_common(s); +} + +int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, const void* to, uint32_t tolen) { + (void)buf; + (void)len; + (void)flags; + (void)to; + (void)tolen; + return send_common(s); +} + +int APS5_VABI sceNetShutdown(int s, int how) { + (void)how; + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + return it->second.type == NET_SOCK_STREAM ? fail(NET_ENOTCONN) : 0; +} + +int APS5_VABI sceNetSetsockopt(int s, int level, int optname, const void* optval, uint32_t optlen) { + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + if (level == NET_SOL_SOCKET && optval != nullptr && optlen >= sizeof(int)) { + int v = 0; + std::memcpy(&v, optval, sizeof(v)); + switch (optname) { + case NET_SO_NBIO: it->second.nonblock = v != 0; break; + case NET_SO_RCVTIMEO: it->second.rcv_timeout_us = v; break; + case NET_SO_SNDTIMEO: it->second.snd_timeout_us = v; break; + default: break; + } + } return 0; } +int APS5_VABI sceNetGetsockopt(int s, int level, int optname, void* optval, uint32_t* optlen) { + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + if (optval == nullptr || optlen == nullptr || *optlen < sizeof(int)) { + return fail(NET_EINVAL); + } + int v = 0; + if (level == NET_SOL_SOCKET) { + switch (optname) { + case NET_SO_NBIO: v = it->second.nonblock ? 1 : 0; break; + case NET_SO_RCVTIMEO: v = it->second.rcv_timeout_us; break; + case NET_SO_SNDTIMEO: v = it->second.snd_timeout_us; break; + default: break; + } + } + std::memcpy(optval, &v, sizeof(v)); + *optlen = sizeof(v); + return 0; +} + +int APS5_VABI sceNetGetsockname(int s, void* addr, uint32_t* addrlen) { + std::lock_guard lk(g_mutex); + auto it = g_socks.find(s); + if (it == g_socks.end()) { + return fail(NET_EBADF); + } + if (addr == nullptr || addrlen == nullptr) { + return fail(NET_EINVAL); + } + std::uint8_t sa[16] = {}; + sa[0] = 16; + sa[1] = static_cast(NET_AF_INET); + std::memcpy(sa + 2, &it->second.port, 2); + std::memcpy(sa + 4, &it->second.addr, 4); + const std::uint32_t n = *addrlen < sizeof(sa) ? *addrlen : static_cast(sizeof(sa)); + std::memcpy(addr, sa, n); + *addrlen = sizeof(sa); + return 0; +} + +int APS5_VABI sceNetGetSockInfo(int s, void* info, int n, int flags) { + (void)info; + (void)n; + (void)flags; + std::lock_guard lk(g_mutex); + return g_socks.count(s) != 0 ? 0 : fail(NET_EBADF); +} + +// Epoll: nothing can become ready without a network, so a wait sleeps for its timeout (microseconds; a +// negative infinite timeout is serviced in bounded slices) and reports zero events. +int APS5_VABI sceNetEpollCreate(const char* name, int flags) { + (void)name; + (void)flags; + std::lock_guard lk(g_mutex); + const int id = g_next_epoll++; + g_epolls.insert(id); + return id; +} + +int APS5_VABI sceNetEpollDestroy(int eid) { + std::lock_guard lk(g_mutex); + if (g_epolls.erase(eid) == 0) { + return fail(NET_EBADF); + } + g_cv.notify_all(); + return 0; +} + +int APS5_VABI sceNetEpollControl(int eid, int op, int id, const NetEpollEvent* event) { + (void)event; + std::lock_guard lk(g_mutex); + if (g_epolls.count(eid) == 0) { + return fail(NET_EBADF); + } + if (g_socks.count(id) == 0) { + return fail(NET_EBADF); + } + if (op < 1 || op > 3) { + return fail(NET_EINVAL); + } + return 0; +} + +int APS5_VABI sceNetEpollWait(int eid, NetEpollEvent* events, int maxevents, int timeout) { + (void)events; + if (maxevents <= 0) { + return fail(NET_EINVAL); + } + std::unique_lock lk(g_mutex); + if (g_epolls.count(eid) == 0) { + return fail(NET_EBADF); + } + if (timeout == 0) { + return 0; + } + // timeout < 0 nominally waits until the epoll object is destroyed; bound each wait slice so an + // infinite-timeout waiter on the inert stack still returns periodically (0 events) instead of + // deadlocking if nobody ever destroys the epoll. + constexpr int INFINITE_WAIT_SLICE_US = 100000; // 100 ms + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::microseconds(timeout > 0 ? timeout : INFINITE_WAIT_SLICE_US); + for (;;) { + if (g_cv.wait_until(lk, deadline) == std::cv_status::timeout) { + return 0; + } + if (g_epolls.count(eid) == 0) { + return fail(NET_EBADF); + } + } +} + +// Byte order and text conversion. +uint32_t APS5_VABI sceNetHtonl_nid_postfix(uint32_t host32) { return swap32(host32); } +uint16_t APS5_VABI sceNetHtons_nid_postfix(uint16_t host16) { return swap16(host16); } +uint32_t APS5_VABI sceNetNtohl_nid_postfix(uint32_t net32) { return swap32(net32); } +uint16_t APS5_VABI sceNetNtohs_nid_postfix(uint16_t net16) { return swap16(net16); } + +int APS5_VABI sceNetInetPton(int af, const char* src, void* dst) { + if (src == nullptr || dst == nullptr) { + return fail(NET_EINVAL); + } + if (af != NET_AF_INET) { + return fail(NET_EAFNOSUPPORT); + } + unsigned a = 0, b = 0, c = 0, d = 0; + char tail = 0; + if (std::sscanf(src, "%3u.%3u.%3u.%3u%c", &a, &b, &c, &d, &tail) != 4 || a > 255 || b > 255 || c > 255 || d > 255) { + return 0; + } + const std::uint8_t out[4] = {static_cast(a), static_cast(b), static_cast(c), static_cast(d)}; + std::memcpy(dst, out, 4); + return 1; +} + +const char* APS5_VABI sceNetInetNtop(int af, const void* src, char* dst, uint32_t size) { + if (src == nullptr || dst == nullptr || af != NET_AF_INET) { + *errno_slot() = af != NET_AF_INET ? NET_EAFNOSUPPORT : NET_EINVAL; + return nullptr; + } + const auto* p = static_cast(src); + char tmp[16]; + const int n = std::snprintf(tmp, sizeof(tmp), "%u.%u.%u.%u", p[0], p[1], p[2], p[3]); + if (n < 0 || static_cast(n) >= size) { + *errno_slot() = 28; // ENOSPC + return nullptr; + } + std::memcpy(dst, tmp, static_cast(n) + 1); + return dst; +} + +int APS5_VABI sceNetEtherNtostr(const NetEtherAddr* n, char* str, size_t len) { + if (n == nullptr || str == nullptr || len < 18) { + return fail(NET_EINVAL); + } + std::snprintf(str, len, "%02x:%02x:%02x:%02x:%02x:%02x", n->data[0], n->data[1], n->data[2], n->data[3], n->data[4], n->data[5]); + return 0; +} + +int APS5_VABI sceNetGetMacAddress(NetEtherAddr* addr, int flags) { + (void)flags; + if (addr == nullptr) { + return fail(NET_EINVAL); + } + // Locally administered address; there is no adapter to read one from. + const std::uint8_t mac[6] = {0x02, 0x50, 0x53, 0x35, 0x00, 0x01}; + std::memcpy(addr->data, mac, 6); + return 0; +} + +// Resolver: there is no DNS server to ask. +int APS5_VABI sceNetResolverCreate(const char* name, int memid, int flags) { + (void)name; + (void)memid; + (void)flags; + std::lock_guard lk(g_mutex); + const int id = g_next_resolver++; + g_resolvers.insert(id); + return id; +} + +int APS5_VABI sceNetResolverDestroy(int rid) { + std::lock_guard lk(g_mutex); + return g_resolvers.erase(rid) != 0 ? 0 : fail(NET_EBADF); +} + +int APS5_VABI sceNetResolverStartNtoa(int rid, const char* hostname, void* addr, int timeout, int retry, int flags) { + (void)hostname; + (void)addr; + (void)timeout; + (void)retry; + (void)flags; + std::lock_guard lk(g_mutex); + if (g_resolvers.count(rid) == 0) { + return fail(NET_EBADF); + } + log_soft(__func__, "ENODNS (no network)"); + *errno_slot() = NET_ETIMEDOUT; + return NET_ERROR_RESOLVER_ENODNS; +} + int APS5_VABI sceNetResolverStartAton(int rid, const void* addr, char* hostname, int len, int timeout, int retry, int flags) { - (void)rid; (void)addr; (void)hostname; (void)len; (void)timeout; (void)retry; (void)flags; - return Fail(GUEST_EHOSTUNREACH); + std::lock_guard lk(g_mutex); + if (g_resolvers.count(rid) == 0) { + return fail(NET_EBADF); + } + log_soft(__func__, "ENODNS (no network)"); + *errno_slot() = NET_ETIMEDOUT; + return NET_ERROR_RESOLVER_ENODNS; } - -int APS5_VABI sceNetSend(int s, const void* msg, size_t len, int flags) { - (void)s; - (void)msg; - (void)len; - (void)flags; - return Fail(GUEST_EBADF); -} - -int APS5_VABI sceNetSendto(int s, const void* msg, size_t len, int flags, const void* to, uint32_t tolen) { - (void)s; - (void)msg; - (void)len; - (void)flags; - (void)to; - (void)tolen; - return Fail(GUEST_EBADF); -} - -int APS5_VABI sceNetSocketAbort(int s, int flags) { - (void)s; - (void)flags; - return Fail(GUEST_EBADF); -} - } diff --git a/core/libs/prx/libSceRudp/Export.cpp b/core/libs/prx/libSceRudp/Export.cpp index a53683063..bb5821fd9 100644 --- a/core/libs/prx/libSceRudp/Export.cpp +++ b/core/libs/prx/libSceRudp/Export.cpp @@ -1,29 +1,173 @@ +#include #include +#include #include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +// Offline reliable-UDP: the library initialises and contexts can be created and bound, but no connection can +// ever be established because there is no network. + +namespace { +constexpr int RUDP_ERROR_NOT_INITIALIZED = static_cast(0x80770001u); +constexpr int RUDP_ERROR_ALREADY_INITIALIZED = static_cast(0x80770002u); +constexpr int RUDP_ERROR_INVALID_CONTEXT_ID = static_cast(0x80770003u); +constexpr int RUDP_ERROR_INVALID_ARGUMENT = static_cast(0x80770004u); +constexpr int RUDP_ERROR_CONN_RESET = static_cast(0x80770009u); +constexpr int RUDP_ERROR_CONN_REFUSED = static_cast(0x8077000Au); + +using GuestRudpEventHandler = void(APS5_VABI*)(int, int, int, void*); + +std::mutex g_mutex; +bool g_inited = false; +int g_next_ctx = 0; +std::map g_ctx; // id -> bound +GuestRudpEventHandler g_handler = nullptr; +void* g_handler_arg = nullptr; + +bool valid_ctx(int ctx) { + return g_ctx.count(ctx) != 0; +} +} // namespace + extern "C" { -int APS5_VABI sceRudpEnableInternalIOThread(uint32_t stack_size, uint32_t priority) { - (void)stack_size; - (void)priority; - NotImplemented_nid_no_patch(__func__); - return 0; -} - int APS5_VABI sceRudpInit_nid_postfix(void* mem_pool, int mem_pool_size) { - (void)mem_pool; - (void)mem_pool_size; - NotImplemented_nid_no_patch(__func__); - return 0; + (void)mem_pool; + (void)mem_pool_size; + std::lock_guard lk(g_mutex); + if (g_inited) { + return RUDP_ERROR_ALREADY_INITIALIZED; + } + g_inited = true; + return 0; } -int APS5_VABI sceRudpSetEventHandler(RudpEventHandler handler, void* arg) { - (void)handler; - (void)arg; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceRudpEnd(int ctx_id) { + std::lock_guard lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + return g_ctx.erase(ctx_id) != 0 ? 0 : RUDP_ERROR_INVALID_CONTEXT_ID; } +int APS5_VABI sceRudpTerminate(void) { + std::lock_guard lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + g_inited = false; + g_ctx.clear(); + return 0; +} + +int APS5_VABI sceRudpEnableInternalIOThread(uint32_t stack_size, uint32_t priority) { + (void)stack_size; + (void)priority; + std::lock_guard lk(g_mutex); + return g_inited ? 0 : RUDP_ERROR_NOT_INITIALIZED; +} + +int APS5_VABI sceRudpSetEventHandler(GuestRudpEventHandler handler, void* arg) { + std::lock_guard lk(g_mutex); + g_handler = handler; + g_handler_arg = arg; + return 0; +} + +int APS5_VABI sceRudpCreateContext(GuestRudpEventHandler handler, void* arg, int* ctx_id) { + (void)handler; + (void)arg; + if (ctx_id == nullptr) { + return RUDP_ERROR_INVALID_ARGUMENT; + } + std::lock_guard lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + const int id = g_next_ctx++; + g_ctx[id] = false; + *ctx_id = id; + return 0; +} + +int APS5_VABI sceRudpBind(int ctx_id, uint16_t vport, int transport, uint16_t port) { + (void)vport; + (void)transport; + (void)port; + std::lock_guard lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + if (!valid_ctx(ctx_id)) { + return RUDP_ERROR_INVALID_CONTEXT_ID; + } + g_ctx[ctx_id] = true; + return 0; +} + +int APS5_VABI sceRudpSetOption(int ctx_id, int option, const void* value, uint32_t len) { + (void)option; + (void)value; + (void)len; + std::lock_guard lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + return valid_ctx(ctx_id) ? 0 : RUDP_ERROR_INVALID_CONTEXT_ID; +} + +int APS5_VABI sceRudpInitiate(int ctx_id, const void* to, uint32_t tolen, uint16_t vport) { + (void)to; + (void)tolen; + (void)vport; + std::lock_guard lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + if (!valid_ctx(ctx_id)) { + return RUDP_ERROR_INVALID_CONTEXT_ID; + } + return RUDP_ERROR_CONN_REFUSED; +} + +int APS5_VABI sceRudpGetContextStatus(int ctx_id, void* status, uint32_t size) { + std::lock_guard lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + if (!valid_ctx(ctx_id)) { + return RUDP_ERROR_INVALID_CONTEXT_ID; + } + if (status != nullptr && size != 0) { + std::memset(status, 0, size); // state 0 = idle, nothing pending + } + return 0; +} + +int APS5_VABI sceRudpWrite(int ctx_id, const void* data, uint32_t len, int flags) { + (void)data; + (void)len; + (void)flags; + std::lock_guard lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + return valid_ctx(ctx_id) ? RUDP_ERROR_CONN_RESET : RUDP_ERROR_INVALID_CONTEXT_ID; +} + +int APS5_VABI sceRudpRead(int ctx_id, void* data, uint32_t len, int flags, void* status) { + (void)data; + (void)len; + (void)flags; + (void)status; + std::lock_guard lk(g_mutex); + if (!g_inited) { + return RUDP_ERROR_NOT_INITIALIZED; + } + return valid_ctx(ctx_id) ? RUDP_ERROR_CONN_RESET : RUDP_ERROR_INVALID_CONTEXT_ID; +} } diff --git a/core/libs/prx/libSceSaveData.native/Export.cpp b/core/libs/prx/libSceSaveData.native/Export.cpp index ce6f82d14..6e1a7d4cc 100644 --- a/core/libs/prx/libSceSaveData.native/Export.cpp +++ b/core/libs/prx/libSceSaveData.native/Export.cpp @@ -24,24 +24,11 @@ static std::string save_root() { return std::string(SAVE_DIR); } -// Events the title polls with sceSaveDataGetEventResult (a backup completes at once here), and the -// "save data memory" blobs (small per-slot memories the title reads and writes without mounting), -// persisted as files under the save root so they survive restarts. +// Events the title polls with sceSaveDataGetEventResult; a backup completes at once here. static std::mutex g_state_mutex; static std::deque g_events; -static std::map> g_memories; constexpr std::uint32_t SAVE_DATA_EVENT_TYPE_BACKUP = 2; -static std::string memory_path(std::uint32_t slot) { - return save_root() + "/_memory_" + std::to_string(slot) + ".bin"; -} - -static void store_memory(std::uint32_t slot, const std::vector& memory) { - std::filesystem::create_directories(save_root()); - std::ofstream file(memory_path(slot), std::ios::binary | std::ios::trunc); - file.write(reinterpret_cast(memory.data()), static_cast(memory.size())); -} - static bool dir_name_match(const char* str, const char* pattern) { if (pattern == nullptr || pattern[0] == '\0') { return true; @@ -72,6 +59,78 @@ static bool dir_name_match(const char* str, const char* pattern) { return *str == '\0' && *pattern == '\0'; } +namespace { + +// Save-data memory: one blob per user and slot, kept next to the save directory (_sd_mem/u/slot.bin), +// with a small .param sidecar holding the last SaveDataParam the title wrote. Keying by user as well as slot +// keeps two users' in-memory saves from colliding on the same console. +constexpr char MEM_DIR[] = "_sd_mem"; +constexpr std::size_t MEM_MAX_SIZE = 0x1000000; // 16 MiB +std::mutex g_mem_mutex; + +std::string mem_path(std::int32_t user_id, std::uint32_t slot, const char* ext) { + char buf[96]; + std::snprintf(buf, sizeof(buf), "%s/u%d/slot%u.%s", MEM_DIR, static_cast(user_id), static_cast(slot), ext); + return buf; +} + +bool file_size_of(const std::string& path, std::size_t* out) { + std::error_code ec; + if (!std::filesystem::is_regular_file(path, ec)) { + return false; + } + const auto sz = std::filesystem::file_size(path, ec); + if (ec) { + return false; + } + *out = static_cast(sz); + return true; +} + +// Atomic-ish write: write to a temp file then rename over the target, so a kill mid-write never +// leaves a torn save behind. +bool write_file_replace(const std::string& path, const std::vector& data) { + const std::string tmp = path + ".tmp"; + { + std::ofstream f(tmp, std::ios::binary | std::ios::trunc); + if (!f) { + return false; + } + if (!data.empty()) { + f.write(data.data(), static_cast(data.size())); + } + f.flush(); + if (!f) { + return false; + } + } + std::error_code ec; + std::filesystem::rename(tmp, path, ec); + if (ec) { + std::filesystem::remove(path, ec); + ec.clear(); + std::filesystem::rename(tmp, path, ec); + } + return !ec; +} + +bool read_file_all(const std::string& path, std::vector& out) { + std::ifstream f(path, std::ios::binary); + if (!f) { + return false; + } + f.seekg(0, std::ios::end); + const auto n = static_cast(f.tellg()); + f.seekg(0); + out.resize(n); + if (n != 0) { + f.read(out.data(), static_cast(n)); + } + return static_cast(f); +} + +} // namespace + extern "C" { int APS5_VABI sceSaveDataBackup(const SaveDataBackup* backup) { @@ -196,29 +255,47 @@ int APS5_VABI sceSaveDataGetParam(const SaveDataMountPoint* mount_point, uint32_ int APS5_VABI sceSaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) { if (get_param == nullptr) { - throw std::runtime_error("sceSaveDataGetSaveDataMemory2: null argument"); + return SAVE_DATA_ERROR_PARAMETER; } - std::lock_guard lock(g_state_mutex); - const auto it = g_memories.find(get_param->slot_id); - if (it == g_memories.end()) { - return SAVE_DATA_ERROR_NOT_FOUND; + if (!g_initialized) { + return SAVE_DATA_ERROR_NOT_INITIALIZED; } - if (get_param->data != nullptr && get_param->data->buf != nullptr) { - const auto& memory = it->second; - if (get_param->data->offset > memory.size()) { + std::lock_guard lk(g_mem_mutex); + const std::string path = mem_path(get_param->user_id, get_param->slot_id, "bin"); + std::size_t size = 0; + if (!file_size_of(path, &size)) { + return SAVE_DATA_ERROR_MEMORY_NOT_READY; + } + const SaveDataMemoryData* d = get_param->data; + if (d != nullptr && d->buf_size != 0) { + if (d->buf == nullptr || d->offset > size || d->buf_size > size - d->offset) { return SAVE_DATA_ERROR_PARAMETER; } - const auto bytes = std::min(get_param->data->buf_size, memory.size() - get_param->data->offset); - std::memcpy(get_param->data->buf, memory.data() + get_param->data->offset, bytes); + std::ifstream f(path, std::ios::binary); + if (!f) { + return SAVE_DATA_ERROR_INTERNAL; + } + f.seekg(static_cast(d->offset)); + f.read(static_cast(d->buf), static_cast(d->buf_size)); + if (!f) { + return SAVE_DATA_ERROR_INTERNAL; + } + } + if (get_param->param != nullptr) { + std::memset(get_param->param, 0, sizeof(SaveDataParam)); + std::vector pd; + if (read_file_all(mem_path(get_param->user_id, get_param->slot_id, "param"), pd)) { + std::memcpy(get_param->param, pd.data(), std::min(pd.size(), sizeof(SaveDataParam))); + } + } + if (get_param->icon != nullptr) { + get_param->icon->data_size = 0; } - if (get_param->param != nullptr) std::memset(get_param->param, 0, sizeof(SaveDataParam)); - if (get_param->icon != nullptr) get_param->icon->data_size = 0; return SAVE_DATA_OK; } int APS5_VABI sceSaveDataInitialize3(const void* init) { (void)init; - // NotImplemented_nid_no_patch(__func__); if (g_initialized) { return SAVE_DATA_ERROR_ALREADY_INITIALIZED; } @@ -332,59 +409,108 @@ int APS5_VABI sceSaveDataSetParam(const SaveDataMountPoint* mount_point, uint32_ int APS5_VABI sceSaveDataSetSaveDataMemory2(const SaveDataMemorySet2* set_param) { if (set_param == nullptr) { - throw std::runtime_error("sceSaveDataSetSaveDataMemory2: null argument"); + return SAVE_DATA_ERROR_PARAMETER; } - std::lock_guard lock(g_state_mutex); - const auto it = g_memories.find(set_param->slot_id); - if (it == g_memories.end()) { - return SAVE_DATA_ERROR_NOT_FOUND; + if (!g_initialized) { + return SAVE_DATA_ERROR_NOT_INITIALIZED; } - auto& memory = it->second; - if (set_param->data != nullptr) { - const auto count = set_param->data_num == 0 ? 1u : set_param->data_num; - for (std::uint32_t i = 0; i < count; ++i) { - const auto& data = set_param->data[i]; - if (data.buf == nullptr) continue; - if (data.offset > memory.size() || data.buf_size > memory.size() - data.offset) { - return SAVE_DATA_ERROR_PARAMETER; - } - std::memcpy(memory.data() + data.offset, data.buf, data.buf_size); + std::lock_guard lk(g_mem_mutex); + const std::string path = mem_path(set_param->user_id, set_param->slot_id, "bin"); + std::size_t size = 0; + if (!file_size_of(path, &size)) { + return SAVE_DATA_ERROR_MEMORY_NOT_READY; + } + // Validate every range first so a bad entry never leaves a partial write. + const std::uint32_t n = set_param->data != nullptr ? (set_param->data_num != 0 ? set_param->data_num : 1u) : 0u; + for (std::uint32_t i = 0; i < n; i++) { + const SaveDataMemoryData& d = set_param->data[i]; + if (d.buf_size == 0) { + continue; + } + if (d.buf == nullptr || d.offset > size || d.buf_size > size - d.offset) { + return SAVE_DATA_ERROR_PARAMETER; } } - store_memory(set_param->slot_id, memory); + if (n != 0) { + std::fstream f(path, std::ios::binary | std::ios::in | std::ios::out); + if (!f) { + return SAVE_DATA_ERROR_INTERNAL; + } + for (std::uint32_t i = 0; i < n; i++) { + const SaveDataMemoryData& d = set_param->data[i]; + if (d.buf_size == 0) { + continue; + } + f.seekp(static_cast(d.offset)); + f.write(static_cast(d.buf), static_cast(d.buf_size)); + } + f.flush(); + if (!f) { + return SAVE_DATA_ERROR_INTERNAL; + } + } + if (set_param->param != nullptr) { + std::vector pd(sizeof(SaveDataParam)); + std::memcpy(pd.data(), set_param->param, sizeof(SaveDataParam)); + write_file_replace(mem_path(set_param->user_id, set_param->slot_id, "param"), pd); + } return SAVE_DATA_OK; } int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup_param, SaveDataMemorySetupResult* result) { if (setup_param == nullptr) { - throw std::runtime_error("sceSaveDataSetupSaveDataMemory2: null argument"); + return SAVE_DATA_ERROR_PARAMETER; } - std::lock_guard lock(g_state_mutex); - auto& memory = g_memories[setup_param->slot_id]; + if (!g_initialized) { + return SAVE_DATA_ERROR_NOT_INITIALIZED; + } + if (setup_param->memory_size == 0 || setup_param->memory_size > MEM_MAX_SIZE) { + return SAVE_DATA_ERROR_PARAMETER; + } + std::lock_guard lk(g_mem_mutex); + const std::string path = mem_path(setup_param->user_id, setup_param->slot_id, "bin"); std::size_t existed = 0; - if (memory.empty()) { - std::ifstream file(memory_path(setup_param->slot_id), std::ios::binary); - if (file) { - memory.assign(std::istreambuf_iterator(file), std::istreambuf_iterator()); - existed = memory.size(); + const bool have = file_size_of(path, &existed); + if (!have) { + existed = 0; + } + // First run: create a zero-filled blob and report existed size 0 so the title treats it as a new save. + if (!have || existed < setup_param->memory_size) { + std::error_code ec; + std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec); + std::vector data; + if (have) { + read_file_all(path, data); + } + data.resize(setup_param->memory_size, 0); + if (!write_file_replace(path, data)) { + return SAVE_DATA_ERROR_INTERNAL; + } + if (setup_param->init_param != nullptr && (setup_param->option & 1u) != 0) { + std::vector pd(sizeof(SaveDataParam)); + std::memcpy(pd.data(), setup_param->init_param, sizeof(SaveDataParam)); + write_file_replace(mem_path(setup_param->user_id, setup_param->slot_id, "param"), pd); } - } else { - existed = memory.size(); } - if (memory.size() < setup_param->memory_size) { - memory.resize(setup_param->memory_size, 0); - } - if (existed == 0) store_memory(setup_param->slot_id, memory); if (result != nullptr) { std::memset(result, 0, sizeof(*result)); - result->existed_memory_size = existed; + result->existed_memory_size = have ? existed : 0; } return SAVE_DATA_OK; } int APS5_VABI sceSaveDataSyncSaveDataMemory(const void* sync_param) { - (void)sync_param; - // Every Set already reached the file; nothing is buffered. + if (sync_param == nullptr) { + return SAVE_DATA_ERROR_PARAMETER; + } + // Every Set already writes straight through to disk; sync only has to confirm the memory exists. + const std::int32_t user_id = *static_cast(sync_param); + const std::uint32_t slot_id = reinterpret_cast(sync_param)[1]; + std::lock_guard lk(g_mem_mutex); + std::size_t size = 0; + if (!file_size_of(mem_path(user_id, slot_id, "bin"), &size)) { + return SAVE_DATA_ERROR_MEMORY_NOT_READY; + } return SAVE_DATA_OK; } @@ -401,9 +527,11 @@ int APS5_VABI sceSaveDataTerminate(void) { int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* mount, SaveDataMountResult* mount_result) { (void)mount; - (void)mount_result; - NotImplemented_nid_no_patch(__func__); - return 0; + if (mount_result != nullptr) { + std::memset(mount_result, 0, sizeof(*mount_result)); + } + // No PS4-to-PS5 transfer data exists on this console. + return SAVE_DATA_ERROR_NOT_FOUND; } int APS5_VABI sceSaveDataUmount2(uint32_t mode, const SaveDataMountPoint* mount_point) { diff --git a/core/libs/prx/libSceSaveData.native/SaveData.hpp b/core/libs/prx/libSceSaveData.native/SaveData.hpp index dd69aca6c..95cf838c1 100644 --- a/core/libs/prx/libSceSaveData.native/SaveData.hpp +++ b/core/libs/prx/libSceSaveData.native/SaveData.hpp @@ -6,15 +6,18 @@ #include constexpr int SAVE_DATA_OK = 0; +// Values follow the SDK error table (0x809F0000 + code): 2 OUT_OF_MEMORY, 3 BUSY, 4 NOT_MOUNTED, 7 EXISTS, 8 NOT_FOUND, 0xB INTERNAL, 0xC MOUNT_FULL. constexpr int SAVE_DATA_ERROR_PARAMETER = -2137063424; constexpr int SAVE_DATA_ERROR_NOT_INITIALIZED = -2137063423; -constexpr int SAVE_DATA_ERROR_ALREADY_INITIALIZED = -2137063422; -constexpr int SAVE_DATA_ERROR_OUT_OF_MEMORY = -2137063421; -constexpr int SAVE_DATA_ERROR_BUSY = -2137063420; -constexpr int SAVE_DATA_ERROR_NOT_MOUNTED = -2137063419; -constexpr int SAVE_DATA_ERROR_MOUNT_FULL = -2137063418; -constexpr int SAVE_DATA_ERROR_EXISTS = -2137063414; -constexpr int SAVE_DATA_ERROR_NOT_FOUND = -2137063413; +constexpr int SAVE_DATA_ERROR_ALREADY_INITIALIZED = -2137063419; // 0x809F0005 +constexpr int SAVE_DATA_ERROR_OUT_OF_MEMORY = -2137063422; +constexpr int SAVE_DATA_ERROR_BUSY = -2137063421; +constexpr int SAVE_DATA_ERROR_NOT_MOUNTED = -2137063420; +constexpr int SAVE_DATA_ERROR_MOUNT_FULL = -2137063412; +constexpr int SAVE_DATA_ERROR_EXISTS = -2137063417; +constexpr int SAVE_DATA_ERROR_NOT_FOUND = -2137063416; +constexpr int SAVE_DATA_ERROR_INTERNAL = -2137063413; // 0x809F000B +constexpr int SAVE_DATA_ERROR_MEMORY_NOT_READY = -2137063406; // 0x809F0012 constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_RDONLY = 1; constexpr std::uint32_t SAVE_DATA_MOUNT_MODE_RDWR = 2; diff --git a/core/libs/prx/libSceSaveData/Export.cpp b/core/libs/prx/libSceSaveData/Export.cpp index 0bc709e26..0d44da8d1 100644 --- a/core/libs/prx/libSceSaveData/Export.cpp +++ b/core/libs/prx/libSceSaveData/Export.cpp @@ -1,53 +1,94 @@ +#include #include +#include #include #include "SceTypes.hpp" +#include "prx/libSceSaveData/SaveDataCore.hpp" #include "prx/libc/include/General.hpp" +// Savedata imports bind by NID either to libSceSaveData.native or (via the relinker alias table, +// by name) to this library. Every entry point used to return SoftFailNp (0x800242e2), which is the +// NP-family "online service not available" code - wrong dialect for a purely local save call and +// fatal-looking for callers. All entry points now delegate to SaveDataCore.hpp: a local, +// file-backed implementation that needs no PSN account and tolerates any userId +// (0 / 65535 / 268435456), mirroring the native library. + +namespace { +// Logs the first three calls of each entry point, then stays silent, keeping guest logs readable. +#define SAVEDATA_TRACE(...) \ + do { \ + static std::atomic traceCount{0}; \ + if (traceCount.fetch_add(1, std::memory_order_relaxed) < 3) { \ + std::fprintf(stderr, "[SAVEDATA:core] " __VA_ARGS__); \ + std::fputc('\n', stderr); \ + std::fflush(stderr); \ + } \ + } while (0) +} // namespace + extern "C" { +int APS5_VABI sceSaveDataInitialize3(const void* init) { + const int rc = savedata::initialize_internal(init); + SAVEDATA_TRACE("sceSaveDataInitialize3 -> 0x%08x", static_cast(rc)); + return rc; +} +int APS5_VABI sceSaveDataTerminate(void) { + const int rc = savedata::terminate_internal(); + SAVEDATA_TRACE("sceSaveDataTerminate -> 0x%08x", static_cast(rc)); + return rc; +} int APS5_VABI sceSaveDataBackup(const SaveDataBackup* backup) { - (void)backup; - NotImplemented_nid_no_patch(__func__); - return 0; + const int rc = savedata::backup_internal(backup, nullptr); + SAVEDATA_TRACE("sceSaveDataBackup -> 0x%08x", static_cast(rc)); + return rc; } - int APS5_VABI sceSaveDataGetEventResult(const void* event_param, SaveDataEvent* event) { - (void)event_param; - (void)event; - NotImplemented_nid_no_patch(__func__); - return 0; + const int rc = savedata::get_event_result_internal(event_param, event); + SAVEDATA_TRACE("sceSaveDataGetEventResult -> 0x%08x", static_cast(rc)); + return rc; } - int APS5_VABI sceSaveDataGetSaveDataMemory2(SaveDataMemoryGet2* get_param) { - (void)get_param; - NotImplemented_nid_no_patch(__func__); - return 0; + if (get_param == nullptr) { + return savedata::SD_ERROR_PARAMETER; + } + const int rc = savedata::get_memory_internal(get_param); + SAVEDATA_TRACE("get2 user=%d slot=%u -> 0x%08x", static_cast(get_param->user_id), + static_cast(get_param->slot_id), static_cast(rc)); + return rc; } - int APS5_VABI sceSaveDataSetSaveDataMemory2(const SaveDataMemorySet2* set_param) { - (void)set_param; - NotImplemented_nid_no_patch(__func__); - return 0; + if (set_param == nullptr) { + return savedata::SD_ERROR_PARAMETER; + } + const int rc = savedata::set_memory_internal(set_param); + SAVEDATA_TRACE("set2 user=%d slot=%u num=%u -> 0x%08x", + static_cast(set_param->user_id), static_cast(set_param->slot_id), + static_cast(set_param->data_num), static_cast(rc)); + return rc; } - -int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup_param, SaveDataMemorySetupResult* result) { - (void)setup_param; - (void)result; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSaveDataSetupSaveDataMemory2(const SaveDataMemorySetup2* setup_param, + SaveDataMemorySetupResult* result) { + const int rc = savedata::setup_memory_internal(setup_param, result, nullptr); + SAVEDATA_TRACE("setup2 -> 0x%08x existed=0x%zx", static_cast(rc), + result != nullptr ? result->existed_memory_size : static_cast(0)); + return rc; } - int APS5_VABI sceSaveDataSyncSaveDataMemory(const void* sync_param) { - (void)sync_param; - NotImplemented_nid_no_patch(__func__); - return 0; + if (sync_param == nullptr) { + return savedata::SD_ERROR_PARAMETER; + } + const auto* sync = static_cast(sync_param); + const int rc = savedata::sync_memory_internal(sync->user_id, sync->slot_id, sync->option, nullptr, true); + SAVEDATA_TRACE("sync user=%d slot=%u option=%u -> 0x%08x", static_cast(sync->user_id), + static_cast(sync->slot_id), static_cast(sync->option), + static_cast(rc)); + return rc; } - -int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* mount, SaveDataMountResult* mount_result) { - (void)mount; - (void)mount_result; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceSaveDataTransferringMount(const SaveDataTransferringMount* mount, + SaveDataMountResult* mount_result) { + const int rc = savedata::transferring_mount_internal(mount, mount_result); + SAVEDATA_TRACE("sceSaveDataTransferringMount -> 0x%08x", static_cast(rc)); + return rc; } - } diff --git a/core/libs/prx/libSceSaveData/SaveDataCore.hpp b/core/libs/prx/libSceSaveData/SaveDataCore.hpp new file mode 100644 index 000000000..e72aa4c77 --- /dev/null +++ b/core/libs/prx/libSceSaveData/SaveDataCore.hpp @@ -0,0 +1,1355 @@ +#ifndef CORE_LIBS_PRX_LIBSCESAVEDATA_SAVEDATACORE_HPP +#define CORE_LIBS_PRX_LIBSCESAVEDATA_SAVEDATACORE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +namespace savedata { + +extern "C" { +int APS5_VABI sceKernelCreateEventFlag(KernelEventFlag* ef, const char* name, std::uint32_t attr, + std::uint64_t init_pattern, const void* param); +int APS5_VABI sceKernelDeleteEventFlag(KernelEventFlag ef); +int APS5_VABI sceKernelSetEventFlag(KernelEventFlag ef, std::uint64_t bit_pattern); +int APS5_VABI sceKernelCancelEventFlag(KernelEventFlag ef, std::uint64_t set_pattern, int* num_wait_threads); +} + +namespace { + +constexpr std::uint32_t SD_FACILITY = 0x809F0000u; +constexpr int sd_error(std::uint32_t code) { return static_cast(SD_FACILITY + code); } + +constexpr int SD_OK = 0; +constexpr int SD_ERROR_PARAMETER = sd_error(0x00); +constexpr int SD_ERROR_NOT_INITIALIZED = sd_error(0x01); +constexpr int SD_ERROR_OUT_OF_MEMORY = sd_error(0x02); +constexpr int SD_ERROR_BUSY = sd_error(0x03); +constexpr int SD_ERROR_NOT_MOUNTED = sd_error(0x04); +constexpr int SD_ERROR_NO_PERMISSION = sd_error(0x05); +constexpr int SD_ERROR_FINGERPRINT_MISMATCH = sd_error(0x06); +constexpr int SD_ERROR_EXISTS = sd_error(0x07); +constexpr int SD_ERROR_NOT_FOUND = sd_error(0x08); +constexpr int SD_ERROR_NO_SPACE_FS = sd_error(0x0A); +constexpr int SD_ERROR_INTERNAL = sd_error(0x0B); +constexpr int SD_ERROR_MOUNT_FULL = sd_error(0x0C); +constexpr int SD_ERROR_BAD_MOUNTED = sd_error(0x0D); +constexpr int SD_ERROR_FILE_NOT_FOUND = sd_error(0x0E); +constexpr int SD_ERROR_BROKEN = sd_error(0x0F); +constexpr int SD_ERROR_INVALID_LOGIN_USER = sd_error(0x11); +constexpr int SD_ERROR_MEMORY_NOT_READY = sd_error(0x12); +constexpr int SD_ERROR_BACKUP_BUSY = sd_error(0x13); +constexpr int SD_ERROR_NOT_REGIST_CALLBACK = sd_error(0x15); +constexpr int SD_ERROR_BUSY_FOR_SAVING = sd_error(0x16); +constexpr int SD_ERROR_LIMITATION_OVER = sd_error(0x17); +constexpr int SD_ERROR_EVENT_BUSY = sd_error(0x18); + +constexpr std::uint32_t SD_MOUNT_RDONLY = 1u; +constexpr std::uint32_t SD_MOUNT_RDWR = 2u; +constexpr std::uint32_t SD_MOUNT_CREATE = 4u; +constexpr std::uint32_t SD_MOUNT_COPY_ICON = 16u; +constexpr std::uint32_t SD_MOUNT_CREATE2 = 32u; + +constexpr std::uint32_t SD_UMOUNT_BACKUP_ASYNC = 1u << 16u; +constexpr std::uint32_t SD_COMMIT_BACKUP_ASYNC = 1u; + +constexpr std::uint32_t SD_EVENT_UMOUNT_BACKUP_END = 1u; +constexpr std::uint32_t SD_EVENT_BACKUP_END = 2u; +constexpr std::uint32_t SD_EVENT_MEMORY_SYNC_END = 3u; +constexpr std::uint32_t SD_EVENT_COMMIT_BACKUP_END = 4u; + +constexpr std::uint64_t SD_BLOCKS_MIN = 1; +constexpr std::uint64_t SD_BLOCKS_MAX = 32768; + +constexpr std::uint32_t SD_MEMORY_SLOTS = 4; +constexpr std::uint32_t SD_MEMORY_MAX_MOUNTED = 4; +constexpr std::size_t SD_MEMORY_MAX_SIZE = 32u * 1024u * 1024u; +constexpr std::uint32_t SD_MEMORY_SET_PARAM = 1u; +constexpr std::uint32_t SD_MEMORY_DOUBLE_BUFFER = 2u; +constexpr std::uint32_t SD_MEMORY_MAX_DATA_NUM = 5; + +constexpr std::uint32_t SD_PARAM_WHOLE = 0; +constexpr std::uint32_t SD_PARAM_TITLE = 1; +constexpr std::uint32_t SD_PARAM_SUB_TITLE = 2; +constexpr std::uint32_t SD_PARAM_DETAIL = 3; +constexpr std::uint32_t SD_PARAM_USER_PARAM = 4; +constexpr std::uint32_t SD_PARAM_MTIME = 5; + +constexpr std::uint32_t SD_APP_STATUS_NONE = 0; +constexpr std::uint32_t SD_APP_STATUS_DELETED = 1; +constexpr std::uint32_t SD_APP_STATUS_LOCAL = 2; +constexpr std::uint32_t SD_APP_STATUS_ONLINE = 4; +constexpr std::uint32_t SD_APP_STATUS_ANY = 0x7FFFFFFFu; + +constexpr char SD_ROOT_GUEST[] = "/_sd"; +constexpr char SD_SUBDIR[] = "sce_sys"; +constexpr char SD_MEMORY_PREFIX[] = "sce_sdmemory"; +constexpr std::size_t SD_MOUNT_PREFIX_LEN = sizeof(SD_ROOT_GUEST); +constexpr std::size_t SD_MOUNT_POINT_CAPACITY = sizeof(SaveDataMountPoint) - SD_MOUNT_PREFIX_LEN - 1; + +struct MemoryData { + void* buf; + std::size_t buf_size; + std::int64_t offset; + std::uint8_t reserved[40]; +}; +static_assert(sizeof(MemoryData) == 64, "SceSaveDataMemoryData is 64 bytes"); + +struct MemorySync { + std::int32_t user_id; + std::uint32_t slot_id; + std::uint32_t option; + std::uint8_t reserved[28]; +}; +static_assert(sizeof(MemorySync) == 40, "SceSaveDataMemorySync is 40 bytes"); + +struct EventParam { + std::size_t size; + std::uint8_t reserved[24]; +}; + +struct MountSlot { + bool used = false; + std::string mount_point; + std::string dir_name; + std::filesystem::path directory; +}; + +struct MemorySlot { + std::vector data; + SaveDataParam param {}; + std::uint32_t option = 0; + bool dirty = false; +}; + +struct PendingEvent { + SaveDataEvent record {}; + KernelEventFlag flag = nullptr; +}; + +struct State { + std::mutex mutex; + std::uint32_t instances = 0; + std::array slots {}; + std::deque events; + std::unordered_map memory; + std::map aliases; + std::int32_t next_transaction_resource = 1; + std::set live_transaction_resources; + void* event_callback = nullptr; + void* event_callback_userdata = nullptr; + bool aliases_loaded = false; +}; + +State& state() { + static State instance; + return instance; +} + +bool is_ready() { + return state().instances != 0; +} + +bool valid_dir_name(const char* name) { + if (name == nullptr || name[0] == '\0') { + return false; + } + std::size_t length = 0; + while (name[length] != '\0') { + length++; + if (length >= sizeof(SceSaveDataDirName::data)) { + return false; + } + } + if (std::strcmp(name, ".") == 0 || std::strcmp(name, "..") == 0) { + return false; + } + for (const char* p = name; *p != '\0'; p++) { + const bool ok = std::isalnum(static_cast(*p)) != 0 || *p == '_' || *p == '-' || + *p == '.' || *p == '@'; + if (!ok) { + return false; + } + } + return true; +} + +bool bounded_string_ok(const char* data, std::size_t capacity) { + return std::memchr(data, '\0', capacity) != nullptr; +} + +const std::filesystem::path& save_root() { + static const std::filesystem::path root = ResolvePath_nid_no_patch(SD_ROOT_GUEST); + return root; +} + +bool ensure_save_root(std::error_code& error) { + std::filesystem::create_directories(save_root(), error); + if (error) { + error.clear(); + } + return std::filesystem::is_directory(save_root(), error); +} + +void load_aliases_locked(State& st) { + if (st.aliases_loaded) { + return; + } + st.aliases_loaded = true; + std::ifstream file(save_root() / "_aliases", std::ios::binary); + std::string line; + while (std::getline(file, line)) { + const std::size_t sep = line.find('\t'); + if (sep == std::string::npos || sep == 0) { + continue; + } + st.aliases[line.substr(0, sep)] = line.substr(sep + 1); + } +} + +void store_aliases_locked(State& st) { + std::error_code error; + ensure_save_root(error); + std::ofstream file(save_root() / "_aliases", std::ios::binary | std::ios::trunc); + for (const auto& [alias, dir_name] : st.aliases) { + file << alias << '\t' << dir_name << '\n'; + } +} + +std::string save_alias_locked(State& st, const std::string& dir_name) { + if (dir_name.size() <= SD_MOUNT_POINT_CAPACITY) { + return dir_name; + } + load_aliases_locked(st); + const std::string base = dir_name.substr(0, 6); + std::uint32_t hash = 2166136261u; + for (const char c : dir_name) { + hash = (hash ^ static_cast(c)) * 16777619u; + } + for (std::uint32_t attempt = 0; attempt < 4096u; attempt++) { + char tail[8]; + std::snprintf(tail, sizeof(tail), "~%03X", static_cast((hash + attempt) & 0xFFFu)); + const std::string candidate = base + tail; + const auto found = st.aliases.find(candidate); + if (found == st.aliases.end()) { + st.aliases[candidate] = dir_name; + store_aliases_locked(st); + return candidate; + } + if (found->second == dir_name) { + return candidate; + } + } + return base; +} + +std::string real_dir_name_locked(State& st, const std::string& alias) { + load_aliases_locked(st); + const auto found = st.aliases.find(alias); + return found != st.aliases.end() ? found->second : alias; +} + +std::string mount_point_for(const std::string& alias) { + return std::string(SD_ROOT_GUEST) + "/" + alias; +} + +std::filesystem::path save_directory(const std::string& alias) { + return save_root() / alias; +} + +std::filesystem::path save_metadata_directory(const std::filesystem::path& directory) { + return directory / SD_SUBDIR; +} + +std::filesystem::path save_param_path(const std::filesystem::path& directory) { + return save_metadata_directory(directory) / "param.bin"; +} + +std::filesystem::path save_blocks_path(const std::filesystem::path& directory) { + return save_metadata_directory(directory) / "blocks.bin"; +} + +bool write_blob(const std::filesystem::path& path, const void* data, std::size_t size) { + std::error_code error; + std::filesystem::create_directories(path.parent_path(), error); + auto temporary = path; + temporary += ".tmp"; + { + std::ofstream file(temporary, std::ios::binary | std::ios::trunc); + if (!file) { + return false; + } + if (size != 0) { + file.write(static_cast(data), static_cast(size)); + } + file.flush(); + if (!file) { + return false; + } + } + error.clear(); + std::filesystem::rename(temporary, path, error); + if (error) { + std::error_code ignored; + std::filesystem::remove(path, ignored); + error.clear(); + std::filesystem::rename(temporary, path, error); + } + std::error_code leftovers; + std::filesystem::remove(temporary, leftovers); + return !error; +} + +bool read_blob(const std::filesystem::path& path, void* data, std::size_t size) { + std::ifstream file(path, std::ios::binary); + if (!file) { + return false; + } + file.read(static_cast(data), static_cast(size)); + return static_cast(file.gcount()) == size; +} + +int write_u64(const std::filesystem::path& path, std::uint64_t value) { + return write_blob(path, &value, sizeof(value)) ? SD_OK : SD_ERROR_INTERNAL; +} + +int read_u64(const std::filesystem::path& path, std::uint64_t* value) { + std::error_code error; + if (!std::filesystem::is_regular_file(path, error) || error) { + return SD_ERROR_BROKEN; + } + if (std::filesystem::file_size(path, error) != sizeof(std::uint64_t) || error) { + return SD_ERROR_BROKEN; + } + return read_blob(path, value, sizeof(*value)) ? SD_OK : SD_ERROR_BROKEN; +} + +int load_save_param(const std::filesystem::path& directory, SaveDataParam* param) { + std::memset(param, 0, sizeof(*param)); + const int status = read_blob(save_param_path(directory), param, sizeof(*param)) ? SD_OK : SD_ERROR_NOT_FOUND; + if (status == SD_OK) { + std::int64_t newest = 0; + std::error_code error; + for (std::filesystem::recursive_directory_iterator it(directory, + std::filesystem::directory_options::skip_permission_denied, error), end; + it != end; it.increment(error)) { + if (error) { + error.clear(); + continue; + } + std::error_code file_error; + if (it->is_regular_file(file_error) && !file_error) { + const auto written = std::filesystem::last_write_time(it->path(), file_error); + if (!file_error) { + const auto seconds = std::chrono::duration_cast( + std::filesystem::file_time_type::clock::now() - written).count(); + const auto now = static_cast(std::time(nullptr)); + newest = std::max(newest, now - static_cast(seconds)); + } + } + } + param->mtime = newest; + } + return status; +} + +bool save_is_mounted_locked(const State& st, const std::filesystem::path& directory) { + for (const auto& slot : st.slots) { + if (slot.used && slot.directory == directory) { + return true; + } + } + return false; +} + +int slot_of_mount_point_locked(const State& st, const char* mount_point) { + for (std::size_t i = 0; i < st.slots.size(); i++) { + if (st.slots[i].used && st.slots[i].mount_point == mount_point) { + return static_cast(i); + } + } + return -1; +} + +int free_slot_locked(const State& st) { + for (std::size_t i = 0; i < st.slots.size(); i++) { + if (!st.slots[i].used) { + return static_cast(i); + } + } + return -1; +} + +void queue_event_locked(State& st, std::uint32_t type, std::int32_t user_id, + const SceSaveDataTitleId* title_id, const SceSaveDataDirName* dir_name, + int error_code, KernelEventFlag flag) { + PendingEvent pending; + pending.record = {}; + pending.record.type = type; + pending.record.error_code = error_code; + pending.record.user_id = user_id; + if (title_id != nullptr) { + std::memcpy(&pending.record.title_id, title_id, sizeof(pending.record.title_id)); + } + if (dir_name != nullptr) { + std::memcpy(&pending.record.dir_name, dir_name, sizeof(pending.record.dir_name)); + } + pending.flag = flag; + st.events.push_back(pending); + while (st.events.size() > 64) { + if (st.events.front().flag != nullptr) { + sceKernelDeleteEventFlag(st.events.front().flag); + } + st.events.pop_front(); + } +} + +KernelEventFlag create_signalled_flag(const char* name) { + KernelEventFlag flag = nullptr; + if (sceKernelCreateEventFlag(&flag, name, 0x20u, 0, nullptr) != 0) { + return nullptr; + } + sceKernelSetEventFlag(flag, 1u); + return flag; +} + +int initialize_internal(const void* init) { + State& st = state(); + std::lock_guard lock(st.mutex); + st.instances++; + return SD_OK; +} + +int terminate_internal() { + State& st = state(); + std::lock_guard lock(st.mutex); + if (st.instances == 0) { + return SD_ERROR_NOT_INITIALIZED; + } + if (std::any_of(st.slots.begin(), st.slots.end(), + [](const MountSlot& slot) { return slot.used; }) || !st.memory.empty()) { + return SD_ERROR_BUSY; + } + st.instances--; + if (st.instances == 0) { + for (auto& pending : st.events) { + if (pending.flag != nullptr) { + sceKernelDeleteEventFlag(pending.flag); + } + } + st.events.clear(); + st.memory.clear(); + st.live_transaction_resources.clear(); + } + return SD_OK; +} + +int mount_internal(const SceSaveDataDirName* dir_name, std::uint32_t mount_mode, + std::uint64_t blocks, std::int32_t user_id, SaveDataMountResult* mount_result, + const char* api) { + if (mount_result == nullptr) { + return SD_ERROR_PARAMETER; + } + std::memset(mount_result, 0, sizeof(*mount_result)); + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (dir_name == nullptr || !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || + !valid_dir_name(dir_name->data)) { + return SD_ERROR_PARAMETER; + } + if (user_id < 0) { + return SD_ERROR_INVALID_LOGIN_USER; + } + const bool create = (mount_mode & SD_MOUNT_CREATE) != 0; + const bool create_or_open = (mount_mode & SD_MOUNT_CREATE2) != 0; + const bool open = !create && !create_or_open && (mount_mode & (SD_MOUNT_RDONLY | SD_MOUNT_RDWR)) != 0; + if (!create && !create_or_open && !open) { + return SD_ERROR_PARAMETER; + } + + State& st = state(); + std::lock_guard lock(st.mutex); + const std::string requested = dir_name->data; + const std::string alias = save_alias_locked(st, requested); + const std::string mount_point = mount_point_for(alias); + if (mount_point.size() + 1 > sizeof(mount_result->mount_point)) { + return SD_ERROR_PARAMETER; + } + const std::filesystem::path directory = save_directory(alias); + for (const auto& slot : st.slots) { + if (slot.used && slot.directory == directory) { + return SD_ERROR_BUSY; + } + } + const int free_index = free_slot_locked(st); + if (free_index == -1) { + return SD_ERROR_MOUNT_FULL; + } + + std::error_code error; + const bool exists = std::filesystem::is_directory(directory, error) && !error; + if (open && !exists) { + return SD_ERROR_NOT_FOUND; + } + if (create && exists) { + return SD_ERROR_EXISTS; + } + bool created = false; + if ((create || create_or_open) && !exists) { + if (blocks < SD_BLOCKS_MIN || blocks > SD_BLOCKS_MAX) { + return SD_ERROR_PARAMETER; + } + std::error_code root_error; + if (!ensure_save_root(root_error)) { + return SD_ERROR_INTERNAL; + } + if (!std::filesystem::create_directories(directory, error) || error) { + return SD_ERROR_INTERNAL; + } + created = true; + if (write_u64(save_blocks_path(directory), blocks) != SD_OK) { + std::error_code cleanup; + std::filesystem::remove_all(directory, cleanup); + return SD_ERROR_INTERNAL; + } + SaveDataParam initial {}; + std::strncpy(initial.title, "Saved Data", sizeof(initial.title) - 1); + if (!write_blob(save_param_path(directory), &initial, sizeof(initial))) { + std::error_code cleanup; + std::filesystem::remove_all(directory, cleanup); + return SD_ERROR_INTERNAL; + } + } + if (exists && !created) { + std::uint64_t recorded = 0; + if (read_u64(save_blocks_path(directory), &recorded) != SD_OK && blocks != 0) { + if (blocks < SD_BLOCKS_MIN || blocks > SD_BLOCKS_MAX) { + return SD_ERROR_PARAMETER; + } + write_u64(save_blocks_path(directory), blocks); + } + } + + MountSlot& slot = st.slots[static_cast(free_index)]; + slot.used = true; + slot.mount_point = mount_point; + slot.dir_name = requested; + slot.directory = directory; + std::memcpy(mount_result->mount_point.data, mount_point.c_str(), mount_point.size() + 1); + mount_result->required_blocks = 0; + mount_result->mount_status = created ? 1u : 0u; + return SD_OK; +} + +int umount_internal(std::uint32_t mode, const SaveDataMountPoint* mount_point, const char* api) { + State& st = state(); + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (mount_point == nullptr || !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { + return SD_ERROR_PARAMETER; + } + std::lock_guard lock(st.mutex); + const int index = slot_of_mount_point_locked(st, mount_point->data); + if (index == -1) { + return SD_ERROR_NOT_MOUNTED; + } + st.slots[static_cast(index)] = MountSlot {}; + if ((mode & SD_UMOUNT_BACKUP_ASYNC) != 0) { + queue_event_locked(st, SD_EVENT_UMOUNT_BACKUP_END, 0, nullptr, nullptr, SD_OK, nullptr); + } + return SD_OK; +} + +int delete_internal(std::int32_t user_id, const SceSaveDataDirName* dir_name, + const SceSaveDataTitleId* title_id) { + State& st = state(); + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (dir_name == nullptr || !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || + !valid_dir_name(dir_name->data) || + (title_id != nullptr && !bounded_string_ok(title_id->data, sizeof(title_id->data)))) { + return SD_ERROR_PARAMETER; + } + if (user_id < 0) { + return SD_ERROR_INVALID_LOGIN_USER; + } + std::lock_guard lock(st.mutex); + const std::string alias = save_alias_locked(st, dir_name->data); + const std::filesystem::path directory = save_directory(alias); + if (save_is_mounted_locked(st, directory)) { + return SD_ERROR_BUSY; + } + std::error_code error; + if (!std::filesystem::exists(directory, error) || error) { + return SD_ERROR_NOT_FOUND; + } + std::filesystem::remove_all(directory, error); + if (error) { + return SD_ERROR_INTERNAL; + } + st.aliases.erase(alias); + if (alias != dir_name->data) { + store_aliases_locked(st); + } + return SD_OK; +} + +bool dir_name_matches(const char* str, const char* pattern) { + if (pattern == nullptr || pattern[0] == '\0') { + return true; + } + while (*str != '\0' && *pattern != '\0') { + if (*pattern == '%') { + for (const char* s = str;; s++) { + if (dir_name_matches(s, pattern + 1)) { + return true; + } + if (*s == '\0') { + break; + } + } + return false; + } + if (*pattern == '_') { + str++; + pattern++; + continue; + } + if (std::tolower(static_cast(*str)) != + std::tolower(static_cast(*pattern))) { + return false; + } + str++; + pattern++; + } + return *str == '\0' && *pattern == '\0'; +} + +int dir_name_search_internal(const SaveDataDirNameSearchCond* cond, + SaveDataDirNameSearchResult* result) { + State& st = state(); + if (result != nullptr) { + std::memset(result, 0, sizeof(*result)); + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (cond == nullptr || result == nullptr || + (result->dir_names_num != 0 && result->dir_names == nullptr) || + (cond->dir_name != nullptr && !bounded_string_ok(cond->dir_name->data, sizeof(cond->dir_name->data))) || + (cond->title_id != nullptr && !bounded_string_ok(cond->title_id->data, sizeof(cond->title_id->data)))) { + return SD_ERROR_PARAMETER; + } + if (cond->user_id < 0) { + return SD_ERROR_INVALID_LOGIN_USER; + } + std::lock_guard lock(st.mutex); + load_aliases_locked(st); + std::vector found; + std::error_code error; + if (std::filesystem::is_directory(save_root(), error) && !error) { + for (const auto& entry : std::filesystem::directory_iterator(save_root(), error)) { + if (error) { + break; + } + std::error_code kind_error; + if (!entry.is_directory(kind_error) || kind_error) { + continue; + } + const std::string alias = entry.path().filename().string(); + if (alias.empty() || alias[0] == '.' || alias[0] == '_' || + !valid_dir_name(alias.c_str())) { + continue; + } + std::error_code meta_error; + if (!std::filesystem::is_regular_file(save_param_path(entry.path()), meta_error) || meta_error) { + continue; + } + const std::string dir_name = real_dir_name_locked(st, alias); + if (cond->dir_name != nullptr && !dir_name_matches(dir_name.c_str(), cond->dir_name->data)) { + continue; + } + found.push_back(dir_name); + } + } + std::sort(found.begin(), found.end()); + if (cond->order == 1) { + std::reverse(found.begin(), found.end()); + } + result->hit_num = static_cast(found.size()); + const std::uint32_t set_count = std::min(result->dir_names_num, found.size()); + for (std::uint32_t i = 0; i < set_count; i++) { + std::snprintf(result->dir_names[i].data, sizeof(result->dir_names[i].data), "%s", + found[i].c_str()); + if (result->params != nullptr) { + load_save_param(save_directory(save_alias_locked(st, found[i])), &result->params[i]); + } + if (result->infos != nullptr) { + std::memset(&result->infos[i], 0, sizeof(result->infos[i])); + std::uint64_t blocks = 0; + if (read_u64(save_blocks_path(save_directory(save_alias_locked(st, found[i]))), &blocks) == SD_OK) { + std::memcpy(&result->infos[i], &blocks, sizeof(blocks)); + } + } + } + result->set_num = set_count; + return SD_OK; +} + +int get_param_internal(const SaveDataMountPoint* mount_point, std::uint32_t param_type, + void* param_buf, std::size_t param_buf_size, std::size_t* got_size) { + State& st = state(); + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (mount_point == nullptr || param_buf == nullptr || param_type > SD_PARAM_MTIME || + !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { + return SD_ERROR_PARAMETER; + } + std::lock_guard lock(st.mutex); + const int index = slot_of_mount_point_locked(st, mount_point->data); + if (index == -1) { + return SD_ERROR_NOT_MOUNTED; + } + SaveDataParam param {}; + const int status = load_save_param(st.slots[static_cast(index)].directory, ¶m); + if (status != SD_OK) { + return status; + } + std::size_t written = 0; + if (param_type == SD_PARAM_WHOLE) { + if (param_buf_size < sizeof(param)) { + return SD_ERROR_PARAMETER; + } + std::memcpy(param_buf, ¶m, sizeof(param)); + written = sizeof(param); + } else if (param_type == SD_PARAM_USER_PARAM || param_type == SD_PARAM_MTIME) { + const std::size_t size = param_type == SD_PARAM_USER_PARAM ? sizeof(param.user_param) + : sizeof(param.mtime); + if (param_buf_size < size) { + return SD_ERROR_PARAMETER; + } + std::memcpy(param_buf, + param_type == SD_PARAM_USER_PARAM + ? static_cast(¶m.user_param) + : static_cast(¶m.mtime), + size); + written = size; + } else { + char* field = param_type == SD_PARAM_TITLE ? param.title + : param_type == SD_PARAM_SUB_TITLE ? param.sub_title : param.detail; + const std::size_t capacity = param_type == SD_PARAM_DETAIL ? sizeof(param.detail) + : sizeof(param.title); + if (param_buf_size == 0) { + return SD_ERROR_PARAMETER; + } + const std::size_t length = std::min(std::strlen(field), param_buf_size - 1); + std::memcpy(param_buf, field, length); + static_cast(param_buf)[length] = '\0'; + written = length + 1; + } + if (got_size != nullptr) { + *got_size = written; + } + return SD_OK; +} + +int set_param_internal(const SaveDataMountPoint* mount_point, std::uint32_t param_type, + const void* param_buf, std::size_t param_buf_size) { + State& st = state(); + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (mount_point == nullptr || param_buf == nullptr || param_type > SD_PARAM_MTIME || + !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { + return SD_ERROR_PARAMETER; + } + std::lock_guard lock(st.mutex); + const int index = slot_of_mount_point_locked(st, mount_point->data); + if (index == -1) { + return SD_ERROR_NOT_MOUNTED; + } + const std::filesystem::path directory = st.slots[static_cast(index)].directory; + if (param_type == SD_PARAM_WHOLE) { + if (param_buf_size < sizeof(SaveDataParam)) { + return SD_ERROR_PARAMETER; + } + SaveDataParam param = *static_cast(param_buf); + param.mtime = static_cast(std::time(nullptr)); + return write_blob(save_param_path(directory), ¶m, sizeof(param)) ? SD_OK : SD_ERROR_INTERNAL; + } + SaveDataParam param {}; + const int status = load_save_param(directory, ¶m); + if (status != SD_OK && status != SD_ERROR_NOT_FOUND) { + return status; + } + if (param_type == SD_PARAM_USER_PARAM || param_type == SD_PARAM_MTIME) { + const std::size_t size = param_type == SD_PARAM_USER_PARAM ? sizeof(param.user_param) + : sizeof(param.mtime); + if (param_buf_size < size) { + return SD_ERROR_PARAMETER; + } + std::memcpy(param_type == SD_PARAM_USER_PARAM ? static_cast(¶m.user_param) + : static_cast(¶m.mtime), + param_buf, size); + } else { + char* field = param_type == SD_PARAM_TITLE ? param.title + : param_type == SD_PARAM_SUB_TITLE ? param.sub_title : param.detail; + const std::size_t capacity = param_type == SD_PARAM_DETAIL ? sizeof(param.detail) + : sizeof(param.title); + if (param_buf_size == 0) { + return SD_ERROR_PARAMETER; + } + const std::size_t length = std::min(param_buf_size, capacity - 1); + const char* text = static_cast(param_buf); + const char* end = static_cast(std::memchr(text, '\0', length)); + std::memset(field, 0, capacity); + std::memcpy(field, text, end != nullptr ? static_cast(end - text) : length); + } + return write_blob(save_param_path(directory), ¶m, sizeof(param)) ? SD_OK : SD_ERROR_INTERNAL; +} + +int get_mount_info_internal(const SaveDataMountPoint* mount_point, SaveDataMountInfo* info) { + State& st = state(); + if (info != nullptr) { + std::memset(info, 0, sizeof(*info)); + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (mount_point == nullptr || info == nullptr || + !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { + return SD_ERROR_PARAMETER; + } + std::lock_guard lock(st.mutex); + const int index = slot_of_mount_point_locked(st, mount_point->data); + if (index == -1) { + return SD_ERROR_NOT_MOUNTED; + } + std::uint64_t blocks = 0; + const int status = read_u64(save_blocks_path(st.slots[static_cast(index)].directory), + &blocks); + if (status != SD_OK) { + return status; + } + info->blocks = blocks; + info->free_blocks = blocks; + return SD_OK; +} + +int is_mounted_internal(const SaveDataMountPoint* mount_point, std::uint32_t* is_mounted) { + if (mount_point == nullptr || is_mounted == nullptr || + !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { + return SD_ERROR_PARAMETER; + } + State& st = state(); + std::lock_guard lock(st.mutex); + *is_mounted = slot_of_mount_point_locked(st, mount_point->data) != -1 ? 1u : 0u; + return SD_OK; +} + +std::int64_t count_mounted_internal() { + State& st = state(); + std::lock_guard lock(st.mutex); + std::int64_t count = 0; + for (const auto& slot : st.slots) { + if (slot.used) { + count++; + } + } + return count; +} + +std::string memory_key(std::int32_t user_id, std::uint32_t slot_id) { + char key[32]; + std::snprintf(key, sizeof(key), "%d:%u", static_cast(user_id), static_cast(slot_id)); + return key; +} + +std::filesystem::path memory_directory(std::int32_t user_id, std::uint32_t slot_id) { + char name[48]; + std::snprintf(name, sizeof(name), "%s%s", SD_MEMORY_PREFIX, + slot_id == 0 ? "" : std::to_string(slot_id).c_str()); + return save_root() / name / std::to_string(static_cast(user_id)); +} + +bool valid_memory_range(const MemoryData& data, std::size_t size) { + return data.buf != nullptr && data.offset >= 0 && + static_cast(data.offset) <= size && + data.buf_size <= size - static_cast(data.offset); +} + +int setup_memory_internal(const SaveDataMemorySetup2* setup, SaveDataMemorySetupResult* result, + KernelEventFlag flag) { + State& st = state(); + if (setup == nullptr || setup->slot_id >= SD_MEMORY_SLOTS || setup->memory_size == 0 || + setup->memory_size > SD_MEMORY_MAX_SIZE || (setup->option & ~(SD_MEMORY_SET_PARAM | SD_MEMORY_DOUBLE_BUFFER)) != 0 || + (setup->init_param != nullptr && setup->option != 0 && (setup->option & SD_MEMORY_SET_PARAM) == 0)) { + return SD_ERROR_PARAMETER; + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (setup->user_id < 0) { + return SD_ERROR_INVALID_LOGIN_USER; + } + std::lock_guard lock(st.mutex); + const std::string key = memory_key(setup->user_id, setup->slot_id); + if (st.memory.count(key) != 0) { + return SD_ERROR_BUSY; + } + if (st.memory.size() >= SD_MEMORY_MAX_MOUNTED) { + return SD_ERROR_LIMITATION_OVER; + } + if (save_is_mounted_locked(st, memory_directory(setup->user_id, setup->slot_id))) { + return SD_ERROR_BUSY; + } + MemorySlot slot; + slot.option = setup->option; + slot.data.resize(setup->memory_size, 0); + std::strncpy(slot.param.title, "Saved Data", sizeof(slot.param.title) - 1); + std::size_t existed = 0; + std::error_code error; + const std::filesystem::path blob = memory_directory(setup->user_id, setup->slot_id) / "memory.dat"; + if (std::filesystem::is_regular_file(blob, error) && !error) { + const std::size_t stored = static_cast(std::filesystem::file_size(blob, error)); + if (error || stored > SD_MEMORY_MAX_SIZE) { + return SD_ERROR_BROKEN; + } + existed = stored; + std::vector loaded(stored, 0); + std::ifstream file(blob, std::ios::binary); + file.read(loaded.data(), static_cast(stored)); + if (!file) { + return SD_ERROR_BROKEN; + } + std::copy_n(loaded.begin(), std::min(stored, slot.data.size()), slot.data.begin()); + load_save_param(memory_directory(setup->user_id, setup->slot_id), &slot.param); + } + if (existed == 0 && setup->init_param != nullptr && (setup->option & SD_MEMORY_SET_PARAM) != 0) { + slot.param = *setup->init_param; + slot.param.mtime = 0; + } + st.memory.emplace(key, std::move(slot)); + if (result != nullptr) { + std::memset(result, 0, sizeof(*result)); + result->existed_memory_size = existed; + } + if (flag != nullptr) { + queue_event_locked(st, SD_EVENT_MEMORY_SYNC_END, setup->user_id, nullptr, nullptr, SD_OK, flag); + } + return SD_OK; +} + +int get_memory_internal(SaveDataMemoryGet2* get_param) { + State& st = state(); + if (get_param == nullptr || get_param->slot_id >= SD_MEMORY_SLOTS) { + return SD_ERROR_PARAMETER; + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (get_param->user_id < 0) { + return SD_ERROR_INVALID_LOGIN_USER; + } + std::lock_guard lock(st.mutex); + const auto it = st.memory.find(memory_key(get_param->user_id, get_param->slot_id)); + if (it == st.memory.end()) { + return SD_ERROR_MEMORY_NOT_READY; + } + const MemorySlot& slot = it->second; + if (get_param->data != nullptr && + !valid_memory_range(*reinterpret_cast(get_param->data), slot.data.size())) { + return SD_ERROR_PARAMETER; + } + if (get_param->param != nullptr && (slot.option & SD_MEMORY_SET_PARAM) == 0) { + return SD_ERROR_PARAMETER; + } + if (get_param->data != nullptr) { + const MemoryData& data = *reinterpret_cast(get_param->data); + std::memcpy(data.buf, slot.data.data() + data.offset, data.buf_size); + } + if (get_param->param != nullptr) { + *get_param->param = slot.param; + } + if (get_param->icon != nullptr) { + get_param->icon->data_size = 0; + } + return SD_OK; +} + +int set_memory_internal(const SaveDataMemorySet2* set_param) { + State& st = state(); + if (set_param == nullptr || set_param->slot_id >= SD_MEMORY_SLOTS || + set_param->data_num > SD_MEMORY_MAX_DATA_NUM || + (set_param->data == nullptr && set_param->data_num != 0)) { + return SD_ERROR_PARAMETER; + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (set_param->user_id < 0) { + return SD_ERROR_INVALID_LOGIN_USER; + } + std::lock_guard lock(st.mutex); + const auto it = st.memory.find(memory_key(set_param->user_id, set_param->slot_id)); + if (it == st.memory.end()) { + return SD_ERROR_MEMORY_NOT_READY; + } + if (set_param->param != nullptr && (it->second.option & SD_MEMORY_SET_PARAM) == 0) { + return SD_ERROR_PARAMETER; + } + const std::uint32_t count = + set_param->data != nullptr && set_param->data_num == 0 ? 1u : set_param->data_num; + const MemoryData* descriptors = reinterpret_cast(set_param->data); + for (std::uint32_t i = 0; i < count; i++) { + if (!valid_memory_range(descriptors[i], it->second.data.size())) { + return SD_ERROR_PARAMETER; + } + } + MemorySlot updated = it->second; + for (std::uint32_t i = 0; i < count; i++) { + const MemoryData& data = descriptors[i]; + std::memcpy(updated.data.data() + data.offset, data.buf, data.buf_size); + } + if (set_param->param != nullptr) { + updated.param = *set_param->param; + } + updated.param.mtime = static_cast(std::time(nullptr)); + updated.dirty = true; + it->second = std::move(updated); + return SD_OK; +} + +int sync_memory_internal(const std::int32_t user_id, const std::uint32_t slot_id, + const std::uint32_t option, KernelEventFlag flag, bool report_event) { + State& st = state(); + if (slot_id >= SD_MEMORY_SLOTS || option > 1) { + return SD_ERROR_PARAMETER; + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (user_id < 0) { + return SD_ERROR_INVALID_LOGIN_USER; + } + std::lock_guard lock(st.mutex); + const auto it = st.memory.find(memory_key(user_id, slot_id)); + if (it == st.memory.end()) { + return SD_ERROR_MEMORY_NOT_READY; + } + const std::filesystem::path directory = memory_directory(user_id, slot_id); + int status = SD_OK; + if (!write_blob(directory / "memory.dat", it->second.data.data(), it->second.data.size())) { + status = SD_ERROR_INTERNAL; + } else if (!write_blob(save_param_path(directory), &it->second.param, sizeof(it->second.param))) { + status = SD_ERROR_INTERNAL; + } else { + it->second.dirty = false; + } + if (option == 1 || !report_event) { + return status; + } + char title_text[sizeof(SceSaveDataTitleId)] = {}; + char dir_text[sizeof(SceSaveDataDirName)] = {}; + std::snprintf(dir_text, sizeof(dir_text), "%s%s", SD_MEMORY_PREFIX, + slot_id == 0 ? "" : std::to_string(slot_id).c_str()); + SceSaveDataTitleId title {}; + SceSaveDataDirName directory_name {}; + std::memcpy(title.data, title_text, sizeof(title.data)); + std::memcpy(directory_name.data, dir_text, sizeof(directory_name.data)); + queue_event_locked(st, SD_EVENT_MEMORY_SYNC_END, user_id, &title, &directory_name, status, flag); + return SD_OK; +} + +int commit_internal(const SaveDataCommitParam* param) { + State& st = state(); + if (param == nullptr) { + return SD_ERROR_PARAMETER; + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + std::lock_guard lock(st.mutex); + for (auto& [key, slot] : st.memory) { + if (!slot.dirty) { + continue; + } + const std::size_t sep = key.find(':'); + const std::int32_t user_id = static_cast(std::strtol(key.c_str(), nullptr, 10)); + const std::uint32_t slot_id = sep == std::string::npos + ? 0u + : static_cast(std::strtoul(key.c_str() + sep + 1, nullptr, 10)); + const std::filesystem::path directory = memory_directory(user_id, slot_id); + if (!write_blob(directory / "memory.dat", slot.data.data(), slot.data.size()) || + !write_blob(save_param_path(directory), &slot.param, sizeof(slot.param))) { + return SD_ERROR_INTERNAL; + } + slot.dirty = false; + } + if ((param->commit_mode & SD_COMMIT_BACKUP_ASYNC) != 0) { + queue_event_locked(st, SD_EVENT_COMMIT_BACKUP_END, 0, nullptr, nullptr, SD_OK, nullptr); + } + return SD_OK; +} + +int prepare_internal(const SaveDataMountPoint* mount_point, const SaveDataPrepareParam* param) { + if (mount_point == nullptr || param == nullptr || + !bounded_string_ok(mount_point->data, sizeof(mount_point->data))) { + return SD_ERROR_PARAMETER; + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + return SD_OK; +} + +int backup_internal(const SaveDataBackup* backup, KernelEventFlag flag) { + State& st = state(); + if (backup == nullptr || backup->dir_name == nullptr || + !bounded_string_ok(backup->dir_name->data, sizeof(backup->dir_name->data))) { + return SD_ERROR_PARAMETER; + } + if (backup->title_id != nullptr && !bounded_string_ok(backup->title_id->data, sizeof(backup->title_id->data))) { + return SD_ERROR_PARAMETER; + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (backup->user_id < 0) { + return SD_ERROR_INVALID_LOGIN_USER; + } + std::lock_guard lock(st.mutex); + queue_event_locked(st, SD_EVENT_BACKUP_END, backup->user_id, backup->title_id, backup->dir_name, + SD_OK, flag); + return SD_OK; +} + +int transferring_mount_internal(const SaveDataTransferringMount* mount, + SaveDataMountResult* mount_result) { + if (mount_result != nullptr) { + std::memset(mount_result, 0, sizeof(*mount_result)); + } + State& st = state(); + if (mount == nullptr || mount_result == nullptr || mount->title_id == nullptr || + mount->dir_name == nullptr || + !bounded_string_ok(mount->title_id->data, sizeof(mount->title_id->data)) || + !bounded_string_ok(mount->dir_name->data, sizeof(mount->dir_name->data))) { + return SD_ERROR_PARAMETER; + } + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + if (mount->user_id < 0) { + return SD_ERROR_INVALID_LOGIN_USER; + } + if (!valid_dir_name(mount->title_id->data) || !valid_dir_name(mount->dir_name->data)) { + return SD_ERROR_PARAMETER; + } + std::lock_guard lock(st.mutex); + const std::filesystem::path directory = save_root() / "transfer" / mount->title_id->data / + mount->dir_name->data; + std::error_code error; + if (!std::filesystem::is_directory(directory, error) || error) { + return SD_ERROR_NOT_FOUND; + } + const int index = free_slot_locked(st); + if (index == -1) { + return SD_ERROR_MOUNT_FULL; + } + for (const auto& slot : st.slots) { + if (slot.used && slot.directory == directory) { + return SD_ERROR_BUSY; + } + } + const std::string mount_point = mount_point_for(save_alias_locked( + st, std::string(mount->title_id->data) + "-" + mount->dir_name->data)); + if (mount_point.size() + 1 > sizeof(mount_result->mount_point)) { + return SD_ERROR_PARAMETER; + } + MountSlot& slot = st.slots[static_cast(index)]; + slot.used = true; + slot.mount_point = mount_point; + slot.dir_name = mount->dir_name->data; + slot.directory = directory; + std::memcpy(mount_result->mount_point.data, mount_point.c_str(), mount_point.size() + 1); + mount_result->required_blocks = 0; + mount_result->mount_status = 0; + return SD_OK; +} + +int get_event_result_internal(const void* event_param, SaveDataEvent* event) { + (void)event_param; + State& st = state(); + if (event == nullptr) { + return SD_ERROR_PARAMETER; + } + std::lock_guard lock(st.mutex); + if (st.events.empty()) { + return SD_ERROR_NOT_FOUND; + } + const PendingEvent pending = st.events.front(); + st.events.pop_front(); + if (pending.flag != nullptr) { + sceKernelDeleteEventFlag(pending.flag); + } + *event = pending.record; + return event->error_code; +} + +int cancel_internal(KernelEventFlag event) { + State& st = state(); + if (event == nullptr) { + return SD_ERROR_PARAMETER; + } + std::lock_guard lock(st.mutex); + for (auto it = st.events.begin(); it != st.events.end(); ++it) { + if (it->flag == event) { + sceKernelCancelEventFlag(event, 0, nullptr); + const KernelEventFlag flag = it->flag; + st.events.erase(it); + sceKernelDeleteEventFlag(flag); + return SD_OK; + } + } + return SD_ERROR_NOT_FOUND; +} + +int abort_internal() { + State& st = state(); + std::lock_guard lock(st.mutex); + for (auto& pending : st.events) { + if (pending.flag != nullptr) { + sceKernelCancelEventFlag(pending.flag, 0, nullptr); + sceKernelDeleteEventFlag(pending.flag); + pending.flag = nullptr; + } + } + st.events.clear(); + return SD_OK; +} + +int create_transaction_resource_internal(std::uint32_t size) { + State& st = state(); + std::lock_guard lock(st.mutex); + if (st.next_transaction_resource <= 0) { + return SD_ERROR_OUT_OF_MEMORY; + } + const std::int32_t id = st.next_transaction_resource++; + st.live_transaction_resources.insert(id); + return id; +} + +int delete_transaction_resource_internal(std::int32_t resource) { + State& st = state(); + std::lock_guard lock(st.mutex); + return st.live_transaction_resources.erase(resource) != 0 ? SD_OK : SD_ERROR_PARAMETER; +} + +std::filesystem::path app_status_path(const std::string& dir_name) { + return save_root() / "_app_status" / dir_name; +} + +int get_local_storage_app_status_internal(const SceSaveDataDirName* dir_name, + const SceSaveDataTitleId* title_id, + std::uint32_t* status) { + if (status == nullptr || dir_name == nullptr || + !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || + (title_id != nullptr && !bounded_string_ok(title_id->data, sizeof(title_id->data)))) { + return SD_ERROR_PARAMETER; + } + State& st = state(); + std::lock_guard lock(st.mutex); + *status = SD_APP_STATUS_NONE; + std::error_code error; + const std::filesystem::path path = app_status_path(dir_name->data); + if (std::filesystem::is_regular_file(path, error) && !error) { + std::uint32_t stored = 0; + std::ifstream file(path, std::ios::binary); + file.read(reinterpret_cast(&stored), sizeof(stored)); + if (!file) { + return SD_ERROR_BROKEN; + } + *status = stored; + return SD_OK; + } + const std::string alias = save_alias_locked(st, dir_name->data); + if (std::filesystem::is_directory(save_directory(alias), error) && !error) { + *status = SD_APP_STATUS_LOCAL; + return SD_OK; + } + *status = SD_APP_STATUS_NONE; + return SD_OK; +} + +int set_local_storage_app_status_internal(const SceSaveDataDirName* dir_name, + const SceSaveDataTitleId* title_id, + std::uint32_t status) { + if (dir_name == nullptr || !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || + (title_id != nullptr && !bounded_string_ok(title_id->data, sizeof(title_id->data))) || + (status & ~(SD_APP_STATUS_DELETED | SD_APP_STATUS_LOCAL | SD_APP_STATUS_ONLINE)) != 0) { + return SD_ERROR_PARAMETER; + } + std::error_code error; + std::filesystem::create_directories(app_status_path(dir_name->data).parent_path(), error); + if (error) { + return SD_ERROR_INTERNAL; + } + return write_blob(app_status_path(dir_name->data), &status, sizeof(status)) ? SD_OK + : SD_ERROR_INTERNAL; +} + +int delete_local_storage_app_status_internal(const SceSaveDataDirName* dir_name, + const SceSaveDataTitleId* title_id) { + if (dir_name == nullptr || !bounded_string_ok(dir_name->data, sizeof(dir_name->data)) || + (title_id != nullptr && !bounded_string_ok(title_id->data, sizeof(title_id->data)))) { + return SD_ERROR_PARAMETER; + } + std::error_code error; + std::filesystem::remove(app_status_path(dir_name->data), error); + return error ? SD_ERROR_INTERNAL : SD_OK; +} + +int check_internal() { + if (!is_ready()) { + return SD_ERROR_NOT_INITIALIZED; + } + std::error_code error; + return ensure_save_root(error) ? SD_OK : SD_ERROR_INTERNAL; +} + +std::uint64_t get_all_size_internal(std::int32_t user_id) { + State& st = state(); + std::lock_guard lock(st.mutex); + std::uint64_t total = 0; + for (const auto& [key, slot] : st.memory) { + total += slot.data.size() * ((slot.option & SD_MEMORY_DOUBLE_BUFFER) != 0 ? 2u : 1u); + } + return total; +} + +} // namespace +} // namespace savedata + +#endif diff --git a/core/libs/prx/libSceSaveDataDialog/Export.cpp b/core/libs/prx/libSceSaveDataDialog/Export.cpp index 1b0bcb415..068c96f14 100644 --- a/core/libs/prx/libSceSaveDataDialog/Export.cpp +++ b/core/libs/prx/libSceSaveDataDialog/Export.cpp @@ -1,55 +1,67 @@ #include +#include #include +#include #include "SceTypes.hpp" +#include "prx/libSceSaveDataDialog.native/SaveDataDialog.hpp" #include "prx/libc/include/General.hpp" + +namespace { +// Throwing from these reaches std::terminate and aborts the game process. +int SoftFailNp(const char* func) { + std::fprintf(stderr, "[SOFT-NP] %s -> 0 (unimplemented SCE API - no-op)\n", func); + std::fflush(stderr); + return 0; +} +} // namespace + extern "C" { int APS5_VABI sceSaveDataDialogClose(const void* close_param) { (void)close_param; - NotImplemented_nid_no_patch(__func__); - return 0; + return SoftFailNp(__func__); } int APS5_VABI sceSaveDataDialogGetResult(void* result) { - (void)result; - NotImplemented_nid_no_patch(__func__); - return 0; + // Never return 0 with an untouched out-struct: the game would assert on the garbage later. + if (result == nullptr) { + return SAVE_DATA_DIALOG_ERROR_ARG_NULL; + } + auto* r = static_cast(result); + std::memset(r, 0, sizeof(*r)); + r->result = SAVE_DATA_DIALOG_RESULT_OK; + r->button_id = SAVE_DATA_DIALOG_BUTTON_ID_OK; + return SoftFailNp(__func__); } int APS5_VABI sceSaveDataDialogInitialize(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return SoftFailNp(__func__); } int APS5_VABI sceSaveDataDialogIsReadyToDisplay(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return SoftFailNp(__func__); } int APS5_VABI sceSaveDataDialogOpen(const void* param) { (void)param; - NotImplemented_nid_no_patch(__func__); - return 0; + return SoftFailNp(__func__); } int APS5_VABI sceSaveDataDialogProgressBarInc(int target, uint32_t delta) { (void)target; (void)delta; - NotImplemented_nid_no_patch(__func__); - return 0; + return SoftFailNp(__func__); } int APS5_VABI sceSaveDataDialogProgressBarSetValue(int target, uint32_t rate) { (void)target; (void)rate; - NotImplemented_nid_no_patch(__func__); - return 0; + return SoftFailNp(__func__); } int APS5_VABI sceSaveDataDialogTerminate(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return SoftFailNp(__func__); } } diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index e41cf34bd..e039c0529 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(libSceVideoOut SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/src/VideoOutDriver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/DisplayWindow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/PadInput.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/MouseInput.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/InputMapping.cpp ) @@ -14,7 +15,7 @@ target_include_directories(libSceVideoOut PRIVATE ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include ) -target_link_libraries(libSceVideoOut PRIVATE libSceAgcDriver libScePad libc libkernel SDL2-static) +target_link_libraries(libSceVideoOut PRIVATE libSceAgcDriver libScePad libSceMouse libc libkernel SDL2-static) if(WIN32) target_link_libraries(libSceVideoOut PRIVATE comctl32) endif() diff --git a/core/libs/prx/libSceVideoOut/include/MouseInput.hpp b/core/libs/prx/libSceVideoOut/include/MouseInput.hpp new file mode 100644 index 000000000..56ea31b6d --- /dev/null +++ b/core/libs/prx/libSceVideoOut/include/MouseInput.hpp @@ -0,0 +1,14 @@ +#ifndef CORE_LIBS_PRX_LIBSCEVIDEOOUT_MOUSEINPUT_HPP +#define CORE_LIBS_PRX_LIBSCEVIDEOOUT_MOUSEINPUT_HPP + +#include "SDL_events.h" + +class MouseInput { +public: + void HandleEvent(const SDL_Event& event, unsigned windowId); + +private: + bool focused = true; +}; + +#endif diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index 05a53f833..a24efd186 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -17,6 +17,7 @@ #include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp" #include "prx/libSceVideoOut/include/DisplayWindow.hpp" #include "prx/libSceVideoOut/include/BufferReuseTracker.hpp" +#include "prx/libc/include/Shutdown.hpp" #include "SDL.h" #include "SceTypes.hpp" @@ -100,6 +101,7 @@ struct VideoOutConfig { bool opened = false; bool closing = false; std::exception_ptr failure; + std::stop_token shutdownToken = LibcShutdownToken_nid_postfix(); int flipRate = 0; uint64_t lastFlipVblank = 0; std::chrono::steady_clock::time_point lastTimingFlip{}; @@ -117,6 +119,7 @@ struct VideoOutConfig { void Check() const { if (failure) std::rethrow_exception(failure); + if (shutdownToken.stop_requested()) throw ProcessShutdown{}; if (!opened || closing) throw std::runtime_error("VideoOut: port is closed"); } }; diff --git a/core/libs/prx/libSceVideoOut/src/Buffer.cpp b/core/libs/prx/libSceVideoOut/src/Buffer.cpp index 28430fe29..aa40461e1 100644 --- a/core/libs/prx/libSceVideoOut/src/Buffer.cpp +++ b/core/libs/prx/libSceVideoOut/src/Buffer.cpp @@ -35,7 +35,7 @@ void APS5_VABI sceVideoOutSetBufferAttribute2(VideoOutBufferAttribute2* attribut attribute->dcc_control = dccControl; } -int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIndexStart, const VideoOutBuffers* buffers, int bufferNum, const VideoOutBufferAttribute2* attribute, int category, void* option) { +int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIndexStart, const VideoOutBuffers* buffers, int bufferNum, const VideoOutBufferAttribute2* attribute, int category, void* option) try { auto cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); @@ -88,9 +88,11 @@ int APS5_VABI sceVideoOutRegisterBuffers2(int handle, int setIndex, int bufferIn cfg->buffers[bufferIndexStart + i] = registered[i]; } return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int setIndex, const VideoOutBufferAttribute2* attribute, void* option) { +int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int setIndex, const VideoOutBufferAttribute2* attribute, void* option) try { auto cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); @@ -120,9 +122,11 @@ int APS5_VABI sceVideoOutSubmitChangeBufferAttribute2(int handle, int setIndex, } cfg->groups[setIndex] = group; return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int setIndex) { +int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int setIndex) try { auto cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); @@ -148,9 +152,11 @@ int APS5_VABI sceVideoOutUnregisterBuffers(int handle, int setIndex) { } } return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t flipArg) { +int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t flipArg) try { auto cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); @@ -173,6 +179,8 @@ int APS5_VABI sceVideoOutSubmitFlip(int handle, int index, int flipMode, int64_t } lock.unlock(); return VideoOutDriver::Get().SubmitFlip(handle, index, flipMode, flipArg); +} 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 fcf64b9e9..c70ba7557 100644 --- a/core/libs/prx/libSceVideoOut/src/Event.cpp +++ b/core/libs/prx/libSceVideoOut/src/Event.cpp @@ -72,7 +72,7 @@ static int registerVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind, e->event.data = 0; }; const int result = EqueueAddEvent_nid_postfix(eq, event); - if (result == EQUEUE_ERROR_EBADF) { + if (result == SCE_KERNEL_ERROR_EBADF) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE"); } if (result != EQUEUE_OK) throw std::runtime_error("VideoOut: event registration failed"); @@ -99,43 +99,57 @@ static int deleteVideoOutEvent(int handle, KernelEqueue eq, int16_t eventKind) { events->erase(std::remove_if(events->begin(), events->end(), [eq](const EventRegistration& r) { return r.eq == eq; }), events->end()); } const int result = EqueueDeleteEvent_nid_postfix(eq, static_cast(eventKind), EVFILT_VIDEO_OUT); - if (result == EQUEUE_ERROR_EBADF) { + if (result == SCE_KERNEL_ERROR_EBADF) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_EVENT_QUEUE"); } - return (result == EQUEUE_ERROR_ENOENT) ? 0 : result; + return (result == SCE_KERNEL_ERROR_ENOENT) ? 0 : result; } extern "C" { -int APS5_VABI sceVideoOutAddFlipEvent(KernelEqueue eq, int handle, void* udata) { +int APS5_VABI sceVideoOutAddFlipEvent(KernelEqueue eq, int handle, void* udata) try { return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_FLIP, udata); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutAddVblankEvent(KernelEqueue eq, int handle, void* udata) { +int APS5_VABI sceVideoOutAddVblankEvent(KernelEqueue eq, int handle, void* udata) try { return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_VBLANK, udata); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutAddPreVblankStartEvent(KernelEqueue eq, int handle, void* udata) { +int APS5_VABI sceVideoOutAddPreVblankStartEvent(KernelEqueue eq, int handle, void* udata) try { static_cast(eq); static_cast(udata); VideoOutDriver::Get().GetConfig(handle); throw std::runtime_error("VideoOut: physical pre-vblank timing is not implemented"); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutAddOutputModeEvent(KernelEqueue eq, int handle, void* udata) { +int APS5_VABI sceVideoOutAddOutputModeEvent(KernelEqueue eq, int handle, void* udata) try { return registerVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_SET_MODE, udata); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutDeleteFlipEvent(KernelEqueue eq, int handle) { +int APS5_VABI sceVideoOutDeleteFlipEvent(KernelEqueue eq, int handle) try { return deleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_FLIP); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutDeleteVblankEvent(KernelEqueue eq, int handle) { +int APS5_VABI sceVideoOutDeleteVblankEvent(KernelEqueue eq, int handle) try { return deleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_VBLANK); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutDeletePreVblankStartEvent(KernelEqueue eq, int handle) { +int APS5_VABI sceVideoOutDeletePreVblankStartEvent(KernelEqueue eq, int handle) try { return deleteVideoOutEvent(handle, eq, VIDEO_OUT_EVENT_PRE_VBLANK_START); +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } int APS5_VABI sceVideoOutGetEventId(const KernelEvent* ev) { diff --git a/core/libs/prx/libSceVideoOut/src/MouseInput.cpp b/core/libs/prx/libSceVideoOut/src/MouseInput.cpp new file mode 100644 index 000000000..3a5443e6e --- /dev/null +++ b/core/libs/prx/libSceVideoOut/src/MouseInput.cpp @@ -0,0 +1,55 @@ +#include "prx/libSceVideoOut/include/MouseInput.hpp" +#include "prx/libSceMouse/include/MouseState.hpp" + +#include "SDL_mouse.h" + +void MouseInput::HandleEvent(const SDL_Event& event, unsigned windowId) { + MouseInputEvent input{}; + switch (event.type) { + case SDL_MOUSEMOTION: + if (event.motion.windowID != windowId || !focused) return; + input.x = event.motion.xrel; + input.y = event.motion.yrel; + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (event.button.windowID != windowId || !focused) return; + switch (event.button.button) { + case SDL_BUTTON_LEFT: input.button = 1; break; + case SDL_BUTTON_RIGHT: input.button = 2; break; + case SDL_BUTTON_MIDDLE: input.button = 4; break; + default: return; + } + input.pressed = event.type == SDL_MOUSEBUTTONDOWN; + break; + case SDL_MOUSEWHEEL: + if (event.wheel.windowID != windowId || !focused) return; + input.wheel = event.wheel.y; + input.tilt = event.wheel.x; + if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { + input.wheel = -input.wheel; + input.tilt = -input.tilt; + } + if (input.wheel == 0 && input.tilt == 0) return; + break; + case SDL_WINDOWEVENT: + if (event.window.windowID != windowId) return; + if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { + focused = false; + input.resetButtons = true; + } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) { + focused = true; + input.connectionChange = true; + } else if (event.window.event == SDL_WINDOWEVENT_CLOSE) { + focused = false; + input.connectionChange = true; + input.connected = false; + } else { + return; + } + break; + default: + return; + } + MousePublishInput_nid_postfix(input); +} diff --git a/core/libs/prx/libSceVideoOut/src/Output.cpp b/core/libs/prx/libSceVideoOut/src/Output.cpp index c859772ca..e1038cff4 100644 --- a/core/libs/prx/libSceVideoOut/src/Output.cpp +++ b/core/libs/prx/libSceVideoOut/src/Output.cpp @@ -29,7 +29,7 @@ static int validateOutputConfig(int handle, uint64_t mode, const VideoOutOutputO extern "C" { -int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param) { +int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* param) try { if (param != nullptr) { throw std::runtime_error(std::string(__func__) + ": param not implemented"); } @@ -47,13 +47,17 @@ int APS5_VABI sceVideoOutOpen(int userId, int busType, int index, const void* pa throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_RESOURCE_BUSY"); } return handle; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutClose(int handle) { +int APS5_VABI sceVideoOutClose(int handle) try { return VideoOutDriver::Get().Close(handle) ? 0 : VIDEO_OUT_ERROR_INVALID_HANDLE; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutSetFlipRate(int handle, int rate) { +int APS5_VABI sceVideoOutSetFlipRate(int handle, int rate) try { auto cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); @@ -65,9 +69,11 @@ int APS5_VABI sceVideoOutSetFlipRate(int handle, int rate) { cfg->Check(); cfg->flipRate = rate; return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) { +int APS5_VABI sceVideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) try { if (status == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS"); } @@ -79,9 +85,11 @@ int APS5_VABI sceVideoOutGetFlipStatus(int handle, VideoOutFlipStatus* status) { cfg->Check(); *status = cfg->flipStatus; return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutGetVblankStatus(int handle, VideoOutVblankStatus* status) { +int APS5_VABI sceVideoOutGetVblankStatus(int handle, VideoOutVblankStatus* status) try { if (status == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS"); } @@ -93,9 +101,11 @@ int APS5_VABI sceVideoOutGetVblankStatus(int handle, VideoOutVblankStatus* statu cfg->Check(); *status = cfg->vblankStatus; return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status) { +int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* status) try { if (status == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS"); } @@ -113,9 +123,11 @@ int APS5_VABI sceVideoOutGetOutputStatus(int handle, VideoOutOutputStatus* statu status->reserved[1] = 0; status->reserved[2] = 0; return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutIsFlipPending(int handle) { +int APS5_VABI sceVideoOutIsFlipPending(int handle) try { auto cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); @@ -123,9 +135,11 @@ int APS5_VABI sceVideoOutIsFlipPending(int handle) { std::unique_lock lock(cfg->mutex); cfg->Check(); return cfg->flipStatus.flipPendingNum; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutWaitVblank(int handle) { +int APS5_VABI sceVideoOutWaitVblank(int handle) try { auto cfg = VideoOutDriver::Get().GetConfig(handle); if (cfg == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); @@ -133,12 +147,11 @@ int APS5_VABI sceVideoOutWaitVblank(int handle) { std::unique_lock lock(cfg->mutex); cfg->Check(); const uint64_t count = cfg->vblankStatus.count; - while (cfg->opened && !cfg->failure && cfg->vblankStatus.count == count) { - cfg->vblankCond.wait(lock); - } - if (cfg->failure) std::rethrow_exception(cfg->failure); - if (!cfg->opened || cfg->closing) throw std::runtime_error("sceVideoOutWaitVblank: port closed during wait"); + cfg->vblankCond.wait(lock, cfg->shutdownToken, [&] { return !cfg->opened || cfg->closing || cfg->failure || cfg->vblankStatus.count != count; }); + cfg->Check(); return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } int APS5_VABI sceVideoOutInitializeOutputOptions(VideoOutOutputOptions* options) { @@ -149,15 +162,17 @@ int APS5_VABI sceVideoOutInitializeOutputOptions(VideoOutOutputOptions* options) return 0; } -int APS5_VABI sceVideoOutIsOutputSupported(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) { +int APS5_VABI sceVideoOutIsOutputSupported(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) try { const int result = validateOutputConfig(handle, mode, options, reservedPtr, reserved); if (result != 0) { return result; } return (mode == VIDEO_OUT_OUTPUT_MODE_119_88HZ) ? 0 : 1; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) { +int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoOutOutputOptions* options, void* reservedPtr, uint64_t reserved) try { const int supported = sceVideoOutIsOutputSupported(handle, mode, options, reservedPtr, reserved); if (supported < 0) { return supported; @@ -173,30 +188,38 @@ int APS5_VABI sceVideoOutConfigureOutput(int handle, uint64_t mode, const VideoO cfg->Check(); cfg->outputMode = mode; return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutSetWindowModeMargins(int handle, int top, int bottom) { +int APS5_VABI sceVideoOutSetWindowModeMargins(int handle, int top, int bottom) try { (void)top; (void)bottom; if (!VideoOutDriver::Get().IsOpen(handle)) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); } return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutLatencyControlWaitBeforeInput(int handle) { +int APS5_VABI sceVideoOutLatencyControlWaitBeforeInput(int handle) try { if (!VideoOutDriver::Get().IsOpen(handle)) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); } return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } -int APS5_VABI sceVideoOutLatencyMeasureSetStartPoint(int handle, uint32_t point) { +int APS5_VABI sceVideoOutLatencyMeasureSetStartPoint(int handle, uint32_t point) try { (void)point; if (!VideoOutDriver::Get().IsOpen(handle)) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_HANDLE"); } return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } int APS5_VABI sceVideoOutColorSettingsSetGamma(VideoOutColorSettings* settings, float gamma) { @@ -210,7 +233,7 @@ int APS5_VABI sceVideoOutColorSettingsSetGamma(VideoOutColorSettings* settings, return 0; } -int APS5_VABI sceVideoOutAdjustColor(int handle, const VideoOutColorSettings* settings) { +int APS5_VABI sceVideoOutAdjustColor(int handle, const VideoOutColorSettings* settings) try { if (settings == nullptr) { throw std::runtime_error(std::string(__func__) + ": VIDEO_OUT_ERROR_INVALID_ADDRESS"); } @@ -220,6 +243,8 @@ int APS5_VABI sceVideoOutAdjustColor(int handle, const VideoOutColorSettings* se } // Output gamma is accepted but not yet applied to presentation. return 0; +} catch (const ProcessShutdown&) { + LibcAwaitExit_nid_postfix(); } } diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index ffc1ac470..26d2bdb67 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -10,6 +10,7 @@ #include "SDL.h" #include "SDL_vulkan.h" #include "prx/libSceVideoOut/include/PadInput.hpp" +#include "prx/libSceVideoOut/include/MouseInput.hpp" #include "prx/libScePad/include/PadState.hpp" #include "prx/libkernel/Equeue/Equeue.hpp" #include "prx/libkernel/Time/include/Time.hpp" @@ -39,7 +40,7 @@ public: : _config(std::move(config)), _index(index), _ticket(ticket) {} void Wait() override { std::unique_lock lock(_config->mutex); - _config->vblankCond.wait(lock, [&] { + _config->vblankCond.wait(lock, _config->shutdownToken, [&] { return _config->failure || _config->closing || !_config->opened || _config->bufferReuse[_index].IsComplete(_ticket); }); @@ -75,13 +76,14 @@ public: if (waitWhenFull) { int waited = 0; while (queue->reservations.load() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) { + if (cfg->shutdownToken.stop_requested()) throw ProcessShutdown{}; std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (++waited % 500 == 0) std::fprintf(stderr, "[flip] queue full for %d s; waiting (APS5_FLIP_QUEUE_WAIT)\n", waited / 100); } } std::lock_guard queueLock(queue->mutex); if (queue->failure) std::rethrow_exception(queue->failure); - require(!queue->stopping, "flip during shutdown"); + if (queue->stopping || cfg->shutdownToken.stop_requested()) throw ProcessShutdown{}; std::lock_guard lock(cfg->mutex); checkConfig(*cfg); require(queue->reservations.load() < VIDEO_OUT_FLIP_QUEUE_CAPACITY, "flip queue full"); @@ -152,7 +154,7 @@ void FlipRequest::GpuReady(const std::shared_ptr& frameT AgcDriver::PerformanceTimer readiness("VideoOut.Readiness"); std::lock_guard queueLock(queue->mutex); if (queue->failure) std::rethrow_exception(queue->failure); - require(!queue->stopping, "GPU flip during shutdown"); + if (queue->stopping || cfg->shutdownToken.stop_requested()) throw ProcessShutdown{}; std::lock_guard lock(cfg->mutex); checkConfig(*cfg); require(reserved && !ready && !terminal && cfg->generation == generation, "invalid flip readiness transition"); @@ -171,7 +173,7 @@ void FlipRequest::GpuReady(const std::shared_ptr& frameT static const bool syncFlip = std::getenv("APS5_SYNC_FLIP") != nullptr; if (!syncFlip) return; std::unique_lock lock(cfg->mutex); - cfg->vblankCond.wait(lock, [&] { return gpuComplete || cfg->failure || cfg->closing; }); + cfg->vblankCond.wait(lock, cfg->shutdownToken, [&] { return gpuComplete || cfg->failure || cfg->closing; }); checkConfig(*cfg); require(gpuComplete, "flip preparation did not complete on GPU"); } @@ -231,24 +233,21 @@ void VideoOutDriver::Shutdown() { require(std::this_thread::get_id() != presentThread.get_id(), "presentation thread cannot stop itself"); std::lock_guard shutdownLock(shutdownMutex); if (stopped) return; + LibcRequestShutdown_nid_postfix(); { - std::lock_guard lock(mutex); - { - std::lock_guard queueLock(flipQueue->mutex); - flipQueue->stopping = true; - } - for (int handle = 1; handle < VIDEO_OUT_NUM_MAX; ++handle) { - if (outputs[handle]) close(handle); - } + std::lock_guard queueLock(flipQueue->mutex); + flipQueue->stopping = true; } presentThread.request_stop(); vblankThread.request_stop(); flipQueue->changed.notify_all(); presentThread.join(); vblankThread.join(); - if (window.Handle() != nullptr) { - AgcDriverReleaseWindow_nid_postfix(window.Handle()); - window.Destroy(); + { + std::lock_guard lock(mutex); + for (int handle = 1; handle < VIDEO_OUT_NUM_MAX; ++handle) { + if (outputs[handle]) close(handle); + } } SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); stopped = true; @@ -261,7 +260,7 @@ int VideoOutDriver::Open(int busType) { { std::lock_guard queueLock(flipQueue->mutex); if (flipQueue->failure) std::rethrow_exception(flipQueue->failure); - require(!flipQueue->stopping, "open during shutdown"); + if (flipQueue->stopping || LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{}; } const int handle = busType + 1; require(handle > 0 && handle < VIDEO_OUT_NUM_MAX, "invalid output bus"); @@ -287,6 +286,7 @@ int VideoOutDriver::Open(int busType) { bool VideoOutDriver::Close(int handle) { std::lock_guard lock(mutex); + if (LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{}; return close(handle); } @@ -301,7 +301,7 @@ bool VideoOutDriver::close(int handle) { const auto removeEvents = [](const auto& events, int kind) { for (const auto& event : events) { const auto result = EqueueDeleteEvent_nid_postfix(event.eq, static_cast(kind), EVFILT_VIDEO_OUT); - require(result == EQUEUE_OK || result == EQUEUE_ERROR_EBADF || result == EQUEUE_ERROR_ENOENT, "event removal during close failed"); + require(result == EQUEUE_OK || result == SCE_KERNEL_ERROR_EBADF || result == SCE_KERNEL_ERROR_ENOENT, "event removal during close failed"); } }; removeEvents(cfg->flipEvents, VIDEO_OUT_EVENT_FLIP); @@ -321,7 +321,7 @@ std::shared_ptr VideoOutDriver::GetConfig(int handle) { { std::lock_guard lock(flipQueue->mutex); if (flipQueue->failure) std::rethrow_exception(flipQueue->failure); - require(!flipQueue->stopping, "output is shutting down"); + if (flipQueue->stopping || LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{}; } std::lock_guard lock(mutex); require(handle > 0 && handle < VIDEO_OUT_NUM_MAX && contexts[handle] != nullptr, "invalid output handle"); @@ -336,6 +336,7 @@ bool VideoOutDriver::IsOpen(int handle) { } int VideoOutDriver::SubmitFlip(int handle, int index, int flipMode, int64_t flipArg) { + if (LibcShutdownToken_nid_postfix().stop_requested()) throw ProcessShutdown{}; // A title that does not pace on flipPendingNum can run ahead of the presenter now that the queue // worker no longer waits per flip; a full queue is the documented error, not a failure. if (flipQueue->reservations.load() >= VIDEO_OUT_FLIP_QUEUE_CAPACITY) return VIDEO_OUT_ERROR_FLIP_QUEUE_FULL; @@ -356,7 +357,7 @@ void VideoOutDriver::triggerEvents(VideoOutConfig& cfg, int eventKind, void* tri for (auto it = events->begin(); it != events->end();) { require(it->generation == cfg.generation, "stale event registration"); const auto result = EqueueTriggerEvent_nid_postfix(it->eq, static_cast(eventKind), EVFILT_VIDEO_OUT, triggerData); - if (result == EQUEUE_ERROR_EBADF || result == EQUEUE_ERROR_ENOENT) it = events->erase(it); + if (result == SCE_KERNEL_ERROR_EBADF || result == SCE_KERNEL_ERROR_ENOENT) it = events->erase(it); else { require(result == EQUEUE_OK, "event delivery failed"); ++it; @@ -391,7 +392,7 @@ void VideoOutDriver::processFlip(FlipRequest& req) { require(req.cfg->lastFlipVblank <= std::numeric_limits::max() - interval, "flip interval overflow"); const auto target = req.cfg->lastFlipVblank + interval; timing.Mark("validate"); - req.cfg->vblankCond.wait(lock, [&] { return req.cfg->vblankStatus.count >= target || req.cfg->failure || req.cfg->closing; }); + req.cfg->vblankCond.wait(lock, req.cfg->shutdownToken, [&] { return req.cfg->vblankStatus.count >= target || req.cfg->failure || req.cfg->closing; }); timing.Mark("vblank_wait"); checkConfig(*req.cfg); } @@ -466,6 +467,7 @@ void VideoOutDriver::presentLoop(std::stop_token token) { std::shared_ptr current; try { PadInput padInput; + MouseInput mouseInput; while (!token.stop_requested()) { { std::unique_lock lock(flipQueue->mutex); @@ -479,8 +481,12 @@ void VideoOutDriver::presentLoop(std::stop_token token) { } SDL_Event event; while (SDL_PollEvent(&event)) { - require(event.type != SDL_QUIT, "window was closed"); + if (event.type == SDL_QUIT) { + LibcRequestExit_nid_postfix(0); + throw ProcessShutdown{}; + } padInput.HandleEvent(event, window); + if (window.Handle() != nullptr) mouseInput.HandleEvent(event, SDL_GetWindowID(window.Handle())); } padInput.Update(); if (current) { @@ -500,15 +506,7 @@ void VideoOutDriver::presentLoop(std::stop_token token) { } current.reset(); } - std::list> cancelled; - { - std::lock_guard lock(flipQueue->mutex); - cancelled.swap(flipQueue->requests); - } - if (!cancelled.empty()) { - auto error = std::make_exception_ptr(std::runtime_error("VideoOut: pending flip cancelled during shutdown")); - for (auto& request : cancelled) request->Fail(error); - } + } catch (const ProcessShutdown&) { } catch (const std::exception& error) { std::fprintf(stderr, "[videoout] presentation failed: %s\n", error.what()); std::fflush(stderr); @@ -518,6 +516,16 @@ void VideoOutDriver::presentLoop(std::stop_token token) { std::fflush(stderr); std::terminate(); } + current.reset(); + { + std::list> cancelled; + { + std::lock_guard lock(flipQueue->mutex); + cancelled.swap(flipQueue->requests); + } + } + AgcDriverShutdown_nid_postfix(); + window.Destroy(); } void VideoOutDriver::vblankLoop(std::stop_token token) { diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index e6301e575..d8e52f150 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -5,6 +5,8 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/windows/CrashReport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/linux/DlIteratePhdr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Strings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/AllocatingStrings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/WideConversion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/StringExtensions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Math.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp diff --git a/core/libs/prx/libc/include/Shutdown.hpp b/core/libs/prx/libc/include/Shutdown.hpp index c1ff16604..dc4007098 100644 --- a/core/libs/prx/libc/include/Shutdown.hpp +++ b/core/libs/prx/libc/include/Shutdown.hpp @@ -1,8 +1,16 @@ #ifndef CORE_LIBS_PRX_LIBC_INCLUDE_SHUTDOWN_HPP #define CORE_LIBS_PRX_LIBC_INCLUDE_SHUTDOWN_HPP +#include + +class ProcessShutdown final {}; + extern "C" void LibcRegisterShutdown_nid_postfix(void (*callback)()); extern "C" void LibcRunShutdown_nid_postfix(); +extern "C" std::stop_token LibcShutdownToken_nid_postfix(); +extern "C" void LibcRequestShutdown_nid_postfix(); +extern "C" void LibcRequestExit_nid_postfix(int code); +extern "C" [[noreturn]] void LibcAwaitExit_nid_postfix(); extern "C" [[noreturn]] void LibcExit_nid_no_patch(int code); #endif diff --git a/core/libs/prx/libc/src/AllocatingStrings.cpp b/core/libs/prx/libc/src/AllocatingStrings.cpp new file mode 100644 index 000000000..d3539587b --- /dev/null +++ b/core/libs/prx/libc/src/AllocatingStrings.cpp @@ -0,0 +1,82 @@ +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include "prx/libc/include/WindowsFormatting.hpp" +#endif + +extern "C" { + +char* APS5_VABI strndup_nid_postfix(const char* source, std::size_t limit) { + std::size_t length = 0; + while (length < limit && source[length] != '\0') ++length; + try { + auto* result = static_cast(ApplicationHeapAllocate_nid_no_patch(length + 1)); + if (!result) { errno = 12; return nullptr; } + std::memcpy(result, source, length); + result[length] = '\0'; + return result; + } catch (const std::bad_alloc&) { + errno = 12; + return nullptr; + } +} + +int APS5_VABI asprintf_nid_postfix(char** destination, const char* format, ...) { + if (!destination) { errno = 22; return -1; } + *destination = nullptr; + if (!format) { errno = 22; return -1; } +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + int result = -1; + try { +#ifdef _WIN32 + std::string text; + const int count = LibcDetail::FormatWindows(nullptr, 0, format, args, &text); + const char* source = text.c_str(); +#else + char* text = nullptr; + const int count = ::vasprintf(&text, format, args); + std::unique_ptr owner(text, std::free); + const char* source = text; +#endif + if (count >= 0) { + auto* output = static_cast(ApplicationHeapAllocate_nid_no_patch(static_cast(count) + 1)); + if (output) { + std::memcpy(output, source, static_cast(count) + 1); + *destination = output; + result = count; + } else errno = 12; + } + } catch (const std::bad_alloc&) { + errno = 12; + } catch (...) { +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + throw; + } +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} + +} diff --git a/core/libs/prx/libc/src/Mspace.cpp b/core/libs/prx/libc/src/Mspace.cpp index c718fed93..7821927a8 100644 --- a/core/libs/prx/libc/src/Mspace.cpp +++ b/core/libs/prx/libc/src/Mspace.cpp @@ -212,6 +212,31 @@ void* APS5_VABI sceLibcMspaceMemalign_nid_postfix(void* handle, std::size_t alig return Allocate(Find(handle), size, std::max(alignment, 16)); } +void* APS5_VABI sceLibcMspaceReallocalign_nid_postfix(void* handle, void* pointer, std::size_t size, std::size_t alignment) { + if (alignment == 0 || (alignment & (alignment - 1))) { + Error(22); + return nullptr; + } + const auto effective = std::max(alignment, 16); + std::lock_guard lock(arenaMutex); + auto* arena = Find(handle); + if (!pointer) return Allocate(arena, size, effective); + auto* block = FindBlock(arena, pointer); + if (!block) return nullptr; + if (!size) { Release(arena, block); return nullptr; } + const auto padding = reinterpret_cast(pointer) - reinterpret_cast(block + 1); + if (size <= block->capacity - padding && (reinterpret_cast(pointer) & (effective - 1)) == 0) { + block->used = size; + return pointer; + } + void* result = Allocate(arena, size, effective); + if (result) { + std::memcpy(result, pointer, std::min(block->used, size)); + Release(arena, block); + } + return result; +} + int APS5_VABI sceLibcMspaceMallocStats_nid_postfix(void* handle, MallocManagedSize* stats) { return FillStats(handle, stats); } diff --git a/core/libs/prx/libc/src/Process.cpp b/core/libs/prx/libc/src/Process.cpp index 7a8dcc613..0e8a39c0c 100644 --- a/core/libs/prx/libc/src/Process.cpp +++ b/core/libs/prx/libc/src/Process.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "prx/libc/include/Shutdown.hpp" #include "prx/libc/include/General.hpp" @@ -22,9 +24,35 @@ std::thread::id shutdownThread; bool shutdownStarted = false; bool shutdownFinished = false; std::exception_ptr shutdownFailure; +std::stop_source shutdownSource; +std::atomic exitRequested{false}; +std::atomic exitStarted{false}; } +extern "C" std::stop_token LibcShutdownToken_nid_postfix() { + return shutdownSource.get_token(); +} + +extern "C" void LibcRequestShutdown_nid_postfix() { + shutdownSource.request_stop(); +} + +extern "C" void LibcRequestExit_nid_postfix(int code) { + if (exitRequested.exchange(true)) return; + LibcRequestShutdown_nid_postfix(); + std::thread([code] { LibcExit_nid_no_patch(code); }).detach(); +} + +extern "C" [[noreturn]] void LibcAwaitExit_nid_postfix() { + if (!exitRequested.load()) throw ProcessShutdown{}; + { + std::lock_guard lock(shutdownMutex); + if (shutdownThread == std::this_thread::get_id()) throw std::runtime_error("libc: exit thread cannot wait for itself"); + } + for (;;) exitRequested.wait(true); +} + extern "C" void LibcRegisterShutdown_nid_postfix(void (*callback)()) { std::lock_guard lock(shutdownMutex); if (callback == nullptr || shutdownStarted) throw std::runtime_error("libc: invalid shutdown registration"); @@ -43,6 +71,7 @@ extern "C" void LibcRunShutdown_nid_postfix() { shutdownThread = std::this_thread::get_id(); auto callbacks = std::move(shutdownCallbacks); lock.unlock(); + LibcRequestShutdown_nid_postfix(); std::exception_ptr error; for (auto it = callbacks.rbegin(); it != callbacks.rend(); ++it) { try { (*it)(); } @@ -68,6 +97,8 @@ extern "C" { } [[noreturn]] void LibcExit_nid_no_patch(int code) { + exitRequested.store(true); + if (exitStarted.exchange(true)) LibcAwaitExit_nid_postfix(); LibcRunShutdown_nid_postfix(); std::exit(code); } diff --git a/core/libs/prx/libc/src/WideConversion.cpp b/core/libs/prx/libc/src/WideConversion.cpp new file mode 100644 index 000000000..085d945d5 --- /dev/null +++ b/core/libs/prx/libc/src/WideConversion.cpp @@ -0,0 +1,19 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" std::size_t APS5_VABI wcstombs_nid_postfix(char* destination, const std::uint16_t* source, std::size_t capacity) { + std::size_t count = 0; + while (!destination || count < capacity) { + const auto value = source[count]; + if (value > 255) { + errno = 86; + return static_cast(-1); + } + if (destination) destination[count] = static_cast(value); + if (value == 0) break; + ++count; + } + return count; +} diff --git a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp index 53d33430b..5359fcd33 100644 --- a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp +++ b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp @@ -355,7 +355,9 @@ void InvalidParameterHandler(const wchar_t*, const wchar_t*, const wchar_t*, uns // Static destruction runs on the thread that ends the process (ExitProcess -> DLL_PROCESS_DETACH), // so its stack names the exit's caller when no libc exit/abort path was taken. struct ExitReporter { - ~ExitReporter() { ReportBacktrace("process exit (static destruction)"); } + ~ExitReporter() { + if (std::getenv("APS5_TRACE_EXIT") != nullptr) ReportBacktrace("process exit (static destruction)"); + } } g_exitReporter; const bool g_crashReportInstalled = [] { diff --git a/core/libs/prx/libcohtml/CMakeLists.txt b/core/libs/prx/libcohtml/CMakeLists.txt index f32848eb2..842799d34 100644 --- a/core/libs/prx/libcohtml/CMakeLists.txt +++ b/core/libs/prx/libcohtml/CMakeLists.txt @@ -4,16 +4,3 @@ add_library(libcohtml SHARED EXCLUDE_FROM_ALL target_include_directories(libcohtml PRIVATE ${LIBS_INCLUDE_DIR}) set_target_properties(libcohtml PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(libcohtml) - -set(LIBCOHTML_PATCHED ${PATCHED_DIR}/libcohtml.prx) -set(LIBCOHTML_PROSPERO_PATCHED ${PATCHED_DIR}/libcohtml.Prospero.prx) -add_custom_command( - OUTPUT ${LIBCOHTML_PROSPERO_PATCHED} - COMMAND ${CMAKE_COMMAND} -E copy ${LIBCOHTML_PATCHED} ${LIBCOHTML_PROSPERO_PATCHED} - DEPENDS ${LIBCOHTML_PATCHED} - COMMENT "Providing libcohtml.Prospero.prx for DT_NEEDED" - VERBATIM -) -add_custom_target(libcohtml_prospero DEPENDS ${LIBCOHTML_PROSPERO_PATCHED}) -list(APPEND PATCHED_OUTPUTS ${LIBCOHTML_PROSPERO_PATCHED}) -set(PATCHED_OUTPUTS ${PATCHED_OUTPUTS} PARENT_SCOPE) diff --git a/core/libs/prx/libcohtml/DEPLOY.md b/core/libs/prx/libcohtml/DEPLOY.md index de4c3ffea..0b4cbdc5d 100644 --- a/core/libs/prx/libcohtml/DEPLOY.md +++ b/core/libs/prx/libcohtml/DEPLOY.md @@ -5,7 +5,7 @@ Game `DT_NEEDED` expects exactly: `libcohtml.Prospero.prx`. Copy next to the relinked eboot (same directory): - `build/core/libs/libs/libcohtml.Prospero.prx` (patched build output, 50 NIDs) — required. -- `libgcc_s_seh-1.dll`, `libstdc++-6.dll`, `libwinpthread-1.dll` from `winlibs-gcc15/mingw64/bin` — required on Windows (see `docs/TechnicalDebt.md`). +- `libgcc_s_seh-1.dll`, `libstdc++-6.dll`, `libwinpthread-1.dll` from `winlibs-gcc15/mingw64/bin` — required on Windows (see [technical debt](../../../../docs/dev/TechnicalDebt.md)). Do NOT deploy: diff --git a/core/libs/prx/libfmod/Export.cpp b/core/libs/prx/libfmod/Export.cpp index 6c5a7476f..bd7545a64 100644 --- a/core/libs/prx/libfmod/Export.cpp +++ b/core/libs/prx/libfmod/Export.cpp @@ -1635,7 +1635,7 @@ public: } else { // Compressed (Vorbis/MP3/FSB/AT9...) or unknown: audible // placeholder so the mixer path can be heard. Real decoders - // (Vorbis/MP3/AT9) remain future work — see docs/TechnicalDebt. + // (Vorbis/MP3/AT9) remain future work — see docs/dev/TechnicalDebt. FmodMakeSine(created->pcm, kFmodOutRate, 2, 440.0f, 1.0f); created->pcmRate = kFmodOutRate; created->pcmCh = 2; diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 801440466..a173504cb 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Rwlock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Sem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Attr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Barrier.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Cond.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Mutex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Rwlock.cpp diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp index b98200f15..53289beff 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp @@ -4,32 +4,20 @@ #include #include +#include "prx/libkernel/KernelErrors.hpp" + static constexpr size_t DIRECT_MEMORY_SIZE = 13824ULL * 1024 * 1024; static constexpr size_t PS5_PAGE_SIZE = 0x4000; -static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; -static constexpr int SCE_KERNEL_ERROR_EAGAIN = 0x80020023; -static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C; -static constexpr int SCE_KERNEL_ERROR_EACCES = 0x8002000D; -static constexpr int SCE_KERNEL_ERROR_EFAULT = 0x8002000E; - -// A block of GPU-visible ("direct") memory the guest asked for, remembered so that later -// queries can answer with the real extent and the type it was allocated as. A title that gets -// back a one-page extent or a type it never requested rejects its own graphics allocations. struct DirectMemoryBlock { uint64_t start; uint64_t end; int memoryType; }; -// memoryType is what sceKernelAllocateDirectMemory was called with; -1 for a range the caller -// has no type for (the pool API and the available-size probe). int DirectMemoryAlloc(int64_t searchStart, int64_t searchEnd, size_t len, size_t alignment, int memoryType, int64_t* physOut); void DirectMemoryFree(int64_t start, size_t len); -// Block containing offset, as the guest was given it at allocation time. bool DirectMemoryQueryBlock(uint64_t offset, DirectMemoryBlock* block); -// Contiguous unallocated bytes starting at offset, capped at limit (what the available-size -// query reports; a whole-aperture figure would let a title plan allocations that cannot fit). 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); diff --git a/core/libs/prx/libkernel/Equeue/Equeue.cpp b/core/libs/prx/libkernel/Equeue/Equeue.cpp index ece17ea7a..50b5c7faa 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.cpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.cpp @@ -71,7 +71,7 @@ bool KernelEqueuePrivate::NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) con int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) { std::unique_lock lock(m_mutex); if (m_closed) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } TriggerExpiredTimers(MonotonicNs()); int ret = 0; @@ -111,7 +111,7 @@ int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) { int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros) { std::unique_lock lock(m_mutex); if (m_closed) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } const std::uint64_t deadline = TimedWait::DeadlineNanos(micros); for (;;) { @@ -148,7 +148,7 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros return ret; } if (m_closed) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } if (micros == 0) { m_cond.Wait(lock); @@ -168,7 +168,7 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros int KernelEqueuePrivate::AddEvent(const KernelEqueueEvent& event) { std::unique_lock lock(m_mutex); if (m_closed) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } auto it = std::find_if(m_events.begin(), m_events.end(), [ident = event.event.ident, filter = event.event.filter](const auto& e) { @@ -191,7 +191,7 @@ int KernelEqueuePrivate::AddEvent(const KernelEqueueEvent& event) { int KernelEqueuePrivate::TriggerEvent(uintptr_t ident, int16_t filter, void* triggerData) { std::unique_lock lock(m_mutex); if (m_closed) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } auto it = std::find_if(m_events.begin(), m_events.end(), [ident, filter](const auto& e) { @@ -199,7 +199,7 @@ int KernelEqueuePrivate::TriggerEvent(uintptr_t ident, int16_t filter, void* tri } ); if (it == m_events.end()) { - return EQUEUE_ERROR_ENOENT; + return SCE_KERNEL_ERROR_ENOENT; } if (it->filter.triggerFunc != nullptr) { it->filter.triggerFunc(&*it, triggerData); @@ -213,7 +213,7 @@ int KernelEqueuePrivate::TriggerEvent(uintptr_t ident, int16_t filter, void* tri int KernelEqueuePrivate::DeleteEvent(uintptr_t ident, int16_t filter) { std::unique_lock lock(m_mutex); if (m_closed) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } auto it = std::find_if(m_events.begin(), m_events.end(), [ident, filter](const auto& e) { @@ -221,7 +221,7 @@ int KernelEqueuePrivate::DeleteEvent(uintptr_t ident, int16_t filter) { } ); if (it == m_events.end()) { - return EQUEUE_ERROR_ENOENT; + return SCE_KERNEL_ERROR_ENOENT; } if (it->filter.deleteEventFunc != nullptr) { auto owner = it->filter.owner; @@ -243,7 +243,7 @@ KernelEqueueRef EqueuePin_nid_postfix(KernelEqueue eq) { int APS5_VABI EqueueAddEvent_nid_postfix(KernelEqueue eq, const KernelEqueueEvent& event) { auto owner = EqueuePin_nid_postfix(eq); if (!owner) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } return owner->AddEvent(event); } @@ -251,7 +251,7 @@ int APS5_VABI EqueueAddEvent_nid_postfix(KernelEqueue eq, const KernelEqueueEven int APS5_VABI EqueueTriggerEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, int16_t filter, void* triggerData) { auto owner = EqueuePin_nid_postfix(eq); if (!owner) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } return owner->TriggerEvent(ident, filter, triggerData); } @@ -259,7 +259,7 @@ int APS5_VABI EqueueTriggerEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, i int APS5_VABI EqueueDeleteEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, int16_t filter) { auto owner = EqueuePin_nid_postfix(eq); if (!owner) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } return owner->DeleteEvent(ident, filter); } @@ -267,7 +267,7 @@ int APS5_VABI EqueueDeleteEvent_nid_postfix(KernelEqueue eq, uintptr_t ident, in int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name) { if (eq == nullptr || name == nullptr) { - return EQUEUE_ERROR_EINVAL; + return SCE_KERNEL_ERROR_EINVAL; } std::unique_lock lock(g_equeueMutex); if (g_nextEqueue > static_cast(std::numeric_limits::max())) { @@ -286,7 +286,7 @@ int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq) { std::unique_lock lock(g_equeueMutex); auto it = g_equeues.find(eq); if (it == g_equeues.end()) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } owner = std::move(it->second); g_equeues.erase(it); @@ -298,13 +298,13 @@ int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq) { int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo) { auto owner = EqueuePin_nid_postfix(eq); if (!owner) { - return EQUEUE_ERROR_EBADF; + return SCE_KERNEL_ERROR_EBADF; } if (ev == nullptr) { - return EQUEUE_ERROR_EFAULT; + return SCE_KERNEL_ERROR_EFAULT; } if (num < 1 || out == nullptr) { - return EQUEUE_ERROR_EINVAL; + return SCE_KERNEL_ERROR_EINVAL; } const auto waitStart = std::chrono::steady_clock::now(); if (timo == nullptr) { @@ -317,11 +317,11 @@ int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int if (timo == nullptr || *timo != 0) { KernelTraceWait_nid_postfix("equeue", __builtin_return_address(0), static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - waitStart).count()), *out == 0); } - if (*out == EQUEUE_ERROR_EBADF) { - return EQUEUE_ERROR_EBADF; + if (*out == SCE_KERNEL_ERROR_EBADF) { + return SCE_KERNEL_ERROR_EBADF; } if (*out == 0) { - return EQUEUE_ERROR_ETIMEDOUT; + return SCE_KERNEL_ERROR_ETIMEDOUT; } return EQUEUE_OK; } @@ -376,10 +376,10 @@ int APS5_VABI sceKernelDeleteUserEvent(KernelEqueue eq, int id) { int APS5_VABI sceKernelAddHRTimerEvent(KernelEqueue eq, int id, const KernelTimespec* ts, void* udata) { if (ts == nullptr) { - return EQUEUE_ERROR_EFAULT; + return SCE_KERNEL_ERROR_EFAULT; } if (ts->tv_sec < 0 || ts->tv_nsec < 0 || ts->tv_nsec >= 1000000000LL) { - return EQUEUE_ERROR_EINVAL; + return SCE_KERNEL_ERROR_EINVAL; } const uint64_t delayNs = static_cast(ts->tv_sec) * 1000000000ULL + diff --git a/core/libs/prx/libkernel/Equeue/Equeue.hpp b/core/libs/prx/libkernel/Equeue/Equeue.hpp index d5bcf3910..48085e6ac 100644 --- a/core/libs/prx/libkernel/Equeue/Equeue.hpp +++ b/core/libs/prx/libkernel/Equeue/Equeue.hpp @@ -11,6 +11,7 @@ #include "SceTypes.hpp" #include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" static constexpr int16_t EVFILT_USER = -11; @@ -23,11 +24,6 @@ static constexpr uint16_t EV_CLEAR = 0x0020; static constexpr uint16_t EV_ERROR = 0x4000; static constexpr int EQUEUE_OK = 0; -static constexpr int EQUEUE_ERROR_EBADF = 0x80020009; -static constexpr int EQUEUE_ERROR_EFAULT = 0x8002000E; -static constexpr int EQUEUE_ERROR_EINVAL = 0x80020016; -static constexpr int EQUEUE_ERROR_ENOENT = 0x80020002; -static constexpr int EQUEUE_ERROR_ETIMEDOUT = 0x8002003C; struct KernelEqueueEvent; diff --git a/core/libs/prx/libkernel/File/src/Open.cpp b/core/libs/prx/libkernel/File/src/Open.cpp index cd71e7fa0..fd45c02fe 100644 --- a/core/libs/prx/libkernel/File/src/Open.cpp +++ b/core/libs/prx/libkernel/File/src/Open.cpp @@ -2,6 +2,7 @@ #include "prx/libkernel/File/include/NativeStat.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include "SceTypes.hpp" #include @@ -9,8 +10,6 @@ #include #include -static constexpr int SCE_KERNEL_ERROR_ENOENT = -2147352574; - #ifdef _WIN32 #include #include diff --git a/core/libs/prx/libkernel/KernelErrors.hpp b/core/libs/prx/libkernel/KernelErrors.hpp new file mode 100644 index 000000000..b7d37ba4f --- /dev/null +++ b/core/libs/prx/libkernel/KernelErrors.hpp @@ -0,0 +1,103 @@ +#ifndef CORE_LIBS_PRX_LIBKERNEL_KERNELERRORS_HPP +#define CORE_LIBS_PRX_LIBKERNEL_KERNELERRORS_HPP + +constexpr int SceKernelError(int errnoValue) { + return static_cast(0x80020000u | static_cast(errnoValue)); +} + +inline constexpr int SCE_KERNEL_ERROR_EPERM = SceKernelError(1); +inline constexpr int SCE_KERNEL_ERROR_ENOENT = SceKernelError(2); +inline constexpr int SCE_KERNEL_ERROR_ESRCH = SceKernelError(3); +inline constexpr int SCE_KERNEL_ERROR_EINTR = SceKernelError(4); +inline constexpr int SCE_KERNEL_ERROR_EIO = SceKernelError(5); +inline constexpr int SCE_KERNEL_ERROR_ENXIO = SceKernelError(6); +inline constexpr int SCE_KERNEL_ERROR_E2BIG = SceKernelError(7); +inline constexpr int SCE_KERNEL_ERROR_ENOEXEC = SceKernelError(8); +inline constexpr int SCE_KERNEL_ERROR_EBADF = SceKernelError(9); +inline constexpr int SCE_KERNEL_ERROR_ECHILD = SceKernelError(10); +inline constexpr int SCE_KERNEL_ERROR_EDEADLK = SceKernelError(11); +inline constexpr int SCE_KERNEL_ERROR_ENOMEM = SceKernelError(12); +inline constexpr int SCE_KERNEL_ERROR_EACCES = SceKernelError(13); +inline constexpr int SCE_KERNEL_ERROR_EFAULT = SceKernelError(14); +inline constexpr int SCE_KERNEL_ERROR_ENOTBLK = SceKernelError(15); +inline constexpr int SCE_KERNEL_ERROR_EBUSY = SceKernelError(16); +inline constexpr int SCE_KERNEL_ERROR_EEXIST = SceKernelError(17); +inline constexpr int SCE_KERNEL_ERROR_EXDEV = SceKernelError(18); +inline constexpr int SCE_KERNEL_ERROR_ENODEV = SceKernelError(19); +inline constexpr int SCE_KERNEL_ERROR_ENOTDIR = SceKernelError(20); +inline constexpr int SCE_KERNEL_ERROR_EISDIR = SceKernelError(21); +inline constexpr int SCE_KERNEL_ERROR_EINVAL = SceKernelError(22); +inline constexpr int SCE_KERNEL_ERROR_ENFILE = SceKernelError(23); +inline constexpr int SCE_KERNEL_ERROR_EMFILE = SceKernelError(24); +inline constexpr int SCE_KERNEL_ERROR_ENOTTY = SceKernelError(25); +inline constexpr int SCE_KERNEL_ERROR_ETXTBSY = SceKernelError(26); +inline constexpr int SCE_KERNEL_ERROR_EFBIG = SceKernelError(27); +inline constexpr int SCE_KERNEL_ERROR_ENOSPC = SceKernelError(28); +inline constexpr int SCE_KERNEL_ERROR_ESPIPE = SceKernelError(29); +inline constexpr int SCE_KERNEL_ERROR_EROFS = SceKernelError(30); +inline constexpr int SCE_KERNEL_ERROR_EMLINK = SceKernelError(31); +inline constexpr int SCE_KERNEL_ERROR_EPIPE = SceKernelError(32); +inline constexpr int SCE_KERNEL_ERROR_EDOM = SceKernelError(33); +inline constexpr int SCE_KERNEL_ERROR_ERANGE = SceKernelError(34); +inline constexpr int SCE_KERNEL_ERROR_EAGAIN = SceKernelError(35); +inline constexpr int SCE_KERNEL_ERROR_EINPROGRESS = SceKernelError(36); +inline constexpr int SCE_KERNEL_ERROR_EALREADY = SceKernelError(37); +inline constexpr int SCE_KERNEL_ERROR_ENOTSOCK = SceKernelError(38); +inline constexpr int SCE_KERNEL_ERROR_EDESTADDRREQ = SceKernelError(39); +inline constexpr int SCE_KERNEL_ERROR_EMSGSIZE = SceKernelError(40); +inline constexpr int SCE_KERNEL_ERROR_EPROTOTYPE = SceKernelError(41); +inline constexpr int SCE_KERNEL_ERROR_ENOPROTOOPT = SceKernelError(42); +inline constexpr int SCE_KERNEL_ERROR_EPROTONOSUPPORT = SceKernelError(43); +inline constexpr int SCE_KERNEL_ERROR_ESOCKTNOSUPPORT = SceKernelError(44); +inline constexpr int SCE_KERNEL_ERROR_EOPNOTSUPP = SceKernelError(45); +inline constexpr int SCE_KERNEL_ERROR_EPFNOSUPPORT = SceKernelError(46); +inline constexpr int SCE_KERNEL_ERROR_EAFNOSUPPORT = SceKernelError(47); +inline constexpr int SCE_KERNEL_ERROR_EADDRINUSE = SceKernelError(48); +inline constexpr int SCE_KERNEL_ERROR_EADDRNOTAVAIL = SceKernelError(49); +inline constexpr int SCE_KERNEL_ERROR_ENETDOWN = SceKernelError(50); +inline constexpr int SCE_KERNEL_ERROR_ENETUNREACH = SceKernelError(51); +inline constexpr int SCE_KERNEL_ERROR_ENETRESET = SceKernelError(52); +inline constexpr int SCE_KERNEL_ERROR_ECONNABORTED = SceKernelError(53); +inline constexpr int SCE_KERNEL_ERROR_ECONNRESET = SceKernelError(54); +inline constexpr int SCE_KERNEL_ERROR_ENOBUFS = SceKernelError(55); +inline constexpr int SCE_KERNEL_ERROR_EISCONN = SceKernelError(56); +inline constexpr int SCE_KERNEL_ERROR_ENOTCONN = SceKernelError(57); +inline constexpr int SCE_KERNEL_ERROR_ESHUTDOWN = SceKernelError(58); +inline constexpr int SCE_KERNEL_ERROR_ETOOMANYREFS = SceKernelError(59); +inline constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = SceKernelError(60); +inline constexpr int SCE_KERNEL_ERROR_ECONNREFUSED = SceKernelError(61); +inline constexpr int SCE_KERNEL_ERROR_ELOOP = SceKernelError(62); +inline constexpr int SCE_KERNEL_ERROR_ENAMETOOLONG = SceKernelError(63); +inline constexpr int SCE_KERNEL_ERROR_EHOSTDOWN = SceKernelError(64); +inline constexpr int SCE_KERNEL_ERROR_EHOSTUNREACH = SceKernelError(65); +inline constexpr int SCE_KERNEL_ERROR_ENOTEMPTY = SceKernelError(66); +inline constexpr int SCE_KERNEL_ERROR_EPROCLIM = SceKernelError(67); +inline constexpr int SCE_KERNEL_ERROR_EUSERS = SceKernelError(68); +inline constexpr int SCE_KERNEL_ERROR_EDQUOT = SceKernelError(69); +inline constexpr int SCE_KERNEL_ERROR_ESTALE = SceKernelError(70); +inline constexpr int SCE_KERNEL_ERROR_EREMOTE = SceKernelError(71); +inline constexpr int SCE_KERNEL_ERROR_EBADRPC = SceKernelError(72); +inline constexpr int SCE_KERNEL_ERROR_ERPCMISMATCH = SceKernelError(73); +inline constexpr int SCE_KERNEL_ERROR_EPROGUNAVAIL = SceKernelError(74); +inline constexpr int SCE_KERNEL_ERROR_EPROGMISMATCH = SceKernelError(75); +inline constexpr int SCE_KERNEL_ERROR_EPROCUNAVAIL = SceKernelError(76); +inline constexpr int SCE_KERNEL_ERROR_ENOLCK = SceKernelError(77); +inline constexpr int SCE_KERNEL_ERROR_ENOSYS = SceKernelError(78); +inline constexpr int SCE_KERNEL_ERROR_EFTYPE = SceKernelError(79); +inline constexpr int SCE_KERNEL_ERROR_EAUTH = SceKernelError(80); +inline constexpr int SCE_KERNEL_ERROR_ENEEDAUTH = SceKernelError(81); +inline constexpr int SCE_KERNEL_ERROR_EIDRM = SceKernelError(82); +inline constexpr int SCE_KERNEL_ERROR_ENOMSG = SceKernelError(83); +inline constexpr int SCE_KERNEL_ERROR_EOVERFLOW = SceKernelError(84); +inline constexpr int SCE_KERNEL_ERROR_ECANCELED = SceKernelError(85); +inline constexpr int SCE_KERNEL_ERROR_EILSEQ = SceKernelError(86); +inline constexpr int SCE_KERNEL_ERROR_ENOATTR = SceKernelError(87); +inline constexpr int SCE_KERNEL_ERROR_EDOOFUS = SceKernelError(88); +inline constexpr int SCE_KERNEL_ERROR_EBADMSG = SceKernelError(89); +inline constexpr int SCE_KERNEL_ERROR_EMULTIHOP = SceKernelError(90); +inline constexpr int SCE_KERNEL_ERROR_ENOLINK = SceKernelError(91); +inline constexpr int SCE_KERNEL_ERROR_EPROTO = SceKernelError(92); +inline constexpr int SCE_KERNEL_ERROR_ENOTCAPABLE = SceKernelError(93); +inline constexpr int SCE_KERNEL_ERROR_ECAPMODE = SceKernelError(94); + +#endif diff --git a/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp new file mode 100644 index 000000000..723fd1267 --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/Posix/Barrier.cpp @@ -0,0 +1,61 @@ +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include + +struct PthreadBarrierPrivate { + std::mutex mutex; + std::condition_variable changed; + unsigned count; + unsigned arrived = 0; + unsigned active = 0; + std::uint64_t generation = 0; +}; + +extern "C" { + +int APS5_VABI pthread_barrier_init_nid_postfix(PthreadBarrierPrivate** barrier, const void* attr, unsigned count) { + if (!barrier || count == 0) return 22; + if (attr) NotImplemented_nid_no_patch("pthread_barrier_init: non-default attributes"); + try { + auto* value = new PthreadBarrierPrivate; + value->count = count; + *barrier = value; + return 0; + } catch (const std::bad_alloc&) { + return 12; + } +} + +int APS5_VABI pthread_barrier_wait_nid_postfix(PthreadBarrierPrivate** barrier) { + if (!barrier || !*barrier) return 22; + auto& value = **barrier; + std::unique_lock lock(value.mutex); + const auto generation = value.generation; + ++value.active; + if (++value.arrived == value.count) { + value.arrived = 0; + ++value.generation; + value.changed.notify_all(); + --value.active; + return -1; + } + value.changed.wait(lock, [&] { return value.generation != generation; }); + --value.active; + return 0; +} + +int APS5_VABI pthread_barrier_destroy_nid_postfix(PthreadBarrierPrivate** barrier) { + if (!barrier || !*barrier) return 22; + auto* value = *barrier; + { + std::lock_guard lock(value->mutex); + if (value->active != 0) return 16; + } + delete value; + *barrier = nullptr; + return 0; +} + +} diff --git a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp index f1d43aa31..fc9fd94d8 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp @@ -57,6 +57,15 @@ Pthread APS5_VABI pthread_self_nid_postfix(void) { return scePthreadSelf(); } +int APS5_VABI pthread_equal_nid_postfix(Pthread first, Pthread second) { + return first == second; +} + +int APS5_VABI sched_yield_nid_postfix(void) { + std::this_thread::yield(); + return 0; +} + int APS5_VABI pthread_setcancelstate_nid_postfix(int state, int* old_state) { return PosixThread::ToErrno(scePthreadSetcancelstate(state, old_state)); } diff --git a/core/libs/prx/libkernel/Pthread/src/Attr.cpp b/core/libs/prx/libkernel/Pthread/src/Attr.cpp index 11fb3c38f..c91c1038a 100644 --- a/core/libs/prx/libkernel/Pthread/src/Attr.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Attr.cpp @@ -1,11 +1,10 @@ #include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include #include static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; -static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C; static constexpr std::size_t DEFAULT_STACK_SIZE = 1u << 20; static constexpr int DETACH_JOINABLE = 0; diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index bb6ea5acf..27b84bba6 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -1,6 +1,7 @@ #include "../include/Pthread.hpp" #include "../include/ThreadLifecycle.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include "prx/libc/include/CpuTopology.hpp" #include #include @@ -19,7 +20,6 @@ #endif static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; static constexpr std::size_t DEFAULT_STACK_SIZE = 1u << 20; static constexpr int DETACH_DETACHED = 1; diff --git a/core/libs/prx/libkernel/Semaphore/include/Semaphore.hpp b/core/libs/prx/libkernel/Semaphore/include/Semaphore.hpp index ff29d1779..774d1f909 100644 --- a/core/libs/prx/libkernel/Semaphore/include/Semaphore.hpp +++ b/core/libs/prx/libkernel/Semaphore/include/Semaphore.hpp @@ -8,13 +8,10 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" constexpr int KERNEL_SEMA_OK = 0; -constexpr int KERNEL_SEMA_ERROR_EINVAL = static_cast(0x80020016); -constexpr int KERNEL_SEMA_ERROR_EACCES = static_cast(0x8002000D); -constexpr int KERNEL_SEMA_ERROR_EBUSY = static_cast(0x80020010); -constexpr int KERNEL_SEMA_ERROR_ETIMEDOUT = static_cast(0x8002003C); struct KernelSemaPrivate { KernelSemaPrivate(std::int32_t initCount, std::int32_t maxCount, std::string name, bool isFifo); diff --git a/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp b/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp index 5597fe2c6..6c5e07d24 100644 --- a/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp +++ b/core/libs/prx/libkernel/Semaphore/src/Semaphore.cpp @@ -29,7 +29,7 @@ int APS5_VABI sceKernelPollSema(KernelSema sem, int need) { std::lock_guard lock(sem->mutex); if (sem->tokenCount < need) { - return KERNEL_SEMA_ERROR_EBUSY; + return SCE_KERNEL_ERROR_EBUSY; } sem->tokenCount -= need; return KERNEL_SEMA_OK; @@ -42,7 +42,7 @@ int APS5_VABI sceKernelSignalSema(KernelSema sem, int count) { std::lock_guard lock(sem->mutex); if (sem->tokenCount + count > sem->maxCount) { - return KERNEL_SEMA_ERROR_EINVAL; + return SCE_KERNEL_ERROR_EINVAL; } sem->tokenCount += count; sem->condition.NotifyAll(); @@ -72,7 +72,7 @@ int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time) sem->condition.Wait(lock, [&] { return sem->tokenCount >= need || sem->deleted; }); traceWait(false); if (sem->deleted) { - return KERNEL_SEMA_ERROR_EACCES; + return SCE_KERNEL_ERROR_EACCES; } sem->tokenCount -= need; return KERNEL_SEMA_OK; @@ -81,10 +81,10 @@ int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time) const bool acquired = sem->condition.WaitUntil(lock, TimedWait::DeadlineNanos(*time), [&] { return sem->tokenCount >= need || sem->deleted; }); traceWait(!acquired); if (sem->deleted) { - return KERNEL_SEMA_ERROR_EACCES; + return SCE_KERNEL_ERROR_EACCES; } if (!acquired) { - return KERNEL_SEMA_ERROR_ETIMEDOUT; + return SCE_KERNEL_ERROR_ETIMEDOUT; } sem->tokenCount -= need; return KERNEL_SEMA_OK; diff --git a/core/libs/tests/GuestFont.cpp b/core/libs/tests/GuestFont.cpp new file mode 100644 index 000000000..36d1fbf9c --- /dev/null +++ b/core/libs/tests/GuestFont.cpp @@ -0,0 +1,75 @@ +#include "prx/libSceFont/include/FontTypes.hpp" +#include +#include +#include + +extern "C" { +int APS5_VABI sceFontMemoryInit(FontMemory*, void*, std::uint32_t, const FontMemoryInterface*, void*, FontMemoryDestroyFunction, void*); +int APS5_VABI sceFontMemoryTerm(FontMemory*); +int APS5_VABI sceFontCreateLibrary(const FontMemory*, const void*, FontLibrary*); +int APS5_VABI sceFontDestroyLibrary(FontLibrary*); +int APS5_VABI sceFontCreateRenderer(const FontMemory*, const void*, FontRenderer*); +int APS5_VABI sceFontDestroyRenderer(FontRenderer*); +int APS5_VABI sceFontSupportSystemFonts(FontLibrary); +int APS5_VABI sceFontSupportExternalFonts(FontLibrary, std::uint32_t, std::uint32_t); +int APS5_VABI sceFontOpenFontSet(FontLibrary, std::uint32_t, std::uint32_t, const FontOpenDetail*, FontHandle*); +int APS5_VABI sceFontOpenFontMemory(FontLibrary, const void*, std::uint32_t, const FontOpenDetail*, FontHandle*); +const void* APS5_VABI sceFontSelectLibraryFt(int); +const void* APS5_VABI sceFontSelectRendererFt(int); +} + +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Font 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); +} + +int main() { + constexpr std::uint32_t SystemFontSet = 0x18070043u; + const FontMemoryInterface iface{Allocate, Release, nullptr, nullptr, nullptr, nullptr}; + FontMemory memory{}; + Require(sceFontMemoryInit(&memory, nullptr, 0, &iface, nullptr, nullptr, nullptr) == SCE_FONT_OK); + Require(sceFontSelectLibraryFt(0) != nullptr && sceFontSelectLibraryFt(1) == nullptr); + Require(sceFontSelectRendererFt(0) != nullptr && sceFontSelectRendererFt(1) == nullptr); + + FontLibrary library = nullptr; + Require(sceFontCreateLibrary(&memory, nullptr, &library) == SCE_FONT_ERROR_INVALID_PARAMETER && library == nullptr); + Require(sceFontCreateLibrary(&memory, sceFontSelectLibraryFt(0), &library) == SCE_FONT_OK && library != nullptr); + + FontHandle font = reinterpret_cast(&memory); + Require(sceFontOpenFontSet(library, SystemFontSet, 1, nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FUNCTION && font == nullptr); + Require(sceFontSupportSystemFonts(library) == SCE_FONT_OK); + Require(sceFontOpenFontSet(library, SystemFontSet, 1, nullptr, &font) == SCE_FONT_ERROR_FONT_OPEN_FAILED && font == nullptr); + Require(sceFontOpenFontSet(library, 0x12345678u, 1, nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FONTSET); + Require(sceFontOpenFontSet(library, SystemFontSet, 7, nullptr, &font) == SCE_FONT_ERROR_INVALID_PARAMETER); + 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, 4, 0x52) == SCE_FONT_OK); + Require(sceFontSupportExternalFonts(library, 4, 0x52) == SCE_FONT_ERROR_ALREADY_SPECIFIED); + Require(sceFontOpenFontMemory(library, nullptr, 0, nullptr, &font) == SCE_FONT_ERROR_INVALID_PARAMETER && font == nullptr); + Require(sceFontOpenFontMemory(library, notAFont, sizeof(notAFont), nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FORMAT && font == nullptr); + + FontRenderer renderer = nullptr; + Require(sceFontCreateRenderer(&memory, sceFontSelectRendererFt(0), &renderer) == SCE_FONT_OK && renderer != nullptr); + Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_OK && renderer == nullptr); + Require(sceFontDestroyRenderer(&renderer) == SCE_FONT_ERROR_INVALID_RENDERER); + + Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK && library == nullptr); + Require(sceFontDestroyLibrary(&library) == SCE_FONT_ERROR_INVALID_LIBRARY); + Require(allocations == 0); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_OK); + Require(sceFontMemoryTerm(&memory) == SCE_FONT_ERROR_INVALID_MEMORY); +} diff --git a/core/libs/tests/HostThreadLocal.cpp b/core/libs/tests/HostThreadLocal.cpp index 4fedf466a..0a3639bd2 100644 --- a/core/libs/tests/HostThreadLocal.cpp +++ b/core/libs/tests/HostThreadLocal.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #ifdef _WIN32 #include @@ -21,4 +23,14 @@ int main() { if (DestroyedHostThreadLocals() != 128 + (i + 1) * 2) std::abort(); } #endif + for (unsigned i = 0; i < 16; ++i) { + const auto before = DestroyedHostThreadLocals(); + std::array, 4> workers; + for (auto& worker : workers) { + worker = std::async(std::launch::async, [] { TouchHostThreadLocal(); TouchHostThreadLocal(); }); + } + for (auto& worker : workers) worker.get(); + if (DestroyedHostThreadLocals() != before + 8) std::abort(); + } + } diff --git a/core/libs/tests/Mspace.cpp b/core/libs/tests/Mspace.cpp index 2cb6143ce..3646992bd 100644 --- a/core/libs/tests/Mspace.cpp +++ b/core/libs/tests/Mspace.cpp @@ -17,6 +17,7 @@ void APS5_VABI sceLibcMspaceFree_nid_postfix(void*, void*); int APS5_VABI sceLibcMspacePosixMemalign_nid_postfix(void*, void**, std::size_t, std::size_t); std::size_t APS5_VABI sceLibcMspaceMallocUsableSize_nid_postfix(const void*); void* APS5_VABI sceLibcMspaceMemalign_nid_postfix(void*, std::size_t, std::size_t); +void* APS5_VABI sceLibcMspaceReallocalign_nid_postfix(void*, void*, std::size_t, std::size_t); int APS5_VABI sceLibcMspaceMallocStats_nid_postfix(void*, void*); int APS5_VABI sceLibcMspaceMallocStatsFast_nid_postfix(void*, void*); } @@ -79,6 +80,15 @@ int main() { void* memaligned = sceLibcMspaceMemalign_nid_postfix(arena, 256, 100); Require(memaligned && (reinterpret_cast(memaligned) & 255) == 0); Require(sceLibcMspaceMemalign_nid_postfix(arena, 24, 100) == nullptr); + unsigned char* realigned = static_cast(sceLibcMspaceReallocalign_nid_postfix(arena, nullptr, 64, 64)); + Require(realigned && (reinterpret_cast(realigned) & 63) == 0); + for (int i = 0; i < 64; ++i) realigned[i] = static_cast(i + 1); + unsigned char* regrown = static_cast(sceLibcMspaceReallocalign_nid_postfix(arena, realigned, 256, 64)); + Require(regrown && (reinterpret_cast(regrown) & 63) == 0); + for (int i = 0; i < 64; ++i) Require(regrown[i] == static_cast(i + 1)); + Require(sceLibcMspaceReallocalign_nid_postfix(arena, regrown, 16, 0) == nullptr); + Require(sceLibcMspaceReallocalign_nid_postfix(arena, regrown, 16, 3) == nullptr); + Require(sceLibcMspaceReallocalign_nid_postfix(arena, regrown, 0, 16) == nullptr); Require(sceLibcMspaceMallocStatsFast_nid_postfix(arena, &stats) == 0 && stats.currentInuseSize >= 100); sceLibcMspaceFree_nid_postfix(arena, memaligned); void* region = sceLibcMspaceMalloc_nid_postfix(arena, 8192); diff --git a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp index 2472fe258..022721a23 100644 --- a/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp +++ b/core/relinker/codegen/include/codegen/x86/X64OpcodeConstants.hpp @@ -130,6 +130,7 @@ inline constexpr std::uint8_t TwoByteModRmRangeFMin = 0x38; inline constexpr std::uint8_t TwoByteModRmRangeFMax = 0x3A; inline constexpr std::uint8_t TwoByteModRmRangeGMin = 0x54; inline constexpr std::uint8_t TwoByteModRmRangeGMax = 0x7F; +inline constexpr std::uint8_t TwoByteEmms = 0x77; inline constexpr std::uint8_t TwoByteModRmRangeHMin = 0xD0; inline constexpr std::uint8_t TwoByteModRmRangeHMax = 0xFE; inline constexpr std::uint8_t TwoByteNopModRm = 0x1F; diff --git a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp index fe0ee7ef9..462ff5c2d 100644 --- a/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp +++ b/core/relinker/codegen/src/x86/X64InstructionDecoder.cpp @@ -252,7 +252,7 @@ std::size_t X64InstructionDecoder::Decode(const std::uint8_t* data, std::size_t (opcode >= TwoByteModRmRangeCMin && opcode <= TwoByteModRmRangeCMax) || (opcode >= TwoByteModRmRangeDMin && opcode <= TwoByteModRmRangeDMax) || (opcode >= TwoByteModRmRangeEMin && opcode <= TwoByteModRmRangeEMax) || - (opcode >= TwoByteModRmRangeGMin && opcode <= TwoByteModRmRangeGMax) || + (opcode >= TwoByteModRmRangeGMin && opcode <= TwoByteModRmRangeGMax && opcode != TwoByteEmms) || (opcode >= TwoByteModRmRangeHMin && opcode <= TwoByteModRmRangeHMax) || (opcode >= TwoByteModRmRangeIMin && opcode <= TwoByteModRmRangeIMax) || (opcode >= TwoBytePrefetchGrpMin && opcode <= TwoBytePrefetchGrpMax) || diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index f36f383c3..705c3f6b0 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -36,6 +36,9 @@ int main(const int argc, char* argv[]) { } try { + auto extension = std::filesystem::path(args.outputPath).extension().string(); + for (auto& character : extension) if (character >= 'A' && character <= 'Z') character = static_cast(character + ('a' - 'A')); + if (!args.toWindows && extension == ".exe") std::cerr << "WARNING: Output filename ends with .exe, but --windows was not specified. The output will be a Linux ELF executable.\n"; Io::FileReader fileReader; Io::FileWriter fileWriter; @@ -111,6 +114,15 @@ int main(const int argc, char* argv[]) { } fileWriter.Write(absPath, executableBytes); std::cout << "External prx references: " << result.RegistryEntries.size() << "\nOutput file: " << absPath << '\n'; + std::cout << "Expected runtime layout (relative to the output executable):\n" + << std::filesystem::path(absPath).filename().string() << "\n" + << "libs/\n *.prx\napp0/\n \n"; + if (!guestArtifacts.empty()) { + std::cout << " " << guestArtifacts.front().Path.parent_path().filename().string() << "/\n"; + for (const auto& artifact : guestArtifacts) std::cout << " " << artifact.Path.filename().string() << '\n'; + } + std::cout << "Game resources and system libraries must be placed in this layout separately.\n"; + if (args.runPath != "$ORIGIN/libs") std::cout << "Custom library search path (--rpath): " << args.runPath << '\n'; if (args.autorun) return Cli::Autorun(absPath, args.toWindows); diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index 4c4c92fea..a8655e1e4 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -18,10 +18,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path const bool hasSingular = std::filesystem::exists(singular); const bool hasPlural = std::filesystem::exists(plural); if (hasSingular && hasPlural) throw Domain::RelinkerException("Both sce_module and sce_modules exist beside the input executable"); - if (!hasSingular && !hasPlural) { - std::cout << "WARNING: sce_module not found beside the input executable; it may be unnecessary or missing.\n"; - return {}; - } + if (!hasSingular && !hasPlural) throw Domain::RelinkerException("sce_module/sce_modules was not found beside the input executable: " + root.string() + ". Use --skip-sce-module only if this game can run without these modules."); const auto directory = hasSingular ? singular : plural; if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string()); std::vector paths; @@ -119,18 +116,17 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size()); Io::AppendString(dynamic.DynStrData, name); }; - const auto relativeDirectory = directory.filename().generic_string(); + const auto relativeDirectory = "app0/" + directory.filename().generic_string(); for (const auto index : order) if (!windows) addNeeded("$ORIGIN/" + relativeDirectory + "/" + images[index].OutputName); for (const auto& name : hostLibraries) addNeeded(name); std::string guestRunPath = runPath; if (!windows) { - if (guestRunPath == "$ORIGIN") guestRunPath = "$ORIGIN/.."; - else if (guestRunPath.starts_with("$ORIGIN/")) guestRunPath.insert(8, "../"); + if (guestRunPath == "$ORIGIN") guestRunPath = "$ORIGIN/../.."; + else if (guestRunPath.starts_with("$ORIGIN/")) guestRunPath.insert(8, "../../"); else if (!std::filesystem::path(guestRunPath).is_absolute()) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN"); } std::vector artifacts; - const auto destination = std::filesystem::absolute(outputPath).parent_path() / directory.filename(); - if (std::filesystem::exists(destination) && std::filesystem::equivalent(destination, directory)) throw Domain::RelinkerException("Guest output directory must differ from the source module directory"); + const auto destination = std::filesystem::absolute(outputPath).parent_path() / relativeDirectory; for (const auto index : order) { const auto& image = images[index]; const auto target = destination / image.OutputName; diff --git a/core/relinker/relinker/tests/test_empty_tls.py b/core/relinker/relinker/tests/test_empty_tls.py index aaf0c633e..c92deba32 100644 --- a/core/relinker/relinker/tests/test_empty_tls.py +++ b/core/relinker/relinker/tests/test_empty_tls.py @@ -29,7 +29,7 @@ def main(): if access: data[0x200:0x20a] = bytes.fromhex("64 48 8b 04 25 00 00 00 00 c3") source.write_bytes(data) - result = subprocess.run([str(relinker), "--windows", str(source), str(output)], + result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)], capture_output=True, text=True, timeout=20) if error: assert result.returncode == 2 and error in result.stderr and not output.exists(), result diff --git a/core/relinker/relinker/tests/test_linux_load_alignment.py b/core/relinker/relinker/tests/test_linux_load_alignment.py index 2e2d8afaf..bcb3ebaec 100644 --- a/core/relinker/relinker/tests/test_linux_load_alignment.py +++ b/core/relinker/relinker/tests/test_linux_load_alignment.py @@ -41,7 +41,11 @@ def main(): source = Path(directory) / "input.elf" output = Path(directory) / "output.elf" source.write_bytes(fixture()) - result = subprocess.run([str(relinker), str(source), str(output)], capture_output=True, text=True, timeout=20) + missing = subprocess.run([str(relinker), str(source), str(output)], capture_output=True, text=True, timeout=20) + assert missing.returncode != 0 and not output.exists(), missing + assert "sce_module/sce_modules was not found" in missing.stderr, missing.stderr + assert "--skip-sce-module only if this game" in missing.stderr, missing.stderr + result = subprocess.run([str(relinker), "--skip-sce-module", str(source), str(output)], capture_output=True, text=True, timeout=20) if result.returncode != 0: raise AssertionError((result.returncode, result.stdout, result.stderr)) segments = loads(output.read_bytes()) @@ -49,6 +53,12 @@ def main(): alignment = max(segment[7] for segment in segments) if alignment != 0x4000 or first[3] % alignment != 0 or first[7] != alignment: raise AssertionError(("first PT_LOAD is not aligned to the largest segment alignment", segments)) + assert "output.elf\nlibs/\n *.prx\napp0/" in result.stdout, result.stdout + named_exe = Path(directory) / "linux.EXE" + warning = subprocess.run([str(relinker), "--skip-sce-module", str(source), str(named_exe)], capture_output=True, text=True, timeout=20) + assert warning.returncode == 0, (warning.stdout, warning.stderr) + assert "--windows was not specified" in warning.stderr, warning.stderr + assert named_exe.read_bytes().startswith(b"\x7fELF"), "The filename must not select the output format" print("Linux load alignment test passed") diff --git a/core/relinker/relinker/tests/test_optional_plt.py b/core/relinker/relinker/tests/test_optional_plt.py index 894b47a7b..548197caa 100644 --- a/core/relinker/relinker/tests/test_optional_plt.py +++ b/core/relinker/relinker/tests/test_optional_plt.py @@ -38,7 +38,7 @@ def main(): source = work / (name + ".elf") output = work / (name + ".exe") source.write_bytes(fixture(tags)) - result = subprocess.run([str(relinker), "--windows", str(source), str(output)], + result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)], capture_output=True, text=True, timeout=20) if error is not None: if result.returncode != 2 or error not in result.stderr or output.exists(): diff --git a/core/relinker/relinker/tests/test_tls_function_coverage.py b/core/relinker/relinker/tests/test_tls_function_coverage.py index c0bc8145b..afc35a9ab 100644 --- a/core/relinker/relinker/tests/test_tls_function_coverage.py +++ b/core/relinker/relinker/tests/test_tls_function_coverage.py @@ -78,7 +78,7 @@ def main(): source = work / (name + ".elf") output = source.with_suffix(".exe") source.write_bytes(image) - result = subprocess.run([str(relinker), "--windows", str(source), str(output)], + result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(output)], capture_output=True, text=True, timeout=30) if error is not None: assert result.returncode == 2 and error in result.stderr and not output.exists(), result diff --git a/core/relinker/relinker/tests/test_windows_gui.py b/core/relinker/relinker/tests/test_windows_gui.py index 12698e829..f7dbc2b83 100644 --- a/core/relinker/relinker/tests/test_windows_gui.py +++ b/core/relinker/relinker/tests/test_windows_gui.py @@ -22,19 +22,19 @@ def main(): source.write_bytes(fixture()) cui_output = work / "cui.exe" - result = subprocess.run([str(relinker), "--windows", str(source), str(cui_output)], + result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", str(source), str(cui_output)], capture_output=True, text=True, timeout=20) assert result.returncode == 0, (result.stdout, result.stderr) assert subsystem(cui_output.read_bytes()) == 3, "expected IMAGE_SUBSYSTEM_WINDOWS_CUI by default" gui_output = work / "gui.exe" - result = subprocess.run([str(relinker), "--windows", "--windows-gui", str(source), str(gui_output)], + result = subprocess.run([str(relinker), "--skip-sce-module", "--windows", "--windows-gui", str(source), str(gui_output)], capture_output=True, text=True, timeout=20) assert result.returncode == 0, (result.stdout, result.stderr) assert subsystem(gui_output.read_bytes()) == 2, "expected IMAGE_SUBSYSTEM_WINDOWS_GUI with --windows-gui" bad_output = work / "bad.exe" - result = subprocess.run([str(relinker), "--windows-gui", str(source), str(bad_output)], + result = subprocess.run([str(relinker), "--skip-sce-module", "--windows-gui", str(source), str(bad_output)], capture_output=True, text=True, timeout=20) assert result.returncode != 0 and not bad_output.exists(), result print("Windows GUI subsystem integration tests passed") diff --git a/core/shader/recompiler/CMakeLists.txt b/core/shader/recompiler/CMakeLists.txt index e9f394071..33751b2a5 100644 --- a/core/shader/recompiler/CMakeLists.txt +++ b/core/shader/recompiler/CMakeLists.txt @@ -138,6 +138,7 @@ function(add_shader_recompiler target runtimeTarget) ) target_include_directories(${target} PUBLIC + ${PROJECT_SOURCE_DIR}/core/libs ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/RdnaDecoder/include ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/ControlFlow/include diff --git a/core/shader/recompiler/CacheKey.hpp b/core/shader/recompiler/CacheKey.hpp index 1e6fc2025..24e4b66b4 100644 --- a/core/shader/recompiler/CacheKey.hpp +++ b/core/shader/recompiler/CacheKey.hpp @@ -2,6 +2,7 @@ #define CORE_SHADER_RECOMPILER_CACHEKEY_HPP #include "Recompiler.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" #include #include #include @@ -37,7 +38,8 @@ public: // key of a source memo whose owner fixes the code (a registered shader at an offset) and the // device itself, and which is bypassed while the probe is active. static std::uint64_t ContextHash(const RecompileRequest& request) { - thread_local std::vector key; + struct ContextKeyStorage {}; + auto& key = HostThreadLocal, ContextKeyStorage>(); key.clear(); append(key, request.shader.stage); append(key, request.context.waveSize); diff --git a/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp b/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp index c9366915a..3a0cabd76 100644 --- a/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp +++ b/core/shader/recompiler/IntermediateRepresentation/src/IrValue.cpp @@ -261,7 +261,7 @@ bool IrValue::operator==(const IrValue& other) const { if (this == &other) { return true; } - if (opcode != other.opcode || type != other.type || hasImmediate != other.hasImmediate) { + if (opcode != other.opcode || type != other.type || flags != other.flags || hasImmediate != other.hasImmediate) { return false; } if (hasImmediate && immediateBits != other.immediateBits) { diff --git a/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp b/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp index ee1e7c6d6..7ea4c4a78 100644 --- a/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp +++ b/core/shader/recompiler/Optimization/src/SrtWalker/SrtDescriptorEvaluation.cpp @@ -1,5 +1,6 @@ #include "Optimization/SrtWalker/SrtDescriptorEvaluation.hpp" #include "Optimization/SrtWalker/SrtEvaluator.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" #include #include @@ -10,7 +11,10 @@ namespace ShaderRecompiler::Detail { namespace { -thread_local std::string g_failureReason; +std::string& failureReason() { + struct FailureReasonStorage {}; + return HostThreadLocal(); +} std::string DescribeValue(const IrValue* value, std::uint32_t depth) { if (value == nullptr) return "null"; @@ -27,7 +31,7 @@ std::string DescribeValue(const IrValue* value, std::uint32_t depth) { } bool Fail(std::string reason) { - g_failureReason = std::move(reason); + failureReason() = std::move(reason); return false; } @@ -41,7 +45,7 @@ const DescriptorSource* Source(const IrResourcePlan& program, std::uint32_t sour } 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) { - g_failureReason.clear(); + failureReason().clear(); static const bool debug = std::getenv("APS5_SRT_DEBUG") != nullptr; if (debug) { for (std::size_t slot = 0; slot < program.srtReads.size(); ++slot) std::fprintf(stderr, "[srt] slot %zu = %s" "\n", slot, DescribeValue(program.srtReads[slot].value, 6).c_str()); @@ -143,7 +147,7 @@ bool EvaluateRuntimeSourcesImpl(const IrResourcePlan& program, std::span getSource(const RecompileRequest& request) { static std::shared_mutex mutex; // Entries whose code hashes alike share a bucket; the code comparison picks the right one. static std::unordered_map, std::vector>, SourceKeyHash> sources; - thread_local std::vector key; + struct SourceKeyStorage {}; + auto& key = HostThreadLocal, SourceKeyStorage>(); RecompileCacheKey::Build(request, key); const auto find = [&]() -> std::shared_ptr { const auto found = sources.find(key); diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index 2aa9f3c36..3a5f8d709 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -186,6 +186,7 @@ private: bool vAlignbyteB32(const RdnaInstruction& inst); bool vLshlAddU32(const RdnaInstruction& inst); bool vAddLshlU32(const RdnaInstruction& inst); + bool vPermB32(const RdnaInstruction& inst); bool vXadU32(const RdnaInstruction& inst); bool vLshlOrB32(const RdnaInstruction& inst); bool vCndmaskB32(const RdnaInstruction& inst); diff --git a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp index 7a3dffd48..310dae951 100644 --- a/core/shader/recompiler/Translation/src/IntegerInstructions.cpp +++ b/core/shader/recompiler/Translation/src/IntegerInstructions.cpp @@ -532,6 +532,28 @@ bool TranslationContext::vAddLshlU32(const RdnaInstruction& inst) { return true; } +bool TranslationContext::vPermB32(const RdnaInstruction& inst) { + const IrU32 high = readU32(sourceAt(inst, 0u)); + const IrU32 low = readU32(sourceAt(inst, 1u)); + const IrU32 selectors = readU32(sourceAt(inst, 2u)); + IrValue* result = &ir.Constant(0u); + for (std::uint32_t byte = 0u; byte < 4u; ++byte) { + IrValue& selector = ir.BitwiseAnd(ir.ShiftRightLogical(selectors.Value(), ir.Constant(byte * 8u)), ir.Constant(255u)); + IrValue& signIndex = ir.IAdd(ir.IMul(ir.BitwiseAnd(selector, ir.Constant(3u)), ir.Constant(2u)), ir.Constant(1u)); + IrValue& index = ir.Select(ir.ULessThan(selector, ir.Constant(8u)), selector, signIndex); + IrValue& word = ir.Select(ir.ULessThan(index, ir.Constant(4u)), low.Value(), high.Value()); + IrValue& shift = ir.IMul(ir.BitwiseAnd(index, ir.Constant(3u)), ir.Constant(8u)); + IrValue& value = ir.BitwiseAnd(ir.ShiftRightLogical(word, shift), ir.Constant(255u)); + IrValue& sign = ir.Select(ir.UGreaterThan(value, ir.Constant(127u)), ir.Constant(255u), ir.Constant(0u)); + IrValue& selected = ir.Select(ir.ULessThan(selector, ir.Constant(8u)), value, sign); + IrValue& fill = ir.Select(ir.IEqual(selector, ir.Constant(12u)), ir.Constant(0u), ir.Constant(255u)); + IrValue& output = ir.Select(ir.ULessThan(selector, ir.Constant(12u)), selected, fill); + result = &ir.BitwiseOr(*result, ir.ShiftLeftLogical(output, ir.Constant(byte * 8u))); + } + writeOperand(inst.destination, result); + return true; +} + bool TranslationContext::vXadU32(const RdnaInstruction& inst) { const IrU32 lhs = readU32(sourceAt(inst, 0u)); const IrU32 rhs = readU32(sourceAt(inst, 1u)); diff --git a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp index 3515a3132..6505fea40 100644 --- a/core/shader/recompiler/Translation/src/ScalarInstructions.cpp +++ b/core/shader/recompiler/Translation/src/ScalarInstructions.cpp @@ -203,6 +203,13 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { return simpleInteger(inst, IrOpcode::BitwiseNot32, IrType::U32, false, false, true); case RdnaOpcode::SBrevB32: return simpleInteger(inst, IrOpcode::BitReverse32, IrType::U32, false, false, false); + case RdnaOpcode::SBrevB64: { + const auto source = readU32Pair(sourceAt(inst, 0u)); + const IrU32 low(ir.Emit(IrOpcode::BitReverse32, IrType::U32, {&source[1].Value()})); + const IrU32 high(ir.Emit(IrOpcode::BitReverse32, IrType::U32, {&source[0].Value()})); + writeU32Pair(inst.destination, {low, high}); + return true; + } case RdnaOpcode::SSextI32I8: case RdnaOpcode::SSextI32I16: { const auto source = readU32(sourceAt(inst, 0u)); @@ -235,6 +242,8 @@ bool TranslationContext::emitScalar(const RdnaInstruction& inst) { return simpleInteger(inst, IrOpcode::ShiftRightLogical32, IrType::U32, false, true, true); case RdnaOpcode::SAshrI32: return simpleInteger(inst, IrOpcode::ShiftRightArithmetic32, IrType::U32, false, true, true); + case RdnaOpcode::SAshrI64: + return simpleInteger(inst, IrOpcode::ShiftRightArithmetic64, IrType::U64, false, false, true); case RdnaOpcode::SLshlB64: return simpleInteger(inst, IrOpcode::ShiftLeftLogical64, IrType::U64, false, false, true); case RdnaOpcode::SLshrB64: diff --git a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp index 92fe17ef0..ecd870cdc 100644 --- a/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp +++ b/core/shader/recompiler/Translation/src/ShaderInputInfoBuilder.cpp @@ -1,4 +1,5 @@ #include "Translation/ShaderInputInfoBuilder.hpp" +#include "prx/libc/include/HostThreadLocal.hpp" #include "IntermediateRepresentation/IrMetadata.hpp" #include #include @@ -7,11 +8,6 @@ namespace ShaderRecompiler { namespace { - -thread_local ShaderPixelInputInfo pixelStorage; -thread_local ShaderComputeInputInfo computeStorage; -thread_local ShaderVertexInputInfo vertexStorage; - IrShaderStage _toIrShaderStage(ShaderStageKind stage) { switch (stage) { case ShaderStageKind::Vertex: return IrShaderStage::Vertex; @@ -74,6 +70,8 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues throw std::runtime_error("ShaderInputInfoBuilder: GuestContext.compute is not set"); } const auto& compute = *context.compute; + struct ComputeStorage {}; + auto& computeStorage = HostThreadLocal(); computeStorage = ShaderComputeInputInfo{}; computeStorage.threadsNum[0] = compute.numThreads[0]; computeStorage.threadsNum[1] = compute.numThreads[1]; @@ -97,6 +95,8 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues throw std::runtime_error("ShaderInputInfoBuilder: GuestContext.pixel is not set"); } const auto& pixel = *context.pixel; + struct PixelStorage {}; + auto& pixelStorage = HostThreadLocal(); pixelStorage = ShaderPixelInputInfo{}; for (std::uint32_t i = 0; i < 32; ++i) { pixelStorage.interpolatorSettings[i] = pixel.interpolatorSettings[i]; @@ -136,6 +136,8 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues } const auto& vertex = *context.vertex; if (vertex.resourcesNum > vertex.resources.size()) throw std::runtime_error("ShaderInputInfoBuilder: invalid vertex resource count"); + struct VertexStorage {}; + auto& vertexStorage = HostThreadLocal(); vertexStorage = ShaderVertexInputInfo{}; vertexStorage.logicalStage = _toIrShaderStage(stage); vertexStorage.fetchEmbedded = vertex.fetchEmbedded; diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 61eba45b1..ff327c922 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -673,6 +673,8 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return vLshlAddU32(inst); case RdnaOpcode::VAddLshlU32: return vAddLshlU32(inst); + case RdnaOpcode::VPermB32: + return vPermB32(inst); case RdnaOpcode::VXadU32: return vXadU32(inst); case RdnaOpcode::VLshlOrB32: diff --git a/docs/TechnicalDebt.md b/docs/TechnicalDebt.md deleted file mode 100644 index 3b2246721..000000000 --- a/docs/TechnicalDebt.md +++ /dev/null @@ -1,41 +0,0 @@ -# Project technical debt - -### Build - -- Building on Windows requires a specific version of mingw - MinGW-w64 GCC 15.2.0 (`winlibs-gcc15`, `x86_64-ucrt-posix-seh`) -- Even compiled prx libraries on Windows require nearby (static linking of these dependencies causes conflicts): - - libgcc_s_seh-1.dll - - libstdc++-6.dll - - libwinpthread-1.dll - -### Silent stubs - -Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except: -- [libSceSaveDataDialog.native](../core/libs/prx/libSceSaveDataDialog.native/Export.cpp) -- [libSceCommonDialog](../core/libs/prx/libSceCommonDialog/Export.cpp) -- The shader recompiler [skips baryctric coordinates](../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). - -### Unknown function info - -- [zARR5aCmkoY](../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature -- [qj7QZpgr9Uw](../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name -- [fd5Bp5tGTgo](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name -- [dolOmWH+huQ](../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name -- [V++UgBtQhn0](../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name -- [gQkqkLttcpw](../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature -- [sceKernelInternalMemoryGetModuleSegmentInfo](../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature -- [sceLibcInternalBacktraceForGame](../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature -- [sceLibcInternalHeapErrorReportForGame](../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature -- [__progname](../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export - -### Functional - -- [Shader recompilation](../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../core/shader/recompiler) was written completely independently from [libs/prx](../core/libs/prx). -- The executable file that [relinker](../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. -- [Relinker](../core/relinker/elfpatcher/src) doesn't add an icon to the generated executable. This should be done without adding dependencies (only standard). -- `--to-intel` does not lower the register form of INSERTQ (`F2 0F 79`) nor MONITORX/MWAITX/CLZERO/RDPRU/MCOMMIT; the [lowering](../core/relinker/codegen/src/x86/Sse4aLowering.cpp) fails the relink instead. SHA-NI is not substituted. -- The length-changing path of the [instruction rewriter](../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. -- The Linux placement of the `--to-intel` stubs in the [ELF patcher](../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test. -- The libc [SSE4a trap emulation](../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. -- `--to-intel` lowerings that need a stub section are rejected for `sce_module` guest modules: the [guest module builder](../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) does not pass stub sites to the [guest module writer](../core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp). -- The game can expect its modified prx from the `sce_module`/`sce_modules` folder - this support is not implemented. diff --git a/docs/CONVENTIONS.md b/docs/dev/CONVENTIONS.md similarity index 100% rename from docs/CONVENTIONS.md rename to docs/dev/CONVENTIONS.md diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md new file mode 100644 index 000000000..8468605e0 --- /dev/null +++ b/docs/dev/TechnicalDebt.md @@ -0,0 +1,41 @@ +# Project technical debt + +### Build + +- Building on Windows requires a specific version of mingw - MinGW-w64 GCC 15.2.0 (`winlibs-gcc15`, `x86_64-ucrt-posix-seh`) +- Even compiled prx libraries on Windows require nearby (static linking of these dependencies causes conflicts): + - libgcc_s_seh-1.dll + - libstdc++-6.dll + - libwinpthread-1.dll + +### Silent stubs + +Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except: +- [libSceSaveDataDialog.native](../../core/libs/prx/libSceSaveDataDialog.native/Export.cpp) +- [libSceCommonDialog](../../core/libs/prx/libSceCommonDialog/Export.cpp) +- The shader recompiler [skips baryctric coordinates](../../core/shader/recompiler/Recompiler.cpp) (is not even passed to SpirvTargetOptions at row 212). + +### Unknown function info + +- [zARR5aCmkoY](../../core/libs/prx/libSceAgc/DcbFlow/src/Control.cpp) (libSceAgc) - unknown name, signature +- [qj7QZpgr9Uw](../../core/libs/prx/libSceAgc/DcbState/src/ContextState.cpp) (libSceAgc) - unknown name +- [fd5Bp5tGTgo](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name +- [dolOmWH+huQ](../../core/libs/prx/libSceAgc/Misc/src/ShaderFusion.cpp) (libSceAgc) - unknown name +- [V++UgBtQhn0](../../core/libs/prx/libSceAgc/Misc/src/PacketInfo.cpp) (libSceAgc) - unknown name +- [gQkqkLttcpw](../../core/libs/prx/libSceAgc/Acb/src/Control.cpp) (libSceAgc) - unknown name, signature +- [sceKernelInternalMemoryGetModuleSegmentInfo](../../core/libs/prx/libkernel/Module/src/Module.cpp) (libkernel) - unknown signature +- [sceLibcInternalBacktraceForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature +- [sceLibcInternalHeapErrorReportForGame](../../core/libs/prx/libc/src/HeapDiagnostics.cpp) (libSceLibcInternal, implemented in libc) - unknown signature +- [__progname](../../core/libs/prx/libkernel/System/src/Process.cpp) (libkernel) - unknown data export + +### Functional + +- [Shader recompilation](../../core/shader/recompiler/Recompiler.cpp) currently occurs right before it was transferred to Vulkan with caching, but should be moved to the [relinker](../../core/relinker/main.cpp) stage. For this purpose, [shader/recompiler](../../core/shader/recompiler) was written completely independently from [libs/prx](../../core/libs/prx). +- The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. +- [Relinker](../../core/relinker/elfpatcher/src) doesn't add an icon to the generated executable. This should be done without adding dependencies (only standard). +- `--to-intel` does not lower the register form of INSERTQ (`F2 0F 79`) nor MONITORX/MWAITX/CLZERO/RDPRU/MCOMMIT; the [lowering](../../core/relinker/codegen/src/x86/Sse4aLowering.cpp) fails the relink instead. SHA-NI is not substituted. +- The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. +- The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test. +- The libc [SSE4a trap emulation](../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it. +- `--to-intel` lowerings that need a stub section are rejected for `sce_module` guest modules: the [guest module builder](../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) does not pass stub sites to the [guest module writer](../../core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp). +- The game can expect its modified prx from the `sce_module`/`sce_modules` folder - this support is not implemented. diff --git a/docs/user/COMPATIBILITY.md b/docs/user/COMPATIBILITY.md new file mode 100644 index 000000000..4b62cc293 --- /dev/null +++ b/docs/user/COMPATIBILITY.md @@ -0,0 +1,5 @@ +# Tested compatibility + +| Game | ID | Windows | Linux | GTX 1050 Ti / i5-7500 3.4GHz | Intel(R) HD Graphic 620 / i5-7200 2.5GHz | +|----------------|-----------|-------------------|-------|------------------------------|------------------------------------------| +| Dreaming Sarah | PPSA02929 | In game, playable | ? | 60 FPS | 36 FPS | diff --git a/docs/INPUT_MAPPING.md b/docs/user/INPUT_MAPPING.md similarity index 100% rename from docs/INPUT_MAPPING.md rename to docs/user/INPUT_MAPPING.md diff --git a/tools/package_release.py b/tools/package_release.py new file mode 100644 index 000000000..25296672b --- /dev/null +++ b/tools/package_release.py @@ -0,0 +1,71 @@ +import argparse +import re +import shutil +import tarfile +import zipfile +from pathlib import Path + + +def package(platform, build, output, version): + if not re.fullmatch(r"v[0-9A-Za-z][0-9A-Za-z._-]*", version) or version.endswith("."): + raise ValueError(f"Invalid release tag for asset filenames: {version}") + libraries = sorted((build / "core/libs/libs").glob("*.prx")) + expected = {f"{directory.name}.prx" for directory in Path("core/libs/prx").iterdir() if directory.is_dir()} + expected.add("libcohtml.Prospero.prx") + missing = expected - {library.name for library in libraries} + if missing: + raise RuntimeError(f"Missing patched libraries: {', '.join(sorted(missing))}") + executable = "relinker.exe" if platform == "windows" else "relinker" + files = list(libraries) + if platform == "windows": + runtime = Path("C:/winlibs/mingw64/bin") + files.extend(runtime / name for name in ("libgcc_s_seh-1.dll", "libstdc++-6.dll", "libwinpthread-1.dll")) + binary = build / "core/relinker" / executable + for file in [*files, binary]: + if not file.is_file() or file.stat().st_size == 0: + raise RuntimeError(f"Missing or empty release file: {file}") + output.mkdir(parents=True, exist_ok=True) + with zipfile.ZipFile(output / f"prx-{platform}-{version}.zip", "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as archive: + for file in files: + archive.write(file, arcname=f"libs/{file.name}") + with tarfile.open(output / f"prx-{platform}-{version}.tar.gz", "w:gz", compresslevel=9) as archive: + for file in files: + archive.add(file, arcname=f"libs/{file.name}") + asset = f"relinker-{version}.exe" if platform == "windows" else f"relinker-{version}" + shutil.copy2(binary, output / asset) + + +def collect_docs(source, output): + documents = sorted(source.rglob("*.md")) + if not documents: + raise RuntimeError(f"No Markdown documents found in {source}") + names = set() + for document in documents: + if document.name in names or (output / document.name).exists(): + raise RuntimeError(f"Duplicate release asset name: {document.name}") + names.add(document.name) + output.mkdir(parents=True, exist_ok=True) + for document in documents: + shutil.copy2(document, output / document.name) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--platform", choices=("linux", "windows")) + parser.add_argument("--build", type=Path) + parser.add_argument("--version") + parser.add_argument("--docs", type=Path) + parser.add_argument("--output", required=True, type=Path) + args = parser.parse_args() + if args.docs is not None: + if args.platform is not None or args.build is not None or args.version is not None: + parser.error("--docs cannot be combined with --platform, --build or --version") + collect_docs(args.docs, args.output) + else: + if args.platform is None or args.build is None or args.version is None: + parser.error("--platform, --build and --version are required when --docs is not specified") + package(args.platform, args.build, args.output, args.version) + + +if __name__ == "__main__": + main() diff --git a/tools/progress.py b/tools/progress.py new file mode 100644 index 000000000..2af716e40 --- /dev/null +++ b/tools/progress.py @@ -0,0 +1,298 @@ +import argparse +import json +import os +import re +from html import escape +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +PRX = ROOT / "core" / "libs" / "prx" +OPCODES = ROOT / "core" / "shader" / "recompiler" / "RdnaDecoder" / "include" / "RdnaDecoder" / "RdnaOpcode.hpp" +ISA = Path(__file__).resolve().parent / "rdna_isa.txt" +SOURCE = f'https://github.com/{os.environ.get("GITHUB_REPOSITORY", "boykopovar/AnyPS5")}/blob/main' +DEFINITION = re.compile(r"\bAPS5_VABI\s+(\w+)\s*\([^;{]*\)\s*(?:noexcept\s*)?\{") +STUB = "NotImplemented_nid_no_patch" +FLAT_SEGMENTS = ("GLOBAL_", "SCRATCH_") +OPCODE_SENTINELS = {"Invalid", "Count", "Unknown", "Unsupported"} +OPCODE_ALIASES = { + "TBufferLoadFormatX": "TBUFFER_LOAD_FORMAT_X", + "TBufferLoadFormatXyzw": "TBUFFER_LOAD_FORMAT_XYZW", + "ExpMrt": "EXP", + "ExpPos": "EXP", + "ExpParam": "EXP", + "VAddcU32": "V_ADD_CO_CI_U32", + "VMadMixloF16": "V_FMA_MIXLO_F16", + "VMadMixhiF16": "V_FMA_MIXHI_F16", +} +REPORT_ROWS = 100 +PANEL_WIDTH, GAP, MAP_HEIGHT, HEADER = 495, 10, 280, 30 +DONE_COLOR, TODO_COLOR, BORDER, TEXT = "#2ea043", "#6e7681", "#0d1117", "#ffffff" + + +def body_end(text, start): + depth = 0 + for i in range(start, len(text)): + if text[i] == "{": + depth += 1 + elif text[i] == "}": + depth -= 1 + if depth == 0: + return i + return len(text) + + +def scan_library(path): + done, todo = set(), set() + for source in path.rglob("*.cpp"): + text = source.read_text(errors="ignore") + for match in DEFINITION.finditer(text): + name = match.group(1) + if name.endswith("_nid_no_patch"): + continue + body = text[match.end() - 1:body_end(text, match.end() - 1)] + (todo if STUB in body else done).add(name) + todo -= done + return {"name": path.name, "label": path.name.removeprefix("libSce"), "done": len(done), "todo": len(todo), + "done_names": sorted(done), "todo_names": sorted(todo)} + + +def summarize(groups): + done = sum(g["done"] for g in groups) + total = done + sum(g["todo"] for g in groups) + return {"done": done, "total": total, "percent": round(100 * done / total, 2) if total else 0, "groups": groups} + + +def collect_libraries(): + return summarize([scan_library(p) for p in sorted(PRX.iterdir()) if p.is_dir()]) + + +def camel(name): + return "".join(part.capitalize() for part in name.split("_")) + + +def collect_shaders(): + isa = {} + for line in ISA.read_text().splitlines(): + if line and not line.startswith("#"): + name, encoding = line.split() + isa[name] = encoding + by_camel = {camel(name): name for name in isa} + enum = re.search(r"enum class RdnaOpcode[^{]*\{(.*?)\};", OPCODES.read_text(), re.S).group(1) + opcodes = [o for o in re.findall(r"^\s*([A-Z]\w*)\s*[,=]", enum, re.M) if o not in OPCODE_SENTINELS] + supported, extra = set(), [] + for opcode in opcodes: + name = OPCODE_ALIASES.get(opcode) or by_camel.get(opcode) + if name in isa: + supported.add(name) + if name.startswith("FLAT_"): + supported.update(n for n in (s + name.removeprefix("FLAT_") for s in FLAT_SEGMENTS) if n in isa) + else: + extra.append(opcode) + groups = {} + for name, encoding in isa.items(): + group = groups.setdefault(encoding, {"name": encoding, "label": encoding, "done": 0, "todo": 0, + "done_names": [], "todo_names": []}) + state = "done" if name in supported else "todo" + group[state] += 1 + group[f"{state}_names"].append(name) + result = summarize(sorted(groups.values(), key=lambda g: g["name"])) + result["extra"] = extra + return result + + +def worst_ratio(row, side): + area = sum(row) + return max(max(side * side * r / area ** 2, area ** 2 / (side * side * r)) for r in row) + + +def place(row, x, y, w, h, rects): + thickness = sum(row) / min(w, h) + offset = 0 + for area in row: + length = area / thickness + if w >= h: + rects.append((x, y + offset, thickness, length)) + else: + rects.append((x + offset, y, length, thickness)) + offset += length + return (x + thickness, y, w - thickness, h) if w >= h else (x, y + thickness, w, h - thickness) + + +def squarify(values, x, y, w, h): + total = sum(values) + areas = [v * w * h / total for v in values] + rects, row = [], [] + while areas: + side = min(w, h) + if not row or worst_ratio(row + [areas[0]], side) <= worst_ratio(row, side): + row.append(areas.pop(0)) + continue + x, y, w, h = place(row, x, y, w, h, rects) + row = [] + if row: + place(row, x, y, w, h, rects) + return rects + + +def cells(count, x, y, w, h): + if not count: + return [] + rows = max(1, min(count, round((count * h / w) ** 0.5))) + result = [] + for r in range(rows): + in_row = count // rows + (r < count % rows) + cw = w / in_row + result += [(x + c * cw, y + r * h / rows, cw, h / rows) for c in range(in_row)] + return result + + +def rect(x, y, w, h, color, stroke=0.5): + return (f'') + + +def text(x, y, value, size): + return (f'{escape(value)}') + + +def treemap(title, data, left): + groups = sorted((g for g in data["groups"] if g["done"] + g["todo"]), key=lambda g: -(g["done"] + g["todo"])) + parts = [text(left + 4, 21, f'{title}: {data["percent"]}% ({data["done"]}/{data["total"]})', 16)] + for group, (x, y, w, h) in zip(groups, squarify([g["done"] + g["todo"] for g in groups], left, HEADER, PANEL_WIDTH, MAP_HEIGHT)): + total = group["done"] + group["todo"] + parts.append(f'{escape(group["name"])}: {group["done"]}/{total} ({100 * group["done"] / total:.0f}%)') + for i, cell in enumerate(cells(total, x + 1, y + 1, w - 2, h - 2)): + parts.append(rect(*cell, DONE_COLOR if i < group["done"] else TODO_COLOR)) + parts.append(f'') + label = group["label"][:int((w - 10) / 7)] + if (label == group["label"] or len(label) >= 3) and h > 22: + parts.append(text(x + 5, y + 16, label, 12)) + parts.append("") + return parts + + +def render(libraries, shaders): + width, height = 2 * PANEL_WIDTH + GAP, HEADER + MAP_HEIGHT + parts = [f'', + rect(0, 0, width, height, BORDER, 0)] + parts += treemap("System libraries*", libraries, 0) + parts += treemap("GPU shader instructions", shaders, PANEL_WIDTH + GAP) + parts.append("") + return "\n".join(parts) + + +def badge(label, data): + percent = data["percent"] + color = "#4c1" if percent >= 90 else "#97ca00" if percent >= 60 else "#dfb317" if percent >= 30 else "#fe7d37" + value = f"{percent}%" + left, right = 10 + 7 * len(label), 10 + 7 * len(value) + width = left + right + return (f'' + f'' + f'' + f'{label}{value}') + + +def table(heading, column, data): + rows = [f"

{heading}

", "", + f""] + for group in sorted(data["groups"], key=lambda g: g["name"].lower()): + total = group["done"] + group["todo"] + percent = f'{100 * group["done"] / total:.0f}%' if total else "-" + rows.append(f'') + rows.append(f'') + rows.append("
{column}ImplementedTotal%
{escape(group["name"])}{group["done"]}{total}{percent}
Total{data["done"]}{data["total"]}{data["percent"]}%
") + return "\n".join(rows) + + +def summary(libraries, shaders): + extra = ", ".join(f"{escape(o)}" for o in shaders["extra"]) + return "\n".join([ + '', + '', + 'AnyPS5 progress', + '', + '', + "

Progress

", + 'progress', + f'

Generated by tools/progress.py on every push to main. ' + 'Raw numbers: progress.json.

', + table("System libraries", "Library", libraries), + "

A function is implemented when it no longer calls NotImplemented_nid_no_patch. " + f'The total only includes functions already declared in core/libs/prx, ' + "not every function exported by the PS5 firmware.

", + table("GPU shader instructions", "Encoding", shaders), + f'

The total is the AMD RDNA 1 + RDNA 2 instruction list (tools/rdna_isa.txt). ' + f'An instruction is implemented when the decoder recognizes it. ' + f"Decoded opcodes not present in AMD's public list are not counted: {extra}.

", + "", + ]) + "\n" + + +def names(data, state): + return {(g["name"], n) for g in data["groups"] for n in g.get(f"{state}_names", [])} + + +def details(icon, title, column, items): + if not items: + return [] + rows = [f"
\n{icon} {len(items)} {title}\n", f"| {column} | Name |", "| - | - |"] + rows += [f"| {group} | `{name}` |" for group, name in sorted(items)[:REPORT_ROWS]] + if len(items) > REPORT_ROWS: + rows.append(f"| ... | {len(items) - REPORT_ROWS} more |") + return rows + ["\n
"] + + +def compare(title, column, unit, base, head): + base_done, head_done = names(base, "done"), names(head, "done") + base_all, head_all = base_done | names(base, "todo"), head_done | names(head, "todo") + implemented, declared = head_done - base_done, head_all - base_all - head_done + regressed, removed = base_done & (head_all - head_done), base_all - head_all + if not (implemented or declared or regressed or removed): + return [] + delta = round(head["percent"] - base["percent"], 2) + icon = "📈" if delta > 0 else "📉" if delta < 0 else "➖" + counts = [f"{n:+} {label}" for n, label in ((len(implemented), "implemented"), (len(declared), "declared"), + (-len(removed), "removed")) if n] + lines = [f'{icon} **{title}**: {head["percent"]}% ({delta:+}%, {", ".join(counts)} {unit})', ""] + lines += details("✅", "implemented", column, implemented) + lines += details("🆕", "declared as stubs", column, declared) + lines += details("⚠️", "went back to stubs", column, regressed) + lines += details("🗑️", "removed", column, removed) + return lines + [""] + + +def report(base, head): + lines = compare("System libraries", "Library", "functions", base["libraries"], head["libraries"]) + lines += compare("GPU shader instructions", "Encoding", "instructions", base["shaders"], head["shaders"]) + return "\n".join(lines) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("output", type=Path, nargs="?") + parser.add_argument("--root", type=Path, help="source tree to measure instead of the one containing this script") + parser.add_argument("--compare", type=Path, nargs=2, metavar=("BASE", "HEAD"), + help="print a markdown report of the changes between two progress.json files") + args = parser.parse_args() + if args.compare: + base, head = (json.loads(path.read_text()) for path in args.compare) + print(report(base, head), end="") + raise SystemExit + if not args.output: + parser.error("the output directory is required") + if args.root: + PRX = args.root / "core" / "libs" / "prx" + OPCODES = args.root / OPCODES.relative_to(ROOT) + output = args.output + output.mkdir(parents=True, exist_ok=True) + libraries, shaders = collect_libraries(), collect_shaders() + (output / "progress.json").write_text(json.dumps({"libraries": libraries, "shaders": shaders}, indent=2)) + (output / "badge-libraries.svg").write_text(badge("libraries*", libraries)) + (output / "badge-shaders.svg").write_text(badge("shaders", shaders)) + (output / "progress.svg").write_text(render(libraries, shaders)) + (output / "index.html").write_text(summary(libraries, shaders)) + print(f'libraries {libraries["done"]}/{libraries["total"]} ({libraries["percent"]}%)') + print(f'shaders {shaders["done"]}/{shaders["total"]} ({shaders["percent"]}%)') diff --git a/tools/rdna_isa.txt b/tools/rdna_isa.txt new file mode 100644 index 000000000..1d854a6c3 --- /dev/null +++ b/tools/rdna_isa.txt @@ -0,0 +1,1169 @@ +# AMD RDNA 1 + RDNA 2 instruction list (name, primary encoding). +# Source: AMD Machine-Readable GPU ISA Specification, https://gpuopen.com/machine-readable-isa/ +# Copyright (c) 2026 Advanced Micro Devices, Inc., or its affiliates. MIT License. +BUFFER_ATOMIC_ADD MUBUF +BUFFER_ATOMIC_ADD_X2 MUBUF +BUFFER_ATOMIC_AND MUBUF +BUFFER_ATOMIC_AND_X2 MUBUF +BUFFER_ATOMIC_CMPSWAP MUBUF +BUFFER_ATOMIC_CMPSWAP_X2 MUBUF +BUFFER_ATOMIC_CSUB MUBUF +BUFFER_ATOMIC_DEC MUBUF +BUFFER_ATOMIC_DEC_X2 MUBUF +BUFFER_ATOMIC_FCMPSWAP MUBUF +BUFFER_ATOMIC_FCMPSWAP_X2 MUBUF +BUFFER_ATOMIC_FMAX MUBUF +BUFFER_ATOMIC_FMAX_X2 MUBUF +BUFFER_ATOMIC_FMIN MUBUF +BUFFER_ATOMIC_FMIN_X2 MUBUF +BUFFER_ATOMIC_INC MUBUF +BUFFER_ATOMIC_INC_X2 MUBUF +BUFFER_ATOMIC_OR MUBUF +BUFFER_ATOMIC_OR_X2 MUBUF +BUFFER_ATOMIC_SMAX MUBUF +BUFFER_ATOMIC_SMAX_X2 MUBUF +BUFFER_ATOMIC_SMIN MUBUF +BUFFER_ATOMIC_SMIN_X2 MUBUF +BUFFER_ATOMIC_SUB MUBUF +BUFFER_ATOMIC_SUB_X2 MUBUF +BUFFER_ATOMIC_SWAP MUBUF +BUFFER_ATOMIC_SWAP_X2 MUBUF +BUFFER_ATOMIC_UMAX MUBUF +BUFFER_ATOMIC_UMAX_X2 MUBUF +BUFFER_ATOMIC_UMIN MUBUF +BUFFER_ATOMIC_UMIN_X2 MUBUF +BUFFER_ATOMIC_XOR MUBUF +BUFFER_ATOMIC_XOR_X2 MUBUF +BUFFER_GL0_INV MUBUF +BUFFER_GL1_INV MUBUF +BUFFER_LOAD_DWORD MUBUF +BUFFER_LOAD_DWORDX2 MUBUF +BUFFER_LOAD_DWORDX3 MUBUF +BUFFER_LOAD_DWORDX4 MUBUF +BUFFER_LOAD_FORMAT_D16_HI_X MUBUF +BUFFER_LOAD_FORMAT_D16_X MUBUF +BUFFER_LOAD_FORMAT_D16_XY MUBUF +BUFFER_LOAD_FORMAT_D16_XYZ MUBUF +BUFFER_LOAD_FORMAT_D16_XYZW MUBUF +BUFFER_LOAD_FORMAT_X MUBUF +BUFFER_LOAD_FORMAT_XY MUBUF +BUFFER_LOAD_FORMAT_XYZ MUBUF +BUFFER_LOAD_FORMAT_XYZW MUBUF +BUFFER_LOAD_SBYTE MUBUF +BUFFER_LOAD_SBYTE_D16 MUBUF +BUFFER_LOAD_SBYTE_D16_HI MUBUF +BUFFER_LOAD_SHORT_D16 MUBUF +BUFFER_LOAD_SHORT_D16_HI MUBUF +BUFFER_LOAD_SSHORT MUBUF +BUFFER_LOAD_UBYTE MUBUF +BUFFER_LOAD_UBYTE_D16 MUBUF +BUFFER_LOAD_UBYTE_D16_HI MUBUF +BUFFER_LOAD_USHORT MUBUF +BUFFER_STORE_BYTE MUBUF +BUFFER_STORE_BYTE_D16_HI MUBUF +BUFFER_STORE_DWORD MUBUF +BUFFER_STORE_DWORDX2 MUBUF +BUFFER_STORE_DWORDX3 MUBUF +BUFFER_STORE_DWORDX4 MUBUF +BUFFER_STORE_FORMAT_D16_HI_X MUBUF +BUFFER_STORE_FORMAT_D16_X MUBUF +BUFFER_STORE_FORMAT_D16_XY MUBUF +BUFFER_STORE_FORMAT_D16_XYZ MUBUF +BUFFER_STORE_FORMAT_D16_XYZW MUBUF +BUFFER_STORE_FORMAT_X MUBUF +BUFFER_STORE_FORMAT_XY MUBUF +BUFFER_STORE_FORMAT_XYZ MUBUF +BUFFER_STORE_FORMAT_XYZW MUBUF +BUFFER_STORE_SHORT MUBUF +BUFFER_STORE_SHORT_D16_HI MUBUF +DS_ADD_F32 DS +DS_ADD_RTN_F32 DS +DS_ADD_RTN_U32 DS +DS_ADD_RTN_U64 DS +DS_ADD_U32 DS +DS_ADD_U64 DS +DS_AND_B32 DS +DS_AND_B64 DS +DS_AND_RTN_B32 DS +DS_AND_RTN_B64 DS +DS_APPEND DS +DS_BPERMUTE_B32 DS +DS_CMPST_B32 DS +DS_CMPST_B64 DS +DS_CMPST_F32 DS +DS_CMPST_F64 DS +DS_CMPST_RTN_B32 DS +DS_CMPST_RTN_B64 DS +DS_CMPST_RTN_F32 DS +DS_CMPST_RTN_F64 DS +DS_CONDXCHG32_RTN_B64 DS +DS_CONSUME DS +DS_DEC_RTN_U32 DS +DS_DEC_RTN_U64 DS +DS_DEC_U32 DS +DS_DEC_U64 DS +DS_GWS_BARRIER DS +DS_GWS_INIT DS +DS_GWS_SEMA_BR DS +DS_GWS_SEMA_P DS +DS_GWS_SEMA_RELEASE_ALL DS +DS_GWS_SEMA_V DS +DS_INC_RTN_U32 DS +DS_INC_RTN_U64 DS +DS_INC_U32 DS +DS_INC_U64 DS +DS_MAX_F32 DS +DS_MAX_F64 DS +DS_MAX_I32 DS +DS_MAX_I64 DS +DS_MAX_RTN_F32 DS +DS_MAX_RTN_F64 DS +DS_MAX_RTN_I32 DS +DS_MAX_RTN_I64 DS +DS_MAX_RTN_U32 DS +DS_MAX_RTN_U64 DS +DS_MAX_U32 DS +DS_MAX_U64 DS +DS_MIN_F32 DS +DS_MIN_F64 DS +DS_MIN_I32 DS +DS_MIN_I64 DS +DS_MIN_RTN_F32 DS +DS_MIN_RTN_F64 DS +DS_MIN_RTN_I32 DS +DS_MIN_RTN_I64 DS +DS_MIN_RTN_U32 DS +DS_MIN_RTN_U64 DS +DS_MIN_U32 DS +DS_MIN_U64 DS +DS_MSKOR_B32 DS +DS_MSKOR_B64 DS +DS_MSKOR_RTN_B32 DS +DS_MSKOR_RTN_B64 DS +DS_NOP DS +DS_ORDERED_COUNT DS +DS_OR_B32 DS +DS_OR_B64 DS +DS_OR_RTN_B32 DS +DS_OR_RTN_B64 DS +DS_PERMUTE_B32 DS +DS_READ2ST64_B32 DS +DS_READ2ST64_B64 DS +DS_READ2_B32 DS +DS_READ2_B64 DS +DS_READ_ADDTID_B32 DS +DS_READ_B128 DS +DS_READ_B32 DS +DS_READ_B64 DS +DS_READ_B96 DS +DS_READ_I16 DS +DS_READ_I8 DS +DS_READ_I8_D16 DS +DS_READ_I8_D16_HI DS +DS_READ_U16 DS +DS_READ_U16_D16 DS +DS_READ_U16_D16_HI DS +DS_READ_U8 DS +DS_READ_U8_D16 DS +DS_READ_U8_D16_HI DS +DS_RSUB_RTN_U32 DS +DS_RSUB_RTN_U64 DS +DS_RSUB_U32 DS +DS_RSUB_U64 DS +DS_SUB_RTN_U32 DS +DS_SUB_RTN_U64 DS +DS_SUB_U32 DS +DS_SUB_U64 DS +DS_SWIZZLE_B32 DS +DS_WRAP_RTN_B32 DS +DS_WRITE2ST64_B32 DS +DS_WRITE2ST64_B64 DS +DS_WRITE2_B32 DS +DS_WRITE2_B64 DS +DS_WRITE_ADDTID_B32 DS +DS_WRITE_B128 DS +DS_WRITE_B16 DS +DS_WRITE_B16_D16_HI DS +DS_WRITE_B32 DS +DS_WRITE_B64 DS +DS_WRITE_B8 DS +DS_WRITE_B8_D16_HI DS +DS_WRITE_B96 DS +DS_WRXCHG2ST64_RTN_B32 DS +DS_WRXCHG2ST64_RTN_B64 DS +DS_WRXCHG2_RTN_B32 DS +DS_WRXCHG2_RTN_B64 DS +DS_WRXCHG_RTN_B32 DS +DS_WRXCHG_RTN_B64 DS +DS_XOR_B32 DS +DS_XOR_B64 DS +DS_XOR_RTN_B32 DS +DS_XOR_RTN_B64 DS +EXP EXP +FLAT_ATOMIC_ADD FLAT +FLAT_ATOMIC_ADD_X2 FLAT +FLAT_ATOMIC_AND FLAT +FLAT_ATOMIC_AND_X2 FLAT +FLAT_ATOMIC_CMPSWAP FLAT +FLAT_ATOMIC_CMPSWAP_X2 FLAT +FLAT_ATOMIC_DEC FLAT +FLAT_ATOMIC_DEC_X2 FLAT +FLAT_ATOMIC_FCMPSWAP FLAT +FLAT_ATOMIC_FCMPSWAP_X2 FLAT +FLAT_ATOMIC_FMAX FLAT +FLAT_ATOMIC_FMAX_X2 FLAT +FLAT_ATOMIC_FMIN FLAT +FLAT_ATOMIC_FMIN_X2 FLAT +FLAT_ATOMIC_INC FLAT +FLAT_ATOMIC_INC_X2 FLAT +FLAT_ATOMIC_OR FLAT +FLAT_ATOMIC_OR_X2 FLAT +FLAT_ATOMIC_SMAX FLAT +FLAT_ATOMIC_SMAX_X2 FLAT +FLAT_ATOMIC_SMIN FLAT +FLAT_ATOMIC_SMIN_X2 FLAT +FLAT_ATOMIC_SUB FLAT +FLAT_ATOMIC_SUB_X2 FLAT +FLAT_ATOMIC_SWAP FLAT +FLAT_ATOMIC_SWAP_X2 FLAT +FLAT_ATOMIC_UMAX FLAT +FLAT_ATOMIC_UMAX_X2 FLAT +FLAT_ATOMIC_UMIN FLAT +FLAT_ATOMIC_UMIN_X2 FLAT +FLAT_ATOMIC_XOR FLAT +FLAT_ATOMIC_XOR_X2 FLAT +FLAT_LOAD_DWORD FLAT +FLAT_LOAD_DWORDX2 FLAT +FLAT_LOAD_DWORDX3 FLAT +FLAT_LOAD_DWORDX4 FLAT +FLAT_LOAD_SBYTE FLAT +FLAT_LOAD_SBYTE_D16 FLAT +FLAT_LOAD_SBYTE_D16_HI FLAT +FLAT_LOAD_SHORT_D16 FLAT +FLAT_LOAD_SHORT_D16_HI FLAT +FLAT_LOAD_SSHORT FLAT +FLAT_LOAD_UBYTE FLAT +FLAT_LOAD_UBYTE_D16 FLAT +FLAT_LOAD_UBYTE_D16_HI FLAT +FLAT_LOAD_USHORT FLAT +FLAT_STORE_BYTE FLAT +FLAT_STORE_BYTE_D16_HI FLAT +FLAT_STORE_DWORD FLAT +FLAT_STORE_DWORDX2 FLAT +FLAT_STORE_DWORDX3 FLAT +FLAT_STORE_DWORDX4 FLAT +FLAT_STORE_SHORT FLAT +FLAT_STORE_SHORT_D16_HI FLAT +GLOBAL_ATOMIC_ADD GLOBAL +GLOBAL_ATOMIC_ADD_X2 GLOBAL +GLOBAL_ATOMIC_AND GLOBAL +GLOBAL_ATOMIC_AND_X2 GLOBAL +GLOBAL_ATOMIC_CMPSWAP GLOBAL +GLOBAL_ATOMIC_CMPSWAP_X2 GLOBAL +GLOBAL_ATOMIC_CSUB GLOBAL +GLOBAL_ATOMIC_DEC GLOBAL +GLOBAL_ATOMIC_DEC_X2 GLOBAL +GLOBAL_ATOMIC_FCMPSWAP GLOBAL +GLOBAL_ATOMIC_FCMPSWAP_X2 GLOBAL +GLOBAL_ATOMIC_FMAX GLOBAL +GLOBAL_ATOMIC_FMAX_X2 GLOBAL +GLOBAL_ATOMIC_FMIN GLOBAL +GLOBAL_ATOMIC_FMIN_X2 GLOBAL +GLOBAL_ATOMIC_INC GLOBAL +GLOBAL_ATOMIC_INC_X2 GLOBAL +GLOBAL_ATOMIC_OR GLOBAL +GLOBAL_ATOMIC_OR_X2 GLOBAL +GLOBAL_ATOMIC_SMAX GLOBAL +GLOBAL_ATOMIC_SMAX_X2 GLOBAL +GLOBAL_ATOMIC_SMIN GLOBAL +GLOBAL_ATOMIC_SMIN_X2 GLOBAL +GLOBAL_ATOMIC_SUB GLOBAL +GLOBAL_ATOMIC_SUB_X2 GLOBAL +GLOBAL_ATOMIC_SWAP GLOBAL +GLOBAL_ATOMIC_SWAP_X2 GLOBAL +GLOBAL_ATOMIC_UMAX GLOBAL +GLOBAL_ATOMIC_UMAX_X2 GLOBAL +GLOBAL_ATOMIC_UMIN GLOBAL +GLOBAL_ATOMIC_UMIN_X2 GLOBAL +GLOBAL_ATOMIC_XOR GLOBAL +GLOBAL_ATOMIC_XOR_X2 GLOBAL +GLOBAL_LOAD_DWORD GLOBAL +GLOBAL_LOAD_DWORDX2 GLOBAL +GLOBAL_LOAD_DWORDX3 GLOBAL +GLOBAL_LOAD_DWORDX4 GLOBAL +GLOBAL_LOAD_DWORD_ADDTID GLOBAL +GLOBAL_LOAD_SBYTE GLOBAL +GLOBAL_LOAD_SBYTE_D16 GLOBAL +GLOBAL_LOAD_SBYTE_D16_HI GLOBAL +GLOBAL_LOAD_SHORT_D16 GLOBAL +GLOBAL_LOAD_SHORT_D16_HI GLOBAL +GLOBAL_LOAD_SSHORT GLOBAL +GLOBAL_LOAD_UBYTE GLOBAL +GLOBAL_LOAD_UBYTE_D16 GLOBAL +GLOBAL_LOAD_UBYTE_D16_HI GLOBAL +GLOBAL_LOAD_USHORT GLOBAL +GLOBAL_STORE_BYTE GLOBAL +GLOBAL_STORE_BYTE_D16_HI GLOBAL +GLOBAL_STORE_DWORD GLOBAL +GLOBAL_STORE_DWORDX2 GLOBAL +GLOBAL_STORE_DWORDX3 GLOBAL +GLOBAL_STORE_DWORDX4 GLOBAL +GLOBAL_STORE_DWORD_ADDTID GLOBAL +GLOBAL_STORE_SHORT GLOBAL +GLOBAL_STORE_SHORT_D16_HI GLOBAL +IMAGE_ATOMIC_ADD MIMG +IMAGE_ATOMIC_AND MIMG +IMAGE_ATOMIC_CMPSWAP MIMG +IMAGE_ATOMIC_DEC MIMG +IMAGE_ATOMIC_FCMPSWAP MIMG +IMAGE_ATOMIC_FMAX MIMG +IMAGE_ATOMIC_FMIN MIMG +IMAGE_ATOMIC_INC MIMG +IMAGE_ATOMIC_OR MIMG +IMAGE_ATOMIC_SMAX MIMG +IMAGE_ATOMIC_SMIN MIMG +IMAGE_ATOMIC_SUB MIMG +IMAGE_ATOMIC_SWAP MIMG +IMAGE_ATOMIC_UMAX MIMG +IMAGE_ATOMIC_UMIN MIMG +IMAGE_ATOMIC_XOR MIMG +IMAGE_BVH64_INTERSECT_RAY MIMG +IMAGE_BVH_INTERSECT_RAY MIMG +IMAGE_GATHER4 MIMG +IMAGE_GATHER4H MIMG +IMAGE_GATHER4H_PCK MIMG +IMAGE_GATHER4_B MIMG +IMAGE_GATHER4_B_CL MIMG +IMAGE_GATHER4_B_CL_O MIMG +IMAGE_GATHER4_B_O MIMG +IMAGE_GATHER4_C MIMG +IMAGE_GATHER4_CL MIMG +IMAGE_GATHER4_CL_O MIMG +IMAGE_GATHER4_C_B MIMG +IMAGE_GATHER4_C_B_CL MIMG +IMAGE_GATHER4_C_B_CL_O MIMG +IMAGE_GATHER4_C_B_O MIMG +IMAGE_GATHER4_C_CL MIMG +IMAGE_GATHER4_C_CL_O MIMG +IMAGE_GATHER4_C_L MIMG +IMAGE_GATHER4_C_LZ MIMG +IMAGE_GATHER4_C_LZ_O MIMG +IMAGE_GATHER4_C_L_O MIMG +IMAGE_GATHER4_C_O MIMG +IMAGE_GATHER4_L MIMG +IMAGE_GATHER4_LZ MIMG +IMAGE_GATHER4_LZ_O MIMG +IMAGE_GATHER4_L_O MIMG +IMAGE_GATHER4_O MIMG +IMAGE_GATHER8H_PCK MIMG +IMAGE_GET_LOD MIMG +IMAGE_GET_RESINFO MIMG +IMAGE_LOAD MIMG +IMAGE_LOAD_BY2 MIMG +IMAGE_LOAD_BY4 MIMG +IMAGE_LOAD_MIP MIMG +IMAGE_LOAD_MIP_BY2 MIMG +IMAGE_LOAD_MIP_BY4 MIMG +IMAGE_LOAD_MIP_PCK MIMG +IMAGE_LOAD_MIP_PCK2 MIMG +IMAGE_LOAD_MIP_PCK4 MIMG +IMAGE_LOAD_MIP_PCK_SGN MIMG +IMAGE_LOAD_PCK MIMG +IMAGE_LOAD_PCK2 MIMG +IMAGE_LOAD_PCK4 MIMG +IMAGE_LOAD_PCK_SGN MIMG +IMAGE_MSAA_LOAD MIMG +IMAGE_SAMPLE MIMG +IMAGE_SAMPLE_B MIMG +IMAGE_SAMPLE_B_CL MIMG +IMAGE_SAMPLE_B_CL_O MIMG +IMAGE_SAMPLE_B_O MIMG +IMAGE_SAMPLE_C MIMG +IMAGE_SAMPLE_CD MIMG +IMAGE_SAMPLE_CD_CL MIMG +IMAGE_SAMPLE_CD_CL_G16 MIMG +IMAGE_SAMPLE_CD_CL_O MIMG +IMAGE_SAMPLE_CD_CL_O_G16 MIMG +IMAGE_SAMPLE_CD_G16 MIMG +IMAGE_SAMPLE_CD_O MIMG +IMAGE_SAMPLE_CD_O_G16 MIMG +IMAGE_SAMPLE_CL MIMG +IMAGE_SAMPLE_CL_O MIMG +IMAGE_SAMPLE_C_B MIMG +IMAGE_SAMPLE_C_B_CL MIMG +IMAGE_SAMPLE_C_B_CL_O MIMG +IMAGE_SAMPLE_C_B_O MIMG +IMAGE_SAMPLE_C_CD MIMG +IMAGE_SAMPLE_C_CD_CL MIMG +IMAGE_SAMPLE_C_CD_CL_G16 MIMG +IMAGE_SAMPLE_C_CD_CL_O MIMG +IMAGE_SAMPLE_C_CD_CL_O_G16 MIMG +IMAGE_SAMPLE_C_CD_G16 MIMG +IMAGE_SAMPLE_C_CD_O MIMG +IMAGE_SAMPLE_C_CD_O_G16 MIMG +IMAGE_SAMPLE_C_CL MIMG +IMAGE_SAMPLE_C_CL_O MIMG +IMAGE_SAMPLE_C_D MIMG +IMAGE_SAMPLE_C_D_CL MIMG +IMAGE_SAMPLE_C_D_CL_G16 MIMG +IMAGE_SAMPLE_C_D_CL_O MIMG +IMAGE_SAMPLE_C_D_CL_O_G16 MIMG +IMAGE_SAMPLE_C_D_G16 MIMG +IMAGE_SAMPLE_C_D_O MIMG +IMAGE_SAMPLE_C_D_O_G16 MIMG +IMAGE_SAMPLE_C_L MIMG +IMAGE_SAMPLE_C_LZ MIMG +IMAGE_SAMPLE_C_LZ_O MIMG +IMAGE_SAMPLE_C_L_O MIMG +IMAGE_SAMPLE_C_O MIMG +IMAGE_SAMPLE_D MIMG +IMAGE_SAMPLE_D_CL MIMG +IMAGE_SAMPLE_D_CL_G16 MIMG +IMAGE_SAMPLE_D_CL_O MIMG +IMAGE_SAMPLE_D_CL_O_G16 MIMG +IMAGE_SAMPLE_D_G16 MIMG +IMAGE_SAMPLE_D_O MIMG +IMAGE_SAMPLE_D_O_G16 MIMG +IMAGE_SAMPLE_L MIMG +IMAGE_SAMPLE_LZ MIMG +IMAGE_SAMPLE_LZ_O MIMG +IMAGE_SAMPLE_L_O MIMG +IMAGE_SAMPLE_O MIMG +IMAGE_STORE MIMG +IMAGE_STORE_BY2 MIMG +IMAGE_STORE_BY4 MIMG +IMAGE_STORE_MIP MIMG +IMAGE_STORE_MIP_BY2 MIMG +IMAGE_STORE_MIP_BY4 MIMG +IMAGE_STORE_MIP_PCK MIMG +IMAGE_STORE_MIP_PCK2 MIMG +IMAGE_STORE_MIP_PCK4 MIMG +IMAGE_STORE_PCK MIMG +IMAGE_STORE_PCK2 MIMG +IMAGE_STORE_PCK4 MIMG +SCRATCH_LOAD_DWORD SCRATCH +SCRATCH_LOAD_DWORDX2 SCRATCH +SCRATCH_LOAD_DWORDX3 SCRATCH +SCRATCH_LOAD_DWORDX4 SCRATCH +SCRATCH_LOAD_SBYTE SCRATCH +SCRATCH_LOAD_SBYTE_D16 SCRATCH +SCRATCH_LOAD_SBYTE_D16_HI SCRATCH +SCRATCH_LOAD_SHORT_D16 SCRATCH +SCRATCH_LOAD_SHORT_D16_HI SCRATCH +SCRATCH_LOAD_SSHORT SCRATCH +SCRATCH_LOAD_UBYTE SCRATCH +SCRATCH_LOAD_UBYTE_D16 SCRATCH +SCRATCH_LOAD_UBYTE_D16_HI SCRATCH +SCRATCH_LOAD_USHORT SCRATCH +SCRATCH_STORE_BYTE SCRATCH +SCRATCH_STORE_BYTE_D16_HI SCRATCH +SCRATCH_STORE_DWORD SCRATCH +SCRATCH_STORE_DWORDX2 SCRATCH +SCRATCH_STORE_DWORDX3 SCRATCH +SCRATCH_STORE_DWORDX4 SCRATCH +SCRATCH_STORE_SHORT SCRATCH +SCRATCH_STORE_SHORT_D16_HI SCRATCH +S_ABSDIFF_I32 SOP2 +S_ABS_I32 SOP1 +S_ADDC_U32 SOP2 +S_ADDK_I32 SOPK +S_ADD_I32 SOP2 +S_ADD_U32 SOP2 +S_ANDN1_SAVEEXEC_B32 SOP1 +S_ANDN1_SAVEEXEC_B64 SOP1 +S_ANDN1_WREXEC_B32 SOP1 +S_ANDN1_WREXEC_B64 SOP1 +S_ANDN2_B32 SOP2 +S_ANDN2_B64 SOP2 +S_ANDN2_SAVEEXEC_B32 SOP1 +S_ANDN2_SAVEEXEC_B64 SOP1 +S_ANDN2_WREXEC_B32 SOP1 +S_ANDN2_WREXEC_B64 SOP1 +S_AND_B32 SOP2 +S_AND_B64 SOP2 +S_AND_SAVEEXEC_B32 SOP1 +S_AND_SAVEEXEC_B64 SOP1 +S_ASHR_I32 SOP2 +S_ASHR_I64 SOP2 +S_ATC_PROBE SMEM +S_ATC_PROBE_BUFFER SMEM +S_BARRIER SOPP +S_BCNT0_I32_B32 SOP1 +S_BCNT0_I32_B64 SOP1 +S_BCNT1_I32_B32 SOP1 +S_BCNT1_I32_B64 SOP1 +S_BFE_I32 SOP2 +S_BFE_I64 SOP2 +S_BFE_U32 SOP2 +S_BFE_U64 SOP2 +S_BFM_B32 SOP2 +S_BFM_B64 SOP2 +S_BITCMP0_B32 SOPC +S_BITCMP0_B64 SOPC +S_BITCMP1_B32 SOPC +S_BITCMP1_B64 SOPC +S_BITREPLICATE_B64_B32 SOP1 +S_BITSET0_B32 SOP1 +S_BITSET0_B64 SOP1 +S_BITSET1_B32 SOP1 +S_BITSET1_B64 SOP1 +S_BRANCH SOPP +S_BREV_B32 SOP1 +S_BREV_B64 SOP1 +S_BUFFER_LOAD_DWORD SMEM +S_BUFFER_LOAD_DWORDX16 SMEM +S_BUFFER_LOAD_DWORDX2 SMEM +S_BUFFER_LOAD_DWORDX4 SMEM +S_BUFFER_LOAD_DWORDX8 SMEM +S_CALL_B64 SOPK +S_CBRANCH_CDBGSYS SOPP +S_CBRANCH_CDBGSYS_AND_USER SOPP +S_CBRANCH_CDBGSYS_OR_USER SOPP +S_CBRANCH_CDBGUSER SOPP +S_CBRANCH_EXECNZ SOPP +S_CBRANCH_EXECZ SOPP +S_CBRANCH_SCC0 SOPP +S_CBRANCH_SCC1 SOPP +S_CBRANCH_VCCNZ SOPP +S_CBRANCH_VCCZ SOPP +S_CLAUSE SOPP +S_CMOVK_I32 SOPK +S_CMOV_B32 SOP1 +S_CMOV_B64 SOP1 +S_CMPK_EQ_I32 SOPK +S_CMPK_EQ_U32 SOPK +S_CMPK_GE_I32 SOPK +S_CMPK_GE_U32 SOPK +S_CMPK_GT_I32 SOPK +S_CMPK_GT_U32 SOPK +S_CMPK_LE_I32 SOPK +S_CMPK_LE_U32 SOPK +S_CMPK_LG_I32 SOPK +S_CMPK_LG_U32 SOPK +S_CMPK_LT_I32 SOPK +S_CMPK_LT_U32 SOPK +S_CMP_EQ_I32 SOPC +S_CMP_EQ_U32 SOPC +S_CMP_EQ_U64 SOPC +S_CMP_GE_I32 SOPC +S_CMP_GE_U32 SOPC +S_CMP_GT_I32 SOPC +S_CMP_GT_U32 SOPC +S_CMP_LE_I32 SOPC +S_CMP_LE_U32 SOPC +S_CMP_LG_I32 SOPC +S_CMP_LG_U32 SOPC +S_CMP_LG_U64 SOPC +S_CMP_LT_I32 SOPC +S_CMP_LT_U32 SOPC +S_CODE_END SOPP +S_CSELECT_B32 SOP2 +S_CSELECT_B64 SOP2 +S_DCACHE_INV SMEM +S_DECPERFLEVEL SOPP +S_DENORM_MODE SOPP +S_ENDPGM SOPP +S_ENDPGM_ORDERED_PS_DONE SOPP +S_ENDPGM_SAVED SOPP +S_FF0_I32_B32 SOP1 +S_FF0_I32_B64 SOP1 +S_FF1_I32_B32 SOP1 +S_FF1_I32_B64 SOP1 +S_FLBIT_I32 SOP1 +S_FLBIT_I32_B32 SOP1 +S_FLBIT_I32_B64 SOP1 +S_FLBIT_I32_I64 SOP1 +S_GETPC_B64 SOP1 +S_GETREG_B32 SOPK +S_GET_WAVEID_IN_WORKGROUP SMEM +S_GL1_INV SMEM +S_ICACHE_INV SOPP +S_INCPERFLEVEL SOPP +S_INST_PREFETCH SOPP +S_LOAD_DWORD SMEM +S_LOAD_DWORDX16 SMEM +S_LOAD_DWORDX2 SMEM +S_LOAD_DWORDX4 SMEM +S_LOAD_DWORDX8 SMEM +S_LSHL1_ADD_U32 SOP2 +S_LSHL2_ADD_U32 SOP2 +S_LSHL3_ADD_U32 SOP2 +S_LSHL4_ADD_U32 SOP2 +S_LSHL_B32 SOP2 +S_LSHL_B64 SOP2 +S_LSHR_B32 SOP2 +S_LSHR_B64 SOP2 +S_MAX_I32 SOP2 +S_MAX_U32 SOP2 +S_MEMREALTIME SMEM +S_MEMTIME SMEM +S_MIN_I32 SOP2 +S_MIN_U32 SOP2 +S_MOVK_I32 SOPK +S_MOVRELD_B32 SOP1 +S_MOVRELD_B64 SOP1 +S_MOVRELSD_2_B32 SOP1 +S_MOVRELS_B32 SOP1 +S_MOVRELS_B64 SOP1 +S_MOV_B32 SOP1 +S_MOV_B64 SOP1 +S_MULK_I32 SOPK +S_MUL_HI_I32 SOP2 +S_MUL_HI_U32 SOP2 +S_MUL_I32 SOP2 +S_NAND_B32 SOP2 +S_NAND_B64 SOP2 +S_NAND_SAVEEXEC_B32 SOP1 +S_NAND_SAVEEXEC_B64 SOP1 +S_NOP SOPP +S_NOR_B32 SOP2 +S_NOR_B64 SOP2 +S_NOR_SAVEEXEC_B32 SOP1 +S_NOR_SAVEEXEC_B64 SOP1 +S_NOT_B32 SOP1 +S_NOT_B64 SOP1 +S_ORN1_SAVEEXEC_B32 SOP1 +S_ORN1_SAVEEXEC_B64 SOP1 +S_ORN2_B32 SOP2 +S_ORN2_B64 SOP2 +S_ORN2_SAVEEXEC_B32 SOP1 +S_ORN2_SAVEEXEC_B64 SOP1 +S_OR_B32 SOP2 +S_OR_B64 SOP2 +S_OR_SAVEEXEC_B32 SOP1 +S_OR_SAVEEXEC_B64 SOP1 +S_PACK_HH_B32_B16 SOP2 +S_PACK_LH_B32_B16 SOP2 +S_PACK_LL_B32_B16 SOP2 +S_QUADMASK_B32 SOP1 +S_QUADMASK_B64 SOP1 +S_RFE_B64 SOP1 +S_ROUND_MODE SOPP +S_SCRATCH_LOAD_DWORD SMEM +S_SCRATCH_LOAD_DWORDX2 SMEM +S_SCRATCH_LOAD_DWORDX4 SMEM +S_SENDMSG SOPP +S_SENDMSGHALT SOPP +S_SETHALT SOPP +S_SETKILL SOPP +S_SETPC_B64 SOP1 +S_SETPRIO SOPP +S_SETREG_B32 SOPK +S_SETREG_IMM32_B32 SOPK +S_SEXT_I32_I16 SOP1 +S_SEXT_I32_I8 SOP1 +S_SLEEP SOPP +S_SUBB_U32 SOP2 +S_SUBVECTOR_LOOP_BEGIN SOPK +S_SUBVECTOR_LOOP_END SOPK +S_SUB_I32 SOP2 +S_SUB_U32 SOP2 +S_SWAPPC_B64 SOP1 +S_TRAP SOPP +S_TTRACEDATA SOPP +S_TTRACEDATA_IMM SOPP +S_VERSION SOPK +S_WAITCNT SOPP +S_WAITCNT_DEPCTR SOPP +S_WAITCNT_EXPCNT SOPK +S_WAITCNT_LGKMCNT SOPK +S_WAITCNT_VMCNT SOPK +S_WAITCNT_VSCNT SOPK +S_WAIT_IDLE SOPP +S_WAKEUP SOPP +S_WQM_B32 SOP1 +S_WQM_B64 SOP1 +S_XNOR_B32 SOP2 +S_XNOR_B64 SOP2 +S_XNOR_SAVEEXEC_B32 SOP1 +S_XNOR_SAVEEXEC_B64 SOP1 +S_XOR_B32 SOP2 +S_XOR_B64 SOP2 +S_XOR_SAVEEXEC_B32 SOP1 +S_XOR_SAVEEXEC_B64 SOP1 +TBUFFER_LOAD_FORMAT_D16_X MTBUF +TBUFFER_LOAD_FORMAT_D16_XY MTBUF +TBUFFER_LOAD_FORMAT_D16_XYZ MTBUF +TBUFFER_LOAD_FORMAT_D16_XYZW MTBUF +TBUFFER_LOAD_FORMAT_X MTBUF +TBUFFER_LOAD_FORMAT_XY MTBUF +TBUFFER_LOAD_FORMAT_XYZ MTBUF +TBUFFER_LOAD_FORMAT_XYZW MTBUF +TBUFFER_STORE_FORMAT_D16_X MTBUF +TBUFFER_STORE_FORMAT_D16_XY MTBUF +TBUFFER_STORE_FORMAT_D16_XYZ MTBUF +TBUFFER_STORE_FORMAT_D16_XYZW MTBUF +TBUFFER_STORE_FORMAT_X MTBUF +TBUFFER_STORE_FORMAT_XY MTBUF +TBUFFER_STORE_FORMAT_XYZ MTBUF +TBUFFER_STORE_FORMAT_XYZW MTBUF +V_ADD3_U32 VOP3 +V_ADD_CO_CI_U32 VOP2 +V_ADD_CO_U32 VOP3 +V_ADD_F16 VOP2 +V_ADD_F32 VOP2 +V_ADD_F64 VOP3 +V_ADD_LSHL_U32 VOP3 +V_ADD_NC_I16 VOP3 +V_ADD_NC_I32 VOP3 +V_ADD_NC_U16 VOP3 +V_ADD_NC_U32 VOP2 +V_ALIGNBIT_B32 VOP3 +V_ALIGNBYTE_B32 VOP3 +V_AND_B32 VOP2 +V_AND_OR_B32 VOP3 +V_ASHRREV_I16 VOP3 +V_ASHRREV_I32 VOP2 +V_ASHRREV_I64 VOP3 +V_BCNT_U32_B32 VOP3 +V_BFE_I32 VOP3 +V_BFE_U32 VOP3 +V_BFI_B32 VOP3 +V_BFM_B32 VOP3 +V_BFREV_B32 VOP1 +V_CEIL_F16 VOP1 +V_CEIL_F32 VOP1 +V_CEIL_F64 VOP1 +V_CLREXCP VOP1 +V_CMPX_CLASS_F16 VOPC +V_CMPX_CLASS_F32 VOPC +V_CMPX_CLASS_F64 VOPC +V_CMPX_EQ_F16 VOPC +V_CMPX_EQ_F32 VOPC +V_CMPX_EQ_F64 VOPC +V_CMPX_EQ_I16 VOPC +V_CMPX_EQ_I32 VOPC +V_CMPX_EQ_I64 VOPC +V_CMPX_EQ_U16 VOPC +V_CMPX_EQ_U32 VOPC +V_CMPX_EQ_U64 VOPC +V_CMPX_F_F16 VOPC +V_CMPX_F_F32 VOPC +V_CMPX_F_F64 VOPC +V_CMPX_F_I32 VOPC +V_CMPX_F_I64 VOPC +V_CMPX_F_U32 VOPC +V_CMPX_F_U64 VOPC +V_CMPX_GE_F16 VOPC +V_CMPX_GE_F32 VOPC +V_CMPX_GE_F64 VOPC +V_CMPX_GE_I16 VOPC +V_CMPX_GE_I32 VOPC +V_CMPX_GE_I64 VOPC +V_CMPX_GE_U16 VOPC +V_CMPX_GE_U32 VOPC +V_CMPX_GE_U64 VOPC +V_CMPX_GT_F16 VOPC +V_CMPX_GT_F32 VOPC +V_CMPX_GT_F64 VOPC +V_CMPX_GT_I16 VOPC +V_CMPX_GT_I32 VOPC +V_CMPX_GT_I64 VOPC +V_CMPX_GT_U16 VOPC +V_CMPX_GT_U32 VOPC +V_CMPX_GT_U64 VOPC +V_CMPX_LE_F16 VOPC +V_CMPX_LE_F32 VOPC +V_CMPX_LE_F64 VOPC +V_CMPX_LE_I16 VOPC +V_CMPX_LE_I32 VOPC +V_CMPX_LE_I64 VOPC +V_CMPX_LE_U16 VOPC +V_CMPX_LE_U32 VOPC +V_CMPX_LE_U64 VOPC +V_CMPX_LG_F16 VOPC +V_CMPX_LG_F32 VOPC +V_CMPX_LG_F64 VOPC +V_CMPX_LT_F16 VOPC +V_CMPX_LT_F32 VOPC +V_CMPX_LT_F64 VOPC +V_CMPX_LT_I16 VOPC +V_CMPX_LT_I32 VOPC +V_CMPX_LT_I64 VOPC +V_CMPX_LT_U16 VOPC +V_CMPX_LT_U32 VOPC +V_CMPX_LT_U64 VOPC +V_CMPX_NEQ_F16 VOPC +V_CMPX_NEQ_F32 VOPC +V_CMPX_NEQ_F64 VOPC +V_CMPX_NE_I16 VOPC +V_CMPX_NE_I32 VOPC +V_CMPX_NE_I64 VOPC +V_CMPX_NE_U16 VOPC +V_CMPX_NE_U32 VOPC +V_CMPX_NE_U64 VOPC +V_CMPX_NGE_F16 VOPC +V_CMPX_NGE_F32 VOPC +V_CMPX_NGE_F64 VOPC +V_CMPX_NGT_F16 VOPC +V_CMPX_NGT_F32 VOPC +V_CMPX_NGT_F64 VOPC +V_CMPX_NLE_F16 VOPC +V_CMPX_NLE_F32 VOPC +V_CMPX_NLE_F64 VOPC +V_CMPX_NLG_F16 VOPC +V_CMPX_NLG_F32 VOPC +V_CMPX_NLG_F64 VOPC +V_CMPX_NLT_F16 VOPC +V_CMPX_NLT_F32 VOPC +V_CMPX_NLT_F64 VOPC +V_CMPX_O_F16 VOPC +V_CMPX_O_F32 VOPC +V_CMPX_O_F64 VOPC +V_CMPX_TRU_F16 VOPC +V_CMPX_TRU_F32 VOPC +V_CMPX_TRU_F64 VOPC +V_CMPX_T_I32 VOPC +V_CMPX_T_I64 VOPC +V_CMPX_T_U32 VOPC +V_CMPX_T_U64 VOPC +V_CMPX_U_F16 VOPC +V_CMPX_U_F32 VOPC +V_CMPX_U_F64 VOPC +V_CMP_CLASS_F16 VOPC +V_CMP_CLASS_F32 VOPC +V_CMP_CLASS_F64 VOPC +V_CMP_EQ_F16 VOPC +V_CMP_EQ_F32 VOPC +V_CMP_EQ_F64 VOPC +V_CMP_EQ_I16 VOPC +V_CMP_EQ_I32 VOPC +V_CMP_EQ_I64 VOPC +V_CMP_EQ_U16 VOPC +V_CMP_EQ_U32 VOPC +V_CMP_EQ_U64 VOPC +V_CMP_F_F16 VOPC +V_CMP_F_F32 VOPC +V_CMP_F_F64 VOPC +V_CMP_F_I32 VOPC +V_CMP_F_I64 VOPC +V_CMP_F_U32 VOPC +V_CMP_F_U64 VOPC +V_CMP_GE_F16 VOPC +V_CMP_GE_F32 VOPC +V_CMP_GE_F64 VOPC +V_CMP_GE_I16 VOPC +V_CMP_GE_I32 VOPC +V_CMP_GE_I64 VOPC +V_CMP_GE_U16 VOPC +V_CMP_GE_U32 VOPC +V_CMP_GE_U64 VOPC +V_CMP_GT_F16 VOPC +V_CMP_GT_F32 VOPC +V_CMP_GT_F64 VOPC +V_CMP_GT_I16 VOPC +V_CMP_GT_I32 VOPC +V_CMP_GT_I64 VOPC +V_CMP_GT_U16 VOPC +V_CMP_GT_U32 VOPC +V_CMP_GT_U64 VOPC +V_CMP_LE_F16 VOPC +V_CMP_LE_F32 VOPC +V_CMP_LE_F64 VOPC +V_CMP_LE_I16 VOPC +V_CMP_LE_I32 VOPC +V_CMP_LE_I64 VOPC +V_CMP_LE_U16 VOPC +V_CMP_LE_U32 VOPC +V_CMP_LE_U64 VOPC +V_CMP_LG_F16 VOPC +V_CMP_LG_F32 VOPC +V_CMP_LG_F64 VOPC +V_CMP_LT_F16 VOPC +V_CMP_LT_F32 VOPC +V_CMP_LT_F64 VOPC +V_CMP_LT_I16 VOPC +V_CMP_LT_I32 VOPC +V_CMP_LT_I64 VOPC +V_CMP_LT_U16 VOPC +V_CMP_LT_U32 VOPC +V_CMP_LT_U64 VOPC +V_CMP_NEQ_F16 VOPC +V_CMP_NEQ_F32 VOPC +V_CMP_NEQ_F64 VOPC +V_CMP_NE_I16 VOPC +V_CMP_NE_I32 VOPC +V_CMP_NE_I64 VOPC +V_CMP_NE_U16 VOPC +V_CMP_NE_U32 VOPC +V_CMP_NE_U64 VOPC +V_CMP_NGE_F16 VOPC +V_CMP_NGE_F32 VOPC +V_CMP_NGE_F64 VOPC +V_CMP_NGT_F16 VOPC +V_CMP_NGT_F32 VOPC +V_CMP_NGT_F64 VOPC +V_CMP_NLE_F16 VOPC +V_CMP_NLE_F32 VOPC +V_CMP_NLE_F64 VOPC +V_CMP_NLG_F16 VOPC +V_CMP_NLG_F32 VOPC +V_CMP_NLG_F64 VOPC +V_CMP_NLT_F16 VOPC +V_CMP_NLT_F32 VOPC +V_CMP_NLT_F64 VOPC +V_CMP_O_F16 VOPC +V_CMP_O_F32 VOPC +V_CMP_O_F64 VOPC +V_CMP_TRU_F16 VOPC +V_CMP_TRU_F32 VOPC +V_CMP_TRU_F64 VOPC +V_CMP_T_I32 VOPC +V_CMP_T_I64 VOPC +V_CMP_T_U32 VOPC +V_CMP_T_U64 VOPC +V_CMP_U_F16 VOPC +V_CMP_U_F32 VOPC +V_CMP_U_F64 VOPC +V_CNDMASK_B32 VOP2 +V_COS_F16 VOP1 +V_COS_F32 VOP1 +V_CUBEID_F32 VOP3 +V_CUBEMA_F32 VOP3 +V_CUBESC_F32 VOP3 +V_CUBETC_F32 VOP3 +V_CVT_F16_F32 VOP1 +V_CVT_F16_I16 VOP1 +V_CVT_F16_U16 VOP1 +V_CVT_F32_F16 VOP1 +V_CVT_F32_F64 VOP1 +V_CVT_F32_I32 VOP1 +V_CVT_F32_U32 VOP1 +V_CVT_F32_UBYTE0 VOP1 +V_CVT_F32_UBYTE1 VOP1 +V_CVT_F32_UBYTE2 VOP1 +V_CVT_F32_UBYTE3 VOP1 +V_CVT_F64_F32 VOP1 +V_CVT_F64_I32 VOP1 +V_CVT_F64_U32 VOP1 +V_CVT_FLR_I32_F32 VOP1 +V_CVT_I16_F16 VOP1 +V_CVT_I32_F32 VOP1 +V_CVT_I32_F64 VOP1 +V_CVT_NORM_I16_F16 VOP1 +V_CVT_NORM_U16_F16 VOP1 +V_CVT_OFF_F32_I4 VOP1 +V_CVT_PKNORM_I16_F16 VOP3 +V_CVT_PKNORM_I16_F32 VOP3 +V_CVT_PKNORM_U16_F16 VOP3 +V_CVT_PKNORM_U16_F32 VOP3 +V_CVT_PKRTZ_F16_F32 VOP2 +V_CVT_PK_I16_I32 VOP3 +V_CVT_PK_U16_U32 VOP3 +V_CVT_PK_U8_F32 VOP3 +V_CVT_RPI_I32_F32 VOP1 +V_CVT_U16_F16 VOP1 +V_CVT_U32_F32 VOP1 +V_CVT_U32_F64 VOP1 +V_DIV_FIXUP_F16 VOP3 +V_DIV_FIXUP_F32 VOP3 +V_DIV_FIXUP_F64 VOP3 +V_DIV_FMAS_F32 VOP3 +V_DIV_FMAS_F64 VOP3 +V_DIV_SCALE_F32 VOP3 +V_DIV_SCALE_F64 VOP3 +V_DOT2C_F32_F16 VOP2 +V_DOT2_F32_F16 VOP3P +V_DOT2_I32_I16 VOP3P +V_DOT2_U32_U16 VOP3P +V_DOT4C_I32_I8 VOP2 +V_DOT4_I32_I8 VOP3P +V_DOT4_U32_U8 VOP3P +V_DOT8_I32_I4 VOP3P +V_DOT8_U32_U4 VOP3P +V_EXP_F16 VOP1 +V_EXP_F32 VOP1 +V_FFBH_I32 VOP1 +V_FFBH_U32 VOP1 +V_FFBL_B32 VOP1 +V_FLOOR_F16 VOP1 +V_FLOOR_F32 VOP1 +V_FLOOR_F64 VOP1 +V_FMAAK_F16 VOP2 +V_FMAAK_F32 VOP2 +V_FMAC_F16 VOP2 +V_FMAC_F32 VOP2 +V_FMAC_LEGACY_F32 VOP2 +V_FMAMK_F16 VOP2 +V_FMAMK_F32 VOP2 +V_FMA_F16 VOP3 +V_FMA_F32 VOP3 +V_FMA_F64 VOP3 +V_FMA_LEGACY_F32 VOP3 +V_FMA_MIXHI_F16 VOP3P +V_FMA_MIXLO_F16 VOP3P +V_FMA_MIX_F32 VOP3P +V_FRACT_F16 VOP1 +V_FRACT_F32 VOP1 +V_FRACT_F64 VOP1 +V_FREXP_EXP_I16_F16 VOP1 +V_FREXP_EXP_I32_F32 VOP1 +V_FREXP_EXP_I32_F64 VOP1 +V_FREXP_MANT_F16 VOP1 +V_FREXP_MANT_F32 VOP1 +V_FREXP_MANT_F64 VOP1 +V_INTERP_MOV_F32 VINTRP +V_INTERP_P1LL_F16 VOP3 +V_INTERP_P1LV_F16 VOP3 +V_INTERP_P1_F32 VINTRP +V_INTERP_P2_F16 VOP3 +V_INTERP_P2_F32 VINTRP +V_LDEXP_F16 VOP2 +V_LDEXP_F32 VOP3 +V_LDEXP_F64 VOP3 +V_LERP_U8 VOP3 +V_LOG_F16 VOP1 +V_LOG_F32 VOP1 +V_LSHLREV_B16 VOP3 +V_LSHLREV_B32 VOP2 +V_LSHLREV_B64 VOP3 +V_LSHL_ADD_U32 VOP3 +V_LSHL_OR_B32 VOP3 +V_LSHRREV_B16 VOP3 +V_LSHRREV_B32 VOP2 +V_LSHRREV_B64 VOP3 +V_MAC_F32 VOP2 +V_MAC_LEGACY_F32 VOP2 +V_MADAK_F32 VOP2 +V_MADMK_F32 VOP2 +V_MAD_F32 VOP3 +V_MAD_I16 VOP3 +V_MAD_I32_I16 VOP3 +V_MAD_I32_I24 VOP3 +V_MAD_I64_I32 VOP3 +V_MAD_LEGACY_F32 VOP3 +V_MAD_U16 VOP3 +V_MAD_U32_U16 VOP3 +V_MAD_U32_U24 VOP3 +V_MAD_U64_U32 VOP3 +V_MAX3_F16 VOP3 +V_MAX3_F32 VOP3 +V_MAX3_I16 VOP3 +V_MAX3_I32 VOP3 +V_MAX3_U16 VOP3 +V_MAX3_U32 VOP3 +V_MAX_F16 VOP2 +V_MAX_F32 VOP2 +V_MAX_F64 VOP3 +V_MAX_I16 VOP3 +V_MAX_I32 VOP2 +V_MAX_U16 VOP3 +V_MAX_U32 VOP2 +V_MBCNT_HI_U32_B32 VOP3 +V_MBCNT_LO_U32_B32 VOP3 +V_MED3_F16 VOP3 +V_MED3_F32 VOP3 +V_MED3_I16 VOP3 +V_MED3_I32 VOP3 +V_MED3_U16 VOP3 +V_MED3_U32 VOP3 +V_MIN3_F16 VOP3 +V_MIN3_F32 VOP3 +V_MIN3_I16 VOP3 +V_MIN3_I32 VOP3 +V_MIN3_U16 VOP3 +V_MIN3_U32 VOP3 +V_MIN_F16 VOP2 +V_MIN_F32 VOP2 +V_MIN_F64 VOP3 +V_MIN_I16 VOP3 +V_MIN_I32 VOP2 +V_MIN_U16 VOP3 +V_MIN_U32 VOP2 +V_MOVRELD_B32 VOP1 +V_MOVRELSD_2_B32 VOP1 +V_MOVRELSD_B32 VOP1 +V_MOVRELS_B32 VOP1 +V_MOV_B32 VOP1 +V_MQSAD_PK_U16_U8 VOP3 +V_MQSAD_U32_U8 VOP3 +V_MSAD_U8 VOP3 +V_MULLIT_F32 VOP3 +V_MUL_F16 VOP2 +V_MUL_F32 VOP2 +V_MUL_F64 VOP3 +V_MUL_HI_I32 VOP3 +V_MUL_HI_I32_I24 VOP2 +V_MUL_HI_U32 VOP3 +V_MUL_HI_U32_U24 VOP2 +V_MUL_I32_I24 VOP2 +V_MUL_LEGACY_F32 VOP2 +V_MUL_LO_U16 VOP3 +V_MUL_LO_U32 VOP3 +V_MUL_U32_U24 VOP2 +V_NOP VOP1 +V_NOT_B32 VOP1 +V_OR3_B32 VOP3 +V_OR_B32 VOP2 +V_PACK_B32_F16 VOP3 +V_PERMLANE16_B32 VOP3 +V_PERMLANEX16_B32 VOP3 +V_PERM_B32 VOP3 +V_PIPEFLUSH VOP1 +V_PK_ADD_F16 VOP3P +V_PK_ADD_I16 VOP3P +V_PK_ADD_U16 VOP3P +V_PK_ASHRREV_I16 VOP3P +V_PK_FMAC_F16 VOP2 +V_PK_FMA_F16 VOP3P +V_PK_LSHLREV_B16 VOP3P +V_PK_LSHRREV_B16 VOP3P +V_PK_MAD_I16 VOP3P +V_PK_MAD_U16 VOP3P +V_PK_MAX_F16 VOP3P +V_PK_MAX_I16 VOP3P +V_PK_MAX_U16 VOP3P +V_PK_MIN_F16 VOP3P +V_PK_MIN_I16 VOP3P +V_PK_MIN_U16 VOP3P +V_PK_MUL_F16 VOP3P +V_PK_MUL_LO_U16 VOP3P +V_PK_SUB_I16 VOP3P +V_PK_SUB_U16 VOP3P +V_QSAD_PK_U16_U8 VOP3 +V_RCP_F16 VOP1 +V_RCP_F32 VOP1 +V_RCP_F64 VOP1 +V_RCP_IFLAG_F32 VOP1 +V_READFIRSTLANE_B32 VOP1 +V_READLANE_B32 VOP3 +V_RNDNE_F16 VOP1 +V_RNDNE_F32 VOP1 +V_RNDNE_F64 VOP1 +V_RSQ_F16 VOP1 +V_RSQ_F32 VOP1 +V_RSQ_F64 VOP1 +V_SAD_HI_U8 VOP3 +V_SAD_U16 VOP3 +V_SAD_U32 VOP3 +V_SAD_U8 VOP3 +V_SAT_PK_U8_I16 VOP1 +V_SIN_F16 VOP1 +V_SIN_F32 VOP1 +V_SQRT_F16 VOP1 +V_SQRT_F32 VOP1 +V_SQRT_F64 VOP1 +V_SUBREV_CO_CI_U32 VOP2 +V_SUBREV_CO_U32 VOP3 +V_SUBREV_F16 VOP2 +V_SUBREV_F32 VOP2 +V_SUBREV_NC_U32 VOP2 +V_SUB_CO_CI_U32 VOP2 +V_SUB_CO_U32 VOP3 +V_SUB_F16 VOP2 +V_SUB_F32 VOP2 +V_SUB_NC_I16 VOP3 +V_SUB_NC_I32 VOP3 +V_SUB_NC_U16 VOP3 +V_SUB_NC_U32 VOP2 +V_SWAPREL_B32 VOP1 +V_SWAP_B32 VOP1 +V_TRIG_PREOP_F64 VOP3 +V_TRUNC_F16 VOP1 +V_TRUNC_F32 VOP1 +V_TRUNC_F64 VOP1 +V_WRITELANE_B32 VOP3 +V_XAD_U32 VOP3 +V_XNOR_B32 VOP2 +V_XOR3_B32 VOP3 +V_XOR_B32 VOP2