Merge remote-tracking branch 'origin/main' into feat/kernel-libc-fixes
# Conflicts: # core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp
This commit is contained in:
commit
5387aef3b7
|
|
@ -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/**'
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
### What
|
||||
|
||||
### Tested
|
||||
<!-- OS, title or homebrew, what was checked. "Not tested" is a valid answer. -->
|
||||
|
||||
### Checklist
|
||||
<!-- Rules behind each item: https://github.com/boykopovar/AnyPS5/blob/main/docs/CONTRIBUTING.md -->
|
||||
- [ ] 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: <!-- #PR, or none -->
|
||||
- [ ] AI-assisted: <!-- yes / no -->
|
||||
|
|
@ -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<<EOF'
|
||||
gh pr list -R "$GITHUB_REPOSITORY" --base "${{ github.event.repository.default_branch }}" -L 200 --json number --jq '.[].number'
|
||||
echo EOF
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
- if: steps.prs.outputs.numbers != ''
|
||||
uses: actions/labeler@v7
|
||||
with:
|
||||
pr-number: ${{ steps.prs.outputs.numbers }}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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='<!-- progress-report -->'
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 42608f77f20749dd6ddc9e0536788eaad70ea4b5
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
26
README.md
26
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.
|
||||
[](https://boykopovar.github.io/AnyPS5/) [](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`.
|
||||
[](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.
|
||||
<sub>* 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.</sub>
|
||||
|
||||
[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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:$<TARGET_FILE_DIR:libSceRtc>")
|
||||
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:$<TARGET_FILE_DIR:libSceFont>")
|
||||
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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef CORE_LIBS_HITLOG_HPP
|
||||
#define CORE_LIBS_HITLOG_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
|
||||
// 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<int> 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
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace AgcDriver {
|
||||
|
||||
class CaptureTrace {
|
||||
public:
|
||||
static bool Enabled() {
|
||||
static const bool enabled = std::getenv("APS5_CAPTURE_TRACE") != nullptr;
|
||||
return enabled;
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
static void Log(const char* format, TArgs... args) {
|
||||
if (!Enabled()) return;
|
||||
std::array<char, 2048> line{};
|
||||
const auto size = std::snprintf(line.data(), line.size(), format, args...);
|
||||
if (size < 0 || static_cast<std::size_t>(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<std::size_t>(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::microseconds>(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<std::streamsize>(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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace AgcDriver {
|
|||
|
||||
// Starts sampling the calling thread when APS5_SAMPLE_WORKER names an output file.
|
||||
void StartWorkerSampler();
|
||||
void StopWorkerSampler();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<std::uint64_t>::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<std::uint64_t>::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<std::shared_ptr<VulkanDevice>> replacedDevices;
|
||||
replacedDevices.push_back(device);
|
||||
}
|
||||
device = std::make_shared<VulkanDevice>(&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<std::uint32_t, QueueState> queues;
|
||||
std::map<std::uint32_t, std::shared_ptr<IVideoOutput>> outputs;
|
||||
DevicePointer device;
|
||||
std::vector<std::shared_ptr<VulkanDevice>> replacedDevices;
|
||||
std::stop_token shutdownToken = LibcShutdownToken_nid_postfix();
|
||||
std::uint64_t accepted = 0;
|
||||
std::uint64_t completed = 0;
|
||||
std::set<std::uint64_t> completedOutOfOrder;
|
||||
std::exception_ptr failure;
|
||||
// Set once `failure` is, so hot loops can check for one without the mutex.
|
||||
std::atomic<bool> failed{false};
|
||||
bool stopping = false;
|
||||
std::atomic<bool> 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<const std::uint32_t> commands, const std::uint32_t* guest = nullptr) {
|
||||
std::map<std::string, std::pair<std::size_t, std::string>> 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<unsigned long long>(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<unsigned long long>(frameSerial), static_cast<unsigned long long>(submission.serial), cursor, static_cast<unsigned long long>(batchesAtFlip), static_cast<unsigned long long>(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<unsigned long long>(submission.serial), submission.queue, cursor, static_cast<unsigned long long>(color), readRegister(queue.context, 0x8e), static_cast<int>(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<unsigned long long>(color), readRegister(queue.context, 0x8e));
|
||||
}
|
||||
} catch (const std::exception& error) {
|
||||
CaptureTrace::Log("draw-error submission=%llu offset=%zu reason=%.256s", static_cast<unsigned long long>(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::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(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<AgcDriver::IVideoOutput>& output) {
|
||||
|
|
|
|||
|
|
@ -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<PFN_vkCmdPipelineBarrier>("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<std::int32_t>(sourceWidth), static_cast<std::int32_t>(sourceHeight), 1};
|
||||
blit.srcOffsets[1] = {static_cast<std::int32_t>(width), static_cast<std::int32_t>(height), 1};
|
||||
blit.dstSubresource = blit.srcSubresource;
|
||||
blit.dstOffsets[1] = blit.srcOffsets[1];
|
||||
blit.dstOffsets[1] = {static_cast<std::int32_t>(sourceWidth), static_cast<std::int32_t>(sourceHeight), 1};
|
||||
context.Function<PFN_vkCmdBlitImage>("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;
|
||||
|
|
|
|||
|
|
@ -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 <atomic>
|
||||
#include <chrono>
|
||||
#include <charconv>
|
||||
#include <condition_variable>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <SDL_loadso.h>
|
||||
|
|
@ -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<const std::byte> full);
|
||||
std::uint32_t DumpScale();
|
||||
void WriteFrameBmp(int index, std::uint32_t fullWidth, std::uint32_t fullHeight, std::span<const std::byte> 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<std::byte> 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<std::byte> pixels;
|
||||
};
|
||||
|
||||
void run() {
|
||||
try {
|
||||
for (;;) {
|
||||
std::deque<Frame> 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<Frame> 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<void> kept;
|
||||
std::unique_ptr<Graphics::Buffer> dumpBuffer;
|
||||
std::unique_ptr<PresentationScaler> 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<typename TFunction>
|
||||
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<std::byte> 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<double>(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<std::uint32_t>(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_<index>.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<const std::byte> full) {
|
||||
const auto scale = DumpScale();
|
||||
void WriteFrameBmp(int index, std::uint32_t fullWidth, std::uint32_t fullHeight, std::span<const std::byte> full, std::uint32_t scale) {
|
||||
require(scale != 0 && full.size() == static_cast<std::size_t>(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<std::byte> pixels(static_cast<std::size_t>(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<std::uint32_t>(-static_cast<std::int32_t>(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<const char*>(&magic), 2);
|
||||
file.write(reinterpret_cast<const char*>(&fileBytes), 4);
|
||||
file.write(reinterpret_cast<const char*>(header + 1), 48);
|
||||
file.write(reinterpret_cast<const char*>(pixels.data()), static_cast<std::streamsize>(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<unsigned long long>(residentPresents), static_cast<unsigned long long>(refreshedPresents), static_cast<unsigned long long>(notPending), static_cast<unsigned long long>(unsuitable), static_cast<unsigned long long>(gpuDumps));
|
||||
}
|
||||
CaptureTrace::Log("present dump=%d address=%llx width=%u height=%u resident=%d generation=%llu", dumpFrame ? state->nextDumpIndex : -1, static_cast<unsigned long long>(buffer.address), buffer.width, buffer.height, resident != nullptr, static_cast<unsigned long long>(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<unsigned long long>(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<PresentationScaler>(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<std::size_t>(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<std::size_t>(slot.dumpWidth) * slot.dumpHeight * 4;
|
||||
if (!slot.dumpBuffer || slot.dumpBuffer->Bytes().size() != dumpBytes) slot.dumpBuffer = std::make_unique<Graphics::Buffer>(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;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,11 @@
|
|||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <stop_token>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#endif
|
||||
|
|
@ -20,6 +23,9 @@ namespace AgcDriver {
|
|||
#ifdef _WIN32
|
||||
namespace {
|
||||
|
||||
std::jthread processSamplerThread;
|
||||
std::jthread workerSamplerThread;
|
||||
|
||||
// Debug aid: APS5_SAMPLE_WORKER=<file> samples the calling thread every millisecond and, every 20 s,
|
||||
// writes "module+offset self inclusive" lines to <file> for offline symbolization with nm.
|
||||
struct Sampler {
|
||||
|
|
@ -29,6 +35,10 @@ struct Sampler {
|
|||
std::map<std::uint64_t, std::pair<std::uint64_t, std::uint64_t>> 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<ProcessSampler>();
|
||||
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>();
|
||||
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
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
namespace AgcDriver::Graphics {
|
||||
|
||||
void ShutdownGuestBufferWorkers();
|
||||
|
||||
struct GuestMemorySnapshot {
|
||||
std::uint64_t address;
|
||||
std::span<const std::byte> bytes;
|
||||
|
|
|
|||
|
|
@ -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> buffer;
|
||||
};
|
||||
DescriptorCache* cache = nullptr;
|
||||
DescriptorCache::SetAllocation allocation;
|
||||
std::vector<Snapshot> snapshots;
|
||||
~DrawBindings();
|
||||
};
|
||||
std::shared_ptr<DrawBindings> 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
|
||||
|
|
|
|||
|
|
@ -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<ShaderResources::DrawBindings>& bindings, std::uint64_t target) {
|
||||
struct Sample {
|
||||
std::uint64_t address;
|
||||
std::size_t offset;
|
||||
std::vector<std::byte> expected;
|
||||
VkBuffer source;
|
||||
VkDeviceSize sourceOffset;
|
||||
};
|
||||
static unsigned long long nextDraw = 0;
|
||||
const auto draw = ++nextDraw;
|
||||
const auto batch = static_cast<unsigned long long>(recorder.Submissions() + 1);
|
||||
std::vector<Sample> 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<std::byte>(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<std::size_t>(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<unsigned long long>(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<const std::byte*>(begin));
|
||||
}
|
||||
CaptureTrace::Log("input-capture draw=%llu batch=%llu target=%llx ranges=%zu bytes=%zu", draw, batch, static_cast<unsigned long long>(target), samples.size(), total);
|
||||
if (samples.empty()) return;
|
||||
auto readback = std::make_shared<Buffer>(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<PFN_vkCmdCopyBuffer>("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<unsigned long long>(sample.address), index, std::to_integer<unsigned>(sample.expected[index]), std::to_integer<unsigned>(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<unsigned long long>(target), static_cast<unsigned long long>(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<const CompiledShader> 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);
|
||||
|
|
|
|||
|
|
@ -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 <windows.h>
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
#include <mutex>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <stop_token>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <chrono>
|
||||
|
|
@ -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<unsigned>(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<const RefreshBlock> 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<const RefreshBlock> job;
|
||||
std::uint64_t serial = 0;
|
||||
unsigned finished = 0;
|
||||
std::atomic<std::size_t> next{0};
|
||||
std::atomic<std::uint64_t> copiedBlocks{0};
|
||||
std::vector<std::jthread> 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<Region* const> 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<Region* const> 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<Region* const> 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<std::size_t>(bytes), Recorder::ReadKind::GpuCopy);
|
||||
std::vector<std::byte> expected;
|
||||
const auto address = region->begin;
|
||||
const auto batch = static_cast<unsigned long long>(recorder->Submissions() + 1);
|
||||
const bool pendingWriter = recorder->PendingWriteOverlaps(address, static_cast<std::size_t>(bytes));
|
||||
const bool writable = WritesOverlap(address, static_cast<std::size_t>(bytes));
|
||||
std::shared_ptr<Buffer> inputSnapshot;
|
||||
auto copySource = region->copySource;
|
||||
auto copyOffset = region->begin - region->copySourceBase;
|
||||
if (!pendingWriter && !writable) {
|
||||
inputSnapshot = std::make_shared<Buffer>(context, static_cast<std::size_t>(bytes), VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
std::memcpy(inputSnapshot->Bytes().data(), reinterpret_cast<const void*>(address), static_cast<std::size_t>(bytes));
|
||||
copySource = inputSnapshot->Handle();
|
||||
copyOffset = 0;
|
||||
recorder->Keep(inputSnapshot);
|
||||
CaptureTrace::Log("copy-input-snapshot batch=%llu address=%llx bytes=%llu", batch, static_cast<unsigned long long>(address), static_cast<unsigned long long>(bytes));
|
||||
}
|
||||
if (capture && bytes == 32) {
|
||||
CaptureTrace::Log("copy-input-candidate batch=%llu address=%llx bytes=%llu pendingWriter=%d writable=%d", batch, static_cast<unsigned long long>(address), static_cast<unsigned long long>(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<Buffer>(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<unsigned long long>(address), index, std::to_integer<unsigned>(expected[index]), std::to_integer<unsigned>(actual[index]));
|
||||
}
|
||||
CaptureTrace::Log("copy-input-result batch=%llu address=%llx bytes=%zu changed=%zu", batch, static_cast<unsigned long long>(address), expected.size(), changed);
|
||||
});
|
||||
}
|
||||
if (inputSnapshot == nullptr) recorder->NotePendingRead(region->begin, static_cast<std::size_t>(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
|
||||
|
|
|
|||
|
|
@ -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<void()> 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<unsigned long long>(submissions + 1), static_cast<unsigned long long>(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<unsigned long long>(submissions + 1), static_cast<unsigned long long>(address), bytes, static_cast<int>(kind));
|
||||
if (bytes == 0 || !ReadTrackingEnabled()) return;
|
||||
ensureOpen();
|
||||
open->reads.push_back({address, address + bytes, kind});
|
||||
|
|
@ -2025,6 +2028,7 @@ void Recorder::NotePendingReads(std::span<const std::pair<std::uint64_t, std::ui
|
|||
if (ranges.empty() || !ReadTrackingEnabled()) return;
|
||||
ensureOpen();
|
||||
for (const auto& [begin, end] : ranges) {
|
||||
CaptureTrace::Log("buffer-read batch=%llu address=%llx bytes=%llu kind=%d", static_cast<unsigned long long>(submissions + 1), static_cast<unsigned long long>(begin), static_cast<unsigned long long>(end - begin), static_cast<int>(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<unsigned long long>(serial), batch->reads.size(), batch->writes.size());
|
||||
VkTimelineSemaphoreSubmitInfoKHR timelineInfo{VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR};
|
||||
timelineInfo.signalSemaphoreValueCount = 1;
|
||||
timelineInfo.pSignalSemaphoreValues = &serial;
|
||||
|
|
|
|||
|
|
@ -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 <algorithm>
|
||||
|
|
@ -260,6 +261,7 @@ std::shared_ptr<StorageTexture> sampledStorageSource(const Context& context, con
|
|||
|
||||
// `guestBytes` is the surface size when the caller described the surface already (0: described here).
|
||||
std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const std::uint32_t> 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<unsigned long long>(resource.baseAddress), resource.width, resource.height, static_cast<unsigned long long>(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::DrawBindings> ShaderResources::PrepareDrawBindings(Recorder& recorder) const {
|
||||
if (_set == VK_NULL_HANDLE || usesBda) return {};
|
||||
const auto reads = guestMemory.InPlaceReads();
|
||||
auto result = std::make_shared<DrawBindings>();
|
||||
std::vector<std::size_t> 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<Buffer>(context, item.size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
|
||||
std::memcpy(buffer->Bytes().data(), reinterpret_cast<const void*>(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<unsigned long long>(recorder.Submissions() + 1), static_cast<unsigned long long>(item.address), item.size);
|
||||
}
|
||||
if (selected.empty()) return {};
|
||||
Require(context.descriptorCache != nullptr, "draw snapshots require a descriptor cache");
|
||||
std::map<VkDescriptorType, std::uint32_t> counts;
|
||||
for (const auto& binding : bindings) counts[binding.layout.descriptorType] += binding.layout.descriptorCount;
|
||||
std::vector<VkDescriptorPoolSize> 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<VkCopyDescriptorSet> 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<std::uint32_t>(copies.size()), copies.data());
|
||||
std::vector<VkDescriptorBufferInfo> infos;
|
||||
infos.reserve(selected.size());
|
||||
for (const auto& snapshot : result->snapshots) infos.push_back({snapshot.buffer->Handle(), 0, snapshot.buffer->Bytes().size()});
|
||||
std::vector<VkWriteDescriptorSet> 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<std::uint32_t>(element);
|
||||
write.descriptorCount = 1;
|
||||
write.descriptorType = binding.layout.descriptorType;
|
||||
write.pBufferInfo = &infos[static_cast<std::size_t>(found - selected.begin())];
|
||||
writes.push_back(write);
|
||||
}
|
||||
}
|
||||
update(context.device, static_cast<std::uint32_t>(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);
|
||||
|
|
|
|||
|
|
@ -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<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), static_cast<unsigned long long>(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<unsigned long long>(descriptor.baseAddress), layer, static_cast<unsigned long long>(layerGeneration[layer]), layer < stampedBlocks.size() && stampedBlocks[layer] != 0, static_cast<int>(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<bool>* layers) {
|
||||
CaptureTrace::Log("upload image=%llx bytes=%llu generation=%llu reason=%s partial=%d", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(guestBytes), static_cast<unsigned long long>(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<unsigned long long>(descriptor.baseAddress), first, count, static_cast<unsigned long long>(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<bool>& layers) {
|
||||
CaptureTrace::Log("writeback image=%llx generation=%llu reason=%s units=%zu", static_cast<unsigned long long>(descriptor.baseAddress), static_cast<unsigned long long>(generation), flushReason, static_cast<std::size_t>(std::count(layers.begin(), layers.end(), true)));
|
||||
static const bool profile = std::getenv("APS5_PROFILE_DRAW") != nullptr;
|
||||
PhaseTimer timer;
|
||||
struct Account {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,21 @@ struct State {
|
|||
AgcDriver::FlipInfo last{};
|
||||
};
|
||||
|
||||
class FlipRelease final {
|
||||
public:
|
||||
explicit FlipRelease(std::shared_ptr<State> 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> state;
|
||||
};
|
||||
|
||||
class Request final : public AgcDriver::IFlipRequest {
|
||||
public:
|
||||
explicit Request(std::shared_ptr<State> value) : state(std::move(value)) { ++state->alive; }
|
||||
|
|
@ -104,24 +119,23 @@ void testFlipAndBoundary() {
|
|||
output->state->block = true;
|
||||
output->state->checkSelfWait = true;
|
||||
}
|
||||
std::future<void> 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<Output>();
|
||||
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<std::string, 4> messages;
|
||||
std::vector<std::thread> 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);
|
||||
|
|
|
|||
|
|
@ -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<MemoryState*>(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<std::uintptr_t>(&memory.destination), "memory transfer resolved an unrelated destination");
|
||||
if (address == reinterpret_cast<std::uintptr_t>(&memory.destination)) {
|
||||
memory.written = true;
|
||||
} else {
|
||||
check(address == reinterpret_cast<std::uintptr_t>(&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");
|
||||
|
|
|
|||
|
|
@ -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<ShaderResources::DrawBindings> 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<void*>(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<void*>(element), 0x33, elementBytes);
|
||||
auto downloaded = std::make_shared<Buffer>(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<PFN_vkCmdCopyBuffer>("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);
|
||||
|
|
|
|||
|
|
@ -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 <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#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<std::int32_t>(0x806a0001u);
|
||||
constexpr std::int32_t kErrOperationFailed = static_cast<std::int32_t>(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<std::size_t>(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<double>(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<std::uint8_t*> frames;
|
||||
std::mutex lock;
|
||||
};
|
||||
|
||||
Player* Check(void* handle) {
|
||||
Player* p = static_cast<Player*>(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<unsigned> fired[64];
|
||||
const auto slot = static_cast<unsigned>(event) < 64 ? static_cast<unsigned>(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::uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(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<unsigned long long>(CurrentFrame(*p)),
|
||||
static_cast<unsigned long long>(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<unsigned>(p.numFramebuffers) : 2u;
|
||||
if (requested < 2u) requested = 2u;
|
||||
if (requested > 8u) requested = 8u;
|
||||
const auto planeBytes = static_cast<std::size_t>(kFramePitch) * kFrameHeight;
|
||||
for (unsigned i = 0; i < requested; ++i) {
|
||||
auto* frame = static_cast<std::uint8_t*>(
|
||||
p.allocTexture(p.memObject, 0x100, static_cast<std::uint32_t>(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<unsigned long long>(kFrameBytes), kFramePitch,
|
||||
reinterpret_cast<void*>(p.allocTexture), static_cast<void*>(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<unsigned long long>(p.framesDelivered),
|
||||
static_cast<unsigned long long>(kFrameBudget),
|
||||
static_cast<unsigned long long>(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<float>(kFrameWidth) / static_cast<float>(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<float>(kFrameWidth) / static_cast<float>(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<void*>(p), tid.str().c_str(), p->playing ? 1 : 0,
|
||||
p->paused ? 1 : 0, p->stopFired ? 1 : 0, p->haveSource ? 1 : 0,
|
||||
static_cast<unsigned long long>(p->framesDelivered), static_cast<unsigned long long>(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<EventCallback>(d->eventCallback);
|
||||
p->autoStart = d->autoStart != 0;
|
||||
p->memObject = d->memObject;
|
||||
p->allocTexture = reinterpret_cast<TextureAllocator>(d->allocTexture);
|
||||
p->deallocTexture = reinterpret_cast<TextureFree>(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<unsigned long long>(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<const InitPrefix*>(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<const std::uint8_t*>(init_ex);
|
||||
*handle = CreateFrom(base != nullptr ? reinterpret_cast<const InitPrefix*>(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<const char* const*>(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<std::uint8_t*> 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<unsigned long long>(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<std::uint8_t*>(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<float>(kFrameWidth) / static_cast<float>(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<std::uint8_t*>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,177 +1 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#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"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
include(${CMAKE_CURRENT_SOURCE_DIR}/../libSceFont/FontLibrary.cmake)
|
||||
add_sce_font_library(libSceFont.native)
|
||||
|
|
@ -1,520 +0,0 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
include(${CMAKE_CURRENT_SOURCE_DIR}/FontLibrary.cmake)
|
||||
add_sce_font_library(libSceFont)
|
||||
|
|
@ -1,520 +0,0 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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 <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#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<std::uint64_t, 2> layout_seed_vec;
|
||||
std::array<std::uint64_t, 2> 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
|
||||
|
|
@ -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 <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
|
||||
#include <ft2build.h>
|
||||
#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<std::int32_t>::min();
|
||||
return static_cast<std::int32_t>(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<std::int32_t>(static_cast<std::uint64_t>(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<std::int32_t>(value >> 16);
|
||||
}
|
||||
|
||||
inline std::int64_t TruncMulUnits(std::int64_t fixed16, std::uint16_t units) {
|
||||
const std::int64_t product = fixed16 * static_cast<std::int64_t>(units);
|
||||
std::int64_t rounded = product;
|
||||
if (product < 0) rounded = product + 0xFFFFLL;
|
||||
return static_cast<std::int64_t>(static_cast<std::int32_t>(rounded >> 16));
|
||||
}
|
||||
|
||||
inline float FixedMulUnitsToF26Dot6(std::int64_t fixed16, std::uint16_t unitsPerEm) {
|
||||
const std::int64_t product = fixed16 * static_cast<std::int64_t>(unitsPerEm);
|
||||
std::int64_t rounded = product + 0xFFFF;
|
||||
if (product >= 0) rounded = product;
|
||||
return static_cast<float>(rounded >> 16) * ONE_OVER_64;
|
||||
}
|
||||
|
||||
inline int FloorToInt(float value) {
|
||||
int result = static_cast<int>(std::trunc(value));
|
||||
if (static_cast<float>(result) > value) --result;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline int CeilToInt(float value) {
|
||||
int result = static_cast<int>(std::trunc(value));
|
||||
if (static_cast<float>(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<FT_ULong>(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<FT_F26Dot6>(65535 * 64);
|
||||
return std::clamp(value, static_cast<FT_F26Dot6>(-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<double>(std::llabs(static_cast<long long>(charW))) / 64.0;
|
||||
const double requestH = static_cast<double>(std::llabs(static_cast<long long>(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<double>::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<double>(strike.x_ppem) / 64.0);
|
||||
const double strikeH = std::max(1.0, static_cast<double>(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
|
||||
|
|
@ -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 <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include <ft2build.h>
|
||||
#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<unsigned char> 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<FontGlyphOutlinePoint> outlinePoints;
|
||||
std::vector<std::uint8_t> outlineTags;
|
||||
std::vector<std::uint16_t> 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<unsigned char> 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
|
||||
|
|
@ -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 <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
constexpr int SCE_FONT_OK = 0;
|
||||
constexpr int SCE_FONT_ERROR_FATAL = static_cast<int>(0x80460001);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_PARAMETER = static_cast<int>(0x80460002);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_MEMORY = static_cast<int>(0x80460003);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_LIBRARY = static_cast<int>(0x80460004);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_FONT_HANDLE = static_cast<int>(0x80460005);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_GLYPH = static_cast<int>(0x80460006);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_RENDERER = static_cast<int>(0x80460007);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_TEXT_SOURCE = static_cast<int>(0x80460008);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_STRING = static_cast<int>(0x80460009);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_WRITING = static_cast<int>(0x8046000A);
|
||||
constexpr int SCE_FONT_ERROR_INVALID_WORDS = static_cast<int>(0x8046000B);
|
||||
constexpr int SCE_FONT_ERROR_ALLOCATION_FAILED = static_cast<int>(0x80460010);
|
||||
constexpr int SCE_FONT_ERROR_FS_OPEN_FAILED = static_cast<int>(0x80460011);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_LIBRARY = static_cast<int>(0x80460018);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_FORMAT = static_cast<int>(0x80460019);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_FUNCTION = static_cast<int>(0x80460020);
|
||||
constexpr int SCE_FONT_ERROR_ALREADY_SPECIFIED = static_cast<int>(0x80460021);
|
||||
constexpr int SCE_FONT_ERROR_ALREADY_ATTACHED = static_cast<int>(0x80460022);
|
||||
constexpr int SCE_FONT_ERROR_ALREADY_OPENED = static_cast<int>(0x80460023);
|
||||
constexpr int SCE_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER = static_cast<int>(0x80460025);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_FONTSET = static_cast<int>(0x80460031);
|
||||
constexpr int SCE_FONT_ERROR_FONT_OPEN_MAX = static_cast<int>(0x80460033);
|
||||
constexpr int SCE_FONT_ERROR_FONT_OPEN_FAILED = static_cast<int>(0x80460036);
|
||||
constexpr int SCE_FONT_ERROR_FONT_CLOSE_FAILED = static_cast<int>(0x80460037);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY = static_cast<int>(0x80460040);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_CODE = static_cast<int>(0x80460041);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_GLYPH = static_cast<int>(0x80460042);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_SCRIPT = static_cast<int>(0x80460043);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_LANGUAGE = static_cast<int>(0x80460044);
|
||||
constexpr int SCE_FONT_ERROR_NO_SUPPORT_SURFACE = static_cast<int>(0x80460050);
|
||||
constexpr int SCE_FONT_ERROR_UNSET_PARAMETER = static_cast<int>(0x80460058);
|
||||
constexpr int SCE_FONT_ERROR_FUNCTIONAL_LIMIT = static_cast<int>(0x8046005C);
|
||||
constexpr int SCE_FONT_ERROR_ALREADY_BOUND_RENDERER = static_cast<int>(0x80460060);
|
||||
constexpr int SCE_FONT_ERROR_NOT_BOUND_RENDERER = static_cast<int>(0x80460061);
|
||||
constexpr int SCE_FONT_ERROR_RENDERER_ALLOCATION_FAILED = static_cast<int>(0x80460063);
|
||||
constexpr int SCE_FONT_ERROR_RENDERER_ALLOCATION_LIMITED = static_cast<int>(0x80460064);
|
||||
constexpr int SCE_FONT_ERROR_RENDERER_RENDER_FAILED = static_cast<int>(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
|
||||
|
|
@ -0,0 +1,522 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include <ft2build.h>
|
||||
#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<float>(std::bit_cast<std::uint32_t>(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<std::int32_t>(static_cast<std::uint32_t>(obj->layout_seed_pair & 0xFFFFFFFFu));
|
||||
const auto seedHigh = static_cast<std::int32_t>(static_cast<std::uint32_t>(obj->layout_seed_pair >> 32));
|
||||
obj->layout_seed_vec[0] = static_cast<std::uint64_t>(static_cast<std::int64_t>(seedLow));
|
||||
obj->layout_seed_vec[1] = static_cast<std::uint64_t>(static_cast<std::int64_t>(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<const RenderSurfaceSystemUse*>(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<float>(left) - x;
|
||||
result->ImageMetrics.bearingY = static_cast<float>(top) - y;
|
||||
result->ImageMetrics.advance = static_cast<float>(advance) - x;
|
||||
int stride = advance;
|
||||
if (advanceF != static_cast<float>(advance)) {
|
||||
const int candidate = static_cast<int>((static_cast<float>(right) - rightF) + advanceF);
|
||||
stride = right + 1;
|
||||
if (advanceF <= rightF) stride = candidate;
|
||||
if (stride < candidate) stride = candidate;
|
||||
}
|
||||
result->ImageMetrics.stride = static_cast<float>(stride) - x;
|
||||
result->ImageMetrics.width = static_cast<std::uint32_t>(std::max(0, right - left));
|
||||
result->ImageMetrics.height = static_cast<std::uint32_t>(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<FT_Face>(obj.ft_face);
|
||||
if (!face || !face->size) return SCE_FONT_ERROR_NO_SUPPORT_GLYPH;
|
||||
FT_Vector delta{};
|
||||
delta.x = static_cast<FT_Pos>(static_cast<std::int32_t>((x - std::floor(x)) * 64.0f));
|
||||
delta.y = static_cast<FT_Pos>(-static_cast<std::int32_t>((y - std::floor(y)) * 64.0f));
|
||||
if (obj.shift_units_x != 0 || obj.shift_units_y != 0) {
|
||||
delta.x += static_cast<FT_Pos>(RoundMulFixed(obj.shift_units_x, static_cast<std::int64_t>(face->size->metrics.x_scale)));
|
||||
delta.y += static_cast<FT_Pos>(RoundMulFixed(obj.shift_units_y, static_cast<std::int64_t>(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<int>(slot->bitmap.width);
|
||||
const int glyphH = static_cast<int>(slot->bitmap.rows);
|
||||
metrics->width = static_cast<float>(slot->metrics.width) / 64.0f;
|
||||
metrics->height = static_cast<float>(slot->metrics.height) / 64.0f;
|
||||
metrics->Horizontal.bearingX = static_cast<float>(slot->metrics.horiBearingX) / 64.0f;
|
||||
metrics->Horizontal.bearingY = static_cast<float>(slot->metrics.horiBearingY) / 64.0f;
|
||||
metrics->Horizontal.advance = static_cast<float>(slot->metrics.horiAdvance) / 64.0f;
|
||||
std::vector<std::uint8_t> bitmap(static_cast<std::size_t>(glyphW) * static_cast<std::size_t>(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<std::ptrdiff_t>(row) * pitch;
|
||||
std::uint8_t* targetRow = bitmap.data() + static_cast<std::size_t>(row) * static_cast<std::size_t>(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<std::size_t>(glyphW));
|
||||
}
|
||||
}
|
||||
}
|
||||
const int destX = static_cast<int>(std::floor(x)) + slot->bitmap_left;
|
||||
const int destY = static_cast<int>(std::floor(y)) - slot->bitmap_top;
|
||||
const int clipX0 = std::clamp(static_cast<int>(surface->sc_x0), 0, surface->width);
|
||||
const int clipY0 = std::clamp(static_cast<int>(surface->sc_y0), 0, surface->height);
|
||||
const int clipX1 = std::clamp(static_cast<int>(surface->sc_x1), 0, surface->width);
|
||||
const int clipY1 = std::clamp(static_cast<int>(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<std::uint8_t*>(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<std::size_t>(row - destY) * static_cast<std::size_t>(glyphW);
|
||||
std::uint8_t* targetRow = targetBase + static_cast<std::size_t>(row) * static_cast<std::size_t>(surface->widthByte);
|
||||
for (int column = startColumn; column < endColumn; ++column) {
|
||||
const std::uint8_t coverage = sourceRow[column - destX];
|
||||
std::memset(targetRow + static_cast<std::size_t>(column) * static_cast<std::size_t>(bytesPerPixel), coverage, static_cast<std::size_t>(bytesPerPixel));
|
||||
}
|
||||
}
|
||||
}
|
||||
result->stage = nullptr;
|
||||
result->SurfaceImage.widthByte = static_cast<std::uint32_t>(surface->widthByte);
|
||||
result->SurfaceImage.pixelSizeByte = static_cast<std::uint8_t>(surface->pixelSizeByte);
|
||||
result->SurfaceImage.pixelFormat = 0;
|
||||
result->SurfaceImage.pad16 = 0;
|
||||
result->UpdateRect.x = static_cast<std::uint32_t>(std::max(updateX, 0));
|
||||
result->UpdateRect.y = static_cast<std::uint32_t>(std::max(updateY, 0));
|
||||
result->UpdateRect.w = static_cast<std::uint32_t>(std::max(updateW, 0));
|
||||
result->UpdateRect.h = static_cast<std::uint32_t>(std::max(updateH, 0));
|
||||
result->SurfaceImage.address = static_cast<std::uint8_t*>(surface->buffer) + static_cast<std::size_t>(result->UpdateRect.y) * static_cast<std::size_t>(surface->widthByte) + static_cast<std::size_t>(result->UpdateRect.x) * static_cast<std::size_t>(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<FontLibNative*>(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<std::int32_t>(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<FontLibNative*>(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<std::int32_t>(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<FT_F26Dot6>(static_cast<std::int32_t>(scaleW * 64.0f));
|
||||
const auto charH = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(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<double>(std::llabs(static_cast<long long>(requested))) / static_cast<double>(std::llabs(static_cast<long long>(applied)));
|
||||
return std::isfinite(value) && value > 0.0 ? static_cast<float>(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<float>(slot->metrics.width) / 64.0f * ratioX;
|
||||
metrics->height = static_cast<float>(slot->metrics.height) / 64.0f * ratioY;
|
||||
metrics->Horizontal.bearingX = static_cast<float>(slot->metrics.horiBearingX) / 64.0f * ratioX;
|
||||
metrics->Horizontal.bearingY = static_cast<float>(slot->metrics.horiBearingY) / 64.0f * ratioY;
|
||||
metrics->Horizontal.advance = static_cast<float>(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<FontLibNative*>(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<std::uint32_t>(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<FT_F26Dot6>(static_cast<std::int32_t>(glyph.glyph.scale_x * 64.0f));
|
||||
const auto charH = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(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<float>(outline.points[i].x) / 64.0f, static_cast<float>(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<std::uint16_t>(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<std::int16_t>(glyph.outlinePoints.size());
|
||||
glyph.outline.contours_cnt = static_cast<std::int16_t>(glyph.outlineContours.size());
|
||||
glyph.outlineInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
#include "prx/libSceFont/include/FontInternal.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Font;
|
||||
|
||||
std::uint32_t EffectiveEdition(std::uint64_t edition) {
|
||||
return static_cast<std::uint32_t>(edition >> 32);
|
||||
}
|
||||
|
||||
std::uint32_t* AcquireDeviceCache(FontLibNative* library) {
|
||||
auto* const busy = reinterpret_cast<std::uint32_t*>(std::numeric_limits<std::uintptr_t>::max());
|
||||
std::atomic_ref<std::uint32_t*> 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<std::uint32_t*>(library->device_cache_buf).store(cache, std::memory_order_release);
|
||||
}
|
||||
|
||||
void* AcquireRendererSelection(RendererNative* renderer) {
|
||||
auto* const busy = reinterpret_cast<void*>(std::numeric_limits<std::uintptr_t>::max());
|
||||
std::atomic_ref<void*> 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<FontCtxHeader*>(ctx);
|
||||
header->lock_word = 0;
|
||||
header->max_entries = entries;
|
||||
header->base = entries != 0 ? static_cast<std::uint8_t*>(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<FontLibNative*>(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<std::int8_t>(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<FontLibNative*>(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<const SysDriver*>(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<FontLibNative*>(*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<const RendererSelection*>(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<RendererNative*>(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<void*>(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<RendererNative*>(*pRenderer);
|
||||
if (!renderer || renderer->magic != RENDERER_MAGIC) return SCE_FONT_ERROR_INVALID_RENDERER;
|
||||
const auto* selection = static_cast<const RendererSelection*>(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<RendererNative*>(fontRenderer);
|
||||
if (!renderer || renderer->magic != RENDERER_MAGIC) return SCE_FONT_ERROR_INVALID_RENDERER;
|
||||
*size = static_cast<std::uint32_t>(renderer->workspace_size);
|
||||
return SCE_FONT_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI sceFontRendererResetOutlineBuffer(FontRenderer fontRenderer) {
|
||||
auto* renderer = static_cast<RendererNative*>(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<std::size_t>(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<RendererNative*>(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<std::uint64_t>(renderer->workspace_size), static_cast<std::uint64_t>(basalSize));
|
||||
if (limitSize != 0) desiredSize = std::min(desiredSize, static_cast<std::uint64_t>(limitSize));
|
||||
if (desiredSize == 0) desiredSize = 0x4000;
|
||||
if (!renderer->workspace || renderer->workspace_size != desiredSize) {
|
||||
void* workspace = renderer->alloc_fn(renderer->alloc_ctx, static_cast<std::uint32_t>(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<FontLibNative*>(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<std::uint32_t*>(buffer);
|
||||
std::uint32_t owned = 0;
|
||||
if (!header) {
|
||||
header = static_cast<std::uint32_t*>(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<FontLibNative*>(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<FontLibNative*>(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
|
||||
|
|
@ -0,0 +1,509 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "prx/libSceFont/include/FontInternal.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Font;
|
||||
|
||||
constexpr std::array<std::uint32_t, 88> 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<FontHandle>(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<std::uint16_t>(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<FontCtxEntry*>(header->base);
|
||||
std::uint32_t entryIndex = 0xFFFFFFFFu;
|
||||
if (header->max_entries != 0 && entries) {
|
||||
const auto uniqueId = static_cast<std::uint32_t>(request.uniqueId);
|
||||
const auto address = request.matchAddress ? reinterpret_cast<std::uint64_t>(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<std::uint32_t>(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<FontCtxHeader*>(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<std::uint32_t>(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<FontLibNative*>(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<unsigned char>& bytes) {
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
if (!file) return false;
|
||||
bytes.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
|
||||
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<FontLibNative*>(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<const unsigned char*>(fontAddress);
|
||||
LoadStateFace(ResetState(*pFontHandle), std::vector<unsigned char>(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<FontLibNative*>(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<unsigned char> 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<FontLibNative*>(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<FontCtxHeader*>(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<FontHandle>(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<std::uint16_t>((font->flags & 0xFF0Fu) | (owned ? HANDLE_OWNED : 0u));
|
||||
auto* entries = static_cast<FontCtxEntry*>(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<std::int32_t>(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<FontObj*>(*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<std::uint32_t>(*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<FontLibNative*>(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<FontLibNative*>(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<const RendererNative*>(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<const RendererNative*>(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
|
||||
|
|
@ -0,0 +1,449 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
|
||||
#include <ft2build.h>
|
||||
#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::uint16_t>(std::lround(std::min(value, static_cast<float>(std::numeric_limits<std::uint16_t>::max()))));
|
||||
}
|
||||
|
||||
std::uint8_t* LayoutCacheBytes(FontHandleNative* font) {
|
||||
return reinterpret_cast<std::uint8_t*>(&font->cached_style) + offsetof(CachedStyle, layout_cache_bytes);
|
||||
}
|
||||
|
||||
RenderSurfaceSystemUse* SurfaceSystemUse(FontRenderSurface* surface) {
|
||||
return reinterpret_cast<RenderSurfaceSystemUse*>(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<std::uint8_t>(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<std::uint8_t>(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<FT_F26Dot6>(static_cast<std::int32_t>(state->scaleW * 64.0f));
|
||||
const auto charH = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(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<float>(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<std::int16_t>(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::uint32_t>(std::max(widthPixel, 0));
|
||||
const auto height = static_cast<std::uint32_t>(std::max(heightPixel, 0));
|
||||
renderSurface->buffer = buffer;
|
||||
renderSurface->widthByte = bufWidthByte;
|
||||
renderSurface->pixelSizeByte = static_cast<std::int8_t>(pixelSizeByte);
|
||||
renderSurface->pad0 = 0;
|
||||
renderSurface->styleFlag = 0;
|
||||
renderSurface->pad2 = 0;
|
||||
renderSurface->width = static_cast<std::int32_t>(width);
|
||||
renderSurface->height = static_cast<std::int32_t>(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<std::uint32_t>(renderSurface->width);
|
||||
if (surfaceW != 0) {
|
||||
std::uint32_t x1;
|
||||
std::uint32_t left;
|
||||
auto width = static_cast<std::uint32_t>(w);
|
||||
if (x0 < 0) {
|
||||
x1 = width + static_cast<std::uint32_t>(x0);
|
||||
if (surfaceW < x1) x1 = surfaceW;
|
||||
if (width <= static_cast<std::uint32_t>(-x0)) x1 = 0;
|
||||
left = 0;
|
||||
} else {
|
||||
x1 = surfaceW;
|
||||
left = surfaceW;
|
||||
if (static_cast<std::uint32_t>(x0) <= surfaceW) {
|
||||
if (surfaceW < width) width = surfaceW;
|
||||
x1 = width + static_cast<std::uint32_t>(x0);
|
||||
left = static_cast<std::uint32_t>(x0);
|
||||
if (surfaceW < x1) x1 = surfaceW;
|
||||
}
|
||||
}
|
||||
renderSurface->sc_x0 = left;
|
||||
renderSurface->sc_x1 = x1;
|
||||
}
|
||||
const auto surfaceH = static_cast<std::uint32_t>(renderSurface->height);
|
||||
if (surfaceH == 0) return;
|
||||
std::uint32_t top;
|
||||
std::uint32_t y1 = surfaceH;
|
||||
auto height = static_cast<std::uint32_t>(h);
|
||||
if (y0 < 0) {
|
||||
top = 0;
|
||||
if (height <= static_cast<std::uint32_t>(-y0)) {
|
||||
renderSurface->sc_y0 = 0;
|
||||
renderSurface->sc_y1 = 0;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (surfaceH < static_cast<std::uint32_t>(y0)) {
|
||||
renderSurface->sc_y0 = surfaceH;
|
||||
renderSurface->sc_y1 = y1;
|
||||
return;
|
||||
}
|
||||
top = static_cast<std::uint32_t>(y0);
|
||||
if (surfaceH < height) height = surfaceH;
|
||||
}
|
||||
const std::uint32_t candidate = height + static_cast<std::uint32_t>(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<std::uint8_t>(~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<std::uint64_t>(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<std::uint16_t>(~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<GeneratedGlyph*>(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
|
||||
|
|
@ -0,0 +1,433 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "prx/libSceFont/include/FontInternal.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex stateMutex;
|
||||
std::unordered_map<FontHandle, std::unique_ptr<Font::FontState>> fontStates;
|
||||
std::mutex glyphMutex;
|
||||
std::unordered_set<FontGlyph> generatedGlyphs;
|
||||
|
||||
FT_Library HostFreeTypeLibrary() {
|
||||
static const FT_Library library = [] {
|
||||
FT_Library created = nullptr;
|
||||
if (FT_Init_FreeType(&created) != 0) return static_cast<FT_Library>(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<std::uint32_t>(word).load(std::memory_order_acquire);
|
||||
if (magic != expectedMagic) return false;
|
||||
if (static_cast<std::int32_t>(current) >= 0) {
|
||||
std::uint32_t expected = current;
|
||||
if (std::atomic_ref<std::uint32_t>(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<std::uint32_t>(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<std::uint32_t>(word).load(std::memory_order_acquire);
|
||||
if (static_cast<std::int32_t>(current) >= 0) {
|
||||
std::uint32_t expected = current;
|
||||
if (std::atomic_ref<std::uint32_t>(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<FontHandleNative*>(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<FontHandle*>(std::numeric_limits<std::uintptr_t>::max());
|
||||
std::atomic_ref<FontHandle*> 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<FontHandle*>(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<FontCtxHeader*>(ctx);
|
||||
if (index >= header->max_entries || !header->base) {
|
||||
*outLockWord = 0;
|
||||
*outObject = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
auto* entry = static_cast<FontCtxEntry*>(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<FontObj*>(*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<FontLibNative*>(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<std::int32_t>(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<FontState>();
|
||||
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<unsigned char> 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<FT_Long>(state.faceData.size()), static_cast<FT_Long>(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<GeneratedGlyph*>(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<std::uint16_t>(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<std::int16_t>(glyph.outlinePoints.size());
|
||||
glyph.outline.contours_cnt = static_cast<std::int16_t>(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<float>(style->dpi_x) / POINTS_PER_INCH;
|
||||
*w = value;
|
||||
}
|
||||
if (h) {
|
||||
float value = style->scale_h;
|
||||
if (points && style->dpi_y != 0) value *= static_cast<float>(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<float>(style->dpi_x);
|
||||
*w = value;
|
||||
}
|
||||
if (h) {
|
||||
float value = style->scale_h;
|
||||
if (pixels && style->dpi_y != 0) value *= POINTS_PER_INCH / static_cast<float>(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<std::uint8_t>(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<std::uint32_t>(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 = {};
|
||||
}
|
||||
|
|
@ -0,0 +1,303 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "prx/libSceFont/include/FontInternal.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace Font;
|
||||
|
||||
template<typename Setter>
|
||||
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<typename Setter>
|
||||
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<typename Getter>
|
||||
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<typename Getter>
|
||||
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<std::uint8_t>(~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<std::uint8_t>(~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<std::uint8_t>(~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<float>(styleFrame->hDpi) / POINTS_PER_INCH) : styleFrame->scalePixelW;
|
||||
if (h) *h = points && styleFrame->vDpi != 0 ? styleFrame->scalePixelH * (static_cast<float>(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<float>(styleFrame->hDpi)) : styleFrame->scalePixelW;
|
||||
if (h) *h = pixels && styleFrame->vDpi != 0 ? styleFrame->scalePixelH * (POINTS_PER_INCH / static_cast<float>(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
|
||||
|
|
@ -0,0 +1,633 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
|
||||
#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<std::uint32_t> refCount{1};
|
||||
std::vector<FontTextCharacter> 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<FontStringData*>(fontString);
|
||||
return data && data->magic == FONT_STRING_MAGIC ? data : nullptr;
|
||||
}
|
||||
|
||||
FontWritingData* GetWritingData(FontWriting* fontWriting) {
|
||||
if (!fontWriting) return nullptr;
|
||||
auto* data = reinterpret_cast<FontWritingData*>(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<std::uint16_t>(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<std::uint64_t>(IsWhitespaceCode(code) ? 0x0E : 0) << 8;
|
||||
if (IsFormatCode(code)) character.flags |= CHARACTER_FLAG_FORMAT;
|
||||
return character;
|
||||
}
|
||||
|
||||
void LinkCharacters(std::vector<FontTextCharacter>& 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<std::uint32_t>((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<const std::uint8_t*>(textAddress) + textSizeByte : nullptr;
|
||||
textSource->systemUse0 = (static_cast<std::uint64_t>(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<void*>(textAddress);
|
||||
textSource->systemUse[1] = const_cast<void*>(end);
|
||||
textSource->systemUse[2] = reinterpret_cast<void*>(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<FontTextParseFunction>(textSource->systemUse[2]);
|
||||
textSource->textObject = textSource->systemUse[3];
|
||||
textSource->defaultFont = static_cast<FontHandle>(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<std::int32_t>(WRITING_FORM_HORIZONTAL) || writingForm > static_cast<std::int32_t>(WRITING_FORM_HORIZONTAL_LTR)) return SCE_FONT_ERROR_INVALID_PARAMETER;
|
||||
textSource->systemUse0 = (textSource->systemUse0 & 0xFFFFFFFFull) | (static_cast<std::uint64_t>(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<std::uint32_t>(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<std::uint32_t>(data->storage->characters.size());
|
||||
data->renderCount = data->textCount;
|
||||
*pFontString = reinterpret_cast<FontString>(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<int>(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<std::uint32_t>(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<std::uint32_t>(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<int>(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<std::size_t>(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<std::uint32_t>(writing->currentIndex);
|
||||
letterStep->Components.glyphsCount = 1;
|
||||
letterStep->Components.glyphsIndex = static_cast<std::uint32_t>(writing->currentIndex);
|
||||
letterStep->Components.characterTextCount = static_cast<std::uint8_t>(std::max<std::uint32_t>(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
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -1,20 +1,116 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,711 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
#include <ft2build.h>
|
||||
#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<FtLibraryContext*>(memory->user);
|
||||
if (!ctx || !ctx->iface || !ctx->iface->alloc) return nullptr;
|
||||
return ctx->iface->alloc(ctx->allocCtx, static_cast<std::uint32_t>(size));
|
||||
}
|
||||
|
||||
void FtFree(FT_Memory memory, void* block) {
|
||||
if (!memory || !block) return;
|
||||
auto* ctx = static_cast<FtLibraryContext*>(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<FtLibraryContext*>(memory->user);
|
||||
if (!ctx || !ctx->iface) return nullptr;
|
||||
if (ctx->iface->realloc_fn) return ctx->iface->realloc_fn(ctx->allocCtx, block, static_cast<std::uint32_t>(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::size_t>(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<float>(value) * ONE_OVER_64;
|
||||
}
|
||||
|
||||
FT_Face FaceOf(const FontObj* obj) {
|
||||
return static_cast<FT_Face>(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<FtLibraryContext*>(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<FT_Memory>(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<FtLibraryContext*>(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<const char*>(fontAddress)[0] == '\0') return SCE_FONT_ERROR_INVALID_PARAMETER;
|
||||
} else {
|
||||
return SCE_FONT_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
auto* ctx = static_cast<FtLibraryContext*>(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<const FT_Byte*>(fontAddress), static_cast<FT_Long>(fontSize), static_cast<FT_Long>(subFontIndex), &face);
|
||||
} else {
|
||||
error = FT_New_Face(ctx->library, static_cast<const char*>(fontAddress), static_cast<FT_Long>(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<FontObj*>(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<FtLibraryContext*>(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<float>(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<const TT_OS2*>(FT_Get_Sfnt_Table(face, FT_SFNT_OS2))) {
|
||||
*outMetric = static_cast<std::uint16_t>(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<FT_F26Dot6>(static_cast<std::int32_t>(scaleX * 64.0f));
|
||||
const auto charH = static_cast<FT_F26Dot6>(static_cast<std::int32_t>(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<std::int64_t>(face->size->metrics.x_scale), units);
|
||||
*outScaleY = FixedMulUnitsToF26Dot6(static_cast<std::int64_t>(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<std::int64_t>(face->size->metrics.y_scale);
|
||||
const auto xScale = static_cast<std::int64_t>(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<std::int64_t>(static_cast<std::uint16_t>(face->units_per_EM));
|
||||
std::int32_t yMinPx = RoundMulFixed(static_cast<std::int64_t>(face->bbox.yMin) + yShift, yScale);
|
||||
std::int32_t yMaxPx = RoundMulFixed(static_cast<std::int64_t>(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<float>(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<float>(unitsScaledY)) / 2;
|
||||
outEffectHeight = static_cast<float>(halfEffectHeightPx) * ONE_OVER_64;
|
||||
yMinPx -= halfEffectHeightPx;
|
||||
yMaxPx += halfEffectHeightPx;
|
||||
}
|
||||
if (slant != 0.0f) {
|
||||
const auto shear = static_cast<std::int64_t>(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<float>(leftAdjustPx) * ONE_OVER_64);
|
||||
StoreFloat(outWords, HORIZONTAL_HALF_EFFECT_WIDTH, static_cast<float>(halfEffectWidthPx) * ONE_OVER_64);
|
||||
const std::int32_t xMinPx = RoundMulFixed(static_cast<std::int64_t>(face->bbox.xMin) + xShift, xScale);
|
||||
const std::int32_t xMaxPx = RoundMulFixed(static_cast<std::int64_t>(face->bbox.xMax) + xShift, xScale);
|
||||
StoreFloat(outWords, HORIZONTAL_LINE_ADVANCE, static_cast<float>(yMaxPx - yMinPx) * ONE_OVER_64);
|
||||
StoreFloat(outWords, HORIZONTAL_BASELINE, static_cast<float>(yMaxPx) * ONE_OVER_64);
|
||||
StoreFloat(outWords, HORIZONTAL_X_BOUND_LO, static_cast<float>(xMinPx + leftAdjustPx) * ONE_OVER_64);
|
||||
StoreFloat(outWords, HORIZONTAL_X_BOUND_HI, static_cast<float>(xMaxPx + halfEffectWidthPx) * ONE_OVER_64);
|
||||
const std::int32_t maxAdvanceWidthPx = RoundMulFixed(static_cast<std::int64_t>(face->max_advance_width) + xShift, xScale);
|
||||
StoreFloat(outWords, HORIZONTAL_MAX_ADVANCE_WIDTH, static_cast<float>(maxAdvanceWidthPx) * ONE_OVER_64);
|
||||
float caretRiseAdjust = 0.0f;
|
||||
if (const auto* hhea = static_cast<const TT_HoriHeader*>(FT_Get_Sfnt_Table(face, FT_SFNT_HHEA))) {
|
||||
const std::int64_t caretRiseUnits = xShift + static_cast<std::int64_t>(hhea->caret_Slope_Rise);
|
||||
const std::int32_t caretRisePx = TruncFixed(caretRiseUnits * xScale);
|
||||
caretRiseAdjust = static_cast<float>(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<std::int64_t>(face->size->metrics.x_scale);
|
||||
const auto yScale = static_cast<std::int64_t>(face->size->metrics.y_scale);
|
||||
const auto* vhea = static_cast<const TT_VertHeader*>(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<std::uint16_t>(face->units_per_EM);
|
||||
std::int64_t yAscender = 0;
|
||||
std::int64_t yDescender = 0;
|
||||
if (vhea) {
|
||||
yAscender = static_cast<std::int64_t>(vhea->Ascender) + yShift;
|
||||
yDescender = static_cast<std::int64_t>(vhea->Descender) + yShift;
|
||||
}
|
||||
const std::int32_t scaledLeft = RoundMulFixed(static_cast<std::int64_t>(face->bbox.xMin) + xShift, xScale);
|
||||
const std::int32_t scaledRight = RoundMulFixed(static_cast<std::int64_t>(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<std::int32_t>(TruncMulUnits(xScale, units));
|
||||
const std::int32_t effect = TruncateFloatToInt(effectWidth * static_cast<float>(unitsScaled)) / 2;
|
||||
outEffectWidth = static_cast<float>(effect) * ONE_OVER_64;
|
||||
}
|
||||
if (effectHeight != 0.0f) {
|
||||
const auto unitsScaled = static_cast<std::int32_t>(TruncMulUnits(yScale, units));
|
||||
const std::int32_t delta = TruncateFloatToInt(effectHeight * static_cast<float>(unitsScaled)) / 2;
|
||||
yAscender -= delta;
|
||||
yDescender += delta;
|
||||
}
|
||||
float outSlantA = 0.0f;
|
||||
float outSlantB = 0.0f;
|
||||
if (slant != 0.0f) {
|
||||
const auto shear = static_cast<std::int64_t>(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<std::uint64_t>(roundedMax);
|
||||
const auto roundedMinBits = static_cast<std::uint64_t>(roundedMin);
|
||||
const std::uint64_t roundedMaxHigh = roundedMaxBits >> 16;
|
||||
const bool maxRoundBitClear = ((roundedMaxBits >> 15) & 1u) == 0;
|
||||
std::uint64_t selectedHigh = roundedMaxHigh;
|
||||
auto selectedLow = static_cast<std::int32_t>(roundedMinBits >> 16);
|
||||
if (maxRoundBitClear && roundedMaxHigh != 0) {
|
||||
selectedHigh = roundedMinBits >> 16;
|
||||
selectedLow = static_cast<std::int32_t>(roundedMaxBits >> 16);
|
||||
}
|
||||
outSlantA = static_cast<float>(selectedLow) * ONE_OVER_64;
|
||||
outSlantB = static_cast<float>(static_cast<std::int32_t>(selectedHigh)) * ONE_OVER_64;
|
||||
}
|
||||
StoreFloat(outWords, VERTICAL_COLUMN_ADVANCE, static_cast<float>(xAbsMax - xMinScaled) * ONE_OVER_64);
|
||||
StoreFloat(outWords, VERTICAL_BASELINE_OFFSET_X, static_cast<float>(-xAbsMax) * ONE_OVER_64);
|
||||
StoreFloat(outWords, VERTICAL_METRICS_0X08, static_cast<float>(RoundMulFixed(yAscender, xScale)) * ONE_OVER_64);
|
||||
StoreFloat(outWords, VERTICAL_METRICS_0X0C, static_cast<float>(RoundMulFixed(yDescender, xScale)) * ONE_OVER_64);
|
||||
const std::int32_t advanceHeight = RoundMulFixed(static_cast<std::int64_t>(face->max_advance_height) + yShift, yScale);
|
||||
StoreFloat(outWords, VERTICAL_ADVANCE_HEIGHT, static_cast<float>(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<std::int32_t>(static_cast<std::uint32_t>(obj->layout_seed_pair & 0xFFFFFFFFu));
|
||||
const auto seedHigh = static_cast<std::int32_t>(static_cast<std::uint32_t>(obj->layout_seed_pair >> 32));
|
||||
obj->layout_seed_vec[0] = static_cast<std::uint64_t>(static_cast<std::int64_t>(seedLow));
|
||||
obj->layout_seed_vec[1] = static_cast<std::uint64_t>(static_cast<std::int64_t>(seedHigh));
|
||||
}
|
||||
|
||||
void WriteOutlineSizes(FT_GlyphSlot slot, std::uint64_t* outWords) {
|
||||
const auto contours = static_cast<std::uint32_t>(slot->outline.n_contours);
|
||||
const auto points = static_cast<std::uint32_t>(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<std::uint8_t*>(outWords);
|
||||
WriteU16(out, 0, static_cast<std::uint16_t>(contours));
|
||||
WriteU16(out, 2, static_cast<std::uint16_t>(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<std::uint16_t>(face->units_per_EM);
|
||||
const bool cachedMatch = obj->cached_glyph_index_0x64 == static_cast<std::int32_t>(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<std::int32_t>(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<std::int64_t>(face->size->metrics.x_scale);
|
||||
const auto yScale = static_cast<std::int64_t>(face->size->metrics.y_scale);
|
||||
obj->cached_units_x_0x68 = static_cast<std::uint64_t>(TruncMulUnits(xScale, unitsPerEm));
|
||||
obj->cached_units_y_0x70 = static_cast<std::uint64_t>(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<std::int32_t>(static_cast<std::uint32_t>(obj->layout_seed_pair & 0xFFFFFFFFu));
|
||||
const auto seedHigh = static_cast<std::int32_t>(static_cast<std::uint32_t>(obj->layout_seed_pair >> 32));
|
||||
obj->layout_seed_vec[0] = static_cast<std::uint64_t>(static_cast<std::int64_t>(RoundMulFixed(seedLow, xScale)));
|
||||
obj->layout_seed_vec[1] = static_cast<std::uint64_t>(static_cast<std::int64_t>(RoundMulFixed(seedHigh, yScale)));
|
||||
obj->layout_scale_vec[0] = static_cast<std::uint64_t>(xScale);
|
||||
obj->layout_scale_vec[1] = static_cast<std::uint64_t>(yScale);
|
||||
}
|
||||
WriteOutlineSizes(face->glyph, outWords);
|
||||
return SCE_FONT_OK;
|
||||
}
|
||||
|
||||
std::int64_t RoundMulFixed64(std::int64_t value, std::int64_t fixed16) {
|
||||
return static_cast<std::int64_t>(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<std::uint8_t>((static_cast<std::uint32_t>(face->face_flags) >> 5) & 1u);
|
||||
const bool hasSeedVector = obj->layout_seed_vec[0] != 0 || obj->layout_seed_vec[1] != 0;
|
||||
outParams[2] = static_cast<std::uint8_t>(0xF0u | (hasSeedVector ? 0x08u : 0u) | faceFlagBit);
|
||||
outParams[3] = 0;
|
||||
WriteU16(outParams, 0x04, static_cast<std::uint16_t>(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<float>(slot->metrics.vertBearingX - slot->metrics.horiBearingX);
|
||||
styleShiftY = static_cast<float>(-(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<std::int32_t>(obj->shift_cache_x);
|
||||
const auto shiftY = static_cast<std::int32_t>(obj->shift_cache_y);
|
||||
const auto seedX = static_cast<std::int32_t>(obj->layout_seed_vec[0]);
|
||||
const auto seedY = static_cast<std::int32_t>(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<float>(static_cast<std::int64_t>(obj->cached_units_x_0x68)) * ONE_OVER_64);
|
||||
StoreFloat(outParams, 0x40, static_cast<float>(static_cast<std::int64_t>(obj->cached_units_y_0x70)) * ONE_OVER_64);
|
||||
WriteU32(outParams, 0x44, 0);
|
||||
auto xScale = static_cast<std::int64_t>(face->size->metrics.x_scale);
|
||||
auto yScale = static_cast<std::int64_t>(face->size->metrics.y_scale);
|
||||
const auto objScaleX = static_cast<std::int64_t>(obj->layout_scale_vec[0]);
|
||||
const auto objScaleY = static_cast<std::int64_t>(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<std::int64_t>(slot->metrics.width);
|
||||
const auto heightRaw = static_cast<std::int64_t>(slot->metrics.height);
|
||||
const auto horiBearingXRaw = static_cast<std::int64_t>(slot->metrics.horiBearingX);
|
||||
const auto horiAdvanceRaw = static_cast<std::int64_t>(slot->metrics.horiAdvance);
|
||||
const auto vertBearingXRaw = static_cast<std::int64_t>(slot->metrics.vertBearingX);
|
||||
const auto vertBearingYRaw = static_cast<std::int64_t>(slot->metrics.vertBearingY);
|
||||
const auto vertAdvanceRaw = static_cast<std::int64_t>(slot->metrics.vertAdvance);
|
||||
const std::int64_t horiAdvanceUnscaled = seedX + horiAdvanceRaw;
|
||||
const std::int64_t vertAdvanceUnscaled = seedY + vertAdvanceRaw;
|
||||
slot->metrics.horiAdvance = static_cast<FT_Pos>(horiAdvanceUnscaled);
|
||||
slot->metrics.vertAdvance = static_cast<FT_Pos>(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<std::uint32_t>(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<FT_Pos>(horiBearingXRaw - horiAdvanceUnscaled / 2);
|
||||
vertBearingX = left - horiAdvance / 2;
|
||||
StoreFloat(outParams, 0x20, static_cast<float>(-(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<const FontHandleNative*>(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<std::int64_t>(TruncateFloatToInt(slantRatio * 65536.0f));
|
||||
std::int64_t minX = std::numeric_limits<std::int64_t>::max();
|
||||
std::int64_t maxX = std::numeric_limits<std::int64_t>::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<std::int64_t>(point.x) + shiftX, xScale);
|
||||
const std::int64_t py = scaleIf(static_cast<std::int64_t>(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<std::uint8_t*>(inoutState)[3] |= 0x80;
|
||||
obj->glyph_cfg_word_0x130 = *inParams;
|
||||
obj->glyph_cfg_mode_0x134 = static_cast<std::uint8_t>(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<RendererFt*>(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<RendererFt*>(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;
|
||||
}
|
||||
|
|
@ -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:$<TARGET_FILE_DIR:libSceMouse>")
|
||||
endif()
|
||||
endif()
|
||||
|
|
@ -1,36 +1,25 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#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<const MouseOpenParam*>(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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBSCEMOUSE_MOUSE_STRUCTS_H
|
||||
#define CORE_LIBS_PRX_LIBSCEMOUSE_MOUSE_STRUCTS_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#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<int>(0x80df0001u);
|
||||
constexpr int MOUSE_ERROR_INVALID_HANDLE = static_cast<int>(0x80df0003u);
|
||||
constexpr int MOUSE_ERROR_ALREADY_OPENED = static_cast<int>(0x80df0004u);
|
||||
constexpr int MOUSE_ERROR_NOT_INITIALIZED = static_cast<int>(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
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
#include "prx/libSceMouse/include/MouseState.hpp"
|
||||
#include "prx/libkernel/Time/include/Time.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
|
||||
namespace {
|
||||
std::mutex mouseMutex;
|
||||
std::array<MouseData, MOUSE_MAX_DATA_NUM> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 <cstdlib>
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
@ -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 <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
|
||||
#include "SceTypes.hpp"
|
||||
#include "HitLog.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#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<int>(0x80B80003u);
|
||||
constexpr int kErrNotFinished = static_cast<int>(0x80B80005u);
|
||||
constexpr int kErrBusy = static_cast<int>(0x80B80007u);
|
||||
constexpr int kErrNotRunning = static_cast<int>(0x80B8000Bu);
|
||||
constexpr int kErrArgNull = static_cast<int>(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<std::uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
|
||||
return reinterpret_cast<std::uintptr_t>(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<const std::uint8_t*>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,339 +1,588 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#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<int> 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<int>(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<int, std::shared_ptr<Epoll>> g_epolls;
|
||||
std::mutex g_mutex;
|
||||
std::condition_variable g_cv;
|
||||
std::map<int, Sock> g_socks;
|
||||
std::set<int> g_epolls;
|
||||
std::set<int> g_pools;
|
||||
std::set<int> 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<int>(0x80410100u | static_cast<unsigned>(error));
|
||||
int* errno_slot() {
|
||||
static thread_local int e = 0;
|
||||
return &e;
|
||||
}
|
||||
|
||||
static std::shared_ptr<Epoll> 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<std::string, int> hits;
|
||||
std::lock_guard<std::mutex> 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<std::uint16_t>((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<std::mutex>& 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<Epoll>();
|
||||
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<const uint8_t*>(src);
|
||||
if (std::snprintf(dst, size, "%u.%u.%u.%u", bytes[0], bytes[1], bytes[2], bytes[3]) >= static_cast<int>(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<uint8_t*>(dst);
|
||||
for (int index = 0; index < 4; ++index) {
|
||||
if (parts[index] > 255) return 0;
|
||||
bytes[index] = static_cast<uint8_t>(parts[index]);
|
||||
}
|
||||
return 1;
|
||||
int* APS5_VABI sceNetErrnoLoc(void) {
|
||||
return errno_slot();
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetInit_nid_postfix(void) {
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<const std::uint8_t*>(addr) + 2, 2);
|
||||
std::memcpy(&ip, static_cast<const std::uint8_t*>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<const std::uint8_t*>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::uint8_t>(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<std::uint32_t>(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<std::mutex> 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<std::mutex> lk(g_mutex);
|
||||
const int id = g_next_epoll++;
|
||||
g_epolls.insert(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetEpollDestroy(int eid) {
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::uint8_t>(a), static_cast<std::uint8_t>(b), static_cast<std::uint8_t>(c), static_cast<std::uint8_t>(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<const std::uint8_t*>(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<uint32_t>(n) >= size) {
|
||||
*errno_slot() = 28; // ENOSPC
|
||||
return nullptr;
|
||||
}
|
||||
std::memcpy(dst, tmp, static_cast<std::size_t>(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<std::mutex> lk(g_mutex);
|
||||
const int id = g_next_resolver++;
|
||||
g_resolvers.insert(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
int APS5_VABI sceNetResolverDestroy(int rid) {
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,173 @@
|
|||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#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<int>(0x80770001u);
|
||||
constexpr int RUDP_ERROR_ALREADY_INITIALIZED = static_cast<int>(0x80770002u);
|
||||
constexpr int RUDP_ERROR_INVALID_CONTEXT_ID = static_cast<int>(0x80770003u);
|
||||
constexpr int RUDP_ERROR_INVALID_ARGUMENT = static_cast<int>(0x80770004u);
|
||||
constexpr int RUDP_ERROR_CONN_RESET = static_cast<int>(0x80770009u);
|
||||
constexpr int RUDP_ERROR_CONN_REFUSED = static_cast<int>(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<int, bool> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lk(g_mutex);
|
||||
return g_inited ? 0 : RUDP_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
int APS5_VABI sceRudpSetEventHandler(GuestRudpEventHandler handler, void* arg) {
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<SaveDataEvent> g_events;
|
||||
static std::map<std::uint32_t, std::vector<std::uint8_t>> 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<std::uint8_t>& memory) {
|
||||
std::filesystem::create_directories(save_root());
|
||||
std::ofstream file(memory_path(slot), std::ios::binary | std::ios::trunc);
|
||||
file.write(reinterpret_cast<const char*>(memory.data()), static_cast<std::streamsize>(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<user>/slot<n>.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<int>(user_id), static_cast<unsigned>(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<std::size_t>(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<char>& 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<std::streamsize>(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<char>& out) {
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
if (!f) {
|
||||
return false;
|
||||
}
|
||||
f.seekg(0, std::ios::end);
|
||||
const auto n = static_cast<std::size_t>(f.tellg());
|
||||
f.seekg(0);
|
||||
out.resize(n);
|
||||
if (n != 0) {
|
||||
f.read(out.data(), static_cast<std::streamsize>(n));
|
||||
}
|
||||
return static_cast<bool>(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<std::mutex> 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<std::streamoff>(d->offset));
|
||||
f.read(static_cast<char*>(d->buf), static_cast<std::streamsize>(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<char> 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<std::mutex> 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<std::streamoff>(d.offset));
|
||||
f.write(static_cast<const char*>(d.buf), static_cast<std::streamsize>(d.buf_size));
|
||||
}
|
||||
f.flush();
|
||||
if (!f) {
|
||||
return SAVE_DATA_ERROR_INTERNAL;
|
||||
}
|
||||
}
|
||||
if (set_param->param != nullptr) {
|
||||
std::vector<char> 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<std::mutex> 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<char>(file), std::istreambuf_iterator<char>());
|
||||
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<char> 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<char> 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<const std::int32_t*>(sync_param);
|
||||
const std::uint32_t slot_id = reinterpret_cast<const std::uint32_t*>(sync_param)[1];
|
||||
std::lock_guard<std::mutex> 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) {
|
||||
|
|
|
|||
|
|
@ -6,15 +6,18 @@
|
|||
#include <string>
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,53 +1,94 @@
|
|||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#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<int> 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<unsigned>(rc));
|
||||
return rc;
|
||||
}
|
||||
int APS5_VABI sceSaveDataTerminate(void) {
|
||||
const int rc = savedata::terminate_internal();
|
||||
SAVEDATA_TRACE("sceSaveDataTerminate -> 0x%08x", static_cast<unsigned>(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<unsigned>(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<unsigned>(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<int>(get_param->user_id),
|
||||
static_cast<unsigned>(get_param->slot_id), static_cast<unsigned>(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<int>(set_param->user_id), static_cast<unsigned>(set_param->slot_id),
|
||||
static_cast<unsigned>(set_param->data_num), static_cast<unsigned>(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<unsigned>(rc),
|
||||
result != nullptr ? result->existed_memory_size : static_cast<std::size_t>(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<const savedata::MemorySync*>(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<int>(sync->user_id),
|
||||
static_cast<unsigned>(sync->slot_id), static_cast<unsigned>(sync->option),
|
||||
static_cast<unsigned>(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<unsigned>(rc));
|
||||
return rc;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,55 +1,67 @@
|
|||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#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<SaveDataDialogResult*>(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__);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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");
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<uintptr_t>(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<void>(eq);
|
||||
static_cast<void>(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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AgcDriver::FrameTiming>& 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<AgcDriver::FrameTiming>& 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<uintptr_t>(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<VideoOutConfig> 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<uintptr_t>(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<uint64_t>::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<FlipRequest> 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<std::shared_ptr<FlipRequest>> 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<std::shared_ptr<FlipRequest>> cancelled;
|
||||
{
|
||||
std::lock_guard lock(flipQueue->mutex);
|
||||
cancelled.swap(flipQueue->requests);
|
||||
}
|
||||
}
|
||||
AgcDriverShutdown_nid_postfix();
|
||||
window.Destroy();
|
||||
}
|
||||
|
||||
void VideoOutDriver::vblankLoop(std::stop_token token) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_SHUTDOWN_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_SHUTDOWN_HPP
|
||||
|
||||
#include <stop_token>
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
#include "prx/libc/include/ApplicationHeap.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#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<char*>(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<char, decltype(&std::free)> owner(text, std::free);
|
||||
const char* source = text;
|
||||
#endif
|
||||
if (count >= 0) {
|
||||
auto* output = static_cast<char*>(ApplicationHeapAllocate_nid_no_patch(static_cast<std::size_t>(count) + 1));
|
||||
if (output) {
|
||||
std::memcpy(output, source, static_cast<std::size_t>(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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -212,6 +212,31 @@ void* APS5_VABI sceLibcMspaceMemalign_nid_postfix(void* handle, std::size_t alig
|
|||
return Allocate(Find(handle), size, std::max<std::size_t>(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<std::size_t>(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<std::uintptr_t>(pointer) - reinterpret_cast<std::uintptr_t>(block + 1);
|
||||
if (size <= block->capacity - padding && (reinterpret_cast<std::uintptr_t>(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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <stdexcept>
|
||||
#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<bool> exitRequested{false};
|
||||
std::atomic<bool> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cerrno>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
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<std::size_t>(-1);
|
||||
}
|
||||
if (destination) destination[count] = static_cast<char>(value);
|
||||
if (value == 0) break;
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
|
@ -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 = [] {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4,32 +4,20 @@
|
|||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#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);
|
||||
|
|
|
|||
|
|
@ -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<uint64_t>(std::numeric_limits<KernelEqueue>::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::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(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<uint64_t>(ts->tv_sec) * 1000000000ULL +
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <cerrno>
|
||||
|
|
@ -9,8 +10,6 @@
|
|||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
static constexpr int SCE_KERNEL_ERROR_ENOENT = -2147352574;
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
|
|
|
|||
|
|
@ -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<int>(0x80020000u | static_cast<unsigned>(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
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
#include "prx/libc/include/General.hpp"
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
#include "prx/libkernel/Pthread/include/Pthread.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libkernel/KernelErrors.hpp"
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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 <algorithm>
|
||||
#include <cstdlib>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<int>(0x80020016);
|
||||
constexpr int KERNEL_SEMA_ERROR_EACCES = static_cast<int>(0x8002000D);
|
||||
constexpr int KERNEL_SEMA_ERROR_EBUSY = static_cast<int>(0x80020010);
|
||||
constexpr int KERNEL_SEMA_ERROR_ETIMEDOUT = static_cast<int>(0x8002003C);
|
||||
|
||||
struct KernelSemaPrivate {
|
||||
KernelSemaPrivate(std::int32_t initCount, std::int32_t maxCount, std::string name, bool isFifo);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue