Merge remote-tracking branch 'origin/main' into feat/kernel-libc-fixes

# Conflicts:
#	core/libs/prx/libkernel/DirectMemory/DirectMemory.hpp
This commit is contained in:
OverkillLabs 2026-09-29 04:13:58 +03:00
commit 5387aef3b7
135 changed files with 12512 additions and 2398 deletions

45
.github/labeler.yml vendored Normal file
View File

@ -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/**'

16
.github/pull_request_template.md vendored Normal file
View File

@ -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 -->

29
.github/workflows/area-labels.yml vendored Normal file
View File

@ -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 }}

75
.github/workflows/build.yml vendored Normal file
View File

@ -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

21
.github/workflows/conflict-label.yml vendored Normal file
View File

@ -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 }}

42
.github/workflows/progress-comment.yml vendored Normal file
View File

@ -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

34
.github/workflows/progress-report.yml vendored Normal file
View File

@ -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

30
.github/workflows/progress.yml vendored Normal file
View File

@ -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

126
.github/workflows/release.yml vendored Normal file
View File

@ -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

3
.gitmodules vendored
View File

@ -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

1
3rdparty/freetype vendored Submodule

@ -0,0 +1 @@
Subproject commit 42608f77f20749dd6ddc9e0536788eaad70ea4b5

View File

@ -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)

View File

@ -4,21 +4,25 @@ Tool for automatic executables porting to Linux and Windows.
Includes a [relinker](core/relinker) that converts executable to the target system's native format and implementations of [system prx libraries](core/libs/prx) suitable for dynamic linking. No emulation or separate runtime process.
`relinker --to-intel` rewrites the Zen 2-only instructions a title may contain (EXTRQ, INSERTQ, MOVNTSS, MOVNTSD) at relink time, in place when an equal-length Intel encoding exists and otherwise through a jump to a generated stub section, so Intel hosts need no runtime emulation.
By default the relinker also converts the title's own `sce_module`/`sce_modules` prx files into guest modules that take priority at link time (they require eager binding). A title whose `sce_module` ships its own `libc.prx`, such as Demon's Souls (PPSA01342), is relinked with `--skip-sce-module`: its libc would replace the project's [libc.prx](core/libs/prx/libc) (guest memory arena, heap, crash reporting) and imports kernel functions that are not implemented, so the executable fails to bind at startup.
Releases will be published after the first full successful launch of at least one game.
## Status
Execution reaches `_start`, [stack unwinding](core/libs/prx/libc/src/exception/Unwind.cpp) and exception handling tables are built, reaches main. Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates.
[![libraries](https://boykopovar.github.io/AnyPS5/badge-libraries.svg)](https://boykopovar.github.io/AnyPS5/) [![shaders](https://boykopovar.github.io/AnyPS5/badge-shaders.svg)](https://boykopovar.github.io/AnyPS5/)
The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces SPIR-V, validated via [Spirv-Tools](3rdparty/SPIRV-Tools) when built with `ANYPS5_ENABLE_SPIRV_TOOLS`.
[![progress map](https://boykopovar.github.io/AnyPS5/progress.svg)](https://boykopovar.github.io/AnyPS5/)
The real game reaches the logo, main menu, and [gameplay](https://gist.github.com/user-attachments/assets/81d28e9b-c237-4545-b2ca-720071129816) with audio.
<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

View File

@ -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

18
core/libs/HitLog.hpp Normal file
View File

@ -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

View File

@ -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 {

View File

@ -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;
};
}

View File

@ -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);

View File

@ -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);

View File

@ -5,6 +5,7 @@ namespace AgcDriver {
// Starts sampling the calling thread when APS5_SAMPLE_WORKER names an output file.
void StartWorkerSampler();
void StopWorkerSampler();
}

View File

@ -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) {

View File

@ -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;

View File

@ -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;

View File

@ -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
}

View File

@ -11,6 +11,8 @@
namespace AgcDriver::Graphics {
void ShutdownGuestBufferWorkers();
struct GuestMemorySnapshot {
std::uint64_t address;
std::span<const std::byte> bytes;

View File

@ -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

View File

@ -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, &region);
}
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);

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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 {

View File

@ -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);

View File

@ -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");

View File

@ -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, &region);
region.dstOffset = elementBytes;
copy(commands, second->snapshots[0].buffer->Handle(), downloaded->Handle(), 1, &region);
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);

View File

@ -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;
}
}

View File

@ -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"

View File

@ -0,0 +1,2 @@
include(${CMAKE_CURRENT_SOURCE_DIR}/../libSceFont/FontLibrary.cmake)
add_sce_font_library(libSceFont.native)

View File

@ -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;
}
}

View File

@ -0,0 +1,2 @@
include(${CMAKE_CURRENT_SOURCE_DIR}/FontLibrary.cmake)
add_sce_font_library(libSceFont)

View File

@ -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;
}
}

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 = {};
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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()

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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__);
}
}

View File

@ -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()

View File

@ -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

View File

@ -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");
}
};

View File

@ -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();
}
}

View File

@ -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) {

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 = [] {

View File

@ -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)

View File

@ -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:

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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 +

View File

@ -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;

View File

@ -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>

View File

@ -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

View File

@ -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;
}
}

View File

@ -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));
}

View File

@ -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;

View File

@ -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;

View File

@ -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